feat(player): implement media session ipc

This commit is contained in:
Tim 2026-05-02 05:46:22 +02:00
parent a83788ef67
commit 82484d032d
2 changed files with 56 additions and 16 deletions

View file

@ -27,6 +27,10 @@ export type WindowState = {
state: number;
};
export type MediaStatus = {
paused: boolean;
};
const createId = () => Math.floor(Math.random() * 9999) + 1;
const useShell = () => {

View file

@ -1,4 +1,6 @@
import { useEffect } from 'react';
import { useShell } from 'stremio/common';
import { MediaStatus } from 'stremio/common/useShell';
const useMediaSession = (
videoState: VideoState,
@ -7,20 +9,31 @@ const useMediaSession = (
onPauseRequested: () => void,
onNextVideoRequested: () => void,
) => {
useEffect(() => {
if (!navigator.mediaSession) return;
const shell = useShell();
// Playback state
useEffect(() => {
const playbackState = !videoState.paused ? 'playing' : 'paused';
navigator.mediaSession.playbackState = playbackState;
if (navigator.mediaSession) {
navigator.mediaSession.playbackState = playbackState;
}
if (shell.active) {
shell.send('media.status', {
paused: !!videoState.paused,
});
}
return () => {
navigator.mediaSession.playbackState = 'none';
if (navigator.mediaSession) {
navigator.mediaSession.playbackState = 'none';
}
};
}, [videoState.paused]);
// Metadata
useEffect(() => {
if (!navigator.mediaSession) return;
const metaItem = player.metaItem && player.metaItem?.type === 'Ready' ? player.metaItem.content as MetaItemPlayer : null;
const videoId = player.selected ? player.selected?.streamRequest?.path?.id : null;
const video = metaItem?.videos.find(({ id }) => id === videoId);
@ -35,22 +48,45 @@ const useMediaSession = (
const artwork = imageUrl ? [{ src: imageUrl }] : undefined;
if (title) {
navigator.mediaSession.metadata = new MediaMetadata({
title,
artist,
artwork,
});
if (navigator.mediaSession) {
navigator.mediaSession.metadata = new MediaMetadata({
title,
artist,
artwork,
});
}
if (shell.active) {
shell.send('media.metadata', {
title,
artist,
artUrl: imageUrl,
});
}
}
}, [player.metaItem, player.selected]);
// Callbacks
useEffect(() => {
if (!navigator.mediaSession) return;
navigator.mediaSession.setActionHandler('play', onPlayRequested);
navigator.mediaSession.setActionHandler('pause', onPauseRequested);
if (navigator.mediaSession) {
navigator.mediaSession.setActionHandler('play', onPlayRequested);
navigator.mediaSession.setActionHandler('pause', onPauseRequested);
}
const nexVideoCallback = player.nextVideo ? onNextVideoRequested : null;
navigator.mediaSession.setActionHandler('nexttrack', nexVideoCallback);
if (navigator.mediaSession && nexVideoCallback) {
navigator.mediaSession.setActionHandler('nexttrack', nexVideoCallback);
}
const onMediaStatus = ({ paused }: MediaStatus) => {
paused ? onPauseRequested() : onPlayRequested();
};
shell.on('media.status', onMediaStatus);
return () => {
shell.on('media.status', onMediaStatus);
};
}, [player.nextVideo, onPlayRequested, onPauseRequested, onNextVideoRequested]);
};