shaka-player 4.2.11 → 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/CHANGELOG.md +25 -0
- package/CONTRIBUTORS +1 -0
- package/demo/custom.js +1 -1
- 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/deps.js +2 -2
- package/dist/locales.js +30 -30
- package/dist/shaka-player.compiled.d.ts +22 -0
- package/dist/shaka-player.compiled.debug.d.ts +22 -0
- package/dist/shaka-player.compiled.debug.js +216 -214
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.js +735 -734
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +22 -0
- package/dist/shaka-player.ui.debug.d.ts +22 -0
- package/dist/shaka-player.ui.debug.js +241 -238
- package/dist/shaka-player.ui.debug.map +3 -3
- package/dist/shaka-player.ui.js +916 -915
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/drm-config.md +9 -6
- 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 +13 -2
- package/lib/media/drm_engine.js +14 -8
- package/lib/media/gap_jumping_controller.js +24 -2
- package/lib/media/media_source_engine.js +5 -3
- package/lib/media/playhead.js +11 -0
- package/lib/media/region_observer.js +19 -1
- package/lib/media/streaming_engine.js +7 -3
- package/lib/media/video_wrapper.js +6 -1
- package/lib/player.js +9 -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 +2 -3
- package/test/cea/mp4_cea_parser_unit.js +26 -3
- package/test/dash/dash_parser_manifest_unit.js +120 -0
- package/test/hls/hls_parser_unit.js +150 -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/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
|
```
|
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,
|
|
@@ -2150,6 +2157,10 @@ shaka.hls.HlsParser = class {
|
|
|
2150
2157
|
// We're interested in the duration part.
|
|
2151
2158
|
const extinfValues = extinfTag.value.split(',');
|
|
2152
2159
|
const duration = Number(extinfValues[0]);
|
|
2160
|
+
// Skip segments without duration
|
|
2161
|
+
if (duration == 0) {
|
|
2162
|
+
return null;
|
|
2163
|
+
}
|
|
2153
2164
|
endTime = startTime + duration;
|
|
2154
2165
|
} else {
|
|
2155
2166
|
endTime = partialSegmentRefs[partialSegmentRefs.length - 1].endTime;
|
|
@@ -2341,9 +2352,9 @@ shaka.hls.HlsParser = class {
|
|
|
2341
2352
|
playlist.absoluteUri,
|
|
2342
2353
|
type,
|
|
2343
2354
|
hlsAes128Key);
|
|
2344
|
-
previousReference = reference;
|
|
2345
2355
|
|
|
2346
2356
|
if (reference) {
|
|
2357
|
+
previousReference = reference;
|
|
2347
2358
|
reference.discontinuitySequence = discontinuitySequence;
|
|
2348
2359
|
|
|
2349
2360
|
if (this.config_.hls.ignoreManifestProgramDateTime &&
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -354,18 +354,24 @@ shaka.media.DrmEngine = class {
|
|
|
354
354
|
shaka.media.DrmEngine.replaceDrmInfo_(variants, servers);
|
|
355
355
|
}
|
|
356
356
|
|
|
357
|
-
|
|
357
|
+
/** @type {!Set<shaka.extern.DrmInfo>} */
|
|
358
|
+
const drmInfos = new Set();
|
|
358
359
|
for (const variant of variants) {
|
|
359
|
-
const
|
|
360
|
-
for (const info of
|
|
361
|
-
|
|
362
|
-
info,
|
|
363
|
-
shaka.util.MapUtils.asMap(this.config_.servers),
|
|
364
|
-
shaka.util.MapUtils.asMap(this.config_.advanced || {}),
|
|
365
|
-
this.config_.keySystemsMapping);
|
|
360
|
+
const variantDrmInfos = this.getVariantDrmInfos_(variant);
|
|
361
|
+
for (const info of variantDrmInfos) {
|
|
362
|
+
drmInfos.add(info);
|
|
366
363
|
}
|
|
367
364
|
}
|
|
368
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
|
+
|
|
369
375
|
/** @type {!Map.<string, MediaKeySystemConfiguration>} */
|
|
370
376
|
let configsByKeySystem;
|
|
371
377
|
|
|
@@ -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));
|
|
@@ -529,11 +529,13 @@ shaka.media.MediaSourceEngine = class {
|
|
|
529
529
|
* @param {boolean=} seeked True if we just seeked
|
|
530
530
|
* @param {boolean=} adaptation True if we just automatically switched active
|
|
531
531
|
* variant(s).
|
|
532
|
+
* @param {boolean=} isChunkedData True if we add to the buffer from the
|
|
533
|
+
* partial read of the segment.
|
|
532
534
|
* @return {!Promise}
|
|
533
535
|
*/
|
|
534
536
|
async appendBuffer(
|
|
535
537
|
contentType, data, reference, hasClosedCaptions, seeked = false,
|
|
536
|
-
adaptation = false) {
|
|
538
|
+
adaptation = false, isChunkedData = false) {
|
|
537
539
|
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
538
540
|
|
|
539
541
|
if (contentType == ContentType.TEXT) {
|
|
@@ -706,13 +708,13 @@ shaka.media.MediaSourceEngine = class {
|
|
|
706
708
|
let bufferedBefore = null;
|
|
707
709
|
|
|
708
710
|
await this.enqueueOperation_(contentType, () => {
|
|
709
|
-
if (goog.DEBUG && reference) {
|
|
711
|
+
if (goog.DEBUG && reference && !reference.isPreload() && !isChunkedData) {
|
|
710
712
|
bufferedBefore = this.getBuffered_(contentType);
|
|
711
713
|
}
|
|
712
714
|
this.append_(contentType, data);
|
|
713
715
|
});
|
|
714
716
|
|
|
715
|
-
if (goog.DEBUG && reference && !reference.isPreload()) {
|
|
717
|
+
if (goog.DEBUG && reference && !reference.isPreload() && !isChunkedData) {
|
|
716
718
|
const bufferedAfter = this.getBuffered_(contentType);
|
|
717
719
|
const newBuffered = shaka.media.TimeRangesUtils.computeAddedRange(
|
|
718
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);
|
|
@@ -1308,7 +1308,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1308
1308
|
const dataToAppend = remaining.subarray(0, offset);
|
|
1309
1309
|
remaining = remaining.subarray(offset);
|
|
1310
1310
|
await this.append_(
|
|
1311
|
-
mediaState, presentationTime, stream, reference, dataToAppend
|
|
1311
|
+
mediaState, presentationTime, stream, reference, dataToAppend,
|
|
1312
|
+
/* isChunkedData= */ true);
|
|
1312
1313
|
}
|
|
1313
1314
|
};
|
|
1314
1315
|
|
|
@@ -1696,10 +1697,12 @@ shaka.media.StreamingEngine = class {
|
|
|
1696
1697
|
* @param {shaka.extern.Stream} stream
|
|
1697
1698
|
* @param {!shaka.media.SegmentReference} reference
|
|
1698
1699
|
* @param {BufferSource} segment
|
|
1700
|
+
* @param {boolean=} isChunkedData
|
|
1699
1701
|
* @return {!Promise}
|
|
1700
1702
|
* @private
|
|
1701
1703
|
*/
|
|
1702
|
-
async append_(mediaState, presentationTime, stream, reference, segment
|
|
1704
|
+
async append_(mediaState, presentationTime, stream, reference, segment,
|
|
1705
|
+
isChunkedData = false) {
|
|
1703
1706
|
const logPrefix = shaka.media.StreamingEngine.logPrefix_(mediaState);
|
|
1704
1707
|
|
|
1705
1708
|
const hasClosedCaptions = stream.closedCaptions &&
|
|
@@ -1732,7 +1735,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1732
1735
|
reference,
|
|
1733
1736
|
hasClosedCaptions,
|
|
1734
1737
|
seeked,
|
|
1735
|
-
adaptation
|
|
1738
|
+
adaptation,
|
|
1739
|
+
isChunkedData);
|
|
1736
1740
|
this.destroyer_.ensureNotDestroyed();
|
|
1737
1741
|
shaka.log.v2(logPrefix, 'appended media segment');
|
|
1738
1742
|
}
|
|
@@ -27,15 +27,19 @@ shaka.media.VideoWrapper = class {
|
|
|
27
27
|
/**
|
|
28
28
|
* @param {!HTMLMediaElement} video
|
|
29
29
|
* @param {function()} onSeek Called when the video seeks.
|
|
30
|
+
* @param {function(number)} onStarted Called when the video has started.
|
|
30
31
|
* @param {number} startTime The time to start at.
|
|
31
32
|
*/
|
|
32
|
-
constructor(video, onSeek, startTime) {
|
|
33
|
+
constructor(video, onSeek, onStarted, startTime) {
|
|
33
34
|
/** @private {HTMLMediaElement} */
|
|
34
35
|
this.video_ = video;
|
|
35
36
|
|
|
36
37
|
/** @private {function()} */
|
|
37
38
|
this.onSeek_ = onSeek;
|
|
38
39
|
|
|
40
|
+
/** @private {function(number)} */
|
|
41
|
+
this.onStarted_ = onStarted;
|
|
42
|
+
|
|
39
43
|
/** @private {number} */
|
|
40
44
|
this.startTime_ = startTime;
|
|
41
45
|
|
|
@@ -165,6 +169,7 @@ shaka.media.VideoWrapper = class {
|
|
|
165
169
|
this.started_ = true;
|
|
166
170
|
|
|
167
171
|
this.eventManager_.listen(this.video_, 'seeking', () => this.onSeek_());
|
|
172
|
+
this.onStarted_(this.video_.currentTime);
|
|
168
173
|
}
|
|
169
174
|
};
|
|
170
175
|
|
package/lib/player.js
CHANGED
|
@@ -1996,7 +1996,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1996
1996
|
this.abrManager_.setMediaElement(mediaElement);
|
|
1997
1997
|
|
|
1998
1998
|
this.playhead_ = this.createPlayhead(has.startTime);
|
|
1999
|
-
this.playheadObservers_ =
|
|
1999
|
+
this.playheadObservers_ =
|
|
2000
|
+
this.createPlayheadObserversForMSE_(has.startTimeOfLoad);
|
|
2000
2001
|
|
|
2001
2002
|
// We need to start the buffer management code near the end because it will
|
|
2002
2003
|
// set the initial buffering state and that depends on other components
|
|
@@ -2761,17 +2762,21 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2761
2762
|
* Create the observers for MSE playback. These observers are responsible for
|
|
2762
2763
|
* notifying the app and player of specific events during MSE playback.
|
|
2763
2764
|
*
|
|
2765
|
+
* @param {number} startTimeOfLoad
|
|
2764
2766
|
* @return {!shaka.media.PlayheadObserverManager}
|
|
2765
2767
|
* @private
|
|
2766
2768
|
*/
|
|
2767
|
-
createPlayheadObserversForMSE_() {
|
|
2769
|
+
createPlayheadObserversForMSE_(startTimeOfLoad) {
|
|
2768
2770
|
goog.asserts.assert(this.manifest_, 'Must have manifest');
|
|
2769
2771
|
goog.asserts.assert(this.regionTimeline_, 'Must have region timeline');
|
|
2770
2772
|
goog.asserts.assert(this.video_, 'Must have video element');
|
|
2771
2773
|
|
|
2774
|
+
const startsPastZero = this.isLive() || startTimeOfLoad > 0;
|
|
2775
|
+
|
|
2772
2776
|
// Create the region observer. This will allow us to notify the app when we
|
|
2773
2777
|
// move in and out of timeline regions.
|
|
2774
|
-
const regionObserver = new shaka.media.RegionObserver(
|
|
2778
|
+
const regionObserver = new shaka.media.RegionObserver(
|
|
2779
|
+
this.regionTimeline_, startsPastZero);
|
|
2775
2780
|
|
|
2776
2781
|
regionObserver.addEventListener('enter', (event) => {
|
|
2777
2782
|
/** @type {shaka.extern.TimelineRegionInfo} */
|
|
@@ -6630,7 +6635,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6630
6635
|
* @define {string} A version number taken from git at compile time.
|
|
6631
6636
|
* @export
|
|
6632
6637
|
*/
|
|
6633
|
-
shaka.Player.version = 'v4.2.
|
|
6638
|
+
shaka.Player.version = 'v4.2.12-uncompiled';
|
|
6634
6639
|
|
|
6635
6640
|
// Initialize the deprecation system using the version string we just set
|
|
6636
6641
|
// on the player.
|
|
@@ -785,7 +785,8 @@ shaka.text.VttTextParser = class {
|
|
|
785
785
|
} else if ((results = /^size:([\d.]+)%$/.exec(word))) {
|
|
786
786
|
cue.size = Number(results[1]);
|
|
787
787
|
} else if ((results =
|
|
788
|
-
|
|
788
|
+
// eslint-disable-next-line max-len
|
|
789
|
+
/^position:([\d.]+)%(?:,(line-left|line-right|middle|center|start|end))?$/
|
|
789
790
|
.exec(word))) {
|
|
790
791
|
cue.position = Number(results[1]);
|
|
791
792
|
if (results[2]) {
|
|
@@ -112,10 +112,11 @@ shaka.util.Mp4BoxParsers = class {
|
|
|
112
112
|
static parseTRUN(reader, version, flags) {
|
|
113
113
|
const sampleCount = reader.readUint32();
|
|
114
114
|
const sampleData = [];
|
|
115
|
+
let dataOffset = null;
|
|
115
116
|
|
|
116
|
-
//
|
|
117
|
+
// "data_offset"
|
|
117
118
|
if (flags & 0x000001) {
|
|
118
|
-
reader.
|
|
119
|
+
dataOffset = reader.readUint32();
|
|
119
120
|
}
|
|
120
121
|
|
|
121
122
|
// Skip "first_sample_flags" if present.
|
|
@@ -159,6 +160,7 @@ shaka.util.Mp4BoxParsers = class {
|
|
|
159
160
|
return {
|
|
160
161
|
sampleCount,
|
|
161
162
|
sampleData,
|
|
163
|
+
dataOffset,
|
|
162
164
|
};
|
|
163
165
|
}
|
|
164
166
|
|
|
@@ -252,13 +254,16 @@ shaka.util.ParsedTREXBox;
|
|
|
252
254
|
/**
|
|
253
255
|
* @typedef {{
|
|
254
256
|
* sampleCount: number,
|
|
255
|
-
* sampleData: !Array.<shaka.util.ParsedTRUNSample
|
|
257
|
+
* sampleData: !Array.<shaka.util.ParsedTRUNSample>,
|
|
258
|
+
* dataOffset: ?number
|
|
256
259
|
* }}
|
|
257
260
|
*
|
|
258
261
|
* @property {number} sampleCount
|
|
259
262
|
* As per the spec: the number of samples being added in this run;
|
|
260
263
|
* @property {!Array.<shaka.util.ParsedTRUNSample>} sampleData
|
|
261
264
|
* An array of size <sampleCount> containing data for each sample
|
|
265
|
+
* @property {?number} dataOffset
|
|
266
|
+
* If specified via flags, this indicate the offset of the sample in bytes.
|
|
262
267
|
*
|
|
263
268
|
* @exportDoc
|
|
264
269
|
*/
|