shaka-player 4.2.3 → 4.2.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 +2 -0
- package/CHANGELOG.md +39 -0
- package/CONTRIBUTORS +2 -0
- package/demo/index.html +1 -1
- package/dist/demo.compiled.debug.js +64 -64
- package/dist/demo.compiled.debug.map +2 -2
- package/dist/demo.compiled.js +64 -64
- package/dist/demo.compiled.map +2 -2
- package/dist/deps.js +5 -4
- package/dist/locales.js +32 -32
- package/dist/receiver.compiled.debug.js +12 -12
- package/dist/receiver.compiled.debug.map +2 -2
- package/dist/receiver.compiled.js +12 -12
- package/dist/receiver.compiled.map +2 -2
- package/dist/shaka-player.compiled.d.ts +15 -1
- package/dist/shaka-player.compiled.debug.d.ts +15 -1
- package/dist/shaka-player.compiled.debug.externs.js +95 -80
- package/dist/shaka-player.compiled.debug.js +583 -573
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +95 -80
- package/dist/shaka-player.compiled.js +823 -820
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +15 -1
- package/dist/shaka-player.ui.debug.d.ts +15 -1
- package/dist/shaka-player.ui.debug.externs.js +95 -80
- package/dist/shaka-player.ui.debug.js +612 -602
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +95 -80
- package/dist/shaka-player.ui.js +963 -960
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/ui.md +7 -7
- package/docs/tutorials/welcome.md +2 -2
- package/externs/mux.js +3 -0
- package/externs/shaka/player.js +2 -1
- package/lib/cast/cast_proxy.js +2 -1
- package/lib/cast/cast_receiver.js +19 -5
- package/lib/cast/cast_sender.js +2 -1
- package/lib/cast/cast_utils.js +16 -6
- package/lib/cea/cea708_window.js +2 -2
- package/lib/dash/segment_template.js +1 -1
- package/lib/hls/hls_parser.js +21 -4
- package/lib/media/content_workarounds.js +12 -0
- package/lib/media/media_source_engine.js +6 -0
- package/lib/media/playhead.js +23 -1
- package/lib/media/streaming_engine.js +3 -12
- package/lib/media/transmuxer.js +6 -0
- package/lib/player.js +24 -11
- package/lib/polyfill/abort_controller.js +143 -0
- package/lib/polyfill/media_capabilities.js +59 -9
- package/lib/text/ttml_text_parser.js +32 -7
- package/lib/text/ui_text_displayer.js +22 -5
- package/lib/util/manifest_parser_utils.js +4 -0
- package/lib/util/platform.js +9 -1
- package/lib/util/player_configuration.js +10 -3
- package/lib/util/stream_utils.js +30 -10
- package/package.json +1 -1
- package/roadmap.md +24 -6
- package/shaka-player.uncompiled.js +1 -0
- package/test/cast/cast_receiver_integration.js +97 -16
- package/test/cast/cast_receiver_unit.js +17 -12
- package/test/cast/cast_utils_unit.js +1 -0
- package/test/dash/dash_parser_manifest_unit.js +1 -0
- package/test/hls/hls_parser_unit.js +1 -0
- package/test/media/drm_engine_unit.js +11 -1
- package/test/media/media_source_engine_integration.js +108 -1
- package/test/media/playhead_unit.js +41 -0
- package/test/media/streaming_engine_integration.js +13 -0
- package/test/media/streaming_engine_unit.js +36 -23
- package/test/player_unit.js +87 -0
- package/test/polyfill/media_capabilities_unit.js +261 -0
- package/test/test/util/simple_fakes.js +3 -0
- package/test/test/util/stream_generator.js +212 -8
- package/test/test/util/test_scheme.js +3 -2
- package/test/test/util/util.js +3 -2
- package/test/text/mp4_ttml_parser_unit.js +6 -4
- package/test/text/ttml_text_parser_unit.js +119 -86
- package/test/text/ui_text_displayer_unit.js +76 -0
- package/test/ui/text_displayer_layout_unit.js +14 -0
- package/test/util/stream_utils_unit.js +18 -0
- package/test/util/xml_utils_unit.js +6 -1
- package/ui/controls.js +49 -37
- package/ui/settings_menu.js +11 -2
|
@@ -34,6 +34,8 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
34
34
|
|
|
35
35
|
/** @type {shaka.util.PublicPromise} */
|
|
36
36
|
let messageWaitPromise;
|
|
37
|
+
/** @type {Array.<string>} */
|
|
38
|
+
let pendingMessages = null;
|
|
37
39
|
|
|
38
40
|
/** @type {!Array.<function()>} */
|
|
39
41
|
let toRestore;
|
|
@@ -91,6 +93,9 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
91
93
|
toRestore = [];
|
|
92
94
|
pendingWaitWrapperCalls = 0;
|
|
93
95
|
|
|
96
|
+
messageWaitPromise = null;
|
|
97
|
+
pendingMessages = null;
|
|
98
|
+
|
|
94
99
|
fakeInitState = {
|
|
95
100
|
player: {
|
|
96
101
|
configure: {},
|
|
@@ -118,6 +123,12 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
118
123
|
player = null;
|
|
119
124
|
video = null;
|
|
120
125
|
receiver = null;
|
|
126
|
+
|
|
127
|
+
if (messageWaitPromise) {
|
|
128
|
+
messageWaitPromise.resolve([]);
|
|
129
|
+
}
|
|
130
|
+
messageWaitPromise = null;
|
|
131
|
+
pendingMessages = null;
|
|
121
132
|
});
|
|
122
133
|
|
|
123
134
|
afterAll(() => {
|
|
@@ -128,6 +139,66 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
128
139
|
}
|
|
129
140
|
});
|
|
130
141
|
|
|
142
|
+
describe('without drm', () => {
|
|
143
|
+
it('sends reasonably-sized updates', async () => {
|
|
144
|
+
// Use an unencrypted asset.
|
|
145
|
+
fakeInitState.manifest = 'test:sintel';
|
|
146
|
+
|
|
147
|
+
const p = waitForLoadedData();
|
|
148
|
+
|
|
149
|
+
// Start the process of loading by sending a fake init message.
|
|
150
|
+
fakeConnectedSenders(1);
|
|
151
|
+
fakeIncomingMessage({
|
|
152
|
+
type: 'init',
|
|
153
|
+
initState: fakeInitState,
|
|
154
|
+
appData: {},
|
|
155
|
+
}, mockShakaMessageBus);
|
|
156
|
+
|
|
157
|
+
await p;
|
|
158
|
+
// Wait for an update message.
|
|
159
|
+
const messages = await waitForUpdateMessages();
|
|
160
|
+
for (const message of messages) {
|
|
161
|
+
// Check that the update message is of a reasonable size. From previous
|
|
162
|
+
// testing we found that the socket would silently reject data that got
|
|
163
|
+
// too big. 6KB is safely below the limit.
|
|
164
|
+
expect(message.length).toBeLessThan(6000);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('has reasonable average message size', async () => {
|
|
169
|
+
// Use an unencrypted asset.
|
|
170
|
+
fakeInitState.manifest = 'test:sintel';
|
|
171
|
+
|
|
172
|
+
const p = waitForLoadedData();
|
|
173
|
+
|
|
174
|
+
// Start the process of loading by sending a fake init message.
|
|
175
|
+
fakeConnectedSenders(1);
|
|
176
|
+
fakeIncomingMessage({
|
|
177
|
+
type: 'init',
|
|
178
|
+
initState: fakeInitState,
|
|
179
|
+
appData: {},
|
|
180
|
+
}, mockShakaMessageBus);
|
|
181
|
+
|
|
182
|
+
await p;
|
|
183
|
+
|
|
184
|
+
// Collect messages over 50 update cycles, and average their length.
|
|
185
|
+
// Not all properties are passed along on every update message, so
|
|
186
|
+
// the average length is expected to be lower than the length of the first
|
|
187
|
+
// update message.
|
|
188
|
+
let totalLength = 0;
|
|
189
|
+
let totalMessages = 0;
|
|
190
|
+
for (let i = 0; i < 50; i++) {
|
|
191
|
+
// eslint-disable-next-line no-await-in-loop
|
|
192
|
+
const messages = await waitForUpdateMessages();
|
|
193
|
+
for (const message of messages) {
|
|
194
|
+
totalLength += message.length;
|
|
195
|
+
totalMessages += 1;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
expect(totalLength / totalMessages).toBeLessThan(2000);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
|
|
131
202
|
filterDescribe('with drm', () => support['com.widevine.alpha'], () => {
|
|
132
203
|
drmIt('sends reasonably-sized updates', async () => {
|
|
133
204
|
// Use an encrypted asset, to make sure DRM info doesn't balloon the size.
|
|
@@ -150,11 +221,13 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
150
221
|
|
|
151
222
|
await p;
|
|
152
223
|
// Wait for an update message.
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
224
|
+
const messages = await waitForUpdateMessages();
|
|
225
|
+
for (const message of messages) {
|
|
226
|
+
// Check that the update message is of a reasonable size. From previous
|
|
227
|
+
// testing we found that the socket would silently reject data that got
|
|
228
|
+
// too big. 6KB is safely below the limit.
|
|
229
|
+
expect(message.length).toBeLessThan(6000);
|
|
230
|
+
}
|
|
158
231
|
});
|
|
159
232
|
|
|
160
233
|
drmIt('has reasonable average message size', async () => {
|
|
@@ -177,17 +250,22 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
177
250
|
}, mockShakaMessageBus);
|
|
178
251
|
|
|
179
252
|
await p;
|
|
180
|
-
|
|
253
|
+
|
|
254
|
+
// Collect messages over 50 update cycles, and average their length.
|
|
181
255
|
// Not all properties are passed along on every update message, so
|
|
182
256
|
// the average length is expected to be lower than the length of the first
|
|
183
257
|
// update message.
|
|
184
258
|
let totalLength = 0;
|
|
259
|
+
let totalMessages = 0;
|
|
185
260
|
for (let i = 0; i < 50; i++) {
|
|
186
261
|
// eslint-disable-next-line no-await-in-loop
|
|
187
|
-
const
|
|
188
|
-
|
|
262
|
+
const messages = await waitForUpdateMessages();
|
|
263
|
+
for (const message of messages) {
|
|
264
|
+
totalLength += message.length;
|
|
265
|
+
totalMessages += 1;
|
|
266
|
+
}
|
|
189
267
|
}
|
|
190
|
-
expect(totalLength /
|
|
268
|
+
expect(totalLength / totalMessages).toBeLessThan(2000);
|
|
191
269
|
});
|
|
192
270
|
});
|
|
193
271
|
|
|
@@ -227,12 +305,12 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
227
305
|
expect(pendingWaitWrapperCalls).toBe(0);
|
|
228
306
|
|
|
229
307
|
// Wait for a final update message before proceeding.
|
|
230
|
-
await
|
|
308
|
+
await waitForUpdateMessages();
|
|
231
309
|
});
|
|
232
310
|
|
|
233
311
|
/**
|
|
234
312
|
* Creates a wrapper around a method on a given prototype, which makes it
|
|
235
|
-
* wait on
|
|
313
|
+
* wait on waitForUpdateMessages before returning, and registers that wrapper
|
|
236
314
|
* to be uninstalled afterwards.
|
|
237
315
|
* The replaced method is expected to be a method that returns a promise.
|
|
238
316
|
* @param {!Object} prototype
|
|
@@ -249,7 +327,7 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
249
327
|
'Waiting for update message before calling ' +
|
|
250
328
|
name + '.' + methodName + '...');
|
|
251
329
|
const originalArguments = Array.from(arguments);
|
|
252
|
-
await
|
|
330
|
+
await waitForUpdateMessages();
|
|
253
331
|
// eslint-disable-next-line no-restricted-syntax
|
|
254
332
|
return original.apply(this, originalArguments);
|
|
255
333
|
};
|
|
@@ -265,7 +343,8 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
265
343
|
});
|
|
266
344
|
}
|
|
267
345
|
|
|
268
|
-
function
|
|
346
|
+
function waitForUpdateMessages() {
|
|
347
|
+
pendingMessages = [];
|
|
269
348
|
messageWaitPromise = new shaka.util.PublicPromise();
|
|
270
349
|
return messageWaitPromise;
|
|
271
350
|
}
|
|
@@ -316,10 +395,12 @@ filterDescribe('CastReceiver', castReceiverIntegrationSupport, () => {
|
|
|
316
395
|
bus.messages.push(CastUtils.deserialize(message));
|
|
317
396
|
// Check to see if it's an update message.
|
|
318
397
|
const parsed = CastUtils.deserialize(message);
|
|
319
|
-
if (parsed.type == 'update' &&
|
|
398
|
+
if (parsed.type == 'update' && pendingMessages) {
|
|
320
399
|
shaka.log.debug('Received update message. Proceeding...');
|
|
321
|
-
|
|
322
|
-
|
|
400
|
+
// The code waiting on this Promise will get an array of all of the
|
|
401
|
+
// messages processed within this tick.
|
|
402
|
+
pendingMessages.push(message);
|
|
403
|
+
messageWaitPromise.resolve(pendingMessages);
|
|
323
404
|
}
|
|
324
405
|
});
|
|
325
406
|
const channel = {
|
|
@@ -245,18 +245,20 @@ filterDescribe('CastReceiver', castReceiverSupport, () => {
|
|
|
245
245
|
const fakeEvent = {type: 'timeupdate'};
|
|
246
246
|
mockVideo.on['timeupdate'](fakeEvent);
|
|
247
247
|
|
|
248
|
-
// There are now "update" and "event" messages, in that
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
248
|
+
// There are now some number of "update" and "event" messages, in that
|
|
249
|
+
// order.
|
|
250
|
+
expect(mockShakaMessageBus.messages).toContain({
|
|
251
|
+
type: 'update',
|
|
252
|
+
update: jasmine.any(Object),
|
|
253
|
+
});
|
|
254
|
+
expect(mockShakaMessageBus.messages).toContain({
|
|
255
|
+
type: 'event',
|
|
256
|
+
targetName: 'video',
|
|
257
|
+
event: jasmine.objectContaining(fakeEvent),
|
|
258
|
+
});
|
|
259
|
+
const eventIndex = mockShakaMessageBus.messages.findIndex(
|
|
260
|
+
(message) => message.type == 'event');
|
|
261
|
+
expect(eventIndex).toBe(mockShakaMessageBus.messages.length - 1);
|
|
260
262
|
});
|
|
261
263
|
});
|
|
262
264
|
|
|
@@ -1105,6 +1107,9 @@ filterDescribe('CastReceiver', castReceiverSupport, () => {
|
|
|
1105
1107
|
for (const name in CastUtils.PlayerGetterMethods) {
|
|
1106
1108
|
player[name] = jasmine.createSpy(name);
|
|
1107
1109
|
}
|
|
1110
|
+
for (const name in CastUtils.LargePlayerGetterMethods) {
|
|
1111
|
+
player[name] = jasmine.createSpy(name);
|
|
1112
|
+
}
|
|
1108
1113
|
for (const name in CastUtils.PlayerGetterMethodsThatRequireLive) {
|
|
1109
1114
|
player[name] = jasmine.createSpy(name);
|
|
1110
1115
|
}
|
|
@@ -34,6 +34,7 @@ describe('CastUtils', () => {
|
|
|
34
34
|
const castMembers = CastUtils.PlayerVoidMethods
|
|
35
35
|
.concat(CastUtils.PlayerPromiseMethods)
|
|
36
36
|
.concat(Object.keys(CastUtils.PlayerGetterMethods))
|
|
37
|
+
.concat(Object.keys(CastUtils.LargePlayerGetterMethods))
|
|
37
38
|
.concat(Object.keys(CastUtils.PlayerGetterMethodsThatRequireLive));
|
|
38
39
|
// eslint-disable-next-line no-restricted-syntax
|
|
39
40
|
const allPlayerMembers = Object.getOwnPropertyNames(shaka.Player.prototype);
|
|
@@ -2106,6 +2106,7 @@ describe('DashParser Manifest', () => {
|
|
|
2106
2106
|
/** @type {shaka.extern.Manifest} */
|
|
2107
2107
|
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
2108
2108
|
expect(manifest.imageStreams.length).toBe(1);
|
|
2109
|
+
expect(manifest.presentationTimeline.getMaxSegmentDuration()).toBe(1);
|
|
2109
2110
|
const imageStream = manifest.imageStreams[0];
|
|
2110
2111
|
expect(imageStream.width).toBe(1024);
|
|
2111
2112
|
expect(imageStream.height).toBe(1152);
|
|
@@ -120,11 +120,17 @@ describe('DrmEngine', () => {
|
|
|
120
120
|
};
|
|
121
121
|
|
|
122
122
|
drmEngine = new shaka.media.DrmEngine(playerInterface);
|
|
123
|
+
|
|
123
124
|
config = shaka.util.PlayerConfiguration.createDefault().drm;
|
|
124
125
|
config.servers = {
|
|
125
126
|
'drm.abc': 'http://abc.drm/license',
|
|
126
127
|
'drm.def': 'http://def.drm/license',
|
|
127
128
|
};
|
|
129
|
+
// Some platforms, such as Xbox, default to parseInbandPssh: true, which
|
|
130
|
+
// ignores encrypted events. So set it explicitly to false, and let
|
|
131
|
+
// individual tests set it to true where relevant.
|
|
132
|
+
config.parseInbandPsshEnabled = false;
|
|
133
|
+
|
|
128
134
|
drmEngine.configure(config);
|
|
129
135
|
});
|
|
130
136
|
|
|
@@ -2548,7 +2554,11 @@ describe('DrmEngine', () => {
|
|
|
2548
2554
|
*/
|
|
2549
2555
|
async function sendEncryptedEvent(
|
|
2550
2556
|
initDataType = 'cenc', initData = new Uint8Array(1), keyId = null) {
|
|
2551
|
-
|
|
2557
|
+
// For some platforms, such as Xbox, where parseInbandPssh defaults to
|
|
2558
|
+
// true, this listener may never be set.
|
|
2559
|
+
if (mockVideo.on['encrypted']) {
|
|
2560
|
+
mockVideo.on['encrypted']({initDataType, initData, keyId});
|
|
2561
|
+
}
|
|
2552
2562
|
|
|
2553
2563
|
await Util.shortDelay();
|
|
2554
2564
|
}
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
describe('MediaSourceEngine', () => {
|
|
8
8
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
9
|
+
const Cue = shaka.text.Cue;
|
|
10
|
+
const Util = shaka.test.Util;
|
|
9
11
|
const presentationDuration = 840;
|
|
10
12
|
|
|
11
13
|
/** @type {!HTMLVideoElement} */
|
|
@@ -18,6 +20,64 @@ describe('MediaSourceEngine', () => {
|
|
|
18
20
|
let metadata;
|
|
19
21
|
// TODO: add text streams to MSE integration tests
|
|
20
22
|
|
|
23
|
+
const mp4CeaCue0 = jasmine.objectContaining({
|
|
24
|
+
startTime: Util.closeTo(0.067, 0.001),
|
|
25
|
+
endTime: Util.closeTo(1, 0.001),
|
|
26
|
+
textAlign: Cue.textAlign.CENTER,
|
|
27
|
+
nestedCues: [
|
|
28
|
+
jasmine.objectContaining({
|
|
29
|
+
startTime: Util.closeTo(0.067, 0.001),
|
|
30
|
+
endTime: Util.closeTo(1, 0.001),
|
|
31
|
+
payload: 'eng:⠀00:00:00:00',
|
|
32
|
+
textAlign: Cue.textAlign.CENTER,
|
|
33
|
+
}),
|
|
34
|
+
],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const tsCeaCue0 = jasmine.objectContaining({
|
|
38
|
+
startTime: Util.closeTo(0.767, 0.001),
|
|
39
|
+
endTime: Util.closeTo(4.972, 0.001),
|
|
40
|
+
textAlign: Cue.textAlign.CENTER,
|
|
41
|
+
payload: 'These are 608 captions\n(top left)',
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const tsCeaCue1 = jasmine.objectContaining({
|
|
45
|
+
startTime: Util.closeTo(5.305, 0.001),
|
|
46
|
+
endTime: Util.closeTo(11.979, 0.001),
|
|
47
|
+
textAlign: Cue.textAlign.CENTER,
|
|
48
|
+
payload: 'These are 608 captions\n(middle)',
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const tsCeaCue2 = jasmine.objectContaining({
|
|
52
|
+
startTime: Util.closeTo(12.312, 0.001),
|
|
53
|
+
endTime: Util.closeTo(19.319, 0.001),
|
|
54
|
+
textAlign: Cue.textAlign.CENTER,
|
|
55
|
+
payload: 'These are 608 captions\n(bottom left)',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
// The same segments as above, but offset by 40 seconds (yes, 40), which is
|
|
59
|
+
// also 2 segments.
|
|
60
|
+
const tsCeaCue3 = jasmine.objectContaining({
|
|
61
|
+
startTime: Util.closeTo(40.767, 0.001),
|
|
62
|
+
endTime: Util.closeTo(44.972, 0.001),
|
|
63
|
+
textAlign: Cue.textAlign.CENTER,
|
|
64
|
+
payload: 'These are 608 captions\n(top left)',
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const tsCeaCue4 = jasmine.objectContaining({
|
|
68
|
+
startTime: Util.closeTo(45.305, 0.001),
|
|
69
|
+
endTime: Util.closeTo(51.979, 0.001),
|
|
70
|
+
textAlign: Cue.textAlign.CENTER,
|
|
71
|
+
payload: 'These are 608 captions\n(middle)',
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const tsCeaCue5 = jasmine.objectContaining({
|
|
75
|
+
startTime: Util.closeTo(52.312, 0.001),
|
|
76
|
+
endTime: Util.closeTo(59.319, 0.001),
|
|
77
|
+
textAlign: Cue.textAlign.CENTER,
|
|
78
|
+
payload: 'These are 608 captions\n(bottom left)',
|
|
79
|
+
});
|
|
80
|
+
|
|
21
81
|
/**
|
|
22
82
|
* We use a fake text displayer so that we can check if CEA text is being
|
|
23
83
|
* passed through the system correctly.
|
|
@@ -70,6 +130,18 @@ describe('MediaSourceEngine', () => {
|
|
|
70
130
|
type, segment, reference, /* hasClosedCaptions= */ false);
|
|
71
131
|
}
|
|
72
132
|
|
|
133
|
+
function appendWithSeek(type, segmentNumber) {
|
|
134
|
+
const segment = generators[type]
|
|
135
|
+
.getSegment(segmentNumber, Date.now() / 1000);
|
|
136
|
+
const reference = dummyReference(type, segmentNumber);
|
|
137
|
+
return mediaSourceEngine.appendBuffer(
|
|
138
|
+
type,
|
|
139
|
+
segment,
|
|
140
|
+
reference,
|
|
141
|
+
/* hasClosedCaptions= */ false,
|
|
142
|
+
/* seeked= */ true);
|
|
143
|
+
}
|
|
144
|
+
|
|
73
145
|
function appendInitWithClosedCaptions(type) {
|
|
74
146
|
const segment = generators[type].getInitSegment(Date.now() / 1000);
|
|
75
147
|
const reference = null;
|
|
@@ -375,9 +447,43 @@ describe('MediaSourceEngine', () => {
|
|
|
375
447
|
// platforms with native TS support.
|
|
376
448
|
await mediaSourceEngine.init(initObject, /* forceTransmuxTS= */ true);
|
|
377
449
|
mediaSourceEngine.setSelectedClosedCaptionId('CC1');
|
|
450
|
+
|
|
378
451
|
await append(ContentType.VIDEO, 0);
|
|
379
452
|
|
|
380
453
|
expect(textDisplayer.appendSpy).toHaveBeenCalledTimes(3);
|
|
454
|
+
|
|
455
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue0]);
|
|
456
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue1]);
|
|
457
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue2]);
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('extracts CEA-708 captions from previous segment from hls', async () => {
|
|
461
|
+
// Load TS file with CEA-708 captions.
|
|
462
|
+
metadata = shaka.test.TestScheme.DATA['cea-708_ts'];
|
|
463
|
+
generators = shaka.test.TestScheme.GENERATORS['cea-708_ts'];
|
|
464
|
+
|
|
465
|
+
const initObject = new Map();
|
|
466
|
+
initObject.set(ContentType.VIDEO, getFakeStream(metadata.video));
|
|
467
|
+
initObject.set(ContentType.TEXT, getFakeStream(metadata.text));
|
|
468
|
+
// Call with forceTransmux = true, so that it will transmux even on
|
|
469
|
+
// platforms with native TS support.
|
|
470
|
+
await mediaSourceEngine.init(initObject, /* forceTransmux= */ true);
|
|
471
|
+
mediaSourceEngine.setSelectedClosedCaptionId('CC1');
|
|
472
|
+
|
|
473
|
+
await append(ContentType.VIDEO, 2);
|
|
474
|
+
|
|
475
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledTimes(3);
|
|
476
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue3]);
|
|
477
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue4]);
|
|
478
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue5]);
|
|
479
|
+
|
|
480
|
+
textDisplayer.appendSpy.calls.reset();
|
|
481
|
+
await appendWithSeek(ContentType.VIDEO, 0);
|
|
482
|
+
|
|
483
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledTimes(3);
|
|
484
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue0]);
|
|
485
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue1]);
|
|
486
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([tsCeaCue2]);
|
|
381
487
|
});
|
|
382
488
|
|
|
383
489
|
it('buffers partial TS video segments in sequence mode', async () => {
|
|
@@ -430,6 +536,7 @@ describe('MediaSourceEngine', () => {
|
|
|
430
536
|
mediaSourceEngine.setSelectedClosedCaptionId('CC1');
|
|
431
537
|
await appendWithClosedCaptions(ContentType.VIDEO, 0);
|
|
432
538
|
|
|
433
|
-
expect(textDisplayer.appendSpy).
|
|
539
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledTimes(1);
|
|
540
|
+
expect(textDisplayer.appendSpy).toHaveBeenCalledWith([mp4CeaCue0]);
|
|
434
541
|
});
|
|
435
542
|
});
|
|
@@ -161,6 +161,8 @@ describe('Playhead', () => {
|
|
|
161
161
|
Util.spyFunc(onSeek),
|
|
162
162
|
Util.spyFunc(onEvent));
|
|
163
163
|
|
|
164
|
+
playhead.ready();
|
|
165
|
+
|
|
164
166
|
expect(video.currentTime).toBe(5);
|
|
165
167
|
expect(playhead.getTime()).toBe(5);
|
|
166
168
|
|
|
@@ -182,6 +184,8 @@ describe('Playhead', () => {
|
|
|
182
184
|
Util.spyFunc(onSeek),
|
|
183
185
|
Util.spyFunc(onEvent));
|
|
184
186
|
|
|
187
|
+
playhead.ready();
|
|
188
|
+
|
|
185
189
|
expect(video.addEventListener).toHaveBeenCalledWith(
|
|
186
190
|
'loadedmetadata', jasmine.any(Function), jasmine.anything());
|
|
187
191
|
expect(video.addEventListener).not.toHaveBeenCalledWith(
|
|
@@ -223,6 +227,8 @@ describe('Playhead', () => {
|
|
|
223
227
|
Util.spyFunc(onSeek),
|
|
224
228
|
Util.spyFunc(onEvent));
|
|
225
229
|
|
|
230
|
+
playhead.ready();
|
|
231
|
+
|
|
226
232
|
video.on['seeking']();
|
|
227
233
|
expect(playhead.getTime()).toBe(5);
|
|
228
234
|
expect(video.currentTime).toBe(5);
|
|
@@ -264,6 +270,8 @@ describe('Playhead', () => {
|
|
|
264
270
|
Util.spyFunc(onSeek),
|
|
265
271
|
Util.spyFunc(onEvent));
|
|
266
272
|
|
|
273
|
+
playhead.ready();
|
|
274
|
+
|
|
267
275
|
expect(playhead.getTime()).toBe(59); // duration - durationBackoff
|
|
268
276
|
expect(video.currentTime).toBe(59); // duration - durationBackoff
|
|
269
277
|
});
|
|
@@ -302,6 +310,8 @@ describe('Playhead', () => {
|
|
|
302
310
|
Util.spyFunc(onSeek),
|
|
303
311
|
Util.spyFunc(onEvent));
|
|
304
312
|
|
|
313
|
+
playhead.ready();
|
|
314
|
+
|
|
305
315
|
expect(playhead.getTime()).toBe(30);
|
|
306
316
|
});
|
|
307
317
|
|
|
@@ -314,6 +324,8 @@ describe('Playhead', () => {
|
|
|
314
324
|
Util.spyFunc(onSeek),
|
|
315
325
|
Util.spyFunc(onEvent));
|
|
316
326
|
|
|
327
|
+
playhead.ready();
|
|
328
|
+
|
|
317
329
|
expect(video.addEventListener).toHaveBeenCalledWith(
|
|
318
330
|
'loadedmetadata', jasmine.any(Function), jasmine.anything());
|
|
319
331
|
|
|
@@ -347,6 +359,8 @@ describe('Playhead', () => {
|
|
|
347
359
|
Util.spyFunc(onSeek),
|
|
348
360
|
Util.spyFunc(onEvent));
|
|
349
361
|
|
|
362
|
+
playhead.ready();
|
|
363
|
+
|
|
350
364
|
expect(video.addEventListener).toHaveBeenCalledWith(
|
|
351
365
|
'loadedmetadata', jasmine.any(Function), jasmine.anything());
|
|
352
366
|
|
|
@@ -380,6 +394,8 @@ describe('Playhead', () => {
|
|
|
380
394
|
Util.spyFunc(onSeek),
|
|
381
395
|
Util.spyFunc(onEvent));
|
|
382
396
|
|
|
397
|
+
playhead.ready();
|
|
398
|
+
|
|
383
399
|
// This has to periodically increment the mock date to allow the onSeeking_
|
|
384
400
|
// handler to seek, if appropriate.
|
|
385
401
|
|
|
@@ -652,6 +668,8 @@ describe('Playhead', () => {
|
|
|
652
668
|
expect(currentTime).toBe(1000);
|
|
653
669
|
seekCount = 0;
|
|
654
670
|
|
|
671
|
+
playhead.ready();
|
|
672
|
+
|
|
655
673
|
// The availability window slips ahead.
|
|
656
674
|
timeline.getSeekRangeStart.and.returnValue(1030);
|
|
657
675
|
timeline.getSeekRangeEnd.and.returnValue(1030);
|
|
@@ -696,6 +714,8 @@ describe('Playhead', () => {
|
|
|
696
714
|
Util.spyFunc(onSeek),
|
|
697
715
|
Util.spyFunc(onEvent));
|
|
698
716
|
|
|
717
|
+
playhead.ready();
|
|
718
|
+
|
|
699
719
|
video.on['seeking']();
|
|
700
720
|
expect(video.currentTime).toBe(5);
|
|
701
721
|
expect(playhead.getTime()).toBe(5);
|
|
@@ -728,6 +748,8 @@ describe('Playhead', () => {
|
|
|
728
748
|
Util.spyFunc(onSeek),
|
|
729
749
|
Util.spyFunc(onEvent));
|
|
730
750
|
|
|
751
|
+
playhead.ready();
|
|
752
|
+
|
|
731
753
|
video.on['seeking']();
|
|
732
754
|
expect(video.currentTime).toBe(5);
|
|
733
755
|
expect(playhead.getTime()).toBe(5);
|
|
@@ -765,6 +787,9 @@ describe('Playhead', () => {
|
|
|
765
787
|
|
|
766
788
|
video.currentTime = 0;
|
|
767
789
|
video.seeking = true;
|
|
790
|
+
|
|
791
|
+
playhead.ready();
|
|
792
|
+
|
|
768
793
|
// "video.seeking" stays true until the buffered range intersects with
|
|
769
794
|
// "video.currentTime". Playhead should correct anyway.
|
|
770
795
|
video.on['seeking']();
|
|
@@ -809,6 +834,8 @@ describe('Playhead', () => {
|
|
|
809
834
|
Util.spyFunc(onSeek),
|
|
810
835
|
Util.spyFunc(onEvent));
|
|
811
836
|
|
|
837
|
+
playhead.ready();
|
|
838
|
+
|
|
812
839
|
/**
|
|
813
840
|
* Prevent retries on the initial start time seek. This will ensure that
|
|
814
841
|
* only one call is made for the initial seek, and that any additional calls
|
|
@@ -933,6 +960,8 @@ describe('Playhead', () => {
|
|
|
933
960
|
Util.spyFunc(onSeek),
|
|
934
961
|
Util.spyFunc(onEvent));
|
|
935
962
|
|
|
963
|
+
playhead.ready();
|
|
964
|
+
|
|
936
965
|
jasmine.clock().tick(500);
|
|
937
966
|
for (let time = data.start; time < data.waitingAt; time++) {
|
|
938
967
|
// We don't want to run tick() for 1 second because it will trigger
|
|
@@ -1142,6 +1171,8 @@ describe('Playhead', () => {
|
|
|
1142
1171
|
Util.spyFunc(onSeek),
|
|
1143
1172
|
Util.spyFunc(onEvent));
|
|
1144
1173
|
|
|
1174
|
+
playhead.ready();
|
|
1175
|
+
|
|
1145
1176
|
jasmine.clock().tick(500);
|
|
1146
1177
|
expect(onEvent).not.toHaveBeenCalled();
|
|
1147
1178
|
|
|
@@ -1190,6 +1221,8 @@ describe('Playhead', () => {
|
|
|
1190
1221
|
Util.spyFunc(onSeek),
|
|
1191
1222
|
Util.spyFunc(onEvent));
|
|
1192
1223
|
|
|
1224
|
+
playhead.ready();
|
|
1225
|
+
|
|
1193
1226
|
playhead.notifyOfBufferingChange();
|
|
1194
1227
|
jasmine.clock().tick(500);
|
|
1195
1228
|
|
|
@@ -1212,6 +1245,8 @@ describe('Playhead', () => {
|
|
|
1212
1245
|
Util.spyFunc(onSeek),
|
|
1213
1246
|
Util.spyFunc(onEvent));
|
|
1214
1247
|
|
|
1248
|
+
playhead.ready();
|
|
1249
|
+
|
|
1215
1250
|
playhead.notifyOfBufferingChange();
|
|
1216
1251
|
jasmine.clock().tick(500);
|
|
1217
1252
|
|
|
@@ -1236,6 +1271,8 @@ describe('Playhead', () => {
|
|
|
1236
1271
|
Util.spyFunc(onSeek),
|
|
1237
1272
|
Util.spyFunc(onEvent));
|
|
1238
1273
|
|
|
1274
|
+
playhead.ready();
|
|
1275
|
+
|
|
1239
1276
|
playhead.notifyOfBufferingChange();
|
|
1240
1277
|
jasmine.clock().tick(500);
|
|
1241
1278
|
|
|
@@ -1260,6 +1297,8 @@ describe('Playhead', () => {
|
|
|
1260
1297
|
Util.spyFunc(onSeek),
|
|
1261
1298
|
Util.spyFunc(onEvent));
|
|
1262
1299
|
|
|
1300
|
+
playhead.ready();
|
|
1301
|
+
|
|
1263
1302
|
playhead.notifyOfBufferingChange();
|
|
1264
1303
|
jasmine.clock().tick(500);
|
|
1265
1304
|
|
|
@@ -1287,6 +1326,8 @@ describe('Playhead', () => {
|
|
|
1287
1326
|
Util.spyFunc(onSeek),
|
|
1288
1327
|
Util.spyFunc(onEvent));
|
|
1289
1328
|
|
|
1329
|
+
playhead.ready();
|
|
1330
|
+
|
|
1290
1331
|
jasmine.clock().tick(500);
|
|
1291
1332
|
expect(onEvent).not.toHaveBeenCalled();
|
|
1292
1333
|
|
|
@@ -154,6 +154,19 @@ describe('StreamingEngine', () => {
|
|
|
154
154
|
/* presentationDuration= */ Infinity);
|
|
155
155
|
setupPlayhead();
|
|
156
156
|
|
|
157
|
+
// Retry on failure for live streams.
|
|
158
|
+
config.failureCallback = () => streamingEngine.retry(0.1);
|
|
159
|
+
|
|
160
|
+
// Ignore 404 errors in live stream tests.
|
|
161
|
+
onError.and.callFake((error) => {
|
|
162
|
+
if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS &&
|
|
163
|
+
error.data[1] == 404) {
|
|
164
|
+
// 404 error
|
|
165
|
+
} else {
|
|
166
|
+
fail(error);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
157
170
|
createStreamingEngine();
|
|
158
171
|
}
|
|
159
172
|
|