Shaka?
This commit is contained in:
parent
d79b5c9a83
commit
5bc975b2c9
@ -34,7 +34,9 @@
|
|||||||
"release-it": "^16.2.1",
|
"release-it": "^16.2.1",
|
||||||
"typescript": "5.1.6"
|
"typescript": "5.1.6"
|
||||||
},
|
},
|
||||||
"dependencies": {},
|
"dependencies": {
|
||||||
|
"shaka-player": "^4.11.7"
|
||||||
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "*",
|
"react": "*",
|
||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
|
@ -6,7 +6,8 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
type RefObject,
|
type RefObject,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import type {VideoRef, ReactVideoProps, VideoMetadata} from './types';
|
import shaka from 'shaka-player/dist/shaka-player.compiled.js';
|
||||||
|
import type { VideoRef, ReactVideoProps, VideoMetadata } from './types';
|
||||||
|
|
||||||
const Video = forwardRef<VideoRef, ReactVideoProps>(
|
const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||||
(
|
(
|
||||||
@ -37,20 +38,28 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
ref,
|
ref,
|
||||||
) => {
|
) => {
|
||||||
const nativeRef = useRef<HTMLVideoElement>(null);
|
const nativeRef = useRef<HTMLVideoElement>(null);
|
||||||
|
const shakaPlayerRef = useRef<shaka.Player | null>(null);
|
||||||
|
|
||||||
const isSeeking = useRef(false);
|
const isSeeking = useRef(false);
|
||||||
|
|
||||||
const seek = useCallback(
|
const seek = useCallback(
|
||||||
async (time: number, _tolerance?: number) => {
|
(time: number, _tolerance?: number) => {
|
||||||
if (isNaN(time)) {
|
if (isNaN(time)) {
|
||||||
throw new Error('Specified time is not a number');
|
throw new Error('Specified time is not a number');
|
||||||
}
|
}
|
||||||
if (!nativeRef.current) {
|
if (!shakaPlayerRef.current) {
|
||||||
console.warn('Video Component is not mounted');
|
console.warn('Shaka Player is not initialized');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
time = Math.max(0, Math.min(time, nativeRef.current.duration));
|
time = Math.max(
|
||||||
nativeRef.current.currentTime = time;
|
0,
|
||||||
onSeek?.({seekTime: time, currentTime: nativeRef.current.currentTime});
|
Math.min(time, shakaPlayerRef.current.seekRange().end)
|
||||||
|
);
|
||||||
|
shakaPlayerRef.current.seek(time);
|
||||||
|
onSeek?.({
|
||||||
|
seekTime: time,
|
||||||
|
currentTime: nativeRef.current?.currentTime || 0,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[onSeek],
|
[onSeek],
|
||||||
);
|
);
|
||||||
@ -198,26 +207,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
setVolume(volume);
|
setVolume(volume);
|
||||||
}, [volume, setVolume]);
|
}, [volume, setVolume]);
|
||||||
|
|
||||||
// we use a ref to prevent triggerring the useEffect when the component rerender with a non-stable `onPlaybackStateChanged`.
|
// Handle playback rate changes
|
||||||
const playbackStateRef = useRef(onPlaybackStateChanged);
|
|
||||||
playbackStateRef.current = onPlaybackStateChanged;
|
|
||||||
useEffect(() => {
|
|
||||||
// Not sure about how to do this but we want to wait for nativeRef to be initialized
|
|
||||||
setTimeout(() => {
|
|
||||||
if (!nativeRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set play state to the player's value (if autoplay is denied)
|
|
||||||
// This is useful if our UI is in a play state but autoplay got denied so
|
|
||||||
// the video is actaully in a paused state.
|
|
||||||
playbackStateRef.current?.({
|
|
||||||
isPlaying: !nativeRef.current.paused,
|
|
||||||
isSeeking: isSeeking.current,
|
|
||||||
});
|
|
||||||
}, 500);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!nativeRef.current || rate === undefined) {
|
if (!nativeRef.current || rate === undefined) {
|
||||||
return;
|
return;
|
||||||
@ -225,46 +215,90 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
nativeRef.current.playbackRate = rate;
|
nativeRef.current.playbackRate = rate;
|
||||||
}, [rate]);
|
}, [rate]);
|
||||||
|
|
||||||
|
// Initialize Shaka Player
|
||||||
|
useEffect(() => {
|
||||||
|
if (!nativeRef.current) {
|
||||||
|
console.warn('Video component is not mounted');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize Shaka Player
|
||||||
|
const player = new shaka.Player(nativeRef.current);
|
||||||
|
shakaPlayerRef.current = player;
|
||||||
|
|
||||||
|
// Error handling
|
||||||
|
player.addEventListener('error', (event) => {
|
||||||
|
const shakaError = event.detail;
|
||||||
|
console.error('Shaka Player Error', shakaError);
|
||||||
|
onError?.({
|
||||||
|
error: {
|
||||||
|
errorString: shakaError.message,
|
||||||
|
code: shakaError.code,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Buffering events
|
||||||
|
player.addEventListener('buffering', (event) => {
|
||||||
|
onBuffer?.({ isBuffering: event.buffering });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Load the video source
|
||||||
|
player
|
||||||
|
.load(source?.uri)
|
||||||
|
.then(() => {
|
||||||
|
// Media loaded successfully
|
||||||
|
if (!nativeRef.current) return;
|
||||||
|
|
||||||
|
const duration = nativeRef.current.duration;
|
||||||
|
const naturalSize = {
|
||||||
|
width: nativeRef.current.videoWidth,
|
||||||
|
height: nativeRef.current.videoHeight,
|
||||||
|
orientation:
|
||||||
|
nativeRef.current.videoWidth > nativeRef.current.videoHeight
|
||||||
|
? 'landscape'
|
||||||
|
: 'portrait',
|
||||||
|
};
|
||||||
|
|
||||||
|
onLoad?.({
|
||||||
|
currentTime: nativeRef.current.currentTime,
|
||||||
|
duration,
|
||||||
|
naturalSize,
|
||||||
|
videoTracks: player.getVariantTracks(),
|
||||||
|
audioTracks: player.getVariantTracks(),
|
||||||
|
textTracks: player.getTextTracks(),
|
||||||
|
});
|
||||||
|
|
||||||
|
onReadyForDisplay?.();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Error loading video', error);
|
||||||
|
onError?.({ error });
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// Cleanup
|
||||||
|
if (shakaPlayerRef.current) {
|
||||||
|
shakaPlayerRef.current.destroy();
|
||||||
|
shakaPlayerRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [source?.uri]);
|
||||||
|
|
||||||
|
// Handle Media Session (if implemented)
|
||||||
useMediaSession(source?.metadata, nativeRef, showNotificationControls);
|
useMediaSession(source?.metadata, nativeRef, showNotificationControls);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<video
|
<video
|
||||||
ref={nativeRef}
|
ref={nativeRef}
|
||||||
src={source?.uri as string | undefined}
|
|
||||||
muted={muted}
|
muted={muted}
|
||||||
autoPlay={!paused}
|
|
||||||
controls={controls}
|
controls={controls}
|
||||||
loop={repeat}
|
loop={repeat}
|
||||||
playsInline
|
playsInline
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
poster={poster}
|
poster={poster}
|
||||||
onCanPlay={() => onBuffer?.({isBuffering: false})}
|
onCanPlay={() => onBuffer?.({ isBuffering: false })}
|
||||||
onWaiting={() => onBuffer?.({isBuffering: true})}
|
onWaiting={() => onBuffer?.({ isBuffering: true })}
|
||||||
onRateChange={() => {
|
|
||||||
if (!nativeRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onPlaybackRateChange?.({
|
|
||||||
playbackRate: nativeRef.current?.playbackRate,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
onDurationChange={() => {
|
|
||||||
if (!nativeRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onLoad?.({
|
|
||||||
currentTime: nativeRef.current.currentTime,
|
|
||||||
duration: nativeRef.current.duration,
|
|
||||||
videoTracks: [],
|
|
||||||
textTracks: [],
|
|
||||||
audioTracks: [],
|
|
||||||
naturalSize: {
|
|
||||||
width: nativeRef.current.videoWidth,
|
|
||||||
height: nativeRef.current.videoHeight,
|
|
||||||
orientation: 'landscape',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
onTimeUpdate={() => {
|
onTimeUpdate={() => {
|
||||||
if (!nativeRef.current) {
|
if (!nativeRef.current) {
|
||||||
return;
|
return;
|
||||||
@ -273,54 +307,33 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
currentTime: nativeRef.current.currentTime,
|
currentTime: nativeRef.current.currentTime,
|
||||||
playableDuration: nativeRef.current.buffered.length
|
playableDuration: nativeRef.current.buffered.length
|
||||||
? nativeRef.current.buffered.end(
|
? nativeRef.current.buffered.end(
|
||||||
nativeRef.current.buffered.length - 1,
|
nativeRef.current.buffered.length - 1
|
||||||
|
)
|
||||||
|
: 0,
|
||||||
|
seekableDuration: nativeRef.current.seekable.length
|
||||||
|
? nativeRef.current.seekable.end(
|
||||||
|
nativeRef.current.seekable.length - 1
|
||||||
)
|
)
|
||||||
: 0,
|
: 0,
|
||||||
seekableDuration: 0,
|
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onLoadedData={() => onReadyForDisplay?.()}
|
onEnded={onEnd}
|
||||||
onError={() => {
|
onError={() => {
|
||||||
if (!nativeRef.current?.error) {
|
if (!nativeRef.current?.error) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
onError?.({
|
onError?.({
|
||||||
error: {
|
error: {
|
||||||
errorString: nativeRef.current.error.message ?? 'Unknown error',
|
errorString:
|
||||||
|
nativeRef.current.error.message || 'Unknown error',
|
||||||
code: nativeRef.current.error.code,
|
code: nativeRef.current.error.code,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onLoadedMetadata={() => {
|
|
||||||
if (source?.startPosition) {
|
|
||||||
seek(source.startPosition / 1000);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onPlay={() =>
|
|
||||||
onPlaybackStateChanged?.({
|
|
||||||
isPlaying: true,
|
|
||||||
isSeeking: isSeeking.current,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onPause={() =>
|
|
||||||
onPlaybackStateChanged?.({
|
|
||||||
isPlaying: false,
|
|
||||||
isSeeking: isSeeking.current,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
onSeeking={() => (isSeeking.current = true)}
|
|
||||||
onSeeked={() => (isSeeking.current = false)}
|
|
||||||
onVolumeChange={() => {
|
|
||||||
if (!nativeRef.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
onVolumeChange?.({volume: nativeRef.current.volume});
|
|
||||||
}}
|
|
||||||
onEnded={onEnd}
|
|
||||||
style={videoStyle}
|
style={videoStyle}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
},
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const videoStyle = {
|
const videoStyle = {
|
||||||
|
Loading…
Reference in New Issue
Block a user