shaka-player 4.9.34 → 4.9.36
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/AUTHORS +1 -0
- package/CONTRIBUTORS +1 -0
- package/README.md +9 -3
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/deps.js +6 -6
- package/dist/locales.js +31 -31
- package/dist/shaka-player.compiled.d.ts +1 -0
- package/dist/shaka-player.compiled.debug.d.ts +1 -0
- package/dist/shaka-player.compiled.debug.externs.js +4 -3
- package/dist/shaka-player.compiled.debug.js +445 -427
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +4 -3
- package/dist/shaka-player.compiled.js +1254 -1240
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.dash.d.ts +4099 -0
- package/dist/shaka-player.dash.debug.d.ts +4099 -0
- package/dist/shaka-player.dash.debug.externs.js +4188 -0
- package/dist/shaka-player.dash.debug.js +2161 -0
- package/dist/shaka-player.dash.debug.map +8 -0
- package/dist/shaka-player.dash.externs.js +4188 -0
- package/dist/shaka-player.dash.js +1007 -0
- package/dist/shaka-player.dash.map +8 -0
- package/dist/shaka-player.hls.d.ts +4196 -0
- package/dist/shaka-player.hls.debug.d.ts +4196 -0
- package/dist/shaka-player.hls.debug.externs.js +4403 -0
- package/dist/shaka-player.hls.debug.js +2243 -0
- package/dist/shaka-player.hls.debug.map +8 -0
- package/dist/shaka-player.hls.externs.js +4403 -0
- package/dist/shaka-player.hls.js +1076 -0
- package/dist/shaka-player.hls.map +8 -0
- package/dist/shaka-player.ui.d.ts +1 -0
- package/dist/shaka-player.ui.debug.d.ts +1 -0
- package/dist/shaka-player.ui.debug.externs.js +4 -3
- package/dist/shaka-player.ui.debug.js +472 -454
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +4 -3
- package/dist/shaka-player.ui.js +1495 -1484
- package/dist/shaka-player.ui.map +3 -3
- package/externs/shaka/player.js +8 -4
- package/lib/cast/cast_proxy.js +48 -10
- package/lib/cea/cea708_service.js +20 -2
- package/lib/cea/dtvcc_packet_builder.js +15 -0
- package/lib/dash/dash_parser.js +24 -13
- package/lib/dash/mpd_utils.js +7 -4
- package/lib/dash/segment_template.js +74 -56
- package/lib/hls/hls_parser.js +5 -4
- package/lib/hls/manifest_text_parser.js +1 -0
- package/lib/media/content_workarounds.js +110 -72
- package/lib/media/drm_engine.js +72 -11
- package/lib/media/gap_jumping_controller.js +14 -1
- package/lib/media/media_source_engine.js +29 -11
- package/lib/media/region_timeline.js +4 -3
- package/lib/media/segment_utils.js +28 -16
- package/lib/media/streaming_engine.js +38 -12
- package/lib/net/http_fetch_plugin.js +1 -1
- package/lib/net/http_plugin_utils.js +4 -1
- package/lib/net/networking_engine.js +8 -0
- package/lib/net/networking_utils.js +1 -0
- package/lib/player.js +109 -37
- package/lib/polyfill/patchedmediakeys_apple.js +21 -0
- package/lib/text/cue.js +6 -2
- package/lib/text/mp4_ttml_parser.js +94 -6
- package/lib/text/ui_text_displayer.js +35 -4
- package/lib/transmuxer/ac3_transmuxer.js +6 -1
- package/lib/util/config_utils.js +13 -3
- package/lib/util/drm_utils.js +8 -0
- package/lib/util/error.js +6 -0
- package/lib/util/mp4_generator.js +19 -4
- package/lib/util/periods.js +4 -2
- package/lib/util/platform.js +60 -12
- package/lib/util/player_configuration.js +13 -2
- package/lib/util/stream_utils.js +18 -0
- package/lib/util/tXml.js +3 -2
- package/package.json +3 -3
- package/ui/controls.js +2 -6
- package/ui/less/thumbnails.less +1 -0
- package/ui/statistics_button.js +3 -1
- package/ui/ui.js +44 -35
|
@@ -58,6 +58,20 @@ shaka.media.ContentWorkarounds = class {
|
|
|
58
58
|
/** @type {!Array.<{box: shaka.extern.ParsedBox, newType: number}>} */
|
|
59
59
|
const boxesToModify = [];
|
|
60
60
|
|
|
61
|
+
const pushEncv = (box) => {
|
|
62
|
+
boxesToModify.push({
|
|
63
|
+
box,
|
|
64
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
65
|
+
});
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const pushEnca = (box) => {
|
|
69
|
+
boxesToModify.push({
|
|
70
|
+
box,
|
|
71
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCA_,
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
61
75
|
new shaka.util.Mp4Parser()
|
|
62
76
|
.box('moov', onSimpleAncestorBox)
|
|
63
77
|
.box('trak', onSimpleAncestorBox)
|
|
@@ -71,78 +85,23 @@ shaka.media.ContentWorkarounds = class {
|
|
|
71
85
|
})
|
|
72
86
|
.fullBox('encv', onEncryptionMetadataBox)
|
|
73
87
|
.fullBox('enca', onEncryptionMetadataBox)
|
|
74
|
-
.fullBox('dvav',
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
.fullBox('
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
.fullBox('
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
})
|
|
92
|
-
.fullBox('dvhe', (box) => {
|
|
93
|
-
boxesToModify.push({
|
|
94
|
-
box,
|
|
95
|
-
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
96
|
-
});
|
|
97
|
-
})
|
|
98
|
-
.fullBox('hev1', (box) => {
|
|
99
|
-
boxesToModify.push({
|
|
100
|
-
box,
|
|
101
|
-
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
102
|
-
});
|
|
103
|
-
})
|
|
104
|
-
.fullBox('hvc1', (box) => {
|
|
105
|
-
boxesToModify.push({
|
|
106
|
-
box,
|
|
107
|
-
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
108
|
-
});
|
|
109
|
-
})
|
|
110
|
-
.fullBox('avc1', (box) => {
|
|
111
|
-
boxesToModify.push({
|
|
112
|
-
box,
|
|
113
|
-
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
114
|
-
});
|
|
115
|
-
})
|
|
116
|
-
.fullBox('avc3', (box) => {
|
|
117
|
-
boxesToModify.push({
|
|
118
|
-
box,
|
|
119
|
-
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
120
|
-
});
|
|
121
|
-
})
|
|
122
|
-
.fullBox('ac-3', (box) => {
|
|
123
|
-
boxesToModify.push({
|
|
124
|
-
box,
|
|
125
|
-
newType: ContentWorkarounds.BOX_TYPE_ENCA_,
|
|
126
|
-
});
|
|
127
|
-
})
|
|
128
|
-
.fullBox('ec-3', (box) => {
|
|
129
|
-
boxesToModify.push({
|
|
130
|
-
box,
|
|
131
|
-
newType: ContentWorkarounds.BOX_TYPE_ENCA_,
|
|
132
|
-
});
|
|
133
|
-
})
|
|
134
|
-
.fullBox('ac-4', (box) => {
|
|
135
|
-
boxesToModify.push({
|
|
136
|
-
box,
|
|
137
|
-
newType: ContentWorkarounds.BOX_TYPE_ENCA_,
|
|
138
|
-
});
|
|
139
|
-
})
|
|
140
|
-
.fullBox('mp4a', (box) => {
|
|
141
|
-
boxesToModify.push({
|
|
142
|
-
box,
|
|
143
|
-
newType: ContentWorkarounds.BOX_TYPE_ENCA_,
|
|
144
|
-
});
|
|
145
|
-
}).parse(initSegment);
|
|
88
|
+
.fullBox('dvav', pushEncv)
|
|
89
|
+
.fullBox('dva1', pushEncv)
|
|
90
|
+
.fullBox('dvh1', pushEncv)
|
|
91
|
+
.fullBox('dvhe', pushEncv)
|
|
92
|
+
.fullBox('dvc1', pushEncv)
|
|
93
|
+
.fullBox('dvi1', pushEncv)
|
|
94
|
+
.fullBox('hev1', pushEncv)
|
|
95
|
+
.fullBox('hvc1', pushEncv)
|
|
96
|
+
.fullBox('avc1', pushEncv)
|
|
97
|
+
.fullBox('avc3', pushEncv)
|
|
98
|
+
.fullBox('ac-3', pushEnca)
|
|
99
|
+
.fullBox('ec-3', pushEnca)
|
|
100
|
+
.fullBox('ac-4', pushEnca)
|
|
101
|
+
.fullBox('Opus', pushEnca)
|
|
102
|
+
.fullBox('fLaC', pushEnca)
|
|
103
|
+
.fullBox('mp4a', pushEnca)
|
|
104
|
+
.parse(initSegment);
|
|
146
105
|
|
|
147
106
|
if (isEncrypted) {
|
|
148
107
|
shaka.log.debug('Init segment already indicates encryption.');
|
|
@@ -335,6 +294,53 @@ shaka.media.ContentWorkarounds = class {
|
|
|
335
294
|
boxView.setUint32(ContentWorkarounds.BOX_SIZE_OFFSET_, newBoxSize);
|
|
336
295
|
}
|
|
337
296
|
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Transform the init segment into a new init segment buffer that indicates
|
|
300
|
+
* EC-3 as audio codec instead of AC-3. Even though any EC-3 decoder should
|
|
301
|
+
* be able to decode AC-3 streams, there are platforms that do not accept
|
|
302
|
+
* AC-3 as codec.
|
|
303
|
+
*
|
|
304
|
+
* Should only be called for MP4 init segments, and only on platforms that
|
|
305
|
+
* need this workaround. Returns a new buffer containing the modified init
|
|
306
|
+
* segment.
|
|
307
|
+
*
|
|
308
|
+
* @param {!BufferSource} initSegmentBuffer
|
|
309
|
+
* @return {!Uint8Array}
|
|
310
|
+
*/
|
|
311
|
+
static fakeEC3(initSegmentBuffer) {
|
|
312
|
+
const ContentWorkarounds = shaka.media.ContentWorkarounds;
|
|
313
|
+
const initSegment = shaka.util.BufferUtils.toUint8(initSegmentBuffer);
|
|
314
|
+
const ancestorBoxes = [];
|
|
315
|
+
|
|
316
|
+
const onSimpleAncestorBox = (box) => {
|
|
317
|
+
ancestorBoxes.push({start: box.start, size: box.size});
|
|
318
|
+
shaka.util.Mp4Parser.children(box);
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
new shaka.util.Mp4Parser()
|
|
322
|
+
.box('moov', onSimpleAncestorBox)
|
|
323
|
+
.box('trak', onSimpleAncestorBox)
|
|
324
|
+
.box('mdia', onSimpleAncestorBox)
|
|
325
|
+
.box('minf', onSimpleAncestorBox)
|
|
326
|
+
.box('stbl', onSimpleAncestorBox)
|
|
327
|
+
.box('stsd', (box) => {
|
|
328
|
+
ancestorBoxes.push({start: box.start, size: box.size});
|
|
329
|
+
const stsdBoxView = shaka.util.BufferUtils.toDataView(
|
|
330
|
+
initSegment, box.start);
|
|
331
|
+
// "size - 3" is because we immediately read a uint32.
|
|
332
|
+
for (let i = 0; i < box.size -3; i++) {
|
|
333
|
+
const codecTag = stsdBoxView.getUint32(i);
|
|
334
|
+
if (codecTag == ContentWorkarounds.BOX_TYPE_AC_3_) {
|
|
335
|
+
stsdBoxView.setUint32(i, ContentWorkarounds.BOX_TYPE_EC_3_);
|
|
336
|
+
} else if (codecTag == ContentWorkarounds.BOX_TYPE_DAC3_) {
|
|
337
|
+
stsdBoxView.setUint32(i, ContentWorkarounds.BOX_TYPE_DEC3_);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}).parse(initSegment);
|
|
341
|
+
|
|
342
|
+
return initSegment;
|
|
343
|
+
}
|
|
338
344
|
};
|
|
339
345
|
|
|
340
346
|
/**
|
|
@@ -470,3 +476,35 @@ shaka.media.ContentWorkarounds.BOX_TYPE_ENCV_ = 0x656e6376;
|
|
|
470
476
|
* @private
|
|
471
477
|
*/
|
|
472
478
|
shaka.media.ContentWorkarounds.BOX_TYPE_ENCA_ = 0x656e6361;
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Box type for "ac-3".
|
|
482
|
+
*
|
|
483
|
+
* @const {number}
|
|
484
|
+
* @private
|
|
485
|
+
*/
|
|
486
|
+
shaka.media.ContentWorkarounds.BOX_TYPE_AC_3_ = 0x61632d33;
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Box type for "dac3".
|
|
490
|
+
*
|
|
491
|
+
* @const {number}
|
|
492
|
+
* @private
|
|
493
|
+
*/
|
|
494
|
+
shaka.media.ContentWorkarounds.BOX_TYPE_DAC3_ = 0x64616333;
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Box type for "ec-3".
|
|
498
|
+
*
|
|
499
|
+
* @const {number}
|
|
500
|
+
* @private
|
|
501
|
+
*/
|
|
502
|
+
shaka.media.ContentWorkarounds.BOX_TYPE_EC_3_ = 0x65632d33;
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Box type for "dec3".
|
|
506
|
+
*
|
|
507
|
+
* @const {number}
|
|
508
|
+
* @private
|
|
509
|
+
*/
|
|
510
|
+
shaka.media.ContentWorkarounds.BOX_TYPE_DEC3_ = 0x64656333;
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -1004,7 +1004,17 @@ shaka.media.DrmEngine = class {
|
|
|
1004
1004
|
|
|
1005
1005
|
// If we have configured preferredKeySystems, choose a preferred keySystem
|
|
1006
1006
|
// if available.
|
|
1007
|
-
|
|
1007
|
+
let preferredKeySystems = this.config_.preferredKeySystems;
|
|
1008
|
+
if (!preferredKeySystems.length) {
|
|
1009
|
+
// If there is no preference set and we only have one license server, we
|
|
1010
|
+
// use this as preference. This is used to override manifests on those
|
|
1011
|
+
// that have the embedded license and the browser supports multiple DRMs.
|
|
1012
|
+
const servers = shaka.util.MapUtils.asMap(this.config_.servers);
|
|
1013
|
+
if (servers.size == 1) {
|
|
1014
|
+
preferredKeySystems = Array.from(servers.keys());
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
for (const preferredKeySystem of preferredKeySystems) {
|
|
1008
1018
|
for (const variant of variants) {
|
|
1009
1019
|
const decodingInfo = variant.decodingInfos.find((decodingInfo) => {
|
|
1010
1020
|
return decodingInfo.supported &&
|
|
@@ -1028,8 +1038,15 @@ shaka.media.DrmEngine = class {
|
|
|
1028
1038
|
if (!decodingInfo.supported || !decodingInfo.keySystemAccess) {
|
|
1029
1039
|
continue;
|
|
1030
1040
|
}
|
|
1031
|
-
const
|
|
1032
|
-
|
|
1041
|
+
const originalKeySystem = decodingInfo.keySystemAccess.keySystem;
|
|
1042
|
+
if (preferredKeySystems.includes(originalKeySystem)) {
|
|
1043
|
+
continue;
|
|
1044
|
+
}
|
|
1045
|
+
let drmInfos = drmInfosByKeySystem.get(originalKeySystem);
|
|
1046
|
+
if (!drmInfos && this.config_.keySystemsMapping[originalKeySystem]) {
|
|
1047
|
+
drmInfos = drmInfosByKeySystem.get(
|
|
1048
|
+
this.config_.keySystemsMapping[originalKeySystem]);
|
|
1049
|
+
}
|
|
1033
1050
|
for (const info of drmInfos) {
|
|
1034
1051
|
if (!!info.licenseServerUri == shouldHaveLicenseServer) {
|
|
1035
1052
|
return decodingInfo.keySystemAccess;
|
|
@@ -1800,7 +1817,18 @@ shaka.media.DrmEngine = class {
|
|
|
1800
1817
|
*/
|
|
1801
1818
|
const processMediaKeySystemAccess = async (keySystem, access) => {
|
|
1802
1819
|
try {
|
|
1803
|
-
|
|
1820
|
+
// Workaround: Our automated test lab runs Windows browsers under a
|
|
1821
|
+
// headless service. In this environment, Firefox's ClearKey CDM seems
|
|
1822
|
+
// to crash when we create the CDM here.
|
|
1823
|
+
if (goog.DEBUG && // not a production build
|
|
1824
|
+
shaka.util.Platform.isWindows() && // on Windows
|
|
1825
|
+
shaka.util.Platform.isFirefox()) { // with Firefox
|
|
1826
|
+
// Reject this, since it crashes our tests.
|
|
1827
|
+
throw new Error('Suppressing Firefox Windows ClearKey in testing!');
|
|
1828
|
+
} else {
|
|
1829
|
+
// Otherwise, create the CDM.
|
|
1830
|
+
await access.createMediaKeys();
|
|
1831
|
+
}
|
|
1804
1832
|
} catch (error) {
|
|
1805
1833
|
// In some cases, we can get a successful access object but fail to
|
|
1806
1834
|
// create a MediaKeys instance. When this happens, don't update the
|
|
@@ -1947,17 +1975,47 @@ shaka.media.DrmEngine = class {
|
|
|
1947
1975
|
support.set(keySystem, null);
|
|
1948
1976
|
}
|
|
1949
1977
|
|
|
1978
|
+
const checkKeySystem = (keySystem) => {
|
|
1979
|
+
// Our Polyfill will reject anything apart com.apple.fps key systems.
|
|
1980
|
+
// It seems the Safari modern EME API will allow to request a
|
|
1981
|
+
// MediaKeySystemAccess for the ClearKey CDM, create and update a key
|
|
1982
|
+
// session but playback will never start
|
|
1983
|
+
// Safari bug: https://bugs.webkit.org/show_bug.cgi?id=231006
|
|
1984
|
+
if (shaka.util.DrmUtils.isClearKeySystem(keySystem) &&
|
|
1985
|
+
shaka.util.Platform.isSafari()) {
|
|
1986
|
+
return false;
|
|
1987
|
+
}
|
|
1988
|
+
// FairPlay is a proprietary DRM from Apple and will never work on
|
|
1989
|
+
// Windows.
|
|
1990
|
+
if (shaka.util.DrmUtils.isFairPlayKeySystem(keySystem) &&
|
|
1991
|
+
shaka.util.Platform.isWindows()) {
|
|
1992
|
+
return false;
|
|
1993
|
+
}
|
|
1994
|
+
// PlayReady is a proprietary DRM from Microsoft and will never work on
|
|
1995
|
+
// Apple platforms
|
|
1996
|
+
if (shaka.util.DrmUtils.isPlayReadyKeySystem(keySystem) &&
|
|
1997
|
+
(shaka.util.Platform.isMac() || shaka.util.Platform.isApple())) {
|
|
1998
|
+
return false;
|
|
1999
|
+
}
|
|
2000
|
+
// Mozilla has no intention of supporting PlayReady according to
|
|
2001
|
+
// comments posted on Bugzilla.
|
|
2002
|
+
if (shaka.util.DrmUtils.isPlayReadyKeySystem(keySystem) &&
|
|
2003
|
+
shaka.util.Platform.isFirefox()) {
|
|
2004
|
+
return false;
|
|
2005
|
+
}
|
|
2006
|
+
// We are sure that WisePlay is not supported on Windows or macOS.
|
|
2007
|
+
if (shaka.util.DrmUtils.isWisePlayKeySystem(keySystem) &&
|
|
2008
|
+
(shaka.util.Platform.isWindows() || shaka.util.Platform.isMac())) {
|
|
2009
|
+
return false;
|
|
2010
|
+
}
|
|
2011
|
+
return true;
|
|
2012
|
+
};
|
|
2013
|
+
|
|
1950
2014
|
// Test each key system and encryption scheme.
|
|
1951
2015
|
const tests = [];
|
|
1952
2016
|
for (const encryptionScheme of testEncryptionSchemes) {
|
|
1953
2017
|
for (const keySystem of testKeySystems) {
|
|
1954
|
-
|
|
1955
|
-
// It seems the Safari modern EME API will allow to request a
|
|
1956
|
-
// MediaKeySystemAccess for the ClearKey CDM, create and update a key
|
|
1957
|
-
// session but playback will never start
|
|
1958
|
-
// Safari bug: https://bugs.webkit.org/show_bug.cgi?id=231006
|
|
1959
|
-
if (keySystem === 'org.w3.clearkey' &&
|
|
1960
|
-
shaka.util.Platform.isSafari()) {
|
|
2018
|
+
if (!checkKeySystem(keySystem)) {
|
|
1961
2019
|
continue;
|
|
1962
2020
|
}
|
|
1963
2021
|
tests.push(testSystemEme(keySystem, encryptionScheme, '', ''));
|
|
@@ -1967,6 +2025,9 @@ shaka.media.DrmEngine = class {
|
|
|
1967
2025
|
|
|
1968
2026
|
for (const keySystem of testKeySystems) {
|
|
1969
2027
|
for (const robustness of (testRobustness[keySystem] || [])) {
|
|
2028
|
+
if (!checkKeySystem(keySystem)) {
|
|
2029
|
+
continue;
|
|
2030
|
+
}
|
|
1970
2031
|
tests.push(testSystemEme(keySystem, null, robustness, ''));
|
|
1971
2032
|
tests.push(testSystemEme(keySystem, null, '', robustness));
|
|
1972
2033
|
tests.push(testSystemMcap(keySystem, null, robustness, ''));
|
|
@@ -53,6 +53,9 @@ shaka.media.GapJumpingController = class {
|
|
|
53
53
|
/** @private {shaka.util.EventManager} */
|
|
54
54
|
this.eventManager_ = new shaka.util.EventManager();
|
|
55
55
|
|
|
56
|
+
/** @private {boolean} */
|
|
57
|
+
this.started_ = false;
|
|
58
|
+
|
|
56
59
|
/** @private {boolean} */
|
|
57
60
|
this.seekingEventReceived_ = false;
|
|
58
61
|
|
|
@@ -90,7 +93,7 @@ shaka.media.GapJumpingController = class {
|
|
|
90
93
|
*/
|
|
91
94
|
this.gapJumpTimer_ = new shaka.util.Timer(() => {
|
|
92
95
|
this.onPollGapJump_();
|
|
93
|
-
})
|
|
96
|
+
});
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
|
|
@@ -134,16 +137,22 @@ shaka.media.GapJumpingController = class {
|
|
|
134
137
|
* @param {number} startTime
|
|
135
138
|
*/
|
|
136
139
|
onStarted(startTime) {
|
|
140
|
+
this.started_ = true;
|
|
137
141
|
if (this.video_.seeking && !this.seekingEventReceived_) {
|
|
138
142
|
this.seekingEventReceived_ = true;
|
|
139
143
|
this.startTime_ = startTime;
|
|
140
144
|
}
|
|
145
|
+
if (this.gapJumpTimer_) {
|
|
146
|
+
this.gapJumpTimer_.tickEvery(this.config_.gapJumpTimerTime);
|
|
147
|
+
}
|
|
148
|
+
this.onPollGapJump_();
|
|
141
149
|
}
|
|
142
150
|
|
|
143
151
|
/** Called when a seek has started. */
|
|
144
152
|
onSeeking() {
|
|
145
153
|
this.seekingEventReceived_ = true;
|
|
146
154
|
this.hadSegmentAppended_ = false;
|
|
155
|
+
this.onPollGapJump_();
|
|
147
156
|
}
|
|
148
157
|
|
|
149
158
|
|
|
@@ -167,6 +176,10 @@ shaka.media.GapJumpingController = class {
|
|
|
167
176
|
if (this.video_.readyState == 0) {
|
|
168
177
|
return;
|
|
169
178
|
}
|
|
179
|
+
// Don't gap jump before playback started
|
|
180
|
+
if (!this.started_) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
170
183
|
// Do not gap jump if seeking has begun, but the seeking event has not
|
|
171
184
|
// yet fired for this particular seek.
|
|
172
185
|
if (this.video_.seeking) {
|
|
@@ -543,6 +543,11 @@ shaka.media.MediaSourceEngine = class {
|
|
|
543
543
|
stream, contentType),
|
|
544
544
|
'Type negotiation should happen before MediaSourceEngine.init!');
|
|
545
545
|
|
|
546
|
+
if (contentType == ContentType.AUDIO && codecs) {
|
|
547
|
+
codecs = shaka.util.StreamUtils.getCorrectAudioCodecs(
|
|
548
|
+
codecs, stream.mimeType);
|
|
549
|
+
}
|
|
550
|
+
|
|
546
551
|
let mimeType = shaka.util.MimeUtils.getFullType(
|
|
547
552
|
stream.mimeType, codecs);
|
|
548
553
|
if (contentType == ContentType.TEXT) {
|
|
@@ -1157,7 +1162,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1157
1162
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
1158
1163
|
if (contentType == ContentType.TEXT) {
|
|
1159
1164
|
await this.textEngine_.remove(startTime, endTime);
|
|
1160
|
-
} else {
|
|
1165
|
+
} else if (endTime > startTime) {
|
|
1161
1166
|
await this.enqueueOperation_(
|
|
1162
1167
|
contentType,
|
|
1163
1168
|
() => this.remove_(contentType, startTime, endTime),
|
|
@@ -1309,20 +1314,24 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1309
1314
|
null));
|
|
1310
1315
|
}
|
|
1311
1316
|
}
|
|
1312
|
-
|
|
1313
|
-
contentType,
|
|
1314
|
-
() => this.setAppendWindow_(
|
|
1315
|
-
contentType, appendWindowStart, appendWindowEnd),
|
|
1316
|
-
null));
|
|
1317
|
-
if (this.needSplitMuxedContent_) {
|
|
1317
|
+
if (appendWindowStart != 0 || appendWindowEnd != Infinity) {
|
|
1318
1318
|
operations.push(this.enqueueOperation_(
|
|
1319
|
-
|
|
1319
|
+
contentType,
|
|
1320
1320
|
() => this.setAppendWindow_(
|
|
1321
|
-
|
|
1321
|
+
contentType, appendWindowStart, appendWindowEnd),
|
|
1322
1322
|
null));
|
|
1323
|
+
if (this.needSplitMuxedContent_) {
|
|
1324
|
+
operations.push(this.enqueueOperation_(
|
|
1325
|
+
ContentType.AUDIO,
|
|
1326
|
+
() => this.setAppendWindow_(
|
|
1327
|
+
ContentType.AUDIO, appendWindowStart, appendWindowEnd),
|
|
1328
|
+
null));
|
|
1329
|
+
}
|
|
1323
1330
|
}
|
|
1324
1331
|
|
|
1325
|
-
|
|
1332
|
+
if (operations.length) {
|
|
1333
|
+
await Promise.all(operations);
|
|
1334
|
+
}
|
|
1326
1335
|
}
|
|
1327
1336
|
|
|
1328
1337
|
/**
|
|
@@ -1841,6 +1850,14 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1841
1850
|
segment = shaka.media.ContentWorkarounds.fakeEncryption(segment, uri);
|
|
1842
1851
|
}
|
|
1843
1852
|
|
|
1853
|
+
if (isInitSegment &&
|
|
1854
|
+
Platform.requiresEC3InitSegments() &&
|
|
1855
|
+
shaka.util.MimeUtils.getContainerType(
|
|
1856
|
+
this.sourceBufferTypes_[contentType]) == 'mp4') {
|
|
1857
|
+
shaka.log.debug('Forcing fake EC-3 information in init segment.');
|
|
1858
|
+
segment = shaka.media.ContentWorkarounds.fakeEC3(segment);
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1844
1861
|
return segment;
|
|
1845
1862
|
}
|
|
1846
1863
|
|
|
@@ -2169,7 +2186,8 @@ shaka.media.MediaSourceEngine = class {
|
|
|
2169
2186
|
const newMimeType = realInfo.mimeType;
|
|
2170
2187
|
|
|
2171
2188
|
let muxedContentCheck = true;
|
|
2172
|
-
if (transmuxerMuxed
|
|
2189
|
+
if (transmuxerMuxed &&
|
|
2190
|
+
ContentType.AUDIO in this.sourceBufferTypes_) {
|
|
2173
2191
|
const muxedRealInfo =
|
|
2174
2192
|
this.getRealInfo_(ContentType.AUDIO, mimeType, codecs);
|
|
2175
2193
|
const muxedCurrentCodec = MimeUtils.getNormalizedCodec(
|
|
@@ -102,13 +102,14 @@ shaka.media.RegionTimeline = class extends shaka.util.FakeEventTarget {
|
|
|
102
102
|
* @private
|
|
103
103
|
*/
|
|
104
104
|
findSimilarRegion_(region) {
|
|
105
|
+
const isDiffNegligible = (a, b) => Math.abs(a - b) < 0.1;
|
|
105
106
|
for (const existing of this.regions_) {
|
|
106
107
|
// The same scheme ID and time range means that it is similar-enough to
|
|
107
108
|
// be the same region.
|
|
108
109
|
const isSimilar = existing.schemeIdUri == region.schemeIdUri &&
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
existing.id == region.id &&
|
|
111
|
+
isDiffNegligible(existing.startTime, region.startTime) &&
|
|
112
|
+
isDiffNegligible(existing.endTime, region.endTime);
|
|
112
113
|
|
|
113
114
|
if (isSimilar) {
|
|
114
115
|
return existing;
|
|
@@ -47,11 +47,12 @@ shaka.media.SegmentUtils = class {
|
|
|
47
47
|
|
|
48
48
|
/**
|
|
49
49
|
* @param {!BufferSource} data
|
|
50
|
-
* @param {boolean
|
|
51
|
-
* @param {boolean
|
|
50
|
+
* @param {boolean} disableAudio
|
|
51
|
+
* @param {boolean} disableVideo
|
|
52
|
+
* @param {boolean} disableText
|
|
52
53
|
* @return {?shaka.media.SegmentUtils.BasicInfo}
|
|
53
54
|
*/
|
|
54
|
-
static getBasicInfoFromTs(data, disableAudio
|
|
55
|
+
static getBasicInfoFromTs(data, disableAudio, disableVideo, disableText) {
|
|
55
56
|
const uint8ArrayData = shaka.util.BufferUtils.toUint8(data);
|
|
56
57
|
const tsParser = new shaka.util.TsParser().parse(uint8ArrayData);
|
|
57
58
|
const tsCodecs = tsParser.getCodecs();
|
|
@@ -63,24 +64,34 @@ shaka.media.SegmentUtils = class {
|
|
|
63
64
|
switch (tsCodecs.audio) {
|
|
64
65
|
case 'aac':
|
|
65
66
|
case 'aac-loas':
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
if (tsParser.getAudioData().length) {
|
|
68
|
+
codecs.push('mp4a.40.2');
|
|
69
|
+
hasAudio = true;
|
|
70
|
+
}
|
|
68
71
|
break;
|
|
69
72
|
case 'mp3':
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
if (tsParser.getAudioData().length) {
|
|
74
|
+
codecs.push('mp4a.40.34');
|
|
75
|
+
hasAudio = true;
|
|
76
|
+
}
|
|
72
77
|
break;
|
|
73
78
|
case 'ac3':
|
|
74
|
-
|
|
75
|
-
|
|
79
|
+
if (tsParser.getAudioData().length) {
|
|
80
|
+
codecs.push('ac-3');
|
|
81
|
+
hasAudio = true;
|
|
82
|
+
}
|
|
76
83
|
break;
|
|
77
84
|
case 'ec3':
|
|
78
|
-
|
|
79
|
-
|
|
85
|
+
if (tsParser.getAudioData().length) {
|
|
86
|
+
codecs.push('ec-3');
|
|
87
|
+
hasAudio = true;
|
|
88
|
+
}
|
|
80
89
|
break;
|
|
81
90
|
case 'opus':
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
if (tsParser.getAudioData().length) {
|
|
92
|
+
codecs.push('opus');
|
|
93
|
+
hasAudio = true;
|
|
94
|
+
}
|
|
84
95
|
break;
|
|
85
96
|
}
|
|
86
97
|
}
|
|
@@ -113,7 +124,7 @@ shaka.media.SegmentUtils = class {
|
|
|
113
124
|
}
|
|
114
125
|
const onlyAudio = hasAudio && !hasVideo;
|
|
115
126
|
const closedCaptions = new Map();
|
|
116
|
-
if (hasVideo) {
|
|
127
|
+
if (hasVideo && !disableText) {
|
|
117
128
|
const captionParser = new shaka.media.ClosedCaptionParser('video/mp2t');
|
|
118
129
|
captionParser.parseFrom(data);
|
|
119
130
|
for (const stream of captionParser.getStreams()) {
|
|
@@ -139,9 +150,10 @@ shaka.media.SegmentUtils = class {
|
|
|
139
150
|
/**
|
|
140
151
|
* @param {?BufferSource} initData
|
|
141
152
|
* @param {!BufferSource} data
|
|
153
|
+
* @param {boolean} disableText
|
|
142
154
|
* @return {?shaka.media.SegmentUtils.BasicInfo}
|
|
143
155
|
*/
|
|
144
|
-
static getBasicInfoFromMp4(initData, data) {
|
|
156
|
+
static getBasicInfoFromMp4(initData, data, disableText) {
|
|
145
157
|
const Mp4Parser = shaka.util.Mp4Parser;
|
|
146
158
|
const SegmentUtils = shaka.media.SegmentUtils;
|
|
147
159
|
|
|
@@ -382,7 +394,7 @@ shaka.media.SegmentUtils = class {
|
|
|
382
394
|
}
|
|
383
395
|
const onlyAudio = hasAudio && !hasVideo;
|
|
384
396
|
const closedCaptions = new Map();
|
|
385
|
-
if (hasVideo) {
|
|
397
|
+
if (hasVideo && !disableText) {
|
|
386
398
|
const captionParser = new shaka.media.ClosedCaptionParser('video/mp4');
|
|
387
399
|
if (initData) {
|
|
388
400
|
captionParser.init(initData);
|
|
@@ -1314,12 +1314,17 @@ shaka.media.StreamingEngine = class {
|
|
|
1314
1314
|
this.playerInterface_.mediaSourceEngine.clearSelectedClosedCaptionId();
|
|
1315
1315
|
}
|
|
1316
1316
|
|
|
1317
|
+
// Update updateIntervalSeconds according to our current playbackrate,
|
|
1318
|
+
// and always considering a minimum of 1.
|
|
1319
|
+
const updateIntervalSeconds = this.config_.updateIntervalSeconds /
|
|
1320
|
+
Math.max(1, Math.abs(this.playerInterface_.getPlaybackRate()));
|
|
1321
|
+
|
|
1317
1322
|
if (!this.playerInterface_.mediaSourceEngine.isStreamingAllowed() &&
|
|
1318
1323
|
mediaState.type != ContentType.TEXT) {
|
|
1319
1324
|
// It is not allowed to add segments yet, so we schedule an update to
|
|
1320
1325
|
// check again later. So any prediction we make now could be terribly
|
|
1321
1326
|
// invalid soon.
|
|
1322
|
-
return
|
|
1327
|
+
return updateIntervalSeconds / 2;
|
|
1323
1328
|
}
|
|
1324
1329
|
|
|
1325
1330
|
const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
|
|
@@ -1393,9 +1398,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1393
1398
|
shaka.log.v2(logPrefix, 'buffering goal met');
|
|
1394
1399
|
|
|
1395
1400
|
// Do not try to predict the next update. Just poll according to
|
|
1396
|
-
// configuration (seconds).
|
|
1397
|
-
|
|
1398
|
-
return this.config_.updateIntervalSeconds / 2;
|
|
1401
|
+
// configuration (seconds).
|
|
1402
|
+
return updateIntervalSeconds / 2;
|
|
1399
1403
|
}
|
|
1400
1404
|
|
|
1401
1405
|
// Lack of segment iterator is the best indicator stream has changed.
|
|
@@ -1407,7 +1411,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1407
1411
|
// In any case just try again... if the manifest is incomplete or is not
|
|
1408
1412
|
// being updated then we'll idle forever; otherwise, we'll end up getting
|
|
1409
1413
|
// a SegmentReference eventually.
|
|
1410
|
-
return
|
|
1414
|
+
return updateIntervalSeconds;
|
|
1411
1415
|
}
|
|
1412
1416
|
// Get media state adaptation and reset this value. By guarding it during
|
|
1413
1417
|
// actual stream change we ensure it won't be cleaned by accident on regular
|
|
@@ -1446,7 +1450,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1446
1450
|
// For example, let video buffering catch up to audio buffering before
|
|
1447
1451
|
// fetching another audio segment.
|
|
1448
1452
|
shaka.log.v2(logPrefix, 'waiting for other streams to buffer');
|
|
1449
|
-
return
|
|
1453
|
+
return updateIntervalSeconds;
|
|
1450
1454
|
}
|
|
1451
1455
|
|
|
1452
1456
|
if (mediaState.segmentPrefetch && mediaState.segmentIterator &&
|
|
@@ -1509,7 +1513,18 @@ shaka.media.StreamingEngine = class {
|
|
|
1509
1513
|
// Something is buffered from the same Stream. Use the current position
|
|
1510
1514
|
// in the segment index. This is updated via next() after each segment is
|
|
1511
1515
|
// appended.
|
|
1512
|
-
|
|
1516
|
+
let ref = mediaState.segmentIterator.current();
|
|
1517
|
+
if (ref && mediaState.lastSegmentReference) {
|
|
1518
|
+
// In HLS sometimes the segment iterator adds or removes segments very
|
|
1519
|
+
// quickly, so we have to be sure that we do not add the last segment
|
|
1520
|
+
// again, tolerating a difference of 1ms.
|
|
1521
|
+
const isDiffNegligible = (a, b) => Math.abs(a - b) < 0.001;
|
|
1522
|
+
const lastStartTime = mediaState.lastSegmentReference.startTime;
|
|
1523
|
+
if (isDiffNegligible(lastStartTime, ref.startTime)) {
|
|
1524
|
+
ref = mediaState.segmentIterator.next().value;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
return ref;
|
|
1513
1528
|
} else if (mediaState.lastSegmentReference || bufferEnd) {
|
|
1514
1529
|
// Something is buffered from another Stream.
|
|
1515
1530
|
const time = mediaState.lastSegmentReference ?
|
|
@@ -2485,7 +2500,7 @@ shaka.media.StreamingEngine = class {
|
|
|
2485
2500
|
const segmentIndex = mediaState.stream.segmentIndex;
|
|
2486
2501
|
if (segmentIndex instanceof shaka.media.MetaSegmentIndex) {
|
|
2487
2502
|
segmentIndex.evict(
|
|
2488
|
-
this.manifest_.presentationTimeline.
|
|
2503
|
+
this.manifest_.presentationTimeline.getSegmentAvailabilityStart());
|
|
2489
2504
|
}
|
|
2490
2505
|
|
|
2491
2506
|
const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
|
|
@@ -2512,15 +2527,26 @@ shaka.media.StreamingEngine = class {
|
|
|
2512
2527
|
}
|
|
2513
2528
|
const bufferedBehind = presentationTime - startTime;
|
|
2514
2529
|
|
|
2515
|
-
const
|
|
2530
|
+
const evictionGoal = this.config_.evictionGoal;
|
|
2531
|
+
|
|
2532
|
+
const seekRangeStart =
|
|
2533
|
+
this.manifest_.presentationTimeline.getSeekRangeStart();
|
|
2534
|
+
const seekRangeEnd =
|
|
2535
|
+
this.manifest_.presentationTimeline.getSeekRangeEnd();
|
|
2536
|
+
|
|
2537
|
+
let overflow = bufferedBehind - bufferBehind;
|
|
2538
|
+
if (seekRangeEnd - seekRangeStart > evictionGoal) {
|
|
2539
|
+
overflow = Math.max(bufferedBehind - bufferBehind,
|
|
2540
|
+
seekRangeStart - evictionGoal - startTime);
|
|
2541
|
+
}
|
|
2516
2542
|
// See: https://github.com/shaka-project/shaka-player/issues/6240
|
|
2517
|
-
if (overflow <=
|
|
2543
|
+
if (overflow <= evictionGoal) {
|
|
2518
2544
|
shaka.log.v2(logPrefix,
|
|
2519
2545
|
'buffer behind okay:',
|
|
2520
2546
|
'presentationTime=' + presentationTime,
|
|
2521
2547
|
'bufferedBehind=' + bufferedBehind,
|
|
2522
2548
|
'bufferBehind=' + bufferBehind,
|
|
2523
|
-
'evictionGoal=' +
|
|
2549
|
+
'evictionGoal=' + evictionGoal,
|
|
2524
2550
|
'underflow=' + Math.abs(overflow));
|
|
2525
2551
|
return;
|
|
2526
2552
|
}
|
|
@@ -2530,7 +2556,7 @@ shaka.media.StreamingEngine = class {
|
|
|
2530
2556
|
'presentationTime=' + presentationTime,
|
|
2531
2557
|
'bufferedBehind=' + bufferedBehind,
|
|
2532
2558
|
'bufferBehind=' + bufferBehind,
|
|
2533
|
-
'evictionGoal=' +
|
|
2559
|
+
'evictionGoal=' + evictionGoal,
|
|
2534
2560
|
'overflow=' + overflow);
|
|
2535
2561
|
|
|
2536
2562
|
await this.playerInterface_.mediaSourceEngine.remove(mediaState.type,
|