Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix player replaying last section/single section item in a loop #628

Merged
merged 1 commit into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 73 additions & 59 deletions src/components/MediaPlayer/VideoJS/VideoJSPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -218,16 +218,21 @@ function VideoJSPlayer({
const player = playerRef.current;

// Block player while metadata is loaded when canvas is not empty
if (!canvasIsEmptyRef.current) player.addClass('vjs-disabled');
if (!canvasIsEmptyRef.current) {
player.addClass('vjs-disabled');

setIsReady(false);
updatePlayer(player);
playerLoadedMetadata(player);
setIsReady(false);
updatePlayer(player);
playerLoadedMetadata(player);

playerDispatch({
player: player,
type: 'updatePlayer',
});
playerDispatch({
player: player,
type: 'updatePlayer',
});
} else {
// Mark as ready to for inaccessible canvas (empty)
setIsReady(true);
}
}
}, [options.sources, videoJSRef]);

Expand Down Expand Up @@ -792,66 +797,75 @@ function VideoJSPlayer({
* (not the next item in list) when the current item is coming to its end.
*/
const handleEnded = React.useMemo(() => throttle(() => {
if (!autoAdvanceRef.current && !hasMultiItems || canvasIsEmptyRef.current) {
const isLastCanvas = cIndexRef.current === lastCanvasIndex;
/**
* Do nothing if Canvas is not multi-sourced AND autoAdvance is turned off
* OR current Canvas is the last Canvas in the Manifest
*/
if ((!autoAdvanceRef.current || isLastCanvas) && !hasMultiItems) {
return;
}

// Remove all the existing structure related markers in the player
if (playerRef.current && playerRef.current.markers) {
playerRef.current.pause();
playerRef.current.markers.removeAll();
}
if (hasMultiItems) {
// When there are multiple sources in a single canvas
// advance to next source
if (srcIndex + 1 < targets.length) {
manifestDispatch({ srcIndex: srcIndex + 1, type: 'setSrcIndex' });
playerDispatch({ currentTime: 0, type: 'setCurrentTime' });
} else {
// Remove all the existing structure related markers in the player
if (playerRef.current && playerRef.current.markers) {
playerRef.current.pause();
playerRef.current.markers.removeAll();
}
} else if (structuresRef.current?.length > 0) {
const nextItem = structuresRef.current[cIndexRef.current + 1];

if (nextItem && nextItem != undefined) {
manifestDispatch({
canvasIndex: cIndexRef.current + 1,
type: 'switchCanvas',
});

// Reset startTime and currentTime to zero
playerDispatch({ startTime: 0, type: 'setTimeFragment' });
playerDispatch({ currentTime: 0, type: 'setCurrentTime' });

// Get first timespan in the next canvas
let firstTimespanInNextCanvas = canvasSegmentsRef.current.filter(
(t) => t.canvasIndex === nextItem.canvasIndex && t.itemIndex === 1
);
// If the nextItem doesn't have an ID (a Canvas media fragment) pick the first timespan
// in the next Canvas
let nextFirstItem = nextItem.id != undefined ? nextItem : firstTimespanInNextCanvas[0];

let start = 0;
if (nextFirstItem != undefined && nextFirstItem.id != undefined) {
start = getMediaFragment(nextFirstItem.id, canvasDurationRef.current).start;
if (hasMultiItems) {
// When there are multiple sources in a single canvas
// advance to next source
if (srcIndex + 1 < targets.length) {
manifestDispatch({ srcIndex: srcIndex + 1, type: 'setSrcIndex' });
playerDispatch({ currentTime: 0, type: 'setCurrentTime' });
playerRef.current.play();
} else {
return;
}
} else if (structuresRef.current?.length > 0) {
const nextItem = structuresRef.current[cIndexRef.current + 1];

// If there's a timespan item at the start of the next canvas
// mark it as the currentNavItem. Otherwise empty out the currentNavItem.
if (start === 0) {
if (nextItem) {
manifestDispatch({
item: nextFirstItem,
type: 'switchItem',
canvasIndex: cIndexRef.current + 1,
type: 'switchCanvas',
});
} else if (nextFirstItem.isEmpty) {
// Switch the currentNavItem and clear isEnded flag
manifestDispatch({
item: nextFirstItem,
type: 'switchItem',
});
playerRef.current.currentTime(start);

// Reset startTime and currentTime to zero
playerDispatch({ startTime: 0, type: 'setTimeFragment' });
playerDispatch({ currentTime: 0, type: 'setCurrentTime' });

// Get first timespan in the next canvas
let firstTimespanInNextCanvas = canvasSegmentsRef.current.filter(
(t) => t.canvasIndex === nextItem.canvasIndex && t.itemIndex === 1
);
// If the nextItem doesn't have an ID (a Canvas media fragment) pick the first timespan
// in the next Canvas
let nextFirstItem = nextItem.id != undefined ? nextItem : firstTimespanInNextCanvas[0];

let start = 0;
if (nextFirstItem != undefined && nextFirstItem.id != undefined) {
start = getMediaFragment(nextFirstItem.id, canvasDurationRef.current).start;
}

// If there's a timespan item at the start of the next canvas
// mark it as the currentNavItem. Otherwise empty out the currentNavItem.
if (start === 0) {
manifestDispatch({
item: nextFirstItem,
type: 'switchItem',
});
} else if (nextFirstItem.isEmpty) {
// Switch the currentNavItem and clear isEnded flag
manifestDispatch({
item: nextFirstItem,
type: 'switchItem',
});
playerRef.current.currentTime(start);
// Only play if the next item is not an inaccessible item
if (!nextItem.isEmpty) playerRef.current.play();
}
}
}
}
playerRef.current.play();
}), [cIndexRef.current]);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ class VideoJSProgress extends vjsComponent {
handleTimeUpdate(curTime) {
const { player, options, el_ } = this;
const { srcIndex, targets } = options;
const { start, end } = targets[srcIndex];
const { start, end, duration } = targets[srcIndex];

// Avoid null player instance when Video.js is getting initialized
if (!el_ || !player) {
Expand All @@ -114,14 +114,17 @@ class VideoJSProgress extends vjsComponent {
if (curTime < start) {
player.currentTime(start);
}
// Some items, particularly in playlists, were not having `player.ended()` properly
// set by the 'ended' event. Providing a fallback check that the player is already
// paused prevents undesirable behavior from excess state changes after play ending.
// Some items, particularly in playlists, were not triggering `player.ended()` event as expected.
// This code block acts as a fallback when that happens. Player is momentarily paused to prevent
// crashing the player when it is in a transient state while switching canvases.
if (curTime >= end && !player.paused() && !player.isDisposed()) {
if (nextItems.length == 0) { options.nextItemClicked(0, targets[0].start); }
player.pause();
player.trigger('ended');

// Pause when playable range < duration of the full media. e.g. clipped playlist items
if (end < duration) {
player.pause();
}
// Delay ended event so that, it fires after pause and display replay icon instead of play/pause
this.setTimeout(() => { player.trigger('ended'); }, 10);

// On the next play event set the time to start or a seeked time
// in between the 'ended' event and 'play' event
Expand Down Expand Up @@ -443,13 +446,13 @@ function ProgressBar({
* Set start values for progress bar
* @param {Number} start canvas start time
*/
const initializeProgress = (start) => {
const initializeProgress = (start) => {
setProgress(start);
setInitTime(start);

setCurrentTime(start);
player.currentTime(start);
}
};

/**
* Set progress and player time when using the input range
Expand Down