feat(player): the OS media controls, the basic keys and resume for the library and shared-link players
Plex, library and /watch tell the operating system what plays (title, line, picture) and take its
play / pause / skip / seek through one hook (lib/mediaSession); the Plex player drives it with its own
seek and clock, so the lock screen shows the film's position, not the stream's. The YouTube player is
left alone: its iframe already publishes all of it, and the OS routes to the frame that plays.
The library and /watch players get the basic keys (lib/mediaKeys, the keymap's mediaPlayer rows) and
open where the viewer stopped (lib/resume): the library keeps the point on the server, per user and
library item (library_states, POST /api/downloads/{id}/progress, listed back on both listings), the
shared link in this browser. A started library row continues, its menu can start it over, and its
picture carries the cards' progress bar. The resume rule (too early / near the end = from the start)
is one function shared with the YouTube route.
The library dialog opens focused on its panel (Modal initialFocus "player"): on the ✕, Space closed
the film. While the media element itself has the focus, Space and the arrows stay its own controls'
— the browser acts on them whatever a listener does, and one Space played and paused.
This commit is contained in:
@@ -56,6 +56,7 @@ import { HlsEngine } from "../lib/hlsEngine";
|
||||
import { createHlsPort } from "../lib/hlsPort";
|
||||
import { enabled, log } from "../lib/log";
|
||||
import { logMediaElement } from "../lib/mediaLog";
|
||||
import { useMediaSession } from "../lib/mediaSession";
|
||||
import { UpNextCard } from "./UpNextCard";
|
||||
import Toaster from "./ui/Toaster";
|
||||
import { useOverlayOrder } from "./ui/overlayOrder";
|
||||
@@ -578,6 +579,30 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false }
|
||||
);
|
||||
const togglePlay = useCallback(() => requestPlay(!engine.shownPlaying()), [engine, requestPlay]);
|
||||
|
||||
// The OS's media controls (lock screen, headset, media keys) drive the player through its own seek
|
||||
// and play intent and read its own clock: the element's starts at the stream's start (a transcode
|
||||
// begins at the resume point), and a seek outside the stream restarts it.
|
||||
const [videoEl, setVideoEl] = useState<HTMLVideoElement | null>(null);
|
||||
useMediaSession(
|
||||
videoEl,
|
||||
detail && {
|
||||
title:
|
||||
detail.kind === "episode" && detail.season_number != null
|
||||
? `S${detail.season_number}·E${detail.episode_number} — ${detail.title}`
|
||||
: detail.title,
|
||||
artist:
|
||||
detail.kind === "episode" ? detail.show_title : detail.year ? String(detail.year) : null,
|
||||
artwork: api.plexImageUrl(id),
|
||||
},
|
||||
{
|
||||
play: () => requestPlay(true),
|
||||
pause: () => requestPlay(false),
|
||||
seekTo: (s) => seekTo(s),
|
||||
position: () => engine.playhead(),
|
||||
duration: () => durationRef.current,
|
||||
},
|
||||
);
|
||||
|
||||
// Volume/mute for the HTML5 <video> (0..1, independent mute), persisted + re-applied per session.
|
||||
const { setVol, nudgeVolume, toggleMute, onWheel } = usePlayerVolume({
|
||||
videoRef,
|
||||
@@ -1095,6 +1120,7 @@ export default function PlexPlayer({ itemId, onClose, queue, fromStart = false }
|
||||
<video
|
||||
ref={(el) => {
|
||||
videoRef.current = el;
|
||||
setVideoEl(el);
|
||||
// So Brave iOS's background play keeps it going when the app is left (lib/browserBackgroundPlay).
|
||||
exposeVideoToBackgroundPlay(el);
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user