diff --git a/src/components/VideoViewer.js b/src/components/VideoViewer.js
index 3e72dbcc28217cd1351499b50e7cd09edf965506..2eec0d0ab30fb358d757b60d15d916d5e3a892f7 100644
--- a/src/components/VideoViewer.js
+++ b/src/components/VideoViewer.js
@@ -6,8 +6,7 @@ export class VideoViewer extends Component {
   /* eslint-disable jsx-a11y/media-has-caption */
   /** */
   render() {
-    const { classes, videoResources } = this.props;
-
+    const { captions, classes, videoResources } = this.props;
     return (
       <div className={classes.container}>
         <video controls className={classes.video}>
@@ -16,6 +15,11 @@ export class VideoViewer extends Component {
               <source src={video.id} type={video.getFormat()} />
             </Fragment>
           ))}
+          {captions.map(caption => (
+            <Fragment key={caption.id}>
+              <track src={caption.id} label={caption.getLabel()} srcLang={caption.getProperty('language')} />
+            </Fragment>
+          ))}
         </video>
       </div>
     );
@@ -24,10 +28,12 @@ export class VideoViewer extends Component {
 }
 
 VideoViewer.propTypes = {
+  captions: PropTypes.arrayOf(PropTypes.object),
   classes: PropTypes.objectOf(PropTypes.string).isRequired,
   videoResources: PropTypes.arrayOf(PropTypes.object),
 };
 
 VideoViewer.defaultProps = {
+  captions: [],
   videoResources: [],
 };
diff --git a/src/containers/VideoViewer.js b/src/containers/VideoViewer.js
index 97f7620362b99368aeea17222afb3dba83437873..f9480ac9b0a0c80a99cf73f2f42a416f7cbcedea 100644
--- a/src/containers/VideoViewer.js
+++ b/src/containers/VideoViewer.js
@@ -4,11 +4,12 @@ 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';
+import { getVisibleCanvasCaptions, getVisibleCanvasVideoResources } from '../state/selectors';
 
 /** */
 const mapStateToProps = (state, { windowId }) => (
   {
+    captions: getVisibleCanvasCaptions(state, { windowId }) || [],
     videoResources: getVisibleCanvasVideoResources(state, { windowId }) || [],
   }
 );
diff --git a/src/lib/MiradorCanvas.js b/src/lib/MiradorCanvas.js
index ebd45f2f5d81cf9b9ada0f663075f23404a765d9..3c490aed07dc85da4174ce4609c205823bd2a39e 100644
--- a/src/lib/MiradorCanvas.js
+++ b/src/lib/MiradorCanvas.js
@@ -99,6 +99,15 @@ export default class MiradorCanvas {
     return flatten(resources.filter((resource) => resource.getProperty('type') === 'Sound'));
   }
 
+  /** */
+  get vttContent() {
+    const resources = flattenDeep([
+      this.canvas.getContent().map(i => i.getBody()),
+    ]);
+
+    return flatten(resources.filter((resource) => resource.getProperty('format') === 'text/vtt'));
+  }
+
   /** */
   get resourceAnnotations() {
     return flattenDeep([
diff --git a/src/state/selectors/canvases.js b/src/state/selectors/canvases.js
index 71b3e274e370b82bf10b81ea86e043f4bddf2a85..a6408805608c234c2a1cc556e59d744e2fbf5808 100644
--- a/src/state/selectors/canvases.js
+++ b/src/state/selectors/canvases.js
@@ -192,6 +192,14 @@ export const getVisibleCanvasVideoResources = createSelector(
     .map(canvas => new MiradorCanvas(canvas).videoResources)),
 );
 
+export const getVisibleCanvasCaptions = createSelector(
+  [
+    getVisibleCanvases,
+  ],
+  canvases => flatten(canvases
+    .map(canvas => new MiradorCanvas(canvas).vttContent)),
+);
+
 export const getVisibleCanvasAudioResources = createSelector(
   [
     getVisibleCanvases,