diff --git a/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx b/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx index cab5f22a8..c566eb1b3 100644 --- a/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx +++ b/ui/v2.5/src/components/ScenePlayer/ScenePlayer.tsx @@ -22,6 +22,7 @@ import "./vtt-thumbnails"; import "./big-buttons"; import "./track-activity"; import "./vrmode"; +import "./media-session"; import cx from "classnames"; import { useSceneSaveActivity, @@ -397,6 +398,7 @@ export const ScenePlayer: React.FC = PatchComponent( pauseBeforeLooping: false, createButtons: uiConfig?.showAbLoopControls ?? false, }, + mediaSession: {}, }, }; @@ -874,6 +876,21 @@ export const ScenePlayer: React.FC = PatchComponent( return () => player.off("ended"); }, [getPlayer, onComplete]); + // set up mediaSession plugin + useEffect(() => { + const player = getPlayer(); + if (!player) return; + + // set up mediasession plugin + player + .mediaSession() + .setMetadata( + scene?.title ?? "Stash", + scene?.studio?.name ?? "Stash", + scene.paths.screenshot || "" + ); + }, [getPlayer, scene]); + function onScrubberScroll() { if (started.current) { getPlayer()?.pause(); diff --git a/ui/v2.5/src/components/ScenePlayer/media-session.ts b/ui/v2.5/src/components/ScenePlayer/media-session.ts new file mode 100644 index 000000000..7be1d0d4e --- /dev/null +++ b/ui/v2.5/src/components/ScenePlayer/media-session.ts @@ -0,0 +1,71 @@ +import videojs, { VideoJsPlayer } from "video.js"; + +class MediaSessionPlugin extends videojs.getPlugin("plugin") { + constructor(player: VideoJsPlayer) { + super(player); + + player.ready(() => { + player.addClass("vjs-media-session"); + this.setActionHandlers(); + }); + + player.on("play", () => { + this.updatePlaybackState(); + }); + + player.on("pause", () => { + this.updatePlaybackState(); + }); + this.updatePlaybackState(); + } + + // manually set poster since it's only set on useEffect + public setMetadata(title: string, studioName: string, poster: string): void { + if ("mediaSession" in navigator) { + navigator.mediaSession.metadata = new MediaMetadata({ + title, + artist: studioName, + artwork: [ + { + src: poster || this.player.poster() || "", + type: "image/jpeg", + }, + ], + }); + } + } + + private updatePlaybackState(): void { + if ("mediaSession" in navigator) { + const playbackState = this.player.paused() ? "paused" : "playing"; + navigator.mediaSession.playbackState = playbackState; + } + } + + private setActionHandlers(): void { + // method initialization + navigator.mediaSession.setActionHandler("play", () => { + this.player.play(); + }); + navigator.mediaSession.setActionHandler("pause", () => { + this.player.pause(); + }); + navigator.mediaSession.setActionHandler("nexttrack", () => { + this.player.skipButtons()?.handleForward(); + }); + navigator.mediaSession.setActionHandler("previoustrack", () => { + this.player.skipButtons()?.handleBackward(); + }); + } +} + +videojs.registerPlugin("mediaSession", MediaSessionPlugin); + +/* eslint-disable @typescript-eslint/naming-convention */ +declare module "video.js" { + interface VideoJsPlayer { + mediaSession: () => MediaSessionPlugin; + } +} + +export default MediaSessionPlugin;