shaka-player 4.0.0 → 4.0.3
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/.github/config.yml +1 -0
- package/.github/workflows/release-please.yaml +6 -11
- package/AUTHORS +1 -0
- package/CHANGELOG.md +39 -0
- package/CONTRIBUTORS +1 -0
- package/README.md +4 -4
- package/build/misspellings.txt +1 -0
- package/build/test.py +1 -1
- package/demo/common/assets.js +1 -1
- package/demo/demo.less +0 -1
- package/demo/main.js +3 -3
- package/dist/controls.css +3 -3
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +3 -3
- package/dist/demo.compiled.debug.map +2 -2
- package/dist/demo.compiled.js +3 -3
- package/dist/demo.compiled.map +2 -2
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +1 -1
- package/dist/locales.js +33 -33
- package/dist/receiver.compiled.debug.js +1 -1
- package/dist/receiver.compiled.debug.map +1 -1
- package/dist/receiver.compiled.js +1 -1
- package/dist/receiver.compiled.map +1 -1
- package/dist/shaka-player.compiled.d.ts +2 -1
- package/dist/shaka-player.compiled.debug.d.ts +2 -1
- package/dist/shaka-player.compiled.debug.externs.js +4 -2
- package/dist/shaka-player.compiled.debug.js +278 -267
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +4 -2
- package/dist/shaka-player.compiled.js +815 -806
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +2 -1
- package/dist/shaka-player.ui.debug.d.ts +2 -1
- package/dist/shaka-player.ui.debug.externs.js +4 -2
- package/dist/shaka-player.ui.debug.js +305 -293
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +4 -2
- package/dist/shaka-player.ui.js +966 -959
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/upgrade.md +4 -2
- package/externs/shaka/player.js +15 -2
- package/karma.conf.js +4 -0
- package/lib/abr/simple_abr_manager.js +2 -1
- package/lib/dash/dash_parser.js +2 -2
- package/lib/hls/hls_parser.js +211 -82
- package/lib/media/drm_engine.js +19 -0
- package/lib/media/media_source_engine.js +91 -37
- package/lib/media/segment_index.js +26 -1
- package/lib/media/segment_reference.js +10 -3
- package/lib/media/streaming_engine.js +2 -3
- package/lib/offline/indexeddb/storage_mechanism.js +4 -2
- package/lib/player.js +165 -107
- package/lib/text/vtt_text_parser.js +7 -7
- package/lib/util/config_utils.js +25 -0
- package/lib/util/error.js +7 -0
- package/lib/util/manifest_parser_utils.js +1 -1
- package/lib/util/mime_utils.js +62 -6
- package/lib/util/periods.js +5 -4
- package/lib/util/player_configuration.js +14 -14
- package/lib/util/stream_utils.js +5 -2
- package/package.json +6 -3
- package/test/hls/hls_live_unit.js +79 -7
- package/test/hls/hls_parser_unit.js +102 -8
- package/test/hls/manifest_text_parser_unit.js +19 -0
- package/test/offline/indexeddb_storage_unit.js +1 -1
- package/test/offline/storage_compatibility_unit.js +1 -1
- package/test/player_external.js +7 -1
- package/test/player_integration.js +6 -10
- package/test/player_src_equals_integration.js +81 -83
- package/test/player_unit.js +98 -1
- package/test/test/boot.js +9 -0
- package/test/test/externs/filters.js +4 -0
- package/test/test/util/simple_fakes.js +31 -2
- package/test/text/vtt_text_parser_unit.js +64 -5
- package/test/util/mime_utils_unit.js +54 -0
- package/test/util/periods_unit.js +79 -0
- package/ui/less/containers.less +1 -1
- package/ui/less/general.less +1 -1
- package/ui/less/range_elements.less +1 -0
- package/ui/resolution_selection.js +1 -1
- package/.github/pull_request_template.md +0 -10
- package/docs/tutorials/gsoc-ideas-list.md +0 -3
package/lib/util/mime_utils.js
CHANGED
|
@@ -8,6 +8,7 @@ goog.provide('shaka.util.MimeUtils');
|
|
|
8
8
|
|
|
9
9
|
goog.require('shaka.dependencies');
|
|
10
10
|
goog.require('shaka.media.Transmuxer');
|
|
11
|
+
goog.require('shaka.util.ManifestParserUtils');
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* @summary A set of utility functions for dealing with MIME types.
|
|
@@ -34,6 +35,8 @@ shaka.util.MimeUtils = class {
|
|
|
34
35
|
/**
|
|
35
36
|
* Takes a MIME type and a codecs string and produces the full MIME
|
|
36
37
|
* type. If it's a transport stream, convert its codecs to MP4 codecs.
|
|
38
|
+
* Otherwise for multiplexed content, convert the video MIME types to
|
|
39
|
+
* their audio equivalents if the content type is audio.
|
|
37
40
|
*
|
|
38
41
|
* @param {string} mimeType
|
|
39
42
|
* @param {string} codecs
|
|
@@ -42,13 +45,19 @@ shaka.util.MimeUtils = class {
|
|
|
42
45
|
*/
|
|
43
46
|
static getFullOrConvertedType(mimeType, codecs, contentType) {
|
|
44
47
|
const fullMimeType = shaka.util.MimeUtils.getFullType(mimeType, codecs);
|
|
48
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
45
49
|
|
|
46
|
-
if (
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
if (shaka.media.Transmuxer.isTsContainer(fullMimeType)) {
|
|
51
|
+
if (shaka.dependencies.muxjs()) {
|
|
52
|
+
return shaka.media.Transmuxer.convertTsCodecs(
|
|
53
|
+
contentType, fullMimeType);
|
|
54
|
+
}
|
|
55
|
+
} else if (contentType == ContentType.AUDIO) {
|
|
56
|
+
// video/mp2t is the correct mime type for TS audio, so only replace the
|
|
57
|
+
// word "video" with "audio" for non-TS audio content.
|
|
58
|
+
return fullMimeType.replace('video', 'audio');
|
|
49
59
|
}
|
|
50
|
-
|
|
51
|
-
return shaka.media.Transmuxer.convertTsCodecs(contentType, fullMimeType);
|
|
60
|
+
return fullMimeType;
|
|
52
61
|
}
|
|
53
62
|
|
|
54
63
|
|
|
@@ -96,6 +105,53 @@ shaka.util.MimeUtils = class {
|
|
|
96
105
|
return codecs.split(',');
|
|
97
106
|
}
|
|
98
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Get the normalized codec from a codec string,
|
|
110
|
+
* independently of their container.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} codecString
|
|
113
|
+
* @return {string}
|
|
114
|
+
*/
|
|
115
|
+
static getNormalizedCodec(codecString) {
|
|
116
|
+
const parts =
|
|
117
|
+
shaka.util.MimeUtils.getCodecParts_(codecString);
|
|
118
|
+
const base = parts[0];
|
|
119
|
+
const profile = parts[1].toLowerCase();
|
|
120
|
+
switch (true) {
|
|
121
|
+
case base === 'mp4a' && profile === '69':
|
|
122
|
+
case base === 'mp4a' && profile === '6b':
|
|
123
|
+
return 'mp3';
|
|
124
|
+
case base === 'mp4a' && profile === '66':
|
|
125
|
+
case base === 'mp4a' && profile === '67':
|
|
126
|
+
case base === 'mp4a' && profile === '68':
|
|
127
|
+
case base === 'mp4a' && profile === '40.2':
|
|
128
|
+
case base === 'mp4a' && profile === '40.02':
|
|
129
|
+
case base === 'mp4a' && profile === '40.5':
|
|
130
|
+
case base === 'mp4a' && profile === '40.05':
|
|
131
|
+
case base === 'mp4a' && profile === '40.29':
|
|
132
|
+
case base === 'mp4a' && profile === '40.42': // Extended HE-AAC
|
|
133
|
+
return 'aac';
|
|
134
|
+
case base === 'mp4a' && profile === 'a5':
|
|
135
|
+
return 'ac-3'; // Dolby Digital
|
|
136
|
+
case base === 'mp4a' && profile === 'a6':
|
|
137
|
+
return 'ec-3'; // Dolby Digital Plus
|
|
138
|
+
case base === 'mp4a' && profile === 'b2':
|
|
139
|
+
return 'dtsx'; // DTS:X
|
|
140
|
+
case base === 'mp4a' && profile === 'a9':
|
|
141
|
+
return 'dtsc'; // DTS Digital Surround
|
|
142
|
+
case base === 'avc1':
|
|
143
|
+
case base === 'avc3':
|
|
144
|
+
return 'avc'; // H264
|
|
145
|
+
case base === 'hvc1':
|
|
146
|
+
case base === 'hev1':
|
|
147
|
+
return 'hevc'; // H265
|
|
148
|
+
case base === 'dvh1':
|
|
149
|
+
case base === 'dvhe':
|
|
150
|
+
return 'dovi'; // Dolby Vision
|
|
151
|
+
}
|
|
152
|
+
return base;
|
|
153
|
+
}
|
|
154
|
+
|
|
99
155
|
/**
|
|
100
156
|
* Get the base codec from a codec string.
|
|
101
157
|
*
|
|
@@ -152,7 +208,7 @@ shaka.util.MimeUtils = class {
|
|
|
152
208
|
|
|
153
209
|
const base = parts[0];
|
|
154
210
|
|
|
155
|
-
parts.
|
|
211
|
+
parts.shift();
|
|
156
212
|
const profile = parts.join('.');
|
|
157
213
|
|
|
158
214
|
// Make sure that we always return a "base" and "profile".
|
package/lib/util/periods.js
CHANGED
|
@@ -573,8 +573,8 @@ shaka.util.PeriodCombiner = class {
|
|
|
573
573
|
// TODO(#1528): Consider changing this when we support codec switching.
|
|
574
574
|
const hasCodec = outputStreams.some((s) => {
|
|
575
575
|
return s.mimeType == stream.mimeType &&
|
|
576
|
-
shaka.util.MimeUtils.
|
|
577
|
-
shaka.util.MimeUtils.
|
|
576
|
+
shaka.util.MimeUtils.getNormalizedCodec(s.codecs) ==
|
|
577
|
+
shaka.util.MimeUtils.getNormalizedCodec(stream.codecs);
|
|
578
578
|
});
|
|
579
579
|
if (!hasCodec) {
|
|
580
580
|
continue;
|
|
@@ -1050,10 +1050,11 @@ shaka.util.PeriodCombiner = class {
|
|
|
1050
1050
|
* @private
|
|
1051
1051
|
*/
|
|
1052
1052
|
static areAVStreamsCompatible_(outputStream, candidate) {
|
|
1053
|
-
const
|
|
1053
|
+
const getCodec = (codecs) =>
|
|
1054
|
+
shaka.util.MimeUtils.getNormalizedCodec(codecs);
|
|
1054
1055
|
// Check MIME type and codecs, which should always be the same.
|
|
1055
1056
|
if (candidate.mimeType != outputStream.mimeType ||
|
|
1056
|
-
|
|
1057
|
+
getCodec(candidate.codecs) != getCodec(outputStream.codecs)) {
|
|
1057
1058
|
return false;
|
|
1058
1059
|
}
|
|
1059
1060
|
|
|
@@ -16,14 +16,6 @@ goog.require('shaka.util.ManifestParserUtils');
|
|
|
16
16
|
goog.require('shaka.util.Platform');
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
// TODO(vaage): Many times in our configs, we need to create an empty
|
|
20
|
-
// implementation of a method, but to avoid closure from removing unused
|
|
21
|
-
// parameters (and breaking our merge config code) we need to use each
|
|
22
|
-
// parameter. Is there a better solution to this problem than what we are
|
|
23
|
-
// doing now?
|
|
24
|
-
//
|
|
25
|
-
// NOTE: Chrome App Content Security Policy prohibits usage of new Function()
|
|
26
|
-
|
|
27
19
|
/**
|
|
28
20
|
* @final
|
|
29
21
|
* @export
|
|
@@ -74,6 +66,11 @@ shaka.util.PlayerConfiguration = class {
|
|
|
74
66
|
clearKeys: {}, // key is arbitrary key system ID, value must be string
|
|
75
67
|
advanced: {}, // key is arbitrary key system ID, value is a record type
|
|
76
68
|
delayLicenseRequestUntilPlayed: false,
|
|
69
|
+
initDataTransform: (initData, initDataType, drmInfo) => {
|
|
70
|
+
return shaka.util.ConfigUtils.referenceParametersAndReturn(
|
|
71
|
+
[initData, initDataType, drmInfo],
|
|
72
|
+
initData);
|
|
73
|
+
},
|
|
77
74
|
logLicenseExchange: false,
|
|
78
75
|
updateExpirationTime: 1,
|
|
79
76
|
preferredKeySystems: [],
|
|
@@ -113,11 +110,10 @@ shaka.util.PlayerConfiguration = class {
|
|
|
113
110
|
'urn:uuid:f239e769-efa3-4850-9c16-a903c6932efb':
|
|
114
111
|
'com.adobe.primetime',
|
|
115
112
|
},
|
|
116
|
-
// Need some operation in the callback or else closure may remove calls
|
|
117
|
-
// to the function as it would be a no-op. The operation can't just be
|
|
118
|
-
// a log message, because those are stripped in the compiled build.
|
|
119
113
|
manifestPreprocessor: (element) => {
|
|
120
|
-
return
|
|
114
|
+
return shaka.util.ConfigUtils.referenceParametersAndReturn(
|
|
115
|
+
[element],
|
|
116
|
+
element);
|
|
121
117
|
},
|
|
122
118
|
},
|
|
123
119
|
hls: {
|
|
@@ -138,7 +134,9 @@ shaka.util.PlayerConfiguration = class {
|
|
|
138
134
|
// log message, because those are stripped in the compiled build.
|
|
139
135
|
failureCallback: (error) => {
|
|
140
136
|
shaka.log.error('Unhandled streaming error', error);
|
|
141
|
-
return
|
|
137
|
+
return shaka.util.ConfigUtils.referenceParametersAndReturn(
|
|
138
|
+
[error],
|
|
139
|
+
undefined);
|
|
142
140
|
},
|
|
143
141
|
// When low latency streaming is enabled, rebufferingGoal will default to
|
|
144
142
|
// 0.01 if not specified.
|
|
@@ -214,7 +212,9 @@ shaka.util.PlayerConfiguration = class {
|
|
|
214
212
|
// to the function as it would be a no-op. The operation can't just be a
|
|
215
213
|
// log message, because those are stripped in the compiled build.
|
|
216
214
|
progressCallback: (content, progress) => {
|
|
217
|
-
return
|
|
215
|
+
return shaka.util.ConfigUtils.referenceParametersAndReturn(
|
|
216
|
+
[content, progress],
|
|
217
|
+
undefined);
|
|
218
218
|
},
|
|
219
219
|
|
|
220
220
|
// By default we use persistent licenses as forces errors to surface if
|
package/lib/util/stream_utils.js
CHANGED
|
@@ -283,12 +283,14 @@ shaka.util.StreamUtils = class {
|
|
|
283
283
|
// both be considered the same codec: avc1.42c01e, avc1.4d401f
|
|
284
284
|
let baseVideoCodec = '';
|
|
285
285
|
if (variant.video) {
|
|
286
|
-
baseVideoCodec =
|
|
286
|
+
baseVideoCodec =
|
|
287
|
+
shaka.util.MimeUtils.getNormalizedCodec(variant.video.codecs);
|
|
287
288
|
}
|
|
288
289
|
|
|
289
290
|
let baseAudioCodec = '';
|
|
290
291
|
if (variant.audio) {
|
|
291
|
-
baseAudioCodec =
|
|
292
|
+
baseAudioCodec =
|
|
293
|
+
shaka.util.MimeUtils.getNormalizedCodec(variant.audio.codecs);
|
|
292
294
|
}
|
|
293
295
|
|
|
294
296
|
return baseVideoCodec + '-' + baseAudioCodec;
|
|
@@ -1006,6 +1008,7 @@ shaka.util.StreamUtils = class {
|
|
|
1006
1008
|
track.channelsCount = audio.channelsCount;
|
|
1007
1009
|
track.audioSamplingRate = audio.audioSamplingRate;
|
|
1008
1010
|
track.audioBandwidth = audio.bandwidth || null;
|
|
1011
|
+
track.spatialAudio = audio.spatialAudio;
|
|
1009
1012
|
track.label = audio.label;
|
|
1010
1013
|
track.audioRoles = audio.roles;
|
|
1011
1014
|
}
|
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": "4.0.
|
|
4
|
+
"version": "4.0.3",
|
|
5
5
|
"homepage": "https://github.com/shaka-project/shaka-player",
|
|
6
6
|
"author": "Google",
|
|
7
7
|
"maintainers": [
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"less": "https://gitpkg.now.sh/joeyparrish/less.js/packages/less?28c63a43",
|
|
51
51
|
"less-plugin-clean-css": "github:austingardner/less-plugin-clean-css#4e9e77bf",
|
|
52
52
|
"material-design-lite": "^1.3.0",
|
|
53
|
-
"mux.js": "^
|
|
53
|
+
"mux.js": "^6.1.0",
|
|
54
54
|
"open-sans-fonts": "^1.6.2",
|
|
55
55
|
"postcss-less": "^6.0.0",
|
|
56
56
|
"pwacompat": "^2.0.17",
|
|
@@ -96,6 +96,9 @@
|
|
|
96
96
|
"prepublishOnly": "python build/checkversion.py && python build/all.py --force"
|
|
97
97
|
},
|
|
98
98
|
"dependencies": {
|
|
99
|
-
"eme-encryption-scheme-polyfill": "^2.0.
|
|
99
|
+
"eme-encryption-scheme-polyfill": "^2.0.5"
|
|
100
|
+
},
|
|
101
|
+
"engines": {
|
|
102
|
+
"node": ">=14"
|
|
100
103
|
}
|
|
101
104
|
}
|
|
@@ -539,11 +539,13 @@ describe('HlsParser live', () => {
|
|
|
539
539
|
ManifestParser.verifySegmentIndex(video, [ref1, ref2]);
|
|
540
540
|
});
|
|
541
541
|
|
|
542
|
+
// Test for https://github.com/shaka-project/shaka-player/issues/4223
|
|
542
543
|
it('parses streams with partial and preload hinted segments', async () => {
|
|
543
544
|
playerInterface.isLowLatencyMode = () => true;
|
|
544
545
|
const mediaWithPartialSegments = [
|
|
545
546
|
'#EXTM3U\n',
|
|
546
547
|
'#EXT-X-TARGETDURATION:5\n',
|
|
548
|
+
'#EXT-X-PART-INF:PART-TARGET=1.5\n',
|
|
547
549
|
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
548
550
|
'#EXT-X-MEDIA-SEQUENCE:0\n',
|
|
549
551
|
// ref includes partialRef, partialRef2
|
|
@@ -576,26 +578,73 @@ describe('HlsParser live', () => {
|
|
|
576
578
|
'test:/partial2.mp4', 2, 4, /* syncTime= */ null,
|
|
577
579
|
/* baseUri= */ '', /* startByte= */ 200, /* endByte= */ 429);
|
|
578
580
|
|
|
581
|
+
const ref = makeReference(
|
|
582
|
+
'test:/main.mp4', 0, 4, /* syncTime= */ null,
|
|
583
|
+
/* baseUri= */ '', /* startByte= */ 0, /* endByte= */ 429,
|
|
584
|
+
/* timestampOffset= */ 0, [partialRef, partialRef2]);
|
|
585
|
+
|
|
579
586
|
const partialRef3 = makeReference(
|
|
580
587
|
'test:/partial.mp4', 4, 6, /* syncTime= */ null,
|
|
581
588
|
/* baseUri= */ '', /* startByte= */ 0, /* endByte= */ 209);
|
|
582
589
|
|
|
583
|
-
// A preload hinted partial segment doesn't have duration information,
|
|
584
|
-
// so its startTime and endTime are the same.
|
|
585
590
|
const preloadRef = makeReference(
|
|
586
|
-
'test:/partial.mp4', 6,
|
|
591
|
+
'test:/partial.mp4', 6, 7.5, /* syncTime= */ null,
|
|
587
592
|
/* baseUri= */ '', /* startByte= */ 210, /* endByte= */ null);
|
|
588
593
|
|
|
594
|
+
// ref2 is not fully published yet, so it doesn't have a segment uri.
|
|
595
|
+
const ref2 = makeReference(
|
|
596
|
+
'', 4, 7.5, /* syncTime= */ null,
|
|
597
|
+
/* baseUri= */ '', /* startByte= */ 0, /* endByte= */ null,
|
|
598
|
+
/* timestampOffset= */ 0, [partialRef3, preloadRef]);
|
|
599
|
+
|
|
600
|
+
const manifest = await parser.start('test:/master', playerInterface);
|
|
601
|
+
const video = manifest.variants[0].video;
|
|
602
|
+
await video.createSegmentIndex();
|
|
603
|
+
ManifestParser.verifySegmentIndex(video, [ref, ref2]);
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
// Test for https://github.com/shaka-project/shaka-player/issues/4223
|
|
607
|
+
it('ignores preload hinted segments without target duration', async () => {
|
|
608
|
+
playerInterface.isLowLatencyMode = () => true;
|
|
609
|
+
|
|
610
|
+
// Missing PART-TARGET, so preload hints are skipped.
|
|
611
|
+
const mediaWithPartialSegments = [
|
|
612
|
+
'#EXTM3U\n',
|
|
613
|
+
'#EXT-X-TARGETDURATION:5\n',
|
|
614
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
615
|
+
'#EXT-X-MEDIA-SEQUENCE:0\n',
|
|
616
|
+
'#EXTINF:4,\n',
|
|
617
|
+
// ref1
|
|
618
|
+
'main.mp4\n',
|
|
619
|
+
// ref2 includes partialRef, but not preloadRef
|
|
620
|
+
// partialRef
|
|
621
|
+
'#EXT-X-PART:DURATION=2,URI="partial.mp4",BYTERANGE=210@0\n',
|
|
622
|
+
// preloadRef
|
|
623
|
+
'#EXT-X-PRELOAD-HINT:TYPE=PART,URI="partial.mp4",BYTERANGE-START=210\n',
|
|
624
|
+
].join('');
|
|
625
|
+
|
|
626
|
+
fakeNetEngine
|
|
627
|
+
.setResponseText('test:/master', master)
|
|
628
|
+
.setResponseText('test:/video', mediaWithPartialSegments)
|
|
629
|
+
.setResponseValue('test:/init.mp4', initSegmentData)
|
|
630
|
+
.setResponseValue('test:/main.mp4', segmentData)
|
|
631
|
+
.setResponseValue('test:/partial.mp4', segmentData)
|
|
632
|
+
.setResponseValue('test:/partial2.mp4', segmentData);
|
|
633
|
+
|
|
589
634
|
const ref = makeReference(
|
|
590
635
|
'test:/main.mp4', 0, 4, /* syncTime= */ null,
|
|
591
|
-
/* baseUri= */ '', /* startByte= */ 0, /* endByte= */
|
|
592
|
-
/* timestampOffset= */ 0, [
|
|
636
|
+
/* baseUri= */ '', /* startByte= */ 0, /* endByte= */ null,
|
|
637
|
+
/* timestampOffset= */ 0, []);
|
|
638
|
+
|
|
639
|
+
const partialRef = makeReference(
|
|
640
|
+
'test:/partial.mp4', 4, 6, /* syncTime= */ null,
|
|
641
|
+
/* baseUri= */ '', /* startByte= */ 0, /* endByte= */ 209);
|
|
593
642
|
|
|
594
643
|
// ref2 is not fully published yet, so it doesn't have a segment uri.
|
|
595
644
|
const ref2 = makeReference(
|
|
596
645
|
'', 4, 6, /* syncTime= */ null,
|
|
597
|
-
/* baseUri= */ '', /* startByte= */ 0, /* endByte= */
|
|
598
|
-
/* timestampOffset= */ 0, [
|
|
646
|
+
/* baseUri= */ '', /* startByte= */ 0, /* endByte= */ 209,
|
|
647
|
+
/* timestampOffset= */ 0, [partialRef]);
|
|
599
648
|
|
|
600
649
|
const manifest = await parser.start('test:/master', playerInterface);
|
|
601
650
|
const video = manifest.variants[0].video;
|
|
@@ -603,6 +652,29 @@ describe('HlsParser live', () => {
|
|
|
603
652
|
ManifestParser.verifySegmentIndex(video, [ref, ref2]);
|
|
604
653
|
});
|
|
605
654
|
|
|
655
|
+
// Test for https://github.com/shaka-project/shaka-player/issues/4185
|
|
656
|
+
it('does not fail on preload hints with LL mode off', async () => {
|
|
657
|
+
// LL mode must be off for this test!
|
|
658
|
+
playerInterface.isLowLatencyMode = () => false;
|
|
659
|
+
|
|
660
|
+
const mediaWithPartialSegments = [
|
|
661
|
+
'#EXTM3U\n',
|
|
662
|
+
'#EXT-X-TARGETDURATION:5\n',
|
|
663
|
+
'#EXT-X-PART-INF:PART-TARGET=1.5\n',
|
|
664
|
+
'#EXTINF:4,\n',
|
|
665
|
+
'main.mp4\n',
|
|
666
|
+
'#EXT-X-PART:DURATION=2,URI="partial.mp4",BYTERANGE=210@0\n',
|
|
667
|
+
'#EXT-X-PRELOAD-HINT:TYPE=PART,URI="partial.mp4",BYTERANGE-START=210\n',
|
|
668
|
+
].join('');
|
|
669
|
+
|
|
670
|
+
fakeNetEngine
|
|
671
|
+
.setResponseText('test:/master', master)
|
|
672
|
+
.setResponseText('test:/video', mediaWithPartialSegments);
|
|
673
|
+
|
|
674
|
+
// If this throws, the test fails. Otherwise, it passes.
|
|
675
|
+
await parser.start('test:/master', playerInterface);
|
|
676
|
+
});
|
|
677
|
+
|
|
606
678
|
describe('update', () => {
|
|
607
679
|
it('adds new segments when they appear', async () => {
|
|
608
680
|
const ref1 = makeReference(
|
|
@@ -2021,6 +2021,10 @@ describe('HlsParser', () => {
|
|
|
2021
2021
|
});
|
|
2022
2022
|
|
|
2023
2023
|
describe('produces syncTime', () => {
|
|
2024
|
+
// Corresponds to "2000-01-01T00:00:00.00Z".
|
|
2025
|
+
// All the PROGRAM-DATE-TIME values in the tests below are at or after this.
|
|
2026
|
+
const syncTimeBase = 946684800;
|
|
2027
|
+
|
|
2024
2028
|
/**
|
|
2025
2029
|
* @param {number} startTime
|
|
2026
2030
|
* @param {number} endTime
|
|
@@ -2039,9 +2043,10 @@ describe('HlsParser', () => {
|
|
|
2039
2043
|
/**
|
|
2040
2044
|
* @param {string} media
|
|
2041
2045
|
* @param {!Array.<number>} startTimes
|
|
2046
|
+
* @param {number} syncTimeOffset
|
|
2042
2047
|
* @param {(function(!shaka.media.SegmentReference))=} modifyFn
|
|
2043
2048
|
*/
|
|
2044
|
-
async function test(media, startTimes, modifyFn) {
|
|
2049
|
+
async function test(media, startTimes, syncTimeOffset, modifyFn) {
|
|
2045
2050
|
const master = [
|
|
2046
2051
|
'#EXTM3U\n',
|
|
2047
2052
|
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,vtt",',
|
|
@@ -2053,7 +2058,8 @@ describe('HlsParser', () => {
|
|
|
2053
2058
|
for (let i = 0; i < startTimes.length - 1; i++) {
|
|
2054
2059
|
const startTime = startTimes[i];
|
|
2055
2060
|
const endTime = startTimes[i + 1];
|
|
2056
|
-
const reference = makeReference(
|
|
2061
|
+
const reference = makeReference(
|
|
2062
|
+
startTime, endTime, syncTimeOffset + startTime);
|
|
2057
2063
|
if (modifyFn) {
|
|
2058
2064
|
modifyFn(reference);
|
|
2059
2065
|
}
|
|
@@ -2100,7 +2106,7 @@ describe('HlsParser', () => {
|
|
|
2100
2106
|
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
|
|
2101
2107
|
'#EXTINF:5,\n',
|
|
2102
2108
|
'main.mp4',
|
|
2103
|
-
].join(''), [0, 5, 10, 15, 20, 25]);
|
|
2109
|
+
].join(''), [0, 5, 10, 15, 20, 25], syncTimeBase + 5);
|
|
2104
2110
|
});
|
|
2105
2111
|
|
|
2106
2112
|
it('when some EXT-X-PROGRAM-DATE-TIME values are missing', async () => {
|
|
@@ -2122,7 +2128,7 @@ describe('HlsParser', () => {
|
|
|
2122
2128
|
'main.mp4\n',
|
|
2123
2129
|
'#EXTINF:4,\n',
|
|
2124
2130
|
'main.mp4',
|
|
2125
|
-
].join(''), [0, 2, 7, 12, 17, 19, 23]);
|
|
2131
|
+
].join(''), [0, 2, 7, 12, 17, 19, 23], syncTimeBase + 8);
|
|
2126
2132
|
});
|
|
2127
2133
|
|
|
2128
2134
|
it('except when ignoreManifestProgramDateTime is set', async () => {
|
|
@@ -2148,7 +2154,7 @@ describe('HlsParser', () => {
|
|
|
2148
2154
|
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
|
|
2149
2155
|
'#EXTINF:5,\n',
|
|
2150
2156
|
'main.mp4',
|
|
2151
|
-
].join(''), [0, 5, 10, 15, 20, 25], (reference) => {
|
|
2157
|
+
].join(''), [0, 5, 10, 15, 20, 25], syncTimeBase + 5, (reference) => {
|
|
2152
2158
|
reference.syncTime = null;
|
|
2153
2159
|
});
|
|
2154
2160
|
});
|
|
@@ -2176,11 +2182,11 @@ describe('HlsParser', () => {
|
|
|
2176
2182
|
'#EXT-X-PROGRAM-DATE-TIME:2000-01-01T00:00:25.00Z\n',
|
|
2177
2183
|
'#EXTINF:5,\n',
|
|
2178
2184
|
'main.mp4',
|
|
2179
|
-
].join(''), [0, 5, 10, 15, 20, 25], (reference) => {
|
|
2185
|
+
].join(''), [0, 5, 10, 15, 20, 25], syncTimeBase + 5, (reference) => {
|
|
2180
2186
|
if (reference.startTime == 10) {
|
|
2181
2187
|
reference.partialReferences = [
|
|
2182
|
-
makeReference(10, 12.5,
|
|
2183
|
-
makeReference(12.5, 15,
|
|
2188
|
+
makeReference(10, 12.5, syncTimeBase + 15),
|
|
2189
|
+
makeReference(12.5, 15, syncTimeBase + 17.5),
|
|
2184
2190
|
];
|
|
2185
2191
|
}
|
|
2186
2192
|
});
|
|
@@ -3555,4 +3561,92 @@ describe('HlsParser', () => {
|
|
|
3555
3561
|
|
|
3556
3562
|
await testHlsParser(media, '', manifest);
|
|
3557
3563
|
});
|
|
3564
|
+
|
|
3565
|
+
it('syncs on sequence with ignoreManifestProgramDateTime', async () => {
|
|
3566
|
+
const config = shaka.util.PlayerConfiguration.createDefault().manifest;
|
|
3567
|
+
config.hls.ignoreManifestProgramDateTime = true;
|
|
3568
|
+
parser.configure(config);
|
|
3569
|
+
|
|
3570
|
+
const master = [
|
|
3571
|
+
'#EXTM3U\n',
|
|
3572
|
+
'#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="eng",URI="audio"\n',
|
|
3573
|
+
'#EXT-X-STREAM-INF:BANDWIDTH=200,CODECS="avc1,mp4a",',
|
|
3574
|
+
'RESOLUTION=960x540,FRAME-RATE=60,AUDIO="aud1"\n',
|
|
3575
|
+
'video\n',
|
|
3576
|
+
].join('');
|
|
3577
|
+
|
|
3578
|
+
const video = [
|
|
3579
|
+
'#EXTM3U\n',
|
|
3580
|
+
'#EXT-X-TARGETDURATION:5\n',
|
|
3581
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
3582
|
+
'#EXT-X-MEDIA-SEQUENCE:1\n',
|
|
3583
|
+
'#EXTINF:5,\n',
|
|
3584
|
+
'video1.mp4\n',
|
|
3585
|
+
'#EXTINF:5,\n',
|
|
3586
|
+
'video2.mp4\n',
|
|
3587
|
+
'#EXTINF:5,\n',
|
|
3588
|
+
'video3.mp4\n',
|
|
3589
|
+
].join('');
|
|
3590
|
+
|
|
3591
|
+
const audio = [
|
|
3592
|
+
'#EXTM3U\n',
|
|
3593
|
+
'#EXT-X-TARGETDURATION:5\n',
|
|
3594
|
+
'#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
|
|
3595
|
+
'#EXT-X-MEDIA-SEQUENCE:3\n',
|
|
3596
|
+
'#EXTINF:5,\n',
|
|
3597
|
+
'audio3.mp4\n',
|
|
3598
|
+
'#EXTINF:5,\n',
|
|
3599
|
+
'audio4.mp4\n',
|
|
3600
|
+
].join('');
|
|
3601
|
+
|
|
3602
|
+
const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
|
|
3603
|
+
manifest.sequenceMode = true;
|
|
3604
|
+
manifest.anyTimeline();
|
|
3605
|
+
manifest.addPartialVariant((variant) => {
|
|
3606
|
+
variant.language = 'en';
|
|
3607
|
+
variant.bandwidth = 200;
|
|
3608
|
+
variant.addPartialStream(ContentType.VIDEO, (stream) => {
|
|
3609
|
+
stream.mime('video/mp4', 'avc1');
|
|
3610
|
+
stream.size(960, 540);
|
|
3611
|
+
stream.frameRate = 60;
|
|
3612
|
+
});
|
|
3613
|
+
variant.addPartialStream(ContentType.AUDIO, (stream) => {
|
|
3614
|
+
stream.language = 'en';
|
|
3615
|
+
stream.mime('audio/mp4', 'mp4a');
|
|
3616
|
+
});
|
|
3617
|
+
});
|
|
3618
|
+
});
|
|
3619
|
+
|
|
3620
|
+
fakeNetEngine
|
|
3621
|
+
.setResponseText('test:/master', master)
|
|
3622
|
+
.setResponseText('test:/audio', audio)
|
|
3623
|
+
.setResponseText('test:/video', video)
|
|
3624
|
+
.setResponseValue('test:/init.mp4', initSegmentData);
|
|
3625
|
+
|
|
3626
|
+
const actualManifest = await parser.start('test:/master', playerInterface);
|
|
3627
|
+
expect(actualManifest).toEqual(manifest);
|
|
3628
|
+
|
|
3629
|
+
const actualVideo = actualManifest.variants[0].video;
|
|
3630
|
+
await actualVideo.createSegmentIndex();
|
|
3631
|
+
goog.asserts.assert(actualVideo.segmentIndex != null, 'Null segmentIndex!');
|
|
3632
|
+
|
|
3633
|
+
const actualAudio = actualManifest.variants[0].audio;
|
|
3634
|
+
await actualAudio.createSegmentIndex();
|
|
3635
|
+
goog.asserts.assert(actualAudio.segmentIndex != null, 'Null segmentIndex!');
|
|
3636
|
+
|
|
3637
|
+
// Verify that the references are aligned on sequence number.
|
|
3638
|
+
const videoSegments = Array.from(actualVideo.segmentIndex);
|
|
3639
|
+
const audioSegments = Array.from(actualAudio.segmentIndex);
|
|
3640
|
+
|
|
3641
|
+
// The first two were dropped to align with the audio.
|
|
3642
|
+
expect(videoSegments.map((ref) => ref.getUris()[0])).toEqual([
|
|
3643
|
+
'test:/video3.mp4',
|
|
3644
|
+
]);
|
|
3645
|
+
// Audio has a 4th segment that video doesn't, but that doesn't get clipped
|
|
3646
|
+
// or used as the base. Alignment is truly based on media sequence number.
|
|
3647
|
+
expect(audioSegments.map((ref) => ref.getUris()[0])).toEqual([
|
|
3648
|
+
'test:/audio3.mp4',
|
|
3649
|
+
'test:/audio4.mp4',
|
|
3650
|
+
]);
|
|
3651
|
+
});
|
|
3558
3652
|
});
|
|
@@ -238,6 +238,25 @@ describe('ManifestTextParser', () => {
|
|
|
238
238
|
'#EXTM3U\n' +
|
|
239
239
|
'#EXT-X-MEDIA:CODECS="avc1.64002a,mp4a.40.2",AUDIO="a1,a2"',
|
|
240
240
|
|
|
241
|
+
// manifest URI:
|
|
242
|
+
'https://test/manifest.m3u8');
|
|
243
|
+
|
|
244
|
+
verifyPlaylist(
|
|
245
|
+
{
|
|
246
|
+
type: shaka.hls.PlaylistType.MASTER,
|
|
247
|
+
tags: [
|
|
248
|
+
new shaka.hls.Tag(/* id= */ 3, 'EXT-X-MEDIA',
|
|
249
|
+
[
|
|
250
|
+
new shaka.hls.Attribute('CODECS',
|
|
251
|
+
'av01.0.08M.08,mp4a.40.2'),
|
|
252
|
+
]),
|
|
253
|
+
],
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
// playlist text:
|
|
257
|
+
'#EXTM3U\n' +
|
|
258
|
+
'#EXT-X-MEDIA:CODECS="av01.0.08M.08,mp4a.40.2"',
|
|
259
|
+
|
|
241
260
|
// manifest URI:
|
|
242
261
|
'https://test/manifest.m3u8');
|
|
243
262
|
});
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* SPDX-License-Identifier: Apache-2.0
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
filterDescribe('IndexeddbStorageCell',
|
|
7
|
+
filterDescribe('IndexeddbStorageCell', offlineSupported, () => {
|
|
8
8
|
const IndexedDBUtils = shaka.test.IndexedDBUtils;
|
|
9
9
|
const OfflineUtils = shaka.test.OfflineUtils;
|
|
10
10
|
const Util = shaka.test.Util;
|
|
@@ -91,7 +91,7 @@ const compatibilityTestsMetadata = [
|
|
|
91
91
|
},
|
|
92
92
|
];
|
|
93
93
|
|
|
94
|
-
filterDescribe('Storage Compatibility',
|
|
94
|
+
filterDescribe('Storage Compatibility', offlineSupported, () => {
|
|
95
95
|
for (const metadata of compatibilityTestsMetadata) {
|
|
96
96
|
describe(metadata.name, () => {
|
|
97
97
|
makeTests(metadata);
|
package/test/player_external.js
CHANGED
|
@@ -114,7 +114,13 @@ describe('Player', () => {
|
|
|
114
114
|
const networkingEngine = player.getNetworkingEngine();
|
|
115
115
|
asset.applyFilters(networkingEngine);
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
// Rather than awaiting the load() method, catch any load() errors and
|
|
118
|
+
// wait on the 'canplay' event. This has the advantage that we will
|
|
119
|
+
// get better logging of the media state on a timeout, since that
|
|
120
|
+
// capabilitiy is built into the waiter for media element events.
|
|
121
|
+
player.load(asset.manifestUri).catch(fail);
|
|
122
|
+
await waiter.timeoutAfter(60).waitForEvent(video, 'canplay');
|
|
123
|
+
|
|
118
124
|
if (asset.features) {
|
|
119
125
|
const isLive = asset.features.includes(Feature.LIVE);
|
|
120
126
|
expect(player.isLive()).toBe(isLive);
|
|
@@ -1069,16 +1069,16 @@ describe('Player', () => {
|
|
|
1069
1069
|
});
|
|
1070
1070
|
}); // describe('unloading')
|
|
1071
1071
|
|
|
1072
|
-
describe('
|
|
1073
|
-
it('
|
|
1072
|
+
describe('addChaptersTrack', () => {
|
|
1073
|
+
it('adds external chapters in vtt format', async () => {
|
|
1074
1074
|
await player.load('test:sintel_no_text_compiled');
|
|
1075
1075
|
const locationUri = new goog.Uri(location.href);
|
|
1076
1076
|
const partialUri1 = new goog.Uri('/base/test/test/assets/chapters.vtt');
|
|
1077
1077
|
const absoluteUri1 = locationUri.resolve(partialUri1);
|
|
1078
1078
|
await player.addChaptersTrack(absoluteUri1.toString(), 'en');
|
|
1079
1079
|
|
|
1080
|
-
|
|
1081
|
-
|
|
1080
|
+
// Data should be available as soon as addChaptersTrack resolves.
|
|
1081
|
+
// See https://github.com/shaka-project/shaka-player/issues/4186
|
|
1082
1082
|
const chapters = player.getChapters('en');
|
|
1083
1083
|
expect(chapters.length).toBe(3);
|
|
1084
1084
|
const chapter1 = chapters[0];
|
|
@@ -1098,8 +1098,6 @@ describe('Player', () => {
|
|
|
1098
1098
|
const absoluteUri2 = locationUri.resolve(partialUri2);
|
|
1099
1099
|
await player.addChaptersTrack(absoluteUri2.toString(), 'en');
|
|
1100
1100
|
|
|
1101
|
-
await shaka.test.Util.delay(1.5);
|
|
1102
|
-
|
|
1103
1101
|
const chaptersUpdated = player.getChapters('en');
|
|
1104
1102
|
expect(chaptersUpdated.length).toBe(6);
|
|
1105
1103
|
const chapterUpdated1 = chaptersUpdated[0];
|
|
@@ -1128,15 +1126,13 @@ describe('Player', () => {
|
|
|
1128
1126
|
expect(chapterUpdated6.endTime).toBe(61.349);
|
|
1129
1127
|
});
|
|
1130
1128
|
|
|
1131
|
-
it('
|
|
1129
|
+
it('adds external chapters in srt format', async () => {
|
|
1132
1130
|
await player.load('test:sintel_no_text_compiled');
|
|
1133
1131
|
const locationUri = new goog.Uri(location.href);
|
|
1134
1132
|
const partialUri = new goog.Uri('/base/test/test/assets/chapters.srt');
|
|
1135
1133
|
const absoluteUri = locationUri.resolve(partialUri);
|
|
1136
1134
|
await player.addChaptersTrack(absoluteUri.toString(), 'es');
|
|
1137
1135
|
|
|
1138
|
-
await shaka.test.Util.delay(1.5);
|
|
1139
|
-
|
|
1140
1136
|
const chapters = player.getChapters('es');
|
|
1141
1137
|
expect(chapters.length).toBe(3);
|
|
1142
1138
|
const chapter1 = chapters[0];
|
|
@@ -1152,5 +1148,5 @@ describe('Player', () => {
|
|
|
1152
1148
|
expect(chapter3.startTime).toBe(30);
|
|
1153
1149
|
expect(chapter3.endTime).toBe(61.349);
|
|
1154
1150
|
});
|
|
1155
|
-
}); // describe('
|
|
1151
|
+
}); // describe('addChaptersTrack')
|
|
1156
1152
|
});
|