Skip to content

Commit

Permalink
Merge pull request #553 from samvera-labs/prev-next-btns-desktop
Browse files Browse the repository at this point in the history
Fix previous/next button functionality with inaccessible item transitions
  • Loading branch information
Dananji authored Jul 5, 2024
2 parents 83cdb2b + 9a9ad29 commit 22f8d13
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 15 deletions.
58 changes: 49 additions & 9 deletions src/components/MediaPlayer/VideoJS/VideoJSPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
useManifestDispatch,
} from '../../../context/manifest-context';
import { CANVAS_MESSAGE_TIMEOUT, checkSrcRange, getMediaFragment, playerHotKeys } from '@Services/utility-helpers';
import { IS_ANDROID, IS_IPAD, IS_MOBILE } from '@Services/browser';
import { IS_ANDROID, IS_IPAD, IS_MOBILE, IS_SAFARI } from '@Services/browser';
import { useLocalStorage } from '@Services/local-storage';
import { SectionButtonIcon } from '@Services/svg-icons';
import './VideoJSPlayer.scss';
Expand Down Expand Up @@ -190,6 +190,10 @@ function VideoJSPlayer({
buildTracksHTML();
videojs.addLanguage(options.language, languageJSON);

// Turn Video.js logging off and handle errors in this code, to avoid
// cluttering the console when loading inaccessible items.
videojs.log.level('off');

const player = playerRef.current = videojs(videoJSRef.current, options, () => {
playerInitSetup(playerRef.current);
});
Expand All @@ -205,8 +209,8 @@ function VideoJSPlayer({
// Update the existing Video.js player on consecutive Canvas changes
const player = playerRef.current;

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

setIsReady(false);
updatePlayer(player);
Expand All @@ -224,15 +228,15 @@ function VideoJSPlayer({
clearDisplayTimeInterval();

if (playerRef.current) {
// For empty Canvas pause the player if it's playing
if (isPlayingRef.current) { playerRef.current.pause(); }
// Set the player's aspect ratio to video
playerRef.current.audioOnlyMode(false);
playerRef.current.canvasIsEmpty = true;
playerRef.current.aspectRatio('16:9');
// Show/hide control bar for valid/inaccessible items respectively
if (canvasIsEmpty) {
playerRef.current.controlBar.addClass('vjs-hidden');
playerRef.current.removeClass('vjs-disabled');
playerRef.current.src('');
} else {
// Reveal control bar; needed when loading a Canvas after an inaccessible item
playerRef.current.controlBar.removeClass('vjs-hidden');
Expand Down Expand Up @@ -446,7 +450,7 @@ function VideoJSPlayer({
*/
const playerLoadedMetadata = (player) => {
player.one('loadedmetadata', () => {
videojs.log('Player loadedmetadata');
console.log('Player loadedmetadata');

player.duration(canvasDurationRef.current);

Expand Down Expand Up @@ -493,7 +497,17 @@ function VideoJSPlayer({
player.altStart = targets[srcIndex].altStart;
}

player.canvasIndex = cIndexRef.current;

setIsReady(true);

/**
* Update currentNavItem on loadedmetadata event in Safari, as it doesn't
* trigger the 'timeupdate' event intermittently on load.
*/
if (IS_SAFARI) {
handleTimeUpdate();
}
});
};

Expand All @@ -505,7 +519,7 @@ function VideoJSPlayer({
*/
const playerInitSetup = (player) => {
player.on('ready', function () {
videojs.log('Player ready');
console.log('Player ready');

// Add this class in mobile/tablet devices to always show the control bar,
// since the inactivityTimeout is flaky in some browsers
Expand All @@ -515,7 +529,6 @@ function VideoJSPlayer({

player.muted(startMuted);
player.volume(startVolume);
player.canvasIndex = cIndexRef.current;
player.srcIndex = srcIndex;
// Need to set this once experimentalSvgIcons option in Video.js options was enabled
player.getChild('controlBar').qualitySelector.setIcon('cog');
Expand Down Expand Up @@ -554,6 +567,33 @@ function VideoJSPlayer({
player.on('qualityRequested', (e, quality) => {
setStartQuality(quality.label);
});
// Use error event listener for inaccessible item display
player.on('error', (e) => {
const error = player.error();
// Handle different error codes
// TODO::In the future, this can be further improved to give proper feedback to the user when playback is not working
switch (error.code) {
case 1:
console.error('MEDIA_ERR_ABORTED: The fetching process for the media resource was aborted by the user agent\
at the user’s request.');
break;
case 2:
console.error('MEDIA_ERR_NETWORK: A network error caused the user agent to stop fetching the media resource,\
after the resource was established to be usable.');
break;
case 3:
console.error('MEDIA_ERR_DECODE: An error occurred while decoding the media resource, after\
the resource was established to be usable.');
break;
case 4:
console.error('MEDIA_ERR_SRC_NOT_SUPPORTED: The media resource indicated by the src attribute was not suitable.');
break;
default:
console.error('An unknown error occurred.');
break;
}
e.stopPropagation();
});
/*
This event handler helps to execute hotkeys functions related to 'keydown' events
before any user interactions with the player or when focused on other non-input
Expand Down Expand Up @@ -675,7 +715,7 @@ function VideoJSPlayer({
* change the player and the state accordingly.
*/
const handleEnded = () => {
if (!autoAdvanceRef.current && !hasMultiItems) {
if (!autoAdvanceRef.current && !hasMultiItems || canvasIsEmptyRef.current) {
return;
}

Expand Down
5 changes: 3 additions & 2 deletions src/components/MediaPlayer/VideoJS/VideoJSPlayer.scss
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
border-radius: 0.3em;
cursor: pointer;
font-size: medium;
top: -0.1em;
position: relative;
display: flex;
align-items: center;
gap: 0.25em;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,23 @@ function NextButton({
let nextRef = React.useRef();
const [cIndex, setCIndex] = React.useState(player.canvasIndex || 0);

/**
* Use both canvasIndex and player.src() as dependecies, since the same
* resource can appear in 2 consecutive canvases in a multi-canvas manifest.
* E.g. 2 playlist items created from the same resource in an Avalon playlist
* manifest.
*/
React.useEffect(() => {
if (player && player != undefined) {
setCIndex(player.canvasIndex);
// When canvasIndex property is not set in the player instance use dataset.
// This happens rarely, but when it does next button cannot be used.
if (player.canvasIndex === undefined && player.children()?.length > 0) {
setCIndex(Number(player.children()[0].dataset.canvasindex));
} else {
setCIndex(player.canvasIndex);
}
}
}, [player.src()]);
}, [player.src(), player.canvasIndex]);

React.useEffect(() => {
if (playerFocusElement == 'nextBtn') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,23 @@ function PreviousButton({
let previousRef = React.useRef();
const [cIndex, setCIndex] = React.useState(player.canvasIndex || 0);

/**
* Use both canvasIndex and player.src() as dependecies, since the same
* resource can appear in 2 consecutive canvases in a multi-canvas manifest.
* E.g. 2 playlist items created from the same resource in an Avalon playlist
* manifest.
*/
React.useEffect(() => {
if (player && player != undefined) {
setCIndex(player.canvasIndex);
// When canvasIndex property is not set in the player instance use dataset.
// This happens rarely, but when it does previous button cannot be used.
if (player.canvasIndex === undefined && player.children()?.length > 0) {
setCIndex(Number(player.children()[0].dataset.canvasindex));
} else {
setCIndex(player.canvasIndex);
}
}
}, [player.src()]);
}, [player.src(), player.canvasIndex]);

React.useEffect(() => {
if (playerFocusElement == 'previousBtn') {
Expand Down

0 comments on commit 22f8d13

Please sign in to comment.