shaka-player 3.1.1 → 3.1.5
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 +7 -2
- package/CHANGELOG.md +199 -0
- package/CONTRIBUTORS +9 -3
- package/build/generateExterns.js +1 -1
- package/build/misspellings.txt +1 -0
- package/build/shakaBuildHelpers.py +6 -5
- package/build/subprocessWindowsPatch.py +103 -0
- package/demo/common/assets.js +1 -1
- package/dist/controls.css +2 -2
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +1 -1
- package/dist/demo.compiled.js +1 -1
- package/dist/deps.js +7 -7
- package/dist/locales.js +35 -33
- package/dist/receiver.compiled.debug.js +1 -1
- package/dist/receiver.compiled.js +1 -1
- package/dist/shaka-player.compiled.d.ts +171 -3
- package/dist/shaka-player.compiled.debug.d.ts +171 -3
- package/dist/shaka-player.compiled.debug.externs.js +179 -1
- package/dist/shaka-player.compiled.debug.js +365 -344
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +179 -1
- package/dist/shaka-player.compiled.js +806 -798
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +171 -3
- package/dist/shaka-player.ui.debug.d.ts +171 -3
- package/dist/shaka-player.ui.debug.externs.js +181 -3
- package/dist/shaka-player.ui.debug.js +458 -435
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +181 -3
- package/dist/shaka-player.ui.js +927 -918
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/fairplay.md +1 -1
- package/docs/tutorials/faq.md +14 -0
- package/docs/tutorials/index.json +2 -2
- package/docs/tutorials/upgrade-v3.md +38 -0
- package/docs/tutorials/upgrade.md +3 -3
- package/externs/ima.js +4 -0
- package/externs/shaka/ads.js +2 -0
- package/externs/shaka/player.js +6 -0
- package/externs/shaka/text.js +10 -0
- package/karma.conf.js +1 -0
- package/lib/ads/ad_manager.js +29 -11
- package/lib/ads/client_side_ad_manager.js +76 -86
- package/lib/ads/server_side_ad_manager.js +26 -11
- package/lib/cast/cast_proxy.js +3 -2
- package/lib/cast/cast_receiver.js +2 -1
- package/lib/cast/cast_sender.js +2 -2
- package/lib/cea/sei_processor.js +24 -17
- package/lib/dash/dash_parser.js +100 -5
- package/lib/dash/segment_list.js +1 -1
- package/lib/dash/segment_template.js +7 -4
- package/lib/hls/hls_parser.js +44 -17
- package/lib/media/closed_caption_parser.js +3 -1
- package/lib/media/content_workarounds.js +12 -0
- package/lib/media/drm_engine.js +20 -1
- package/lib/media/gap_jumping_controller.js +8 -4
- package/lib/media/media_source_engine.js +1 -2
- package/lib/media/playhead.js +1 -1
- package/lib/media/presentation_timeline.js +3 -1
- package/lib/media/segment_reference.js +8 -0
- package/lib/media/streaming_engine.js +28 -19
- package/lib/net/networking_engine.js +2 -1
- package/lib/player.js +108 -54
- package/lib/polyfill/encryption_scheme.js +2 -0
- package/lib/polyfill/fullscreen.js +2 -0
- package/lib/polyfill/mathround.js +2 -0
- package/lib/polyfill/media_capabilities.js +76 -22
- package/lib/polyfill/mediasource.js +8 -0
- package/lib/polyfill/orientation.js +3 -1
- package/lib/polyfill/patchedmediakeys_apple.js +6 -4
- package/lib/polyfill/patchedmediakeys_ms.js +6 -4
- package/lib/polyfill/patchedmediakeys_nop.js +2 -0
- package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
- package/lib/polyfill/pip_webkit.js +2 -0
- package/lib/polyfill/storage_estimate.js +2 -0
- package/lib/polyfill/video_play_promise.js +2 -0
- package/lib/polyfill/videoplaybackquality.js +2 -0
- package/lib/polyfill/vttcue.js +2 -0
- package/lib/text/cue.js +17 -0
- package/lib/text/mp4_ttml_parser.js +3 -0
- package/lib/text/text_engine.js +14 -2
- package/lib/text/ttml_text_parser.js +135 -87
- package/lib/text/ui_text_displayer.js +233 -83
- package/lib/text/vtt_text_parser.js +4 -2
- package/lib/util/fake_event.js +53 -7
- package/lib/util/mp4_box_parsers.js +1 -1
- package/lib/util/periods.js +111 -35
- package/lib/util/platform.js +13 -1
- package/lib/util/stream_utils.js +79 -10
- package/lib/util/xml_utils.js +19 -0
- package/package.json +2 -2
- package/test/cast/cast_proxy_unit.js +12 -6
- package/test/cast/cast_receiver_integration.js +1 -1
- package/test/cast/cast_utils_unit.js +1 -1
- package/test/cea/mp4_cea_parser_unit.js +26 -0
- package/test/dash/dash_parser_live_unit.js +1 -0
- package/test/dash/dash_parser_manifest_unit.js +220 -2
- package/test/dash/dash_parser_segment_template_unit.js +3 -3
- package/test/hls/hls_parser_unit.js +5 -1
- package/test/media/closed_caption_parser_unit.js +20 -0
- package/test/media/drm_engine_unit.js +56 -0
- package/test/media/playhead_unit.js +27 -1
- package/test/media/presentation_timeline_unit.js +19 -0
- package/test/media/streaming_engine_integration.js +2 -1
- package/test/media/streaming_engine_unit.js +148 -3
- package/test/player_integration.js +38 -0
- package/test/player_unit.js +101 -0
- package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
- package/test/test/assets/3675/dash_0.mpd +30 -0
- package/test/test/assets/3675/dash_1.mpd +30 -0
- package/test/test/assets/3675/dash_2.mpd +30 -0
- package/test/test/assets/3675/dash_3.mpd +30 -0
- package/test/test/assets/3675/dash_4.mpd +30 -0
- package/test/test/assets/3675/dash_5.mpd +26 -0
- package/test/test/assets/3675/init-stream0.m4s +0 -0
- package/test/test/assets/empty_caption_video_init.mp4 +0 -0
- package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
- package/test/test/util/fake_ad_manager.js +4 -1
- package/test/test/util/simple_fakes.js +5 -3
- package/test/test/util/ttml_utils.js +81 -0
- package/test/text/mp4_ttml_parser_unit.js +11 -18
- package/test/text/ttml_text_parser_unit.js +542 -220
- package/test/text/ui_text_displayer_unit.js +95 -18
- package/test/text/vtt_text_parser_unit.js +17 -1
- package/test/ui/localization_unit.js +15 -14
- package/test/ui/text_displayer_layout_unit.js +52 -1
- package/test/util/mp4_box_parsers_unit.js +28 -0
- package/test/util/periods_unit.js +190 -2
- package/test/util/stream_utils_unit.js +59 -2
- package/ui/airplay_button.js +5 -0
- package/ui/big_play_button.js +10 -0
- package/ui/cast_button.js +2 -3
- package/ui/controls.js +8 -6
- package/ui/less/buttons.less +0 -4
- package/ui/less/containers.less +10 -5
- package/ui/locales/en.json +1 -0
- package/ui/locales/ru.json +6 -6
- package/ui/locales/source.json +4 -0
- package/ui/localization.js +28 -51
- package/ui/pip_button.js +2 -3
- package/ui/play_button.js +10 -10
- package/ui/small_play_button.js +12 -0
- package/ui/text_selection.js +1 -0
- package/docs/tutorials/upgrade-v2-4.md +0 -481
|
@@ -14,10 +14,12 @@ goog.provide('shaka.ads.ServerSideAdManager');
|
|
|
14
14
|
goog.require('goog.asserts');
|
|
15
15
|
goog.require('shaka.ads.ServerSideAd');
|
|
16
16
|
goog.require('shaka.log');
|
|
17
|
+
goog.require('shaka.util.IReleasable');
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* A class responsible for server-side ad interactions.
|
|
22
|
+
* @implements {shaka.util.IReleasable}
|
|
21
23
|
*/
|
|
22
24
|
shaka.ads.ServerSideAdManager = class {
|
|
23
25
|
/**
|
|
@@ -75,9 +77,7 @@ shaka.ads.ServerSideAdManager = class {
|
|
|
75
77
|
|
|
76
78
|
this.onEvent_(new shaka.util.FakeEvent(
|
|
77
79
|
shaka.ads.AdManager.IMA_STREAM_MANAGER_LOADED,
|
|
78
|
-
|
|
79
|
-
'imaStreamManager': this.streamManager_,
|
|
80
|
-
}));
|
|
80
|
+
(new Map()).set('imaStreamManager', this.streamManager_)));
|
|
81
81
|
|
|
82
82
|
// Events
|
|
83
83
|
this.eventManager_.listen(this.streamManager_,
|
|
@@ -201,11 +201,27 @@ shaka.ads.ServerSideAdManager = class {
|
|
|
201
201
|
* Resets the stream manager and removes any continuous polling.
|
|
202
202
|
*/
|
|
203
203
|
stop() {
|
|
204
|
-
|
|
204
|
+
// TODO:
|
|
205
|
+
// For SS DAI streams, if a different asset gets unloaded as
|
|
206
|
+
// part of the process
|
|
207
|
+
// of loading a DAI asset, stream manager state gets reset and we
|
|
208
|
+
// don't get any ad events.
|
|
209
|
+
// We need to figure out if it makes sense to stop the SS
|
|
210
|
+
// manager on unload, and, if it does, find
|
|
211
|
+
// a way to do it safely.
|
|
212
|
+
// this.streamManager_.reset();
|
|
205
213
|
this.backupUrl_ = '';
|
|
206
214
|
this.snapForwardTime_ = null;
|
|
207
215
|
}
|
|
208
216
|
|
|
217
|
+
/** @override */
|
|
218
|
+
release() {
|
|
219
|
+
this.stop();
|
|
220
|
+
if (this.eventManager_) {
|
|
221
|
+
this.eventManager_.release();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
209
225
|
/**
|
|
210
226
|
* @param {string} type
|
|
211
227
|
* @param {Uint8Array|string} data
|
|
@@ -280,7 +296,7 @@ shaka.ads.ServerSideAdManager = class {
|
|
|
280
296
|
}
|
|
281
297
|
|
|
282
298
|
this.onEvent_(new shaka.util.FakeEvent(shaka.ads.AdManager.AD_STARTED,
|
|
283
|
-
|
|
299
|
+
(new Map()).set('ad', this.ad_)));
|
|
284
300
|
this.adContainer_.setAttribute('ad-active', 'true');
|
|
285
301
|
}
|
|
286
302
|
|
|
@@ -306,9 +322,8 @@ shaka.ads.ServerSideAdManager = class {
|
|
|
306
322
|
onLoaded_(e) {
|
|
307
323
|
const now = Date.now() / 1000;
|
|
308
324
|
const loadTime = now - this.streamRequestStartTime_;
|
|
309
|
-
this.onEvent_(
|
|
310
|
-
new
|
|
311
|
-
{'loadTime': loadTime}));
|
|
325
|
+
this.onEvent_(new shaka.util.FakeEvent(shaka.ads.AdManager.ADS_LOADED,
|
|
326
|
+
(new Map()).set('loadTime', loadTime)));
|
|
312
327
|
|
|
313
328
|
const streamData = e.getStreamData();
|
|
314
329
|
const url = streamData.url;
|
|
@@ -368,8 +383,8 @@ shaka.ads.ServerSideAdManager = class {
|
|
|
368
383
|
cuePoints.push(shakaCuePoint);
|
|
369
384
|
}
|
|
370
385
|
|
|
371
|
-
this.onEvent_(
|
|
372
|
-
|
|
373
|
-
|
|
386
|
+
this.onEvent_(new shaka.util.FakeEvent(
|
|
387
|
+
shaka.ads.AdManager.CUEPOINTS_CHANGED,
|
|
388
|
+
(new Map()).set('cuepoints', cuePoints)));
|
|
374
389
|
}
|
|
375
390
|
};
|
package/lib/cast/cast_proxy.js
CHANGED
|
@@ -525,7 +525,8 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
|
|
|
525
525
|
goog.asserts.assert(error instanceof shaka.util.Error,
|
|
526
526
|
'Wrong error type!');
|
|
527
527
|
const eventType = shaka.Player.EventName.Error;
|
|
528
|
-
const
|
|
528
|
+
const data = (new Map()).set('detail', error);
|
|
529
|
+
const event = new shaka.util.FakeEvent(eventType, data);
|
|
529
530
|
this.localPlayer_.dispatchEvent(event);
|
|
530
531
|
});
|
|
531
532
|
}
|
|
@@ -598,7 +599,7 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
|
|
|
598
599
|
|
|
599
600
|
// Convert this real Event into a FakeEvent for dispatch from our
|
|
600
601
|
// FakeEventListener.
|
|
601
|
-
const fakeEvent =
|
|
602
|
+
const fakeEvent = shaka.util.FakeEvent.fromRealEvent(event);
|
|
602
603
|
this.videoEventTarget_.dispatchEvent(fakeEvent);
|
|
603
604
|
}
|
|
604
605
|
|
|
@@ -407,7 +407,8 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget {
|
|
|
407
407
|
goog.asserts.assert(error instanceof shaka.util.Error,
|
|
408
408
|
'Wrong error type!');
|
|
409
409
|
const eventType = shaka.Player.EventName.Error;
|
|
410
|
-
const
|
|
410
|
+
const data = (new Map()).set('detail', error);
|
|
411
|
+
const event = new shaka.util.FakeEvent(eventType, data);
|
|
411
412
|
// Only dispatch the event if the player still exists.
|
|
412
413
|
if (this.player_) {
|
|
413
414
|
this.player_.dispatchEvent(event);
|
package/lib/cast/cast_sender.js
CHANGED
|
@@ -647,7 +647,7 @@ shaka.cast.CastSender = class {
|
|
|
647
647
|
case 'event': {
|
|
648
648
|
const targetName = message['targetName'];
|
|
649
649
|
const event = message['event'];
|
|
650
|
-
const fakeEvent =
|
|
650
|
+
const fakeEvent = shaka.util.FakeEvent.fromRealEvent(event);
|
|
651
651
|
this.onRemoteEvent_(targetName, fakeEvent);
|
|
652
652
|
break;
|
|
653
653
|
}
|
|
@@ -725,7 +725,7 @@ shaka.cast.CastSender = class {
|
|
|
725
725
|
|
|
726
726
|
// Dispatch it through the Player proxy
|
|
727
727
|
const fakeEvent = new shaka.util.FakeEvent(
|
|
728
|
-
'error',
|
|
728
|
+
'error', (new Map()).set('detail', shakaError));
|
|
729
729
|
this.onRemoteEvent_('player', fakeEvent);
|
|
730
730
|
|
|
731
731
|
// Force this session to disconnect and transfer playback to the local
|
package/lib/cea/sei_processor.js
CHANGED
|
@@ -17,31 +17,31 @@ shaka.cea.SeiProcessor = class {
|
|
|
17
17
|
* @return {!Iterable.<!Uint8Array>}
|
|
18
18
|
*/
|
|
19
19
|
* process(naluData) {
|
|
20
|
-
const
|
|
20
|
+
const naluClone = this.removeEmu(naluData);
|
|
21
21
|
|
|
22
22
|
// The following is an implementation of section 7.3.2.3.1
|
|
23
23
|
// in Rec. ITU-T H.264 (06/2019), the H.264 spec.
|
|
24
24
|
let offset = 0;
|
|
25
25
|
|
|
26
|
-
while (offset
|
|
26
|
+
while (offset < naluClone.length) {
|
|
27
27
|
let payloadType = 0; // SEI payload type as defined by H.264 spec
|
|
28
|
-
while (
|
|
28
|
+
while (naluClone[offset] == 0xFF) {
|
|
29
29
|
payloadType += 255;
|
|
30
30
|
offset++;
|
|
31
31
|
}
|
|
32
|
-
payloadType +=
|
|
32
|
+
payloadType += naluClone[offset++];
|
|
33
33
|
|
|
34
34
|
let payloadSize = 0; // SEI payload size as defined by H.264 spec
|
|
35
|
-
while (
|
|
35
|
+
while (naluClone[offset] == 0xFF) {
|
|
36
36
|
payloadSize += 255;
|
|
37
37
|
offset++;
|
|
38
38
|
}
|
|
39
|
-
payloadSize +=
|
|
39
|
+
payloadSize += naluClone[offset++];
|
|
40
40
|
|
|
41
41
|
// Payload type 4 is user_data_registered_itu_t_t35, as per the H.264
|
|
42
42
|
// spec. This payload type contains caption data.
|
|
43
43
|
if (payloadType == 0x04) {
|
|
44
|
-
yield
|
|
44
|
+
yield naluClone.subarray(offset, offset + payloadSize);
|
|
45
45
|
}
|
|
46
46
|
offset += payloadSize;
|
|
47
47
|
}
|
|
@@ -50,28 +50,35 @@ shaka.cea.SeiProcessor = class {
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Removes H.264 emulation prevention bytes from the byte array.
|
|
53
|
+
*
|
|
54
|
+
* Note: Remove bytes by shifting will cause Chromium (VDA) to complain
|
|
55
|
+
* about conformance. Recreating a new array solves it.
|
|
56
|
+
*
|
|
53
57
|
* @param {!Uint8Array} naluData NALU from which EMUs should be removed.
|
|
54
|
-
* @return {
|
|
55
|
-
* @private
|
|
58
|
+
* @return {!Uint8Array} The NALU with the emulation prevention byte removed.
|
|
56
59
|
*/
|
|
57
|
-
|
|
60
|
+
removeEmu(naluData) {
|
|
61
|
+
let naluClone = naluData;
|
|
58
62
|
let zeroCount = 0;
|
|
59
63
|
let src = 0;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
while (src < naluClone.length) {
|
|
65
|
+
if (zeroCount == 2 && naluClone[src] == 0x03) {
|
|
66
|
+
// 0x00, 0x00, 0x03 pattern detected
|
|
63
67
|
zeroCount = 0;
|
|
68
|
+
|
|
69
|
+
// Splice the array and recreate a new one, instead of shifting bytes
|
|
70
|
+
const newArr = [...naluClone];
|
|
71
|
+
newArr.splice(src, 1);
|
|
72
|
+
naluClone = new Uint8Array(newArr);
|
|
64
73
|
} else {
|
|
65
|
-
if (
|
|
74
|
+
if (naluClone[src] == 0x00) {
|
|
66
75
|
zeroCount++;
|
|
67
76
|
} else {
|
|
68
77
|
zeroCount = 0;
|
|
69
78
|
}
|
|
70
|
-
naluData[dst] = naluData[src];
|
|
71
|
-
dst++;
|
|
72
79
|
}
|
|
73
80
|
src++;
|
|
74
81
|
}
|
|
75
|
-
return
|
|
82
|
+
return naluClone;
|
|
76
83
|
}
|
|
77
84
|
};
|
package/lib/dash/dash_parser.js
CHANGED
|
@@ -95,6 +95,18 @@ shaka.dash.DashParser = class {
|
|
|
95
95
|
/** @private {!shaka.util.OperationManager} */
|
|
96
96
|
this.operationManager_ = new shaka.util.OperationManager();
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Largest period start time seen.
|
|
100
|
+
* @private {?number}
|
|
101
|
+
*/
|
|
102
|
+
this.largestPeriodStartTime_ = null;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Period IDs seen in previous manifest.
|
|
106
|
+
* @private {!Array.<number>}
|
|
107
|
+
*/
|
|
108
|
+
this.lastManifestUpdatePeriodIds_ = [];
|
|
109
|
+
|
|
98
110
|
/**
|
|
99
111
|
* The minimum of the availabilityTimeOffset values among the adaptation
|
|
100
112
|
* sets.
|
|
@@ -533,6 +545,7 @@ shaka.dash.DashParser = class {
|
|
|
533
545
|
for (const {i, item: elem, next} of enumerate(periodNodes)) {
|
|
534
546
|
const start = /** @type {number} */ (
|
|
535
547
|
XmlUtils.parseAttr(elem, 'start', XmlUtils.parseDuration, prevEnd));
|
|
548
|
+
const periodId = elem.id;
|
|
536
549
|
const givenDuration =
|
|
537
550
|
XmlUtils.parseAttr(elem, 'duration', XmlUtils.parseDuration);
|
|
538
551
|
|
|
@@ -566,6 +579,42 @@ shaka.dash.DashParser = class {
|
|
|
566
579
|
periodDuration = givenDuration;
|
|
567
580
|
}
|
|
568
581
|
|
|
582
|
+
/**
|
|
583
|
+
* This is to improve robustness when the player observes manifest with
|
|
584
|
+
* past periods that are inconsistent to previous ones.
|
|
585
|
+
*
|
|
586
|
+
* This may happen when a CDN or proxy server switches its upstream from
|
|
587
|
+
* one encoder to another redundant encoder.
|
|
588
|
+
*
|
|
589
|
+
* Skip periods that match all of the following criteria:
|
|
590
|
+
* - Start time is earlier than latest period start time ever seen
|
|
591
|
+
* - Period ID is never seen in the previous manifest
|
|
592
|
+
* - Not the last period in the manifest
|
|
593
|
+
*
|
|
594
|
+
* Periods that meet the aforementioned criteria are considered invalid
|
|
595
|
+
* and should be safe to discard.
|
|
596
|
+
*/
|
|
597
|
+
|
|
598
|
+
if (this.largestPeriodStartTime_ !== null &&
|
|
599
|
+
periodId !== null && start !== null &&
|
|
600
|
+
start < this.largestPeriodStartTime_ &&
|
|
601
|
+
!this.lastManifestUpdatePeriodIds_.includes(periodId) &&
|
|
602
|
+
i + 1 != periodNodes.length) {
|
|
603
|
+
shaka.log.debug(
|
|
604
|
+
`Skipping Period with ID ${periodId} as its start time is smaller` +
|
|
605
|
+
' than the largest period start time that has been seen, and ID ' +
|
|
606
|
+
'is unseen before');
|
|
607
|
+
continue;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
// Save maximum period start time if it is the last period
|
|
612
|
+
if (start !== null &&
|
|
613
|
+
(this.largestPeriodStartTime_ === null ||
|
|
614
|
+
start > this.largestPeriodStartTime_)) {
|
|
615
|
+
this.largestPeriodStartTime_ = start;
|
|
616
|
+
}
|
|
617
|
+
|
|
569
618
|
// Parse child nodes.
|
|
570
619
|
const info = {
|
|
571
620
|
start: start,
|
|
@@ -597,6 +646,9 @@ shaka.dash.DashParser = class {
|
|
|
597
646
|
prevEnd = start + periodDuration;
|
|
598
647
|
} // end of period parsing loop
|
|
599
648
|
|
|
649
|
+
// Replace previous seen periods with the current one.
|
|
650
|
+
this.lastManifestUpdatePeriodIds_ = periods.map((el) => el.id);
|
|
651
|
+
|
|
600
652
|
if (presentationDuration != null) {
|
|
601
653
|
if (prevEnd != presentationDuration) {
|
|
602
654
|
shaka.log.warning(
|
|
@@ -808,6 +860,25 @@ shaka.dash.DashParser = class {
|
|
|
808
860
|
}
|
|
809
861
|
}
|
|
810
862
|
|
|
863
|
+
// Parallel for HLS VIDEO-RANGE as defined in DASH-IF IOP v4.3 6.2.5.1.
|
|
864
|
+
let videoRange;
|
|
865
|
+
const videoRangeScheme = 'urn:mpeg:mpegB:cicp:TransferCharacteristics';
|
|
866
|
+
const getVideoRangeFromTransferCharacteristicCICP = (cicp) => {
|
|
867
|
+
switch (cicp) {
|
|
868
|
+
case 1:
|
|
869
|
+
case 6:
|
|
870
|
+
case 13:
|
|
871
|
+
case 14:
|
|
872
|
+
case 15:
|
|
873
|
+
return 'SDR';
|
|
874
|
+
case 16:
|
|
875
|
+
return 'PQ';
|
|
876
|
+
case 18:
|
|
877
|
+
return 'HLG';
|
|
878
|
+
}
|
|
879
|
+
return undefined;
|
|
880
|
+
};
|
|
881
|
+
|
|
811
882
|
const essentialProperties =
|
|
812
883
|
XmlUtils.findChildren(elem, 'EssentialProperty');
|
|
813
884
|
// ID of real AdaptationSet if this is a trick mode set:
|
|
@@ -817,11 +888,26 @@ shaka.dash.DashParser = class {
|
|
|
817
888
|
const schemeId = prop.getAttribute('schemeIdUri');
|
|
818
889
|
if (schemeId == 'http://dashif.org/guidelines/trickmode') {
|
|
819
890
|
trickModeFor = prop.getAttribute('value');
|
|
891
|
+
} else if (schemeId == videoRangeScheme) {
|
|
892
|
+
videoRange = getVideoRangeFromTransferCharacteristicCICP(
|
|
893
|
+
parseInt(prop.getAttribute('value'), 10),
|
|
894
|
+
);
|
|
820
895
|
} else {
|
|
821
896
|
unrecognizedEssentialProperty = true;
|
|
822
897
|
}
|
|
823
898
|
}
|
|
824
899
|
|
|
900
|
+
const supplementalProperties =
|
|
901
|
+
XmlUtils.findChildren(elem, 'SupplementalProperty');
|
|
902
|
+
for (const prop of supplementalProperties) {
|
|
903
|
+
const schemeId = prop.getAttribute('schemeIdUri');
|
|
904
|
+
if (schemeId == videoRangeScheme) {
|
|
905
|
+
videoRange = getVideoRangeFromTransferCharacteristicCICP(
|
|
906
|
+
parseInt(prop.getAttribute('value'), 10),
|
|
907
|
+
);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
825
911
|
const accessibilities = XmlUtils.findChildren(elem, 'Accessibility');
|
|
826
912
|
const LanguageUtils = shaka.util.LanguageUtils;
|
|
827
913
|
const closedCaptions = new Map();
|
|
@@ -858,7 +944,9 @@ shaka.dash.DashParser = class {
|
|
|
858
944
|
channel = channelAndLanguage[0].startsWith('CC') ?
|
|
859
945
|
channelAndLanguage[0] : `CC${channelAndLanguage[0]}`;
|
|
860
946
|
|
|
861
|
-
|
|
947
|
+
// 3 letters (ISO 639-2). In b/187442669, we saw a blank string
|
|
948
|
+
// (CC2=;CC3=), so default to "und" (the code for "undetermined").
|
|
949
|
+
language = channelAndLanguage[1] || 'und';
|
|
862
950
|
}
|
|
863
951
|
closedCaptions.set(channel, LanguageUtils.normalize(language));
|
|
864
952
|
}
|
|
@@ -945,8 +1033,13 @@ shaka.dash.DashParser = class {
|
|
|
945
1033
|
// Parse Representations into Streams.
|
|
946
1034
|
const representations = XmlUtils.findChildren(elem, 'Representation');
|
|
947
1035
|
const streams = representations.map((representation) => {
|
|
948
|
-
|
|
949
|
-
language, label, main, roleValues,
|
|
1036
|
+
const parsedRepresentation = this.parseRepresentation_(context,
|
|
1037
|
+
contentProtection, kind, language, label, main, roleValues,
|
|
1038
|
+
closedCaptions, representation);
|
|
1039
|
+
if (parsedRepresentation) {
|
|
1040
|
+
parsedRepresentation.hdr = parsedRepresentation.hdr || videoRange;
|
|
1041
|
+
}
|
|
1042
|
+
return parsedRepresentation;
|
|
950
1043
|
}).filter((s) => !!s);
|
|
951
1044
|
|
|
952
1045
|
if (streams.length == 0) {
|
|
@@ -1122,8 +1215,10 @@ shaka.dash.DashParser = class {
|
|
|
1122
1215
|
|
|
1123
1216
|
let forced = false;
|
|
1124
1217
|
if (isText) {
|
|
1125
|
-
// See: https://github.com/google/shaka-player/issues/2122
|
|
1126
|
-
|
|
1218
|
+
// See: https://github.com/google/shaka-player/issues/2122 and
|
|
1219
|
+
// https://github.com/Dash-Industry-Forum/DASH-IF-IOP/issues/165
|
|
1220
|
+
forced = roles.includes('forced_subtitle') ||
|
|
1221
|
+
roles.includes('forced-subtitle');
|
|
1127
1222
|
}
|
|
1128
1223
|
|
|
1129
1224
|
let tilesLayout;
|
package/lib/dash/segment_list.js
CHANGED
|
@@ -63,12 +63,12 @@ shaka.dash.SegmentList = class {
|
|
|
63
63
|
const start = context.presentationTimeline.getSegmentAvailabilityStart();
|
|
64
64
|
segmentIndex.mergeAndEvict(references, start);
|
|
65
65
|
} else {
|
|
66
|
-
context.presentationTimeline.notifySegments(references);
|
|
67
66
|
segmentIndex = new shaka.media.SegmentIndex(references);
|
|
68
67
|
if (id && context.dynamic) {
|
|
69
68
|
segmentIndexMap[id] = segmentIndex;
|
|
70
69
|
}
|
|
71
70
|
}
|
|
71
|
+
context.presentationTimeline.notifySegments(references);
|
|
72
72
|
|
|
73
73
|
if (!context.dynamic || !context.periodInfo.isLastPeriod) {
|
|
74
74
|
const periodStart = context.periodInfo.start;
|
|
@@ -116,12 +116,12 @@ shaka.dash.SegmentTemplate = class {
|
|
|
116
116
|
segmentIndex.mergeAndEvict(references,
|
|
117
117
|
context.presentationTimeline.getSegmentAvailabilityStart());
|
|
118
118
|
} else {
|
|
119
|
-
context.presentationTimeline.notifySegments(references);
|
|
120
119
|
segmentIndex = new shaka.media.SegmentIndex(references);
|
|
121
120
|
if (id && context.dynamic) {
|
|
122
121
|
segmentIndexMap[id] = segmentIndex;
|
|
123
122
|
}
|
|
124
123
|
}
|
|
124
|
+
context.presentationTimeline.notifySegments(references);
|
|
125
125
|
|
|
126
126
|
if (shouldFit) {
|
|
127
127
|
segmentIndex.fit(periodStart, periodEnd);
|
|
@@ -390,17 +390,17 @@ shaka.dash.SegmentTemplate = class {
|
|
|
390
390
|
|
|
391
391
|
// Relative to the presentation.
|
|
392
392
|
const segmentStart = segmentPeriodTime + periodStart;
|
|
393
|
+
const trueSegmentEnd = segmentStart + segmentDuration;
|
|
393
394
|
// Cap the segment end at the period end so that references from the
|
|
394
395
|
// next period will fit neatly after it.
|
|
395
|
-
const segmentEnd = Math.min(
|
|
396
|
-
getPeriodEnd());
|
|
396
|
+
const segmentEnd = Math.min(trueSegmentEnd, getPeriodEnd());
|
|
397
397
|
|
|
398
398
|
// This condition will be true unless the segmentStart was >= periodEnd.
|
|
399
399
|
// If we've done the position calculations correctly, this won't happen.
|
|
400
400
|
goog.asserts.assert(segmentStart < segmentEnd,
|
|
401
401
|
'Generated a segment outside of the period!');
|
|
402
402
|
|
|
403
|
-
|
|
403
|
+
const ref = new shaka.media.SegmentReference(
|
|
404
404
|
segmentStart,
|
|
405
405
|
segmentEnd,
|
|
406
406
|
getUris,
|
|
@@ -410,6 +410,9 @@ shaka.dash.SegmentTemplate = class {
|
|
|
410
410
|
timestampOffset,
|
|
411
411
|
/* appendWindowStart= */ periodStart,
|
|
412
412
|
/* appendWindowEnd= */ getPeriodEnd());
|
|
413
|
+
// This is necessary information for thumbnail streams:
|
|
414
|
+
ref.trueEndTime = trueSegmentEnd;
|
|
415
|
+
return ref;
|
|
413
416
|
};
|
|
414
417
|
|
|
415
418
|
for (let position = minPosition; position <= maxPosition; ++position) {
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -414,18 +414,16 @@ shaka.hls.HlsParser = class {
|
|
|
414
414
|
const uri = tag.getAttributeValue('URI');
|
|
415
415
|
const language = tag.getAttributeValue('LANGUAGE');
|
|
416
416
|
const value = tag.getAttributeValue('VALUE');
|
|
417
|
-
const data =
|
|
418
|
-
'id': id,
|
|
419
|
-
};
|
|
417
|
+
const data = (new Map()).set('id', id);
|
|
420
418
|
if (uri) {
|
|
421
|
-
data.uri
|
|
422
|
-
shaka.hls.Utils.constructAbsoluteUri(this.masterPlaylistUri_, uri);
|
|
419
|
+
data.set('uri',
|
|
420
|
+
shaka.hls.Utils.constructAbsoluteUri(this.masterPlaylistUri_, uri));
|
|
423
421
|
}
|
|
424
422
|
if (language) {
|
|
425
|
-
data.language
|
|
423
|
+
data.set('language', language);
|
|
426
424
|
}
|
|
427
425
|
if (value) {
|
|
428
|
-
data.value
|
|
426
|
+
data.set('value', value);
|
|
429
427
|
}
|
|
430
428
|
const event = new shaka.util.FakeEvent('sessiondata', data);
|
|
431
429
|
if (this.playerInterface_) {
|
|
@@ -529,6 +527,30 @@ shaka.hls.HlsParser = class {
|
|
|
529
527
|
}
|
|
530
528
|
}
|
|
531
529
|
|
|
530
|
+
// Now that the content has been fit, notify segments.
|
|
531
|
+
this.segmentsToNotifyByStream_ = [];
|
|
532
|
+
const streamsToNotify = [];
|
|
533
|
+
for (const variant of variants) {
|
|
534
|
+
for (const stream of [variant.video, variant.audio]) {
|
|
535
|
+
if (stream) {
|
|
536
|
+
streamsToNotify.push(stream);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
await Promise.all(streamsToNotify.map(async (stream) => {
|
|
541
|
+
await stream.createSegmentIndex();
|
|
542
|
+
}));
|
|
543
|
+
for (const stream of streamsToNotify) {
|
|
544
|
+
this.segmentsToNotifyByStream_.push(stream.segmentIndex.references);
|
|
545
|
+
}
|
|
546
|
+
this.notifySegments_();
|
|
547
|
+
|
|
548
|
+
// This asserts that the live edge is being calculated from segment times.
|
|
549
|
+
// For VOD and event streams, this check should still pass.
|
|
550
|
+
goog.asserts.assert(
|
|
551
|
+
!this.presentationTimeline_.usingPresentationStartTime(),
|
|
552
|
+
'We should not be using the presentation start time in HLS!');
|
|
553
|
+
|
|
532
554
|
this.manifest_ = {
|
|
533
555
|
presentationTimeline: this.presentationTimeline_,
|
|
534
556
|
variants,
|
|
@@ -1151,7 +1173,7 @@ shaka.hls.HlsParser = class {
|
|
|
1151
1173
|
// "AUTOSELECT=YES". A value of "AUTOSELECT=NO" would imply that it may
|
|
1152
1174
|
// only be selected explicitly by the user, and we don't have a way to
|
|
1153
1175
|
// represent that in our model.
|
|
1154
|
-
const defaultAttrValue = tag.
|
|
1176
|
+
const defaultAttrValue = tag.getAttributeValue('DEFAULT');
|
|
1155
1177
|
const primary = defaultAttrValue == 'YES';
|
|
1156
1178
|
|
|
1157
1179
|
const channelsCount = type == 'audio' ? this.getChannelsCount_(tag) : null;
|
|
@@ -1538,12 +1560,6 @@ shaka.hls.HlsParser = class {
|
|
|
1538
1560
|
}
|
|
1539
1561
|
|
|
1540
1562
|
this.notifySegments_();
|
|
1541
|
-
|
|
1542
|
-
// This asserts that the live edge is being calculated from segment times.
|
|
1543
|
-
// For VOD and event streams, this check should still pass.
|
|
1544
|
-
goog.asserts.assert(
|
|
1545
|
-
!this.presentationTimeline_.usingPresentationStartTime(),
|
|
1546
|
-
'We should not be using the presentation start time in HLS!');
|
|
1547
1563
|
}
|
|
1548
1564
|
|
|
1549
1565
|
/**
|
|
@@ -1915,9 +1931,6 @@ shaka.hls.HlsParser = class {
|
|
|
1915
1931
|
}
|
|
1916
1932
|
}
|
|
1917
1933
|
|
|
1918
|
-
this.segmentsToNotifyByStream_.push(references);
|
|
1919
|
-
this.notifySegments_();
|
|
1920
|
-
|
|
1921
1934
|
return references;
|
|
1922
1935
|
}
|
|
1923
1936
|
|
|
@@ -2253,6 +2266,12 @@ shaka.hls.HlsParser = class {
|
|
|
2253
2266
|
reader.rewind(1);
|
|
2254
2267
|
};
|
|
2255
2268
|
|
|
2269
|
+
// We will look a few packet-lengths forward to find the first sync byte.
|
|
2270
|
+
// Note that we are using this method on what is already a subset of the
|
|
2271
|
+
// file (the first |shaka.hls.HlsParser.START_OF_SEGMENT_SIZE_| bytes), so
|
|
2272
|
+
// we can't look too far ahead to begin with.
|
|
2273
|
+
let syncByteScanLength = Math.min(reader.getLength() - 188, 5 * 188);
|
|
2274
|
+
|
|
2256
2275
|
// TODO: refactor this while loop for better readability.
|
|
2257
2276
|
// eslint-disable-next-line no-constant-condition
|
|
2258
2277
|
while (true) {
|
|
@@ -2261,8 +2280,16 @@ shaka.hls.HlsParser = class {
|
|
|
2261
2280
|
|
|
2262
2281
|
syncByte = reader.readUint8();
|
|
2263
2282
|
if (syncByte != 0x47) {
|
|
2283
|
+
if (syncByteScanLength > 0) {
|
|
2284
|
+
// This file could have started with a cut-off TS packet. Scan forward
|
|
2285
|
+
// until we find a sync byte.
|
|
2286
|
+
syncByteScanLength -= 1;
|
|
2287
|
+
continue;
|
|
2288
|
+
}
|
|
2264
2289
|
fail();
|
|
2265
2290
|
}
|
|
2291
|
+
// If we've found a sync byte, stop scanning forward for future packets.
|
|
2292
|
+
syncByteScanLength = 0;
|
|
2266
2293
|
|
|
2267
2294
|
const flagsAndPacketId = reader.readUint16();
|
|
2268
2295
|
const packetId = flagsAndPacketId & 0x1fff;
|
|
@@ -87,7 +87,9 @@ shaka.media.ClosedCaptionParser = class {
|
|
|
87
87
|
for (const captionPacket of captionPackets) {
|
|
88
88
|
const uint8ArrayData =
|
|
89
89
|
shaka.util.BufferUtils.toUint8(captionPacket.packet);
|
|
90
|
-
|
|
90
|
+
if (uint8ArrayData.length > 0) {
|
|
91
|
+
this.ceaDecoder_.extract(uint8ArrayData, captionPacket.pts);
|
|
92
|
+
}
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
// Decode and return the parsed captions.
|
|
@@ -75,6 +75,18 @@ shaka.media.ContentWorkarounds = class {
|
|
|
75
75
|
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
76
76
|
});
|
|
77
77
|
})
|
|
78
|
+
.fullBox('avc3', (box) => {
|
|
79
|
+
boxesToModify.push({
|
|
80
|
+
box,
|
|
81
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCV_,
|
|
82
|
+
});
|
|
83
|
+
})
|
|
84
|
+
.fullBox('ac-3', (box) => {
|
|
85
|
+
boxesToModify.push({
|
|
86
|
+
box,
|
|
87
|
+
newType: ContentWorkarounds.BOX_TYPE_ENCA_,
|
|
88
|
+
});
|
|
89
|
+
})
|
|
78
90
|
.fullBox('ec-3', (box) => {
|
|
79
91
|
boxesToModify.push({
|
|
80
92
|
box,
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -126,6 +126,9 @@ shaka.media.DrmEngine = class {
|
|
|
126
126
|
|
|
127
127
|
/** @const {!shaka.util.Destroyer} */
|
|
128
128
|
this.destroyer_ = new shaka.util.Destroyer(() => this.destroyNow_());
|
|
129
|
+
|
|
130
|
+
/** @private {boolean} */
|
|
131
|
+
this.srcEquals_ = false;
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
/** @override */
|
|
@@ -181,6 +184,7 @@ shaka.media.DrmEngine = class {
|
|
|
181
184
|
this.config_ = null;
|
|
182
185
|
this.onError_ = () => {};
|
|
183
186
|
this.playerInterface_ = null;
|
|
187
|
+
this.srcEquals_ = false;
|
|
184
188
|
}
|
|
185
189
|
|
|
186
190
|
/**
|
|
@@ -194,6 +198,13 @@ shaka.media.DrmEngine = class {
|
|
|
194
198
|
this.config_ = config;
|
|
195
199
|
}
|
|
196
200
|
|
|
201
|
+
/**
|
|
202
|
+
* @param {!boolean} value
|
|
203
|
+
*/
|
|
204
|
+
setSrcEquals(value) {
|
|
205
|
+
this.srcEquals_ = value;
|
|
206
|
+
}
|
|
207
|
+
|
|
197
208
|
/**
|
|
198
209
|
* Initialize the drm engine for storing and deleting stored content.
|
|
199
210
|
*
|
|
@@ -356,7 +367,7 @@ shaka.media.DrmEngine = class {
|
|
|
356
367
|
// in the drm infos, and before queryMediaKeys_().
|
|
357
368
|
if (useMediaCapabilities) {
|
|
358
369
|
await shaka.util.StreamUtils.getDecodingInfosForVariants(variants,
|
|
359
|
-
this.usePersistentLicenses_);
|
|
370
|
+
this.usePersistentLicenses_, this.srcEquals_);
|
|
360
371
|
} else {
|
|
361
372
|
configsByKeySystem =
|
|
362
373
|
this.prepareMediaKeyConfigsForVariants_(variants);
|
|
@@ -512,6 +523,7 @@ shaka.media.DrmEngine = class {
|
|
|
512
523
|
tasks.push(session.remove());
|
|
513
524
|
|
|
514
525
|
await Promise.all(tasks);
|
|
526
|
+
this.activeSessions_.delete(session);
|
|
515
527
|
}
|
|
516
528
|
|
|
517
529
|
/**
|
|
@@ -1877,9 +1889,16 @@ shaka.media.DrmEngine = class {
|
|
|
1877
1889
|
// audio adaptations, so we shouldn't have to worry about checking
|
|
1878
1890
|
// robustness.
|
|
1879
1891
|
if (drm1.keySystem == drm2.keySystem) {
|
|
1892
|
+
/** @type {Array<shaka.extern.InitDataOverride>} */
|
|
1880
1893
|
let initData = [];
|
|
1881
1894
|
initData = initData.concat(drm1.initData || []);
|
|
1882
1895
|
initData = initData.concat(drm2.initData || []);
|
|
1896
|
+
initData = initData.filter((d, i) => {
|
|
1897
|
+
return d.keyId === undefined || i === initData.findIndex((d2) => {
|
|
1898
|
+
return d2.keyId === d.keyId;
|
|
1899
|
+
});
|
|
1900
|
+
});
|
|
1901
|
+
|
|
1883
1902
|
const keyIds = drm1.keyIds && drm2.keyIds ?
|
|
1884
1903
|
new Set([...drm1.keyIds, ...drm2.keyIds]) :
|
|
1885
1904
|
drm1.keyIds || drm2.keyIds;
|