shaka-player 2.5.4 → 2.5.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/.csslintrc +13 -0
- package/CHANGELOG.md +56 -0
- package/build/build.py +4 -1
- package/build/check.py +17 -0
- package/build/compiler.py +40 -0
- package/build/types/core +1 -0
- package/build/types/ui +4 -1
- package/demo/asset_card.js +41 -11
- package/demo/cast_receiver/receiver_app.css +19 -13
- package/demo/close_button.js +0 -6
- package/demo/common/asset.js +6 -1
- package/demo/common/assets.js +72 -14
- package/demo/demo.less +105 -16
- package/demo/index.html +8 -5
- package/demo/load.js +5 -7
- package/demo/main.js +81 -16
- package/dist/.csslintstamp +0 -0
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +46 -39
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +46 -39
- package/dist/demo.compiled.map +3 -3
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +11 -7
- package/dist/receiver.compiled.debug.js +10 -6
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +10 -6
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.debug.externs.js +123 -92
- package/dist/shaka-player.compiled.debug.js +113 -105
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +123 -92
- package/dist/shaka-player.compiled.js +551 -548
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.debug.externs.js +170 -101
- package/dist/shaka-player.ui.debug.js +187 -188
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +170 -101
- package/dist/shaka-player.ui.js +651 -651
- package/dist/shaka-player.ui.map +3 -3
- package/docs/tutorials/drm-config.md +1 -0
- package/docs/tutorials/fairplay.md +77 -0
- package/docs/tutorials/index.json +1 -0
- package/docs/tutorials/ui-customization.md +3 -0
- package/docs/tutorials/ui.md +3 -1
- package/externs/mediasession.js +16 -1
- package/externs/shaka/net.js +5 -1
- package/externs/shaka/player.js +136 -90
- package/externs/webkitmediakeys.js +4 -0
- package/lib/dash/content_protection.js +33 -1
- package/lib/dash/dash_parser.js +12 -4
- package/lib/hls/hls_parser.js +20 -4
- package/lib/media/drm_engine.js +29 -2
- package/lib/media/media_source_engine.js +7 -3
- package/lib/media/playhead.js +8 -3
- package/lib/media/streaming_engine.js +24 -8
- package/lib/media/video_wrapper.js +8 -3
- package/lib/net/networking_engine.js +1 -0
- package/lib/player.js +3 -2
- package/lib/polyfill/patchedmediakeys_apple.js +2 -72
- package/lib/util/error.js +12 -0
- package/lib/util/fairplay_utils.js +122 -0
- package/lib/util/player_configuration.js +5 -0
- package/lib/util/pssh.js +41 -0
- package/package.json +4 -3
- package/shaka-player.uncompiled.js +5 -1
- package/test/dash/dash_parser_content_protection_unit.js +35 -1
- package/test/dash/dash_parser_manifest_unit.js +31 -0
- package/test/hls/hls_live_unit.js +7 -1
- package/test/hls/hls_parser_unit.js +50 -0
- package/test/media/media_source_engine_integration.js +12 -0
- package/test/media/playhead_unit.js +2 -0
- package/test/player_external.js +3 -2
- package/test/player_integration.js +75 -0
- package/test/test/externs/jasmine.js +6 -0
- package/test/test/util/test_scheme.js +32 -1
- package/test/test/util/ui_utils.js +41 -0
- package/test/ui/ui_unit.js +129 -3
- package/ui/audio_language_selection.js +17 -96
- package/ui/big_play_button.js +61 -0
- package/ui/controls.js +111 -86
- package/ui/controls.less +3 -5
- package/ui/externs/ui.js +14 -2
- package/ui/fullscreen_button.js +2 -52
- package/ui/less/buttons.less +8 -8
- package/ui/less/containers.less +21 -21
- package/ui/less/general.less +11 -0
- package/ui/less/other_elements.less +2 -1
- package/ui/less/overflow_menu.less +11 -10
- package/ui/less/range_elements.less +6 -0
- package/ui/less/spinner.less +2 -0
- package/ui/pip_button.js +1 -1
- package/ui/{play_pause_button.js → play_button.js} +17 -47
- package/ui/resolution_selection.js +41 -104
- package/ui/seek_bar.js +15 -17
- package/ui/settings_menu.js +113 -0
- package/ui/small_play_button.js +73 -0
- package/ui/text_displayer.js +2 -2
- package/ui/text_selection.js +39 -105
- package/ui/ui.js +12 -0
package/lib/util/pssh.js
CHANGED
|
@@ -104,3 +104,44 @@ shaka.util.Pssh.prototype.parseBox_ = function(box) {
|
|
|
104
104
|
shaka.log.warning('Mismatch between box size and data size!');
|
|
105
105
|
}
|
|
106
106
|
};
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Creates a pssh blob from the given system ID and data.
|
|
111
|
+
*
|
|
112
|
+
* @param {!Uint8Array} data
|
|
113
|
+
* @param {!Uint8Array} systemId
|
|
114
|
+
* @return {!Uint8Array}
|
|
115
|
+
*/
|
|
116
|
+
shaka.util.Pssh.createPssh = function(data, systemId) {
|
|
117
|
+
const dataLength = data.length;
|
|
118
|
+
const psshSize = 0x4 + 0x4 + 0x4 + systemId.length + 0x4 + dataLength;
|
|
119
|
+
|
|
120
|
+
/** @type {!ArrayBuffer} */
|
|
121
|
+
const psshBoxBuffer = new ArrayBuffer(psshSize);
|
|
122
|
+
|
|
123
|
+
/** @type {!Uint8Array} */
|
|
124
|
+
const psshBox = new Uint8Array(psshBoxBuffer);
|
|
125
|
+
|
|
126
|
+
/** @type {!DataView} */
|
|
127
|
+
const psshData = new DataView(psshBoxBuffer);
|
|
128
|
+
|
|
129
|
+
let byteCursor = 0;
|
|
130
|
+
psshData.setUint32(byteCursor, psshSize);
|
|
131
|
+
byteCursor += 0x4;
|
|
132
|
+
psshData.setUint32(byteCursor, 0x70737368); // 'pssh'
|
|
133
|
+
byteCursor += 0x4;
|
|
134
|
+
psshData.setUint32(byteCursor, 0); // flags
|
|
135
|
+
byteCursor += 0x4;
|
|
136
|
+
psshBox.set(systemId, byteCursor);
|
|
137
|
+
byteCursor += systemId.length;
|
|
138
|
+
psshData.setUint32(byteCursor, dataLength);
|
|
139
|
+
byteCursor += 0x4;
|
|
140
|
+
psshBox.set(data, byteCursor);
|
|
141
|
+
byteCursor += dataLength;
|
|
142
|
+
|
|
143
|
+
goog.asserts.assert(byteCursor === psshSize,
|
|
144
|
+
'MS PRO invalid length.');
|
|
145
|
+
|
|
146
|
+
return psshBox;
|
|
147
|
+
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shaka-player",
|
|
3
3
|
"description": "DASH/EME video player library",
|
|
4
|
-
"version": "2.5.
|
|
4
|
+
"version": "2.5.5",
|
|
5
5
|
"homepage": "https://github.com/google/shaka-player",
|
|
6
6
|
"author": "Google",
|
|
7
7
|
"maintainers": [
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"karma-safari-launcher": "^1.0.0",
|
|
40
40
|
"karma-sourcemap-loader": "^0.3.7",
|
|
41
41
|
"karma-spec-reporter": "0.0.32",
|
|
42
|
-
"karma-webdriver-launcher": "^1.0.
|
|
42
|
+
"karma-webdriver-launcher": "^1.0.7",
|
|
43
43
|
"less": "^3.9.0",
|
|
44
44
|
"less-plugin-clean-css": "^1.5.1",
|
|
45
45
|
"material-design-lite": "^1.3.0",
|
|
@@ -48,8 +48,9 @@
|
|
|
48
48
|
"promise-polyfill": "<8.1.1",
|
|
49
49
|
"rimraf": "^2.6.3",
|
|
50
50
|
"sprintf-js": "^1.1.2",
|
|
51
|
+
"stylelint": "github:theodab/stylelint",
|
|
52
|
+
"stylelint-config-standard": "^18.3.0",
|
|
51
53
|
"tippy.js": "^4.3.1",
|
|
52
|
-
"wd": "^1.11.2",
|
|
53
54
|
"which": "^1.3.1"
|
|
54
55
|
},
|
|
55
56
|
"main": "dist/shaka-player.compiled.js",
|
|
@@ -64,8 +64,11 @@ goog.require('shaka.text.TextEngine');
|
|
|
64
64
|
goog.require('shaka.text.TtmlTextParser');
|
|
65
65
|
goog.require('shaka.text.VttTextParser');
|
|
66
66
|
goog.require('shaka.ui.Controls');
|
|
67
|
+
goog.require('shaka.ui.PlayButton');
|
|
68
|
+
goog.require('shaka.ui.SettingsMenu');
|
|
67
69
|
goog.require('shaka.ui.OverflowMenu');
|
|
68
70
|
goog.require('shaka.ui.AudioLanguageSelection');
|
|
71
|
+
goog.require('shaka.ui.BigPlayButton');
|
|
69
72
|
goog.require('shaka.ui.CastButton');
|
|
70
73
|
goog.require('shaka.ui.Element');
|
|
71
74
|
goog.require('shaka.ui.FastForwardButton');
|
|
@@ -74,7 +77,7 @@ goog.require('shaka.ui.Localization');
|
|
|
74
77
|
goog.require('shaka.ui.MuteButton');
|
|
75
78
|
goog.require('shaka.ui.Overlay');
|
|
76
79
|
goog.require('shaka.ui.PipButton');
|
|
77
|
-
goog.require('shaka.ui.
|
|
80
|
+
goog.require('shaka.ui.SmallPlayButton');
|
|
78
81
|
goog.require('shaka.ui.PresentationTimeTracker');
|
|
79
82
|
goog.require('shaka.ui.ResolutionSelection');
|
|
80
83
|
goog.require('shaka.ui.RewindButton');
|
|
@@ -83,4 +86,5 @@ goog.require('shaka.ui.TextSelection');
|
|
|
83
86
|
goog.require('shaka.ui.VolumeBar');
|
|
84
87
|
goog.require('shaka.util.Dom');
|
|
85
88
|
goog.require('shaka.util.Error');
|
|
89
|
+
goog.require('shaka.util.FairPlayUtils');
|
|
86
90
|
goog.require('shaka.util.Iterables');
|
|
@@ -68,7 +68,8 @@ describe('DashParser ContentProtection', function() {
|
|
|
68
68
|
adaptationSetLines, representation1Lines, representation2Lines) {
|
|
69
69
|
let template = [
|
|
70
70
|
'<MPD xmlns="urn:mpeg:DASH:schema:MPD:2011"',
|
|
71
|
-
' xmlns:cenc="urn:mpeg:cenc:2013"
|
|
71
|
+
' xmlns:cenc="urn:mpeg:cenc:2013"',
|
|
72
|
+
' xmlns:mspr="urn:microsoft:playready">',
|
|
72
73
|
' <Period duration="PT30S">',
|
|
73
74
|
' <SegmentTemplate media="s.mp4" duration="2" />',
|
|
74
75
|
' <AdaptationSet mimeType="video/mp4" codecs="avc1.4d401f">',
|
|
@@ -298,6 +299,39 @@ describe('DashParser ContentProtection', function() {
|
|
|
298
299
|
await testDashParser(source, expected);
|
|
299
300
|
});
|
|
300
301
|
|
|
302
|
+
it('extracts embedded PSSHs with mspr:pro', async () => {
|
|
303
|
+
const source = buildManifestText([
|
|
304
|
+
// AdaptationSet lines
|
|
305
|
+
'<ContentProtection',
|
|
306
|
+
' schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95">',
|
|
307
|
+
' <mspr:pro>UGxheXJlYWR5</mspr:pro>',
|
|
308
|
+
'</ContentProtection>',
|
|
309
|
+
], [], []);
|
|
310
|
+
const expected = buildExpectedManifest([
|
|
311
|
+
buildDrmInfo('com.microsoft.playready', [], [
|
|
312
|
+
'AAAAKXBzc2gAAAAAmgTweZhAQoarkuZb4IhflQAAAAlQbGF5cmVhZHk=',
|
|
313
|
+
]),
|
|
314
|
+
]);
|
|
315
|
+
await testDashParser(source, expected);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
it('extracts embedded PSSHs and prefer cenc:pssh over mspr:pro', async () => {
|
|
319
|
+
const source = buildManifestText([
|
|
320
|
+
// AdaptationSet lines
|
|
321
|
+
'<ContentProtection',
|
|
322
|
+
' schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95">',
|
|
323
|
+
' <cenc:pssh>bm8gaHVtYW4gY2FuIHJlYWQgYmFzZTY0IGRpcmVjdGx5</cenc:pssh>',
|
|
324
|
+
' <mspr:pro>ZmFrZSBQbGF5cmVhZHkgUFJP</mspr:pro>',
|
|
325
|
+
'</ContentProtection>',
|
|
326
|
+
], [], []);
|
|
327
|
+
const expected = buildExpectedManifest([
|
|
328
|
+
buildDrmInfo('com.microsoft.playready', [], [
|
|
329
|
+
'bm8gaHVtYW4gY2FuIHJlYWQgYmFzZTY0IGRpcmVjdGx5',
|
|
330
|
+
]),
|
|
331
|
+
]);
|
|
332
|
+
await testDashParser(source, expected);
|
|
333
|
+
});
|
|
334
|
+
|
|
301
335
|
it('assumes all known key systems for generic CENC', async () => {
|
|
302
336
|
let source = buildManifestText([
|
|
303
337
|
// AdaptationSet lines
|
|
@@ -1244,4 +1244,35 @@ describe('DashParser Manifest', function() {
|
|
|
1244
1244
|
expect(presentationDelay).not.toBeNaN();
|
|
1245
1245
|
expect(presentationDelay).toBe(config.dash.defaultPresentationDelay);
|
|
1246
1246
|
});
|
|
1247
|
+
|
|
1248
|
+
it('converts Accessibility element to "kind"', async () => {
|
|
1249
|
+
const manifestText = [
|
|
1250
|
+
'<MPD minBufferTime="PT75S">',
|
|
1251
|
+
' <Period id="1" duration="PT30S">',
|
|
1252
|
+
' <AdaptationSet id="1" contentType="text">',
|
|
1253
|
+
' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
|
|
1254
|
+
' value="captions" />',
|
|
1255
|
+
' <Accessibility schemeIdUri="urn:mpeg:dash:role:2011" ',
|
|
1256
|
+
' value="foo" />',
|
|
1257
|
+
' <Accessibility schemeIdUri="foobar" value="bar" />',
|
|
1258
|
+
' <Representation id="text-en" mimeType="text/webvtt">',
|
|
1259
|
+
' <BaseURL>t-en.vtt</BaseURL>',
|
|
1260
|
+
' </Representation>',
|
|
1261
|
+
' </AdaptationSet>',
|
|
1262
|
+
' <AdaptationSet id="1" mimeType="video/mp4">',
|
|
1263
|
+
' <Representation id="video-sd" width="640" height="480">',
|
|
1264
|
+
' <BaseURL>v-sd.mp4</BaseURL>',
|
|
1265
|
+
' <SegmentBase indexRange="100-200" />',
|
|
1266
|
+
' </Representation>',
|
|
1267
|
+
' </AdaptationSet>',
|
|
1268
|
+
' </Period>',
|
|
1269
|
+
'</MPD>',
|
|
1270
|
+
].join('\n');
|
|
1271
|
+
|
|
1272
|
+
fakeNetEngine.setResponseText('dummy://foo', manifestText);
|
|
1273
|
+
const manifest = await parser.start('dummy://foo', playerInterface);
|
|
1274
|
+
const textStream = manifest.periods[0].textStreams[0];
|
|
1275
|
+
expect(textStream.roles).toEqual(['captions', 'foo']);
|
|
1276
|
+
expect(textStream.kind).toBe('caption');
|
|
1277
|
+
});
|
|
1247
1278
|
});
|
|
@@ -273,12 +273,18 @@ describe('HlsParser live', function() {
|
|
|
273
273
|
});
|
|
274
274
|
|
|
275
275
|
it('updates all streams', function(done) {
|
|
276
|
+
const masterlist = [
|
|
277
|
+
'#EXTM3U\n',
|
|
278
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1",AUDIO="aud1",',
|
|
279
|
+
'RESOLUTION=960x540,FRAME-RATE=60\n',
|
|
280
|
+
'video\n',
|
|
281
|
+
].join('');
|
|
276
282
|
const audio = [
|
|
277
283
|
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",',
|
|
278
284
|
'URI="audio"\n',
|
|
279
285
|
].join('');
|
|
280
286
|
|
|
281
|
-
let masterWithAudio =
|
|
287
|
+
let masterWithAudio = masterlist + audio;
|
|
282
288
|
let ref1 = ManifestParser.makeReference('test:/main.mp4',
|
|
283
289
|
0, 2, 4);
|
|
284
290
|
let ref2 = ManifestParser.makeReference('test:/main2.mp4',
|
|
@@ -1107,6 +1107,56 @@ describe('HlsParser', function() {
|
|
|
1107
1107
|
.catch(fail).then(done);
|
|
1108
1108
|
});
|
|
1109
1109
|
|
|
1110
|
+
it('drops failed text streams when configured to', async () => {
|
|
1111
|
+
const master = [
|
|
1112
|
+
'#EXTM3U\n',
|
|
1113
|
+
'#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="sub1",LANGUAGE="eng",',
|
|
1114
|
+
'URI="text"\n',
|
|
1115
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,vtt",',
|
|
1116
|
+
'RESOLUTION=960x540,FRAME-RATE=60,SUBTITLES="sub1"\n',
|
|
1117
|
+
'video\n',
|
|
1118
|
+
].join('');
|
|
1119
|
+
|
|
1120
|
+
const media = [
|
|
1121
|
+
'#EXTM3U\n',
|
|
1122
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
1123
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
1124
|
+
'#EXTINF:5,\n',
|
|
1125
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
1126
|
+
'main.mp4',
|
|
1127
|
+
].join('');
|
|
1128
|
+
|
|
1129
|
+
const textMedia = [
|
|
1130
|
+
'#EXTM3U\n',
|
|
1131
|
+
'#EXT-X-PLAYLIST-TYPE:VOD\n',
|
|
1132
|
+
'#EXTINF:5,\n',
|
|
1133
|
+
'#EXT-X-BYTERANGE:121090@616\n',
|
|
1134
|
+
'main.foo',
|
|
1135
|
+
].join('');
|
|
1136
|
+
|
|
1137
|
+
/* eslint-disable indent */
|
|
1138
|
+
const manifest = new shaka.test.ManifestGenerator()
|
|
1139
|
+
.anyTimeline()
|
|
1140
|
+
.addPeriod(0)
|
|
1141
|
+
.addPartialVariant()
|
|
1142
|
+
.addPartialStream(ContentType.VIDEO)
|
|
1143
|
+
.build();
|
|
1144
|
+
/* eslint-enable indent */
|
|
1145
|
+
|
|
1146
|
+
fakeNetEngine
|
|
1147
|
+
.setResponseText('test:/master', master)
|
|
1148
|
+
.setResponseText('test:/audio', media)
|
|
1149
|
+
.setResponseText('test:/video', media)
|
|
1150
|
+
.setResponseText('test:/text', textMedia)
|
|
1151
|
+
.setResponseText('test:/main.foo', '')
|
|
1152
|
+
.setResponseValue('test:/init.mp4', initSegmentData)
|
|
1153
|
+
.setResponseValue('test:/main.mp4', segmentData);
|
|
1154
|
+
|
|
1155
|
+
config.hls.ignoreTextStreamFailures = true;
|
|
1156
|
+
const actual = await parser.start('test:/master', playerInterface);
|
|
1157
|
+
expect(actual).toEqual(manifest);
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1110
1160
|
it('parses video described by a media tag', async () => {
|
|
1111
1161
|
const master = [
|
|
1112
1162
|
'#EXTM3U\n',
|
|
@@ -193,6 +193,18 @@ describe('MediaSourceEngine', function() {
|
|
|
193
193
|
expect(mediaSource.duration).toBeCloseTo(30);
|
|
194
194
|
});
|
|
195
195
|
|
|
196
|
+
it('does not throw if endOfStrem called more than once', async () => {
|
|
197
|
+
const initObject = new Map();
|
|
198
|
+
initObject.set(ContentType.VIDEO, getFakeStream(metadata.video));
|
|
199
|
+
await mediaSourceEngine.init(initObject, false);
|
|
200
|
+
await mediaSourceEngine.setDuration(presentationDuration);
|
|
201
|
+
await appendInit(ContentType.VIDEO);
|
|
202
|
+
await append(ContentType.VIDEO, 1);
|
|
203
|
+
// Call endOfStream twice. There should be no exception.
|
|
204
|
+
await mediaSourceEngine.endOfStream();
|
|
205
|
+
await mediaSourceEngine.endOfStream();
|
|
206
|
+
});
|
|
207
|
+
|
|
196
208
|
it('queues operations', function(done) {
|
|
197
209
|
let resolutionOrder = [];
|
|
198
210
|
let requests = [];
|
|
@@ -212,6 +212,7 @@ describe('Playhead', function() {
|
|
|
212
212
|
|
|
213
213
|
expect(video.addEventListener).toHaveBeenCalledWith(
|
|
214
214
|
'seeking', jasmine.any(Function), jasmine.anything());
|
|
215
|
+
video.on['seeking']();
|
|
215
216
|
|
|
216
217
|
expect(playhead.getTime()).toBe(5);
|
|
217
218
|
expect(video.currentTime).toBe(5);
|
|
@@ -239,6 +240,7 @@ describe('Playhead', function() {
|
|
|
239
240
|
Util.spyFunc(onSeek),
|
|
240
241
|
Util.spyFunc(onEvent));
|
|
241
242
|
|
|
243
|
+
video.on['seeking']();
|
|
242
244
|
expect(playhead.getTime()).toBe(5);
|
|
243
245
|
expect(video.currentTime).toBe(5);
|
|
244
246
|
|
package/test/player_external.js
CHANGED
|
@@ -89,7 +89,7 @@ describe('Player', () => {
|
|
|
89
89
|
|
|
90
90
|
let wit = asset.focus ? fit : it;
|
|
91
91
|
wit(testName, async () => {
|
|
92
|
-
if (asset.
|
|
92
|
+
if (!asset.isClear() &&
|
|
93
93
|
!asset.drm.some((keySystem) => support.drm[keySystem])) {
|
|
94
94
|
pending('None of the required key systems are supported.');
|
|
95
95
|
}
|
|
@@ -102,7 +102,8 @@ describe('Player', () => {
|
|
|
102
102
|
if (asset.features.includes(Feature.MP4)) {
|
|
103
103
|
mimeTypes.push('video/mp4');
|
|
104
104
|
}
|
|
105
|
-
if (
|
|
105
|
+
if (mimeTypes.length &&
|
|
106
|
+
!mimeTypes.some((type) => support.media[type])) {
|
|
106
107
|
pending('None of the required MIME types are supported.');
|
|
107
108
|
}
|
|
108
109
|
}
|
|
@@ -464,6 +464,81 @@ describe('Player', function() {
|
|
|
464
464
|
}).catch(fail).then(done);
|
|
465
465
|
});
|
|
466
466
|
});
|
|
467
|
+
|
|
468
|
+
describe('tracks', () => {
|
|
469
|
+
// This is a regression test for b/138941217, in which tracks briefly
|
|
470
|
+
// vanished during the loading process. On Chromecast devices, where the
|
|
471
|
+
// timing is very different from on desktop, this could occur such that
|
|
472
|
+
// there were no tracks after load() is resolved.
|
|
473
|
+
// This is an integration test so that we can check the behavior of the
|
|
474
|
+
// Player against actual platform behavior on all supported platforms.
|
|
475
|
+
it('remain available at every stage of loading', async () => {
|
|
476
|
+
let tracksFound = false;
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* @param {string} when When the check takes place.
|
|
480
|
+
*
|
|
481
|
+
* Will fail the test if tracks disappear after they first become
|
|
482
|
+
* available.
|
|
483
|
+
*/
|
|
484
|
+
const checkTracks = (when) => {
|
|
485
|
+
// If tracks have already been found, expect them to still be found.
|
|
486
|
+
const tracksNow = player.getVariantTracks().length != 0;
|
|
487
|
+
if (tracksFound) {
|
|
488
|
+
expect(tracksNow).toBe(true);
|
|
489
|
+
} else {
|
|
490
|
+
// If tracks are now found, they should not, at any point during
|
|
491
|
+
// the loading process, disappear again.
|
|
492
|
+
if (tracksNow) {
|
|
493
|
+
tracksFound = true;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
shaka.log.debug(
|
|
497
|
+
'checkTracks', when,
|
|
498
|
+
'tracksFound=', tracksFound,
|
|
499
|
+
'tracksNow=', tracksNow);
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
/** @param {Event} event */
|
|
503
|
+
const checkOnEvent = (event) => {
|
|
504
|
+
checkTracks(event.type + ' event');
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
// On each of these events, we will notice when tracks first appear, and
|
|
508
|
+
// verify that they never disappear at any point in the loading sequence.
|
|
509
|
+
eventManager.listen(video, 'canplay', checkOnEvent);
|
|
510
|
+
eventManager.listen(video, 'canplaythrough', checkOnEvent);
|
|
511
|
+
eventManager.listen(video, 'durationchange', checkOnEvent);
|
|
512
|
+
eventManager.listen(video, 'emptied', checkOnEvent);
|
|
513
|
+
eventManager.listen(video, 'loadeddata', checkOnEvent);
|
|
514
|
+
eventManager.listen(video, 'loadedmetadata', checkOnEvent);
|
|
515
|
+
eventManager.listen(video, 'loadstart', checkOnEvent);
|
|
516
|
+
eventManager.listen(video, 'pause', checkOnEvent);
|
|
517
|
+
eventManager.listen(video, 'play', checkOnEvent);
|
|
518
|
+
eventManager.listen(video, 'playing', checkOnEvent);
|
|
519
|
+
eventManager.listen(video, 'seeked', checkOnEvent);
|
|
520
|
+
eventManager.listen(video, 'seeking', checkOnEvent);
|
|
521
|
+
eventManager.listen(video, 'stalled', checkOnEvent);
|
|
522
|
+
eventManager.listen(video, 'waiting', checkOnEvent);
|
|
523
|
+
eventManager.listen(player, 'trackschanged', checkOnEvent);
|
|
524
|
+
|
|
525
|
+
const waiter = (new shaka.test.Waiter(eventManager)).timeoutAfter(10);
|
|
526
|
+
const canPlayThrough = waiter.waitForEvent(video, 'canplaythrough');
|
|
527
|
+
|
|
528
|
+
// Important: use a stream that starts somewhere other than zero, so that
|
|
529
|
+
// the video element's time is initially different from the start time of
|
|
530
|
+
// playback, and there is no content at time zero.
|
|
531
|
+
await player.load('test:sintel_start_at_3_compiled', 5);
|
|
532
|
+
shaka.log.debug('load resolved');
|
|
533
|
+
|
|
534
|
+
// When load is resolved(), tracks should definitely exist.
|
|
535
|
+
expect(tracksFound).toBe(true);
|
|
536
|
+
|
|
537
|
+
// Let the test keep running until we can play through. In the original
|
|
538
|
+
// bug, tracks would disappear _after_ load() on some platforms.
|
|
539
|
+
await canPlayThrough;
|
|
540
|
+
});
|
|
541
|
+
});
|
|
467
542
|
});
|
|
468
543
|
|
|
469
544
|
// TODO(vaage): Try to group the stat tests together.
|
|
@@ -187,6 +187,12 @@ jasmine.Matchers.prototype.toHaveBeenCalledOnceMore = function() {};
|
|
|
187
187
|
*/
|
|
188
188
|
jasmine.Matchers.prototype.toHaveBeenCalledOnceMoreWith = function(args) {};
|
|
189
189
|
|
|
190
|
+
/**
|
|
191
|
+
* @param {string} message
|
|
192
|
+
* @return {!jasmine.Matchers}
|
|
193
|
+
*/
|
|
194
|
+
jasmine.Matchers.prototype.withContext = function(message) {};
|
|
195
|
+
|
|
190
196
|
|
|
191
197
|
/** @type {!jasmine.MatchersAsync} */
|
|
192
198
|
jasmine.MatchersAsync.prototype.not;
|
|
@@ -126,6 +126,37 @@ shaka.test.TestScheme.DATA = {
|
|
|
126
126
|
duration: 30,
|
|
127
127
|
},
|
|
128
128
|
|
|
129
|
+
// Like 'sintel' above, but with a non-zero period start time.
|
|
130
|
+
// This helps expose edge cases around startup and live streams.
|
|
131
|
+
'sintel_start_at_3': {
|
|
132
|
+
periodStart: 3,
|
|
133
|
+
video: {
|
|
134
|
+
initSegmentUri: '/base/test/test/assets/sintel-video-init.mp4',
|
|
135
|
+
mdhdOffset: 0x1ba,
|
|
136
|
+
segmentUri: '/base/test/test/assets/sintel-video-segment.mp4',
|
|
137
|
+
tfdtOffset: 0x38,
|
|
138
|
+
segmentDuration: 10,
|
|
139
|
+
presentationTimeOffset: 0,
|
|
140
|
+
mimeType: 'video/mp4',
|
|
141
|
+
codecs: 'avc1.42c01e',
|
|
142
|
+
},
|
|
143
|
+
audio: {
|
|
144
|
+
initSegmentUri: '/base/test/test/assets/sintel-audio-init.mp4',
|
|
145
|
+
mdhdOffset: 0x1b6,
|
|
146
|
+
segmentUri: '/base/test/test/assets/sintel-audio-segment.mp4',
|
|
147
|
+
tfdtOffset: 0x3c,
|
|
148
|
+
segmentDuration: 10.005,
|
|
149
|
+
presentationTimeOffset: 0,
|
|
150
|
+
mimeType: 'audio/mp4',
|
|
151
|
+
codecs: 'mp4a.40.2',
|
|
152
|
+
},
|
|
153
|
+
text: {
|
|
154
|
+
uri: '/base/test/test/assets/text-clip.vtt',
|
|
155
|
+
mimeType: 'text/vtt',
|
|
156
|
+
},
|
|
157
|
+
duration: 30,
|
|
158
|
+
},
|
|
159
|
+
|
|
129
160
|
// Like 'sintel' above, but with languages and delayed setup.
|
|
130
161
|
// These extra features help expose some edge cases.
|
|
131
162
|
'sintel_realistic': {
|
|
@@ -477,7 +508,7 @@ shaka.test.TestScheme.createManifests = function(shaka, suffix) {
|
|
|
477
508
|
|
|
478
509
|
let gen = new windowShaka.test.ManifestGenerator(shaka)
|
|
479
510
|
.setPresentationDuration(data.duration)
|
|
480
|
-
.addPeriod(/* startTime= */ 0)
|
|
511
|
+
.addPeriod(/* startTime= */ data.periodStart || 0)
|
|
481
512
|
.addVariant(0);
|
|
482
513
|
|
|
483
514
|
if (data[ContentType.VIDEO]) {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2016 Google Inc.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
goog.provide('shaka.test.UiUtils');
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
shaka.test.UiUtils = class {
|
|
23
|
+
/**
|
|
24
|
+
* Simulates a native event (e.g. 'click') on the given element.
|
|
25
|
+
*
|
|
26
|
+
* @param {EventTarget} target
|
|
27
|
+
* @param {string} name
|
|
28
|
+
*/
|
|
29
|
+
static simulateEvent(target, name) {
|
|
30
|
+
const type = {
|
|
31
|
+
'click': 'MouseEvent',
|
|
32
|
+
'dblclick': 'MouseEvent',
|
|
33
|
+
}[name] || 'CustomEvent';
|
|
34
|
+
|
|
35
|
+
// Note we can't use the MouseEvent constructor since it isn't supported on
|
|
36
|
+
// IE11.
|
|
37
|
+
const event = document.createEvent(type);
|
|
38
|
+
event.initEvent(name, true, true);
|
|
39
|
+
target.dispatchEvent(event);
|
|
40
|
+
}
|
|
41
|
+
};
|