From 336551153471e7f72ebf754f8d76178f066ea1cf Mon Sep 17 00:00:00 2001 From: Jack Reed <phillipjreed@gmail.com> Date: Tue, 22 Sep 2020 13:10:36 -0600 Subject: [PATCH] Setup audio and video viewer support --- __tests__/integration/mirador/video.html | 5 ++-- src/components/AVViewer.js | 23 ---------------- src/components/AudioViewer.js | 27 ++++++++++++++++++ src/components/PrimaryWindow.js | 24 +++++++++++----- src/components/VideoViewer.js | 27 ++++++++++++++++++ src/containers/AVViewer.js | 21 -------------- src/containers/AudioViewer.js | 35 ++++++++++++++++++++++++ src/containers/PrimaryWindow.js | 8 ++++-- src/containers/VideoViewer.js | 35 ++++++++++++++++++++++++ src/lib/MiradorCanvas.js | 18 ++++++++++++ src/state/selectors/canvases.js | 16 +++++++++++ 11 files changed, 184 insertions(+), 55 deletions(-) delete mode 100644 src/components/AVViewer.js create mode 100644 src/components/AudioViewer.js create mode 100644 src/components/VideoViewer.js delete mode 100644 src/containers/AVViewer.js create mode 100644 src/containers/AudioViewer.js create mode 100644 src/containers/VideoViewer.js diff --git a/__tests__/integration/mirador/video.html b/__tests__/integration/mirador/video.html index 45c8db36d..f245fb4c2 100644 --- a/__tests__/integration/mirador/video.html +++ b/__tests__/integration/mirador/video.html @@ -16,11 +16,12 @@ windows: [ { manifestId: 'https://preview.iiif.io/cookbook/master/recipe/0003-mvm-video/manifest.json', - view: 'av' }, { manifestId: 'https://preview.iiif.io/cookbook/0026_0064_0065-opera-recipes/recipe/0064-opera-one-canvas/manifest.json', - view: 'av' + }, + { + manifestId: 'https://iiif.io/api/cookbook/recipe/0014-accompanyingcanvas/manifest.json' } ], }); diff --git a/src/components/AVViewer.js b/src/components/AVViewer.js deleted file mode 100644 index d06c1cfa0..000000000 --- a/src/components/AVViewer.js +++ /dev/null @@ -1,23 +0,0 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; - -/** */ -export class AVViewer extends Component { - /** */ - render() { - const { currentCanvases } = this.props; - const video = currentCanvases[0].getContent()[0].getBody()[0]; - return ( - <div style={{ display: 'flex', 'alignItems': 'center' }}> - <video controls> - <source src={video.id} type={video.format} /> - </video> - </div> - ); - } -} - -AVViewer.propTypes = { - currentCanvases: PropTypes.array.isRequired, // eslint-disable-line react/forbid-prop-types - windowId: PropTypes.string.isRequired, -}; diff --git a/src/components/AudioViewer.js b/src/components/AudioViewer.js new file mode 100644 index 000000000..9c5d6c69c --- /dev/null +++ b/src/components/AudioViewer.js @@ -0,0 +1,27 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +/** */ +export class AudioViewer extends Component { + /* eslint-disable jsx-a11y/media-has-caption */ + /** */ + render() { + const { classes, audioResources } = this.props; + const audio = audioResources && audioResources[0]; + if (!audio) return <></>; + + return ( + <div className={classes.container}> + <audio controls className={classes.audio}> + <source src={audio.id} type={audio.getFormat()} /> + </audio> + </div> + ); + } + /* eslint-enable jsx-a11y/media-has-caption */ +} + +AudioViewer.propTypes = { + audioResources: PropTypes.arrayOf(PropTypes.object).isRequired, + classes: PropTypes.objectOf(PropTypes.string).isRequired, +}; diff --git a/src/components/PrimaryWindow.js b/src/components/PrimaryWindow.js index df0e9e89a..97728d889 100644 --- a/src/components/PrimaryWindow.js +++ b/src/components/PrimaryWindow.js @@ -6,28 +6,29 @@ import CompanionArea from '../containers/CompanionArea'; import CollectionDialog from '../containers/CollectionDialog'; import ns from '../config/css-ns'; +const AudioViewer = lazy(() => import('../containers/AudioViewer')); const GalleryView = lazy(() => import('../containers/GalleryView')); const SelectCollection = lazy(() => import('../containers/SelectCollection')); const WindowViewer = lazy(() => import('../containers/WindowViewer')); -const AVViewer = lazy(() => import('../containers/AVViewer')); +const VideoViewer = lazy(() => import('../containers/VideoViewer')); GalleryView.displayName = 'GalleryView'; SelectCollection.displayName = 'SelectCollection'; WindowViewer.displayName = 'WindowViewer'; /** - * WindowMiddleContent - component that renders the "middle" area of the - * Mirador Window + * PrimaryWindow - component that renders the primary content of a Mirador + * window. Right now this differentiates between a Image, Video, or Audio viewer. */ export class PrimaryWindow extends Component { /** - * renderViewer + * renderViewer - logic used to determine what type of view to show * * @return {(String|null)} */ renderViewer() { const { - isCollection, isCollectionDialogVisible, isFetching, view, windowId, + audioResources, isCollection, isCollectionDialogVisible, isFetching, videoResources, view, windowId, } = this.props; if (isCollection) { return ( @@ -47,9 +48,16 @@ export class PrimaryWindow extends Component { /> ); } - if (view === 'av') { + if (videoResources.length > 0) { return ( - <AVViewer + <VideoViewer + windowId={windowId} + /> + ); + } + if (audioResources.length > 0) { + return ( + <AudioViewer windowId={windowId} /> ); @@ -81,10 +89,12 @@ export class PrimaryWindow extends Component { } PrimaryWindow.propTypes = { + audioResources: PropTypes.arrayOf(PropTypes.object).isRequired, classes: PropTypes.objectOf(PropTypes.string).isRequired, isCollection: PropTypes.bool, isCollectionDialogVisible: PropTypes.bool, isFetching: PropTypes.bool, + videoResources: PropTypes.arrayOf(PropTypes.object).isRequired, view: PropTypes.string, windowId: PropTypes.string.isRequired, }; diff --git a/src/components/VideoViewer.js b/src/components/VideoViewer.js new file mode 100644 index 000000000..7a31969fa --- /dev/null +++ b/src/components/VideoViewer.js @@ -0,0 +1,27 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; + +/** */ +export class VideoViewer extends Component { + /* eslint-disable jsx-a11y/media-has-caption */ + /** */ + render() { + const { classes, videoResources } = this.props; + const video = videoResources && videoResources[0]; + if (!video) return <></>; + + return ( + <div className={classes.container}> + <video controls className={classes.video}> + <source src={video.id} type={video.getFormat()} /> + </video> + </div> + ); + } + /* eslint-enable jsx-a11y/media-has-caption */ +} + +VideoViewer.propTypes = { + classes: PropTypes.objectOf(PropTypes.string).isRequired, + videoResources: PropTypes.arrayOf(PropTypes.object).isRequired, +}; diff --git a/src/containers/AVViewer.js b/src/containers/AVViewer.js deleted file mode 100644 index b6e452ef3..000000000 --- a/src/containers/AVViewer.js +++ /dev/null @@ -1,21 +0,0 @@ -import { connect } from 'react-redux'; -import { compose } from 'redux'; -import { withTranslation } from 'react-i18next'; -import { withPlugins } from '../extend/withPlugins'; -import { AVViewer } from '../components/AVViewer'; -import { getVisibleCanvases } from '../state/selectors'; - -/** */ -const mapStateToProps = (state, { windowId }) => ( - { - currentCanvases: getVisibleCanvases(state, { windowId }) || [], - } -); - -const enhance = compose( - withTranslation(), - connect(mapStateToProps, null), - withPlugins('AVViewer'), -); - -export default enhance(AVViewer); diff --git a/src/containers/AudioViewer.js b/src/containers/AudioViewer.js new file mode 100644 index 000000000..65d198f5a --- /dev/null +++ b/src/containers/AudioViewer.js @@ -0,0 +1,35 @@ +import { connect } from 'react-redux'; +import { compose } from 'redux'; +import { withTranslation } from 'react-i18next'; +import { withStyles } from '@material-ui/core'; +import { withPlugins } from '../extend/withPlugins'; +import { AudioViewer } from '../components/AudioViewer'; +import { getVisibleCanvasAudioResources } from '../state/selectors'; + +/** */ +const mapStateToProps = (state, { windowId }) => ( + { + audioResources: getVisibleCanvasAudioResources(state, { windowId }) || [], + } +); + +/** */ +const styles = () => ({ + audio: { + width: '100%', + }, + container: { + alignItems: 'center', + display: 'flex', + width: '100%', + }, +}); + +const enhance = compose( + withTranslation(), + withStyles(styles), + connect(mapStateToProps, null), + withPlugins('AudioViewer'), +); + +export default enhance(AudioViewer); diff --git a/src/containers/PrimaryWindow.js b/src/containers/PrimaryWindow.js index 35a86dfa2..336894235 100644 --- a/src/containers/PrimaryWindow.js +++ b/src/containers/PrimaryWindow.js @@ -2,15 +2,19 @@ import { compose } from 'redux'; import { connect } from 'react-redux'; import { withStyles } from '@material-ui/core/styles'; import { withPlugins } from '../extend/withPlugins'; -import { getManifestoInstance, getWindow } from '../state/selectors'; +import { + getManifestoInstance, getVisibleCanvasAudioResources, getVisibleCanvasVideoResources, getWindow, +} from '../state/selectors'; import { PrimaryWindow } from '../components/PrimaryWindow'; /** */ const mapStateToProps = (state, { windowId }) => { const manifestoInstance = getManifestoInstance(state, { windowId }); return { + audioResources: getVisibleCanvasAudioResources(state, { windowId }) || [], isCollection: manifestoInstance && manifestoInstance.isCollection(), isCollectionDialogVisible: getWindow(state, { windowId }).collectionDialogOn, + videoResources: getVisibleCanvasVideoResources(state, { windowId }) || [], }; }; @@ -24,7 +28,7 @@ const styles = { const enhance = compose( withStyles(styles), - connect(mapStateToProps), + connect(mapStateToProps, null), withPlugins('PrimaryWindow'), ); diff --git a/src/containers/VideoViewer.js b/src/containers/VideoViewer.js new file mode 100644 index 000000000..97f762036 --- /dev/null +++ b/src/containers/VideoViewer.js @@ -0,0 +1,35 @@ +import { connect } from 'react-redux'; +import { compose } from 'redux'; +import { withTranslation } from 'react-i18next'; +import { withStyles } from '@material-ui/core'; +import { withPlugins } from '../extend/withPlugins'; +import { VideoViewer } from '../components/VideoViewer'; +import { getVisibleCanvasVideoResources } from '../state/selectors'; + +/** */ +const mapStateToProps = (state, { windowId }) => ( + { + videoResources: getVisibleCanvasVideoResources(state, { windowId }) || [], + } +); + +/** */ +const styles = () => ({ + container: { + alignItems: 'center', + display: 'flex', + width: '100%', + }, + video: { + width: '100%', + }, +}); + +const enhance = compose( + withTranslation(), + withStyles(styles), + connect(mapStateToProps, null), + withPlugins('VideoViewer'), +); + +export default enhance(VideoViewer); diff --git a/src/lib/MiradorCanvas.js b/src/lib/MiradorCanvas.js index ac375b1df..ebd45f2f5 100644 --- a/src/lib/MiradorCanvas.js +++ b/src/lib/MiradorCanvas.js @@ -81,6 +81,24 @@ export default class MiradorCanvas { })); } + /** */ + get videoResources() { + const resources = flattenDeep([ + this.canvas.getContent().map(i => i.getBody()), + ]); + + return flatten(resources.filter((resource) => resource.getProperty('type') === 'Video')); + } + + /** */ + get audioResources() { + const resources = flattenDeep([ + this.canvas.getContent().map(i => i.getBody()), + ]); + + return flatten(resources.filter((resource) => resource.getProperty('type') === 'Sound')); + } + /** */ get resourceAnnotations() { return flattenDeep([ diff --git a/src/state/selectors/canvases.js b/src/state/selectors/canvases.js index 4a3c99248..71b3e274e 100644 --- a/src/state/selectors/canvases.js +++ b/src/state/selectors/canvases.js @@ -184,6 +184,22 @@ export const getVisibleCanvasNonTiledResources = createSelector( .filter(resource => resource.getServices().length < 1), ); +export const getVisibleCanvasVideoResources = createSelector( + [ + getVisibleCanvases, + ], + canvases => flatten(canvases + .map(canvas => new MiradorCanvas(canvas).videoResources)), +); + +export const getVisibleCanvasAudioResources = createSelector( + [ + getVisibleCanvases, + ], + canvases => flatten(canvases + .map(canvas => new MiradorCanvas(canvas).audioResources)), +); + export const selectInfoResponse = createSelector( [ (state, { infoId }) => infoId, -- GitLab