Implement WebViewer with a Video Addon to Load and Annotate HTML Videos

This sample uses the video add-on for WebViewer. It allows the loading of HTML5 videos (.mp4, .ogg, .webm) so that their frames can be annotated and commented on for collaboration and review. For more information, see this guide.

Watch a video that highlights the new features included in the 3.0 release.

WebViewer provides a slick out-of-the-box responsive UI that enables you to view, annotate and manipulate PDFs and other document types inside any web project.

Click the button below to view the full project on GitHub.

1import React, { useRef, useEffect, useState } from 'react';
2import WebViewer from '@pdftron/webviewer';
3import { initializeVideoViewer } from '@pdftron/webviewer-video';
4import './App.css';
5import {
6 initializeAudioViewer
7} from '@pdftron/webviewer-audio';
8import {
9 demoPeaks,
10 demoXFDFString,
11} from './constants/demo-vars';
12
13const App = () => {
14 const viewer = useRef(null);
15 const inputFile = useRef(null);
16 const [state, setState] = useState({ instance: null, videoInstance: null, audioInstance: null });
17
18 useEffect(() => {
19 WebViewer.Iframe(
20 {
21 path: '/webviewer/lib',
22 enableRedaction: process.env.DEMO ? true : false,
23 },
24 viewer.current,
25 ).then(async instance => {
26 const license = `---- Insert commercial license key here after purchase ----`;
27 const videoUrl = 'https://pdftron.s3.amazonaws.com/downloads/pl/video/video.mp4';
28
29 const audioInstance = await initializeAudioViewer(
30 instance,
31 { license },
32 );
33
34 const videoInstance = await initializeVideoViewer(
35 instance,
36 {
37 license,
38 AudioComponent: audioInstance.Waveform,
39 isDemoMode: process.env.DEMO,
40 generatedPeaks: !process.env.DEMO ? null : demoPeaks, // waves can be pre-generated as seen here for fast loading: https://github.com/bbc/audiowaveform
41 enableRedaction: process.env.DEMO ? true : false,
42 }
43 );
44
45 instance.UI.setTheme('dark');
46
47 setState({ instance, videoInstance, audioInstance });
48
49 // Load a video at a specific url. Can be a local or public link
50 // If local it needs to be relative to lib/ui/index.html.
51 // Or at the root. (eg '/video.mp4')
52 videoInstance.loadVideo(videoUrl);
53 initializeHeader(instance);
54
55 if (process.env.DEMO) {
56 const { documentViewer } = instance.Core;
57 const annotManager = documentViewer.getAnnotationManager();
58 // Load saved annotations
59 documentViewer.addEventListener(
60 'videoElementReady',
61 async () => {
62 const video = videoInstance.getVideo();
63 const xfdfString = demoXFDFString;
64 await annotManager.importAnnotations(xfdfString);
65 video.updateAnnotationsToTime(0);
66 },
67 { once: true },
68 );
69 }
70 });
71 }, []);
72
73 const onFileChange = async event => {
74 const file = event.target.files[0];
75 const url = URL.createObjectURL(file);
76 const { instance, videoInstance, audioInstance } = state;
77
78 // Seamlessly switch between PDFs and videos.
79 // Can also detect by specific video file types (ie. mp4, ogg, etc.)
80 if (file.type.includes('video') ||
81 (file.name.includes('.mpd') && file.type === '')
82 ) {
83 videoInstance.loadVideo(url, { fileName: file.name });
84 // TODO: Notespanel needs to be delayed when opening. Not sure why.
85 setTimeout(() => {
86 instance.openElements('notesPanel');
87 });
88 } else if (file.type.includes('audio')) {
89 audioInstance.loadAudio(url, { fileName: file.name });
90
91 setTimeout(() => {
92 instance.UI.openElements('notesPanel');
93 });
94 } else {
95 instance.UI.setToolMode('AnnotationEdit');
96 instance.UI.loadDocument(url);
97 }
98 };
99
100 function initializeHeader(instance) {
101 const { setHeaderItems } = instance.UI;
102
103 setHeaderItems(header => {
104 // Add upload file button
105 header.push({
106 type: 'actionButton',
107 img: `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
108 <path d="M11 15H13V9H16L12 4L8 9H11V15Z" fill="currentColor"/>
109 <path d="M20 18H4V11H2V18C2 19.103 2.897 20 4 20H20C21.103 20 22 19.103 22 18V11H20V18Z" fill="currentColor"/>
110 </svg>`,
111 title: 'Load file',
112 dataElement: 'audio-loadFileButton',
113 onClick: () => {
114 inputFile.current.click();
115 }
116 });
117 });
118 }
119
120 return (
121 <div className="App">
122 <input type="file" hidden ref={inputFile} onChange={onFileChange} value=""/>
123 <div className="webviewer" ref={viewer}/>
124 </div>
125 );
126};
127
128export default App;
129

Did you find this helpful?

Trial setup questions?

Ask experts on Discord

Need other help?

Contact Support

Pricing or product questions?

Contact Sales