UNPKG

44 kBMarkdownView Raw
1<img width=300 src="./logo.svg" alt="VHS Logo consisting of a VHS tape, the Video.js logo and the words VHS" />
2
3# videojs-http-streaming (VHS)
4
5[![Build Status][travis-icon]][travis-link]
6[![Slack Status][slack-icon]][slack-link]
7[![Greenkeeper badge][greenkeeper-icon]][greenkeeper-link]
8
9Play HLS, DASH, and future HTTP streaming protocols with video.js, even where they're not
10natively supported.
11
12**Included in video.js 7 by default!** See the [video.js 7 blog post](https://blog.videojs.com/video-js-7-is-here/)
13
14Maintenance Status: Stable
15
16Video.js Compatibility: 7.x, 8.x
17
18<!-- START doctoc generated TOC please keep comment here to allow auto update -->
19<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
20
21- [Installation](#installation)
22 - [NPM](#npm)
23 - [CDN](#cdn)
24 - [Releases](#releases)
25 - [Manual Build](#manual-build)
26- [Contributing](#contributing)
27- [Troubleshooting](#troubleshooting)
28- [Talk to us](#talk-to-us)
29- [Getting Started](#getting-started)
30- [Compatibility](#compatibility)
31 - [Browsers which support MSE](#browsers-which-support-mse)
32 - [Native only](#native-only)
33 - [DRM](#drm)
34- [Documentation](#documentation)
35 - [Options](#options)
36 - [How to use](#how-to-use)
37 - [Initialization](#initialization)
38 - [Source](#source)
39 - [List](#list)
40 - [withCredentials](#withcredentials)
41 - [useCueTags](#usecuetags)
42 - [parse708captions](#parse708captions)
43 - [overrideNative](#overridenative)
44 - [playlistExclusionDuration](#playlistexclusionduration)
45 - [maxPlaylistRetries](#maxplaylistretries)
46 - [bandwidth](#bandwidth)
47 - [useBandwidthFromLocalStorage](#usebandwidthfromlocalstorage)
48 - [enableLowInitialPlaylist](#enablelowinitialplaylist)
49 - [limitRenditionByPlayerDimensions](#limitrenditionbyplayerdimensions)
50 - [useDevicePixelRatio](#usedevicepixelratio)
51 - [allowSeeksWithinUnsafeLiveWindow](#allowseekswithinunsafelivewindow)
52 - [customTagParsers](#customtagparsers)
53 - [customTagMappers](#customtagmappers)
54 - [cacheEncryptionKeys](#cacheencryptionkeys)
55 - [handlePartialData](#handlepartialdata)
56 - [liveRangeSafeTimeDelta](#liverangesafetimedelta)
57 - [useNetworkInformationApi](#usenetworkinformationapi)
58 - [useDtsForTimestampOffset](#usedtsfortimestampoffset)
59 - [useForcedSubtitles](#useforcedsubtitles)
60 - [captionServices](#captionservices)
61 - [Format](#format)
62 - [Example](#example)
63 - [Runtime Properties](#runtime-properties)
64 - [vhs.playlists.main](#vhsplaylistsmain)
65 - [vhs.playlists.media](#vhsplaylistsmedia)
66 - [vhs.systemBandwidth](#vhssystembandwidth)
67 - [vhs.bandwidth](#vhsbandwidth)
68 - [vhs.throughput](#vhsthroughput)
69 - [vhs.selectPlaylist](#vhsselectplaylist)
70 - [vhs.representations](#vhsrepresentations)
71 - [vhs.xhr](#vhsxhr)
72 - [vhs.stats](#vhsstats)
73 - [Events](#events)
74 - [loadedmetadata](#loadedmetadata)
75 - [xhr-hooks-ready](#xhr-hooks-ready)
76 - [VHS Usage Events](#vhs-usage-events)
77 - [Presence Stats](#presence-stats)
78 - [Use Stats](#use-stats)
79 - [In-Band Metadata](#in-band-metadata)
80 - [Segment Metadata](#segment-metadata)
81 - [Object as Source](#object-as-source)
82- [Hosting Considerations](#hosting-considerations)
83- [Known Issues and Workarounds](#known-issues-and-workarounds)
84 - [Fragmented MP4 Support](#fragmented-mp4-support)
85 - [Assets with an Audio-Only Rate Get Stuck in Audio-Only](#assets-with-an-audio-only-rate-get-stuck-in-audio-only)
86 - [DASH Assets with `$Time` Interpolation and `SegmentTimeline`s with No `t`](#dash-assets-with-time-interpolation-and-segmenttimelines-with-no-t)
87- [Testing](#testing)
88- [Debugging](#debugging)
89- [Release History](#release-history)
90- [Building](#building)
91- [Development](#development)
92 - [Tools](#tools)
93 - [Commands](#commands)
94
95<!-- END doctoc generated TOC please keep comment here to allow auto update -->
96
97## Installation
98
99In most cases **it is not necessary to separately install http-streaming**, as it has been included in the default build of Video.js since version 7.
100
101Only install if you need a specifc combination of video.js and http-streaming versions. If installing separately, use the "core" version of Video.js without the bundled version of http-streaming.
102
103### NPM
104To install `videojs-http-streaming` with npm, run
105
106```bash
107npm install --save @videojs/http-streaming
108```
109
110### CDN
111Select a version of VHS from the [CDN](https://unpkg.com/@videojs/http-streaming/dist/)
112
113### Releases
114Download a release of [videojs-http-streaming](https://github.com/videojs/http-streaming/releases)
115
116### Manual Build
117Download a copy of this git repository and then follow the steps in [Building](#building)
118
119## Contributing
120See [CONTRIBUTING.md](/CONTRIBUTING.md)
121
122## Troubleshooting
123See [our troubleshooting guide](/docs/troubleshooting.md)
124
125## Talk to us
126Drop by the [Video.js slack][slack-link].
127
128## Getting Started
129This library is included in Video.js 7 by default.
130
131**Only if need a specific combination of versions of Video.js and VHS** you can get a copy of [videojs-http-streaming](#installation) and include it in your page along with video.js. In this case, you should use the "core" build of Video.js, without a bundled VHS:
132
133```html
134<video-js id=vid1 width=600 height=300 class="vjs-default-skin" controls>
135 <source
136 src="https://example.com/index.m3u8"
137 type="application/x-mpegURL">
138</video-js>
139<!-- "core" version of Video.js -->
140<script src="video.core.min.js"></script>
141<script src="videojs-http-streaming.min.js"></script>
142<script>
143var player = videojs('vid1');
144player.play();
145</script>
146```
147
148Is it recommended to use the `<video-js>` element or load a source with `player.src(sourceObject)` in order to prevent the video element from playing the source natively where HLS is supported.
149
150## Compatibility
151
152The [Media Source Extensions](http://caniuse.com/#feat=mediasource) API is required for http-streaming to play HLS or MPEG-DASH.
153
154### Browsers which support MSE
155
156- Chrome
157- Firefox
158- Internet Explorer 11 Windows 10 or 8.1
159
160These browsers have some level of native HLS support, however by default the [overrideNative](#overridenative) option is set to `true` except on Safari, so MSE playback is used:
161
162- Chrome Android
163- Firefox Android
164- Edge
165
166### Native only
167
168- Mac Safari
169- iOS Safari
170
171Mac and iPad Safari do have MSE support, but native HLS is recommended
172
173### DRM
174
175DRM is supported through [videojs-contrib-eme](https://github.com/videojs/videojs-contrib-eme). In order to use DRM, include the videojs-contrib-eme plug, [initialize it](https://github.com/videojs/videojs-contrib-eme#initialization), and add options to either the [plugin](https://github.com/videojs/videojs-contrib-eme#plugin-options) or the [source](https://github.com/videojs/videojs-contrib-eme#source-options).
176
177Detailed option information can be found in the [videojs-contrib-eme README](https://github.com/videojs/videojs-contrib-eme/blob/main/README.md).
178
179## Documentation
180[HTTP Live Streaming](https://developer.apple.com/streaming/) (HLS) has
181become a de-facto standard for streaming video on mobile devices
182thanks to its native support on iOS and Android. There are a number of
183reasons independent of platform to recommend the format, though:
184
185- Supports (client-driven) adaptive bitrate selection
186- Delivered over standard HTTP ports
187- Simple, text-based manifest format
188- No proprietary streaming servers required
189
190Unfortunately, all the major desktop browsers except for Safari are
191missing HLS support. That leaves web developers in the unfortunate
192position of having to maintain alternate renditions of the same video
193and potentially having to forego HTML-based video entirely to provide
194the best desktop viewing experience.
195
196This project addresses that situation by providing a polyfill for HLS
197on browsers that have support for [Media Source
198Extensions](http://caniuse.com/#feat=mediasource).
199You can deploy a single HLS stream, code against the
200regular HTML5 video APIs, and create a fast, high-quality video
201experience across all the big web device categories.
202
203Check out the [full documentation](docs/README.md) for details on how HLS works
204and advanced configuration. A description of the [adaptive switching
205behavior](docs/bitrate-switching.md) is available, too.
206
207videojs-http-streaming supports a bunch of HLS features. Here
208are some highlights:
209
210- video-on-demand and live playback modes
211- backup or redundant streams
212- mid-segment quality switching
213- AES-128 segment encryption
214- CEA-608 captions are automatically translated into standard HTML5
215 [caption text tracks][0]
216- In-Manifest WebVTT subtitles are automatically translated into standard HTML5
217 subtitle tracks
218- Timed ID3 Metadata is automatically translated into HTML5 metedata
219 text tracks
220- Highly customizable adaptive bitrate selection
221- Automatic bandwidth tracking
222- Cross-domain credentials support with CORS
223- Tight integration with video.js and a philosophy of exposing as much
224 as possible with standard HTML APIs
225- Stream with multiple audio tracks and switching to those audio tracks
226 (see the docs folder) for info
227- Media content in
228 [fragmented MP4s](https://developer.apple.com/videos/play/wwdc2016/504/)
229 instead of the MPEG2-TS container format.
230
231[0]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/track
232
233For a more complete list of supported and missing features, refer to
234[this doc](docs/supported-features.md).
235
236### Options
237#### How to use
238
239##### Initialization
240You may pass in an options object to the hls source handler at player
241initialization. You can pass in options just like you would for other
242parts of video.js:
243
244```javascript
245// html5 for html hls
246videojs(video, {
247 html5: {
248 vhs: {
249 withCredentials: true
250 }
251 }
252});
253```
254
255##### Source
256Some options, such as `withCredentials` can be passed in to vhs during
257`player.src`
258
259```javascript
260
261var player = videojs('some-video-id');
262
263player.src({
264 src: 'https://d2zihajmogu5jn.cloudfront.net/bipbop-advanced/bipbop_16x9_variant.m3u8',
265 type: 'application/x-mpegURL',
266 withCredentials: true
267});
268```
269
270#### List
271##### withCredentials
272* Type: `boolean`
273* can be used as a source option
274* can be used as an initialization option
275
276When the `withCredentials` property is set to `true`, all XHR requests for
277manifests and segments would have `withCredentials` set to `true` as well. This
278enables storing and passing cookies from the server that the manifests and
279segments live on. This has some implications on CORS because when set, the
280`Access-Control-Allow-Origin` header cannot be set to `*`, also, the response
281headers require the addition of `Access-Control-Allow-Credentials` header which
282is set to `true`.
283See html5rocks's [article](http://www.html5rocks.com/en/tutorials/cors/)
284for more info.
285
286##### useCueTags
287* Type: `boolean`
288* can be used as an initialization option
289
290When the `useCueTags` property is set to `true,` a text track is created with
291label 'ad-cues' and kind 'metadata'. The track is then added to
292`player.textTracks()`. Changes in active cue may be
293tracked by following the Video.js cue points API for text tracks. For example:
294
295```javascript
296let textTracks = player.textTracks();
297let cuesTrack;
298
299for (let i = 0; i < textTracks.length; i++) {
300  if (textTracks[i].label === 'ad-cues') {
301    cuesTrack = textTracks[i];
302  }
303}
304
305cuesTrack.addEventListener('cuechange', function() {
306 let activeCues = cuesTrack.activeCues;
307
308  for (let i = 0; i < activeCues.length; i++) {
309 let activeCue = activeCues[i];
310
311    console.log('Cue runs from ' + activeCue.startTime +
312 ' to ' + activeCue.endTime);
313  }
314});
315```
316
317##### parse708captions
318* Type: `boolean`
319* Default: `true`
320* can be used as an initialization option
321
322When set to `false`, 708 captions in the stream are not parsed and will not show up in text track lists or the captions menu.
323
324##### overrideNative
325* Type: `boolean`
326* can be used as an initialization option
327
328Try to use videojs-http-streaming even on platforms that provide some
329level of HLS support natively. There are a number of platforms that
330*technically* play back HLS content but aren't very reliable or are
331missing features like CEA-608 captions support. When `overrideNative`
332is true, if the platform supports Media Source Extensions
333videojs-http-streaming will take over HLS playback to provide a more
334consistent experience.
335
336```javascript
337// via the constructor
338var player = videojs('playerId', {
339 html5: {
340 vhs: {
341 overrideNative: true
342 },
343 nativeAudioTracks: false,
344 nativeVideoTracks: false
345 }
346});
347```
348
349Since MSE playback may be desirable on all browsers with some native support other than Safari, `overrideNative: !videojs.browser.IS_SAFARI` could be used.
350
351##### playlistExclusionDuration
352* Type: `number`
353* can be used as an initialization option
354
355When the `playlistExclusionDuration` property is set to a time duration in seconds,
356if a playlist is excluded, it will be excluded for a period of that
357customized duration. This enables the exclusion duration to be configured
358by the user.
359
360##### maxPlaylistRetries
361* Type: `number`
362* Default: `Infinity`
363* can be used as an initialization option
364
365The max number of times that a playlist will retry loading following an error
366before being indefinitely excluded from the rendition selection algorithm. Note: the number of retry attempts needs to _exceed_ this value before a playlist will be excluded.
367
368##### bandwidth
369* Type: `number`
370* can be used as an initialization option
371
372When the `bandwidth` property is set (bits per second), it will be used in
373the calculation for initial playlist selection, before more bandwidth
374information is seen by the player.
375
376##### useBandwidthFromLocalStorage
377* Type: `boolean`
378* can be used as an initialization option
379
380If true, `bandwidth` and `throughput` values are stored in and retrieved from local
381storage on startup (for initial rendition selection). This setting is `false` by default.
382
383##### enableLowInitialPlaylist
384* Type: `boolean`
385* can be used as an initialization option
386
387When `enableLowInitialPlaylist` is set to true, it will be used to select
388the lowest bitrate playlist initially. This helps to decrease playback start time.
389This setting is `false` by default.
390
391##### limitRenditionByPlayerDimensions
392* Type: `boolean`
393* can be used as an initialization option
394
395When `limitRenditionByPlayerDimensions` is set to true, rendition
396selection logic will take into account the player size and rendition
397resolutions when making a decision.
398This setting is `true` by default.
399
400##### useDevicePixelRatio
401* Type: `boolean`
402* can be used as an initialization option.
403
404If true, this will take the device pixel ratio into account when doing rendition switching. This means that if you have a player with the width of `540px` in a high density display with a device pixel ratio of 2, a rendition of `1080p` will be allowed.
405This setting is `false` by default.
406
407##### allowSeeksWithinUnsafeLiveWindow
408* Type: `boolean`
409* can be used as a source option
410
411When `allowSeeksWithinUnsafeLiveWindow` is set to `true`, if the active playlist is live
412and a seek is made to a time between the safe live point (end of manifest minus three
413times the target duration,
414see [the hls spec](https://tools.ietf.org/html/draft-pantos-http-live-streaming-23#section-6.3.3)
415for details) and the end of the playlist, the seek is allowed, rather than corrected to
416the safe live point.
417
418This option can help in instances where the live stream's target duration is greater than
419the segment durations, playback ends up in the unsafe live window, and there are gaps in
420the content. In this case the player will attempt to seek past the gaps but end up seeking
421inside of the unsafe range, leading to a correction and seek back into a previously played
422content.
423
424The property defaults to `false`.
425
426##### customTagParsers
427* Type: `Array`
428* can be used as a source option
429
430With `customTagParsers` you can pass an array of custom m3u8 tag parser objects. See https://github.com/videojs/m3u8-parser#custom-parsers
431
432##### customTagMappers
433* Type: `Array`
434* can be used as a source option
435
436Similar to `customTagParsers`, with `customTagMappers` you can pass an array of custom m3u8 tag mapper objects. See https://github.com/videojs/m3u8-parser#custom-parsers
437
438##### cacheEncryptionKeys
439* Type: `boolean`
440* can be used as a source option
441* can be used as an initialization option
442
443This option forces the player to cache AES-128 encryption keys internally instead of requesting the key alongside every segment request.
444This option defaults to `false`.
445
446##### handlePartialData
447* Type: `boolean`,
448* Default: `false`
449* Use partial appends in the transmuxer and segment loader
450
451##### liveRangeSafeTimeDelta
452* Type: `number`,
453* Default: [`SAFE_TIME_DELTA`](https://github.com/videojs/http-streaming/blob/e7cb63af010779108336eddb5c8fd138d6390e95/src/ranges.js#L17)
454* Allow to re-define length (in seconds) of time delta when you compare current time and the end of the buffered range.
455
456##### useNetworkInformationApi
457* Type: `boolean`,
458* Default: `false`
459* Use [window.networkInformation.downlink](https://developer.mozilla.org/en-US/docs/Web/API/NetworkInformation/downlink) to estimate the network's bandwidth. Per mdn, _The value is never greater than 10 Mbps, as a non-standard anti-fingerprinting measure_. Given this, if bandwidth estimates from both the player and networkInfo are >= 10 Mbps, the player will use the larger of the two values as its bandwidth estimate.
460
461##### useDtsForTimestampOffset
462* Type: `boolean`,
463* Default: `false`
464* Use [Decode Timestamp](https://www.w3.org/TR/media-source/#decode-timestamp) instead of [Presentation Timestamp](https://www.w3.org/TR/media-source/#presentation-timestamp) for [timestampOffset](https://www.w3.org/TR/media-source/#dom-sourcebuffer-timestampoffset) calculation. This option was introduced to align with DTS-based browsers. This option affects only transmuxed data (eg: transport stream). For more info please check the following [issue](https://github.com/videojs/http-streaming/issues/1247).
465
466##### useForcedSubtitles
467* Type: `boolean`
468* Default: `false`
469* can be used as a source option
470* can be used as an initialization option
471
472If true, this option allows the player to display forced subtitles. When available, forced subtitles allow to translate foreign language dialogues or images containing foreign language characters.
473
474##### captionServices
475* Type: `object`
476* Default: undefined
477* Provide extra information, like a label or a language, for instream (608 and 708) captions.
478
479The captionServices options object has properties that map to the caption services. Each property is an object itself that includes several properties, like a label or language.
480
481For 608 captions, the service names are `CC1`, `CC2`, `CC3`, and `CC4`. For 708 captions, the service names are `SERVICEn` where `n` is a digit between `1` and `63`.
482
483For 708 caption services, you may additionally provide an `encoding` value that will be used by the transmuxer to decode the captions using an instance of [TextDecoder](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder). This is to permit and is required for legacy multi-byte encodings. Please review the `TextDecoder` documentation for accepted encoding labels.
484
485###### Format
486```js
487{
488 vhs: {
489 captionServices: {
490 [serviceName]: {
491 language: String, // optional
492 label: String, // optional
493 default: boolean, // optional,
494 encoding: String // optional, 708 services only
495 }
496 }
497 }
498}
499```
500###### Example
501```js
502{
503 vhs: {
504 captionServices: {
505 CC1: {
506 language: 'en',
507 label: 'English'
508 },
509 SERVICE1: {
510 langauge: 'kr',
511 label: 'Korean',
512 encoding: 'euc-kr'
513 default: true,
514 }
515 }
516 }
517}
518```
519
520### Runtime Properties
521Runtime properties are attached to the tech object when HLS is in
522use. You can get a reference to the VHS source handler like this:
523
524```javascript
525var vhs = player.tech().vhs;
526```
527
528If you *were* thinking about modifying runtime properties in a
529video.js plugin, we'd recommend you avoid it. Your plugin won't work
530with videos that don't use videojs-http-streaming and the best plugins
531work across all the media types that video.js supports. If you're
532deploying videojs-http-streaming on your own website and want to make a
533couple tweaks though, go for it!
534
535#### vhs.playlists.main
536Type: `object`
537
538An object representing the parsed main playlist. If a media playlist
539is loaded directly, a main playlist with only one entry will be
540created.
541
542#### vhs.playlists.media
543Type: `function`
544
545A function that can be used to retrieve or modify the currently active
546media playlist. The active media playlist is referred to when
547additional video data needs to be downloaded. Calling this function
548with no arguments returns the parsed playlist object for the active
549media playlist. Calling this function with a playlist object from the
550main playlist or a URI string as specified in the main playlist
551will kick off an asynchronous load of the specified media
552playlist. Once it has been retreived, it will become the active media
553playlist.
554
555#### vhs.systemBandwidth
556Type: `number`
557
558`systemBandwidth` is a combination of two serial processes' bitrates. The first
559is the network bitrate provided by `bandwidth` and the second is the bitrate of
560the entire process after that (decryption, transmuxing, and appending) provided
561by `throughput`. This value is used by the default implementation of `selectPlaylist`
562to select an appropriate bitrate to play.
563
564Since the two process are serial, the overall system bandwidth is given by:
565`systemBandwidth = 1 / (1 / bandwidth + 1 / throughput)`
566
567#### vhs.bandwidth
568Type: `number`
569
570The number of bits downloaded per second in the last segment download.
571
572Before the first video segment has been downloaded, it's hard to
573estimate bandwidth accurately. The HLS tech uses a starting value of 4194304 or 0.5 MB/s. If you
574have a more accurate source of bandwidth information, you can override
575this value as soon as the HLS tech has loaded to provide an initial
576bandwidth estimate.
577
578#### vhs.throughput
579Type: `number`
580
581The number of bits decrypted, transmuxed, and appended per second as a cumulative average across active processing time.
582
583#### vhs.selectPlaylist
584Type: `function`
585
586A function that returns the media playlist object to use to download
587the next segment. It is invoked by the tech immediately before a new
588segment is downloaded. You can override this function to provide your
589adaptive streaming logic. You must, however, be sure to return a valid
590media playlist object that is present in `player.tech().vhs.main`.
591
592Overridding this function with your own is very powerful but is overkill
593for many purposes. Most of the time, you should use the much simpler
594function below to selectively enable or disable a playlist from the
595adaptive streaming logic.
596
597#### vhs.representations
598Type: `function`
599
600It is recommended to include the [videojs-contrib-quality-levels](https://github.com/videojs/videojs-contrib-quality-levels) plugin to your page so that videojs-http-streaming will automatically populate the QualityLevelList exposed on the player by the plugin. You can access this list by calling `player.qualityLevels()`. See the [videojs-contrib-quality-levels project page](https://github.com/videojs/videojs-contrib-quality-levels) for more information on how to use the api.
601
602Example, only enabling representations with a width greater than or equal to 720:
603
604```javascript
605var qualityLevels = player.qualityLevels();
606
607for (var i = 0; i < qualityLevels.length; i++) {
608 var quality = qualityLevels[i];
609 if (quality.width >= 720) {
610 quality.enabled = true;
611 } else {
612 quality.enabled = false;
613 }
614}
615```
616
617If including [videojs-contrib-quality-levels](https://github.com/videojs/videojs-contrib-quality-levels) is not an option, you can use the representations api. To get all of the available representations, call the `representations()` method on `player.tech().vhs`. This will return a list of plain objects, each with `width`, `height`, `bandwidth`, and `id` properties, and an `enabled()` method.
618
619```javascript
620player.tech().vhs.representations();
621```
622
623To see whether the representation is enabled or disabled, call its `enabled()` method with no arguments. To set whether it is enabled/disabled, call its `enabled()` method and pass in a boolean value. Calling `<representation>.enabled(true)` will allow the adaptive bitrate algorithm to select the representation while calling `<representation>.enabled(false)` will disallow any selection of that representation.
624
625Example, only enabling representations with a width greater than or equal to 720:
626
627```javascript
628player.tech().vhs.representations().forEach(function(rep) {
629 if (rep.width >= 720) {
630 rep.enabled(true);
631 } else {
632 rep.enabled(false);
633 }
634});
635```
636
637#### vhs.xhr
638Type: `function`
639
640The xhr function that is used by VHS internally is exposed on the per-
641player `vhs` object. While it is possible, we do not recommend replacing
642the function with your own implementation. Instead, `xhr` provides
643the ability to specify `onRequest` and `onResponse` hooks which each take a
644callback function as a parameter, as well as `offRequest` and `offResponse`
645functions which can remove a callback function from the `onRequest` or
646`onResponse` Set. An `xhr-hooks-ready` event is fired from a player when per-player
647hooks are ready to be added or removed. This will ensure player specific hooks are
648set prior to any manifest or segment requests.
649
650The `onRequest(callback)` function takes a `callback` function that will pass an xhr `options`
651Object to that callback. These callbacks are called synchronously, in the order registered
652and act as pre-request hooks for modifying the xhr `options` Object prior to making a request.
653
654Note: This callback *MUST* return an `options` Object as the `xhr` wrapper and each `onRequest`
655hook receives the returned `options` as a parameter.
656
657Example:
658```javascript
659player.on('xhr-hooks-ready', () => {
660 const playerRequestHook = (options) => {
661 return {
662 uri: 'https://new.options.uri'
663 };
664 };
665 player.tech().vhs.xhr.onRequest(playerRequestHook);
666});
667```
668
669If access to the `xhr` Object is required prior to the `xhr.send` call, an `options.beforeSend`
670callback can be set within an `onRequest` callback function that will pass the `xhr` Object
671as a parameter and will be called immediately prior to `xhr.send`.
672
673Example:
674```javascript
675player.on('xhr-hooks-ready', () => {
676 const playerXhrRequestHook = (options) => {
677 options.beforeSend = (xhr) => {
678 xhr.setRequestHeader('foo', 'bar');
679 };
680 return options;
681 };
682 player.tech().vhs.xhr.onRequest(playerXhrRequestHook);
683});
684```
685
686The `onResponse(callback)` function takes a `callback` function that will pass the xhr
687`request`, `error`, and `response` Objects to that callback. These callbacks are called
688in the order registered and act as post-request hooks for gathering data from the
689xhr `request`, `error` and `response` Objects. `onResponse` callbacks do not require a
690return value, the parameters are passed to each subsequent callback by reference.
691
692Example:
693```javascript
694player.on('xhr-hooks-ready', () => {
695 const playerResponseHook = (request, error, response) => {
696 const bar = response.headers.foo;
697 };
698 player.tech().vhs.xhr.onResponse(playerResponseHook);
699});
700```
701
702The `offRequest` function takes a `callback` function, and will remove that function from
703the collection of `onRequest` hooks if it exists.
704
705Example:
706```javascript
707player.on('xhr-hooks-ready', () => {
708 player.tech().vhs.xhr.offRequest(playerRequestHook);
709});
710```
711
712The `offResponse` function takes a `callback` function, and will remove that function from
713the collection of `offResponse` hooks if it exists.
714
715Example:
716```javascript
717player.on('xhr-hooks-ready', () => {
718 player.tech().vhs.xhr.offResponse(playerResponseHook);
719});
720```
721
722The global `videojs.Vhs` also exposes an `xhr` property. Adding `onRequest`
723and/or `onResponse` hooks will allow you to intercept the request options and xhr
724Object as well as request, error, and response data for *all* requests in *every*
725player on a page. For consistency across browsers the video source should be set
726at runtime once the video player is ready.
727
728Example:
729```javascript
730// Global request callback, will affect every player.
731const globalRequestHook = (options) => {
732 return {
733 uri: 'https://new.options.global.uri'
734 };
735};
736videojs.Vhs.xhr.onRequest(globalRequestHook);
737```
738
739```javascript
740// Global request callback defining beforeSend function, will affect every player.
741const globalXhrRequestHook = (options) => {
742 options.beforeSend = (xhr) => {
743 xhr.setRequestHeader('foo', 'bar');
744 };
745 return options;
746};
747videojs.Vhs.xhr.onRequest(globalXhrRequestHook);
748```
749
750```javascript
751// Global response hook callback, will affect every player.
752const globalResponseHook = (request, error, response) => {
753 const bar = response.headers.foo
754};
755
756videojs.Vhs.xhr.onResponse(globalResponseHook);
757```
758
759```javascript
760// Remove a global onRequest callback.
761videojs.Vhs.xhr.offRequest(globalRequestHook);
762```
763
764```javascript
765// Remove a global onResponse callback.
766videojs.Vhs.xhr.offResponse(globalResponseHook);
767```
768
769For information on the type of options that you can modify see the
770documentation at [https://github.com/Raynos/xhr](https://github.com/Raynos/xhr).
771
772#### vhs.stats
773Type: `object`
774
775This object contains a summary of HLS and player related stats.
776
777| Property Name | Type | Description |
778| --------------------- | ------ | ----------- |
779| bandwidth | number | Rate of the last segment download in bits/second |
780| mediaRequests | number | Total number of media segment requests |
781| mediaRequestsAborted | number | Total number of aborted media segment requests |
782| mediaRequestsTimedout | number | Total number of timedout media segment requests |
783| mediaRequestsErrored | number | Total number of errored media segment requests |
784| mediaTransferDuration | number | Total time spent downloading media segments in milliseconds |
785| mediaBytesTransferred | number | Total number of content bytes downloaded |
786| mediaSecondsLoaded | number | Total number of content seconds downloaded |
787| buffered | array | List of time ranges of content that are in the SourceBuffer |
788| currentTime | number | The current position of the player |
789| currentSource | object | The source object. Has the structure `{src: 'url', type: 'mimetype'}` |
790| currentTech | string | The name of the tech in use |
791| duration | number | Duration of the video in seconds |
792| main | object | The [main playlist object](#vhsplaylistsmain) |
793| playerDimensions | object | Contains the width and height of the player |
794| seekable | array | List of time ranges that the player can seek to |
795| timestamp | number | Timestamp of when `vhs.stats` was accessed |
796| videoPlaybackQuality | object | Media playback quality metrics as specified by the [W3C's Media Playback Quality API](https://wicg.github.io/media-playback-quality/) |
797
798
799### Events
800Standard HTML video events are handled by video.js automatically and
801are triggered on the player object.
802
803#### loadedmetadata
804
805Fired after the first segment is downloaded for a playlist. This will not happen
806until playback if video.js's `metadata` setting is `none`
807
808#### xhr-hooks-ready
809
810Fired when the player `xhr` object is ready to set `onRequest` and `onResponse` hooks, as well
811as remove hooks with `offRequest` and `offResponse`.
812
813### VHS Usage Events
814
815Usage tracking events are fired when we detect a certain HLS feature, encoding setting,
816or API is used. These can be helpful for analytics, and to pinpoint the cause of HLS errors.
817For instance, if errors are being fired in tandem with a usage event indicating that the
818player was playing an AES encrypted stream, then we have a possible avenue to explore when
819debugging the error.
820
821Note that although these usage events are listed below, they may change at any time without
822a major version change.
823
824VHS usage events are triggered on the tech with the exception of the 3 vhs-reload-error
825events, which are triggered on the player.
826
827To listen for usage events triggered on the tech, listen for the event type of `'usage'`:
828
829```javascript
830player.on('ready', () => {
831 player.tech().on('usage', (e) => {
832 console.log(e.name);
833 });
834});
835```
836
837Note that these events are triggered as soon as a case is encountered, and often only
838once. For example, the `vhs-demuxed` usage event will be triggered as soon as the main
839manifest is downloaded and parsed, and will not be triggered again.
840
841#### Presence Stats
842
843Each of the following usage events are fired once per source if (and when) detected:
844
845| Name | Description |
846| ------------- | ------------- |
847| vhs-webvtt | main manifest has at least one segmented WebVTT playlist |
848| vhs-aes | a playlist is AES encrypted |
849| vhs-fmp4 | a playlist used fMP4 segments |
850| vhs-demuxed | audio and video are demuxed by default |
851| vhs-alternate-audio | alternate audio available in the main manifest |
852| vhs-playlist-cue-tags | a playlist used cue tags (see useCueTags(#usecuetags) for details) |
853| vhs-bandwidth-from-local-storage | starting bandwidth was retrieved from local storage (see useBandwidthFromLocalStorage(#useBandwidthFromLocalStorage) for details) |
854| vhs-throughput-from-local-storage | starting throughput was retrieved from local storage (see useBandwidthFromLocalStorage(#useBandwidthFromLocalStorage) for details) |
855
856#### Use Stats
857
858Each of the following usage events are fired per use:
859
860| Name | Description |
861| ------------- | ------------- |
862| vhs-gap-skip | player skipped a gap in the buffer |
863| vhs-player-access | player.vhs was accessed |
864| vhs-audio-change | a user selected an alternate audio stream |
865| vhs-rendition-disabled | a rendition was disabled |
866| vhs-rendition-enabled | a rendition was enabled |
867| vhs-rendition-excluded| a rendition was excluded |
868| vhs-timestamp-offset | a timestamp offset was set in HLS (can identify discontinuities) |
869| vhs-unknown-waiting | the player stopped for an unknown reason and we seeked to current time try to address it |
870| vhs-live-resync | playback fell off the back of a live playlist and we resynced to the live point |
871| vhs-video-underflow | we seeked to current time to address video underflow |
872| vhs-error-reload-initialized | the reloadSourceOnError plugin was initialized |
873| vhs-error-reload | the reloadSourceOnError plugin reloaded a source |
874| vhs-error-reload-canceled | an error occurred too soon after the last reload, so we didn't reload again (to prevent error loops) |
875
876
877### In-Band Metadata
878The HLS tech supports [timed
879metadata](https://developer.apple.com/library/ios/#documentation/AudioVideo/Conceptual/HTTP_Live_Streaming_Metadata_Spec/Introduction/Introduction.html)
880embedded as [ID3 tags](http://id3.org/id3v2.3.0). When a stream is
881encountered with embedded metadata, an [in-band metadata text
882track](https://html.spec.whatwg.org/multipage/embedded-content.html#text-track-in-band-metadata-track-dispatch-type)
883will automatically be created and populated with cues as they are
884encountered in the stream. UTF-8 encoded
885[TXXX](http://id3.org/id3v2.3.0#User_defined_text_information_frame)
886and [WXXX](http://id3.org/id3v2.3.0#User_defined_URL_link_frame) ID3
887frames are mapped to cue points and their values set as the cue
888text. Cues are created for all other frame types and the data is
889attached to the generated cue:
890
891```javascript
892cue.value.data
893```
894
895There are lots of guides and references to using text tracks [around
896the web](http://www.html5rocks.com/en/tutorials/track/basics/).
897
898### Segment Metadata
899You can get metadata about the segments currently in the buffer by using the `segment-metadata`
900text track. You can get the metadata of the currently rendered segment by looking at the
901track's `activeCues` array. The metadata will be attached to the `cue.value` property and
902will have this structure
903
904```javascript
905cue.value = {
906 byteLength, // The size of the segment in bytes
907 bandwidth, // The peak bitrate reported by the segment's playlist
908 resolution, // The resolution reported by the segment's playlist
909 codecs, // The codecs reported by the segment's playlist
910 uri, // The Segment uri
911 timeline, // Timeline of the segment for detecting discontinuities
912 playlist, // The Playlist uri
913 start, // Segment start time
914 end // Segment end time
915};
916```
917
918Example:
919Detect when a change in quality is rendered on screen
920```javascript
921let tracks = player.textTracks();
922let segmentMetadataTrack;
923
924for (let i = 0; i < tracks.length; i++) {
925 if (tracks[i].label === 'segment-metadata') {
926 segmentMetadataTrack = tracks[i];
927 }
928}
929
930let previousPlaylist;
931
932if (segmentMetadataTrack) {
933 segmentMetadataTrack.on('cuechange', function() {
934 let activeCue = segmentMetadataTrack.activeCues[0];
935
936 if (activeCue) {
937 if (previousPlaylist !== activeCue.value.playlist) {
938 console.log('Switched from rendition ' + previousPlaylist +
939 ' to rendition ' + activeCue.value.playlist);
940 }
941 previousPlaylist = activeCue.value.playlist;
942 }
943 });
944}
945```
946
947### Object as Source
948
949*Note* that this is an advanced use-case, and may be more fragile for production
950environments, as the schema for a VHS object and how it's used internally are not set in
951stone and may change in future releases.
952
953In normal use, VHS accepts a URL as the source of the video. But VHS also has the ability
954to accept a JSON object as the source.
955
956Passing a JSON object as the source has many uses. A couple of examples include:
957* The manifest has already been downloaded, so there's no need to make another request
958* You want to change some aspect of the manifest, e.g., add a segment, without modifying
959 the manifest itself
960
961In order to pass a JSON object as the source, provide a parsed manifest object in via a
962[data URI](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs),
963and using the "vnd.videojs.vhs+json" media type when setting the source type. For instance:
964
965```
966var player = videojs('some-video-id');
967const parser = new M3u8Parser();
968
969parser.push(manifestString);
970parser.end();
971
972player.src({
973 src: `data:application/vnd.videojs.vhs+json,${JSON.stringify(parser.manifest)}`,
974 type: 'application/vnd.videojs.vhs+json'
975});
976```
977
978The manifest object should follow the "VHS manifest object schema" (a somewhat flexible
979and informally documented structure) provided in the README of
980[m3u8-parser](https://github.com/videojs/m3u8-parser) and
981[mpd-parser](https://github.com/videojs/mpd-parser). This may be referred to in the
982project as `vhs-json`.
983
984## Hosting Considerations
985Unlike a native HLS implementation, the HLS tech has to comply with
986the browser's security policies. That means that all the files that
987make up the stream must be served from the same domain as the page
988hosting the video player or from a server that has appropriate [CORS
989headers](https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS)
990configured. Easy [instructions are
991available](http://enable-cors.org/server.html) for popular webservers
992and most CDNs should have no trouble turning CORS on for your account.
993
994
995## Known Issues and Workarounds
996Issues that are currenty known. If you want to
997help find a solution that would be appreciated!
998
999### Fragmented MP4 Support
1000Edge has native support for HLS but only in the MPEG2-TS container. If
1001you attempt to play an HLS stream with fragmented MP4 segments (without
1002[overriding native playback](#overridenative)), Edge will stall.
1003Fragmented MP4s are only supported on browsers that have
1004[Media Source Extensions](http://caniuse.com/#feat=mediasource) available.
1005
1006### Assets with an Audio-Only Rate Get Stuck in Audio-Only
1007Some assets which have an audio-only rate and the lowest-bandwidth
1008audio + video rate isn't that low get stuck in audio-only mode. This is
1009because the initial bandwidth calculation thinks it there's insufficient
1010bandwidth for selecting the lowest-quality audio+video playlist, so it picks
1011the only-audio one, which unfortunately locks it to being audio-only forever,
1012preventing a switch to the audio+video playlist when it gets a better
1013estimation of bandwidth.
1014
1015Until we've implemented a full fix, it is recommended to set the
1016[`enableLowInitialPlaylist` option](#enablelowinitialplaylist) for any assets
1017that include an audio-only rate; it should always select the lowest-bandwidth
1018audio+video playlist for its first playlist.
1019
1020It's also worth mentioning that Apple no longer requires having an audio-only
1021rate; instead, they require a 192kbps audio+video rate (see Apple's current
1022[HLS Authoring Specification][]). Removing the audio-only rate would of course
1023eliminate this problem since there would be only audio+video playlists to
1024choose from.
1025
1026Follow progress on this in issue [#175](https://github.com/videojs/http-streaming/issues/175).
1027
1028[HLS Authoring Specification]: https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_devices
1029
1030### DASH Assets with `$Time` Interpolation and `SegmentTimeline`s with No `t`
1031
1032DASH assets which use `$Time` in a `SegmentTemplate`, and also have a
1033`SegmentTimeline` where only the first `S` has a `t` and the rest only have a
1034`d`, do not load currently.
1035
1036There is currently no workaround for this, but you can track progress on this
1037in issue [#256](https://github.com/videojs/http-streaming/issues/256).
1038
1039## Testing
1040
1041For testing, you run `npm run test`. You will need Chrome and Firefox for running the tests.
1042
1043_videojs-http-streaming uses [BrowserStack](https://browserstack.com) for compatibility testing._
1044
1045## Debugging
1046
1047videojs-http-streaming makes use of `videojs.log` for debug logging. You can enable these logs
1048by setting the log level to `debug` using `videojs.log.level('debug')`. You can access a complete
1049history of the logs using `videojs.log.history()`. This history is maintained even when the
1050log level is not set to `debug`.
1051
1052`vhs.stats` can also be helpful when debugging. Accessing this object will give you
1053a snapshot summary of various HLS and player stats. See [vhs.stats](#vhsstats) for details
1054about what this object contains.
1055
1056__NOTE__: The `debug` level is only available in video.js v6.6.0+. With earlier versions of
1057video.js, no debug messages will be logged to console.
1058
1059## Release History
1060Check out the [changelog](CHANGELOG.md) for a summary of each release.
1061
1062## Building
1063To build a copy of videojs-http-streaming run the following commands
1064
1065```bash
1066git clone https://github.com/videojs/http-streaming
1067cd http-streaming
1068npm i
1069npm run build
1070```
1071
1072videojs-http-streaming will have created all of the files for using it in a dist folder
1073
1074## Development
1075
1076### Tools
1077* Download stream locally with the [HLS Fetcher](https://github.com/videojs/hls-fetcher)
1078* Simulate errors with [Murphy](https://github.com/videojs/murphy)
1079* Inspect content with [Thumbcoil](http://thumb.co.il)
1080
1081### Commands
1082All commands for development are listed in the `package.json` file and are run using
1083```bash
1084npm run <command>
1085```
1086
1087[slack-icon]: http://slack.videojs.com/badge.svg
1088[slack-link]: http://slack.videojs.com
1089[travis-icon]: https://travis-ci.org/videojs/http-streaming.svg?branch=main
1090[travis-link]: https://travis-ci.org/videojs/http-streaming
1091[issue-stats-link]: http://issuestats.com/github/videojs/http-streaming
1092[issue-stats-pr-icon]: http://issuestats.com/github/videojs/http-streaming/badge/pr
1093[issue-stats-issues-icon]: http://issuestats.com/github/videojs/http-streaming/badge/issue
1094[greenkeeper-icon]: https://badges.greenkeeper.io/videojs/http-streaming.svg
1095[greenkeeper-link]: https://greenkeeper.io/