UNPKG

16.1 kBJavaScriptView Raw
1/* global window document */
2/* eslint-disable vars-on-top, no-var, object-shorthand, no-console */
3(function(window) {
4 var representationsEl = document.getElementById('representations');
5
6 representationsEl.addEventListener('change', function() {
7 var selectedIndex = representationsEl.selectedIndex;
8
9 if (!selectedIndex || selectedIndex < 1 || !window.vhs) {
10 return;
11 }
12 var selectedOption = representationsEl.options[representationsEl.selectedIndex];
13
14 if (!selectedOption) {
15 return;
16 }
17
18 var id = selectedOption.value;
19
20 window.vhs.representations().forEach(function(rep) {
21 rep.playlist.disabled = rep.id !== id;
22 });
23
24 window.mpc.fastQualityChange_();
25 });
26 var isManifestObjectType = function(url) {
27 return (/application\/vnd\.videojs\.vhs\+json/).test(url);
28 };
29 var hlsOptGroup = document.querySelector('[label="hls"]');
30 var dashOptGroup = document.querySelector('[label="dash"]');
31 var drmOptGroup = document.querySelector('[label="drm"]');
32 var liveOptGroup = document.querySelector('[label="live"]');
33 var llliveOptGroup = document.querySelector('[label="low latency live"]');
34 var manifestOptGroup = document.querySelector('[label="json manifest object"]');
35
36 var sourceList;
37 var hlsDataManifest;
38 var dashDataManifest;
39
40 var addSourcesToDom = function() {
41 if (!sourceList || !hlsDataManifest || !dashDataManifest) {
42 return;
43 }
44
45 sourceList.forEach(function(source) {
46 var option = document.createElement('option');
47
48 option.innerText = source.name;
49 option.value = source.uri;
50
51 if (source.keySystems) {
52 option.setAttribute('data-key-systems', JSON.stringify(source.keySystems, null, 2));
53 }
54
55 if (source.mimetype) {
56 option.setAttribute('data-mimetype', source.mimetype);
57 }
58
59 if (source.features.indexOf('low-latency') !== -1) {
60 llliveOptGroup.appendChild(option);
61 } else if (source.features.indexOf('live') !== -1) {
62 liveOptGroup.appendChild(option);
63 } else if (source.keySystems) {
64 drmOptGroup.appendChild(option);
65 } else if (source.mimetype === 'application/x-mpegurl') {
66 hlsOptGroup.appendChild(option);
67 } else if (source.mimetype === 'application/dash+xml') {
68 dashOptGroup.appendChild(option);
69 }
70 });
71
72 var hlsOption = document.createElement('option');
73 var dashOption = document.createElement('option');
74
75 dashOption.innerText = 'Dash Manifest Object Test, does not survive page reload';
76 dashOption.value = 'data:application/vnd.videojs.vhs+json,' + dashDataManifest;
77 hlsOption.innerText = 'HLS Manifest Object Test, does not survive page reload';
78 hlsOption.value = 'data:application/vnd.videojs.vhs+json,' + hlsDataManifest;
79
80 manifestOptGroup.appendChild(hlsOption);
81 manifestOptGroup.appendChild(dashOption);
82 };
83
84 var sourcesXhr = new window.XMLHttpRequest();
85
86 sourcesXhr.addEventListener('load', function() {
87 sourceList = JSON.parse(sourcesXhr.responseText);
88 addSourcesToDom();
89 });
90 sourcesXhr.open('GET', './scripts/sources.json');
91 sourcesXhr.send();
92
93 var hlsManifestXhr = new window.XMLHttpRequest();
94
95 hlsManifestXhr.addEventListener('load', function() {
96 hlsDataManifest = hlsManifestXhr.responseText;
97 addSourcesToDom();
98
99 });
100 hlsManifestXhr.open('GET', './scripts/hls-manifest-object.json');
101 hlsManifestXhr.send();
102
103 var dashManifestXhr = new window.XMLHttpRequest();
104
105 dashManifestXhr.addEventListener('load', function() {
106 dashDataManifest = dashManifestXhr.responseText;
107 addSourcesToDom();
108 });
109 dashManifestXhr.open('GET', './scripts/dash-manifest-object.json');
110 dashManifestXhr.send();
111
112 // all relevant elements
113 var urlButton = document.getElementById('load-url');
114 var sources = document.getElementById('load-source');
115 var stateEls = {};
116
117 var getInputValue = function(el) {
118 if (el.type === 'url' || el.type === 'text' || el.nodeName.toLowerCase() === 'textarea') {
119 if (isManifestObjectType(el.value)) {
120 return '';
121 }
122 return encodeURIComponent(el.value);
123 } else if (el.type === 'select-one') {
124 return el.options[el.selectedIndex].value;
125 } else if (el.type === 'checkbox') {
126 return el.checked;
127 }
128
129 console.warn('unhandled input type ' + el.type);
130 return '';
131 };
132
133 var setInputValue = function(el, value) {
134 if (el.type === 'url' || el.type === 'text' || el.nodeName.toLowerCase() === 'textarea') {
135 el.value = decodeURIComponent(value);
136 } else if (el.type === 'select-one') {
137 for (var i = 0; i < el.options.length; i++) {
138 if (el.options[i].value === value) {
139 el.options[i].selected = true;
140 }
141 }
142 } else {
143 // get the `value` into a Boolean.
144 el.checked = JSON.parse(value);
145 }
146
147 };
148
149 var newEvent = function(name) {
150 var event;
151
152 if (typeof window.Event === 'function') {
153 event = new window.Event(name);
154 } else {
155 event = document.createEvent('Event');
156 event.initEvent(name, true, true);
157 }
158
159 return event;
160 };
161
162 // taken from video.js
163 var getFileExtension = function(path) {
164 var splitPathRe;
165 var pathParts;
166
167 if (typeof path === 'string') {
168 splitPathRe = /^(\/?)([\s\S]*?)((?:\.{1,2}|[^\/]*?)(\.([^\.\/\?]+)))(?:[\/]*|[\?].*)$/i;
169 pathParts = splitPathRe.exec(path);
170
171 if (pathParts) {
172 return pathParts.pop().toLowerCase();
173 }
174 }
175
176 return '';
177 };
178
179 var saveState = function() {
180 var query = '';
181
182 if (!window.history.replaceState) {
183 return;
184 }
185
186 Object.keys(stateEls).forEach(function(elName) {
187 var symbol = query.length ? '&' : '?';
188
189 query += symbol + elName + '=' + getInputValue(stateEls[elName]);
190 });
191
192 window.history.replaceState({}, 'vhs demo', query);
193 };
194
195 window.URLSearchParams = window.URLSearchParams || function(locationSearch) {
196 this.get = function(name) {
197 var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(locationSearch);
198
199 return results ? decodeURIComponent(results[1]) : null;
200 };
201 };
202
203 // eslint-disable-next-line
204 var loadState = function() {
205 var params = new window.URLSearchParams(window.location.search);
206
207 return Object.keys(stateEls).reduce(function(acc, elName) {
208 acc[elName] = typeof params.get(elName) !== 'object' ? params.get(elName) : getInputValue(stateEls[elName]);
209 return acc;
210 }, {});
211 };
212
213 // eslint-disable-next-line
214 var reloadScripts = function(urls, cb) {
215 var el = document.getElementById('reload-scripts');
216
217 if (!el) {
218 el = document.createElement('div');
219 el.id = 'reload-scripts';
220 document.body.appendChild(el);
221 }
222
223 while (el.firstChild) {
224 el.removeChild(el.firstChild);
225 }
226
227 var loaded = [];
228
229 var checkDone = function() {
230 if (loaded.length === urls.length) {
231 cb();
232 }
233 };
234
235 urls.forEach(function(url) {
236 var script = document.createElement('script');
237
238 // scripts marked as defer will be loaded asynchronously but will be executed in the order they are in the DOM
239 script.defer = true;
240 // dynamically created scripts are async by default unless otherwise specified
241 // async scripts are loaded asynchronously but also executed as soon as they are loaded
242 // we want to load them in the order they are added therefore we want to turn off async
243 script.async = false;
244 script.src = url;
245 script.onload = function() {
246 loaded.push(url);
247 checkDone();
248 };
249 el.appendChild(script);
250 });
251 };
252
253 var regenerateRepresentations = function() {
254 while (representationsEl.firstChild) {
255 representationsEl.removeChild(representationsEl.firstChild);
256 }
257
258 var selectedIndex;
259
260 window.vhs.representations().forEach(function(rep, i) {
261 var option = document.createElement('option');
262
263 option.value = rep.id;
264 option.innerText = JSON.stringify({
265 id: rep.id,
266 videoCodec: rep.codecs.video,
267 audioCodec: rep.codecs.audio,
268 bandwidth: rep.bandwidth,
269 heigth: rep.heigth,
270 width: rep.width
271 });
272
273 if (window.mpc.media().id === rep.id) {
274 selectedIndex = i;
275 }
276
277 representationsEl.appendChild(option);
278 });
279
280 representationsEl.selectedIndex = selectedIndex;
281 };
282
283 [
284 'debug',
285 'autoplay',
286 'muted',
287 'minified',
288 'sync-workers',
289 'liveui',
290 'llhls',
291 'url',
292 'type',
293 'keysystems',
294 'buffer-water',
295 'exact-manifest-timings',
296 'pixel-diff-selector',
297 'override-native',
298 'preload',
299 'mirror-source'
300 ].forEach(function(name) {
301 stateEls[name] = document.getElementById(name);
302 });
303
304 window.startDemo = function(cb) {
305 var state = loadState();
306
307 Object.keys(state).forEach(function(elName) {
308 setInputValue(stateEls[elName], state[elName]);
309 });
310
311 Array.prototype.forEach.call(sources.options, function(s, i) {
312 if (s.value === state.url) {
313 sources.selectedIndex = i;
314 }
315 });
316
317 stateEls.muted.addEventListener('change', function(event) {
318 saveState();
319 window.player.muted(event.target.checked);
320 });
321
322 stateEls.autoplay.addEventListener('change', function(event) {
323 saveState();
324 window.player.autoplay(event.target.checked);
325 });
326
327 // stateEls that reload the player and scripts
328 [
329 'mirror-source',
330 'sync-workers',
331 'preload',
332 'llhls',
333 'buffer-water',
334 'override-native',
335 'liveui',
336 'pixel-diff-selector',
337 'exact-manifest-timings'
338 ].forEach(function(name) {
339 stateEls[name].addEventListener('change', function(event) {
340 saveState();
341
342 stateEls.minified.dispatchEvent(newEvent('change'));
343 });
344 });
345
346 stateEls.debug.addEventListener('change', function(event) {
347 saveState();
348 window.videojs.log.level(event.target.checked ? 'debug' : 'info');
349 });
350
351 stateEls.minified.addEventListener('change', function(event) {
352 var urls = [
353 'node_modules/video.js/dist/alt/video.core',
354 'node_modules/videojs-contrib-eme/dist/videojs-contrib-eme',
355 'node_modules/videojs-contrib-quality-levels/dist/videojs-contrib-quality-levels',
356 'node_modules/videojs-http-source-selector/dist/videojs-http-source-selector'
357 ].map(function(url) {
358 return url + (event.target.checked ? '.min' : '') + '.js';
359 });
360
361 if (stateEls['sync-workers'].checked) {
362 urls.push('dist/videojs-http-streaming-sync-workers.js');
363 } else {
364 urls.push('dist/videojs-http-streaming' + (event.target.checked ? '.min' : '') + '.js');
365 }
366
367 saveState();
368
369 if (window.player) {
370 window.player.dispose();
371 delete window.player;
372 }
373 if (window.videojs) {
374 delete window.videojs;
375 }
376
377 reloadScripts(urls, function() {
378 var player;
379 var fixture = document.getElementById('player-fixture');
380 var videoEl = document.createElement('video-js');
381
382 videoEl.setAttribute('controls', '');
383 videoEl.setAttribute('preload', stateEls.preload.options[stateEls.preload.selectedIndex].value || 'auto');
384 videoEl.className = 'vjs-default-skin';
385 fixture.appendChild(videoEl);
386
387 var mirrorSource = getInputValue(stateEls['mirror-source']);
388
389 player = window.player = window.videojs(videoEl, {
390 plugins: {
391 httpSourceSelector: {
392 default: 'auto'
393 }
394 },
395 liveui: stateEls.liveui.checked,
396 enableSourceset: mirrorSource,
397 html5: {
398 vhs: {
399 overrideNative: getInputValue(stateEls['override-native']),
400 experimentalBufferBasedABR: getInputValue(stateEls['buffer-water']),
401 experimentalLLHLS: getInputValue(stateEls.llhls),
402 experimentalExactManifestTimings: getInputValue(stateEls['exact-manifest-timings']),
403 experimentalLeastPixelDiffSelector: getInputValue(stateEls['pixel-diff-selector'])
404 }
405 }
406 });
407
408 player.on('sourceset', function() {
409 var source = player.currentSource();
410
411 if (source.keySystems) {
412 var copy = JSON.parse(JSON.stringify(source.keySystems));
413
414 // have to delete pssh as it will often make keySystems too big
415 // for a uri
416 Object.keys(copy).forEach(function(key) {
417 if (copy[key].hasOwnProperty('pssh')) {
418 delete copy[key].pssh;
419 }
420 });
421
422 stateEls.keysystems.value = JSON.stringify(copy, null, 2);
423 }
424
425 if (source.src) {
426 stateEls.url.value = encodeURI(source.src);
427 }
428
429 if (source.type) {
430 stateEls.type.value = source.type;
431 }
432
433 saveState();
434 });
435
436 player.width(640);
437 player.height(264);
438
439 // configure videojs-contrib-eme
440 player.eme();
441
442 stateEls.debug.dispatchEvent(newEvent('change'));
443 stateEls.muted.dispatchEvent(newEvent('change'));
444 stateEls.autoplay.dispatchEvent(newEvent('change'));
445
446 // run the load url handler for the intial source
447 if (stateEls.url.value) {
448 urlButton.dispatchEvent(newEvent('click'));
449 } else {
450 sources.dispatchEvent(newEvent('change'));
451 }
452 player.on('loadedmetadata', function() {
453 if (player.tech_.vhs) {
454 window.vhs = player.tech_.vhs;
455 window.mpc = player.tech_.vhs.masterPlaylistController_;
456 window.mpc.masterPlaylistLoader_.on('mediachange', regenerateRepresentations);
457 regenerateRepresentations();
458
459 } else {
460 window.vhs = null;
461 window.mpc = null;
462 }
463 });
464 cb(player);
465 });
466 });
467
468 var urlButtonClick = function(event) {
469 var ext;
470 var type = stateEls.type.value;
471
472 // reset type if it's a manifest object's type
473 if (type === 'application/vnd.videojs.vhs+json') {
474 type = '';
475 }
476
477 if (isManifestObjectType(stateEls.url.value)) {
478 type = 'application/vnd.videojs.vhs+json';
479 }
480
481 if (!type.trim()) {
482 ext = getFileExtension(stateEls.url.value);
483
484 if (ext === 'mpd') {
485 type = 'application/dash+xml';
486 } else if (ext === 'm3u8') {
487 type = 'application/x-mpegURL';
488 }
489 }
490
491 saveState();
492
493 var source = {
494 src: stateEls.url.value,
495 type: type
496 };
497
498 if (stateEls.keysystems.value) {
499 source.keySystems = JSON.parse(stateEls.keysystems.value);
500 }
501
502 sources.selectedIndex = -1;
503
504 Array.prototype.forEach.call(sources.options, function(s, i) {
505 if (s.value === stateEls.url.value) {
506 sources.selectedIndex = i;
507 }
508 });
509
510 window.player.src(source);
511 };
512
513 urlButton.addEventListener('click', urlButtonClick);
514 urlButton.addEventListener('tap', urlButtonClick);
515
516 sources.addEventListener('change', function(event) {
517 var selectedOption = sources.options[sources.selectedIndex];
518
519 if (!selectedOption) {
520 return;
521 }
522 var src = selectedOption.value;
523
524 stateEls.url.value = src;
525 stateEls.type.value = selectedOption.getAttribute('data-mimetype');
526 stateEls.keysystems.value = selectedOption.getAttribute('data-key-systems');
527
528 urlButton.dispatchEvent(newEvent('click'));
529 });
530
531 stateEls.url.addEventListener('keyup', function(event) {
532 if (event.key === 'Enter') {
533 urlButton.click();
534 }
535 });
536 stateEls.url.addEventListener('input', function(event) {
537 if (stateEls.type.value.length) {
538 stateEls.type.value = '';
539 }
540 });
541 stateEls.type.addEventListener('keyup', function(event) {
542 if (event.key === 'Enter') {
543 urlButton.click();
544 }
545 });
546
547 // run the change handler for the first time
548 stateEls.minified.dispatchEvent(newEvent('change'));
549 };
550}(window));