diff --git a/__tests__/fixtures/version-3/video_annotations.json b/__tests__/fixtures/version-3/video_annotations.json new file mode 100644 index 0000000000000000000000000000000000000000..f3a02cc14db2eb1a5a0de63486d83571334dbc56 --- /dev/null +++ b/__tests__/fixtures/version-3/video_annotations.json @@ -0,0 +1,57 @@ +{ + "@context": "http://iiif.io/api/presentation/3/context.json", + "id": "https://iiif.io/api/cookbook/recipe/0266-full-canvas-annotation/manifest.json", + "type": "Manifest", + "label": { "en": [ "Video Example 3" ] }, + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0003-mvm-video-annot/canvas", + "type": "Canvas", + "height": 360, + "width": 640, + "duration": 572.034, + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0003-mvm-video-annot/canvas/page", + "type": "AnnotationPage", + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0003-mvm-video-annot/canvas/page/annotation", + "type": "Annotation", + "motivation": "painting", + "body": { + "id": "https://fixtures.iiif.io/video/indiana/lunchroom_manners/high/lunchroom_manners_1024kb.mp4", + "type": "Video", + "height": 360, + "width": 480, + "duration": 572.034, + "format": "video/mp4" + }, + "target": "https://iiif.io/api/cookbook/recipe/0003-mvm-video-annot/canvas" + } + ] + } + ], + "annotations": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0266-full-canvas-annotation/canvas-1/annopage-2", + "type": "AnnotationPage", + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0266-full-canvas-annotation/canvas-1/annopage-2/anno-1", + "type": "Annotation", + "motivation": "commenting", + "body": { + "type": "TextualBody", + "language": "de", + "format": "text/plain", + "value": "Göttinger Marktplatz mit Gänseliesel Brunnen" + }, + "target": "https://iiif.io/api/cookbook/recipe/0003-mvm-video-annot/canvas/page/annotation" + } + ] + } + ] + } + ] +} diff --git a/__tests__/fixtures/version-3/video_captions.json b/__tests__/fixtures/version-3/video_captions.json new file mode 100644 index 0000000000000000000000000000000000000000..c23a5adbbe4331388ea2836e6ada1c6fb9dca6b9 --- /dev/null +++ b/__tests__/fixtures/version-3/video_captions.json @@ -0,0 +1,60 @@ +{ + "@context": "http://iiif.io/api/presentation/3/context.json", + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/manifest.json", + "type": "Manifest", + "label": { + "en": [ + "Lunchroom Manners" + ] + }, + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas", + "type": "Canvas", + "height": 360, + "width": 480, + "duration": 572.034, + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas/page", + "type": "AnnotationPage", + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas/page/annotation1", + "type": "Annotation", + "motivation": "painting", + "body": { + "id": "https://fixtures.iiif.io/video/indiana/lunchroom_manners/high/lunchroom_manners_1024kb.mp4", + "type": "Video", + "height": 360, + "width": 480, + "duration": 572.034, + "format": "video/mp4" + }, + "target": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas" + } + ] + } + ], + "annotations": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas/page2", + "type": "AnnotationPage", + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas/page2/a1", + "type": "Annotation", + "motivation": "supplementing", + "body": { + "id": "https://fixtures.iiif.io/video/indiana/lunchroom_manners/lunchroom_manners.vtt", + "format": "text/vtt", + "language": "en" + }, + "target": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas" + } + ] + } + ] + } + ] +} diff --git a/__tests__/fixtures/version-3/video_captions_other.json b/__tests__/fixtures/version-3/video_captions_other.json new file mode 100644 index 0000000000000000000000000000000000000000..1c90a90880cbbe281ac54c0e08ce11bbc71d87a7 --- /dev/null +++ b/__tests__/fixtures/version-3/video_captions_other.json @@ -0,0 +1,78 @@ +{ + "@context": "http://iiif.io/api/presentation/3/context.json", + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/manifest.json", + "type": "Manifest", + "label": { + "en": [ + "Lunchroom Manners" + ] + }, + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas", + "type": "Canvas", + "height": 360, + "width": 480, + "duration": 572.034, + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas/page", + "type": "AnnotationPage", + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas/page/annotation1", + "type": "Annotation", + "motivation": "painting", + "body": { + "id": "https://fixtures.iiif.io/video/indiana/lunchroom_manners/high/lunchroom_manners_1024kb.mp4", + "type": "Video", + "height": 360, + "width": 480, + "duration": 572.034, + "format": "video/mp4" + }, + "target": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas" + } + ] + } + ], + "annotations": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas/page2", + "type": "AnnotationPage", + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas/page2/a1", + "type": "Annotation", + "motivation": "supplementing", + "body": [ + { + "id": "https://fixtures.iiif.io/video/indiana/lunchroom_manners/lunchroom_manners.vtt#fr", + "format": "text/vtt", + "language": "fr" + }, + { + "id": "https://fixtures.iiif.io/video/indiana/lunchroom_manners/lunchroom_manners.vtt#en", + "format": "text/vtt", + "language": "en" + } + ], + "target": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas" + }, + { + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas/page2/a1", + "type": "Annotation", + "motivation": "supplementing", + "body": { + "id": "https://fixtures.iiif.io/video/indiana/lunchroom_manners/lunchroom_manners.vtt#ru", + "format": "text/vtt", + "language": "ru" + }, + "target": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas" + } + ] + } + ] + } + ] +} diff --git a/__tests__/fixtures/version-3/video_multiples_captions.json b/__tests__/fixtures/version-3/video_multiples_captions.json new file mode 100644 index 0000000000000000000000000000000000000000..e232895458f12c0b8570bf00b2bb7b42b8e7c61e --- /dev/null +++ b/__tests__/fixtures/version-3/video_multiples_captions.json @@ -0,0 +1,67 @@ +{ + "@context": "http://iiif.io/api/presentation/3/context.json", + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/manifest.json", + "type": "Manifest", + "label": { + "en": [ + "Lunchroom Manners" + ] + }, + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas", + "type": "Canvas", + "height": 360, + "width": 480, + "duration": 572.034, + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas/page", + "type": "AnnotationPage", + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas/page/annotation1", + "type": "Annotation", + "motivation": "painting", + "body": { + "id": "https://fixtures.iiif.io/video/indiana/lunchroom_manners/high/lunchroom_manners_1024kb.mp4", + "type": "Video", + "height": 360, + "width": 480, + "duration": 572.034, + "format": "video/mp4" + }, + "target": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas" + } + ] + } + ], + "annotations": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas/page2", + "type": "AnnotationPage", + "items": [ + { + "id": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas/page2/a1", + "type": "Annotation", + "motivation": "supplementing", + "body": [ + { + "id": "https://fixtures.iiif.io/video/indiana/lunchroom_manners/lunchroom_manners.vtt#fr", + "format": "text/vtt", + "language": "fr" + }, + { + "id": "https://fixtures.iiif.io/video/indiana/lunchroom_manners/lunchroom_manners.vtt#en", + "format": "text/vtt", + "language": "en" + } + ], + "target": "https://iiif.io/api/cookbook/recipe/0219-using-caption-file/canvas" + } + ] + } + ] + } + ] +} diff --git a/__tests__/src/components/VideoViewer.test.js b/__tests__/src/components/VideoViewer.test.js index 5de8ae713c85de7f12ab7125d2fed9ab773c2ffe..8526e6ebe19b19f4885c56184122c495daed8d26 100644 --- a/__tests__/src/components/VideoViewer.test.js +++ b/__tests__/src/components/VideoViewer.test.js @@ -1,8 +1,12 @@ import React from 'react'; import { shallow } from 'enzyme'; import { Utils } from 'manifesto.js'; +import AnnotationFactory from '../../../src/lib/AnnotationFactory'; import { VideoViewer } from '../../../src/components/VideoViewer'; import videoSimple from '../../fixtures/version-3/video.json'; +import videoCaptions from '../../fixtures/version-3/video_captions.json'; +import videoMultiCaptions from '../../fixtures/version-3/video_multiples_captions.json'; +import videoMultiCaptionsMultiAnno from '../../fixtures/version-3/video_captions_other.json'; /** create wrapper */ function createWrapper(props, suspenseFallback) { @@ -18,17 +22,47 @@ function createWrapper(props, suspenseFallback) { describe('VideoViewer', () => { let wrapper; describe('render', () => { - const canvasSimple = Utils.parseManifest(videoSimple).getSequences()[0].getCanvases()[0]; - it('videoResources', () => { + it('video', () => { wrapper = createWrapper({ - canvas: canvasSimple, + canvas: Utils.parseManifest(videoSimple).getSequences()[0].getCanvases()[0], }, true); + expect(wrapper.exists('video[crossOrigin="anonymous"]')).toBe(true); // eslint-disable-line jsx-a11y/media-has-caption expect(wrapper.contains(<source src="https://fixtures.iiif.io/video/indiana/30-minute-clock/medium/30-minute-clock.mp4" type="video/mp4" />)).toBe(true); }); - it('passes through configurable options', () => { + it('one caption', () => { + const canvas = Utils.parseManifest(videoCaptions).getSequences()[0].getCanvases()[0]; + /* cf selectors/annotations/getPresentAnnotationsCanvas */ + const annotations = canvas.__jsonld.annotations.flatMap((a) => AnnotationFactory.determineAnnotation(a)); + wrapper = createWrapper({ + annotations, + canvas, + }, true); + expect(wrapper.contains(<track src="https://fixtures.iiif.io/video/indiana/lunchroom_manners/lunchroom_manners.vtt" srcLang="en" />)).toBe(true); + expect(wrapper.exists('video[crossOrigin="anonymous"]')).toBe(true); // eslint-disable-line jsx-a11y/media-has-caption + }); + it('multiples captions', () => { + const canvas = Utils.parseManifest(videoMultiCaptions).getSequences()[0].getCanvases()[0]; + /* cf selectors/annotations/getPresentAnnotationsCanvas */ + const annotations = canvas.__jsonld.annotations.flatMap((a) => AnnotationFactory.determineAnnotation(a)); + wrapper = createWrapper({ + annotations, + canvas, + }, true); + expect(wrapper.contains(<track src="https://fixtures.iiif.io/video/indiana/lunchroom_manners/lunchroom_manners.vtt#en" srcLang="en" />)).toBe(true); + expect(wrapper.contains(<track src="https://fixtures.iiif.io/video/indiana/lunchroom_manners/lunchroom_manners.vtt#fr" srcLang="fr" />)).toBe(true); + expect(wrapper.exists('video[crossOrigin="anonymous"]')).toBe(true); // eslint-disable-line jsx-a11y/media-has-caption + }); + it('multiples captions in multiples annotations', () => { + const canvas = Utils.parseManifest(videoMultiCaptionsMultiAnno).getSequences()[0].getCanvases()[0]; + /* cf selectors/annotations/getPresentAnnotationsCanvas */ + const annotations = canvas.__jsonld.annotations.flatMap((a) => AnnotationFactory.determineAnnotation(a)); wrapper = createWrapper({ - canvas: canvasSimple, + annotations, + canvas, }, true); + expect(wrapper.contains(<track src="https://fixtures.iiif.io/video/indiana/lunchroom_manners/lunchroom_manners.vtt#en" srcLang="en" />)).toBe(true); + expect(wrapper.contains(<track src="https://fixtures.iiif.io/video/indiana/lunchroom_manners/lunchroom_manners.vtt#fr" srcLang="fr" />)).toBe(true); + expect(wrapper.contains(<track src="https://fixtures.iiif.io/video/indiana/lunchroom_manners/lunchroom_manners.vtt#ru" srcLang="ru" />)).toBe(true); expect(wrapper.exists('video[crossOrigin="anonymous"]')).toBe(true); // eslint-disable-line jsx-a11y/media-has-caption }); }); diff --git a/src/components/VideoViewer.js b/src/components/VideoViewer.js index d13ebbdafd40db0370f8fd8b840960ce6e03cdaf..bc0403f2acbd77e7d33ecdd069772817c1c78e42 100644 --- a/src/components/VideoViewer.js +++ b/src/components/VideoViewer.js @@ -21,18 +21,11 @@ export class VideoViewer extends Component { /** */ componentDidMount() { - const { annotations, setHasTextTrack, setPaused } = this.props; + const { setPaused, setHasTextTrack } = this.props; setPaused(true); - const vttContent = flatten( - flattenDeep([ - annotations.map(annotation => annotation.resources.map( - resources_ => resources_.resource, - )), - ]).filter(resource => resource.body && resource.body[0] && resource.body[0].format === 'text/vtt'), - ); - if (vttContent && vttContent.length > 0) { - setHasTextTrack(true); - } + + const video = this.videoRef.current; + if (video && video.textTracks.length > 0) setHasTextTrack(true); } /** */ @@ -69,7 +62,7 @@ export class VideoViewer extends Component { video.muted = muted; } if (video.textTracks && video.textTracks.length > 0) { - const newMode = textTrackDisabled ? 'hidden' : 'showing'; + const newMode = textTrackDisabled ? 'disabled' : 'showing'; if (video.textTracks[0].mode !== newMode) { video.textTracks[0].mode = newMode; } @@ -135,13 +128,10 @@ export class VideoViewer extends Component { }), ]).filter((resource) => resource.body && resource.body[0].__jsonld && resource.body[0].__jsonld.type === 'Video'), ); - const vttContent = flatten( - flattenDeep([ - annotations.map(annotation => annotation.resources.map( - resources_ => resources_.resource, - )), - ]).filter(resource => resource.body && resource.body[0] && resource.body[0].format === 'text/vtt'), - ); + + const vttContent = annotations + .flatMap(annoPage => annoPage.json.items.map(anno => anno.body)) + .flat().filter((body) => body.format === 'text/vtt'); // Only one video can be displayed at a time in this implementation. const len = videoResources.length; @@ -149,12 +139,6 @@ export class VideoViewer extends Component { ? videoResources[len - 1].body[0] : null; const videoTargetTemporalfragment = len > 0 ? videoResources[len - 1].temporalfragment : []; - let caption = null; - if (vttContent && vttContent.length > 0) { - caption = { - id: vttContent[0].body[0].id, - }; - } return ( <div className={classes.flexContainer}> <div className={classes.flexFill}> @@ -162,9 +146,7 @@ export class VideoViewer extends Component { <> <video className={classes.video} key={video.id} ref={this.videoRef} {...videoOptions}> <source src={video.id} type={video.getFormat()} /> - { caption && ( - <track src={caption.id} /> - )} + { vttContent.map(vttc => (<track key={vttc.id} src={vttc.id} srcLang={vttc.language} />)) } </video> <AnnotationsOverlayVideo windowId={windowId} videoRef={this.videoRef} videoTarget={videoTargetTemporalfragment} key={`${windowId} ${video.id}`} /> </>