shaka-player 4.3.0 → 4.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build-and-test.yaml +20 -0
- package/AUTHORS +2 -0
- package/CHANGELOG.md +44 -0
- package/CONTRIBUTORS +2 -0
- package/README.md +1 -1
- package/conditional/dummy_cast_proxy.js +120 -0
- package/dist/demo.compiled.debug.js +104 -110
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +104 -110
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +8 -6
- package/dist/locales.js +32 -32
- package/dist/receiver.compiled.debug.js +54 -60
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +54 -60
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +19 -2
- package/dist/shaka-player.compiled.debug.d.ts +19 -2
- package/dist/shaka-player.compiled.debug.externs.js +102 -80
- package/dist/shaka-player.compiled.debug.js +657 -652
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +102 -80
- package/dist/shaka-player.compiled.js +904 -901
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +19 -2
- package/dist/shaka-player.ui.debug.d.ts +19 -2
- package/dist/shaka-player.ui.debug.externs.js +102 -80
- package/dist/shaka-player.ui.debug.js +703 -698
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +102 -80
- package/dist/shaka-player.ui.js +1029 -1026
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/welcome.md +2 -2
- package/externs/shaka/mp4_parser.js +3 -0
- package/externs/shaka/player.js +2 -1
- package/lib/ads/ad_manager.js +2 -2
- package/lib/cea/cea708_window.js +2 -2
- package/lib/cea/mp4_cea_parser.js +37 -16
- package/lib/dash/segment_template.js +1 -1
- package/lib/hls/hls_parser.js +49 -23
- package/lib/media/content_workarounds.js +12 -0
- package/lib/media/media_source_capabilities.js +36 -0
- package/lib/media/media_source_engine.js +28 -7
- package/lib/media/segment_reference.js +3 -0
- package/lib/media/streaming_engine.js +33 -10
- package/lib/media/transmuxer.js +6 -4
- package/lib/player.js +10 -6
- package/lib/polyfill/abort_controller.js +143 -0
- package/lib/polyfill/media_capabilities.js +65 -11
- package/lib/polyfill/patchedmediakeys_apple.js +33 -28
- package/lib/text/ttml_text_parser.js +32 -7
- package/lib/text/ui_text_displayer.js +17 -0
- package/lib/util/mp4_box_parsers.js +24 -0
- package/lib/util/mp4_parser.js +35 -0
- package/lib/util/player_configuration.js +10 -3
- package/lib/util/stream_utils.js +35 -13
- package/package.json +4 -4
- package/shaka-player.uncompiled.js +1 -0
- package/test/ads/ad_manager_unit.js +3 -1
- package/test/dash/dash_parser_manifest_unit.js +1 -0
- package/test/hls/hls_parser_unit.js +132 -0
- package/test/media/drm_engine_unit.js +11 -1
- package/test/media/media_source_engine_integration.js +78 -2
- package/test/player_integration.js +10 -3
- package/test/polyfill/media_capabilities_unit.js +259 -0
- package/test/polyfill/patchedmediakeys_apple_unit.js +6 -0
- package/test/test/boot.js +29 -11
- package/test/test/util/manifest_parser_util.js +4 -3
- package/test/test/util/stream_generator.js +212 -8
- package/test/test/util/test_scheme.js +6 -5
- package/test/test/util/util.js +3 -2
- package/test/text/mp4_ttml_parser_unit.js +6 -4
- package/test/text/ttml_text_parser_unit.js +119 -86
- package/test/text/ui_text_displayer_unit.js +76 -0
- package/test/util/stream_utils_unit.js +18 -0
- package/test/util/xml_utils_unit.js +6 -1
- package/third_party/closure-uri/uri.js +1 -1
- package/ui/controls.js +49 -36
|
@@ -29,8 +29,8 @@ You can build Shaka on Linux, Windows, or Mac.
|
|
|
29
29
|
To get the sources and compile the library, you will need:
|
|
30
30
|
* {@link https://git-scm.com/downloads Git v1.9+}
|
|
31
31
|
* {@link https://www.python.org/downloads/ Python v2.7 or v3.5+}
|
|
32
|
-
* {@link https://java.com/en/download/ Java Runtime Environment
|
|
33
|
-
* {@link https://nodejs.org/en/download/ NodeJS
|
|
32
|
+
* {@link https://java.com/en/download/ Java Runtime Environment v14+}
|
|
33
|
+
* {@link https://nodejs.org/en/download/ NodeJS v14+}
|
|
34
34
|
* A local web server, such as {@link https://httpd.apache.org/ Apache}
|
|
35
35
|
* _NOTE: A local web server is required because browsers place restrictions
|
|
36
36
|
on applications from file:/// URLs._
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* @typedef {{
|
|
14
|
+
* name: string,
|
|
14
15
|
* parser: !shaka.util.Mp4Parser,
|
|
15
16
|
* partialOkay: boolean,
|
|
16
17
|
* start: number,
|
|
@@ -21,6 +22,8 @@
|
|
|
21
22
|
* has64BitSize: boolean
|
|
22
23
|
* }}
|
|
23
24
|
*
|
|
25
|
+
* @property {string} name
|
|
26
|
+
* The box name, a 4-character string (fourcc).
|
|
24
27
|
* @property {!shaka.util.Mp4Parser} parser
|
|
25
28
|
* The parser that parsed this box. The parser can be used to parse child
|
|
26
29
|
* boxes where the configuration of the current parser is needed to parsed
|
package/externs/shaka/player.js
CHANGED
|
@@ -131,7 +131,8 @@ shaka.extern.StateChange;
|
|
|
131
131
|
* @property {number} manifestTimeSeconds
|
|
132
132
|
* The amount of time it took to download and parse the manifest.
|
|
133
133
|
* @property {number} drmTimeSeconds
|
|
134
|
-
* The amount of time it took to download the first drm key
|
|
134
|
+
* The amount of time it took to download the first drm key, and load that key
|
|
135
|
+
* into the drm system.
|
|
135
136
|
* @property {number} playTime
|
|
136
137
|
* The total time spent in a playing state in seconds.
|
|
137
138
|
* @property {number} pauseTime
|
package/lib/ads/ad_manager.js
CHANGED
|
@@ -19,8 +19,8 @@ goog.require('shaka.util.IReleasable');
|
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* @event shaka.ads.AdManager.
|
|
23
|
-
* @description Fired when
|
|
22
|
+
* @event shaka.ads.AdManager.AdsLoadedEvent
|
|
23
|
+
* @description Fired when a sequence of ads has been loaded.
|
|
24
24
|
* @property {string} type
|
|
25
25
|
* 'ads-loaded'
|
|
26
26
|
* @property {number} loadTime
|
package/lib/cea/cea708_window.js
CHANGED
|
@@ -420,7 +420,7 @@ shaka.cea.Cea708Window.TextJustification = {
|
|
|
420
420
|
shaka.cea.Cea708Window.MAX_COLS = 42;
|
|
421
421
|
|
|
422
422
|
/**
|
|
423
|
-
* Maximum of
|
|
423
|
+
* Maximum of 16 rows that can be indexed from 0 to 15.
|
|
424
424
|
* @private @const {number}
|
|
425
425
|
*/
|
|
426
|
-
shaka.cea.Cea708Window.MAX_ROWS =
|
|
426
|
+
shaka.cea.Cea708Window.MAX_ROWS = 16;
|
|
@@ -66,14 +66,7 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
66
66
|
const trackIds = [];
|
|
67
67
|
const timescales = [];
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
// bitstream format. It makes the list of codec-signalling boxes below
|
|
71
|
-
// more compact and readable.
|
|
72
|
-
const bitstreamFormatSetter = (format) => {
|
|
73
|
-
return (box) => {
|
|
74
|
-
this.bitstreamFormat_ = format;
|
|
75
|
-
};
|
|
76
|
-
};
|
|
69
|
+
const codecBoxParser = (box) => this.setBitstreamFormat_(box.name);
|
|
77
70
|
|
|
78
71
|
new Mp4Parser()
|
|
79
72
|
.box('moov', Mp4Parser.children)
|
|
@@ -108,14 +101,21 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
108
101
|
.fullBox('stsd', Mp4Parser.sampleDescription)
|
|
109
102
|
|
|
110
103
|
// These are the various boxes that signal a codec.
|
|
111
|
-
|
|
112
|
-
.box('
|
|
113
|
-
.box('
|
|
114
|
-
.box('
|
|
115
|
-
.box('
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
104
|
+
.box('avc1', codecBoxParser)
|
|
105
|
+
.box('avc3', codecBoxParser)
|
|
106
|
+
.box('hev1', codecBoxParser)
|
|
107
|
+
.box('hvc1', codecBoxParser)
|
|
108
|
+
.box('dvh1', codecBoxParser)
|
|
109
|
+
.box('dvhe', codecBoxParser)
|
|
110
|
+
|
|
111
|
+
// This signals an encrypted sample, which we can go inside of to find
|
|
112
|
+
// the codec used.
|
|
113
|
+
.box('encv', Mp4Parser.visualSampleEntry)
|
|
114
|
+
.box('sinf', Mp4Parser.children)
|
|
115
|
+
.box('frma', (box) => {
|
|
116
|
+
const {codec} = shaka.util.Mp4BoxParsers.parseFRMA(box.reader);
|
|
117
|
+
this.setBitstreamFormat_(codec);
|
|
118
|
+
})
|
|
119
119
|
|
|
120
120
|
.parse(initSegment, /* partialOkay= */ true);
|
|
121
121
|
|
|
@@ -338,6 +338,16 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* @param {string} codec A fourcc for a codec.
|
|
344
|
+
* @private
|
|
345
|
+
*/
|
|
346
|
+
setBitstreamFormat_(codec) {
|
|
347
|
+
if (codec in shaka.cea.Mp4CeaParser.CodecBitstreamMap_) {
|
|
348
|
+
this.bitstreamFormat_ = shaka.cea.Mp4CeaParser.CodecBitstreamMap_[codec];
|
|
349
|
+
}
|
|
350
|
+
}
|
|
341
351
|
};
|
|
342
352
|
|
|
343
353
|
/** @enum {number} */
|
|
@@ -346,3 +356,14 @@ shaka.cea.Mp4CeaParser.BitstreamFormat = {
|
|
|
346
356
|
H264: 1,
|
|
347
357
|
H265: 2,
|
|
348
358
|
};
|
|
359
|
+
|
|
360
|
+
/** @private {Object.<string, shaka.cea.Mp4CeaParser.BitstreamFormat>} */
|
|
361
|
+
shaka.cea.Mp4CeaParser.CodecBitstreamMap_ = {
|
|
362
|
+
'avc1': shaka.cea.Mp4CeaParser.BitstreamFormat.H264,
|
|
363
|
+
'avc3': shaka.cea.Mp4CeaParser.BitstreamFormat.H264,
|
|
364
|
+
'hev1': shaka.cea.Mp4CeaParser.BitstreamFormat.H265,
|
|
365
|
+
'hvc1': shaka.cea.Mp4CeaParser.BitstreamFormat.H265,
|
|
366
|
+
// Dobly vision is also H265.
|
|
367
|
+
'dvh1': shaka.cea.Mp4CeaParser.BitstreamFormat.H265,
|
|
368
|
+
'dvhe': shaka.cea.Mp4CeaParser.BitstreamFormat.H265,
|
|
369
|
+
};
|
|
@@ -67,7 +67,7 @@ shaka.dash.SegmentTemplate = class {
|
|
|
67
67
|
},
|
|
68
68
|
};
|
|
69
69
|
} else if (info.segmentDuration) {
|
|
70
|
-
if (!isUpdate) {
|
|
70
|
+
if (!isUpdate && context.adaptationSet.contentType !== 'image') {
|
|
71
71
|
context.presentationTimeline.notifyMaxSegmentDuration(
|
|
72
72
|
info.segmentDuration);
|
|
73
73
|
context.presentationTimeline.notifyMinSegmentStartTime(
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -618,9 +618,23 @@ shaka.hls.HlsParser = class {
|
|
|
618
618
|
if (playlist.segments.length) {
|
|
619
619
|
const parsedUri = new goog.Uri(playlist.segments[0].absoluteUri);
|
|
620
620
|
const extension = parsedUri.getPath().split('.').pop();
|
|
621
|
-
|
|
621
|
+
let mimeType = HlsParser.RAW_FORMATS_TO_MIME_TYPES_[extension];
|
|
622
622
|
if (mimeType) {
|
|
623
623
|
fullMimeType = mimeType;
|
|
624
|
+
} else if (extension === 'ts') {
|
|
625
|
+
// TODO: Fetch one segment a use the TsParser to analize if there is
|
|
626
|
+
// video, audio or both.
|
|
627
|
+
} else if (extension === 'mp4') {
|
|
628
|
+
// TODO: Fetch one segment a use the Mp4Parser to analize if there is
|
|
629
|
+
// video, audio or both.
|
|
630
|
+
} else if (HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_[extension]) {
|
|
631
|
+
mimeType = HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_[extension];
|
|
632
|
+
const defaultAudioCodec = this.config_.hls.defaultAudioCodec;
|
|
633
|
+
fullMimeType = `${mimeType}; codecs="${defaultAudioCodec}"`;
|
|
634
|
+
} else if (HlsParser.VIDEO_EXTENSIONS_TO_MIME_TYPES_[extension]) {
|
|
635
|
+
mimeType = HlsParser.VIDEO_EXTENSIONS_TO_MIME_TYPES_[extension];
|
|
636
|
+
const defaultVideoCodec = this.config_.hls.defaultVideoCodec;
|
|
637
|
+
fullMimeType = `${mimeType}; codecs="${defaultVideoCodec}"`;
|
|
624
638
|
}
|
|
625
639
|
}
|
|
626
640
|
|
|
@@ -1123,7 +1137,7 @@ shaka.hls.HlsParser = class {
|
|
|
1123
1137
|
shaka.log.debug('Guessing audio-only.');
|
|
1124
1138
|
type = ContentType.AUDIO;
|
|
1125
1139
|
ignoreStream = true;
|
|
1126
|
-
} else if (res.video.length) {
|
|
1140
|
+
} else if (res.video.length && !res.audio.length) {
|
|
1127
1141
|
// There are associated video streams. Assume this is audio.
|
|
1128
1142
|
shaka.log.debug('Guessing audio-only.');
|
|
1129
1143
|
type = ContentType.AUDIO;
|
|
@@ -1677,9 +1691,10 @@ shaka.hls.HlsParser = class {
|
|
|
1677
1691
|
stream.encrypted = realStream.encrypted;
|
|
1678
1692
|
stream.drmInfos = realStream.drmInfos;
|
|
1679
1693
|
stream.keyIds = realStream.keyIds;
|
|
1680
|
-
stream.kind = realStream.kind;
|
|
1681
|
-
stream.roles = realStream.roles;
|
|
1682
1694
|
stream.mimeType = realStream.mimeType;
|
|
1695
|
+
if (shaka.media.MediaSourceEngine.RAW_FORMATS.includes(stream.mimeType)) {
|
|
1696
|
+
stream.codecs = '';
|
|
1697
|
+
}
|
|
1683
1698
|
|
|
1684
1699
|
// Since we lazy-loaded this content, the player may need to create new
|
|
1685
1700
|
// sessions for the DRM info in this stream.
|
|
@@ -1694,6 +1709,10 @@ shaka.hls.HlsParser = class {
|
|
|
1694
1709
|
// To aid manifest filtering, assume before loading that all video
|
|
1695
1710
|
// renditions have the same MIME type. (And likewise for audio.)
|
|
1696
1711
|
otherStreamInfo.stream.mimeType = realStream.mimeType;
|
|
1712
|
+
if (shaka.media.MediaSourceEngine.RAW_FORMATS
|
|
1713
|
+
.includes(otherStreamInfo.stream.mimeType)) {
|
|
1714
|
+
otherStreamInfo.stream.codecs = '';
|
|
1715
|
+
}
|
|
1697
1716
|
}
|
|
1698
1717
|
}
|
|
1699
1718
|
}
|
|
@@ -1923,23 +1942,20 @@ shaka.hls.HlsParser = class {
|
|
|
1923
1942
|
|
|
1924
1943
|
const mediaSequenceToStartTime = this.isLive_() ?
|
|
1925
1944
|
this.mediaSequenceToStartTimeByType_.get(type) : new Map();
|
|
1945
|
+
const prevLowLatencyMode = this.lowLatencyMode_;
|
|
1926
1946
|
const segments = this.createSegments_(verbatimMediaPlaylistUri, playlist,
|
|
1927
1947
|
type, mimeType, mediaSequenceToStartTime, mediaVariables);
|
|
1928
1948
|
|
|
1949
|
+
// This happens when autoLowLatencyMode is true, so we need set the
|
|
1950
|
+
// correct lowLatencyPresentationDelay_
|
|
1951
|
+
if (prevLowLatencyMode != this.lowLatencyMode_) {
|
|
1952
|
+
this.determinePresentationType_(playlist);
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1929
1955
|
const lastEndTime = segments[segments.length - 1].endTime;
|
|
1930
1956
|
/** @type {!shaka.media.SegmentIndex} */
|
|
1931
1957
|
const segmentIndex = new shaka.media.SegmentIndex(segments);
|
|
1932
1958
|
|
|
1933
|
-
const kind = (type == shaka.util.ManifestParserUtils.ContentType.TEXT) ?
|
|
1934
|
-
shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE : undefined;
|
|
1935
|
-
|
|
1936
|
-
const roles = [];
|
|
1937
|
-
if (characteristics) {
|
|
1938
|
-
for (const characteristic of characteristics.split(',')) {
|
|
1939
|
-
roles.push(characteristic);
|
|
1940
|
-
}
|
|
1941
|
-
}
|
|
1942
|
-
|
|
1943
1959
|
const serverControlTag = shaka.hls.Utils.getFirstTagWithName(
|
|
1944
1960
|
playlist.tags, 'EXT-X-SERVER-CONTROL');
|
|
1945
1961
|
const canSkipSegments = serverControlTag ?
|
|
@@ -1951,8 +1967,6 @@ shaka.hls.HlsParser = class {
|
|
|
1951
1967
|
stream.encrypted = encrypted;
|
|
1952
1968
|
stream.drmInfos = drmInfos;
|
|
1953
1969
|
stream.keyIds = keyIds;
|
|
1954
|
-
stream.kind = kind;
|
|
1955
|
-
stream.roles = roles;
|
|
1956
1970
|
stream.mimeType = mimeType;
|
|
1957
1971
|
|
|
1958
1972
|
return {
|
|
@@ -2002,7 +2016,8 @@ shaka.hls.HlsParser = class {
|
|
|
2002
2016
|
segmentIndex: null,
|
|
2003
2017
|
mimeType,
|
|
2004
2018
|
codecs,
|
|
2005
|
-
kind:
|
|
2019
|
+
kind: (type == shaka.util.ManifestParserUtils.ContentType.TEXT) ?
|
|
2020
|
+
shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE : undefined,
|
|
2006
2021
|
encrypted: false,
|
|
2007
2022
|
drmInfos: [],
|
|
2008
2023
|
keyIds: new Set(),
|
|
@@ -2018,7 +2033,7 @@ shaka.hls.HlsParser = class {
|
|
|
2018
2033
|
width: undefined,
|
|
2019
2034
|
height: undefined,
|
|
2020
2035
|
bandwidth: undefined,
|
|
2021
|
-
roles: [],
|
|
2036
|
+
roles: characteristics ? characteristics.split(',') : [],
|
|
2022
2037
|
forced,
|
|
2023
2038
|
channelsCount,
|
|
2024
2039
|
audioSamplingRate: null,
|
|
@@ -2156,8 +2171,11 @@ shaka.hls.HlsParser = class {
|
|
|
2156
2171
|
shaka.util.Error.Code.HLS_AES_128_INVALID_KEY_LENGTH);
|
|
2157
2172
|
}
|
|
2158
2173
|
|
|
2174
|
+
const algorithm = {
|
|
2175
|
+
name: 'AES-CBC',
|
|
2176
|
+
};
|
|
2159
2177
|
keyInfo.cryptoKey = await window.crypto.subtle.importKey(
|
|
2160
|
-
'raw', keyResponse.data,
|
|
2178
|
+
'raw', keyResponse.data, algorithm, true, ['decrypt']);
|
|
2161
2179
|
keyInfo.fetchKey = undefined; // No longer needed.
|
|
2162
2180
|
};
|
|
2163
2181
|
|
|
@@ -2574,12 +2592,12 @@ shaka.hls.HlsParser = class {
|
|
|
2574
2592
|
/** @type {shaka.extern.HlsAes128Key|undefined} */
|
|
2575
2593
|
let hlsAes128Key = undefined;
|
|
2576
2594
|
|
|
2577
|
-
|
|
2578
|
-
|
|
2595
|
+
let discontinuitySequence = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
2596
|
+
playlist.tags, 'EXT-X-DISCONTINUITY-SEQUENCE', 0);
|
|
2579
2597
|
const mediaSequenceNumber = shaka.hls.Utils.getFirstTagWithNameAsNumber(
|
|
2580
2598
|
playlist.tags, 'EXT-X-MEDIA-SEQUENCE', 0);
|
|
2581
|
-
const skipTag = shaka.hls.Utils.getFirstTagWithName(
|
|
2582
|
-
'EXT-X-SKIP');
|
|
2599
|
+
const skipTag = shaka.hls.Utils.getFirstTagWithName(
|
|
2600
|
+
playlist.tags, 'EXT-X-SKIP');
|
|
2583
2601
|
const skippedSegments =
|
|
2584
2602
|
skipTag ? Number(skipTag.getAttributeValue('SKIPPED-SEGMENTS')) : 0;
|
|
2585
2603
|
let position = mediaSequenceNumber + skippedSegments;
|
|
@@ -2602,6 +2620,12 @@ shaka.hls.HlsParser = class {
|
|
|
2602
2620
|
(i == 0) ? firstStartTime : previousReference.endTime;
|
|
2603
2621
|
position = mediaSequenceNumber + skippedSegments + i;
|
|
2604
2622
|
|
|
2623
|
+
const discontinuityTag = shaka.hls.Utils.getFirstTagWithName(
|
|
2624
|
+
playlist.tags, 'EXT-X-DISCONTINUITY');
|
|
2625
|
+
if (discontinuityTag) {
|
|
2626
|
+
discontinuitySequence++;
|
|
2627
|
+
}
|
|
2628
|
+
|
|
2605
2629
|
// Apply new AES-128 tags as you see them, keeping a running total.
|
|
2606
2630
|
for (const drmTag of item.tags) {
|
|
2607
2631
|
if (drmTag.name == 'EXT-X-KEY' &&
|
|
@@ -2639,6 +2663,8 @@ shaka.hls.HlsParser = class {
|
|
|
2639
2663
|
previousReference = reference;
|
|
2640
2664
|
|
|
2641
2665
|
if (reference) {
|
|
2666
|
+
reference.discontinuitySequence = discontinuitySequence;
|
|
2667
|
+
|
|
2642
2668
|
if (this.config_.hls.ignoreManifestProgramDateTime &&
|
|
2643
2669
|
this.minSequenceNumber_ != null &&
|
|
2644
2670
|
position < this.minSequenceNumber_) {
|
|
@@ -69,6 +69,18 @@ shaka.media.ContentWorkarounds = class {
|
|
|
69
69
|
})
|
|
70
70
|
.fullBox('encv', onEncryptionMetadataBox)
|
|
71
71
|
.fullBox('enca', onEncryptionMetadataBox)
|
|
72
|
+
.fullBox('hev1', (box) => {
|
|
73
|
+
boxesToModify.push({
|
|
74
|
+
box,
|
|
75
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
76
|
+
});
|
|
77
|
+
})
|
|
78
|
+
.fullBox('hvc1', (box) => {
|
|
79
|
+
boxesToModify.push({
|
|
80
|
+
box,
|
|
81
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
82
|
+
});
|
|
83
|
+
})
|
|
72
84
|
.fullBox('avc1', (box) => {
|
|
73
85
|
boxesToModify.push({
|
|
74
86
|
box,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/*! @license
|
|
2
|
+
* Shaka Player
|
|
3
|
+
* Copyright 2016 Google LLC
|
|
4
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
goog.provide('shaka.media.Capabilities');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @summary
|
|
11
|
+
* This is for capturing all media source capabilities on current platform.
|
|
12
|
+
* And this is for static check and can not be constructed.
|
|
13
|
+
*/
|
|
14
|
+
shaka.media.Capabilities = class {
|
|
15
|
+
/**
|
|
16
|
+
* Cache browser engine call to improve performance on some poor platforms
|
|
17
|
+
*
|
|
18
|
+
* @param {string} type
|
|
19
|
+
* @return {boolean}
|
|
20
|
+
*/
|
|
21
|
+
static isTypeSupported(type) {
|
|
22
|
+
const supportMap = shaka.media.Capabilities.MediaSourceTypeSupportMap;
|
|
23
|
+
if (supportMap.has(type)) {
|
|
24
|
+
return supportMap.get(type);
|
|
25
|
+
}
|
|
26
|
+
const currentSupport = MediaSource.isTypeSupported(type);
|
|
27
|
+
supportMap.set(type, currentSupport);
|
|
28
|
+
return currentSupport;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Public it for unit test, and developer could also check the support map.
|
|
34
|
+
* @type {!Map.<string, boolean>}
|
|
35
|
+
*/
|
|
36
|
+
shaka.media.Capabilities.MediaSourceTypeSupportMap = new Map();
|
|
@@ -8,6 +8,7 @@ goog.provide('shaka.media.MediaSourceEngine');
|
|
|
8
8
|
|
|
9
9
|
goog.require('goog.asserts');
|
|
10
10
|
goog.require('shaka.log');
|
|
11
|
+
goog.require('shaka.media.Capabilities');
|
|
11
12
|
goog.require('shaka.media.ContentWorkarounds');
|
|
12
13
|
goog.require('shaka.media.ClosedCaptionParser');
|
|
13
14
|
goog.require('shaka.media.IClosedCaptionParser');
|
|
@@ -175,7 +176,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
175
176
|
stream.mimeType, stream.codecs);
|
|
176
177
|
const extendedMimeType = shaka.util.MimeUtils.getExtendedType(stream);
|
|
177
178
|
return shaka.text.TextEngine.isTypeSupported(fullMimeType) ||
|
|
178
|
-
|
|
179
|
+
shaka.media.Capabilities.isTypeSupported(extendedMimeType) ||
|
|
179
180
|
shaka.media.Transmuxer.isSupported(fullMimeType, stream.type);
|
|
180
181
|
}
|
|
181
182
|
|
|
@@ -231,7 +232,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
231
232
|
if (shaka.text.TextEngine.isTypeSupported(type)) {
|
|
232
233
|
support[type] = true;
|
|
233
234
|
} else {
|
|
234
|
-
support[type] =
|
|
235
|
+
support[type] = shaka.media.Capabilities.isTypeSupported(type) ||
|
|
235
236
|
shaka.media.Transmuxer.isSupported(type);
|
|
236
237
|
}
|
|
237
238
|
} else {
|
|
@@ -365,7 +366,8 @@ shaka.media.MediaSourceEngine = class {
|
|
|
365
366
|
if (contentType == ContentType.TEXT) {
|
|
366
367
|
this.reinitText(mimeType, sequenceMode);
|
|
367
368
|
} else {
|
|
368
|
-
if ((forceTransmux ||
|
|
369
|
+
if ((forceTransmux ||
|
|
370
|
+
!shaka.media.Capabilities.isTypeSupported(mimeType)) &&
|
|
369
371
|
shaka.media.Transmuxer.isSupported(mimeType, contentType)) {
|
|
370
372
|
this.transmuxers_[contentType] =
|
|
371
373
|
new shaka.media.Transmuxer(mimeType);
|
|
@@ -586,11 +588,14 @@ shaka.media.MediaSourceEngine = class {
|
|
|
586
588
|
mimeType = this.transmuxers_[contentType].getOrginalMimeType();
|
|
587
589
|
}
|
|
588
590
|
if (shaka.util.TsParser.probe(uint8ArrayData)) {
|
|
589
|
-
const
|
|
590
|
-
|
|
591
|
+
const tsParser = new shaka.util.TsParser().parse(uint8ArrayData);
|
|
592
|
+
// The SourceBuffer timestampOffset may or may not be set yet, so this is
|
|
593
|
+
// the timestamp offset that would eventually compute for this segment
|
|
594
|
+
// either way.
|
|
595
|
+
const timestampOffset =
|
|
596
|
+
reference.startTime - (tsParser.getStartTime()[contentType] || 0);
|
|
597
|
+
const metadata = tsParser.getMetadata();
|
|
591
598
|
if (metadata.length) {
|
|
592
|
-
const timestampOffset =
|
|
593
|
-
this.sourceBuffers_[contentType].timestampOffset;
|
|
594
599
|
this.onMetadata_(metadata, timestampOffset,
|
|
595
600
|
reference ? reference.endTime : null);
|
|
596
601
|
}
|
|
@@ -919,6 +924,22 @@ shaka.media.MediaSourceEngine = class {
|
|
|
919
924
|
]);
|
|
920
925
|
}
|
|
921
926
|
|
|
927
|
+
/**
|
|
928
|
+
* Adjust timestamp offset to maintain AV sync across discontinuities.
|
|
929
|
+
* Only used in sequence mode.
|
|
930
|
+
*
|
|
931
|
+
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
|
|
932
|
+
* @param {number} timestampOffset
|
|
933
|
+
* @return {!Promise}
|
|
934
|
+
*/
|
|
935
|
+
async resync(contentType, timestampOffset) {
|
|
936
|
+
goog.asserts.assert(this.sequenceMode_,
|
|
937
|
+
'resyncAudio only used with sequence mode!');
|
|
938
|
+
await this.enqueueOperation_(
|
|
939
|
+
contentType,
|
|
940
|
+
() => this.setTimestampOffset_(contentType, timestampOffset));
|
|
941
|
+
}
|
|
942
|
+
|
|
922
943
|
/**
|
|
923
944
|
* @param {string=} reason Valid reasons are 'network' and 'decode'.
|
|
924
945
|
* @return {!Promise}
|
|
@@ -622,6 +622,11 @@ shaka.media.StreamingEngine = class {
|
|
|
622
622
|
* within the presentation timeline.
|
|
623
623
|
*/
|
|
624
624
|
seeked() {
|
|
625
|
+
if (!this.playerInterface_) {
|
|
626
|
+
// Already destroyed.
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
|
|
625
630
|
const presentationTime = this.playerInterface_.getPresentationTime();
|
|
626
631
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
627
632
|
const newTimeIsBuffered = (type) => {
|
|
@@ -679,7 +684,7 @@ shaka.media.StreamingEngine = class {
|
|
|
679
684
|
|
|
680
685
|
/**
|
|
681
686
|
* Clear the buffer for a given stream. Unlike clearBuffer_, this will handle
|
|
682
|
-
* cases where a MediaState is performing an update. After this runs,
|
|
687
|
+
* cases where a MediaState is performing an update. After this runs, the
|
|
683
688
|
* MediaState will have a pending update.
|
|
684
689
|
* @param {!shaka.media.StreamingEngine.MediaState_} mediaState
|
|
685
690
|
* @private
|
|
@@ -795,9 +800,9 @@ shaka.media.StreamingEngine = class {
|
|
|
795
800
|
for (const type of streamsByType.keys()) {
|
|
796
801
|
const stream = streamsByType.get(type);
|
|
797
802
|
if (!this.mediaStates_.has(type)) {
|
|
798
|
-
const
|
|
799
|
-
this.mediaStates_.set(type,
|
|
800
|
-
this.scheduleUpdate_(
|
|
803
|
+
const mediaState = this.createMediaState_(stream);
|
|
804
|
+
this.mediaStates_.set(type, mediaState);
|
|
805
|
+
this.scheduleUpdate_(mediaState, 0);
|
|
801
806
|
}
|
|
802
807
|
}
|
|
803
808
|
}
|
|
@@ -909,7 +914,7 @@ shaka.media.StreamingEngine = class {
|
|
|
909
914
|
'mediastate.stream should not have segmentIndex yet.');
|
|
910
915
|
thisStream.closeSegmentIndex();
|
|
911
916
|
}
|
|
912
|
-
if (mediaState.
|
|
917
|
+
if (!mediaState.performingUpdate && !mediaState.updateTimer) {
|
|
913
918
|
this.scheduleUpdate_(mediaState, 0);
|
|
914
919
|
}
|
|
915
920
|
return;
|
|
@@ -1394,7 +1399,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1394
1399
|
// If the network slows down, abort the current fetch request and start
|
|
1395
1400
|
// a new one, and ignore the error message.
|
|
1396
1401
|
mediaState.performingUpdate = false;
|
|
1397
|
-
mediaState
|
|
1402
|
+
this.cancelUpdate_(mediaState);
|
|
1398
1403
|
this.scheduleUpdate_(mediaState, 0);
|
|
1399
1404
|
} else if (mediaState.type == ContentType.TEXT &&
|
|
1400
1405
|
this.config_.ignoreTextStreamFailures) {
|
|
@@ -1467,7 +1472,10 @@ shaka.media.StreamingEngine = class {
|
|
|
1467
1472
|
|
|
1468
1473
|
for (const mediaState of this.mediaStates_.values()) {
|
|
1469
1474
|
const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
|
|
1470
|
-
if
|
|
1475
|
+
// Only schedule an update if it has an error, but it's not mid-update
|
|
1476
|
+
// and there is not already an update scheduled.
|
|
1477
|
+
if (mediaState.hasError && !mediaState.performingUpdate &&
|
|
1478
|
+
!mediaState.updateTimer) {
|
|
1471
1479
|
shaka.log.info(logPrefix, 'Retrying after failure...');
|
|
1472
1480
|
mediaState.hasError = false;
|
|
1473
1481
|
this.scheduleUpdate_(mediaState, delaySeconds);
|
|
@@ -1661,6 +1669,19 @@ shaka.media.StreamingEngine = class {
|
|
|
1661
1669
|
}
|
|
1662
1670
|
}
|
|
1663
1671
|
|
|
1672
|
+
if (this.manifest_.sequenceMode) {
|
|
1673
|
+
// Across discontinuity bounds, we should resync timestamps for
|
|
1674
|
+
// sequence mode playbacks. The next segment appended should
|
|
1675
|
+
// land at its theoretical timestamp from the segment index.
|
|
1676
|
+
const lastDiscontinuitySequence =
|
|
1677
|
+
mediaState.lastSegmentReference ?
|
|
1678
|
+
mediaState.lastSegmentReference.discontinuitySequence : null;
|
|
1679
|
+
if (reference.discontinuitySequence != lastDiscontinuitySequence) {
|
|
1680
|
+
operations.push(this.playerInterface_.mediaSourceEngine.resync(
|
|
1681
|
+
mediaState.type, reference.startTime));
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1664
1685
|
await Promise.all(operations);
|
|
1665
1686
|
}
|
|
1666
1687
|
|
|
@@ -1715,8 +1736,6 @@ shaka.media.StreamingEngine = class {
|
|
|
1715
1736
|
|
|
1716
1737
|
await this.evict_(mediaState, presentationTime);
|
|
1717
1738
|
this.destroyer_.ensureNotDestroyed();
|
|
1718
|
-
shaka.log.v1(logPrefix, 'appending media segment at',
|
|
1719
|
-
(reference.syncTime == null ? 'unknown' : reference.syncTime));
|
|
1720
1739
|
|
|
1721
1740
|
// 'seeked' or 'adaptation' triggered logic applies only to this
|
|
1722
1741
|
// appendBuffer() call.
|
|
@@ -2079,7 +2098,11 @@ shaka.media.StreamingEngine = class {
|
|
|
2079
2098
|
shaka.log.debug(logPrefix, 'cleared buffer');
|
|
2080
2099
|
mediaState.clearingBuffer = false;
|
|
2081
2100
|
mediaState.endOfStream = false;
|
|
2082
|
-
|
|
2101
|
+
// Since the clear operation was async, check to make sure we're not doing
|
|
2102
|
+
// another update and we don't have one scheduled yet.
|
|
2103
|
+
if (!mediaState.performingUpdate && !mediaState.updateTimer) {
|
|
2104
|
+
this.scheduleUpdate_(mediaState, 0);
|
|
2105
|
+
}
|
|
2083
2106
|
}
|
|
2084
2107
|
|
|
2085
2108
|
|
package/lib/media/transmuxer.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
goog.provide('shaka.media.Transmuxer');
|
|
8
8
|
|
|
9
9
|
goog.require('goog.asserts');
|
|
10
|
+
goog.require('shaka.media.Capabilities');
|
|
10
11
|
goog.require('shaka.util.BufferUtils');
|
|
11
12
|
goog.require('shaka.util.Error');
|
|
12
13
|
goog.require('shaka.util.IDestroyable');
|
|
@@ -74,6 +75,7 @@ shaka.media.Transmuxer = class {
|
|
|
74
75
|
*/
|
|
75
76
|
static isSupported(mimeType, contentType) {
|
|
76
77
|
const Transmuxer = shaka.media.Transmuxer;
|
|
78
|
+
const Capabilities = shaka.media.Capabilities;
|
|
77
79
|
|
|
78
80
|
const isTs = Transmuxer.isTsContainer_(mimeType);
|
|
79
81
|
const isAac = Transmuxer.isAacContainer_(mimeType);
|
|
@@ -83,11 +85,11 @@ shaka.media.Transmuxer = class {
|
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
if (isAac) {
|
|
86
|
-
return
|
|
88
|
+
return Capabilities.isTypeSupported(Transmuxer.convertAacCodecs_());
|
|
87
89
|
}
|
|
88
90
|
|
|
89
91
|
if (contentType) {
|
|
90
|
-
return
|
|
92
|
+
return Capabilities.isTypeSupported(
|
|
91
93
|
Transmuxer.convertTsCodecs_(contentType, mimeType));
|
|
92
94
|
}
|
|
93
95
|
|
|
@@ -95,8 +97,8 @@ shaka.media.Transmuxer = class {
|
|
|
95
97
|
|
|
96
98
|
const audioMime = Transmuxer.convertTsCodecs_(ContentType.AUDIO, mimeType);
|
|
97
99
|
const videoMime = Transmuxer.convertTsCodecs_(ContentType.VIDEO, mimeType);
|
|
98
|
-
return
|
|
99
|
-
|
|
100
|
+
return Capabilities.isTypeSupported(audioMime) ||
|
|
101
|
+
Capabilities.isTypeSupported(videoMime);
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
|
package/lib/player.js
CHANGED
|
@@ -2160,15 +2160,15 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2160
2160
|
// Unless the user has already picked a variant, anyway, by calling
|
|
2161
2161
|
// selectVariantTrack before this loading stage.
|
|
2162
2162
|
let initialVariant = null;
|
|
2163
|
-
const
|
|
2164
|
-
if (!
|
|
2163
|
+
const activeVariant = this.streamingEngine_.getCurrentVariant();
|
|
2164
|
+
if (!activeVariant) {
|
|
2165
2165
|
initialVariant = this.chooseVariant_();
|
|
2166
2166
|
goog.asserts.assert(initialVariant, 'Must choose an initial variant!');
|
|
2167
2167
|
}
|
|
2168
2168
|
|
|
2169
2169
|
// Lazy-load the stream, so we will have enough info to make the playhead.
|
|
2170
2170
|
const createSegmentIndexPromises = [];
|
|
2171
|
-
const toLazyLoad =
|
|
2171
|
+
const toLazyLoad = activeVariant || initialVariant;
|
|
2172
2172
|
for (const stream of [toLazyLoad.video, toLazyLoad.audio]) {
|
|
2173
2173
|
if (stream && !stream.segmentIndex) {
|
|
2174
2174
|
createSegmentIndexPromises.push(stream.createSegmentIndex());
|
|
@@ -2189,7 +2189,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2189
2189
|
this.startBufferManagement_(rebufferThreshold);
|
|
2190
2190
|
|
|
2191
2191
|
// Now we can switch to the initial variant.
|
|
2192
|
-
if (!
|
|
2192
|
+
if (!activeVariant) {
|
|
2193
2193
|
goog.asserts.assert(initialVariant,
|
|
2194
2194
|
'Must have choosen an initial variant!');
|
|
2195
2195
|
|
|
@@ -4330,6 +4330,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4330
4330
|
return [];
|
|
4331
4331
|
}
|
|
4332
4332
|
const chapters = [];
|
|
4333
|
+
const uniqueChapters = new Set();
|
|
4333
4334
|
for (const chaptersTrack of chaptersTracksWithLanguage) {
|
|
4334
4335
|
if (chaptersTrack && chaptersTrack.cues) {
|
|
4335
4336
|
for (const cue of chaptersTrack.cues) {
|
|
@@ -4344,7 +4345,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4344
4345
|
startTime: cue.startTime,
|
|
4345
4346
|
endTime: cue.endTime,
|
|
4346
4347
|
};
|
|
4347
|
-
|
|
4348
|
+
if (!uniqueChapters.has(id)) {
|
|
4349
|
+
chapters.push(chapter);
|
|
4350
|
+
uniqueChapters.add(id);
|
|
4351
|
+
}
|
|
4348
4352
|
}
|
|
4349
4353
|
}
|
|
4350
4354
|
}
|
|
@@ -6974,7 +6978,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6974
6978
|
* @define {string} A version number taken from git at compile time.
|
|
6975
6979
|
* @export
|
|
6976
6980
|
*/
|
|
6977
|
-
shaka.Player.version = 'v4.3.
|
|
6981
|
+
shaka.Player.version = 'v4.3.2-uncompiled';
|
|
6978
6982
|
|
|
6979
6983
|
// Initialize the deprecation system using the version string we just set
|
|
6980
6984
|
// on the player.
|