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

Add previous/next section buttons to player icons #188

Merged
merged 1 commit into from
May 30, 2023
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
2 changes: 2 additions & 0 deletions docs/build/bundle.129e3a17.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions docs/build/bundle.59265786.js

This file was deleted.

2 changes: 1 addition & 1 deletion docs/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,6 @@
</head>
<body>
<div id="rsg-root"></div>
<script src="build/bundle.59265786.js"></script>
<script src="build/bundle.129e3a17.js"></script>
</body>
</html>
40 changes: 37 additions & 3 deletions src/components/MediaPlayer/MediaPlayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import VideoJSPlayer from '@Components/MediaPlayer/VideoJSPlayer';
import ErrorMessage from '@Components/ErrorMessage/ErrorMessage';
import { getMediaInfo, getPoster } from '@Services/iiif-parser';
import { canvasesInManifest, getMediaInfo, getPoster } from '@Services/iiif-parser';
import { getMediaFragment } from '@Services/utility-helpers';
import {
useManifestDispatch,
Expand Down Expand Up @@ -30,6 +30,8 @@ const MediaPlayer = ({ enableFileDownload = false }) => {
const [ready, setReady] = React.useState(false);
const [cIndex, setCIndex] = React.useState(canvasIndex);
const [isMultiSource, setIsMultiSource] = React.useState();
const [isMultiCanvased, setIsMultiCanvased] = React.useState(false);
const [lastCanvasIndex, setLastCanvasIndex] = React.useState(0);

const { canvasIndex, manifest, canvasDuration, srcIndex, targets } =
manifestState;
Expand All @@ -38,6 +40,12 @@ const MediaPlayer = ({ enableFileDownload = false }) => {
React.useEffect(() => {
if (manifest) {
initCanvas(canvasIndex);

// flag to identify multiple canvases in the manifest
// to render previous/next buttons
const canvases = canvasesInManifest(manifest);
setIsMultiCanvased(canvases.length > 1 ? true : false);
setLastCanvasIndex(canvases.length - 1);
}

return () => {
Expand Down Expand Up @@ -143,8 +151,14 @@ const MediaPlayer = ({ enableFileDownload = false }) => {
};

// Switch player when navigating across canvases
const switchPlayer = () => {
initCanvas(canvasIndex);
const switchPlayer = (index) => {
if (canvasIndex != index) {
manifestDispatch({
canvasIndex: index,
type: 'switchCanvas',
});
}
initCanvas(index);
};

// Load next canvas in the list when current media ends
Expand All @@ -164,7 +178,9 @@ const MediaPlayer = ({ enableFileDownload = false }) => {
// See https://docs.videojs.com/tutorial-components.html for options of what
// seem to be supported controls
children: [
isMultiCanvased ? 'videoJSPreviousButton' : '',
'playToggle',
isMultiCanvased ? 'videoJSNextButton' : '',
'volumePanel',
'videoJSProgress',
'videoJSCurrentTime',
Expand Down Expand Up @@ -207,6 +223,24 @@ const MediaPlayer = ({ enableFileDownload = false }) => {
};
}

if (isMultiCanvased) {
videoJsOptions = {
...videoJsOptions,
controlBar: {
...videoJsOptions.controlBar,
videoJSPreviousButton: {
canvasIndex,
switchPlayer
},
videoJSNextButton: {
canvasIndex,
lastCanvasIndex,
switchPlayer
},
}
};
}

return ready ? (
<div
data-testid="media-player"
Expand Down
39 changes: 38 additions & 1 deletion src/components/MediaPlayer/MediaPlayer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { withManifestAndPlayerProvider } from '../../services/testing-helpers';
import MediaPlayer from './MediaPlayer';
import audioManifest from '@Json/test_data/mahler-symphony-audio';
import audioManifest from '@Json/test_data/transcript-canvas';
import videoManifest from '@Json/test_data/lunchroom-manners';
import multiSrcManifest from '@Json/test_data/transcript-annotation';

describe('MediaPlayer component', () => {
describe('with audio manifest', () => {
Expand Down Expand Up @@ -67,4 +68,40 @@ describe('MediaPlayer component', () => {
expect(screen.queryByTestId('videojs-file-download')).toBeInTheDocument();
});
});

describe('with a manifest', () => {
describe('with a single canvas', () => {
test('does not render previous/next section buttons', () => {
const PlayerWithManifest = withManifestAndPlayerProvider(MediaPlayer, {
initialManifestState: { manifest: audioManifest, canvasIndex: 0 },
initialPlayerState: {},
});
render(<PlayerWithManifest />);
expect(screen.queryByTestId('videojs-next-button')).not.toBeInTheDocument();
expect(screen.queryByTestId('videojs-previous-button')).not.toBeInTheDocument();
});

test('with multiple sources does not render previous/next buttons', () => {
const PlayerWithManifest = withManifestAndPlayerProvider(MediaPlayer, {
initialManifestState: { manifest: multiSrcManifest, canvasIndex: 0 },
initialPlayerState: {},
});
render(<PlayerWithManifest />);
expect(screen.queryByTestId('videojs-next-button')).not.toBeInTheDocument();
expect(screen.queryByTestId('videojs-previous-button')).not.toBeInTheDocument();
});
});

describe('with multiple canvases', () => {
test('renders previous/next section buttons', () => {
const PlayerWithManifest = withManifestAndPlayerProvider(MediaPlayer, {
initialManifestState: { manifest: videoManifest, canvasIndex: 0 },
initialPlayerState: {},
});
render(<PlayerWithManifest />);
expect(screen.queryByTestId('videojs-next-button')).toBeInTheDocument();
expect(screen.queryByTestId('videojs-previous-button')).toBeInTheDocument();
});
});
});
});

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
import videojs from 'video.js';
import { timeToHHmmss } from '../../../services/utility-helpers';
import './VideoJSCurrentTime.scss';
import { timeToHHmmss } from '../../../../services/utility-helpers';
import '../styles/VideoJSCurrentTime.scss';

const vjsComponent = videojs.getComponent('Component');

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,38 @@
import React from 'react';
import ReactDOM from 'react-dom';
import videojs from 'video.js';
import './VideoJSFileDownload.scss';
import VideoJSDownloadIcon from './VideoJSDownloadIcon';
import '../styles/VideoJSFileDownload.scss';
import { getRenderingFiles } from '@Services/iiif-parser';
import { fileDownload } from '@Services/utility-helpers';

const vjsComponent = videojs.getComponent('Component');

/** SVG for the download icon */
const DownloadIcon = ({ scale }) => {
return (
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 330 330"
style={{ enableBackground: "new 0 0 4 490 490", fill: 'white', height: '1.25rem', scale: scale }}>
<g id="XMLID_23_">
<path id="XMLID_24_" d="M154.389,255.602c0.351,0.351,0.719,0.683,1.103,0.998c0.169,0.138,0.347,0.258,0.52,0.388
c0.218,0.164,0.432,0.333,0.659,0.484c0.212,0.142,0.432,0.265,0.649,0.395c0.202,0.121,0.4,0.248,0.608,0.359
c0.223,0.12,0.453,0.221,0.681,0.328c0.215,0.102,0.427,0.21,0.648,0.301c0.223,0.092,0.45,0.167,0.676,0.247
c0.236,0.085,0.468,0.175,0.709,0.248c0.226,0.068,0.456,0.119,0.684,0.176c0.246,0.062,0.489,0.131,0.739,0.181
c0.263,0.052,0.529,0.083,0.794,0.121c0.219,0.031,0.435,0.073,0.658,0.095c0.492,0.048,0.986,0.075,1.48,0.075
c0.494,0,0.988-0.026,1.479-0.075c0.226-0.022,0.444-0.064,0.667-0.096c0.262-0.037,0.524-0.068,0.784-0.12
c0.255-0.05,0.504-0.121,0.754-0.184c0.223-0.057,0.448-0.105,0.669-0.172c0.246-0.075,0.483-0.167,0.724-0.253
c0.221-0.08,0.444-0.152,0.662-0.242c0.225-0.093,0.44-0.202,0.659-0.306c0.225-0.106,0.452-0.206,0.672-0.324
c0.21-0.112,0.408-0.239,0.611-0.361c0.217-0.13,0.437-0.252,0.648-0.394c0.222-0.148,0.431-0.314,0.644-0.473
c0.179-0.134,0.362-0.258,0.536-0.4c0.365-0.3,0.714-0.617,1.049-0.949c0.016-0.016,0.034-0.028,0.049-0.044l70.002-69.998
c5.858-5.858,5.858-15.355,0-21.213c-5.857-5.857-15.355-5.858-21.213-0.001l-44.396,44.393V25c0-8.284-6.716-15-15-15
c-8.284,0-15,6.716-15,15v183.785l-44.392-44.391c-5.857-5.858-15.355-5.858-21.213,0c-5.858,5.858-5.858,15.355,0,21.213
L154.389,255.602z"/>
<path id="XMLID_25_" d="M315,160c-8.284,0-15,6.716-15,15v115H30V175c0-8.284-6.716-15-15-15c-8.284,0-15,6.716-15,15v130
c0,8.284,6.716,15,15,15h300c8.284,0,15-6.716,15-15V175C330,166.716,323.284,160,315,160z"/>
</g>
</svg>
);
};

/**
* Custom VideoJS component for providing access to supplementing
* files in a IIIF manifest under the `rendering` property.
Expand All @@ -18,7 +43,6 @@ const vjsComponent = videojs.getComponent('Component');
class VideoJSFileDownload extends vjsComponent {
constructor(player, options) {
super(player, options);
this.addClass('vjs-custom-file-download');
this.setAttribute('data-testid', 'videojs-file-download');

this.mount = this.mount.bind(this);
Expand Down Expand Up @@ -66,7 +90,7 @@ function Downloader({ manifest, canvasIndex }) {
title="Alternate Resource Download"
onMouseEnter={() => setShowMenu(true)}
onMouseLeave={() => setShowMenu(false)}>
<VideoJSDownloadIcon width="1rem" scale="0.9" />
<DownloadIcon width="1rem" scale="0.9" />
</button>
{showMenu && (
<div className='vjs-menu'
Expand All @@ -79,7 +103,7 @@ function Downloader({ manifest, canvasIndex }) {
return <li className='vjs-menu-item' key={index}>
<a href={f.id} className='vjs-menu-item-text'
onClick={e => handleDownload(e, f)}>
<VideoJSDownloadIcon width="0.5rem" scale="0.6" />
<DownloadIcon width="0.5rem" scale="0.6" />
<span>{f.label}</span>
</a>
</li>;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React from 'react';
import ReactDOM from 'react-dom';
import videojs from 'video.js';
import '../styles/VideoJSSectionButtons.scss';

const vjsComponent = videojs.getComponent('Component');

const NextButtonIcon = ({ scale }) => {
return (
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" transform="rotate(0)"
style={{ fill: 'white', height: '1.25rem', width: '1.25rem', scale: scale }}>
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
<g id="SVGRepo_tracerCarrier" strokeLinecap="round" strokeLinejoin="round"></g>
<g id="SVGRepo_iconCarrier">
<path d="M4 20L15.3333 12L4 4V20Z" fill="#ffffff"></path>
<path d="M20 4H17.3333V20H20V4Z" fill="#ffffff"></path>
</g>
</svg>
);
};

/**
* Custom VideoJS component for skipping to the next canvas
* when multiple canvases are present in the manifest
* @param {Object} options
* @param {Number} options.canvasIndex current canvas's index
* @param {Number} options.lastCanvasIndex last canvas's index
* @param {Function} options.switchPlayer callback function switch to next canvas
*/
class VideoJSNextButton extends vjsComponent {
constructor(player, options) {
super(player, options);
this.setAttribute('data-testid', 'videojs-next-button');

this.mount = this.mount.bind(this);
this.options = options;

/* When player is ready, call method to mount React component */
player.ready(() => {
this.mount();
});

/* Remove React root when component is destroyed */
this.on('dispose', () => {
ReactDOM.unmountComponentAtNode(this.el());
});
}

mount() {
ReactDOM.render(
<NextButton {...this.options} />,
this.el()
);
}
}

function NextButton({ canvasIndex, lastCanvasIndex, switchPlayer }) {
const handleNextClick = () => {
if (canvasIndex != lastCanvasIndex) {
switchPlayer(canvasIndex + 1);
}
};

return (
<div className="vjs-button vjs-control">
<button className="vjs-button vjs-next-button"
title={"Next Canvas"}
onClick={handleNextClick}>
<NextButtonIcon scale="0.9" />
</button>
</div >
);
}

vjsComponent.registerComponent('VideoJSNextButton', VideoJSNextButton);

export default VideoJSNextButton;
Loading