shaka-player 3.0.14 → 3.0.15
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 +2 -0
- package/CHANGELOG.md +39 -0
- package/CONTRIBUTORS +2 -0
- package/build/generateExterns.js +1 -1
- package/demo/common/assets.js +1 -1
- package/dist/controls.css +1 -1
- package/dist/demo.compiled.debug.js +1 -1
- package/dist/demo.compiled.js +1 -1
- package/dist/deps.js +1 -1
- package/dist/locales.js +34 -34
- package/dist/receiver.compiled.debug.js +1 -1
- package/dist/receiver.compiled.js +1 -1
- package/dist/shaka-player.compiled.d.ts +1 -1
- package/dist/shaka-player.compiled.debug.d.ts +1 -1
- package/dist/shaka-player.compiled.debug.externs.js +1 -1
- package/dist/shaka-player.compiled.debug.js +181 -178
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +1 -1
- package/dist/shaka-player.compiled.js +688 -686
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +1 -1
- package/dist/shaka-player.ui.debug.d.ts +1 -1
- package/dist/shaka-player.ui.debug.externs.js +3 -3
- package/dist/shaka-player.ui.debug.js +169 -166
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +3 -3
- package/dist/shaka-player.ui.js +815 -813
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/fairplay.md +1 -1
- package/karma.conf.js +1 -0
- package/lib/ads/client_side_ad_manager.js +46 -86
- package/lib/ads/server_side_ad_manager.js +7 -10
- package/lib/cast/cast_proxy.js +3 -2
- package/lib/cast/cast_receiver.js +6 -2
- package/lib/cast/cast_sender.js +2 -2
- package/lib/dash/dash_parser.js +52 -0
- package/lib/dash/segment_list.js +1 -1
- package/lib/dash/segment_template.js +1 -1
- package/lib/hls/hls_parser.js +25 -10
- package/lib/media/drm_engine.js +7 -0
- package/lib/media/gap_jumping_controller.js +4 -2
- package/lib/media/streaming_engine.js +2 -1
- package/lib/net/networking_engine.js +2 -1
- package/lib/player.js +18 -17
- package/lib/polyfill/patchedmediakeys_apple.js +4 -4
- package/lib/polyfill/patchedmediakeys_ms.js +4 -4
- package/lib/polyfill/patchedmediakeys_webkit.js +4 -4
- package/lib/text/mp4_ttml_parser.js +3 -0
- package/lib/text/ttml_text_parser.js +5 -0
- package/lib/text/ui_text_displayer.js +6 -1
- package/lib/util/fake_event.js +53 -7
- package/lib/util/periods.js +4 -1
- package/package.json +1 -1
- package/test/cast/cast_proxy_unit.js +12 -6
- package/test/cast/cast_utils_unit.js +1 -1
- package/test/dash/dash_parser_manifest_unit.js +73 -0
- package/test/hls/hls_parser_unit.js +5 -1
- package/test/media/drm_engine_unit.js +43 -0
- package/test/player_integration.js +38 -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/util/fake_ad_manager.js +1 -1
- package/test/text/ttml_text_parser_unit.js +9 -0
- package/test/text/ui_text_displayer_unit.js +18 -0
- package/test/ui/localization_unit.js +15 -14
- package/ui/cast_button.js +2 -3
- package/ui/controls.js +4 -6
- package/ui/locales/ru.json +6 -6
- package/ui/localization.js +28 -51
- package/ui/pip_button.js +2 -3
package/lib/player.js
CHANGED
|
@@ -570,7 +570,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
570
570
|
enterNode: (node, has, wants) => {
|
|
571
571
|
this.dispatchEvent(this.makeEvent_(
|
|
572
572
|
/* name= */ shaka.Player.EventName.OnStateChange,
|
|
573
|
-
/* data= */
|
|
573
|
+
/* data= */ (new Map()).set('state', node.name)));
|
|
574
574
|
|
|
575
575
|
const action = actions.get(node);
|
|
576
576
|
return action(has, wants);
|
|
@@ -601,7 +601,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
601
601
|
onIdle: (node) => {
|
|
602
602
|
this.dispatchEvent(this.makeEvent_(
|
|
603
603
|
/* name= */ shaka.Player.EventName.OnStateIdle,
|
|
604
|
-
/* data= */
|
|
604
|
+
/* data= */ (new Map()).set('state', node.name)));
|
|
605
605
|
},
|
|
606
606
|
};
|
|
607
607
|
|
|
@@ -621,7 +621,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
621
621
|
|
|
622
622
|
/**
|
|
623
623
|
* @param {!shaka.Player.EventName} name
|
|
624
|
-
* @param {Object
|
|
624
|
+
* @param {Map.<string, Object>=} data
|
|
625
625
|
* @return {!shaka.util.FakeEvent}
|
|
626
626
|
* @private
|
|
627
627
|
*/
|
|
@@ -1111,6 +1111,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1111
1111
|
'mpg': 'video/mpeg',
|
|
1112
1112
|
'mpeg': 'video/mpeg',
|
|
1113
1113
|
'm3u8': 'application/x-mpegurl',
|
|
1114
|
+
'mpd': 'application/dash+xml',
|
|
1114
1115
|
'mp3': 'audio/mpeg',
|
|
1115
1116
|
'aac': 'audio/aac',
|
|
1116
1117
|
'flac': 'audio/flac',
|
|
@@ -2342,12 +2343,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2342
2343
|
goog.asserts.assert(!endTime || startTime <= endTime,
|
|
2343
2344
|
'Metadata start time should be less or equal to the end time!');
|
|
2344
2345
|
const eventName = shaka.Player.EventName.Metadata;
|
|
2345
|
-
const data =
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
};
|
|
2346
|
+
const data = new Map()
|
|
2347
|
+
.set('startTime', startTime)
|
|
2348
|
+
.set('endTime', endTime)
|
|
2349
|
+
.set('metadataType', metadataType)
|
|
2350
|
+
.set('payload', payload);
|
|
2351
2351
|
this.dispatchEvent(this.makeEvent_(eventName, data));
|
|
2352
2352
|
}
|
|
2353
2353
|
|
|
@@ -4268,7 +4268,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4268
4268
|
// Surface the buffering event so that the app knows if/when we are
|
|
4269
4269
|
// buffering.
|
|
4270
4270
|
const eventName = shaka.Player.EventName.Buffering;
|
|
4271
|
-
|
|
4271
|
+
const data = (new Map()).set('buffering', isBuffering);
|
|
4272
|
+
this.dispatchEvent(this.makeEvent_(eventName, data));
|
|
4272
4273
|
}
|
|
4273
4274
|
|
|
4274
4275
|
/**
|
|
@@ -4623,10 +4624,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4623
4624
|
|
|
4624
4625
|
/** @private */
|
|
4625
4626
|
onAbrStatusChanged_() {
|
|
4626
|
-
const
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
this.delayDispatchEvent_(event);
|
|
4627
|
+
const data = (new Map()).set('newStatus', this.config_.abr.enabled);
|
|
4628
|
+
this.delayDispatchEvent_(this.makeEvent_(
|
|
4629
|
+
shaka.Player.EventName.AbrStatusChanged, data));
|
|
4630
4630
|
}
|
|
4631
4631
|
|
|
4632
4632
|
/**
|
|
@@ -4643,7 +4643,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4643
4643
|
}
|
|
4644
4644
|
|
|
4645
4645
|
const eventName = shaka.Player.EventName.Error;
|
|
4646
|
-
const event = this.makeEvent_(eventName,
|
|
4646
|
+
const event = this.makeEvent_(eventName, (new Map()).set('detail', error));
|
|
4647
4647
|
this.dispatchEvent(event);
|
|
4648
4648
|
if (event.defaultPrevented) {
|
|
4649
4649
|
error.handled = true;
|
|
@@ -4672,7 +4672,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4672
4672
|
eventElement: region.eventElement,
|
|
4673
4673
|
};
|
|
4674
4674
|
|
|
4675
|
-
|
|
4675
|
+
const data = (new Map()).set('detail', clone);
|
|
4676
|
+
this.dispatchEvent(this.makeEvent_(eventName, data));
|
|
4676
4677
|
}
|
|
4677
4678
|
|
|
4678
4679
|
/**
|
|
@@ -5472,7 +5473,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
5472
5473
|
* @define {string} A version number taken from git at compile time.
|
|
5473
5474
|
* @export
|
|
5474
5475
|
*/
|
|
5475
|
-
shaka.Player.version = 'v3.0.
|
|
5476
|
+
shaka.Player.version = 'v3.0.15-uncompiled';
|
|
5476
5477
|
|
|
5477
5478
|
// Initialize the deprecation system using the version string we just set
|
|
5478
5479
|
// on the player.
|
|
@@ -567,10 +567,10 @@ class extends shaka.util.FakeEventTarget {
|
|
|
567
567
|
|
|
568
568
|
const isNew = this.keyStatuses.getStatus() == undefined;
|
|
569
569
|
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
570
|
+
const data = new Map()
|
|
571
|
+
.set('messageType', isNew ? 'license-request' : 'license-renewal')
|
|
572
|
+
.set('message', shaka.util.BufferUtils.toArrayBuffer(event.message));
|
|
573
|
+
const event2 = new shaka.util.FakeEvent('message', data);
|
|
574
574
|
|
|
575
575
|
this.dispatchEvent(event2);
|
|
576
576
|
}
|
|
@@ -519,10 +519,10 @@ class extends shaka.util.FakeEventTarget {
|
|
|
519
519
|
|
|
520
520
|
const isNew = this.keyStatuses.getStatus() == undefined;
|
|
521
521
|
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
522
|
+
const data = new Map()
|
|
523
|
+
.set('messageType', isNew ? 'license-request' : 'license-renewal')
|
|
524
|
+
.set('message', shaka.util.BufferUtils.toArrayBuffer(event.message));
|
|
525
|
+
const event2 = new shaka.util.FakeEvent('message', data);
|
|
526
526
|
|
|
527
527
|
this.dispatchEvent(event2);
|
|
528
528
|
}
|
|
@@ -454,10 +454,10 @@ shaka.polyfill.PatchedMediaKeysWebkit.MediaKeys = class {
|
|
|
454
454
|
|
|
455
455
|
const isNew = session.keyStatuses.getStatus() == undefined;
|
|
456
456
|
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
457
|
+
const data = new Map()
|
|
458
|
+
.set('messageType', isNew ? 'licenserequest' : 'licenserenewal')
|
|
459
|
+
.set('message', event.message);
|
|
460
|
+
const event2 = new shaka.util.FakeEvent('message', data);
|
|
461
461
|
|
|
462
462
|
session.generated();
|
|
463
463
|
session.dispatchEvent(event2);
|
|
@@ -88,6 +88,9 @@ shaka.text.Mp4TtmlParser = class {
|
|
|
88
88
|
|
|
89
89
|
shaka.text.TextEngine.registerParser(
|
|
90
90
|
'application/mp4; codecs="stpp"', () => new shaka.text.Mp4TtmlParser());
|
|
91
|
+
shaka.text.TextEngine.registerParser(
|
|
92
|
+
'application/mp4; codecs="stpp.ttml"',
|
|
93
|
+
() => new shaka.text.Mp4TtmlParser());
|
|
91
94
|
shaka.text.TextEngine.registerParser(
|
|
92
95
|
'application/mp4; codecs="stpp.ttml.im1t"',
|
|
93
96
|
() => new shaka.text.Mp4TtmlParser());
|
|
@@ -277,6 +277,11 @@ shaka.text.TtmlTextParser = class {
|
|
|
277
277
|
!cueElement.hasAttribute('begin') &&
|
|
278
278
|
!cueElement.hasAttribute('end');
|
|
279
279
|
|
|
280
|
+
if (cueElement.nodeType == Node.COMMENT_NODE) {
|
|
281
|
+
// The comments do not contain information that interests us here.
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
|
|
280
285
|
if (
|
|
281
286
|
cueElement.nodeType != Node.ELEMENT_NODE ||
|
|
282
287
|
/* Disregards empty elements without time attributes nor content
|
|
@@ -156,9 +156,14 @@ shaka.text.UITextDisplayer = class {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
// Remove the cues out of the time range.
|
|
159
|
+
const oldNumCues = this.cues_.length;
|
|
159
160
|
this.cues_ = this.cues_.filter(
|
|
160
161
|
(cue) => cue.startTime < start || cue.endTime >= end);
|
|
161
|
-
this
|
|
162
|
+
// If anything was actually removed in this process, force the captions to
|
|
163
|
+
// update. This makes sure that the currently-displayed cues will stop
|
|
164
|
+
// displaying if removed (say, due to the user changing languages).
|
|
165
|
+
const forceUpdate = oldNumCues > this.cues_.length;
|
|
166
|
+
this.updateCaptions_(forceUpdate);
|
|
162
167
|
|
|
163
168
|
return true;
|
|
164
169
|
}
|
package/lib/util/fake_event.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
goog.provide('shaka.util.FakeEvent');
|
|
8
8
|
|
|
9
|
+
goog.require('goog.asserts');
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
13
|
* @summary Create an Event work-alike object based on the provided dictionary.
|
|
@@ -16,18 +18,62 @@ goog.provide('shaka.util.FakeEvent');
|
|
|
16
18
|
*/
|
|
17
19
|
shaka.util.FakeEvent = class {
|
|
18
20
|
/**
|
|
19
|
-
* @param {
|
|
20
|
-
* @
|
|
21
|
+
* @param {!Event} event
|
|
22
|
+
* @return {!shaka.util.FakeEvent}
|
|
21
23
|
*/
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
for (const key in
|
|
25
|
-
Object.defineProperty(
|
|
26
|
-
value:
|
|
24
|
+
static fromRealEvent(event) {
|
|
25
|
+
const fakeEvent = new shaka.util.FakeEvent(event.type);
|
|
26
|
+
for (const key in event) {
|
|
27
|
+
Object.defineProperty(fakeEvent, key, {
|
|
28
|
+
value: event[key],
|
|
27
29
|
writable: true,
|
|
28
30
|
enumerable: true,
|
|
29
31
|
});
|
|
30
32
|
}
|
|
33
|
+
return fakeEvent;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Allows us to tell the compiler that the dictionary "map" is actually a
|
|
38
|
+
* generic object, for backwards compatibility.
|
|
39
|
+
* @param {!Map.<string, Object>} dict
|
|
40
|
+
* @return {!Object}
|
|
41
|
+
* @suppress {invalidCasts}
|
|
42
|
+
* @private
|
|
43
|
+
*/
|
|
44
|
+
static recastDictAsObject_(dict) {
|
|
45
|
+
goog.asserts.assert(!(dict instanceof Map), 'dict should not be a map');
|
|
46
|
+
return /** @type {!Object} */ (dict);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* @param {string} type
|
|
51
|
+
* @param {Map.<string, Object>=} dict
|
|
52
|
+
*/
|
|
53
|
+
constructor(type, dict) {
|
|
54
|
+
if (dict) {
|
|
55
|
+
if (dict instanceof Map) {
|
|
56
|
+
// Take properties from dict if present.
|
|
57
|
+
for (const key of dict.keys()) {
|
|
58
|
+
Object.defineProperty(this, key, {
|
|
59
|
+
value: dict.get(key),
|
|
60
|
+
writable: true,
|
|
61
|
+
enumerable: true,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
} else {
|
|
65
|
+
// For backwards compatibility with external apps that may make use of
|
|
66
|
+
// this public constructor, this should still accept generic objects.
|
|
67
|
+
const obj = shaka.util.FakeEvent.recastDictAsObject_(dict);
|
|
68
|
+
for (const key in obj) {
|
|
69
|
+
Object.defineProperty(this, key, {
|
|
70
|
+
value: obj[key],
|
|
71
|
+
writable: true,
|
|
72
|
+
enumerable: true,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
31
77
|
|
|
32
78
|
// The properties below cannot be set by the dict. They are all provided
|
|
33
79
|
// for compatibility with native events.
|
package/lib/util/periods.js
CHANGED
|
@@ -75,7 +75,10 @@ shaka.util.PeriodCombiner = class {
|
|
|
75
75
|
|
|
76
76
|
/** @return {!Array.<shaka.extern.Stream>} */
|
|
77
77
|
getTextStreams() {
|
|
78
|
-
|
|
78
|
+
// Return a copy of the array because makeTextStreamsForClosedCaptions
|
|
79
|
+
// may make changes to the contents of the array. Those changes should not
|
|
80
|
+
// propagate back to the PeriodCombiner.
|
|
81
|
+
return this.textStreams_.slice();
|
|
79
82
|
}
|
|
80
83
|
|
|
81
84
|
/**
|
package/package.json
CHANGED
|
@@ -297,7 +297,8 @@ describe('CastProxy', () => {
|
|
|
297
297
|
'timeupdate', Util.spyFunc(proxyListener));
|
|
298
298
|
|
|
299
299
|
expect(proxyListener).not.toHaveBeenCalled();
|
|
300
|
-
const fakeEvent = new FakeEvent(
|
|
300
|
+
const fakeEvent = new FakeEvent(
|
|
301
|
+
'timeupdate', (new Map()).set('detail', 8675309));
|
|
301
302
|
mockVideo.on['timeupdate'](fakeEvent);
|
|
302
303
|
expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
|
|
303
304
|
type: 'timeupdate',
|
|
@@ -315,7 +316,8 @@ describe('CastProxy', () => {
|
|
|
315
316
|
mockSender.hasRemoteProperties.and.returnValue(true);
|
|
316
317
|
|
|
317
318
|
expect(proxyListener).not.toHaveBeenCalled();
|
|
318
|
-
const fakeEvent = new FakeEvent(
|
|
319
|
+
const fakeEvent = new FakeEvent(
|
|
320
|
+
'timeupdate', (new Map()).set('detail', 8675309));
|
|
319
321
|
mockVideo.on['timeupdate'](fakeEvent);
|
|
320
322
|
expect(proxyListener).not.toHaveBeenCalled();
|
|
321
323
|
});
|
|
@@ -332,7 +334,8 @@ describe('CastProxy', () => {
|
|
|
332
334
|
mockSender.hasRemoteProperties.and.returnValue(true);
|
|
333
335
|
|
|
334
336
|
expect(proxyListener).not.toHaveBeenCalled();
|
|
335
|
-
const fakeEvent = new FakeEvent(
|
|
337
|
+
const fakeEvent = new FakeEvent(
|
|
338
|
+
'timeupdate', (new Map()).set('detail', 8675309));
|
|
336
339
|
mockSender.onRemoteEvent('video', fakeEvent);
|
|
337
340
|
expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
|
|
338
341
|
type: 'timeupdate',
|
|
@@ -446,7 +449,8 @@ describe('CastProxy', () => {
|
|
|
446
449
|
'buffering', Util.spyFunc(proxyListener));
|
|
447
450
|
|
|
448
451
|
expect(proxyListener).not.toHaveBeenCalled();
|
|
449
|
-
const fakeEvent = new FakeEvent(
|
|
452
|
+
const fakeEvent = new FakeEvent(
|
|
453
|
+
'buffering', (new Map()).set('detail', 8675309));
|
|
450
454
|
mockPlayer.listeners['buffering'](fakeEvent);
|
|
451
455
|
expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
|
|
452
456
|
type: 'buffering',
|
|
@@ -464,7 +468,8 @@ describe('CastProxy', () => {
|
|
|
464
468
|
mockSender.hasRemoteProperties.and.returnValue(true);
|
|
465
469
|
|
|
466
470
|
expect(proxyListener).not.toHaveBeenCalled();
|
|
467
|
-
const fakeEvent = new FakeEvent(
|
|
471
|
+
const fakeEvent = new FakeEvent(
|
|
472
|
+
'buffering', (new Map()).set('detail', 8675309));
|
|
468
473
|
mockPlayer.listeners['buffering'](fakeEvent);
|
|
469
474
|
expect(proxyListener).not.toHaveBeenCalled();
|
|
470
475
|
});
|
|
@@ -481,7 +486,8 @@ describe('CastProxy', () => {
|
|
|
481
486
|
mockSender.hasRemoteProperties.and.returnValue(true);
|
|
482
487
|
|
|
483
488
|
expect(proxyListener).not.toHaveBeenCalled();
|
|
484
|
-
const fakeEvent = new FakeEvent(
|
|
489
|
+
const fakeEvent = new FakeEvent(
|
|
490
|
+
'buffering', (new Map()).set('detail', 8675309));
|
|
485
491
|
mockSender.onRemoteEvent('player', fakeEvent);
|
|
486
492
|
expect(proxyListener).toHaveBeenCalledWith(jasmine.objectContaining({
|
|
487
493
|
type: 'buffering',
|
|
@@ -129,7 +129,7 @@ describe('CastUtils', () => {
|
|
|
129
129
|
expect(typeof deserialized).toBe('object');
|
|
130
130
|
|
|
131
131
|
// The object can be used to construct a FakeEvent.
|
|
132
|
-
const fakeEvent =
|
|
132
|
+
const fakeEvent = FakeEvent.fromRealEvent(deserialized);
|
|
133
133
|
|
|
134
134
|
// The fake event has the same type and properties as the original.
|
|
135
135
|
const asObj = /** @type {!Object} */ (fakeEvent);
|
|
@@ -1796,4 +1796,77 @@ describe('DashParser Manifest', () => {
|
|
|
1796
1796
|
expect(uri).not.toContain('/p2/');
|
|
1797
1797
|
}
|
|
1798
1798
|
});
|
|
1799
|
+
|
|
1800
|
+
/**
|
|
1801
|
+
* @param {!Array.<number>} periods Start time of multiple periods
|
|
1802
|
+
* @return {string}
|
|
1803
|
+
*/
|
|
1804
|
+
function buildManifestWithPeriodStartTime(periods) {
|
|
1805
|
+
const mpdTemplate = [
|
|
1806
|
+
`<MPD type="dynamic"`,
|
|
1807
|
+
'availabilityStartTime="1970-01-01T00:00:00Z"',
|
|
1808
|
+
'timeShiftBufferDepth="PT10H">',
|
|
1809
|
+
' %(periods)s',
|
|
1810
|
+
'</MPD>',
|
|
1811
|
+
].join('\n');
|
|
1812
|
+
const periodTemplate = (id, period, duration) => {
|
|
1813
|
+
return [
|
|
1814
|
+
` <Period id="${id}" start="PT${period}S">`,
|
|
1815
|
+
' <AdaptationSet mimeType="video/mp4" lang="en" group="1">',
|
|
1816
|
+
' <SegmentTemplate startNumber="1" media="l-$Number$.mp4">',
|
|
1817
|
+
' <SegmentTimeline>',
|
|
1818
|
+
` <S t="0" d="${duration}" />`,
|
|
1819
|
+
' </SegmentTimeline>',
|
|
1820
|
+
' </SegmentTemplate>',
|
|
1821
|
+
' <Representation id="1"/>',
|
|
1822
|
+
' </AdaptationSet>',
|
|
1823
|
+
' </Period>',
|
|
1824
|
+
].join('\n');
|
|
1825
|
+
};
|
|
1826
|
+
const periodXmls = periods.map((period, i) => {
|
|
1827
|
+
const duration = i+1 === periods.length ? 10 : periods[i+1] - period;
|
|
1828
|
+
// Period start time as ID here. If we use index then there will be
|
|
1829
|
+
// periods with same period ID and different start time which are invalid.
|
|
1830
|
+
return periodTemplate(period, period, duration);
|
|
1831
|
+
});
|
|
1832
|
+
return sprintf(mpdTemplate, {
|
|
1833
|
+
periods: periodXmls.join('\n'),
|
|
1834
|
+
});
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
// Bug description: Inconsistent period start time in the manifests due
|
|
1838
|
+
// to failover triggered in backend servers
|
|
1839
|
+
|
|
1840
|
+
// When one of the servers is down, the manifest will be served by other
|
|
1841
|
+
// redundant servers. The period start time might become out of sync
|
|
1842
|
+
// during the switch-over/recovery.
|
|
1843
|
+
|
|
1844
|
+
it('skip periods that are earlier than max period start time', async () => {
|
|
1845
|
+
const sources = [
|
|
1846
|
+
buildManifestWithPeriodStartTime([5, 15]),
|
|
1847
|
+
buildManifestWithPeriodStartTime([6, 15]), // simulate out-of-sync of +1s
|
|
1848
|
+
buildManifestWithPeriodStartTime([4, 15]), // simulate out-of-sync of -1s
|
|
1849
|
+
];
|
|
1850
|
+
const segments = [];
|
|
1851
|
+
|
|
1852
|
+
for (const source of sources) {
|
|
1853
|
+
fakeNetEngine.setResponseText('dummy://foo', source);
|
|
1854
|
+
/** @type {shaka.extern.Manifest} */
|
|
1855
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1856
|
+
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
1857
|
+
const video = manifest.variants[0].video;
|
|
1858
|
+
// eslint-disable-next-line no-await-in-loop
|
|
1859
|
+
await video.createSegmentIndex();
|
|
1860
|
+
goog.asserts.assert(video.segmentIndex, 'Null segmentIndex!');
|
|
1861
|
+
segments.push(Array.from(video.segmentIndex));
|
|
1862
|
+
}
|
|
1863
|
+
|
|
1864
|
+
// Expect identical segments
|
|
1865
|
+
expect(segments[0].length).toBe(2);
|
|
1866
|
+
expect(segments[1].length).toBe(2);
|
|
1867
|
+
expect(segments[0][0].startTime).toBe(5);
|
|
1868
|
+
expect(segments[1][0].startTime).toBe(5);
|
|
1869
|
+
expect(segments[0][1].startTime).toBe(15);
|
|
1870
|
+
expect(segments[1][1].startTime).toBe(15);
|
|
1871
|
+
});
|
|
1799
1872
|
});
|
|
@@ -776,7 +776,7 @@ describe('HlsParser', () => {
|
|
|
776
776
|
'RESOLUTION=960x540,FRAME-RATE=120,AUDIO="aud2"\n',
|
|
777
777
|
'video2\n',
|
|
778
778
|
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
|
779
|
-
'URI="audio"\n',
|
|
779
|
+
'DEFAULT=YES,URI="audio"\n',
|
|
780
780
|
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud2",LANGUAGE="fr",',
|
|
781
781
|
'URI="audio2"\n',
|
|
782
782
|
].join('');
|
|
@@ -794,6 +794,7 @@ describe('HlsParser', () => {
|
|
|
794
794
|
manifest.anyTimeline();
|
|
795
795
|
manifest.addPartialVariant((variant) => {
|
|
796
796
|
variant.bandwidth = 200;
|
|
797
|
+
variant.primary = true;
|
|
797
798
|
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
798
799
|
stream.size(960, 540);
|
|
799
800
|
});
|
|
@@ -803,6 +804,7 @@ describe('HlsParser', () => {
|
|
|
803
804
|
});
|
|
804
805
|
manifest.addPartialVariant((variant) => {
|
|
805
806
|
variant.bandwidth = 300;
|
|
807
|
+
variant.primary = false;
|
|
806
808
|
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
807
809
|
stream.size(960, 540);
|
|
808
810
|
});
|
|
@@ -1211,6 +1213,8 @@ describe('HlsParser', () => {
|
|
|
1211
1213
|
// stream.
|
|
1212
1214
|
const timeline = actual.presentationTimeline;
|
|
1213
1215
|
expect(timeline.getDuration()).toBe(10);
|
|
1216
|
+
expect(timeline.getSeekRangeStart()).toBe(0);
|
|
1217
|
+
expect(timeline.getSeekRangeEnd()).toBe(10);
|
|
1214
1218
|
|
|
1215
1219
|
expect(actual.textStreams.length).toBe(1);
|
|
1216
1220
|
expect(actual.variants.length).toBe(1);
|
|
@@ -1954,6 +1954,49 @@ describe('DrmEngine', () => {
|
|
|
1954
1954
|
[drmInfoAudio]);
|
|
1955
1955
|
expect(returned).toEqual([drmInfoDesired]);
|
|
1956
1956
|
});
|
|
1957
|
+
|
|
1958
|
+
it('dedupes the merged init data based on keyId matching', () => {
|
|
1959
|
+
const serverCert = new Uint8Array(0);
|
|
1960
|
+
const drmInfoVideo = {
|
|
1961
|
+
keySystem: 'drm.abc',
|
|
1962
|
+
licenseServerUri: 'http://abc.drm/license',
|
|
1963
|
+
distinctiveIdentifierRequired: false,
|
|
1964
|
+
persistentStateRequired: true,
|
|
1965
|
+
videoRobustness: 'really_really_ridiculously_good',
|
|
1966
|
+
serverCertificate: serverCert,
|
|
1967
|
+
serverCertificateUri: '',
|
|
1968
|
+
initData: [{keyId: 'v-init'}],
|
|
1969
|
+
keyIds: new Set(['deadbeefdeadbeefdeadbeefdeadbeef']),
|
|
1970
|
+
};
|
|
1971
|
+
const drmInfoAudio = {
|
|
1972
|
+
keySystem: 'drm.abc',
|
|
1973
|
+
licenseServerUri: undefined,
|
|
1974
|
+
distinctiveIdentifierRequired: true,
|
|
1975
|
+
persistentStateRequired: false,
|
|
1976
|
+
audioRobustness: 'good',
|
|
1977
|
+
serverCertificate: undefined,
|
|
1978
|
+
serverCertificateUri: '',
|
|
1979
|
+
initData: [{keyId: 'v-init'}, {keyId: 'a-init'}],
|
|
1980
|
+
keyIds: new Set(['eadbeefdeadbeefdeadbeefdeadbeefd']),
|
|
1981
|
+
};
|
|
1982
|
+
const drmInfoDesired = {
|
|
1983
|
+
keySystem: 'drm.abc',
|
|
1984
|
+
licenseServerUri: 'http://abc.drm/license',
|
|
1985
|
+
distinctiveIdentifierRequired: true,
|
|
1986
|
+
persistentStateRequired: true,
|
|
1987
|
+
audioRobustness: 'good',
|
|
1988
|
+
videoRobustness: 'really_really_ridiculously_good',
|
|
1989
|
+
serverCertificate: serverCert,
|
|
1990
|
+
initData: [{keyId: 'v-init'}, {keyId: 'a-init'}],
|
|
1991
|
+
keyIds: new Set([
|
|
1992
|
+
'deadbeefdeadbeefdeadbeefdeadbeef',
|
|
1993
|
+
'eadbeefdeadbeefdeadbeefdeadbeefd',
|
|
1994
|
+
]),
|
|
1995
|
+
};
|
|
1996
|
+
const returned = shaka.media.DrmEngine.getCommonDrmInfos([drmInfoVideo],
|
|
1997
|
+
[drmInfoAudio]);
|
|
1998
|
+
expect(returned).toEqual([drmInfoDesired]);
|
|
1999
|
+
});
|
|
1957
2000
|
}); // describe('getCommonDrmInfos')
|
|
1958
2001
|
|
|
1959
2002
|
describe('configure', () => {
|
|
@@ -649,6 +649,44 @@ describe('Player', () => {
|
|
|
649
649
|
});
|
|
650
650
|
});
|
|
651
651
|
|
|
652
|
+
describe('seek range', () => {
|
|
653
|
+
// Regression test for Issue #3675.
|
|
654
|
+
it('has a reasonable seek range after going from live to VOD', async () => {
|
|
655
|
+
const netEngine = player.getNetworkingEngine();
|
|
656
|
+
const startTime = Date.now();
|
|
657
|
+
netEngine.registerRequestFilter((type, request) => {
|
|
658
|
+
if (type != shaka.net.NetworkingEngine.RequestType.MANIFEST) {
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
// Simulate a live stream by providing different manifests over time.
|
|
662
|
+
const time = (Date.now() - startTime) / 1000;
|
|
663
|
+
const manifestNumber = Math.min(5, Math.floor(0.5 + time / 2));
|
|
664
|
+
request.uris = [
|
|
665
|
+
'/base/test/test/assets/3675/dash_' + manifestNumber + '.mpd',
|
|
666
|
+
];
|
|
667
|
+
console.log('getting manifest', request.uris);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
// Play the stream.
|
|
671
|
+
await player.load('/base/test/test/assets/3675/dash_0.mpd');
|
|
672
|
+
video.play();
|
|
673
|
+
|
|
674
|
+
// Wait for the stream to be over.
|
|
675
|
+
eventManager.listen(player, 'error', Util.spyFunc(onErrorSpy));
|
|
676
|
+
/** @type {shaka.test.Waiter} */
|
|
677
|
+
const waiter = new shaka.test.Waiter(eventManager)
|
|
678
|
+
.timeoutAfter(20).failOnTimeout(true);
|
|
679
|
+
await waiter.waitForEnd(video);
|
|
680
|
+
|
|
681
|
+
// The stream should have transitioned to VOD by now.
|
|
682
|
+
expect(player.isLive()).toBe(false);
|
|
683
|
+
|
|
684
|
+
// Check that the final seek range is as expected.
|
|
685
|
+
const seekRange = player.seekRange();
|
|
686
|
+
expect(seekRange.end).toBe(14);
|
|
687
|
+
});
|
|
688
|
+
});
|
|
689
|
+
|
|
652
690
|
describe('buffering', () => {
|
|
653
691
|
const startBuffering = jasmine.objectContaining({buffering: true});
|
|
654
692
|
const endBuffering = jasmine.objectContaining({buffering: false});
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
3
|
+
xmlns="urn:mpeg:dash:schema:mpd:2011"
|
|
4
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
5
|
+
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
|
|
6
|
+
profiles="urn:mpeg:dash:profile:isoff-live:2011"
|
|
7
|
+
type="dynamic"
|
|
8
|
+
minimumUpdatePeriod="PT2S"
|
|
9
|
+
suggestedPresentationDelay="PT2S"
|
|
10
|
+
availabilityStartTime="2021-10-28T07:36:27.755Z"
|
|
11
|
+
publishTime="2021-10-28T07:36:29.744Z"
|
|
12
|
+
timeShiftBufferDepth="PT10.0S"
|
|
13
|
+
maxSegmentDuration="PT2.0S"
|
|
14
|
+
minBufferTime="PT4.0S">
|
|
15
|
+
<ProgramInformation>
|
|
16
|
+
</ProgramInformation>
|
|
17
|
+
<ServiceDescription id="0">
|
|
18
|
+
</ServiceDescription>
|
|
19
|
+
<Period id="0" start="PT0.0S">
|
|
20
|
+
<AdaptationSet id="0" contentType="video" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" frameRate="30/1" maxWidth="1280" maxHeight="720" par="16:9">
|
|
21
|
+
<Representation id="0" mimeType="video/mp4" codecs="avc1.64001f" bandwidth="122012" width="1280" height="720" sar="1:1">
|
|
22
|
+
<SegmentTemplate timescale="15360" initialization="init-stream$RepresentationID$.m4s" media="chunk-stream$RepresentationID$-$Number%05d$.m4s" startNumber="1">
|
|
23
|
+
<SegmentTimeline>
|
|
24
|
+
<S t="0" d="30720" />
|
|
25
|
+
</SegmentTimeline>
|
|
26
|
+
</SegmentTemplate>
|
|
27
|
+
</Representation>
|
|
28
|
+
</AdaptationSet>
|
|
29
|
+
</Period>
|
|
30
|
+
</MPD>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
+
<MPD xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
|
3
|
+
xmlns="urn:mpeg:dash:schema:mpd:2011"
|
|
4
|
+
xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
5
|
+
xsi:schemaLocation="urn:mpeg:DASH:schema:MPD:2011 http://standards.iso.org/ittf/PubliclyAvailableStandards/MPEG-DASH_schema_files/DASH-MPD.xsd"
|
|
6
|
+
profiles="urn:mpeg:dash:profile:isoff-live:2011"
|
|
7
|
+
type="dynamic"
|
|
8
|
+
minimumUpdatePeriod="PT2S"
|
|
9
|
+
suggestedPresentationDelay="PT2S"
|
|
10
|
+
availabilityStartTime="2021-10-28T07:36:27.755Z"
|
|
11
|
+
publishTime="2021-10-28T07:36:31.750Z"
|
|
12
|
+
timeShiftBufferDepth="PT10.0S"
|
|
13
|
+
maxSegmentDuration="PT2.0S"
|
|
14
|
+
minBufferTime="PT4.0S">
|
|
15
|
+
<ProgramInformation>
|
|
16
|
+
</ProgramInformation>
|
|
17
|
+
<ServiceDescription id="0">
|
|
18
|
+
</ServiceDescription>
|
|
19
|
+
<Period id="0" start="PT0.0S">
|
|
20
|
+
<AdaptationSet id="0" contentType="video" startWithSAP="1" segmentAlignment="true" bitstreamSwitching="true" frameRate="30/1" maxWidth="1280" maxHeight="720" par="16:9">
|
|
21
|
+
<Representation id="0" mimeType="video/mp4" codecs="avc1.64001f" bandwidth="122012" width="1280" height="720" sar="1:1">
|
|
22
|
+
<SegmentTemplate timescale="15360" initialization="init-stream$RepresentationID$.m4s" media="chunk-stream$RepresentationID$-$Number%05d$.m4s" startNumber="1">
|
|
23
|
+
<SegmentTimeline>
|
|
24
|
+
<S t="0" d="30720" r="1" />
|
|
25
|
+
</SegmentTimeline>
|
|
26
|
+
</SegmentTemplate>
|
|
27
|
+
</Representation>
|
|
28
|
+
</AdaptationSet>
|
|
29
|
+
</Period>
|
|
30
|
+
</MPD>
|