Fix Railbird regressions found during v6.19.2 review
Some checks failed
Build Android / Build Android Example App (pull_request) Has been cancelled
Build Android / Build Android Example App With Ads (pull_request) Has been cancelled
Build iOS / Build iOS Example App (pull_request) Has been cancelled
Build iOS / Build iOS Example App With Ads (pull_request) Has been cancelled
Build iOS / Build iOS Example App With Caching (pull_request) Has been cancelled
Check Android / Kotlin-Lint (pull_request) Has been cancelled
Check iOS / Swift-Lint (pull_request) Has been cancelled
Check iOS / Swift-Format (pull_request) Has been cancelled
Check JS / Check TS (tsc) (pull_request) Has been cancelled
Check JS / Lint JS (eslint, prettier) (pull_request) Has been cancelled
Some checks failed
Build Android / Build Android Example App (pull_request) Has been cancelled
Build Android / Build Android Example App With Ads (pull_request) Has been cancelled
Build iOS / Build iOS Example App (pull_request) Has been cancelled
Build iOS / Build iOS Example App With Ads (pull_request) Has been cancelled
Build iOS / Build iOS Example App With Caching (pull_request) Has been cancelled
Check Android / Kotlin-Lint (pull_request) Has been cancelled
Check iOS / Swift-Lint (pull_request) Has been cancelled
Check iOS / Swift-Format (pull_request) Has been cancelled
Check JS / Check TS (tsc) (pull_request) Has been cancelled
Check JS / Lint JS (eslint, prettier) (pull_request) Has been cancelled
This commit is contained in:
@@ -73,7 +73,7 @@ class VideoEventEmitter {
|
|||||||
lateinit var onVideoBandwidthUpdate: (bitRateEstimate: Long, height: Int, width: Int, trackId: String?) -> Unit
|
lateinit var onVideoBandwidthUpdate: (bitRateEstimate: Long, height: Int, width: Int, trackId: String?) -> Unit
|
||||||
lateinit var onVideoPlaybackStateChanged: (isPlaying: Boolean, isSeeking: Boolean) -> Unit
|
lateinit var onVideoPlaybackStateChanged: (isPlaying: Boolean, isSeeking: Boolean) -> Unit
|
||||||
lateinit var onVideoSeek: (currentPosition: Long, seekTime: Long) -> Unit
|
lateinit var onVideoSeek: (currentPosition: Long, seekTime: Long) -> Unit
|
||||||
lateinit var onVideoSeekComplete: (currentPosition: Long) -> Unit
|
lateinit var onVideoSeekComplete: (currentPosition: Long, seekTime: Long) -> Unit
|
||||||
lateinit var onVideoEnd: () -> Unit
|
lateinit var onVideoEnd: () -> Unit
|
||||||
lateinit var onVideoFullscreenPlayerWillPresent: () -> Unit
|
lateinit var onVideoFullscreenPlayerWillPresent: () -> Unit
|
||||||
lateinit var onVideoFullscreenPlayerDidPresent: () -> Unit
|
lateinit var onVideoFullscreenPlayerDidPresent: () -> Unit
|
||||||
@@ -202,9 +202,11 @@ class VideoEventEmitter {
|
|||||||
putDouble("seekTime", seekTime / 1000.0)
|
putDouble("seekTime", seekTime / 1000.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onVideoSeekComplete = { currentPosition ->
|
onVideoSeekComplete = { currentPosition, seekTime ->
|
||||||
event.dispatch(EventTypes.EVENT_SEEK_COMPLETE) {
|
event.dispatch(EventTypes.EVENT_SEEK_COMPLETE) {
|
||||||
putDouble("currentTime", currentPosition / 1000.0)
|
putDouble("currentTime", currentPosition / 1000.0)
|
||||||
|
putDouble("seekTime", seekTime / 1000.0)
|
||||||
|
putInt("target", view.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onVideoEnd = {
|
onVideoEnd = {
|
||||||
|
|||||||
@@ -319,7 +319,10 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
private void handleSeekCompletion() {
|
private void handleSeekCompletion() {
|
||||||
if (player != null && player.getPlaybackState() == Player.STATE_READY && isSeekInProgress) {
|
if (player != null && player.getPlaybackState() == Player.STATE_READY && isSeekInProgress) {
|
||||||
Log.d("ReactExoplayerView", "handleSeekCompletion: currentPosition=" + player.getCurrentPosition());
|
Log.d("ReactExoplayerView", "handleSeekCompletion: currentPosition=" + player.getCurrentPosition());
|
||||||
eventEmitter.onVideoSeekComplete.invoke(player.getCurrentPosition());
|
if (isSeeking) {
|
||||||
|
eventEmitter.onVideoSeek.invoke(player.getCurrentPosition(), seekPosition);
|
||||||
|
}
|
||||||
|
eventEmitter.onVideoSeekComplete.invoke(player.getCurrentPosition(), seekPosition);
|
||||||
isSeeking = false;
|
isSeeking = false;
|
||||||
seekPosition = -1;
|
seekPosition = -1;
|
||||||
isSeekInProgress = false;
|
isSeekInProgress = false;
|
||||||
@@ -1847,6 +1850,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||||||
// We need to update the selected track to make sure that it still matches user selection if track list has changed in this period
|
// We need to update the selected track to make sure that it still matches user selection if track list has changed in this period
|
||||||
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
|
setSelectedTrack(C.TRACK_TYPE_VIDEO, videoTrackType, videoTrackValue);
|
||||||
}
|
}
|
||||||
|
handleSeekCompletion();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (playerNeedsSource) {
|
if (playerNeedsSource) {
|
||||||
|
|||||||
@@ -801,8 +801,6 @@ The documentation for this prop is incomplete and will be updated as each option
|
|||||||
|
|
||||||
<PlatformsList types={['Android', 'iOS', 'visionOS', 'Windows UWP']} />
|
<PlatformsList types={['Android', 'iOS', 'visionOS', 'Windows UWP']} />
|
||||||
|
|
||||||
<PlatformsList types={['Android', 'iOS', 'visionOS', 'Windows UWP']} />
|
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
Pass the asset directly (deprecated):
|
Pass the asset directly (deprecated):
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ Then follow the instructions for your platform to link `react-native-video` into
|
|||||||
## iOS
|
## iOS
|
||||||
|
|
||||||
### Standard Method
|
### Standard Method
|
||||||
|
|
||||||
Run `pod install` in the `ios` directory of your project.
|
Run `pod install` in the `ios` directory of your project.
|
||||||
|
|
||||||
⚠️ From version `6.0.0`, the minimum iOS version required is `13.0`. For more information, see the [updating section](updating.md).
|
⚠️ From version `6.0.0`, the minimum iOS version required is `13.0`. For more information, see the [updating section](updating.md).
|
||||||
@@ -100,6 +101,7 @@ You can enable or disable the following features by setting the corresponding va
|
|||||||
Each enabled feature increases the APK size, so only enable what you need.
|
Each enabled feature increases the APK size, so only enable what you need.
|
||||||
|
|
||||||
By default, the enabled features are:
|
By default, the enabled features are:
|
||||||
|
|
||||||
- `useExoplayerSmoothStreaming`
|
- `useExoplayerSmoothStreaming`
|
||||||
- `useExoplayerDash`
|
- `useExoplayerDash`
|
||||||
- `useExoplayerHls`
|
- `useExoplayerHls`
|
||||||
@@ -222,17 +224,10 @@ Run `pod install` in the `visionos` directory of your project.
|
|||||||
|
|
||||||
## Web
|
## Web
|
||||||
|
|
||||||
No additional setup is required. Everything should work out of the box.
|
No additional setup is required.
|
||||||
|
|
||||||
However, only basic video support is available. HLS, Dash, ads, and DRM are not currently supported.
|
The Railbird fork uses Shaka Player for browser playback, including HLS and DASH
|
||||||
|
sources. The native ads and DRM props are not currently integrated with the web
|
||||||
</details>
|
player.
|
||||||
|
|
||||||
<details>
|
|
||||||
<summary>web</summary>
|
|
||||||
|
|
||||||
Nothing to do, everything should work out of the box.
|
|
||||||
|
|
||||||
Note that only basic video support is present, no hls/dash or ads/drm for now.
|
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
@@ -66,39 +66,25 @@ class ActionQueue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function shallowEqual(obj1: unknown, obj2: unknown): boolean {
|
function isDeepEqual<T>(first: T, second: T): boolean {
|
||||||
// If both are strictly equal (covers primitive types and identical object references)
|
if (first === second) {
|
||||||
if (obj1 === obj2) {
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If one is not an object (meaning it's a primitive), they must be strictly equal
|
const bothAreObjects =
|
||||||
if (
|
first && second && typeof first === 'object' && typeof second === 'object';
|
||||||
typeof obj1 !== 'object' ||
|
if (!bothAreObjects) {
|
||||||
typeof obj2 !== 'object' ||
|
|
||||||
obj1 === null ||
|
|
||||||
obj2 === null
|
|
||||||
) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const first = obj1 as Record<string, unknown>;
|
const firstObject = first as Record<string, unknown>;
|
||||||
const second = obj2 as Record<string, unknown>;
|
const secondObject = second as Record<string, unknown>;
|
||||||
|
return (
|
||||||
// Get the keys of both objects
|
Object.keys(firstObject).length === Object.keys(secondObject).length &&
|
||||||
const keys1 = Object.keys(first);
|
Object.entries(firstObject).every(([key, value]) =>
|
||||||
const keys2 = Object.keys(second);
|
isDeepEqual(value, secondObject[key]),
|
||||||
|
)
|
||||||
// If the number of keys is different, the objects are not equal
|
);
|
||||||
if (keys1.length !== keys2.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that all keys and their corresponding values are the same
|
|
||||||
return keys1.every((key) => {
|
|
||||||
// If the value is an object, we fall back to reference equality (shallow comparison)
|
|
||||||
return first[key] === second[key];
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Video = forwardRef<VideoRef, ReactVideoProps>(
|
const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||||
@@ -136,10 +122,17 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
const shakaPlayerRef = useRef<shaka.Player | null>(null);
|
const shakaPlayerRef = useRef<shaka.Player | null>(null);
|
||||||
const [activeSource, setActiveSource] = useState(source);
|
const [activeSource, setActiveSource] = useState(source);
|
||||||
const sourceProp = useRef(source);
|
const sourceProp = useRef(source);
|
||||||
const [loadedSource, setLoadedSource] = useState<object | null>(null);
|
const [loadedSource, setLoadedSource] = useState<{
|
||||||
|
source: ReactVideoSource | undefined;
|
||||||
|
} | null>(null);
|
||||||
const actionQueue = useRef(new ActionQueue());
|
const actionQueue = useRef(new ActionQueue());
|
||||||
|
const mountedRef = useRef(true);
|
||||||
|
const sourceGenerationRef = useRef(0);
|
||||||
|
const pausedRef = useRef(paused);
|
||||||
|
pausedRef.current = paused;
|
||||||
|
|
||||||
const isSeeking = useRef(false);
|
const isSeeking = useRef(false);
|
||||||
|
const isProgrammaticSeek = useRef(false);
|
||||||
|
|
||||||
const seek = useCallback(
|
const seek = useCallback(
|
||||||
(time: number, _tolerance?: number) => {
|
(time: number, _tolerance?: number) => {
|
||||||
@@ -152,7 +145,13 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
time = Math.max(0, Math.min(time, nativeRef.current.duration));
|
time = Math.max(0, Math.min(time, nativeRef.current.duration));
|
||||||
nativeRef.current.currentTime = time;
|
isProgrammaticSeek.current = true;
|
||||||
|
try {
|
||||||
|
nativeRef.current.currentTime = time;
|
||||||
|
} catch (error) {
|
||||||
|
isProgrammaticSeek.current = false;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
onSeek?.({
|
onSeek?.({
|
||||||
seekTime: time,
|
seekTime: time,
|
||||||
currentTime: nativeRef.current.currentTime,
|
currentTime: nativeRef.current.currentTime,
|
||||||
@@ -210,7 +209,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (shallowEqual(source, sourceProp.current)) {
|
if (isDeepEqual(source, sourceProp.current)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sourceProp.current = source;
|
sourceProp.current = source;
|
||||||
@@ -286,22 +285,22 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const enterPictureInPicture = useCallback(() => {
|
const enterPictureInPicture = useCallback(() => {
|
||||||
actionQueue.current.enqueue(async () => {
|
if (!nativeRef.current) {
|
||||||
if (!nativeRef.current) {
|
console.warn('Video Component is not mounted');
|
||||||
console.warn('Video Component is not mounted');
|
return;
|
||||||
return;
|
}
|
||||||
}
|
nativeRef.current.requestPictureInPicture().catch((error: unknown) => {
|
||||||
await nativeRef.current.requestPictureInPicture();
|
console.error('Could not enter Picture-in-Picture', error);
|
||||||
}, 'enterPictureInPicture');
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const exitPictureInPicture = useCallback(() => {
|
const exitPictureInPicture = useCallback(() => {
|
||||||
actionQueue.current.enqueue(async () => {
|
if (nativeRef.current !== document.pictureInPictureElement) {
|
||||||
if (nativeRef.current !== document.pictureInPictureElement) {
|
return;
|
||||||
return;
|
}
|
||||||
}
|
document.exitPictureInPicture().catch((error: unknown) => {
|
||||||
await document.exitPictureInPicture();
|
console.error('Could not exit Picture-in-Picture', error);
|
||||||
}, 'exitPictureInPicture');
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useImperativeHandle(
|
useImperativeHandle(
|
||||||
@@ -382,46 +381,76 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
}, [rate]);
|
}, [rate]);
|
||||||
|
|
||||||
const makeNewShaka = useCallback(() => {
|
const makeNewShaka = useCallback(() => {
|
||||||
|
const sourceToLoad = activeSource;
|
||||||
|
const generation = ++sourceGenerationRef.current;
|
||||||
|
isSeeking.current = false;
|
||||||
|
isProgrammaticSeek.current = false;
|
||||||
|
|
||||||
actionQueue.current.enqueue(async () => {
|
actionQueue.current.enqueue(async () => {
|
||||||
if (!nativeRef.current) {
|
const video = nativeRef.current;
|
||||||
|
if (
|
||||||
|
!mountedRef.current ||
|
||||||
|
!video ||
|
||||||
|
generation !== sourceGenerationRef.current
|
||||||
|
) {
|
||||||
console.warn('No video element to attach Shaka Player');
|
console.warn('No video element to attach Shaka Player');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pause the video before changing the source
|
// Pause the video before changing the source
|
||||||
nativeRef.current.pause();
|
video.pause();
|
||||||
|
|
||||||
// Unload the previous Shaka player if it exists
|
// Unload the previous Shaka player if it exists
|
||||||
if (shakaPlayerRef.current) {
|
const previousPlayer = shakaPlayerRef.current;
|
||||||
await shakaPlayerRef.current.unload();
|
if (previousPlayer) {
|
||||||
await shakaPlayerRef.current.destroy();
|
|
||||||
shakaPlayerRef.current = null;
|
shakaPlayerRef.current = null;
|
||||||
|
await previousPlayer.unload().catch((error: unknown) => {
|
||||||
|
console.error('Error unloading previous Shaka Player', error);
|
||||||
|
});
|
||||||
|
await previousPlayer.destroy().catch((error: unknown) => {
|
||||||
|
console.error('Error destroying previous Shaka Player', error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const sourceUri = activeSource?.uri;
|
if (
|
||||||
|
!mountedRef.current ||
|
||||||
|
nativeRef.current !== video ||
|
||||||
|
generation !== sourceGenerationRef.current
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceUri = sourceToLoad?.uri;
|
||||||
if (typeof sourceUri !== 'string' || !sourceUri) {
|
if (typeof sourceUri !== 'string' || !sourceUri) {
|
||||||
nativeRef.current.removeAttribute('src');
|
video.removeAttribute('src');
|
||||||
nativeRef.current.load();
|
video.load();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new Shaka player and attach it to the video element
|
// Create a new Shaka player and attach it to the video element
|
||||||
shakaPlayerRef.current = new shaka.Player();
|
const player = new shaka.Player();
|
||||||
|
shakaPlayerRef.current = player;
|
||||||
|
|
||||||
await shakaPlayerRef.current.attach(nativeRef.current);
|
const isCurrentPlayer = () =>
|
||||||
|
mountedRef.current &&
|
||||||
|
nativeRef.current === video &&
|
||||||
|
generation === sourceGenerationRef.current &&
|
||||||
|
shakaPlayerRef.current === player;
|
||||||
|
|
||||||
if (activeSource.cropStart) {
|
const destroyStalePlayer = async () => {
|
||||||
shakaPlayerRef.current.configure({
|
if (shakaPlayerRef.current !== player) {
|
||||||
playRangeStart: activeSource.cropStart / 1000,
|
return;
|
||||||
|
}
|
||||||
|
shakaPlayerRef.current = null;
|
||||||
|
await player.destroy().catch((error: unknown) => {
|
||||||
|
console.error('Error destroying stale Shaka Player', error);
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
if (activeSource.cropEnd) {
|
|
||||||
shakaPlayerRef.current.configure({
|
|
||||||
playRangeEnd: activeSource.cropEnd / 1000,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
shakaPlayerRef.current.addEventListener('error', (event) => {
|
player.addEventListener('error', (event) => {
|
||||||
|
if (!isCurrentPlayer()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const shakaError = (
|
const shakaError = (
|
||||||
event as CustomEvent<{message?: string; code?: number}>
|
event as CustomEvent<{message?: string; code?: number}>
|
||||||
).detail;
|
).detail;
|
||||||
@@ -438,18 +467,43 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
|
|
||||||
// Load the new source
|
// Load the new source
|
||||||
try {
|
try {
|
||||||
await shakaPlayerRef.current.load(sourceUri);
|
await player.attach(video);
|
||||||
|
if (!isCurrentPlayer()) {
|
||||||
|
await destroyStalePlayer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceToLoad.cropStart) {
|
||||||
|
player.configure({
|
||||||
|
playRangeStart: sourceToLoad.cropStart / 1000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (sourceToLoad.cropEnd) {
|
||||||
|
player.configure({
|
||||||
|
playRangeEnd: sourceToLoad.cropEnd / 1000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await player.load(sourceUri);
|
||||||
|
if (!isCurrentPlayer()) {
|
||||||
|
await destroyStalePlayer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.log(`${sourceUri} finished loading`);
|
console.log(`${sourceUri} finished loading`);
|
||||||
|
|
||||||
// Optionally resume playback if not paused
|
// Optionally resume playback if not paused
|
||||||
if (!paused) {
|
if (!pausedRef.current) {
|
||||||
try {
|
try {
|
||||||
await nativeRef.current.play();
|
await video.play();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error playing video:', e);
|
console.error('Error playing video:', e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (!isCurrentPlayer()) {
|
||||||
|
await destroyStalePlayer();
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.error('Error loading video with Shaka Player', e);
|
console.error('Error loading video with Shaka Player', e);
|
||||||
const shakaError = e as {message?: string; code?: number};
|
const shakaError = e as {message?: string; code?: number};
|
||||||
onError?.({
|
onError?.({
|
||||||
@@ -460,7 +514,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 'makeNewShaka');
|
}, 'makeNewShaka');
|
||||||
}, [activeSource, paused, onError]);
|
}, [activeSource, onError]);
|
||||||
|
|
||||||
const nativeRefDefined = !!nativeRef.current;
|
const nativeRefDefined = !!nativeRef.current;
|
||||||
|
|
||||||
@@ -471,20 +525,26 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!shallowEqual(activeSource, loadedSource)) {
|
if (
|
||||||
|
loadedSource === null ||
|
||||||
|
!isDeepEqual(activeSource, loadedSource.source)
|
||||||
|
) {
|
||||||
console.log(
|
console.log(
|
||||||
'Making new shaka, Old source: ',
|
'Making new shaka, Old source: ',
|
||||||
loadedSource,
|
loadedSource?.source,
|
||||||
'New source',
|
'New source',
|
||||||
activeSource,
|
activeSource,
|
||||||
);
|
);
|
||||||
setLoadedSource(activeSource ?? null);
|
setLoadedSource({source: activeSource});
|
||||||
makeNewShaka();
|
makeNewShaka();
|
||||||
}
|
}
|
||||||
}, [activeSource, nativeRefDefined, loadedSource, makeNewShaka]);
|
}, [activeSource, nativeRefDefined, loadedSource, makeNewShaka]);
|
||||||
|
|
||||||
useEffect(
|
useEffect(() => {
|
||||||
() => () => {
|
mountedRef.current = true;
|
||||||
|
return () => {
|
||||||
|
mountedRef.current = false;
|
||||||
|
sourceGenerationRef.current += 1;
|
||||||
const player = shakaPlayerRef.current;
|
const player = shakaPlayerRef.current;
|
||||||
shakaPlayerRef.current = null;
|
shakaPlayerRef.current = null;
|
||||||
if (player) {
|
if (player) {
|
||||||
@@ -492,9 +552,8 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
console.error('Error destroying Shaka Player', error);
|
console.error('Error destroying Shaka Player', error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
[],
|
}, []);
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -612,11 +671,19 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
|||||||
}
|
}
|
||||||
onSeeking={() => (isSeeking.current = true)}
|
onSeeking={() => (isSeeking.current = true)}
|
||||||
onSeeked={() => {
|
onSeeked={() => {
|
||||||
|
const currentTime = nativeRef.current?.currentTime || 0.0;
|
||||||
|
if (isSeeking.current && !isProgrammaticSeek.current) {
|
||||||
|
onSeek?.({
|
||||||
|
currentTime,
|
||||||
|
seekTime: currentTime,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
isSeeking.current = false;
|
isSeeking.current = false;
|
||||||
|
isProgrammaticSeek.current = false;
|
||||||
|
|
||||||
onSeekComplete?.({
|
onSeekComplete?.({
|
||||||
currentTime:
|
currentTime: currentTime - cropStartSeconds,
|
||||||
(nativeRef.current?.currentTime || 0.0) - cropStartSeconds,
|
|
||||||
seekTime: 0.0,
|
seekTime: 0.0,
|
||||||
target: 0.0,
|
target: 0.0,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user