mirror of
https://github.com/DanielnetoDotCom/YouPHPTube
synced 2025-10-06 03:50:04 +02:00
Update
This commit is contained in:
parent
c940cd61ac
commit
59a20745e7
2101 changed files with 1312074 additions and 30292 deletions
63
node_modules/@videojs/http-streaming/CHANGELOG.md
generated
vendored
63
node_modules/@videojs/http-streaming/CHANGELOG.md
generated
vendored
|
@ -1,3 +1,66 @@
|
|||
<a name="3.10.0"></a>
|
||||
# [3.10.0](https://github.com/videojs/http-streaming/compare/v3.9.1...v3.10.0) (2024-01-17)
|
||||
|
||||
### Features
|
||||
|
||||
* handle rollover for VTT cues ([#1472](https://github.com/videojs/http-streaming/issues/1472)) ([8e8a341](https://github.com/videojs/http-streaming/commit/8e8a341))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Check if change to the provided type is supported ([#1463](https://github.com/videojs/http-streaming/issues/1463)) ([#1475](https://github.com/videojs/http-streaming/issues/1475)) ([e2ab570](https://github.com/videojs/http-streaming/commit/e2ab570))
|
||||
|
||||
<a name="3.9.1"></a>
|
||||
## [3.9.1](https://github.com/videojs/http-streaming/compare/v3.9.0...v3.9.1) (2024-01-02)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Account for difference between duration info in the playlist and the actual duration ([#1470](https://github.com/videojs/http-streaming/issues/1470)) ([455b020](https://github.com/videojs/http-streaming/commit/455b020))
|
||||
* keyId filtering loadedplaylist listener and improvements ([#1468](https://github.com/videojs/http-streaming/issues/1468)) ([f12c197](https://github.com/videojs/http-streaming/commit/f12c197))
|
||||
* select next if we are at the of the current segment ([#1467](https://github.com/videojs/http-streaming/issues/1467)) ([7debc17](https://github.com/videojs/http-streaming/commit/7debc17))
|
||||
* toLowerCase keyIds from manifest and use fastQualityChange ([#1466](https://github.com/videojs/http-streaming/issues/1466)) ([88a5671](https://github.com/videojs/http-streaming/commit/88a5671))
|
||||
|
||||
<a name="3.9.0"></a>
|
||||
# [3.9.0](https://github.com/videojs/http-streaming/compare/v3.8.0...v3.9.0) (2023-12-14)
|
||||
|
||||
### Features
|
||||
|
||||
* enable playlists with 'usable' keystatus ([#1460](https://github.com/videojs/http-streaming/issues/1460)) ([7d7c639](https://github.com/videojs/http-streaming/commit/7d7c639))
|
||||
|
||||
### Chores
|
||||
|
||||
* update mpd-parser to v1.3.0 ([#1461](https://github.com/videojs/http-streaming/issues/1461)) ([39dbe77](https://github.com/videojs/http-streaming/commit/39dbe77))
|
||||
|
||||
<a name="3.8.0"></a>
|
||||
# [3.8.0](https://github.com/videojs/http-streaming/compare/v3.7.0...v3.8.0) (2023-12-04)
|
||||
|
||||
### Features
|
||||
|
||||
* Content Steering HLS Pathway Cloning ([#1432](https://github.com/videojs/http-streaming/issues/1432)) ([731058b](https://github.com/videojs/http-streaming/commit/731058b))
|
||||
* media-sequence sync strategy, remove calculateTimestampOffsetForEachSegment and remove replaceSegmentsUntil ([#1457](https://github.com/videojs/http-streaming/issues/1457)) ([e304c20](https://github.com/videojs/http-streaming/commit/e304c20)), closes [#1452](https://github.com/videojs/http-streaming/issues/1452) [#1451](https://github.com/videojs/http-streaming/issues/1451) [#1444](https://github.com/videojs/http-streaming/issues/1444) [#1439](https://github.com/videojs/http-streaming/issues/1439) [#1426](https://github.com/videojs/http-streaming/issues/1426) [#1414](https://github.com/videojs/http-streaming/issues/1414) [#1458](https://github.com/videojs/http-streaming/issues/1458)
|
||||
* public function for updating VHS options ([#1446](https://github.com/videojs/http-streaming/issues/1446)) ([9f2a4de](https://github.com/videojs/http-streaming/commit/9f2a4de))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* Always use VOD sync-point for VOD streams ([#1456](https://github.com/videojs/http-streaming/issues/1456)) ([a5579b0](https://github.com/videojs/http-streaming/commit/a5579b0))
|
||||
* check for transmuxer for vtt-segment-loader ([#1452](https://github.com/videojs/http-streaming/issues/1452)) ([b4dd748](https://github.com/videojs/http-streaming/commit/b4dd748))
|
||||
* content steering bug fixes and tests ([#1430](https://github.com/videojs/http-streaming/issues/1430)) ([532aa4d](https://github.com/videojs/http-streaming/commit/532aa4d))
|
||||
* Do not call load after mediachange for hls playlist loader ([#1447](https://github.com/videojs/http-streaming/issues/1447)) ([28413f8](https://github.com/videojs/http-streaming/commit/28413f8))
|
||||
* fix several issues with calculate timestamp offset for each segment ([#1451](https://github.com/videojs/http-streaming/issues/1451)) ([3bbc6ef](https://github.com/videojs/http-streaming/commit/3bbc6ef))
|
||||
* prevent wrapping in resetMainLoaderReplaceSegments ([#1439](https://github.com/videojs/http-streaming/issues/1439)) ([719b7f4](https://github.com/videojs/http-streaming/commit/719b7f4))
|
||||
* replaceSegmentsUntil flag resetting too early ([#1444](https://github.com/videojs/http-streaming/issues/1444)) ([af39ba5](https://github.com/videojs/http-streaming/commit/af39ba5))
|
||||
* use startTime instead of 0 for finiteDuration ([#1448](https://github.com/videojs/http-streaming/issues/1448)) ([dc78d78](https://github.com/videojs/http-streaming/commit/dc78d78))
|
||||
* wrap onwarn values in a message object ([#1428](https://github.com/videojs/http-streaming/issues/1428)) ([beccfa1](https://github.com/videojs/http-streaming/commit/beccfa1))
|
||||
|
||||
### Chores
|
||||
|
||||
* fix tests, remove qunit only ([#1449](https://github.com/videojs/http-streaming/issues/1449)) ([f294133](https://github.com/videojs/http-streaming/commit/f294133))
|
||||
* Update mux.js 7.0.1 to 7.0.2 ([#1450](https://github.com/videojs/http-streaming/issues/1450)) ([b22f6f1](https://github.com/videojs/http-streaming/commit/b22f6f1))
|
||||
|
||||
### Documentation
|
||||
|
||||
* add docs for content steering ([#1442](https://github.com/videojs/http-streaming/issues/1442)) ([cc22082](https://github.com/videojs/http-streaming/commit/cc22082))
|
||||
* Update arch.md ([#1459](https://github.com/videojs/http-streaming/issues/1459)) ([a891580](https://github.com/videojs/http-streaming/commit/a891580))
|
||||
|
||||
<a name="3.7.0"></a>
|
||||
# [3.7.0](https://github.com/videojs/http-streaming/compare/v3.6.0...v3.7.0) (2023-10-12)
|
||||
|
||||
|
|
6
node_modules/@videojs/http-streaming/README.md
generated
vendored
6
node_modules/@videojs/http-streaming/README.md
generated
vendored
|
@ -463,12 +463,6 @@ This option defaults to `false`.
|
|||
* Default: `false`
|
||||
* 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).
|
||||
|
||||
##### calculateTimestampOffsetForEachSegment
|
||||
* Type: `boolean`,
|
||||
* Default: `false`
|
||||
* Calculate timestampOffset for each segment, regardless of its timeline. Sometimes it is helpful when you have corrupted DTS/PTS timestamps during discontinuities.
|
||||
|
||||
|
||||
##### useForcedSubtitles
|
||||
* Type: `boolean`
|
||||
* Default: `false`
|
||||
|
|
1261
node_modules/@videojs/http-streaming/dist/videojs-http-streaming-sync-workers.js
generated
vendored
1261
node_modules/@videojs/http-streaming/dist/videojs-http-streaming-sync-workers.js
generated
vendored
File diff suppressed because it is too large
Load diff
1223
node_modules/@videojs/http-streaming/dist/videojs-http-streaming.cjs.js
generated
vendored
1223
node_modules/@videojs/http-streaming/dist/videojs-http-streaming.cjs.js
generated
vendored
File diff suppressed because it is too large
Load diff
1221
node_modules/@videojs/http-streaming/dist/videojs-http-streaming.es.js
generated
vendored
1221
node_modules/@videojs/http-streaming/dist/videojs-http-streaming.es.js
generated
vendored
File diff suppressed because it is too large
Load diff
1261
node_modules/@videojs/http-streaming/dist/videojs-http-streaming.js
generated
vendored
1261
node_modules/@videojs/http-streaming/dist/videojs-http-streaming.js
generated
vendored
File diff suppressed because it is too large
Load diff
10
node_modules/@videojs/http-streaming/dist/videojs-http-streaming.min.js
generated
vendored
10
node_modules/@videojs/http-streaming/dist/videojs-http-streaming.min.js
generated
vendored
File diff suppressed because one or more lines are too long
4
node_modules/@videojs/http-streaming/docs/arch.md
generated
vendored
4
node_modules/@videojs/http-streaming/docs/arch.md
generated
vendored
|
@ -6,9 +6,9 @@ This project has three primary duties:
|
|||
1. Feed content bits to a SourceBuffer by downloading and transmuxing video segments
|
||||
|
||||
### Playlist Management
|
||||
The [playlist loader](../src/playlist-loader.js) handles all of the details of requesting, parsing, updating, and switching playlists at runtime. It's operation is described by this state diagram:
|
||||
The [playlist loader](playlist-loader.md) ([source](../src/playlist-loader.js)) handles all of the details of requesting, parsing, updating, and switching playlists at runtime. It's operation is described by this state diagram:
|
||||
|
||||

|
||||

|
||||
|
||||
During VOD playback, the loader will move quickly to the HAVE_METADATA state and then stay there unless a quality switch request sends it to SWITCHING_MEDIA while it fetches an alternate playlist. The loader enters the HAVE_CURRENT_METADATA when a live stream is detected and it's time to refresh the current media playlist to find out about new video segments.
|
||||
|
||||
|
|
47
node_modules/@videojs/http-streaming/docs/content-steering.md
generated
vendored
Normal file
47
node_modules/@videojs/http-streaming/docs/content-steering.md
generated
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
# Content Steering
|
||||
|
||||
Content Steering provides content creators a method of runtime control over
|
||||
the location from which segments are fetched via a content steering server and
|
||||
pathways defined in the content manifest. For a working example visit
|
||||
https://www.content-steering.com/.
|
||||
|
||||
HLS and DASH each define their own specific Content Steering tags and properties
|
||||
that prescribe how the client should fetch the content steering manifest as well
|
||||
as make steering decisions. `#EXT-X-CONTENT-STEERING` and `<ContentSteering>` respectively.
|
||||
|
||||
For reference, HLS spec section 4.4.6.6:
|
||||
https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis#section-4.4.6.6
|
||||
|
||||
DASH-IF:
|
||||
https://dashif.org/docs/DASH-IF-CTS-00XX-Content-Steering-Community-Review.pdf
|
||||
|
||||
Both protocols rely on a content steering server to provide steering guidance.
|
||||
VHS will request the content steering manifest from the location defined in the
|
||||
content steering tag in the `.m3u8` or `.mpd` and refresh the steering manifest
|
||||
at an interval defined in that manifest.
|
||||
|
||||
A content steering manifest response will look something like this:
|
||||
```
|
||||
{
|
||||
"VERSION": 1,
|
||||
"TTL": 300,
|
||||
"RELOAD-URI": "https://steeringservice.com/app/instance12345?session=abc",
|
||||
"CDN-PRIORITY": ["beta","alpha"]
|
||||
}
|
||||
```
|
||||
`CDN-PRIORITY` represents either `PATHWAY-PRIORITY` for HLS or `SERVICE-LOCATION-PRIORITY` for DASH. This list of keys in priority order will match with either a `PATHWAY-ID` or `serviceLocation` (HLS and DASH respectively) associated with a location where VHS can fetch segments.
|
||||
|
||||
VHS will attempt to fetch segments from the locations defined in the steering manifest response in the order. Then, during playback, VHS will provide quality of experience metrics back to the steering server which can adjust the steering guidance accordingly.
|
||||
|
||||
## Notable Support
|
||||
|
||||
### HLS
|
||||
* Pathway Cloning
|
||||
### DASH
|
||||
* queryBeforeStart
|
||||
* proxyServerURL
|
||||
|
||||
## Currently Missing Support
|
||||
|
||||
### DASH
|
||||
* Extended HTTP GET request parametrization, see: ISO/IEC 23009-1 [2], clause I.3
|
1
node_modules/@videojs/http-streaming/docs/supported-features.md
generated
vendored
1
node_modules/@videojs/http-streaming/docs/supported-features.md
generated
vendored
|
@ -65,6 +65,7 @@ not meant serve as an exhaustive list.
|
|||
tracks
|
||||
* [AES-128] segment encryption
|
||||
* DASH In-manifest EventStream and Event tags are automatically translated into HTML5 metadata cues
|
||||
* [Content Steering](content-steering.md) for both HLS and DASH.
|
||||
|
||||
## Notable Missing Features
|
||||
|
||||
|
|
5
node_modules/@videojs/http-streaming/index.html
generated
vendored
5
node_modules/@videojs/http-streaming/index.html
generated
vendored
|
@ -144,11 +144,6 @@
|
|||
<label class="form-check-label" for="dts-offset">Use DTS instead of PTS for Timestamp Offset calculation (reloads player)</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input id=offset-each-segment type="checkbox" class="form-check-input">
|
||||
<label class="form-check-label" for="offset-each-segment">Calculate timestampOffset for each segment, regardless of its timeline (reloads player)</label>
|
||||
</div>
|
||||
|
||||
<div class="form-check">
|
||||
<input id=llhls type="checkbox" class="form-check-input">
|
||||
<label class="form-check-label" for="llhls">[EXPERIMENTAL] Enables support for ll-hls (reloads player)</label>
|
||||
|
|
6
node_modules/@videojs/http-streaming/package.json
generated
vendored
6
node_modules/@videojs/http-streaming/package.json
generated
vendored
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@videojs/http-streaming",
|
||||
"version": "3.7.0",
|
||||
"version": "3.10.0",
|
||||
"description": "Play back HLS and DASH with Video.js, even where it's not natively supported",
|
||||
"main": "dist/videojs-http-streaming.cjs.js",
|
||||
"module": "dist/videojs-http-streaming.es.js",
|
||||
|
@ -62,8 +62,8 @@
|
|||
"aes-decrypter": "4.0.1",
|
||||
"global": "^4.4.0",
|
||||
"m3u8-parser": "^7.1.0",
|
||||
"mpd-parser": "^1.2.2",
|
||||
"mux.js": "7.0.1",
|
||||
"mpd-parser": "^1.3.0",
|
||||
"mux.js": "7.0.2",
|
||||
"video.js": "^7 || ^8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
3
node_modules/@videojs/http-streaming/scripts/index.js
generated
vendored
3
node_modules/@videojs/http-streaming/scripts/index.js
generated
vendored
|
@ -470,7 +470,6 @@
|
|||
'pixel-diff-selector',
|
||||
'network-info',
|
||||
'dts-offset',
|
||||
'offset-each-segment',
|
||||
'override-native',
|
||||
'preload',
|
||||
'mirror-source',
|
||||
|
@ -526,7 +525,6 @@
|
|||
'pixel-diff-selector',
|
||||
'network-info',
|
||||
'dts-offset',
|
||||
'offset-each-segment',
|
||||
'exact-manifest-timings',
|
||||
'forced-subtitles'
|
||||
].forEach(function(name) {
|
||||
|
@ -611,7 +609,6 @@
|
|||
leastPixelDiffSelector: getInputValue(stateEls['pixel-diff-selector']),
|
||||
useNetworkInformationApi: getInputValue(stateEls['network-info']),
|
||||
useDtsForTimestampOffset: getInputValue(stateEls['dts-offset']),
|
||||
calculateTimestampOffsetForEachSegment: getInputValue(stateEls['offset-each-segment']),
|
||||
useForcedSubtitles: getInputValue(stateEls['forced-subtitles'])
|
||||
}
|
||||
}
|
||||
|
|
73
node_modules/@videojs/http-streaming/src/content-steering-controller.js
generated
vendored
73
node_modules/@videojs/http-streaming/src/content-steering-controller.js
generated
vendored
|
@ -11,10 +11,12 @@ import videojs from 'video.js';
|
|||
* TTL: number in seconds (optional) until the next content steering manifest reload.
|
||||
* RELOAD-URI: string (optional) uri to fetch the next content steering manifest.
|
||||
* SERVICE-LOCATION-PRIORITY or PATHWAY-PRIORITY a non empty array of unique string values.
|
||||
* PATHWAY-CLONES: array (optional) (HLS only) pathway clone objects to copy from other playlists.
|
||||
*/
|
||||
class SteeringManifest {
|
||||
constructor() {
|
||||
this.priority_ = [];
|
||||
this.pathwayClones_ = new Map();
|
||||
}
|
||||
|
||||
set version(number) {
|
||||
|
@ -43,6 +45,13 @@ class SteeringManifest {
|
|||
}
|
||||
}
|
||||
|
||||
set pathwayClones(array) {
|
||||
// pathwayClones must be non-empty.
|
||||
if (array && array.length) {
|
||||
this.pathwayClones_ = new Map(array.map((clone) => [clone.ID, clone]));
|
||||
}
|
||||
}
|
||||
|
||||
get version() {
|
||||
return this.version_;
|
||||
}
|
||||
|
@ -58,6 +67,10 @@ class SteeringManifest {
|
|||
get priority() {
|
||||
return this.priority_;
|
||||
}
|
||||
|
||||
get pathwayClones() {
|
||||
return this.pathwayClones_;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,14 +88,15 @@ export default class ContentSteeringController extends videojs.EventTarget {
|
|||
|
||||
this.currentPathway = null;
|
||||
this.defaultPathway = null;
|
||||
this.queryBeforeStart = null;
|
||||
this.queryBeforeStart = false;
|
||||
this.availablePathways_ = new Set();
|
||||
this.excludedPathways_ = new Set();
|
||||
this.steeringManifest = new SteeringManifest();
|
||||
this.proxyServerUrl_ = null;
|
||||
this.manifestType_ = null;
|
||||
this.ttlTimeout_ = null;
|
||||
this.request_ = null;
|
||||
this.currentPathwayClones = new Map();
|
||||
this.nextPathwayClones = new Map();
|
||||
this.excludedSteeringManifestURLs = new Set();
|
||||
this.logger_ = logger('Content Steering');
|
||||
this.xhr_ = xhr;
|
||||
|
@ -92,7 +106,7 @@ export default class ContentSteeringController extends videojs.EventTarget {
|
|||
/**
|
||||
* Assigns the content steering tag properties to the steering controller
|
||||
*
|
||||
* @param {string} baseUrl the baseURL from the manifest for resolving the steering manifest url
|
||||
* @param {string} baseUrl the baseURL from the main manifest for resolving the steering manifest url
|
||||
* @param {Object} steeringTag the content steering tag from the main manifest
|
||||
*/
|
||||
assignTagProperties(baseUrl, steeringTag) {
|
||||
|
@ -110,13 +124,14 @@ export default class ContentSteeringController extends videojs.EventTarget {
|
|||
this.decodeDataUriManifest_(steeringUri.substring(steeringUri.indexOf(',') + 1));
|
||||
return;
|
||||
}
|
||||
// With DASH queryBeforeStart, we want to use the steeringUri as soon as possible for the request.
|
||||
this.steeringManifest.reloadUri = this.queryBeforeStart ? steeringUri : resolveUrl(baseUrl, steeringUri);
|
||||
|
||||
// reloadUri is the resolution of the main manifest URL and steering URL.
|
||||
this.steeringManifest.reloadUri = resolveUrl(baseUrl, steeringUri);
|
||||
// pathwayId is HLS defaultServiceLocation is DASH
|
||||
this.defaultPathway = steeringTag.pathwayId || steeringTag.defaultServiceLocation;
|
||||
// currently only DASH supports the following properties on <ContentSteering> tags.
|
||||
this.queryBeforeStart = steeringTag.queryBeforeStart || false;
|
||||
this.proxyServerUrl_ = steeringTag.proxyServerURL || null;
|
||||
this.queryBeforeStart = steeringTag.queryBeforeStart;
|
||||
this.proxyServerUrl_ = steeringTag.proxyServerURL;
|
||||
|
||||
// trigger a steering event if we have a pathway from the content steering tag.
|
||||
// this tells VHS which segment pathway to start with.
|
||||
|
@ -124,10 +139,6 @@ export default class ContentSteeringController extends videojs.EventTarget {
|
|||
if (this.defaultPathway && !this.queryBeforeStart) {
|
||||
this.trigger('content-steering');
|
||||
}
|
||||
|
||||
if (this.queryBeforeStart) {
|
||||
this.requestSteeringManifest(this.steeringManifest.reloadUri);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -136,13 +147,12 @@ export default class ContentSteeringController extends videojs.EventTarget {
|
|||
*
|
||||
* @param {string} initialUri The optional uri to make the request with.
|
||||
* If set, the request should be made with exactly what is passed in this variable.
|
||||
* This scenario is specific to DASH when the queryBeforeStart parameter is true.
|
||||
* This scenario should only happen once on initalization.
|
||||
*/
|
||||
requestSteeringManifest(initialUri) {
|
||||
requestSteeringManifest(initial) {
|
||||
const reloadUri = this.steeringManifest.reloadUri;
|
||||
|
||||
if (!initialUri && !reloadUri) {
|
||||
if (!reloadUri) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -150,7 +160,7 @@ export default class ContentSteeringController extends videojs.EventTarget {
|
|||
// ExtUrlQueryInfo tag support. See the DASH content steering spec section 8.1.
|
||||
|
||||
// This request URI accounts for manifest URIs that have been excluded.
|
||||
const uri = initialUri || this.getRequestURI(reloadUri);
|
||||
const uri = initial ? reloadUri : this.getRequestURI(reloadUri);
|
||||
|
||||
// If there are no valid manifest URIs, we should stop content steering.
|
||||
if (!uri) {
|
||||
|
@ -196,8 +206,8 @@ export default class ContentSteeringController extends videojs.EventTarget {
|
|||
}
|
||||
const steeringManifestJson = JSON.parse(this.request_.responseText);
|
||||
|
||||
this.startTTLTimeout_();
|
||||
this.assignSteeringProperties_(steeringManifestJson);
|
||||
this.startTTLTimeout_();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -269,7 +279,11 @@ export default class ContentSteeringController extends videojs.EventTarget {
|
|||
this.steeringManifest.reloadUri = steeringJson['RELOAD-URI'];
|
||||
// HLS = PATHWAY-PRIORITY required. DASH = SERVICE-LOCATION-PRIORITY optional
|
||||
this.steeringManifest.priority = steeringJson['PATHWAY-PRIORITY'] || steeringJson['SERVICE-LOCATION-PRIORITY'];
|
||||
// TODO: HLS handle PATHWAY-CLONES. See section 7.2 https://datatracker.ietf.org/doc/draft-pantos-hls-rfc8216bis/
|
||||
|
||||
// Pathway clones to be created/updated in HLS.
|
||||
// See section 7.2 https://datatracker.ietf.org/doc/draft-pantos-hls-rfc8216bis/
|
||||
this.steeringManifest.pathwayClones = steeringJson['PATHWAY-CLONES'];
|
||||
this.nextPathwayClones = this.steeringManifest.pathwayClones;
|
||||
|
||||
// 1. apply first pathway from the array.
|
||||
// 2. if first pathway doesn't exist in manifest, try next pathway.
|
||||
|
@ -397,7 +411,6 @@ export default class ContentSteeringController extends videojs.EventTarget {
|
|||
this.request_ = null;
|
||||
this.excludedSteeringManifestURLs = new Set();
|
||||
this.availablePathways_ = new Set();
|
||||
this.excludedPathways_ = new Set();
|
||||
this.steeringManifest = new SteeringManifest();
|
||||
}
|
||||
|
||||
|
@ -413,13 +426,35 @@ export default class ContentSteeringController extends videojs.EventTarget {
|
|||
}
|
||||
|
||||
/**
|
||||
* clears all pathways from the available pathways set
|
||||
* Clears all pathways from the available pathways set
|
||||
*/
|
||||
clearAvailablePathways() {
|
||||
this.availablePathways_.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a pathway from the available pathways set.
|
||||
*/
|
||||
excludePathway(pathway) {
|
||||
return this.availablePathways_.delete(pathway);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the refreshed DASH manifest content steering tag for changes.
|
||||
*
|
||||
* @param {string} baseURL new steering tag on DASH manifest refresh
|
||||
* @param {Object} newTag the new tag to check for changes
|
||||
* @return a true or false whether the new tag has different values
|
||||
*/
|
||||
didDASHTagChange(baseURL, newTag) {
|
||||
return !newTag && this.steeringManifest.reloadUri ||
|
||||
newTag && (resolveUrl(baseURL, newTag.serverURL) !== this.steeringManifest.reloadUri ||
|
||||
newTag.defaultServiceLocation !== this.defaultPathway ||
|
||||
newTag.queryBeforeStart !== this.queryBeforeStart ||
|
||||
newTag.proxyServerURL !== this.proxyServerUrl_);
|
||||
}
|
||||
|
||||
getAvailablePathways() {
|
||||
return this.availablePathways_;
|
||||
}
|
||||
}
|
||||
|
|
27
node_modules/@videojs/http-streaming/src/dash-playlist-loader.js
generated
vendored
27
node_modules/@videojs/http-streaming/src/dash-playlist-loader.js
generated
vendored
|
@ -325,10 +325,7 @@ export default class DashPlaylistLoader extends EventTarget {
|
|||
|
||||
// live playlist staleness timeout
|
||||
this.on('mediaupdatetimeout', () => {
|
||||
// We handle live content steering in the playlist controller
|
||||
if (!this.media().attributes.serviceLocation) {
|
||||
this.refreshMedia_(this.media().id);
|
||||
}
|
||||
this.refreshMedia_(this.media().id);
|
||||
});
|
||||
|
||||
this.state = 'HAVE_NOTHING';
|
||||
|
@ -927,4 +924,26 @@ export default class DashPlaylistLoader extends EventTarget {
|
|||
this.addMetadataToTextTrack('EventStream', metadataArray, this.mainPlaylistLoader_.main.duration);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key ID set from a playlist
|
||||
*
|
||||
* @param {playlist} playlist to fetch the key ID set from.
|
||||
* @return a Set of 32 digit hex strings that represent the unique keyIds for that playlist.
|
||||
*/
|
||||
getKeyIdSet(playlist) {
|
||||
if (playlist.contentProtection) {
|
||||
const keyIds = new Set();
|
||||
|
||||
for (const keysystem in playlist.contentProtection) {
|
||||
const defaultKID = playlist.contentProtection[keysystem].attributes['cenc:default_KID'];
|
||||
|
||||
if (defaultKID) {
|
||||
// DASH keyIds are separated by dashes.
|
||||
keyIds.add(defaultKID.replace(/-/g, '').toLowerCase());
|
||||
}
|
||||
}
|
||||
return keyIds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
6
node_modules/@videojs/http-streaming/src/manifest.js
generated
vendored
6
node_modules/@videojs/http-streaming/src/manifest.js
generated
vendored
|
@ -11,7 +11,7 @@ export const createPlaylistID = (index, uri) => {
|
|||
};
|
||||
|
||||
// default function for creating a group id
|
||||
const groupID = (type, group, label) => {
|
||||
export const groupID = (type, group, label) => {
|
||||
return `placeholder-uri-${type}-${group}-${label}`;
|
||||
};
|
||||
|
||||
|
@ -93,7 +93,7 @@ export const parseManifest = ({
|
|||
}
|
||||
|
||||
if (onwarn) {
|
||||
onwarn(`manifest has no targetDuration defaulting to ${targetDuration}`);
|
||||
onwarn({ message: `manifest has no targetDuration defaulting to ${targetDuration}` });
|
||||
}
|
||||
manifest.targetDuration = targetDuration;
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ export const parseManifest = ({
|
|||
const partTargetDuration = parts.reduce((acc, p) => Math.max(acc, p.duration), 0);
|
||||
|
||||
if (onwarn) {
|
||||
onwarn(`manifest has no partTargetDuration defaulting to ${partTargetDuration}`);
|
||||
onwarn({ message: `manifest has no partTargetDuration defaulting to ${partTargetDuration}` });
|
||||
log.error('LL-HLS manifest has parts but lacks required #EXT-X-PART-INF:PART-TARGET value. See https://datatracker.ietf.org/doc/html/draft-pantos-hls-rfc8216bis-09#section-4.4.3.7. Playback is not guaranteed.');
|
||||
}
|
||||
manifest.partTargetDuration = partTargetDuration;
|
||||
|
|
369
node_modules/@videojs/http-streaming/src/playlist-controller.js
generated
vendored
369
node_modules/@videojs/http-streaming/src/playlist-controller.js
generated
vendored
|
@ -28,6 +28,7 @@ import logger from './util/logger';
|
|||
import {merge, createTimeRanges} from './util/vjs-compat';
|
||||
import { addMetadata, createMetadataTrackIfNotExists, addDateRangeMetadata } from './util/text-tracks';
|
||||
import ContentSteeringController from './content-steering-controller';
|
||||
import { bufferToHexString } from './util/string.js';
|
||||
|
||||
const ABORT_EARLY_EXCLUSION_SECONDS = 10;
|
||||
|
||||
|
@ -235,12 +236,12 @@ export class PlaylistController extends videojs.EventTarget {
|
|||
this.sourceUpdater_ = new SourceUpdater(this.mediaSource);
|
||||
this.inbandTextTracks_ = {};
|
||||
this.timelineChangeController_ = new TimelineChangeController();
|
||||
this.keyStatusMap_ = new Map();
|
||||
|
||||
const segmentLoaderSettings = {
|
||||
vhs: this.vhs_,
|
||||
parse708captions: options.parse708captions,
|
||||
useDtsForTimestampOffset: options.useDtsForTimestampOffset,
|
||||
calculateTimestampOffsetForEachSegment: options.calculateTimestampOffsetForEachSegment,
|
||||
captionServices,
|
||||
mediaSource: this.mediaSource,
|
||||
currentTime: this.tech_.currentTime.bind(this.tech_),
|
||||
|
@ -404,7 +405,7 @@ export class PlaylistController extends videojs.EventTarget {
|
|||
switchMedia_(playlist, cause, delay) {
|
||||
const oldMedia = this.media();
|
||||
const oldId = oldMedia && (oldMedia.id || oldMedia.uri);
|
||||
const newId = playlist.id || playlist.uri;
|
||||
const newId = playlist && (playlist.id || playlist.uri);
|
||||
|
||||
if (oldId && oldId !== newId) {
|
||||
this.logger_(`switch media ${oldId} -> ${newId} from ${cause}`);
|
||||
|
@ -608,6 +609,8 @@ export class PlaylistController extends videojs.EventTarget {
|
|||
let updatedPlaylist = this.mainPlaylistLoader_.media();
|
||||
|
||||
if (!updatedPlaylist) {
|
||||
// Add content steering listeners on first load and init.
|
||||
this.attachContentSteeringListeners_();
|
||||
this.initContentSteeringController_();
|
||||
// exclude any variants that are not supported by the browser before selecting
|
||||
// an initial media as the playlist selectors do not consider browser support
|
||||
|
@ -671,15 +674,23 @@ export class PlaylistController extends videojs.EventTarget {
|
|||
this.requestOptions_.timeout = requestTimeout;
|
||||
}
|
||||
|
||||
this.mainPlaylistLoader_.load();
|
||||
if (this.sourceType_ === 'dash') {
|
||||
// we don't want to re-request the same hls playlist right after it was changed
|
||||
this.mainPlaylistLoader_.load();
|
||||
}
|
||||
|
||||
// TODO: Create a new event on the PlaylistLoader that signals
|
||||
// that the segments have changed in some way and use that to
|
||||
// update the SegmentLoader instead of doing it twice here and
|
||||
// on `loadedplaylist`
|
||||
this.mainSegmentLoader_.pause();
|
||||
this.mainSegmentLoader_.playlist(media, this.requestOptions_);
|
||||
|
||||
this.mainSegmentLoader_.load();
|
||||
if (this.waitingForFastQualityPlaylistReceived_) {
|
||||
this.runFastQualitySwitch_();
|
||||
} else {
|
||||
this.mainSegmentLoader_.load();
|
||||
}
|
||||
|
||||
this.tech_.trigger({
|
||||
type: 'mediachange',
|
||||
|
@ -741,7 +752,12 @@ export class PlaylistController extends videojs.EventTarget {
|
|||
// that the segments have changed in some way and use that to
|
||||
// update the SegmentLoader instead of doing it twice here and
|
||||
// on `mediachange`
|
||||
this.mainSegmentLoader_.pause();
|
||||
this.mainSegmentLoader_.playlist(updatedPlaylist, this.requestOptions_);
|
||||
if (this.waitingForFastQualityPlaylistReceived_) {
|
||||
this.runFastQualitySwitch_();
|
||||
}
|
||||
|
||||
this.updateDuration(!updatedPlaylist.endList);
|
||||
|
||||
// If the player isn't paused, ensure that the segment loader is running,
|
||||
|
@ -956,39 +972,39 @@ export class PlaylistController extends videojs.EventTarget {
|
|||
|
||||
/**
|
||||
* Re-tune playback quality level for the current player
|
||||
* conditions. This will reset the main segment loader
|
||||
* and the next segment position to the currentTime.
|
||||
* This is good for manual quality changes.
|
||||
* conditions. This method will perform destructive actions like removing
|
||||
* already buffered content in order to readjust the currently active
|
||||
* playlist quickly. This is good for manual quality changes
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
fastQualityChange_(media = this.selectPlaylist()) {
|
||||
if (media === this.mainPlaylistLoader_.media()) {
|
||||
if (media && media === this.mainPlaylistLoader_.media()) {
|
||||
this.logger_('skipping fastQualityChange because new media is same as old');
|
||||
return;
|
||||
}
|
||||
|
||||
this.switchMedia_(media, 'fast-quality');
|
||||
// Reset main segment loader properties and next segment position information.
|
||||
// Don't need to reset audio as it is reset when media changes.
|
||||
// We resetLoaderProperties separately here as we want to fetch init segments if
|
||||
// necessary and ensure we're not in an ended state when we switch playlists.
|
||||
this.resetMainLoaderReplaceSegments();
|
||||
|
||||
// we would like to avoid race condition when we call fastQuality,
|
||||
// reset everything and start loading segments from prev segments instead of new because new playlist is not received yet
|
||||
this.waitingForFastQualityPlaylistReceived_ = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the replaceUntil flag on the main segment soader to the buffered end
|
||||
* and resets the main segment loaders properties.
|
||||
*/
|
||||
resetMainLoaderReplaceSegments() {
|
||||
const buffered = this.tech_.buffered();
|
||||
const bufferedEnd = buffered.end(buffered.length - 1);
|
||||
runFastQualitySwitch_() {
|
||||
this.waitingForFastQualityPlaylistReceived_ = false;
|
||||
// Delete all buffered data to allow an immediate quality switch, then seek to give
|
||||
// the browser a kick to remove any cached frames from the previous rendtion (.04 seconds
|
||||
// ahead was roughly the minimum that will accomplish this across a variety of content
|
||||
// in IE and Edge, but seeking in place is sufficient on all other browsers)
|
||||
// Edge/IE bug: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/14600375/
|
||||
// Chrome bug: https://bugs.chromium.org/p/chromium/issues/detail?id=651904
|
||||
this.mainSegmentLoader_.pause();
|
||||
this.mainSegmentLoader_.resetEverything(() => {
|
||||
this.tech_.setCurrentTime(this.tech_.currentTime());
|
||||
});
|
||||
|
||||
// Set the replace segments flag to the buffered end, this forces fetchAtBuffer
|
||||
// on the main loader to remain, false after the resetLoader call, until we have
|
||||
// replaced all content buffered ahead of the currentTime.
|
||||
this.mainSegmentLoader_.replaceSegmentsUntil = bufferedEnd;
|
||||
this.mainSegmentLoader_.resetLoaderProperties();
|
||||
this.mainSegmentLoader_.resetLoader();
|
||||
// don't need to reset audio as it is reset when media changes
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1450,11 +1466,14 @@ export class PlaylistController extends videojs.EventTarget {
|
|||
|
||||
// cancel outstanding requests so we begin buffering at the new
|
||||
// location
|
||||
this.mainSegmentLoader_.pause();
|
||||
this.mainSegmentLoader_.resetEverything();
|
||||
if (this.mediaTypes_.AUDIO.activePlaylistLoader) {
|
||||
this.audioSegmentLoader_.pause();
|
||||
this.audioSegmentLoader_.resetEverything();
|
||||
}
|
||||
if (this.mediaTypes_.SUBTITLES.activePlaylistLoader) {
|
||||
this.subtitleSegmentLoader_.pause();
|
||||
this.subtitleSegmentLoader_.resetEverything();
|
||||
}
|
||||
|
||||
|
@ -1691,6 +1710,7 @@ export class PlaylistController extends videojs.EventTarget {
|
|||
this.mainPlaylistLoader_.dispose();
|
||||
this.mainSegmentLoader_.dispose();
|
||||
this.contentSteeringController_.dispose();
|
||||
this.keyStatusMap_.clear();
|
||||
|
||||
if (this.loadOnPlay_) {
|
||||
this.tech_.off('play', this.loadOnPlay_);
|
||||
|
@ -2104,51 +2124,84 @@ export class PlaylistController extends videojs.EventTarget {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility for getting the pathway or service location from an HLS or DASH playlist.
|
||||
*
|
||||
* @param {Object} playlist for getting pathway from.
|
||||
* @return the pathway attribute of a playlist
|
||||
*/
|
||||
pathwayAttribute_(playlist) {
|
||||
return playlist.attributes['PATHWAY-ID'] || playlist.attributes.serviceLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize content steering listeners and apply the tag properties.
|
||||
* Initialize available pathways and apply the tag properties.
|
||||
*/
|
||||
initContentSteeringController_() {
|
||||
const initialMain = this.main();
|
||||
const main = this.main();
|
||||
|
||||
if (!initialMain.contentSteering) {
|
||||
if (!main.contentSteering) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updateSteeringValues = (main) => {
|
||||
for (const playlist of main.playlists) {
|
||||
this.contentSteeringController_.addAvailablePathway(this.pathwayAttribute_(playlist));
|
||||
}
|
||||
|
||||
this.contentSteeringController_.assignTagProperties(main.uri, main.contentSteering);
|
||||
};
|
||||
|
||||
updateSteeringValues(initialMain);
|
||||
|
||||
this.contentSteeringController_.on('content-steering', this.excludeThenChangePathway_.bind(this));
|
||||
|
||||
// We need to ensure we update the content steering values when a new
|
||||
// manifest is loaded in live DASH with content steering.
|
||||
if (this.sourceType_ === 'dash') {
|
||||
this.mainPlaylistLoader_.on('mediaupdatetimeout', () => {
|
||||
this.mainPlaylistLoader_.refreshMedia_(this.mainPlaylistLoader_.media().id);
|
||||
|
||||
// clear past values
|
||||
this.contentSteeringController_.abort();
|
||||
this.contentSteeringController_.clearTTLTimeout_();
|
||||
this.contentSteeringController_.clearAvailablePathways();
|
||||
|
||||
updateSteeringValues(this.main());
|
||||
});
|
||||
for (const playlist of main.playlists) {
|
||||
this.contentSteeringController_.addAvailablePathway(this.pathwayAttribute_(playlist));
|
||||
}
|
||||
this.contentSteeringController_.assignTagProperties(main.uri, main.contentSteering);
|
||||
// request the steering manifest immediately if queryBeforeStart is set.
|
||||
if (this.contentSteeringController_.queryBeforeStart) {
|
||||
// When queryBeforeStart is true, initial request should omit steering parameters.
|
||||
this.contentSteeringController_.requestSteeringManifest(true);
|
||||
return;
|
||||
}
|
||||
// otherwise start content steering after playback starts
|
||||
this.tech_.one('canplay', () => {
|
||||
this.contentSteeringController_.requestSteeringManifest();
|
||||
});
|
||||
}
|
||||
|
||||
// Do this at startup only, after that the steering requests are managed by the Content Steering class.
|
||||
// DASH queryBeforeStart scenarios will be handled by the Content Steering class.
|
||||
if (!this.contentSteeringController_.queryBeforeStart) {
|
||||
this.tech_.one('canplay', () => {
|
||||
this.contentSteeringController_.requestSteeringManifest();
|
||||
/**
|
||||
* Reset the content steering controller and re-init.
|
||||
*/
|
||||
resetContentSteeringController_() {
|
||||
this.contentSteeringController_.clearAvailablePathways();
|
||||
this.contentSteeringController_.dispose();
|
||||
this.initContentSteeringController_();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches the listeners for content steering.
|
||||
*/
|
||||
attachContentSteeringListeners_() {
|
||||
this.contentSteeringController_.on('content-steering', this.excludeThenChangePathway_.bind(this));
|
||||
if (this.sourceType_ === 'dash') {
|
||||
this.mainPlaylistLoader_.on('loadedplaylist', () => {
|
||||
const main = this.main();
|
||||
// check if steering tag or pathways changed.
|
||||
const didDashTagChange = this.contentSteeringController_.didDASHTagChange(main.uri, main.contentSteering);
|
||||
const didPathwaysChange = () => {
|
||||
const availablePathways = this.contentSteeringController_.getAvailablePathways();
|
||||
const newPathways = [];
|
||||
|
||||
for (const playlist of main.playlists) {
|
||||
const serviceLocation = playlist.attributes.serviceLocation;
|
||||
|
||||
if (serviceLocation) {
|
||||
newPathways.push(serviceLocation);
|
||||
if (!availablePathways.has(serviceLocation)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
// If we have no new serviceLocations and previously had availablePathways
|
||||
if (!newPathways.length && availablePathways.size) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
if (didDashTagChange || didPathwaysChange()) {
|
||||
this.resetContentSteeringController_();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -2162,6 +2215,9 @@ export class PlaylistController extends videojs.EventTarget {
|
|||
if (!currentPathway) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.handlePathwayClones_();
|
||||
|
||||
const main = this.main();
|
||||
const playlists = main.playlists;
|
||||
const ids = new Set();
|
||||
|
@ -2211,6 +2267,106 @@ export class PlaylistController extends videojs.EventTarget {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add, update, or delete playlists and media groups for
|
||||
* the pathway clones for HLS Content Steering.
|
||||
*
|
||||
* See https://datatracker.ietf.org/doc/draft-pantos-hls-rfc8216bis/
|
||||
*
|
||||
* NOTE: Pathway cloning does not currently support the `PER_VARIANT_URIS` and
|
||||
* `PER_RENDITION_URIS` as we do not handle `STABLE-VARIANT-ID` or
|
||||
* `STABLE-RENDITION-ID` values.
|
||||
*/
|
||||
handlePathwayClones_() {
|
||||
const main = this.main();
|
||||
const playlists = main.playlists;
|
||||
const currentPathwayClones = this.contentSteeringController_.currentPathwayClones;
|
||||
const nextPathwayClones = this.contentSteeringController_.nextPathwayClones;
|
||||
|
||||
const hasClones = (currentPathwayClones && currentPathwayClones.size) || (nextPathwayClones && nextPathwayClones.size);
|
||||
|
||||
if (!hasClones) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [id, clone] of currentPathwayClones.entries()) {
|
||||
const newClone = nextPathwayClones.get(id);
|
||||
|
||||
// Delete the old pathway clone.
|
||||
if (!newClone) {
|
||||
this.mainPlaylistLoader_.updateOrDeleteClone(clone);
|
||||
this.contentSteeringController_.excludePathway(id);
|
||||
}
|
||||
}
|
||||
|
||||
for (const [id, clone] of nextPathwayClones.entries()) {
|
||||
const oldClone = currentPathwayClones.get(id);
|
||||
|
||||
// Create a new pathway if it is a new pathway clone object.
|
||||
if (!oldClone) {
|
||||
const playlistsToClone = playlists.filter(p => {
|
||||
return p.attributes['PATHWAY-ID'] === clone['BASE-ID'];
|
||||
});
|
||||
|
||||
playlistsToClone.forEach((p) => {
|
||||
this.mainPlaylistLoader_.addClonePathway(clone, p);
|
||||
});
|
||||
|
||||
this.contentSteeringController_.addAvailablePathway(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
// There have not been changes to the pathway clone object, so skip.
|
||||
if (this.equalPathwayClones_(oldClone, clone)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Update a preexisting cloned pathway.
|
||||
// True is set for the update flag.
|
||||
this.mainPlaylistLoader_.updateOrDeleteClone(clone, true);
|
||||
this.contentSteeringController_.addAvailablePathway(id);
|
||||
}
|
||||
|
||||
// Deep copy contents of next to current pathways.
|
||||
this.contentSteeringController_.currentPathwayClones = new Map(JSON.parse(JSON.stringify([...nextPathwayClones])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether two pathway clone objects are equivalent.
|
||||
*
|
||||
* @param {Object} a The first pathway clone object.
|
||||
* @param {Object} b The second pathway clone object.
|
||||
* @return {boolean} True if the pathway clone objects are equal, false otherwise.
|
||||
*/
|
||||
equalPathwayClones_(a, b) {
|
||||
if (
|
||||
a['BASE-ID'] !== b['BASE-ID'] ||
|
||||
a.ID !== b.ID ||
|
||||
a['URI-REPLACEMENT'].HOST !== b['URI-REPLACEMENT'].HOST
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const aParams = a['URI-REPLACEMENT'].PARAMS;
|
||||
const bParams = b['URI-REPLACEMENT'].PARAMS;
|
||||
|
||||
// We need to iterate through both lists of params because one could be
|
||||
// missing a parameter that the other has.
|
||||
for (const p in aParams) {
|
||||
if (aParams[p] !== bParams[p]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const p in bParams) {
|
||||
if (aParams[p] !== bParams[p]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the current playlists for audio, video and subtitles after a new pathway
|
||||
* is chosen from content steering.
|
||||
|
@ -2227,4 +2383,97 @@ export class PlaylistController extends videojs.EventTarget {
|
|||
|
||||
this.switchMedia_(nextPlaylist, 'content-steering');
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterates through playlists and check their keyId set and compare with the
|
||||
* keyStatusMap, only enable playlists that have a usable key. If the playlist
|
||||
* has no keyId leave it enabled by default.
|
||||
*/
|
||||
excludeNonUsablePlaylistsByKeyId_() {
|
||||
if (!this.mainPlaylistLoader_ || !this.mainPlaylistLoader_.main) {
|
||||
return;
|
||||
}
|
||||
|
||||
let nonUsableKeyStatusCount = 0;
|
||||
const NON_USABLE = 'non-usable';
|
||||
|
||||
this.mainPlaylistLoader_.main.playlists.forEach((playlist) => {
|
||||
const keyIdSet = this.mainPlaylistLoader_.getKeyIdSet(playlist);
|
||||
|
||||
// If the playlist doesn't have keyIDs lets not exclude it.
|
||||
if (!keyIdSet || !keyIdSet.size) {
|
||||
return;
|
||||
}
|
||||
keyIdSet.forEach((key) => {
|
||||
const USABLE = 'usable';
|
||||
const hasUsableKeyStatus = this.keyStatusMap_.has(key) && this.keyStatusMap_.get(key) === USABLE;
|
||||
const nonUsableExclusion = playlist.lastExcludeReason_ === NON_USABLE && playlist.excludeUntil === Infinity;
|
||||
|
||||
if (!hasUsableKeyStatus) {
|
||||
// Only exclude playlists that haven't already been excluded as non-usable.
|
||||
if (playlist.excludeUntil !== Infinity && playlist.lastExcludeReason_ !== NON_USABLE) {
|
||||
playlist.excludeUntil = Infinity;
|
||||
playlist.lastExcludeReason_ = NON_USABLE;
|
||||
this.logger_(`excluding playlist ${playlist.id} because the key ID ${key} doesn't exist in the keyStatusMap or is not ${USABLE}`);
|
||||
}
|
||||
// count all nonUsableKeyStatus
|
||||
nonUsableKeyStatusCount++;
|
||||
} else if (hasUsableKeyStatus && nonUsableExclusion) {
|
||||
delete playlist.excludeUntil;
|
||||
delete playlist.lastExcludeReason_;
|
||||
this.logger_(`enabling playlist ${playlist.id} because key ID ${key} is ${USABLE}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// If for whatever reason every playlist has a non usable key status. Lets try re-including the SD renditions as a failsafe.
|
||||
if (nonUsableKeyStatusCount >= this.mainPlaylistLoader_.main.playlists.length) {
|
||||
this.mainPlaylistLoader_.main.playlists.forEach((playlist) => {
|
||||
const isNonHD = playlist && playlist.attributes && playlist.attributes.RESOLUTION && playlist.attributes.RESOLUTION.height < 720;
|
||||
const excludedForNonUsableKey = playlist.excludeUntil === Infinity && playlist.lastExcludeReason_ === NON_USABLE;
|
||||
|
||||
if (isNonHD && excludedForNonUsableKey) {
|
||||
// Only delete the excludeUntil so we don't try and re-exclude these playlists.
|
||||
delete playlist.excludeUntil;
|
||||
videojs.log.warn(`enabling non-HD playlist ${playlist.id} because all playlists were excluded due to ${NON_USABLE} key IDs`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a keystatus to the keystatus map, tries to convert to string if necessary.
|
||||
*
|
||||
* @param {any} keyId the keyId to add a status for
|
||||
* @param {string} status the status of the keyId
|
||||
*/
|
||||
addKeyStatus_(keyId, status) {
|
||||
const isString = typeof keyId === 'string';
|
||||
const keyIdHexString = isString ? keyId : bufferToHexString(keyId);
|
||||
const formattedKeyIdString = keyIdHexString.slice(0, 32).toLowerCase();
|
||||
|
||||
this.logger_(`KeyStatus '${status}' with key ID ${formattedKeyIdString} added to the keyStatusMap`);
|
||||
this.keyStatusMap_.set(formattedKeyIdString, status);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function for adding key status to the keyStatusMap and filtering usable encrypted playlists.
|
||||
*
|
||||
* @param {any} keyId the keyId from the keystatuschange event
|
||||
* @param {string} status the key status string
|
||||
*/
|
||||
updatePlaylistByKeyStatus(keyId, status) {
|
||||
this.addKeyStatus_(keyId, status);
|
||||
if (!this.waitingForFastQualityPlaylistReceived_) {
|
||||
this.excludeNonUsableThenChangePlaylist_();
|
||||
}
|
||||
// Listen to loadedplaylist with a single listener and check for new contentProtection elements when a playlist is updated.
|
||||
this.mainPlaylistLoader_.off('loadedplaylist', this.excludeNonUsableThenChangePlaylist_.bind(this));
|
||||
this.mainPlaylistLoader_.on('loadedplaylist', this.excludeNonUsableThenChangePlaylist_.bind(this));
|
||||
}
|
||||
|
||||
excludeNonUsableThenChangePlaylist_() {
|
||||
this.excludeNonUsablePlaylistsByKeyId_();
|
||||
this.fastQualityChange_();
|
||||
}
|
||||
}
|
||||
|
|
288
node_modules/@videojs/http-streaming/src/playlist-loader.js
generated
vendored
288
node_modules/@videojs/http-streaming/src/playlist-loader.js
generated
vendored
|
@ -14,7 +14,9 @@ import {
|
|||
addPropertiesToMain,
|
||||
mainForMedia,
|
||||
setupMediaPlaylist,
|
||||
forEachMediaGroup
|
||||
forEachMediaGroup,
|
||||
createPlaylistID,
|
||||
groupID
|
||||
} from './manifest';
|
||||
import {getKnownPartCount} from './playlist.js';
|
||||
import {merge} from './util/vjs-compat';
|
||||
|
@ -929,4 +931,288 @@ export default class PlaylistLoader extends EventTarget {
|
|||
this.trigger('loadedmetadata');
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates or deletes a preexisting pathway clone.
|
||||
* Ensures that all playlists related to the old pathway clone are
|
||||
* either updated or deleted.
|
||||
*
|
||||
* @param {Object} clone On update, the pathway clone object for the newly updated pathway clone.
|
||||
* On delete, the old pathway clone object to be deleted.
|
||||
* @param {boolean} isUpdate True if the pathway is to be updated,
|
||||
* false if it is meant to be deleted.
|
||||
*/
|
||||
updateOrDeleteClone(clone, isUpdate) {
|
||||
const main = this.main;
|
||||
const pathway = clone.ID;
|
||||
|
||||
let i = main.playlists.length;
|
||||
|
||||
// Iterate backwards through the playlist so we can remove playlists if necessary.
|
||||
while (i--) {
|
||||
const p = main.playlists[i];
|
||||
|
||||
if (p.attributes['PATHWAY-ID'] === pathway) {
|
||||
const oldPlaylistUri = p.resolvedUri;
|
||||
const oldPlaylistId = p.id;
|
||||
|
||||
// update the indexed playlist and add new playlists by ID and URI
|
||||
if (isUpdate) {
|
||||
const newPlaylistUri = this.createCloneURI_(p.resolvedUri, clone);
|
||||
const newPlaylistId = createPlaylistID(pathway, newPlaylistUri);
|
||||
const attributes = this.createCloneAttributes_(pathway, p.attributes);
|
||||
const updatedPlaylist = this.createClonePlaylist_(p, newPlaylistId, clone, attributes);
|
||||
|
||||
main.playlists[i] = updatedPlaylist;
|
||||
main.playlists[newPlaylistId] = updatedPlaylist;
|
||||
main.playlists[newPlaylistUri] = updatedPlaylist;
|
||||
} else {
|
||||
// Remove the indexed playlist.
|
||||
main.playlists.splice(i, 1);
|
||||
}
|
||||
|
||||
// Remove playlists by the old ID and URI.
|
||||
delete main.playlists[oldPlaylistId];
|
||||
delete main.playlists[oldPlaylistUri];
|
||||
}
|
||||
}
|
||||
|
||||
this.updateOrDeleteCloneMedia(clone, isUpdate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates or deletes media data based on the pathway clone object.
|
||||
* Due to the complexity of the media groups and playlists, in all cases
|
||||
* we remove all of the old media groups and playlists.
|
||||
* On updates, we then create new media groups and playlists based on the
|
||||
* new pathway clone object.
|
||||
*
|
||||
* @param {Object} clone The pathway clone object for the newly updated pathway clone.
|
||||
* @param {boolean} isUpdate True if the pathway is to be updated,
|
||||
* false if it is meant to be deleted.
|
||||
*/
|
||||
updateOrDeleteCloneMedia(clone, isUpdate) {
|
||||
const main = this.main;
|
||||
const id = clone.ID;
|
||||
|
||||
['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach((mediaType) => {
|
||||
if (!main.mediaGroups[mediaType] || !main.mediaGroups[mediaType][id]) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const groupKey in main.mediaGroups[mediaType]) {
|
||||
// Remove all media playlists for the media group for this pathway clone.
|
||||
if (groupKey === id) {
|
||||
for (const labelKey in main.mediaGroups[mediaType][groupKey]) {
|
||||
const oldMedia = main.mediaGroups[mediaType][groupKey][labelKey];
|
||||
|
||||
oldMedia.playlists.forEach((p, i) => {
|
||||
const oldMediaPlaylist = main.playlists[p.id];
|
||||
const oldPlaylistId = oldMediaPlaylist.id;
|
||||
const oldPlaylistUri = oldMediaPlaylist.resolvedUri;
|
||||
|
||||
delete main.playlists[oldPlaylistId];
|
||||
delete main.playlists[oldPlaylistUri];
|
||||
});
|
||||
}
|
||||
|
||||
// Delete the old media group.
|
||||
delete main.mediaGroups[mediaType][groupKey];
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Create the new media groups and playlists if there is an update.
|
||||
if (isUpdate) {
|
||||
this.createClonedMediaGroups_(clone);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a pathway clone object, clones all necessary playlists.
|
||||
*
|
||||
* @param {Object} clone The pathway clone object.
|
||||
* @param {Object} basePlaylist The original playlist to clone from.
|
||||
*/
|
||||
addClonePathway(clone, basePlaylist = {}) {
|
||||
const main = this.main;
|
||||
const index = main.playlists.length;
|
||||
const uri = this.createCloneURI_(basePlaylist.resolvedUri, clone);
|
||||
const playlistId = createPlaylistID(clone.ID, uri);
|
||||
const attributes = this.createCloneAttributes_(clone.ID, basePlaylist.attributes);
|
||||
|
||||
const playlist = this.createClonePlaylist_(basePlaylist, playlistId, clone, attributes);
|
||||
|
||||
main.playlists[index] = playlist;
|
||||
|
||||
// add playlist by ID and URI
|
||||
main.playlists[playlistId] = playlist;
|
||||
main.playlists[uri] = playlist;
|
||||
|
||||
this.createClonedMediaGroups_(clone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a pathway clone object we create clones of all media.
|
||||
* In this function, all necessary information and updated playlists
|
||||
* are added to the `mediaGroup` object.
|
||||
* Playlists are also added to the `playlists` array so the media groups
|
||||
* will be properly linked.
|
||||
*
|
||||
* @param {Object} clone The pathway clone object.
|
||||
*/
|
||||
createClonedMediaGroups_(clone) {
|
||||
const id = clone.ID;
|
||||
const baseID = clone['BASE-ID'];
|
||||
const main = this.main;
|
||||
|
||||
['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach((mediaType) => {
|
||||
// If the media type doesn't exist, or there is already a clone, skip
|
||||
// to the next media type.
|
||||
if (!main.mediaGroups[mediaType] || main.mediaGroups[mediaType][id]) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const groupKey in main.mediaGroups[mediaType]) {
|
||||
if (groupKey === baseID) {
|
||||
// Create the group.
|
||||
main.mediaGroups[mediaType][id] = {};
|
||||
} else {
|
||||
// There is no need to iterate over label keys in this case.
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const labelKey in main.mediaGroups[mediaType][groupKey]) {
|
||||
const oldMedia = main.mediaGroups[mediaType][groupKey][labelKey];
|
||||
|
||||
main.mediaGroups[mediaType][id][labelKey] = Object.assign({}, oldMedia);
|
||||
const newMedia = main.mediaGroups[mediaType][id][labelKey];
|
||||
|
||||
// update URIs on the media
|
||||
const newUri = this.createCloneURI_(oldMedia.resolvedUri, clone);
|
||||
|
||||
newMedia.resolvedUri = newUri;
|
||||
newMedia.uri = newUri;
|
||||
|
||||
// Reset playlists in the new media group.
|
||||
newMedia.playlists = [];
|
||||
|
||||
// Create new playlists in the newly cloned media group.
|
||||
oldMedia.playlists.forEach((p, i) => {
|
||||
const oldMediaPlaylist = main.playlists[p.id];
|
||||
const group = groupID(mediaType, id, labelKey);
|
||||
const newPlaylistID = createPlaylistID(id, group);
|
||||
|
||||
// Check to see if it already exists
|
||||
if (oldMediaPlaylist && !main.playlists[newPlaylistID]) {
|
||||
const newMediaPlaylist = this.createClonePlaylist_(oldMediaPlaylist, newPlaylistID, clone);
|
||||
|
||||
const newPlaylistUri = newMediaPlaylist.resolvedUri;
|
||||
|
||||
main.playlists[newPlaylistID] = newMediaPlaylist;
|
||||
main.playlists[newPlaylistUri] = newMediaPlaylist;
|
||||
}
|
||||
|
||||
newMedia.playlists[i] = this.createClonePlaylist_(p, newPlaylistID, clone);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Using the original playlist to be cloned, and the pathway clone object
|
||||
* information, we create a new playlist.
|
||||
*
|
||||
* @param {Object} basePlaylist The original playlist to be cloned from.
|
||||
* @param {string} id The desired id of the newly cloned playlist.
|
||||
* @param {Object} clone The pathway clone object.
|
||||
* @param {Object} attributes An optional object to populate the `attributes` property in the playlist.
|
||||
*
|
||||
* @return {Object} The combined cloned playlist.
|
||||
*/
|
||||
createClonePlaylist_(basePlaylist, id, clone, attributes) {
|
||||
const uri = this.createCloneURI_(basePlaylist.resolvedUri, clone);
|
||||
const newProps = {
|
||||
resolvedUri: uri,
|
||||
uri,
|
||||
id
|
||||
};
|
||||
|
||||
// Remove all segments from previous playlist in the clone.
|
||||
if (basePlaylist.segments) {
|
||||
newProps.segments = [];
|
||||
}
|
||||
|
||||
if (attributes) {
|
||||
newProps.attributes = attributes;
|
||||
}
|
||||
|
||||
return merge(basePlaylist, newProps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an updated URI for a cloned pathway based on the original
|
||||
* pathway's URI and the paramaters from the pathway clone object in the
|
||||
* content steering server response.
|
||||
*
|
||||
* @param {string} baseUri URI to be updated in the cloned pathway.
|
||||
* @param {Object} clone The pathway clone object.
|
||||
*
|
||||
* @return {string} The updated URI for the cloned pathway.
|
||||
*/
|
||||
createCloneURI_(baseURI, clone) {
|
||||
const uri = new URL(baseURI);
|
||||
|
||||
uri.hostname = clone['URI-REPLACEMENT'].HOST;
|
||||
|
||||
const params = clone['URI-REPLACEMENT'].PARAMS;
|
||||
|
||||
// Add params to the cloned URL.
|
||||
for (const key of Object.keys(params)) {
|
||||
uri.searchParams.set(key, params[key]);
|
||||
}
|
||||
|
||||
return uri.href;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to create the attributes needed for the new clone.
|
||||
* This mainly adds the necessary media attributes.
|
||||
*
|
||||
* @param {string} id The pathway clone object ID.
|
||||
* @param {Object} oldAttributes The old attributes to compare to.
|
||||
* @return {Object} The new attributes to add to the playlist.
|
||||
*/
|
||||
createCloneAttributes_(id, oldAttributes) {
|
||||
const attributes = { ['PATHWAY-ID']: id };
|
||||
|
||||
['AUDIO', 'SUBTITLES', 'CLOSED-CAPTIONS'].forEach((mediaType) => {
|
||||
if (oldAttributes[mediaType]) {
|
||||
attributes[mediaType] = id;
|
||||
}
|
||||
});
|
||||
|
||||
return attributes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key ID set from a playlist
|
||||
*
|
||||
* @param {playlist} playlist to fetch the key ID set from.
|
||||
* @return a Set of 32 digit hex strings that represent the unique keyIds for that playlist.
|
||||
*/
|
||||
getKeyIdSet(playlist) {
|
||||
if (playlist.contentProtection) {
|
||||
const keyIds = new Set();
|
||||
|
||||
for (const keysystem in playlist.contentProtection) {
|
||||
const keyId = playlist.contentProtection[keysystem].attributes.keyId;
|
||||
|
||||
if (keyId) {
|
||||
keyIds.add(keyId.toLowerCase());
|
||||
}
|
||||
}
|
||||
return keyIds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
23
node_modules/@videojs/http-streaming/src/playlist.js
generated
vendored
23
node_modules/@videojs/http-streaming/src/playlist.js
generated
vendored
|
@ -511,6 +511,29 @@ export const getMediaInfoForTime = function({
|
|||
|
||||
time -= partAndSegment.duration;
|
||||
|
||||
const canUseFudgeFactor = partAndSegment.duration > TIME_FUDGE_FACTOR;
|
||||
const isExactlyAtTheEnd = time === 0;
|
||||
const isExtremelyCloseToTheEnd = canUseFudgeFactor && (time + TIME_FUDGE_FACTOR >= 0);
|
||||
|
||||
if (isExactlyAtTheEnd || isExtremelyCloseToTheEnd) {
|
||||
// 1) We are exactly at the end of the current segment.
|
||||
// 2) We are extremely close to the end of the current segment (The difference is less than 1 / 30).
|
||||
// We may encounter this situation when
|
||||
// we don't have exact match between segment duration info in the manifest and the actual duration of the segment
|
||||
// For example:
|
||||
// We appended 3 segments 10 seconds each, meaning we should have 30 sec buffered,
|
||||
// but we the actual buffered is 29.99999
|
||||
//
|
||||
// In both cases:
|
||||
// if we passed current time -> it means that we already played current segment
|
||||
// if we passed buffered.end -> it means that this segment is already loaded and buffered
|
||||
|
||||
// we should select the next segment if we have one:
|
||||
if (i !== partsAndSegments.length - 1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (exactManifestTimings) {
|
||||
if (time > 0) {
|
||||
continue;
|
||||
|
|
68
node_modules/@videojs/http-streaming/src/segment-loader.js
generated
vendored
68
node_modules/@videojs/http-streaming/src/segment-loader.js
generated
vendored
|
@ -179,10 +179,6 @@ export const segmentInfoString = (segmentInfo) => {
|
|||
|
||||
const timingInfoPropertyForMedia = (mediaType) => `${mediaType}TimingInfo`;
|
||||
|
||||
const getBufferedEndOrFallback = (buffered, fallback) => buffered.length ?
|
||||
buffered.end(buffered.length - 1) :
|
||||
fallback;
|
||||
|
||||
/**
|
||||
* Returns the timestamp offset to use for the segment.
|
||||
*
|
||||
|
@ -194,8 +190,6 @@ const getBufferedEndOrFallback = (buffered, fallback) => buffered.length ?
|
|||
* The estimated segment start
|
||||
* @param {TimeRange[]} buffered
|
||||
* The loader's buffer
|
||||
* @param {boolean} calculateTimestampOffsetForEachSegment
|
||||
* Feature flag to always calculate timestampOffset
|
||||
* @param {boolean} overrideCheck
|
||||
* If true, no checks are made to see if the timestamp offset value should be set,
|
||||
* but sets it directly to a value.
|
||||
|
@ -209,13 +203,8 @@ export const timestampOffsetForSegment = ({
|
|||
currentTimeline,
|
||||
startOfSegment,
|
||||
buffered,
|
||||
calculateTimestampOffsetForEachSegment,
|
||||
overrideCheck
|
||||
}) => {
|
||||
if (calculateTimestampOffsetForEachSegment) {
|
||||
return getBufferedEndOrFallback(buffered, startOfSegment);
|
||||
}
|
||||
|
||||
// Check to see if we are crossing a discontinuity to see if we need to set the
|
||||
// timestamp offset on the transmuxer and source buffer.
|
||||
//
|
||||
|
@ -259,7 +248,7 @@ export const timestampOffsetForSegment = ({
|
|||
// should often be correct, it's better to rely on the buffered end, as the new
|
||||
// content post discontinuity should line up with the buffered end as if it were
|
||||
// time 0 for the new content.
|
||||
return getBufferedEndOrFallback(buffered, startOfSegment);
|
||||
return buffered.length ? buffered.end(buffered.length - 1) : startOfSegment;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -570,7 +559,6 @@ export default class SegmentLoader extends videojs.EventTarget {
|
|||
this.shouldSaveSegmentTimingInfo_ = true;
|
||||
this.parse708captions_ = settings.parse708captions;
|
||||
this.useDtsForTimestampOffset_ = settings.useDtsForTimestampOffset;
|
||||
this.calculateTimestampOffsetForEachSegment_ = settings.calculateTimestampOffsetForEachSegment;
|
||||
this.captionServices_ = settings.captionServices;
|
||||
this.exactManifestTimings = settings.exactManifestTimings;
|
||||
this.addMetadataToTextTrack = settings.addMetadataToTextTrack;
|
||||
|
@ -579,6 +567,7 @@ export default class SegmentLoader extends videojs.EventTarget {
|
|||
this.checkBufferTimeout_ = null;
|
||||
this.error_ = void 0;
|
||||
this.currentTimeline_ = -1;
|
||||
this.shouldForceTimestampOffsetAfterResync_ = false;
|
||||
this.pendingSegment_ = null;
|
||||
this.xhrOptions_ = null;
|
||||
this.pendingSegments_ = [];
|
||||
|
@ -641,8 +630,6 @@ export default class SegmentLoader extends videojs.EventTarget {
|
|||
|
||||
// ...for determining the fetch location
|
||||
this.fetchAtBuffer_ = false;
|
||||
// For comparing with currentTime when overwriting segments on fastQualityChange_ changes. Use -1 as the inactive flag.
|
||||
this.replaceSegmentsUntil_ = -1;
|
||||
|
||||
this.logger_ = logger(`SegmentLoader[${this.loaderType_}]`);
|
||||
|
||||
|
@ -1046,6 +1033,7 @@ export default class SegmentLoader extends videojs.EventTarget {
|
|||
}
|
||||
|
||||
this.logger_(`playlist update [${oldId} => ${newPlaylist.id || newPlaylist.uri}]`);
|
||||
this.syncController_.updateMediaSequenceMap(newPlaylist, this.currentTime_(), this.loaderType_);
|
||||
|
||||
// in VOD, this is always a rendition switch (or we updated our syncInfo above)
|
||||
// in LIVE, we always want to update with new playlists (including refreshes)
|
||||
|
@ -1170,18 +1158,6 @@ export default class SegmentLoader extends videojs.EventTarget {
|
|||
return this.checkBufferTimeout_ === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the segment loader ended and init properties.
|
||||
*/
|
||||
resetLoaderProperties() {
|
||||
this.ended_ = false;
|
||||
this.activeInitSegmentId_ = null;
|
||||
this.appendInitSegment_ = {
|
||||
audio: true,
|
||||
video: true
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all the buffered data and reset the SegmentLoader
|
||||
*
|
||||
|
@ -1189,7 +1165,12 @@ export default class SegmentLoader extends videojs.EventTarget {
|
|||
* operation is complete
|
||||
*/
|
||||
resetEverything(done) {
|
||||
this.resetLoaderProperties();
|
||||
this.ended_ = false;
|
||||
this.activeInitSegmentId_ = null;
|
||||
this.appendInitSegment_ = {
|
||||
audio: true,
|
||||
video: true
|
||||
};
|
||||
this.resetLoader();
|
||||
|
||||
// remove from 0, the earliest point, to Infinity, to signify removal of everything.
|
||||
|
@ -1234,6 +1215,7 @@ export default class SegmentLoader extends videojs.EventTarget {
|
|||
this.partIndex = null;
|
||||
this.syncPoint_ = null;
|
||||
this.isPendingTimestampOffset_ = false;
|
||||
this.shouldForceTimestampOffsetAfterResync_ = true;
|
||||
this.callQueue_ = [];
|
||||
this.loadQueue_ = [];
|
||||
this.metadataQueue_.id3 = [];
|
||||
|
@ -1441,7 +1423,8 @@ export default class SegmentLoader extends videojs.EventTarget {
|
|||
this.playlist_,
|
||||
this.duration_(),
|
||||
this.currentTimeline_,
|
||||
this.currentTime_()
|
||||
this.currentTime_(),
|
||||
this.loaderType_
|
||||
);
|
||||
|
||||
const next = {
|
||||
|
@ -1454,6 +1437,7 @@ export default class SegmentLoader extends videojs.EventTarget {
|
|||
|
||||
if (next.isSyncRequest) {
|
||||
next.mediaIndex = getSyncSegmentCandidate(this.currentTimeline_, segments, bufferedEnd);
|
||||
this.logger_(`choose next request. Can not find sync point. Fallback to media Index: ${next.mediaIndex}`);
|
||||
} else if (this.mediaIndex !== null) {
|
||||
const segment = segments[this.mediaIndex];
|
||||
const partIndex = typeof this.partIndex === 'number' ? this.partIndex : -1;
|
||||
|
@ -1482,6 +1466,8 @@ export default class SegmentLoader extends videojs.EventTarget {
|
|||
next.mediaIndex = segmentIndex;
|
||||
next.startOfSegment = startTime;
|
||||
next.partIndex = partIndex;
|
||||
|
||||
this.logger_(`choose next request. Playlist switched and we have a sync point. Media Index: ${next.mediaIndex} `);
|
||||
}
|
||||
|
||||
const nextSegment = segments[next.mediaIndex];
|
||||
|
@ -1540,6 +1526,12 @@ export default class SegmentLoader extends videojs.EventTarget {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (this.shouldForceTimestampOffsetAfterResync_) {
|
||||
this.shouldForceTimestampOffsetAfterResync_ = false;
|
||||
next.forceTimestampOffset = true;
|
||||
this.logger_('choose next request. Force timestamp offset after loader resync');
|
||||
}
|
||||
|
||||
return this.generateSegmentInfo_(next);
|
||||
}
|
||||
|
||||
|
@ -1598,7 +1590,6 @@ export default class SegmentLoader extends videojs.EventTarget {
|
|||
currentTimeline: this.currentTimeline_,
|
||||
startOfSegment,
|
||||
buffered: this.buffered_(),
|
||||
calculateTimestampOffsetForEachSegment: this.calculateTimestampOffsetForEachSegment_,
|
||||
overrideCheck
|
||||
});
|
||||
|
||||
|
@ -3072,10 +3063,7 @@ export default class SegmentLoader extends videojs.EventTarget {
|
|||
this.logger_(`Appended ${segmentInfoString(segmentInfo)}`);
|
||||
|
||||
this.addSegmentMetadataCue_(segmentInfo);
|
||||
if (this.currentTime_() >= this.replaceSegmentsUntil_) {
|
||||
this.replaceSegmentsUntil_ = -1;
|
||||
this.fetchAtBuffer_ = true;
|
||||
}
|
||||
this.fetchAtBuffer_ = true;
|
||||
if (this.currentTimeline_ !== segmentInfo.timeline) {
|
||||
this.timelineChangeController_.lastTimelineChange({
|
||||
type: this.loaderType_,
|
||||
|
@ -3229,16 +3217,4 @@ export default class SegmentLoader extends videojs.EventTarget {
|
|||
|
||||
this.segmentMetadataTrack_.addCue(cue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for defining the private replaceSegmentsUntil_ property, which
|
||||
* determines when we can return fetchAtBuffer to true if overwriting the buffer.
|
||||
*
|
||||
* @param {number} bufferedEnd the end of the buffered range to replace segments
|
||||
* until currentTime reaches this time.
|
||||
*/
|
||||
set replaceSegmentsUntil(bufferedEnd) {
|
||||
this.logger_(`Replacing currently buffered segments until ${bufferedEnd}`);
|
||||
this.replaceSegmentsUntil_ = bufferedEnd;
|
||||
}
|
||||
}
|
||||
|
|
16
node_modules/@videojs/http-streaming/src/source-updater.js
generated
vendored
16
node_modules/@videojs/http-streaming/src/source-updater.js
generated
vendored
|
@ -9,7 +9,7 @@ import {getMimeForCodec} from '@videojs/vhs-utils/es/codecs.js';
|
|||
import window from 'global/window';
|
||||
import toTitleCase from './util/to-title-case.js';
|
||||
import { QUOTA_EXCEEDED_ERR } from './error-codes';
|
||||
import {createTimeRanges, prettyBuffered} from './util/vjs-compat';
|
||||
import {createTimeRanges} from './util/vjs-compat';
|
||||
|
||||
const bufferTypes = [
|
||||
'video',
|
||||
|
@ -281,8 +281,13 @@ const actions = {
|
|||
|
||||
sourceUpdater.logger_(`changing ${type}Buffer codec from ${sourceUpdater.codecs[type]} to ${codec}`);
|
||||
|
||||
sourceBuffer.changeType(mime);
|
||||
sourceUpdater.codecs[type] = codec;
|
||||
// check if change to the provided type is supported
|
||||
try {
|
||||
sourceBuffer.changeType(mime);
|
||||
sourceUpdater.codecs[type] = codec;
|
||||
} catch (e) {
|
||||
videojs.log.warn(`Failed to changeType on ${type}Buffer`, e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -297,11 +302,6 @@ const pushQueue = ({type, sourceUpdater, action, doneFn, name}) => {
|
|||
};
|
||||
|
||||
const onUpdateend = (type, sourceUpdater) => (e) => {
|
||||
const buffered = sourceUpdater[`${type}Buffered`]();
|
||||
const bufferedAsString = prettyBuffered(buffered);
|
||||
|
||||
sourceUpdater.logger_(`${type} source buffer update end. Buffered: \n`, bufferedAsString);
|
||||
|
||||
// Although there should, in theory, be a pending action for any updateend receieved,
|
||||
// there are some actions that may trigger updateend events without set definitions in
|
||||
// the w3c spec. For instance, setting the duration on the media source may trigger
|
||||
|
|
200
node_modules/@videojs/http-streaming/src/sync-controller.js
generated
vendored
200
node_modules/@videojs/http-streaming/src/sync-controller.js
generated
vendored
|
@ -31,6 +31,86 @@ export const syncPointStrategies = [
|
|||
return null;
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'MediaSequence',
|
||||
/**
|
||||
* run media sequence strategy
|
||||
*
|
||||
* @param {SyncController} syncController
|
||||
* @param {Object} playlist
|
||||
* @param {number} duration
|
||||
* @param {number} currentTimeline
|
||||
* @param {number} currentTime
|
||||
* @param {string} type
|
||||
*/
|
||||
run: (syncController, playlist, duration, currentTimeline, currentTime, type) => {
|
||||
if (!type) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const mediaSequenceMap = syncController.getMediaSequenceMap(type);
|
||||
|
||||
if (!mediaSequenceMap || mediaSequenceMap.size === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (playlist.mediaSequence === undefined || !Array.isArray(playlist.segments) || !playlist.segments.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let currentMediaSequence = playlist.mediaSequence;
|
||||
let segmentIndex = 0;
|
||||
|
||||
for (const segment of playlist.segments) {
|
||||
const range = mediaSequenceMap.get(currentMediaSequence);
|
||||
|
||||
if (!range) {
|
||||
// unexpected case
|
||||
// we expect this playlist to be the same playlist in the map
|
||||
// just break from the loop and move forward to the next strategy
|
||||
break;
|
||||
}
|
||||
|
||||
if (currentTime >= range.start && currentTime < range.end) {
|
||||
// we found segment
|
||||
|
||||
if (Array.isArray(segment.parts) && segment.parts.length) {
|
||||
let currentPartStart = range.start;
|
||||
let partIndex = 0;
|
||||
|
||||
for (const part of segment.parts) {
|
||||
const start = currentPartStart;
|
||||
const end = start + part.duration;
|
||||
|
||||
if (currentTime >= start && currentTime < end) {
|
||||
return {
|
||||
time: range.start,
|
||||
segmentIndex,
|
||||
partIndex
|
||||
};
|
||||
}
|
||||
|
||||
partIndex++;
|
||||
currentPartStart = end;
|
||||
}
|
||||
}
|
||||
|
||||
// no parts found, return sync point for segment
|
||||
return {
|
||||
time: range.start,
|
||||
segmentIndex,
|
||||
partIndex: null
|
||||
};
|
||||
}
|
||||
|
||||
segmentIndex++;
|
||||
currentMediaSequence++;
|
||||
}
|
||||
|
||||
// we didn't find any segments for provided current time
|
||||
return null;
|
||||
}
|
||||
},
|
||||
// Stategy "ProgramDateTime": We have a program-date-time tag in this playlist
|
||||
{
|
||||
name: 'ProgramDateTime',
|
||||
|
@ -193,9 +273,79 @@ export default class SyncController extends videojs.EventTarget {
|
|||
this.discontinuities = [];
|
||||
this.timelineToDatetimeMappings = {};
|
||||
|
||||
/**
|
||||
* @type {Map<string, Map<number, { start: number, end: number }>>}
|
||||
* @private
|
||||
*/
|
||||
this.mediaSequenceStorage_ = new Map();
|
||||
|
||||
this.logger_ = logger('SyncController');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get media sequence map by type
|
||||
*
|
||||
* @param {string} type - segment loader type
|
||||
* @return {Map<number, { start: number, end: number }> | undefined}
|
||||
*/
|
||||
getMediaSequenceMap(type) {
|
||||
return this.mediaSequenceStorage_.get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Media Sequence Map -> <MediaSequence, Range>
|
||||
*
|
||||
* @param {Object} playlist - parsed playlist
|
||||
* @param {number} currentTime - current player's time
|
||||
* @param {string} type - segment loader type
|
||||
* @return {void}
|
||||
*/
|
||||
updateMediaSequenceMap(playlist, currentTime, type) {
|
||||
// we should not process this playlist if it does not have mediaSequence or segments
|
||||
if (playlist.mediaSequence === undefined || !Array.isArray(playlist.segments) || !playlist.segments.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentMap = this.getMediaSequenceMap(type);
|
||||
const result = new Map();
|
||||
|
||||
let currentMediaSequence = playlist.mediaSequence;
|
||||
let currentBaseTime;
|
||||
|
||||
if (!currentMap) {
|
||||
// first playlist setup:
|
||||
currentBaseTime = 0;
|
||||
} else if (currentMap.has(playlist.mediaSequence)) {
|
||||
// further playlists setup:
|
||||
currentBaseTime = currentMap.get(playlist.mediaSequence).start;
|
||||
} else {
|
||||
// it seems like we have a gap between playlists, use current time as a fallback:
|
||||
this.logger_(`MediaSequence sync for ${type} segment loader - received a gap between playlists.
|
||||
Fallback base time to: ${currentTime}.
|
||||
Received media sequence: ${currentMediaSequence}.
|
||||
Current map: `, currentMap);
|
||||
currentBaseTime = currentTime;
|
||||
}
|
||||
|
||||
this.logger_(`MediaSequence sync for ${type} segment loader.
|
||||
Received media sequence: ${currentMediaSequence}.
|
||||
base time is ${currentBaseTime}
|
||||
Current map: `, currentMap);
|
||||
|
||||
playlist.segments.forEach((segment) => {
|
||||
const start = currentBaseTime;
|
||||
const end = start + segment.duration;
|
||||
const range = { start, end };
|
||||
|
||||
result.set(currentMediaSequence, range);
|
||||
|
||||
currentMediaSequence++;
|
||||
currentBaseTime = end;
|
||||
});
|
||||
|
||||
this.mediaSequenceStorage_.set(type, result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a sync-point for the playlist specified
|
||||
*
|
||||
|
@ -208,15 +358,27 @@ export default class SyncController extends videojs.EventTarget {
|
|||
* Duration of the MediaSource (Infinite if playing a live source)
|
||||
* @param {number} currentTimeline
|
||||
* The last timeline from which a segment was loaded
|
||||
* @param {number} currentTime
|
||||
* Current player's time
|
||||
* @param {string} type
|
||||
* Segment loader type
|
||||
* @return {Object}
|
||||
* A sync-point object
|
||||
*/
|
||||
getSyncPoint(playlist, duration, currentTimeline, currentTime) {
|
||||
getSyncPoint(playlist, duration, currentTimeline, currentTime, type) {
|
||||
// Always use VOD sync point for VOD
|
||||
if (duration !== Infinity) {
|
||||
const vodSyncPointStrategy = syncPointStrategies.find(({ name }) => name === 'VOD');
|
||||
|
||||
return vodSyncPointStrategy.run(this, playlist, duration);
|
||||
}
|
||||
|
||||
const syncPoints = this.runStrategies_(
|
||||
playlist,
|
||||
duration,
|
||||
currentTimeline,
|
||||
currentTime
|
||||
currentTime,
|
||||
type
|
||||
);
|
||||
|
||||
if (!syncPoints.length) {
|
||||
|
@ -226,6 +388,28 @@ export default class SyncController extends videojs.EventTarget {
|
|||
return null;
|
||||
}
|
||||
|
||||
// If we have exact match just return it instead of finding the nearest distance
|
||||
for (const syncPointInfo of syncPoints) {
|
||||
const { syncPoint, strategy } = syncPointInfo;
|
||||
const { segmentIndex, time } = syncPoint;
|
||||
|
||||
if (segmentIndex < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const selectedSegment = playlist.segments[segmentIndex];
|
||||
|
||||
const start = time;
|
||||
const end = start + selectedSegment.duration;
|
||||
|
||||
this.logger_(`Strategy: ${strategy}. Current time: ${currentTime}. selected segment: ${segmentIndex}. Time: [${start} -> ${end}]}`);
|
||||
|
||||
if (currentTime >= start && currentTime < end) {
|
||||
this.logger_('Found sync point with exact match: ', syncPoint);
|
||||
return syncPoint;
|
||||
}
|
||||
}
|
||||
|
||||
// Now find the sync-point that is closest to the currentTime because
|
||||
// that should result in the most accurate guess about which segment
|
||||
// to fetch
|
||||
|
@ -252,7 +436,8 @@ export default class SyncController extends videojs.EventTarget {
|
|||
playlist,
|
||||
duration,
|
||||
playlist.discontinuitySequence,
|
||||
0
|
||||
0,
|
||||
'main'
|
||||
);
|
||||
|
||||
// Without sync-points, there is not enough information to determine the expired time
|
||||
|
@ -290,10 +475,14 @@ export default class SyncController extends videojs.EventTarget {
|
|||
* Duration of the MediaSource (Infinity if playing a live source)
|
||||
* @param {number} currentTimeline
|
||||
* The last timeline from which a segment was loaded
|
||||
* @param {number} currentTime
|
||||
* Current player's time
|
||||
* @param {string} type
|
||||
* Segment loader type
|
||||
* @return {Array}
|
||||
* A list of sync-point objects
|
||||
*/
|
||||
runStrategies_(playlist, duration, currentTimeline, currentTime) {
|
||||
runStrategies_(playlist, duration, currentTimeline, currentTime, type) {
|
||||
const syncPoints = [];
|
||||
|
||||
// Try to find a sync-point in by utilizing various strategies...
|
||||
|
@ -304,7 +493,8 @@ export default class SyncController extends videojs.EventTarget {
|
|||
playlist,
|
||||
duration,
|
||||
currentTimeline,
|
||||
currentTime
|
||||
currentTime,
|
||||
type
|
||||
);
|
||||
|
||||
if (syncPoint) {
|
||||
|
|
6
node_modules/@videojs/http-streaming/src/util/string.js
generated
vendored
6
node_modules/@videojs/http-streaming/src/util/string.js
generated
vendored
|
@ -1,2 +1,8 @@
|
|||
export const uint8ToUtf8 = (uintArray) =>
|
||||
decodeURIComponent(escape(String.fromCharCode.apply(null, uintArray)));
|
||||
|
||||
export const bufferToHexString = (buffer) => {
|
||||
const uInt8Buffer = new Uint8Array(buffer);
|
||||
|
||||
return Array.from(uInt8Buffer).map((byte) => byte.toString(16).padStart(2, '0')).join('');
|
||||
};
|
||||
|
|
2
node_modules/@videojs/http-streaming/src/util/text-tracks.js
generated
vendored
2
node_modules/@videojs/http-streaming/src/util/text-tracks.js
generated
vendored
|
@ -234,7 +234,7 @@ export const addMetadata = ({
|
|||
// Map each cue group's endTime to the next group's startTime
|
||||
sortedStartTimes.forEach((startTime, idx) => {
|
||||
const cueGroup = cuesGroupedByStartTime[startTime];
|
||||
const finiteDuration = isFinite(videoDuration) ? videoDuration : 0;
|
||||
const finiteDuration = isFinite(videoDuration) ? videoDuration : startTime;
|
||||
const nextTime = Number(sortedStartTimes[idx + 1]) || finiteDuration;
|
||||
|
||||
// Map each cue's endTime the next group's startTime
|
||||
|
|
25
node_modules/@videojs/http-streaming/src/util/vjs-compat.js
generated
vendored
25
node_modules/@videojs/http-streaming/src/util/vjs-compat.js
generated
vendored
|
@ -24,28 +24,3 @@ export function createTimeRanges(...args) {
|
|||
|
||||
return fn.apply(context, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts any buffered time range to a descriptive string
|
||||
*
|
||||
* @param {TimeRanges} buffered - time ranges
|
||||
* @return {string} - descriptive string
|
||||
*/
|
||||
export function prettyBuffered(buffered) {
|
||||
let result = '';
|
||||
|
||||
for (let i = 0; i < buffered.length; i++) {
|
||||
const start = buffered.start(i);
|
||||
const end = buffered.end(i);
|
||||
|
||||
const duration = end - start;
|
||||
|
||||
if (result.length) {
|
||||
result += '\n';
|
||||
}
|
||||
|
||||
result += `[${duration}](${start} -> ${end})`;
|
||||
}
|
||||
|
||||
return result || 'empty';
|
||||
}
|
||||
|
|
53
node_modules/@videojs/http-streaming/src/videojs-http-streaming.js
generated
vendored
53
node_modules/@videojs/http-streaming/src/videojs-http-streaming.js
generated
vendored
|
@ -678,7 +678,15 @@ class VhsHandler extends Component {
|
|||
this.on(this.tech_, 'play', this.play);
|
||||
}
|
||||
|
||||
setOptions_() {
|
||||
/**
|
||||
* Set VHS options based on options from configuration, as well as partial
|
||||
* options to be passed at a later time.
|
||||
*
|
||||
* @param {Object} options A partial chunk of config options
|
||||
*/
|
||||
setOptions_(options = {}) {
|
||||
this.options_ = merge(this.options_, options);
|
||||
|
||||
// defaults
|
||||
this.options_.withCredentials = this.options_.withCredentials || false;
|
||||
this.options_.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions === false ? false : true;
|
||||
|
@ -690,7 +698,6 @@ class VhsHandler extends Component {
|
|||
this.options_.useForcedSubtitles = this.options_.useForcedSubtitles || false;
|
||||
this.options_.useNetworkInformationApi = this.options_.useNetworkInformationApi || false;
|
||||
this.options_.useDtsForTimestampOffset = this.options_.useDtsForTimestampOffset || false;
|
||||
this.options_.calculateTimestampOffsetForEachSegment = this.options_.calculateTimestampOffsetForEachSegment || false;
|
||||
this.options_.customTagParsers = this.options_.customTagParsers || [];
|
||||
this.options_.customTagMappers = this.options_.customTagMappers || [];
|
||||
this.options_.cacheEncryptionKeys = this.options_.cacheEncryptionKeys || false;
|
||||
|
@ -744,7 +751,6 @@ class VhsHandler extends Component {
|
|||
'useForcedSubtitles',
|
||||
'useNetworkInformationApi',
|
||||
'useDtsForTimestampOffset',
|
||||
'calculateTimestampOffsetForEachSegment',
|
||||
'exactManifestTimings',
|
||||
'leastPixelDiffSelector'
|
||||
].forEach((option) => {
|
||||
|
@ -756,6 +762,10 @@ class VhsHandler extends Component {
|
|||
this.limitRenditionByPlayerDimensions = this.options_.limitRenditionByPlayerDimensions;
|
||||
this.useDevicePixelRatio = this.options_.useDevicePixelRatio;
|
||||
}
|
||||
// alias for public method to set options
|
||||
setOptions(options = {}) {
|
||||
this.setOptions_(options);
|
||||
}
|
||||
/**
|
||||
* called when player.src gets called, handle a new source
|
||||
*
|
||||
|
@ -1116,42 +1126,7 @@ class VhsHandler extends Component {
|
|||
});
|
||||
|
||||
this.player_.tech_.on('keystatuschange', (e) => {
|
||||
if (e.status !== 'output-restricted') {
|
||||
return;
|
||||
}
|
||||
|
||||
const mainPlaylist = this.playlistController_.main();
|
||||
|
||||
if (!mainPlaylist || !mainPlaylist.playlists) {
|
||||
return;
|
||||
}
|
||||
|
||||
const excludedHDPlaylists = [];
|
||||
|
||||
// Assume all HD streams are unplayable and exclude them from ABR selection
|
||||
mainPlaylist.playlists.forEach(playlist => {
|
||||
if (playlist && playlist.attributes && playlist.attributes.RESOLUTION &&
|
||||
playlist.attributes.RESOLUTION.height >= 720) {
|
||||
if (!playlist.excludeUntil || playlist.excludeUntil < Infinity) {
|
||||
|
||||
playlist.excludeUntil = Infinity;
|
||||
excludedHDPlaylists.push(playlist);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (excludedHDPlaylists.length) {
|
||||
videojs.log.warn(
|
||||
'DRM keystatus changed to "output-restricted." Removing the following HD playlists ' +
|
||||
'that will most likely fail to play and clearing the buffer. ' +
|
||||
'This may be due to HDCP restrictions on the stream and the capabilities of the current device.',
|
||||
...excludedHDPlaylists
|
||||
);
|
||||
|
||||
// Clear the buffer before switching playlists, since it may already contain unplayable segments
|
||||
this.playlistController_.mainSegmentLoader_.resetEverything();
|
||||
this.playlistController_.fastQualityChange_();
|
||||
}
|
||||
this.playlistController_.updatePlaylistByKeyStatus(e.keyId, e.status);
|
||||
});
|
||||
|
||||
this.handleWaitingForKey_ = this.handleWaitingForKey_.bind(this);
|
||||
|
|
66
node_modules/@videojs/http-streaming/src/vtt-segment-loader.js
generated
vendored
66
node_modules/@videojs/http-streaming/src/vtt-segment-loader.js
generated
vendored
|
@ -477,13 +477,25 @@ export default class VTTSegmentLoader extends SegmentLoader {
|
|||
return;
|
||||
}
|
||||
|
||||
const timestampmap = segmentInfo.timestampmap;
|
||||
const diff = (timestampmap.MPEGTS / ONE_SECOND_IN_TS) - timestampmap.LOCAL + mappingObj.mapping;
|
||||
const { MPEGTS, LOCAL } = segmentInfo.timestampmap;
|
||||
|
||||
/**
|
||||
* From the spec:
|
||||
* The MPEGTS media timestamp MUST use a 90KHz timescale,
|
||||
* even when non-WebVTT Media Segments use a different timescale.
|
||||
*/
|
||||
const mpegTsInSeconds = MPEGTS / ONE_SECOND_IN_TS;
|
||||
|
||||
const diff = mpegTsInSeconds - LOCAL + mappingObj.mapping;
|
||||
|
||||
segmentInfo.cues.forEach((cue) => {
|
||||
// First convert cue time to TS time using the timestamp-map provided within the vtt
|
||||
cue.startTime += diff;
|
||||
cue.endTime += diff;
|
||||
const duration = cue.endTime - cue.startTime;
|
||||
const startTime = MPEGTS === 0 ?
|
||||
cue.startTime + diff :
|
||||
this.handleRollover_(cue.startTime + diff, mappingObj.time);
|
||||
|
||||
cue.startTime = Math.max(startTime, 0);
|
||||
cue.endTime = Math.max(startTime + duration, 0);
|
||||
});
|
||||
|
||||
if (!playlist.syncInfo) {
|
||||
|
@ -496,4 +508,48 @@ export default class VTTSegmentLoader extends SegmentLoader {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MPEG-TS PES timestamps are limited to 2^33.
|
||||
* Once they reach 2^33, they roll over to 0.
|
||||
* mux.js handles PES timestamp rollover for the following scenarios:
|
||||
* [forward rollover(right)] ->
|
||||
* PES timestamps monotonically increase, and once they reach 2^33, they roll over to 0
|
||||
* [backward rollover(left)] -->
|
||||
* we seek back to position before rollover.
|
||||
*
|
||||
* According to the HLS SPEC:
|
||||
* When synchronizing WebVTT with PES timestamps, clients SHOULD account
|
||||
* for cases where the 33-bit PES timestamps have wrapped and the WebVTT
|
||||
* cue times have not. When the PES timestamp wraps, the WebVTT Segment
|
||||
* SHOULD have a X-TIMESTAMP-MAP header that maps the current WebVTT
|
||||
* time to the new (low valued) PES timestamp.
|
||||
*
|
||||
* So we want to handle rollover here and align VTT Cue start/end time to the player's time.
|
||||
*/
|
||||
handleRollover_(value, reference) {
|
||||
if (reference === null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
let valueIn90khz = value * ONE_SECOND_IN_TS;
|
||||
const referenceIn90khz = reference * ONE_SECOND_IN_TS;
|
||||
|
||||
let offset;
|
||||
|
||||
if (referenceIn90khz < valueIn90khz) {
|
||||
// - 2^33
|
||||
offset = -8589934592;
|
||||
} else {
|
||||
// + 2^33
|
||||
offset = 8589934592;
|
||||
}
|
||||
|
||||
// distance(value - reference) > 2^32
|
||||
while (Math.abs(valueIn90khz - referenceIn90khz) > 4294967296) {
|
||||
valueIn90khz += offset;
|
||||
}
|
||||
|
||||
return valueIn90khz / ONE_SECOND_IN_TS;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue