Files
maher 703f50a09d
Some checks failed
Build / run (push) Has been cancelled
first commit
2025-10-29 11:42:25 +01:00

120 lines
3.5 KiB
TypeScript
Executable File

import {useCallback, useContext, useEffect, useRef, useState} from 'react';
import {PlayerStoreContext} from '@common/player/player-context';
import {usePlayerStore} from '@common/player/hooks/use-player-store';
import {useHtmlMediaInternalState} from '@common/player/providers/html-media/use-html-media-internal-state';
import {useHtmlMediaEvents} from '@common/player/providers/html-media/use-html-media-events';
import {useHtmlMediaApi} from '@common/player/providers/html-media/use-html-media-api';
import {MediaPlayer, MediaPlayerClass, supportsMediaSource} from 'dashjs';
export default function DashProvider() {
const store = useContext(PlayerStoreContext);
const cuedMedia = usePlayerStore(s => s.cuedMedia);
// html medial element state
const videoRef = useRef<HTMLVideoElement>(null!);
const htmlMediaState = useHtmlMediaInternalState(videoRef);
const htmlMediaEvents = useHtmlMediaEvents(htmlMediaState);
const htmlMediaApi = useHtmlMediaApi(htmlMediaState);
const dash = useRef<MediaPlayerClass | undefined>();
const [dashReady, setDashReady] = useState(false);
const destroyDash = useCallback(() => {
if (dash.current) {
dash.current.destroy();
dash.current = undefined;
setDashReady(false);
}
}, []);
const setupDash = useCallback(() => {
if (!supportsMediaSource()) {
store.getState().emit('error', {fatal: true});
return;
}
const dashInstance = MediaPlayer().create();
dashInstance.on(MediaPlayer.events.ERROR, (e: any) => {
store.getState().emit('error', {sourceEvent: e});
});
dashInstance.on(MediaPlayer.events.PLAYBACK_METADATA_LOADED, () => {
const levels = dashInstance.getBitrateInfoListFor('video');
if (!levels?.length) return;
store.getState().emit('playbackQualities', {
qualities: ['auto', ...levels.map(levelToPlaybackQuality)],
});
store.getState().emit('playbackQualityChange', {quality: 'auto'});
});
dashInstance.initialize(videoRef.current, undefined, false);
// set dash instance after attaching to video element, so "attachSource" is called after
dash.current = dashInstance;
setDashReady(true);
}, [store]);
useEffect(() => {
setupDash();
return () => {
destroyDash();
};
}, [setupDash, destroyDash]);
useEffect(() => {
if (dash.current && cuedMedia?.src) {
dash.current.attachSource(cuedMedia.src);
}
}, [cuedMedia?.src, dashReady]);
useEffect(() => {
if (!dashReady) return;
store.setState({
providerApi: {
...htmlMediaApi,
setPlaybackQuality: (quality: string) => {
if (!dash.current) return;
const levels = dash.current.getBitrateInfoListFor('video');
const index = levels.findIndex(
level => levelToPlaybackQuality(level) === quality
);
dash.current.updateSettings({
streaming: {
abr: {
autoSwitchBitrate: {
video: index === -1,
},
},
},
});
if (index >= 0) {
dash.current.setQualityFor('video', index);
}
store.getState().emit('playbackQualityChange', {quality});
},
},
});
}, [store, htmlMediaApi, dashReady]);
return (
<video
className="h-full w-full"
ref={videoRef}
playsInline
poster={cuedMedia?.poster}
{...htmlMediaEvents}
/>
);
}
const levelToPlaybackQuality = (level: any) => {
return level === -1 ? 'auto' : `${level.height}p`;
};