diff --git a/__tests__/fixtures/version-3/0002-mvm-audio.json b/__tests__/fixtures/version-3/0002-mvm-audio.json
new file mode 100644
index 0000000000000000000000000000000000000000..75aaf7bf56966f4bdc3ad7975537e659244934c9
--- /dev/null
+++ b/__tests__/fixtures/version-3/0002-mvm-audio.json
@@ -0,0 +1,37 @@
+{
+  "@context": "http://iiif.io/api/presentation/3/context.json",
+  "id": "https://iiif.io/api/cookbook/recipe/0002-mvm-audio/manifest.json",
+  "type": "Manifest",
+  "label": {
+    "en": [
+      "Simplest Audio Example 1"
+    ]
+  },
+  "items": [
+    {
+      "id": "https://iiif.io/api/cookbook/recipe/0002-mvm-audio/canvas",
+      "type": "Canvas",
+      "duration": 1985.024,
+      "items": [
+        {
+          "id": "https://iiif.io/api/cookbook/recipe/0002-mvm-audio/canvas/page",
+          "type": "AnnotationPage",
+          "items": [
+            {
+              "id": "https://iiif.io/api/cookbook/recipe/0002-mvm-audio/canvas/page/annotation",
+              "type": "Annotation",
+              "motivation": "painting",
+              "body": {
+                "id": "https://fixtures.iiif.io/audio/indiana/mahler-symphony-3/CD1/medium/128Kbps.mp4",
+                "type": "Sound",
+                "format": "audio/mp4",
+                "duration": 1985.024
+              },
+              "target": "https://iiif.io/api/cookbook/recipe/0002-mvm-audio/canvas/page"
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/__tests__/fixtures/version-3/0015-start.json b/__tests__/fixtures/version-3/0015-start.json
new file mode 100644
index 0000000000000000000000000000000000000000..65308e44a609a6af5070865b7cf22175202f5d1a
--- /dev/null
+++ b/__tests__/fixtures/version-3/0015-start.json
@@ -0,0 +1,52 @@
+{
+  "@context": "http://iiif.io/api/presentation/3/context.json",
+  "id": "https://iiif.io/api/cookbook/recipe/0015-start/manifest.json",
+  "type": "Manifest",
+  "label": {
+    "en": [
+      "Video of a 30-minute digital clock"
+    ]
+  },
+  "start": {
+    "id": "https://iiif.io/api/cookbook/recipe/0015-start/canvas-start/segment1",
+    "type": "SpecificResource",
+    "source": "https://iiif.io/api/cookbook/recipe/0015-start/canvas/segment1",
+    "selector": {
+      "type": "PointSelector",
+      "t": 120.5
+    }
+  },
+  "items": [
+    {
+      "id": "https://iiif.io/api/cookbook/recipe/0015-start/canvas/segment1",
+      "type": "Canvas",
+      "duration": 1801.055,
+      "items": [
+        {
+          "id": "https://iiif.io/api/cookbook/recipe/0015-start/annotation/segment1/page",
+          "type": "AnnotationPage",
+          "items": [
+            {
+              "id": "https://iiif.io/api/cookbook/recipe/0015-start/annotation/segment1-video",
+              "type": "Annotation",
+              "motivation": "painting",
+              "body": [
+                {
+                  "id": "https://fixtures.iiif.io/video/indiana/30-minute-clock/medium/30-minute-clock.mp4",
+                  "type": "Video",
+                  "duration": 1801.055,
+                  "format": "video/mp4"
+                },
+                {
+                  "id": "https://example.com/file.vtt",
+                  "format": "text/vtt"
+                }
+              ],
+              "target": "https://iiif.io/api/cookbook/recipe/0015-start/canvas/segment1"
+            }
+          ]
+        }
+      ]
+    }
+  ]
+}
diff --git a/__tests__/src/components/AudioViewer.test.js b/__tests__/src/components/AudioViewer.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..1e807aaeeb4c5c2f4a2e7e5b748cd7b4c83ec9e7
--- /dev/null
+++ b/__tests__/src/components/AudioViewer.test.js
@@ -0,0 +1,42 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import { AudioViewer } from '../../../src/components/AudioViewer';
+
+/** create wrapper */
+function createWrapper(props, suspenseFallback) {
+  return shallow(
+    <AudioViewer
+      classes={{}}
+      {...props}
+    />,
+  );
+}
+
+describe('AudioViewer', () => {
+  let wrapper;
+  describe('render', () => {
+    it('audioResources', () => {
+      wrapper = createWrapper({
+        audioResources: [
+          { getFormat: () => 'video/mp4', id: 1 },
+          { getFormat: () => 'video/mp4', id: 2 },
+        ],
+      }, true);
+      expect(wrapper.contains(<source src="1" type="video/mp4" />));
+      expect(wrapper.contains(<source src="2" type="video/mp4" />));
+    });
+    it('captions', () => {
+      wrapper = createWrapper({
+        audioResources: [
+          { getFormat: () => 'video/mp4', id: 1 },
+        ],
+        captions: [
+          { getLabel: () => 'English', getProperty: () => 'en', id: 1 },
+          { getLabel: () => 'French', getProperty: () => 'fr', id: 2 },
+        ],
+      }, true);
+      expect(wrapper.contains(<track src="1" label="English" srcLang="en" />));
+      expect(wrapper.contains(<track src="2" label="French" srcLang="fr" />));
+    });
+  });
+});
diff --git a/__tests__/src/components/VideoViewer.test.js b/__tests__/src/components/VideoViewer.test.js
new file mode 100644
index 0000000000000000000000000000000000000000..31945a749ea8a6206c0eed0bf9637e0241f2a24f
--- /dev/null
+++ b/__tests__/src/components/VideoViewer.test.js
@@ -0,0 +1,42 @@
+import React from 'react';
+import { shallow } from 'enzyme';
+import { VideoViewer } from '../../../src/components/VideoViewer';
+
+/** create wrapper */
+function createWrapper(props, suspenseFallback) {
+  return shallow(
+    <VideoViewer
+      classes={{}}
+      {...props}
+    />,
+  );
+}
+
+describe('VideoViewer', () => {
+  let wrapper;
+  describe('render', () => {
+    it('videoResources', () => {
+      wrapper = createWrapper({
+        videoResources: [
+          { getFormat: () => 'video/mp4', id: 1 },
+          { getFormat: () => 'video/mp4', id: 2 },
+        ],
+      }, true);
+      expect(wrapper.contains(<source src="1" type="video/mp4" />));
+      expect(wrapper.contains(<source src="2" type="video/mp4" />));
+    });
+    it('captions', () => {
+      wrapper = createWrapper({
+        captions: [
+          { getLabel: () => 'English', getProperty: () => 'en', id: 1 },
+          { getLabel: () => 'French', getProperty: () => 'fr', id: 2 },
+        ],
+        videoResources: [
+          { getFormat: () => 'video/mp4', id: 1 },
+        ],
+      }, true);
+      expect(wrapper.contains(<track src="1" label="English" srcLang="en" />));
+      expect(wrapper.contains(<track src="2" label="French" srcLang="fr" />));
+    });
+  });
+});
diff --git a/__tests__/src/lib/MiradorCanvas.test.js b/__tests__/src/lib/MiradorCanvas.test.js
index e28717725568c34c048fca46c84427f9213b7bd5..e40237b59748f58b00f5575ef9fc99dce49c7f03 100644
--- a/__tests__/src/lib/MiradorCanvas.test.js
+++ b/__tests__/src/lib/MiradorCanvas.test.js
@@ -6,6 +6,8 @@ import otherContentFixture from '../../fixtures/version-2/299843.json';
 import otherContentStringsFixture from '../../fixtures/version-2/BibliographicResource_3000126341277.json';
 import fragmentFixture from '../../fixtures/version-2/hamilton.json';
 import fragmentFixtureV3 from '../../fixtures/version-3/hamilton.json';
+import audioFixture from '../../fixtures/version-3/0002-mvm-audio.json';
+import videoFixture from '../../fixtures/version-3/0015-start.json';
 
 describe('MiradorCanvas', () => {
   let instance;
@@ -100,4 +102,28 @@ describe('MiradorCanvas', () => {
       ).toEqual([552, 1584, 3360, 2368]);
     });
   });
+  describe('videoResources', () => {
+    it('returns video', () => {
+      instance = new MiradorCanvas(
+        Utils.parseManifest(videoFixture).getSequences()[0].getCanvases()[0],
+      );
+      expect(instance.videoResources.length).toEqual(1);
+    });
+  });
+  describe('audioResources', () => {
+    it('returns audio', () => {
+      instance = new MiradorCanvas(
+        Utils.parseManifest(audioFixture).getSequences()[0].getCanvases()[0],
+      );
+      expect(instance.audioResources.length).toEqual(1);
+    });
+  });
+  describe('vttContent', () => {
+    it('returns vttContent', () => {
+      instance = new MiradorCanvas(
+        Utils.parseManifest(videoFixture).getSequences()[0].getCanvases()[0],
+      );
+      expect(instance.vttContent.length).toEqual(1);
+    });
+  });
 });
diff --git a/__tests__/src/selectors/canvases.test.js b/__tests__/src/selectors/canvases.test.js
index c6dcf8145bbf416670ddda930b6dcffe04cd8a93..e8fc9adb8ce1f84de636ddfc70c2b75d7896de70 100644
--- a/__tests__/src/selectors/canvases.test.js
+++ b/__tests__/src/selectors/canvases.test.js
@@ -2,6 +2,8 @@ import manifestFixture001 from '../../fixtures/version-2/001.json';
 import manifestFixture019 from '../../fixtures/version-2/019.json';
 import minimumRequired from '../../fixtures/version-2/minimumRequired.json';
 import minimumRequired3 from '../../fixtures/version-3/minimumRequired.json';
+import audioFixture from '../../fixtures/version-3/0002-mvm-audio.json';
+import videoFixture from '../../fixtures/version-3/0015-start.json';
 import settings from '../../../src/config/settings';
 
 import {
@@ -12,6 +14,9 @@ import {
   getCanvasLabel,
   selectInfoResponse,
   getVisibleCanvasNonTiledResources,
+  getVisibleCanvasVideoResources,
+  getVisibleCanvasAudioResources,
+  getVisibleCanvasCaptions,
   getVisibleCanvasIds,
 } from '../../../src/state/selectors/canvases';
 
@@ -375,4 +380,76 @@ describe('getVisibleCanvasNonTiledResources', () => {
       state, { windowId: 'a' },
     )[0].id).toBe('http://iiif.io/api/presentation/2.1/example/fixtures/resources/page1-full.png');
   });
+
+  describe('getVisibleCanvasVideoResources', () => {
+    it('returns canvases resources', () => {
+      const state = {
+        manifests: {
+          'https://iiif.io/api/cookbook/recipe/0015-start/manifest.json': {
+            id: 'https://iiif.io/api/cookbook/recipe/0015-start/manifest.json',
+            json: videoFixture,
+          },
+        },
+        windows: {
+          a: {
+            manifestId: 'https://iiif.io/api/cookbook/recipe/0015-start/manifest.json',
+            visibleCanvases: [
+              'https://iiif.io/api/cookbook/recipe/0015-start/canvas/segment1',
+            ],
+          },
+        },
+      };
+      expect(getVisibleCanvasVideoResources(
+        state, { windowId: 'a' },
+      )[0].id).toBe('https://fixtures.iiif.io/video/indiana/30-minute-clock/medium/30-minute-clock.mp4');
+    });
+  });
+
+  describe('getVisibleCanvasCaptions', () => {
+    it('returns canvases resources', () => {
+      const state = {
+        manifests: {
+          'https://iiif.io/api/cookbook/recipe/0015-start/manifest.json': {
+            id: 'https://iiif.io/api/cookbook/recipe/0015-start/manifest.json',
+            json: videoFixture,
+          },
+        },
+        windows: {
+          a: {
+            manifestId: 'https://iiif.io/api/cookbook/recipe/0015-start/manifest.json',
+            visibleCanvases: [
+              'https://iiif.io/api/cookbook/recipe/0015-start/canvas/segment1',
+            ],
+          },
+        },
+      };
+      expect(getVisibleCanvasCaptions(
+        state, { windowId: 'a' },
+      )[0].id).toBe('https://example.com/file.vtt');
+    });
+  });
+
+  describe('getVisibleCanvasAudioResources', () => {
+    it('returns canvases resources', () => {
+      const state = {
+        manifests: {
+          'https://iiif.io/api/cookbook/recipe/0002-mvm-audio/manifest.json': {
+            id: 'https://iiif.io/api/cookbook/recipe/0002-mvm-audio/manifest.json',
+            json: audioFixture,
+          },
+        },
+        windows: {
+          a: {
+            manifestId: 'https://iiif.io/api/cookbook/recipe/0002-mvm-audio/manifest.json',
+            visibleCanvases: [
+              'https://iiif.io/api/cookbook/recipe/0002-mvm-audio/canvas',
+            ],
+          },
+        },
+      };
+      expect(getVisibleCanvasAudioResources(
+        state, { windowId: 'a' },
+      )[0].id).toBe('https://fixtures.iiif.io/audio/indiana/mahler-symphony-3/CD1/medium/128Kbps.mp4');
+    });
+  });
 });
diff --git a/src/components/PrimaryWindow.js b/src/components/PrimaryWindow.js
index f7ad1d18b34ec846ba183d9078cf06d974747eb6..3313139085422dbb06c24f01b88834cbf9448446 100644
--- a/src/components/PrimaryWindow.js
+++ b/src/components/PrimaryWindow.js
@@ -28,7 +28,8 @@ export class PrimaryWindow extends Component {
    */
   renderViewer() {
     const {
-      audioResources, isCollection, isCollectionDialogVisible, isFetching, videoResources, view, windowId,
+      audioResources, isCollection, isCollectionDialogVisible,
+      isFetching, videoResources, view, windowId,
     } = this.props;
     if (isCollection) {
       return (