shaka-player 4.2.10 → 4.2.12
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/workflows/appspot.yaml +39 -0
- package/.github/workflows/demo-version-index.yaml +3 -3
- package/CHANGELOG.md +42 -0
- package/CONTRIBUTORS +1 -0
- package/README.md +3 -0
- package/demo/custom.js +1 -1
- package/demo/demo.less +1 -0
- package/dist/demo.compiled.debug.js +2 -2
- package/dist/demo.compiled.debug.map +2 -2
- package/dist/demo.compiled.js +2 -2
- package/dist/demo.compiled.map +2 -2
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +2 -2
- package/dist/locales.js +31 -31
- package/dist/shaka-player.compiled.d.ts +30 -0
- package/dist/shaka-player.compiled.debug.d.ts +30 -0
- package/dist/shaka-player.compiled.debug.externs.js +9 -0
- package/dist/shaka-player.compiled.debug.js +250 -246
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +9 -0
- package/dist/shaka-player.compiled.js +789 -787
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +30 -0
- package/dist/shaka-player.ui.debug.d.ts +30 -0
- package/dist/shaka-player.ui.debug.externs.js +9 -0
- package/dist/shaka-player.ui.debug.js +290 -285
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.externs.js +9 -0
- package/dist/shaka-player.ui.js +943 -941
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/drm-config.md +9 -6
- package/docs/tutorials/welcome.md +1 -1
- package/externs/ima.js +34 -0
- package/lib/abr/simple_abr_manager.js +1 -1
- package/lib/cea/mp4_cea_parser.js +30 -14
- package/lib/dash/dash_parser.js +18 -3
- package/lib/hls/hls_parser.js +19 -2
- package/lib/hls/manifest_text_parser.js +10 -1
- package/lib/media/drm_engine.js +23 -14
- package/lib/media/gap_jumping_controller.js +24 -2
- package/lib/media/media_source_engine.js +10 -6
- package/lib/media/playhead.js +11 -0
- package/lib/media/region_observer.js +19 -1
- package/lib/media/segment_reference.js +22 -0
- package/lib/media/streaming_engine.js +16 -7
- package/lib/media/video_wrapper.js +6 -1
- package/lib/player.js +13 -4
- package/lib/polyfill/abort_controller.js +7 -0
- package/lib/text/vtt_text_parser.js +2 -1
- package/lib/util/manifest_parser_utils.js +1 -1
- package/lib/util/mp4_box_parsers.js +8 -3
- package/lib/util/stream_utils.js +1 -0
- package/package.json +9 -4
- package/test/cea/mp4_cea_parser_unit.js +26 -3
- package/test/dash/dash_parser_manifest_unit.js +120 -0
- package/test/hls/hls_live_unit.js +3 -1
- package/test/hls/hls_parser_unit.js +150 -0
- package/test/media/media_source_engine_unit.js +31 -0
- package/test/media/region_observer_unit.js +2 -1
- package/test/test/assets/multiple-trun-init.mp4 +0 -0
- package/test/test/assets/multiple-trun-segment.mp4 +0 -0
- package/test/test/util/layout_tests.js +4 -3
- package/ui/resolution_selection.js +7 -1
|
@@ -59,15 +59,18 @@ If this is the only `<ContentProtection>` element in the manifest, Shaka will
|
|
|
59
59
|
try all key systems it knows. (Based on keySystemsByURI in
|
|
60
60
|
{@linksource shaka.extern.DashManifestConfiguration}.)
|
|
61
61
|
|
|
62
|
-
Through `player.configure()`, you can
|
|
62
|
+
Through `player.configure()`, you can update the dash key systems mapping by
|
|
63
63
|
scheme URI:
|
|
64
|
+
|
|
64
65
|
```js
|
|
65
66
|
player.configure({
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
manifest: {
|
|
68
|
+
dash: {
|
|
69
|
+
keySystemsByURI: {
|
|
70
|
+
'urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95': 'com.microsoft.playready.recommendation',
|
|
71
|
+
'urn:uuid:79f0049a-4098-8642-ab92-e65be0885f95': 'com.microsoft.playready.recommendation',
|
|
72
|
+
}
|
|
73
|
+
}
|
|
71
74
|
}
|
|
72
75
|
});
|
|
73
76
|
```
|
|
@@ -29,7 +29,7 @@ You can build Shaka on Linux, Windows, or Mac.
|
|
|
29
29
|
To get the sources and compile the library, you will need:
|
|
30
30
|
* {@link https://git-scm.com/downloads Git v1.9+}
|
|
31
31
|
* {@link https://www.python.org/downloads/ Python v2.7 or v3.5+}
|
|
32
|
-
* {@link https://
|
|
32
|
+
* {@link https://learn.microsoft.com/en-us/java/openjdk/download Java Runtime Environment v14+}
|
|
33
33
|
* {@link https://nodejs.org/en/download/ NodeJS v14+}
|
|
34
34
|
* A local web server, such as {@link https://httpd.apache.org/ Apache}
|
|
35
35
|
* _NOTE: A local web server is required because browsers place restrictions
|
package/externs/ima.js
CHANGED
|
@@ -501,6 +501,14 @@ google.ima.dai.api.StreamRequest.prototype.streamActivityMonitorId;
|
|
|
501
501
|
google.ima.dai.api.StreamRequest.prototype.format;
|
|
502
502
|
|
|
503
503
|
|
|
504
|
+
/**
|
|
505
|
+
* @type {Object.<
|
|
506
|
+
* google.ima.dai.api.OmidVerificationVendor,
|
|
507
|
+
* (google.ima.dai.api.OmidAccessMode | undefined)>}
|
|
508
|
+
*/
|
|
509
|
+
google.ima.dai.api.StreamRequest.prototype.omidAccessModeRules;
|
|
510
|
+
|
|
511
|
+
|
|
504
512
|
/**
|
|
505
513
|
* @enum {string}
|
|
506
514
|
*/
|
|
@@ -587,3 +595,29 @@ google.ima.dai.api.StreamEvent.Type = {
|
|
|
587
595
|
SKIPPED: 'skip',
|
|
588
596
|
VIDEO_CLICKED: 'videoClicked',
|
|
589
597
|
};
|
|
598
|
+
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* @enum {number}
|
|
602
|
+
*/
|
|
603
|
+
google.ima.dai.api.OmidVerificationVendor = {
|
|
604
|
+
OTHER: 1,
|
|
605
|
+
MOAT: 2,
|
|
606
|
+
DOUBLEVERIFY: 3,
|
|
607
|
+
INTEGRAL_AD_SCIENCE: 4,
|
|
608
|
+
PIXELATE: 5,
|
|
609
|
+
NIELSEN: 6,
|
|
610
|
+
COMSCORE: 7,
|
|
611
|
+
MEETRICS: 8,
|
|
612
|
+
GOOGLE: 9,
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* @enum {string}
|
|
618
|
+
*/
|
|
619
|
+
google.ima.dai.api.OmidAccessMode = {
|
|
620
|
+
FULL: 'full',
|
|
621
|
+
DOMAIN: 'domain',
|
|
622
|
+
LIMITED: 'limited',
|
|
623
|
+
};
|
|
@@ -54,7 +54,7 @@ shaka.abr.SimpleAbrManager = class {
|
|
|
54
54
|
// of connectivity changes.
|
|
55
55
|
if (navigator.connection && navigator.connection.addEventListener) {
|
|
56
56
|
this.onNetworkInformationChange_ = () => {
|
|
57
|
-
if (this.
|
|
57
|
+
if (this.enabled_ && this.config_.useNetworkInformation) {
|
|
58
58
|
this.bandwidthEstimator_ = new shaka.abr.EwmaBandwidthEstimator();
|
|
59
59
|
if (this.config_) {
|
|
60
60
|
this.bandwidthEstimator_.configure(this.config_.advanced);
|
|
@@ -9,6 +9,7 @@ goog.provide('shaka.cea.Mp4CeaParser');
|
|
|
9
9
|
goog.require('goog.asserts');
|
|
10
10
|
goog.require('shaka.cea.ICeaParser');
|
|
11
11
|
goog.require('shaka.cea.SeiProcessor');
|
|
12
|
+
goog.require('shaka.log');
|
|
12
13
|
goog.require('shaka.util.DataViewReader');
|
|
13
14
|
goog.require('shaka.util.Error');
|
|
14
15
|
goog.require('shaka.util.Mp4Parser');
|
|
@@ -122,12 +123,19 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
122
123
|
// Fields that are found in MOOF boxes
|
|
123
124
|
let defaultSampleDuration = this.defaultSampleDuration_;
|
|
124
125
|
let defaultSampleSize = this.defaultSampleSize_;
|
|
125
|
-
let
|
|
126
|
+
let moofOffset = 0;
|
|
127
|
+
/** @type {!Array<shaka.util.ParsedTRUNBox>} */
|
|
128
|
+
let parsedTRUNs = [];
|
|
126
129
|
let baseMediaDecodeTime = null;
|
|
127
130
|
let timescale = shaka.cea.ICeaParser.DEFAULT_TIMESCALE_VALUE;
|
|
128
131
|
|
|
129
132
|
new Mp4Parser()
|
|
130
|
-
.box('moof',
|
|
133
|
+
.box('moof', (box) => {
|
|
134
|
+
moofOffset = box.start;
|
|
135
|
+
// trun box parsing is reset on each moof.
|
|
136
|
+
parsedTRUNs = [];
|
|
137
|
+
Mp4Parser.children(box);
|
|
138
|
+
})
|
|
131
139
|
.box('traf', Mp4Parser.children)
|
|
132
140
|
.fullBox('trun', (box) => {
|
|
133
141
|
goog.asserts.assert(
|
|
@@ -136,10 +144,8 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
136
144
|
|
|
137
145
|
const parsedTRUN = shaka.util.Mp4BoxParsers.parseTRUN(
|
|
138
146
|
box.reader, box.version, box.flags);
|
|
139
|
-
|
|
140
|
-
sampleData = parsedTRUN.sampleData;
|
|
147
|
+
parsedTRUNs.push(parsedTRUN);
|
|
141
148
|
})
|
|
142
|
-
|
|
143
149
|
.fullBox('tfhd', (box) => {
|
|
144
150
|
goog.asserts.assert(
|
|
145
151
|
box.flags != null,
|
|
@@ -163,7 +169,6 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
163
169
|
timescale = this.trackIdToTimescale_.get(trackId);
|
|
164
170
|
}
|
|
165
171
|
})
|
|
166
|
-
|
|
167
172
|
.fullBox('tfdt', (box) => {
|
|
168
173
|
goog.asserts.assert(
|
|
169
174
|
box.version != null,
|
|
@@ -176,15 +181,18 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
176
181
|
})
|
|
177
182
|
.box('mdat', (box) => {
|
|
178
183
|
if (baseMediaDecodeTime === null) {
|
|
179
|
-
// This field should have been populated by
|
|
180
|
-
//
|
|
184
|
+
// This field should have been populated by the Base Media Decode
|
|
185
|
+
// Time in the tfdt box.
|
|
186
|
+
shaka.log.alwaysWarn(
|
|
187
|
+
'Unable to find base media decode time for CEA captions!');
|
|
181
188
|
throw new shaka.util.Error(
|
|
182
189
|
shaka.util.Error.Severity.CRITICAL,
|
|
183
190
|
shaka.util.Error.Category.TEXT,
|
|
184
191
|
shaka.util.Error.Code.INVALID_MP4_CEA);
|
|
185
192
|
}
|
|
193
|
+
const offset = moofOffset - box.start - 8;
|
|
186
194
|
this.parseMdat_(box.reader, baseMediaDecodeTime, timescale,
|
|
187
|
-
defaultSampleDuration, defaultSampleSize,
|
|
195
|
+
defaultSampleDuration, defaultSampleSize, offset, parsedTRUNs,
|
|
188
196
|
captionPackets);
|
|
189
197
|
})
|
|
190
198
|
.parse(mediaSegment, /* partialOkay= */ false);
|
|
@@ -199,12 +207,13 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
199
207
|
* @param {number} timescale
|
|
200
208
|
* @param {number} defaultSampleDuration
|
|
201
209
|
* @param {number} defaultSampleSize
|
|
202
|
-
* @param {
|
|
210
|
+
* @param {number} offset
|
|
211
|
+
* @param {!Array<shaka.util.ParsedTRUNBox>} parsedTRUNs
|
|
203
212
|
* @param {!Array<!shaka.cea.ICeaParser.CaptionPacket>} captionPackets
|
|
204
213
|
* @private
|
|
205
214
|
*/
|
|
206
215
|
parseMdat_(reader, time, timescale, defaultSampleDuration,
|
|
207
|
-
defaultSampleSize,
|
|
216
|
+
defaultSampleSize, offset, parsedTRUNs, captionPackets) {
|
|
208
217
|
let sampleIndex = 0;
|
|
209
218
|
|
|
210
219
|
// The fields in each ParsedTRUNSample contained in the sampleData
|
|
@@ -213,17 +222,24 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
213
222
|
// composition time offset, we default to 0.
|
|
214
223
|
let sampleSize = defaultSampleSize;
|
|
215
224
|
|
|
225
|
+
// Combine all sample data. This assumes that the samples described across
|
|
226
|
+
// multiple trun boxes are still continuous in the mdat box.
|
|
227
|
+
const sampleDatas = parsedTRUNs.map((t) => t.sampleData);
|
|
228
|
+
const sampleData = [].concat(...sampleDatas);
|
|
229
|
+
|
|
216
230
|
if (sampleData.length) {
|
|
217
231
|
sampleSize = sampleData[0].sampleSize || defaultSampleSize;
|
|
218
232
|
}
|
|
219
233
|
|
|
234
|
+
reader.skip(offset + parsedTRUNs[0].dataOffset);
|
|
235
|
+
|
|
220
236
|
while (reader.hasMoreData()) {
|
|
221
237
|
const naluSize = reader.readUint32();
|
|
222
238
|
const naluType = reader.readUint8() & 0x1F;
|
|
223
239
|
if (naluType == shaka.cea.ICeaParser.NALU_TYPE_SEI) {
|
|
224
240
|
let timeOffset = 0;
|
|
225
241
|
|
|
226
|
-
if (sampleData.length
|
|
242
|
+
if (sampleIndex < sampleData.length) {
|
|
227
243
|
timeOffset = sampleData[sampleIndex].sampleCompositionTimeOffset || 0;
|
|
228
244
|
}
|
|
229
245
|
|
|
@@ -246,7 +262,7 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
246
262
|
}
|
|
247
263
|
sampleSize -= (naluSize + 4);
|
|
248
264
|
if (sampleSize == 0) {
|
|
249
|
-
if (sampleData.length
|
|
265
|
+
if (sampleIndex < sampleData.length) {
|
|
250
266
|
time += sampleData[sampleIndex].sampleDuration ||
|
|
251
267
|
defaultSampleDuration;
|
|
252
268
|
} else {
|
|
@@ -255,7 +271,7 @@ shaka.cea.Mp4CeaParser = class {
|
|
|
255
271
|
|
|
256
272
|
sampleIndex++;
|
|
257
273
|
|
|
258
|
-
if (sampleData.length
|
|
274
|
+
if (sampleIndex < sampleData.length) {
|
|
259
275
|
sampleSize = sampleData[sampleIndex].sampleSize || defaultSampleSize;
|
|
260
276
|
} else {
|
|
261
277
|
sampleSize = defaultSampleSize;
|
package/lib/dash/dash_parser.js
CHANGED
|
@@ -886,7 +886,19 @@ shaka.dash.DashParser = class {
|
|
|
886
886
|
|
|
887
887
|
// Parallel for HLS VIDEO-RANGE as defined in DASH-IF IOP v4.3 6.2.5.1.
|
|
888
888
|
let videoRange;
|
|
889
|
-
|
|
889
|
+
|
|
890
|
+
// Ref. https://dashif.org/docs/DASH-IF-IOP-v4.3.pdf
|
|
891
|
+
// If signaled, a Supplemental or Essential Property descriptor
|
|
892
|
+
// shall be used, with the schemeIdUri set to
|
|
893
|
+
// urn:mpeg:mpegB:cicp:<Parameter> as defined in
|
|
894
|
+
// ISO/IEC 23001-8 [49] and <Parameter> one of the
|
|
895
|
+
// following: ColourPrimaries, TransferCharacteristics,
|
|
896
|
+
// or MatrixCoefficients.
|
|
897
|
+
const scheme = 'urn:mpeg:mpegB:cicp';
|
|
898
|
+
const transferCharacteristicsScheme = `${scheme}:TransferCharacteristics`;
|
|
899
|
+
const colourPrimariesScheme = `${scheme}:ColourPrimaries`;
|
|
900
|
+
const matrixCoefficientsScheme = `${scheme}:MatrixCoefficients`;
|
|
901
|
+
|
|
890
902
|
const getVideoRangeFromTransferCharacteristicCICP = (cicp) => {
|
|
891
903
|
switch (cicp) {
|
|
892
904
|
case 1:
|
|
@@ -912,10 +924,13 @@ shaka.dash.DashParser = class {
|
|
|
912
924
|
const schemeId = prop.getAttribute('schemeIdUri');
|
|
913
925
|
if (schemeId == 'http://dashif.org/guidelines/trickmode') {
|
|
914
926
|
trickModeFor = prop.getAttribute('value');
|
|
915
|
-
} else if (schemeId ==
|
|
927
|
+
} else if (schemeId == transferCharacteristicsScheme) {
|
|
916
928
|
videoRange = getVideoRangeFromTransferCharacteristicCICP(
|
|
917
929
|
parseInt(prop.getAttribute('value'), 10),
|
|
918
930
|
);
|
|
931
|
+
} else if (schemeId == colourPrimariesScheme ||
|
|
932
|
+
schemeId == matrixCoefficientsScheme) {
|
|
933
|
+
continue;
|
|
919
934
|
} else {
|
|
920
935
|
unrecognizedEssentialProperty = true;
|
|
921
936
|
}
|
|
@@ -925,7 +940,7 @@ shaka.dash.DashParser = class {
|
|
|
925
940
|
XmlUtils.findChildren(elem, 'SupplementalProperty');
|
|
926
941
|
for (const prop of supplementalProperties) {
|
|
927
942
|
const schemeId = prop.getAttribute('schemeIdUri');
|
|
928
|
-
if (schemeId ==
|
|
943
|
+
if (schemeId == transferCharacteristicsScheme) {
|
|
929
944
|
videoRange = getVideoRangeFromTransferCharacteristicCICP(
|
|
930
945
|
parseInt(prop.getAttribute('value'), 10),
|
|
931
946
|
);
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -1713,6 +1713,13 @@ shaka.hls.HlsParser = class {
|
|
|
1713
1713
|
}
|
|
1714
1714
|
}
|
|
1715
1715
|
|
|
1716
|
+
// If there are no roles, and we have defaulted to the subtitle "kind" for
|
|
1717
|
+
// this track, add the implied subtitle role.
|
|
1718
|
+
if (!roles.length &&
|
|
1719
|
+
kind === shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE) {
|
|
1720
|
+
roles.push(shaka.util.ManifestParserUtils.TextStreamKind.SUBTITLE);
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1716
1723
|
const serverControlTag = shaka.hls.Utils.getFirstTagWithName(
|
|
1717
1724
|
playlist.tags, 'EXT-X-SERVER-CONTROL');
|
|
1718
1725
|
const canSkipSegments = serverControlTag ?
|
|
@@ -1742,7 +1749,7 @@ shaka.hls.HlsParser = class {
|
|
|
1742
1749
|
width: undefined,
|
|
1743
1750
|
height: undefined,
|
|
1744
1751
|
bandwidth: undefined,
|
|
1745
|
-
roles
|
|
1752
|
+
roles,
|
|
1746
1753
|
forced: forced,
|
|
1747
1754
|
channelsCount,
|
|
1748
1755
|
audioSamplingRate: null,
|
|
@@ -2131,6 +2138,9 @@ shaka.hls.HlsParser = class {
|
|
|
2131
2138
|
/* tileDuration= */ null,
|
|
2132
2139
|
/* syncTime= */ null,
|
|
2133
2140
|
partialStatus);
|
|
2141
|
+
if (item.name == 'EXT-X-PRELOAD-HINT') {
|
|
2142
|
+
partial.markAsPreload();
|
|
2143
|
+
}
|
|
2134
2144
|
partialSegmentRefs.push(partial);
|
|
2135
2145
|
} // for-loop of hlsSegment.partialSegments
|
|
2136
2146
|
}
|
|
@@ -2147,6 +2157,10 @@ shaka.hls.HlsParser = class {
|
|
|
2147
2157
|
// We're interested in the duration part.
|
|
2148
2158
|
const extinfValues = extinfTag.value.split(',');
|
|
2149
2159
|
const duration = Number(extinfValues[0]);
|
|
2160
|
+
// Skip segments without duration
|
|
2161
|
+
if (duration == 0) {
|
|
2162
|
+
return null;
|
|
2163
|
+
}
|
|
2150
2164
|
endTime = startTime + duration;
|
|
2151
2165
|
} else {
|
|
2152
2166
|
endTime = partialSegmentRefs[partialSegmentRefs.length - 1].endTime;
|
|
@@ -2338,9 +2352,9 @@ shaka.hls.HlsParser = class {
|
|
|
2338
2352
|
playlist.absoluteUri,
|
|
2339
2353
|
type,
|
|
2340
2354
|
hlsAes128Key);
|
|
2341
|
-
previousReference = reference;
|
|
2342
2355
|
|
|
2343
2356
|
if (reference) {
|
|
2357
|
+
previousReference = reference;
|
|
2344
2358
|
reference.discontinuitySequence = discontinuitySequence;
|
|
2345
2359
|
|
|
2346
2360
|
if (this.config_.hls.ignoreManifestProgramDateTime &&
|
|
@@ -2451,6 +2465,9 @@ shaka.hls.HlsParser = class {
|
|
|
2451
2465
|
* @private
|
|
2452
2466
|
*/
|
|
2453
2467
|
variableSubstitution_(uri, variables) {
|
|
2468
|
+
if (!variables.size) {
|
|
2469
|
+
return uri;
|
|
2470
|
+
}
|
|
2454
2471
|
let newUri = String(uri).replace(/%7B/g, '{').replace(/%7D/g, '}');
|
|
2455
2472
|
|
|
2456
2473
|
const uriVariables = newUri.match(/{\$\w*}/g);
|
|
@@ -158,7 +158,15 @@ shaka.hls.ManifestTextParser = class {
|
|
|
158
158
|
partialSegmentTags.push(tag);
|
|
159
159
|
} else if (tag.name == 'EXT-X-PRELOAD-HINT') {
|
|
160
160
|
if (tag.getAttributeValue('TYPE') == 'PART') {
|
|
161
|
-
|
|
161
|
+
// Note: BYTERANGE-START without BYTERANGE-LENGTH is being
|
|
162
|
+
// ignored.
|
|
163
|
+
if (tag.getAttributeValue('BYTERANGE-START') != null) {
|
|
164
|
+
if (tag.getAttributeValue('BYTERANGE-LENGTH') != null) {
|
|
165
|
+
partialSegmentTags.push(tag);
|
|
166
|
+
}
|
|
167
|
+
} else {
|
|
168
|
+
partialSegmentTags.push(tag);
|
|
169
|
+
}
|
|
162
170
|
} else if (tag.getAttributeValue('TYPE') == 'MAP') {
|
|
163
171
|
// Rename the Preload Hint tag to be a Map tag.
|
|
164
172
|
tag.setName('EXT-X-MAP');
|
|
@@ -300,6 +308,7 @@ shaka.hls.ManifestTextParser.MEDIA_PLAYLIST_TAGS = [
|
|
|
300
308
|
'EXT-X-ENDLIST',
|
|
301
309
|
'EXT-X-SERVER-CONTROL',
|
|
302
310
|
'EXT-X-SKIP',
|
|
311
|
+
'EXT-X-PART-INF',
|
|
303
312
|
];
|
|
304
313
|
|
|
305
314
|
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -133,6 +133,9 @@ shaka.media.DrmEngine = class {
|
|
|
133
133
|
|
|
134
134
|
/** @private {Promise} */
|
|
135
135
|
this.mediaKeysAttached_ = null;
|
|
136
|
+
|
|
137
|
+
/** @private {?shaka.extern.InitDataOverride} */
|
|
138
|
+
this.manifestInitData_ = null;
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
/** @override */
|
|
@@ -351,18 +354,24 @@ shaka.media.DrmEngine = class {
|
|
|
351
354
|
shaka.media.DrmEngine.replaceDrmInfo_(variants, servers);
|
|
352
355
|
}
|
|
353
356
|
|
|
354
|
-
|
|
357
|
+
/** @type {!Set<shaka.extern.DrmInfo>} */
|
|
358
|
+
const drmInfos = new Set();
|
|
355
359
|
for (const variant of variants) {
|
|
356
|
-
const
|
|
357
|
-
for (const info of
|
|
358
|
-
|
|
359
|
-
info,
|
|
360
|
-
shaka.util.MapUtils.asMap(this.config_.servers),
|
|
361
|
-
shaka.util.MapUtils.asMap(this.config_.advanced || {}),
|
|
362
|
-
this.config_.keySystemsMapping);
|
|
360
|
+
const variantDrmInfos = this.getVariantDrmInfos_(variant);
|
|
361
|
+
for (const info of variantDrmInfos) {
|
|
362
|
+
drmInfos.add(info);
|
|
363
363
|
}
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
+
for (const info of drmInfos) {
|
|
367
|
+
shaka.media.DrmEngine.fillInDrmInfoDefaults_(
|
|
368
|
+
info,
|
|
369
|
+
shaka.util.MapUtils.asMap(this.config_.servers),
|
|
370
|
+
shaka.util.MapUtils.asMap(this.config_.advanced || {}),
|
|
371
|
+
this.config_.keySystemsMapping);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
|
|
366
375
|
/** @type {!Map.<string, MediaKeySystemConfiguration>} */
|
|
367
376
|
let configsByKeySystem;
|
|
368
377
|
|
|
@@ -480,10 +489,10 @@ shaka.media.DrmEngine = class {
|
|
|
480
489
|
() => this.closeOpenSessions_());
|
|
481
490
|
}
|
|
482
491
|
|
|
483
|
-
|
|
484
|
-
this.currentDrmInfo_.initData.find(
|
|
492
|
+
this.manifestInitData_ = this.currentDrmInfo_ ?
|
|
493
|
+
(this.currentDrmInfo_.initData.find(
|
|
485
494
|
(initDataOverride) => initDataOverride.initData.length > 0,
|
|
486
|
-
) : null;
|
|
495
|
+
) || null) : null;
|
|
487
496
|
|
|
488
497
|
/**
|
|
489
498
|
* We can attach media keys before the playback actually begins when:
|
|
@@ -491,7 +500,7 @@ shaka.media.DrmEngine = class {
|
|
|
491
500
|
* - Some initData already has been generated (through the manifest)
|
|
492
501
|
* - In case of an offline session
|
|
493
502
|
*/
|
|
494
|
-
if (
|
|
503
|
+
if (this.manifestInitData_ ||
|
|
495
504
|
this.currentDrmInfo_.keySystem !== 'com.apple.fps' ||
|
|
496
505
|
this.offlineSessionIds_.length) {
|
|
497
506
|
await this.attachMediaKeys_();
|
|
@@ -504,7 +513,7 @@ shaka.media.DrmEngine = class {
|
|
|
504
513
|
// Also suppress 'encrypted' events when parsing in-band ppsh
|
|
505
514
|
// from media segments because that serves the same purpose as the
|
|
506
515
|
// 'encrypted' events.
|
|
507
|
-
if (!
|
|
516
|
+
if (!this.manifestInitData_ && !this.offlineSessionIds_.length &&
|
|
508
517
|
!this.config_.parseInbandPsshEnabled) {
|
|
509
518
|
this.eventManager_.listen(
|
|
510
519
|
this.video_, 'encrypted', (e) => this.onEncryptedEvent_(e));
|
|
@@ -2353,7 +2362,7 @@ shaka.media.DrmEngine = class {
|
|
|
2353
2362
|
* @return {!Promise<void>}
|
|
2354
2363
|
*/
|
|
2355
2364
|
parseInbandPssh(contentType, mediaSegment) {
|
|
2356
|
-
if (!this.config_.parseInbandPsshEnabled) {
|
|
2365
|
+
if (!this.config_.parseInbandPsshEnabled || this.manifestInitData_) {
|
|
2357
2366
|
return Promise.resolve();
|
|
2358
2367
|
}
|
|
2359
2368
|
|
|
@@ -58,6 +58,9 @@ shaka.media.GapJumpingController = class {
|
|
|
58
58
|
/** @private {number} */
|
|
59
59
|
this.prevReadyState_ = video.readyState;
|
|
60
60
|
|
|
61
|
+
/** @private {number} */
|
|
62
|
+
this.startTime_ = 0;
|
|
63
|
+
|
|
61
64
|
/** @private {number} */
|
|
62
65
|
this.gapsJumped_ = 0;
|
|
63
66
|
|
|
@@ -123,6 +126,18 @@ shaka.media.GapJumpingController = class {
|
|
|
123
126
|
this.onPollGapJump_();
|
|
124
127
|
}
|
|
125
128
|
|
|
129
|
+
/**
|
|
130
|
+
* Called when playback has started and the video element is
|
|
131
|
+
* listening for seeks.
|
|
132
|
+
*
|
|
133
|
+
* @param {number} startTime
|
|
134
|
+
*/
|
|
135
|
+
onStarted(startTime) {
|
|
136
|
+
if (this.video_.seeking && !this.seekingEventReceived_) {
|
|
137
|
+
this.seekingEventReceived_ = true;
|
|
138
|
+
this.startTime_ = startTime;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
126
141
|
|
|
127
142
|
/** Called when a seek has started. */
|
|
128
143
|
onSeeking() {
|
|
@@ -164,8 +179,8 @@ shaka.media.GapJumpingController = class {
|
|
|
164
179
|
// while paused on a livestream. We make an exception for time 0, since we
|
|
165
180
|
// may be _required_ to seek on startup before play can begin, but only if
|
|
166
181
|
// autoplay is enabled.
|
|
167
|
-
if (this.video_.paused && (this.video_.currentTime !=
|
|
168
|
-
(!this.video_.autoplay && this.video_.currentTime ==
|
|
182
|
+
if (this.video_.paused && (this.video_.currentTime != this.startTime_ ||
|
|
183
|
+
(!this.video_.autoplay && this.video_.currentTime == this.startTime_))) {
|
|
169
184
|
return;
|
|
170
185
|
}
|
|
171
186
|
|
|
@@ -228,6 +243,13 @@ shaka.media.GapJumpingController = class {
|
|
|
228
243
|
}
|
|
229
244
|
|
|
230
245
|
this.video_.currentTime = jumpTo;
|
|
246
|
+
// This accounts for the possibility that we jump a gap at the start
|
|
247
|
+
// position but we jump _into_ another gap. By setting the start
|
|
248
|
+
// position to the new jumpTo we ensure that the check above will
|
|
249
|
+
// pass even though the video is still paused.
|
|
250
|
+
if (currentTime == this.startTime_) {
|
|
251
|
+
this.startTime_ = jumpTo;
|
|
252
|
+
}
|
|
231
253
|
this.gapsJumped_++;
|
|
232
254
|
this.onEvent_(
|
|
233
255
|
new shaka.util.FakeEvent(shaka.util.FakeEvent.EventName.GapJumped));
|
|
@@ -99,15 +99,15 @@ shaka.media.MediaSourceEngine = class {
|
|
|
99
99
|
/** @private {!shaka.util.PublicPromise} */
|
|
100
100
|
this.mediaSourceOpen_ = new shaka.util.PublicPromise();
|
|
101
101
|
|
|
102
|
+
/** @private {string} */
|
|
103
|
+
this.url_ = '';
|
|
104
|
+
|
|
102
105
|
/** @private {MediaSource} */
|
|
103
106
|
this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_);
|
|
104
107
|
|
|
105
108
|
/** @type {!shaka.util.Destroyer} */
|
|
106
109
|
this.destroyer_ = new shaka.util.Destroyer(() => this.doDestroy_());
|
|
107
110
|
|
|
108
|
-
/** @private {string} */
|
|
109
|
-
this.url_ = '';
|
|
110
|
-
|
|
111
111
|
/** @private {boolean} */
|
|
112
112
|
this.sequenceMode_ = false;
|
|
113
113
|
|
|
@@ -144,6 +144,8 @@ shaka.media.MediaSourceEngine = class {
|
|
|
144
144
|
* @private
|
|
145
145
|
*/
|
|
146
146
|
onSourceOpen_(p) {
|
|
147
|
+
goog.asserts.assert(this.url_, 'Must have object URL');
|
|
148
|
+
|
|
147
149
|
// Release the object URL that was previously created, to prevent memory
|
|
148
150
|
// leak.
|
|
149
151
|
// createObjectURL creates a strong reference to the MediaSource object
|
|
@@ -527,11 +529,13 @@ shaka.media.MediaSourceEngine = class {
|
|
|
527
529
|
* @param {boolean=} seeked True if we just seeked
|
|
528
530
|
* @param {boolean=} adaptation True if we just automatically switched active
|
|
529
531
|
* variant(s).
|
|
532
|
+
* @param {boolean=} isChunkedData True if we add to the buffer from the
|
|
533
|
+
* partial read of the segment.
|
|
530
534
|
* @return {!Promise}
|
|
531
535
|
*/
|
|
532
536
|
async appendBuffer(
|
|
533
537
|
contentType, data, reference, hasClosedCaptions, seeked = false,
|
|
534
|
-
adaptation = false) {
|
|
538
|
+
adaptation = false, isChunkedData = false) {
|
|
535
539
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
536
540
|
|
|
537
541
|
if (contentType == ContentType.TEXT) {
|
|
@@ -704,13 +708,13 @@ shaka.media.MediaSourceEngine = class {
|
|
|
704
708
|
let bufferedBefore = null;
|
|
705
709
|
|
|
706
710
|
await this.enqueueOperation_(contentType, () => {
|
|
707
|
-
if (goog.DEBUG && reference) {
|
|
711
|
+
if (goog.DEBUG && reference && !reference.isPreload() && !isChunkedData) {
|
|
708
712
|
bufferedBefore = this.getBuffered_(contentType);
|
|
709
713
|
}
|
|
710
714
|
this.append_(contentType, data);
|
|
711
715
|
});
|
|
712
716
|
|
|
713
|
-
if (goog.DEBUG && reference) {
|
|
717
|
+
if (goog.DEBUG && reference && !reference.isPreload() && !isChunkedData) {
|
|
714
718
|
const bufferedAfter = this.getBuffered_(contentType);
|
|
715
719
|
const newBuffered = shaka.media.TimeRangesUtils.computeAddedRange(
|
|
716
720
|
bufferedBefore, bufferedAfter);
|
package/lib/media/playhead.js
CHANGED
|
@@ -251,6 +251,7 @@ shaka.media.MediaSourcePlayhead = class {
|
|
|
251
251
|
this.videoWrapper_ = new shaka.media.VideoWrapper(
|
|
252
252
|
mediaElement,
|
|
253
253
|
() => this.onSeeking_(),
|
|
254
|
+
(realStartTime) => this.onStarted_(realStartTime),
|
|
254
255
|
this.getStartTime_(startTime));
|
|
255
256
|
|
|
256
257
|
/** @type {shaka.util.Timer} */
|
|
@@ -390,6 +391,16 @@ shaka.media.MediaSourcePlayhead = class {
|
|
|
390
391
|
}
|
|
391
392
|
}
|
|
392
393
|
|
|
394
|
+
/**
|
|
395
|
+
* Called when the video element has started up and is listening for new seeks
|
|
396
|
+
*
|
|
397
|
+
* @param {number} startTime
|
|
398
|
+
* @private
|
|
399
|
+
*/
|
|
400
|
+
onStarted_(startTime) {
|
|
401
|
+
this.gapController_.onStarted(startTime);
|
|
402
|
+
}
|
|
403
|
+
|
|
393
404
|
/**
|
|
394
405
|
* Handles when a seek happens on the video.
|
|
395
406
|
*
|
|
@@ -27,13 +27,24 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
|
|
|
27
27
|
* destroy the timeline.
|
|
28
28
|
*
|
|
29
29
|
* @param {!shaka.media.RegionTimeline} timeline
|
|
30
|
+
* @param {boolean} startsPastZero
|
|
30
31
|
*/
|
|
31
|
-
constructor(timeline) {
|
|
32
|
+
constructor(timeline, startsPastZero) {
|
|
32
33
|
super();
|
|
33
34
|
|
|
34
35
|
/** @private {shaka.media.RegionTimeline} */
|
|
35
36
|
this.timeline_ = timeline;
|
|
36
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Whether the asset is expected to start at a time beyond 0 seconds.
|
|
40
|
+
* For example, if the asset is a live stream.
|
|
41
|
+
* If true, we will not start polling for regions until the playhead has
|
|
42
|
+
* moved past 0 seconds, to avoid bad behaviors where the current time is
|
|
43
|
+
* briefly 0 before we have enough data to play.
|
|
44
|
+
* @private {boolean}
|
|
45
|
+
*/
|
|
46
|
+
this.startsPastZero_ = startsPastZero;
|
|
47
|
+
|
|
37
48
|
/**
|
|
38
49
|
* A mapping between a region and where we previously were relative to it.
|
|
39
50
|
* When the value here differs from what we calculate, it means we moved and
|
|
@@ -121,6 +132,13 @@ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
|
|
|
121
132
|
/** @override */
|
|
122
133
|
poll(positionInSeconds, wasSeeking) {
|
|
123
134
|
const RegionObserver = shaka.media.RegionObserver;
|
|
135
|
+
if (this.startsPastZero_ && positionInSeconds == 0) {
|
|
136
|
+
// Don't start checking regions until the timeline has begun moving.
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
// Now that we have seen the playhead go past 0, it's okay if it goes
|
|
140
|
+
// back there (e.g. seeking back to the start).
|
|
141
|
+
this.startsPastZero_ = false;
|
|
124
142
|
|
|
125
143
|
for (const region of this.timeline_.regions()) {
|
|
126
144
|
const previousPosition = this.oldPosition_.get(region);
|
|
@@ -238,6 +238,9 @@ shaka.media.SegmentReference = class {
|
|
|
238
238
|
/** @type {shaka.media.SegmentReference.Status} */
|
|
239
239
|
this.status = status;
|
|
240
240
|
|
|
241
|
+
/** @type {boolean} */
|
|
242
|
+
this.preload = false;
|
|
243
|
+
|
|
241
244
|
/** @type {?shaka.extern.HlsAes128Key} */
|
|
242
245
|
this.hlsAes128Key = hlsAes128Key;
|
|
243
246
|
|
|
@@ -358,6 +361,25 @@ shaka.media.SegmentReference = class {
|
|
|
358
361
|
this.status = shaka.media.SegmentReference.Status.UNAVAILABLE;
|
|
359
362
|
}
|
|
360
363
|
|
|
364
|
+
/**
|
|
365
|
+
* Mark the reference as preload.
|
|
366
|
+
*
|
|
367
|
+
* @export
|
|
368
|
+
*/
|
|
369
|
+
markAsPreload() {
|
|
370
|
+
this.preload = true;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Returns true if the segment is preloaded.
|
|
375
|
+
*
|
|
376
|
+
* @return {boolean}
|
|
377
|
+
* @export
|
|
378
|
+
*/
|
|
379
|
+
isPreload() {
|
|
380
|
+
return this.preload;
|
|
381
|
+
}
|
|
382
|
+
|
|
361
383
|
/**
|
|
362
384
|
* Offset the segment reference by a fixed amount.
|
|
363
385
|
*
|