shaka-player 4.9.13 → 4.10.1
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/README.md +4 -0
- package/dist/controls.css +2 -2
- package/dist/controls.css.map +1 -1
- package/dist/deps.js +24 -19
- package/dist/locales.js +46 -35
- package/dist/shaka-player.compiled.d.ts +291 -12
- package/dist/shaka-player.compiled.debug.d.ts +291 -12
- package/dist/shaka-player.compiled.debug.externs.js +443 -6
- package/dist/shaka-player.compiled.debug.js +651 -556
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +443 -6
- package/dist/shaka-player.compiled.js +1325 -1270
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +306 -13
- package/dist/shaka-player.ui.debug.d.ts +306 -13
- package/dist/shaka-player.ui.debug.externs.js +477 -18
- package/dist/shaka-player.ui.debug.js +799 -681
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +477 -18
- package/dist/shaka-player.ui.js +1600 -1533
- package/dist/shaka-player.ui.map +4 -4
- package/externs/shaka/ads.js +21 -1
- package/externs/shaka/manifest.js +20 -1
- package/externs/shaka/manifest_parser.js +18 -1
- package/externs/shaka/player.js +164 -50
- package/lib/ads/ad_manager.js +225 -10
- package/lib/ads/ad_utils.js +323 -0
- package/lib/ads/ads_stats.js +24 -0
- package/lib/ads/client_side_ad_manager.js +42 -34
- package/lib/ads/interstitial_ad.js +292 -0
- package/lib/ads/interstitial_ad_manager.js +772 -0
- package/lib/ads/media_tailor_ad_manager.js +19 -22
- package/lib/ads/server_side_ad_manager.js +12 -15
- package/lib/cast/cast_utils.js +1 -0
- package/lib/cea/cea_utils.js +12 -0
- package/lib/cea/mp4_cea_parser.js +17 -0
- package/lib/dash/dash_parser.js +81 -28
- package/lib/dash/segment_base.js +3 -0
- package/lib/hls/hls_parser.js +429 -121
- package/lib/hls/manifest_text_parser.js +1 -0
- package/lib/media/adaptation_set_criteria.js +31 -1
- package/lib/media/drm_engine.js +16 -2
- package/lib/media/manifest_filterer.js +7 -5
- package/lib/media/preload_manager.js +8 -10
- package/lib/media/presentation_timeline.js +37 -5
- package/lib/media/quality_observer.js +68 -2
- package/lib/media/segment_reference.js +1 -1
- package/lib/media/segment_utils.js +33 -1
- package/lib/media/streaming_engine.js +120 -43
- package/lib/mss/mss_parser.js +13 -2
- package/lib/net/networking_engine.js +1 -0
- package/lib/offline/manifest_converter.js +3 -0
- package/lib/offline/offline_manifest_parser.js +5 -0
- package/lib/offline/storage.js +2 -0
- package/lib/player.js +498 -165
- package/lib/util/fake_event.js +1 -0
- package/lib/util/manifest_parser_utils.js +9 -2
- package/lib/util/mp4_box_parsers.js +32 -0
- package/lib/util/player_configuration.js +21 -9
- package/lib/util/stats.js +43 -0
- package/package.json +1 -1
- package/shaka-player.uncompiled.js +2 -0
- package/ui/ad_counter.js +3 -3
- package/ui/ad_position.js +3 -3
- package/ui/ad_statistics_button.js +237 -0
- package/ui/audio_language_selection.js +8 -4
- package/ui/controls.js +6 -3
- package/ui/element.js +3 -3
- package/ui/enums.js +1 -0
- package/ui/externs/ui.js +3 -0
- package/ui/language_utils.js +48 -3
- package/ui/less/containers.less +32 -0
- package/ui/less/thumbnails.less +13 -0
- package/ui/locales/en.json +2 -0
- package/ui/locales/es-419.json +2 -0
- package/ui/locales/es.json +3 -0
- package/ui/locales/fr.json +1 -0
- package/ui/locales/pl.json +1 -0
- package/ui/locales/pt-BR.json +1 -0
- package/ui/locales/pt-PT.json +1 -0
- package/ui/locales/source.json +8 -0
- package/ui/mute_button.js +4 -4
- package/ui/overflow_menu.js +3 -3
- package/ui/play_button.js +5 -7
- package/ui/presentation_time.js +3 -3
- package/ui/save_video_frame_button.js +196 -0
- package/ui/seek_bar.js +75 -20
- package/ui/skip_ad_button.js +4 -4
- package/ui/statistics_button.js +14 -2
- package/ui/ui.js +15 -0
- package/ui/volume_bar.js +4 -4
|
@@ -115,11 +115,12 @@ shaka.media.PreferenceBasedCriteria = class {
|
|
|
115
115
|
* @param {string=} videoLabel
|
|
116
116
|
* @param {shaka.config.CodecSwitchingStrategy=} codecSwitchingStrategy
|
|
117
117
|
* @param {boolean=} enableAudioGroups
|
|
118
|
+
* @param {string=} audioCodec
|
|
118
119
|
*/
|
|
119
120
|
constructor(language, role, channelCount, hdrLevel, spatialAudio,
|
|
120
121
|
videoLayout, audioLabel = '', videoLabel = '',
|
|
121
122
|
codecSwitchingStrategy = shaka.config.CodecSwitchingStrategy.RELOAD,
|
|
122
|
-
enableAudioGroups = false) {
|
|
123
|
+
enableAudioGroups = false, audioCodec = '') {
|
|
123
124
|
/** @private {string} */
|
|
124
125
|
this.language_ = language;
|
|
125
126
|
/** @private {string} */
|
|
@@ -140,6 +141,8 @@ shaka.media.PreferenceBasedCriteria = class {
|
|
|
140
141
|
this.codecSwitchingStrategy_ = codecSwitchingStrategy;
|
|
141
142
|
/** @private {boolean} */
|
|
142
143
|
this.enableAudioGroups_ = enableAudioGroups;
|
|
144
|
+
/** @private {string} */
|
|
145
|
+
this.audioCodec_ = audioCodec;
|
|
143
146
|
}
|
|
144
147
|
|
|
145
148
|
/** @override */
|
|
@@ -229,6 +232,16 @@ shaka.media.PreferenceBasedCriteria = class {
|
|
|
229
232
|
shaka.log.warning('No exact match for spatial audio could be found.');
|
|
230
233
|
}
|
|
231
234
|
|
|
235
|
+
if (this.audioCodec_) {
|
|
236
|
+
const byAudioCodec = Class.filterVariantsByAudioCodec_(
|
|
237
|
+
current, this.audioCodec_);
|
|
238
|
+
if (byAudioCodec.length) {
|
|
239
|
+
current = byAudioCodec;
|
|
240
|
+
} else {
|
|
241
|
+
shaka.log.warning('No exact match for audio codec could be found.');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
232
245
|
const supportsSmoothCodecTransitions = this.codecSwitchingStrategy_ ==
|
|
233
246
|
shaka.config.CodecSwitchingStrategy.SMOOTH &&
|
|
234
247
|
shaka.media.Capabilities.isChangeTypeSupported();
|
|
@@ -403,4 +416,21 @@ shaka.media.PreferenceBasedCriteria = class {
|
|
|
403
416
|
return true;
|
|
404
417
|
});
|
|
405
418
|
}
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Filters variants according to the given audio codec.
|
|
423
|
+
*
|
|
424
|
+
* @param {!Array<shaka.extern.Variant>} variants
|
|
425
|
+
* @param {string} audioCodec
|
|
426
|
+
* @private
|
|
427
|
+
*/
|
|
428
|
+
static filterVariantsByAudioCodec_(variants, audioCodec) {
|
|
429
|
+
return variants.filter((variant) => {
|
|
430
|
+
if (variant.audio && variant.audio.codecs != audioCodec) {
|
|
431
|
+
return false;
|
|
432
|
+
}
|
|
433
|
+
return true;
|
|
434
|
+
});
|
|
435
|
+
}
|
|
406
436
|
};
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -2225,9 +2225,12 @@ shaka.media.DrmEngine = class {
|
|
|
2225
2225
|
/** @type {!Set.<string>} */
|
|
2226
2226
|
const keyIds = new Set();
|
|
2227
2227
|
|
|
2228
|
+
/** @type {!Set.<string>} */
|
|
2229
|
+
const keySystemUris = new Set();
|
|
2230
|
+
|
|
2228
2231
|
shaka.media.DrmEngine.processDrmInfos_(
|
|
2229
2232
|
drmInfos, encryptionSchemes, licenseServers, serverCerts,
|
|
2230
|
-
serverCertificateUris, initDatas, keyIds);
|
|
2233
|
+
serverCertificateUris, initDatas, keyIds, keySystemUris);
|
|
2231
2234
|
|
|
2232
2235
|
if (encryptionSchemes.length > 1) {
|
|
2233
2236
|
shaka.log.warning('Multiple unique encryption schemes found! ' +
|
|
@@ -2268,6 +2271,10 @@ shaka.media.DrmEngine = class {
|
|
|
2268
2271
|
keyIds,
|
|
2269
2272
|
};
|
|
2270
2273
|
|
|
2274
|
+
if (keySystemUris.size > 0) {
|
|
2275
|
+
res.keySystemUris = keySystemUris;
|
|
2276
|
+
}
|
|
2277
|
+
|
|
2271
2278
|
for (const info of drmInfos) {
|
|
2272
2279
|
if (info.distinctiveIdentifierRequired) {
|
|
2273
2280
|
res.distinctiveIdentifierRequired = info.distinctiveIdentifierRequired;
|
|
@@ -2369,11 +2376,12 @@ shaka.media.DrmEngine = class {
|
|
|
2369
2376
|
* @param {!Array.<string>} serverCertificateUris
|
|
2370
2377
|
* @param {!Array.<!shaka.extern.InitDataOverride>} initDatas
|
|
2371
2378
|
* @param {!Set.<string>} keyIds
|
|
2379
|
+
* @param {!Set.<string>} [keySystemUris]
|
|
2372
2380
|
* @private
|
|
2373
2381
|
*/
|
|
2374
2382
|
static processDrmInfos_(
|
|
2375
2383
|
drmInfos, encryptionSchemes, licenseServers, serverCerts,
|
|
2376
|
-
serverCertificateUris, initDatas, keyIds) {
|
|
2384
|
+
serverCertificateUris, initDatas, keyIds, keySystemUris) {
|
|
2377
2385
|
/** @type {function(shaka.extern.InitDataOverride,
|
|
2378
2386
|
* shaka.extern.InitDataOverride):boolean} */
|
|
2379
2387
|
const initDataOverrideEqual = (a, b) => {
|
|
@@ -2437,6 +2445,12 @@ shaka.media.DrmEngine = class {
|
|
|
2437
2445
|
keyIds.add(keyId);
|
|
2438
2446
|
}
|
|
2439
2447
|
}
|
|
2448
|
+
|
|
2449
|
+
if (drmInfo.keySystemUris && keySystemUris) {
|
|
2450
|
+
for (const keySystemUri of drmInfo.keySystemUris) {
|
|
2451
|
+
keySystemUris.add(keySystemUri);
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2440
2454
|
}
|
|
2441
2455
|
|
|
2442
2456
|
if (clearKeyLicenseServers.length == 1) {
|
|
@@ -51,11 +51,13 @@ shaka.media.ManifestFilterer = class {
|
|
|
51
51
|
goog.asserts.assert(manifest, 'Manifest should exist!');
|
|
52
52
|
await shaka.util.StreamUtils.filterManifest(this.drmEngine_, manifest,
|
|
53
53
|
this.config_.drm.preferredKeySystems);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
if (!this.config_.streaming.dontChooseCodecs) {
|
|
55
|
+
shaka.util.StreamUtils.chooseCodecsAndFilterManifest(
|
|
56
|
+
manifest,
|
|
57
|
+
this.config_.preferredVideoCodecs,
|
|
58
|
+
this.config_.preferredAudioCodecs,
|
|
59
|
+
this.config_.preferredDecodingAttributes);
|
|
60
|
+
}
|
|
59
61
|
this.checkPlayableVariants_(manifest);
|
|
60
62
|
return this.filterManifestWithRestrictions(manifest);
|
|
61
63
|
}
|
|
@@ -38,11 +38,10 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
|
|
|
38
38
|
/**
|
|
39
39
|
* @param {string} assetUri
|
|
40
40
|
* @param {?string} mimeType
|
|
41
|
-
* @param {number} startTimeOfLoad
|
|
42
41
|
* @param {?number} startTime
|
|
43
42
|
* @param {*} playerInterface
|
|
44
43
|
*/
|
|
45
|
-
constructor(assetUri, mimeType,
|
|
44
|
+
constructor(assetUri, mimeType, startTime, playerInterface) {
|
|
46
45
|
super();
|
|
47
46
|
|
|
48
47
|
// Making the playerInterface a * and casting it to the right type allows
|
|
@@ -67,9 +66,6 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
|
|
|
67
66
|
/** @private {?shaka.media.AdaptationSetCriteria} */
|
|
68
67
|
this.currentAdaptationSetCriteria_ = null;
|
|
69
68
|
|
|
70
|
-
/** @private {number} */
|
|
71
|
-
this.startTimeOfLoad_ = startTimeOfLoad;
|
|
72
|
-
|
|
73
69
|
/** @private {number} */
|
|
74
70
|
this.startTimeOfDrm_ = 0;
|
|
75
71
|
|
|
@@ -196,16 +192,18 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
|
|
|
196
192
|
this.queuedOperations_ = null;
|
|
197
193
|
}
|
|
198
194
|
|
|
195
|
+
/** @param {number} offset */
|
|
196
|
+
setOffsetToStartTime(offset) {
|
|
197
|
+
if (this.startTime_ && offset) {
|
|
198
|
+
this.startTime_ += offset;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
199
202
|
/** @return {?number} */
|
|
200
203
|
getStartTime() {
|
|
201
204
|
return this.startTime_;
|
|
202
205
|
}
|
|
203
206
|
|
|
204
|
-
/** @return {number} */
|
|
205
|
-
getStartTimeOfLoad() {
|
|
206
|
-
return this.startTimeOfLoad_;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
207
|
/** @return {number} */
|
|
210
208
|
getStartTimeOfDRM() {
|
|
211
209
|
return this.startTimeOfDrm_;
|
|
@@ -247,12 +247,22 @@ shaka.media.PresentationTimeline = class {
|
|
|
247
247
|
return;
|
|
248
248
|
}
|
|
249
249
|
|
|
250
|
-
|
|
251
|
-
const
|
|
250
|
+
// Date.now() is in milliseconds, from which we compute "now" in seconds.
|
|
251
|
+
const now = (Date.now() + this.clockOffset_) / 1000.0;
|
|
252
|
+
// Exclude time ranges that are in the "future".
|
|
253
|
+
const timelineForStart = timeline.filter((timeRange) =>
|
|
254
|
+
timeRange.start + startOffset < now);
|
|
255
|
+
if (timelineForStart.length == 0) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const firstStartTime = timelineForStart[0].start + startOffset;
|
|
260
|
+
const lastEndTime = timelineForStart[timelineForStart.length - 1].end +
|
|
261
|
+
startOffset;
|
|
252
262
|
|
|
253
263
|
this.notifyMinSegmentStartTime(firstStartTime);
|
|
254
264
|
|
|
255
|
-
this.maxSegmentDuration_ =
|
|
265
|
+
this.maxSegmentDuration_ = timelineForStart.reduce(
|
|
256
266
|
(max, r) => { return Math.max(max, r.end - r.start); },
|
|
257
267
|
this.maxSegmentDuration_);
|
|
258
268
|
|
|
@@ -263,8 +273,6 @@ shaka.media.PresentationTimeline = class {
|
|
|
263
273
|
!this.startTimeLocked_) {
|
|
264
274
|
// Since we have explicit segment end times, calculate a presentation
|
|
265
275
|
// start based on them. This start time accounts for drift.
|
|
266
|
-
// Date.now() is in milliseconds, from which we compute "now" in seconds.
|
|
267
|
-
const now = (Date.now() + this.clockOffset_) / 1000.0;
|
|
268
276
|
this.presentationStartTime_ =
|
|
269
277
|
now - this.maxSegmentEndTime_ - this.maxSegmentDuration_;
|
|
270
278
|
}
|
|
@@ -323,6 +331,17 @@ shaka.media.PresentationTimeline = class {
|
|
|
323
331
|
}
|
|
324
332
|
|
|
325
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Gets the end time of the last available segment.
|
|
336
|
+
*
|
|
337
|
+
* @return {?number}
|
|
338
|
+
* @export
|
|
339
|
+
*/
|
|
340
|
+
getMaxSegmentEndTime() {
|
|
341
|
+
return this.maxSegmentEndTime_;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
|
|
326
345
|
/**
|
|
327
346
|
* Lock the presentation timeline's start time. After this is called, no
|
|
328
347
|
* further adjustments to presentationStartTime_ will be permitted.
|
|
@@ -630,6 +649,19 @@ shaka.media.PresentationTimeline = class {
|
|
|
630
649
|
this.availabilityTimeOffset_ = offset;
|
|
631
650
|
}
|
|
632
651
|
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Gets the presentation's segment availability time offset. This should be
|
|
655
|
+
* only configured for Low Latency Dash.
|
|
656
|
+
*
|
|
657
|
+
* @return {number} availabilityTimeOffset parameter
|
|
658
|
+
* @export
|
|
659
|
+
*/
|
|
660
|
+
getAvailabilityTimeOffset() {
|
|
661
|
+
return this.availabilityTimeOffset_;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
|
|
633
665
|
/**
|
|
634
666
|
* Debug only: assert that the timeline parameters make sense for the type
|
|
635
667
|
* of presentation (VOD, IPR, live).
|
|
@@ -8,8 +8,10 @@ goog.provide('shaka.media.QualityObserver');
|
|
|
8
8
|
|
|
9
9
|
goog.require('shaka.media.IPlayheadObserver');
|
|
10
10
|
goog.require('shaka.log');
|
|
11
|
+
goog.require('shaka.util.ArrayUtils');
|
|
11
12
|
goog.require('shaka.util.FakeEvent');
|
|
12
13
|
goog.require('shaka.util.FakeEventTarget');
|
|
14
|
+
goog.require('shaka.util.ManifestParserUtils');
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
17
|
* Monitors the quality of content being appended to the source buffers and
|
|
@@ -152,12 +154,39 @@ shaka.media.QualityObserver = class extends shaka.util.FakeEventTarget {
|
|
|
152
154
|
/** @override */
|
|
153
155
|
poll(positionInSeconds, wasSeeking) {
|
|
154
156
|
for (const contentTypeState of this.contentTypeStates_.values()) {
|
|
157
|
+
const currentQuality = contentTypeState.currentQuality;
|
|
155
158
|
const qualityAtPosition =
|
|
156
159
|
shaka.media.QualityObserver.getMediaQualityAtPosition_(
|
|
157
160
|
positionInSeconds, contentTypeState);
|
|
158
|
-
|
|
161
|
+
|
|
162
|
+
const differentQualities = qualityAtPosition &&
|
|
159
163
|
!shaka.media.QualityObserver.mediaQualitiesAreTheSame_(
|
|
160
|
-
|
|
164
|
+
currentQuality, qualityAtPosition);
|
|
165
|
+
const differentLabel = qualityAtPosition && currentQuality &&
|
|
166
|
+
qualityAtPosition.label && currentQuality.label &&
|
|
167
|
+
currentQuality.label !== qualityAtPosition.label;
|
|
168
|
+
const differentLanguage = qualityAtPosition && currentQuality &&
|
|
169
|
+
qualityAtPosition.language && currentQuality.language &&
|
|
170
|
+
currentQuality.language !== qualityAtPosition.language;
|
|
171
|
+
const differentRoles = qualityAtPosition && currentQuality &&
|
|
172
|
+
qualityAtPosition.roles && currentQuality.roles &&
|
|
173
|
+
!shaka.util.ArrayUtils.equal(currentQuality.roles,
|
|
174
|
+
qualityAtPosition.roles);
|
|
175
|
+
|
|
176
|
+
if (differentLabel || differentLanguage || differentRoles) {
|
|
177
|
+
if (this.positionIsBuffered_(
|
|
178
|
+
positionInSeconds, qualityAtPosition.contentType)) {
|
|
179
|
+
contentTypeState.currentQuality = qualityAtPosition;
|
|
180
|
+
|
|
181
|
+
const event = new shaka.util.FakeEvent('audiotrackchange', new Map([
|
|
182
|
+
['quality', qualityAtPosition],
|
|
183
|
+
['position', positionInSeconds],
|
|
184
|
+
]));
|
|
185
|
+
this.dispatchEvent(event);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (differentQualities) {
|
|
161
190
|
if (this.positionIsBuffered_(
|
|
162
191
|
positionInSeconds, qualityAtPosition.contentType)) {
|
|
163
192
|
contentTypeState.currentQuality = qualityAtPosition;
|
|
@@ -232,6 +261,43 @@ shaka.media.QualityObserver = class extends shaka.util.FakeEventTarget {
|
|
|
232
261
|
contentTypeState.qualityChangePositions = [];
|
|
233
262
|
}
|
|
234
263
|
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Create a MediaQualityInfo object from a stream object.
|
|
267
|
+
*
|
|
268
|
+
* @param {!shaka.extern.Stream} stream
|
|
269
|
+
* @return {!shaka.extern.MediaQualityInfo}
|
|
270
|
+
*/
|
|
271
|
+
static createQualityInfo(stream) {
|
|
272
|
+
const basicQuality = {
|
|
273
|
+
bandwidth: stream.bandwidth || 0,
|
|
274
|
+
audioSamplingRate: null,
|
|
275
|
+
codecs: stream.codecs,
|
|
276
|
+
contentType: stream.type,
|
|
277
|
+
frameRate: null,
|
|
278
|
+
height: null,
|
|
279
|
+
mimeType: stream.mimeType,
|
|
280
|
+
channelsCount: null,
|
|
281
|
+
pixelAspectRatio: null,
|
|
282
|
+
width: null,
|
|
283
|
+
label: null,
|
|
284
|
+
roles: stream.roles,
|
|
285
|
+
language: null,
|
|
286
|
+
};
|
|
287
|
+
if (stream.type == shaka.util.ManifestParserUtils.ContentType.VIDEO) {
|
|
288
|
+
basicQuality.frameRate = stream.frameRate || null;
|
|
289
|
+
basicQuality.height = stream.height || null;
|
|
290
|
+
basicQuality.pixelAspectRatio = stream.pixelAspectRatio || null;
|
|
291
|
+
basicQuality.width = stream.width || null;
|
|
292
|
+
}
|
|
293
|
+
if (stream.type == shaka.util.ManifestParserUtils.ContentType.AUDIO) {
|
|
294
|
+
basicQuality.audioSamplingRate = stream.audioSamplingRate;
|
|
295
|
+
basicQuality.channelsCount = stream.channelsCount;
|
|
296
|
+
basicQuality.label = stream.label || null;
|
|
297
|
+
basicQuality.language = stream.language;
|
|
298
|
+
}
|
|
299
|
+
return basicQuality;
|
|
300
|
+
}
|
|
235
301
|
};
|
|
236
302
|
|
|
237
303
|
/**
|
|
@@ -46,7 +46,7 @@ shaka.media.InitSegmentReference = class {
|
|
|
46
46
|
/** @const {?number} */
|
|
47
47
|
this.endByte = endByte;
|
|
48
48
|
|
|
49
|
-
/** @
|
|
49
|
+
/** @type {shaka.extern.MediaQualityInfo|null} */
|
|
50
50
|
this.mediaQuality = mediaQuality;
|
|
51
51
|
|
|
52
52
|
/** @type {number|null} */
|
|
@@ -327,12 +327,39 @@ shaka.media.SegmentUtils = class {
|
|
|
327
327
|
case 'avc3':
|
|
328
328
|
codecBase = 'dvav';
|
|
329
329
|
break;
|
|
330
|
+
case 'av01':
|
|
331
|
+
codecBase = 'dav1';
|
|
332
|
+
break;
|
|
330
333
|
}
|
|
331
334
|
const parsedDVCCBox = shaka.util.Mp4BoxParsers.parseDVCC(
|
|
332
335
|
codecBase, box.reader, box.name);
|
|
333
336
|
videoCodecs.push(parsedDVCCBox.codec);
|
|
334
337
|
hasVideo = true;
|
|
335
338
|
})
|
|
339
|
+
.box('dvvC', (box) => {
|
|
340
|
+
let codecBase = baseBox || '';
|
|
341
|
+
switch (baseBox) {
|
|
342
|
+
case 'hvc1':
|
|
343
|
+
codecBase = 'dvh1';
|
|
344
|
+
break;
|
|
345
|
+
case 'hev1':
|
|
346
|
+
codecBase = 'dvhe';
|
|
347
|
+
break;
|
|
348
|
+
case 'avc1':
|
|
349
|
+
codecBase = 'dva1';
|
|
350
|
+
break;
|
|
351
|
+
case 'avc3':
|
|
352
|
+
codecBase = 'dvav';
|
|
353
|
+
break;
|
|
354
|
+
case 'av01':
|
|
355
|
+
codecBase = 'dav1';
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
const parsedDVCCBox = shaka.util.Mp4BoxParsers.parseDVVC(
|
|
359
|
+
codecBase, box.reader, box.name);
|
|
360
|
+
videoCodecs.push(parsedDVCCBox.codec);
|
|
361
|
+
hasVideo = true;
|
|
362
|
+
})
|
|
336
363
|
.fullBox('vpcC', (box) => {
|
|
337
364
|
const codecBase = baseBox || '';
|
|
338
365
|
const parsedVPCCBox = shaka.util.Mp4BoxParsers.parseVPCC(
|
|
@@ -341,7 +368,12 @@ shaka.media.SegmentUtils = class {
|
|
|
341
368
|
hasVideo = true;
|
|
342
369
|
})
|
|
343
370
|
.box('av1C', (box) => {
|
|
344
|
-
|
|
371
|
+
let codecBase = baseBox || '';
|
|
372
|
+
switch (baseBox) {
|
|
373
|
+
case 'dav1':
|
|
374
|
+
codecBase = 'av01';
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
345
377
|
const parsedAV1CBox = shaka.util.Mp4BoxParsers.parseAV1C(
|
|
346
378
|
codecBase, box.reader, box.name);
|
|
347
379
|
videoCodecs.push(parsedAV1CBox.codec);
|
|
@@ -1182,7 +1182,12 @@ shaka.media.StreamingEngine = class {
|
|
|
1182
1182
|
if (!mediaState.stream.segmentIndex) {
|
|
1183
1183
|
const thisStream = mediaState.stream;
|
|
1184
1184
|
|
|
1185
|
-
|
|
1185
|
+
try {
|
|
1186
|
+
await mediaState.stream.createSegmentIndex();
|
|
1187
|
+
} catch (error) {
|
|
1188
|
+
await this.handleStreamingError_(mediaState, error);
|
|
1189
|
+
return;
|
|
1190
|
+
}
|
|
1186
1191
|
|
|
1187
1192
|
if (thisStream != mediaState.stream) {
|
|
1188
1193
|
// We switched streams while in the middle of this async call to
|
|
@@ -1562,9 +1567,11 @@ shaka.media.StreamingEngine = class {
|
|
|
1562
1567
|
const isMP4 = stream.mimeType == 'video/mp4' ||
|
|
1563
1568
|
stream.mimeType == 'audio/mp4';
|
|
1564
1569
|
const isReadableStreamSupported = window.ReadableStream;
|
|
1570
|
+
const lowLatencyMode = this.config_.lowLatencyMode &&
|
|
1571
|
+
this.manifest_.isLowLatency;
|
|
1565
1572
|
// Enable MP4 low latency streaming with ReadableStream chunked data.
|
|
1566
1573
|
// And only for DASH and HLS with byterange optimization.
|
|
1567
|
-
if (
|
|
1574
|
+
if (lowLatencyMode && isReadableStreamSupported && isMP4 &&
|
|
1568
1575
|
(this.manifest_.type != shaka.media.ManifestParser.HLS ||
|
|
1569
1576
|
reference.hasByterangeOptimization())) {
|
|
1570
1577
|
let remaining = new Uint8Array(0);
|
|
@@ -1655,7 +1662,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1655
1662
|
reference.startTime, /* skipFirst= */ true);
|
|
1656
1663
|
}
|
|
1657
1664
|
} else {
|
|
1658
|
-
if (
|
|
1665
|
+
if (lowLatencyMode && !isReadableStreamSupported) {
|
|
1659
1666
|
shaka.log.warning('Low latency streaming mode is enabled, but ' +
|
|
1660
1667
|
'ReadableStream is not supported by the browser.');
|
|
1661
1668
|
}
|
|
@@ -1986,46 +1993,11 @@ shaka.media.StreamingEngine = class {
|
|
|
1986
1993
|
}
|
|
1987
1994
|
}
|
|
1988
1995
|
|
|
1989
|
-
const setProperties = async () => {
|
|
1990
|
-
/**
|
|
1991
|
-
* @type {!Map.<shaka.util.ManifestParserUtils.ContentType,
|
|
1992
|
-
* shaka.extern.Stream>}
|
|
1993
|
-
*/
|
|
1994
|
-
const streamsByType = new Map();
|
|
1995
|
-
if (this.currentVariant_.audio) {
|
|
1996
|
-
streamsByType.set(ContentType.AUDIO, this.currentVariant_.audio);
|
|
1997
|
-
}
|
|
1998
|
-
if (this.currentVariant_.video) {
|
|
1999
|
-
streamsByType.set(ContentType.VIDEO, this.currentVariant_.video);
|
|
2000
|
-
}
|
|
2001
|
-
try {
|
|
2002
|
-
mediaState.lastAppendWindowStart = appendWindowStart;
|
|
2003
|
-
mediaState.lastAppendWindowEnd = appendWindowEnd;
|
|
2004
|
-
mediaState.lastCodecs = codecs;
|
|
2005
|
-
mediaState.lastMimeType = mimeType;
|
|
2006
|
-
mediaState.lastTimestampOffset = timestampOffset;
|
|
2007
|
-
|
|
2008
|
-
const ignoreTimestampOffset = this.manifest_.sequenceMode ||
|
|
2009
|
-
this.manifest_.type == shaka.media.ManifestParser.HLS;
|
|
2010
|
-
|
|
2011
|
-
await this.playerInterface_.mediaSourceEngine.setStreamProperties(
|
|
2012
|
-
mediaState.type, timestampOffset, appendWindowStart,
|
|
2013
|
-
appendWindowEnd, ignoreTimestampOffset,
|
|
2014
|
-
reference.mimeType || mediaState.stream.mimeType,
|
|
2015
|
-
reference.codecs || mediaState.stream.codecs, streamsByType);
|
|
2016
|
-
} catch (error) {
|
|
2017
|
-
mediaState.lastAppendWindowStart = null;
|
|
2018
|
-
mediaState.lastAppendWindowEnd = null;
|
|
2019
|
-
mediaState.lastCodecs = null;
|
|
2020
|
-
mediaState.lastTimestampOffset = null;
|
|
2021
|
-
|
|
2022
|
-
throw error;
|
|
2023
|
-
}
|
|
2024
|
-
};
|
|
2025
1996
|
// Dispatching init asynchronously causes the sourceBuffers in
|
|
2026
1997
|
// the MediaSourceEngine to become detached do to race conditions
|
|
2027
1998
|
// with mediaSource and sourceBuffers being created simultaneously.
|
|
2028
|
-
await
|
|
1999
|
+
await this.setProperties_(mediaState, timestampOffset, appendWindowStart,
|
|
2000
|
+
appendWindowEnd, reference, codecs, mimeType);
|
|
2029
2001
|
}
|
|
2030
2002
|
|
|
2031
2003
|
if (!shaka.media.InitSegmentReference.equal(
|
|
@@ -2146,6 +2118,62 @@ shaka.media.StreamingEngine = class {
|
|
|
2146
2118
|
await Promise.all(operations);
|
|
2147
2119
|
}
|
|
2148
2120
|
|
|
2121
|
+
/**
|
|
2122
|
+
*
|
|
2123
|
+
* @param {!shaka.media.StreamingEngine.MediaState_} mediaState
|
|
2124
|
+
* @param {number} timestampOffset
|
|
2125
|
+
* @param {number} appendWindowStart
|
|
2126
|
+
* @param {number} appendWindowEnd
|
|
2127
|
+
* @param {!shaka.media.SegmentReference} reference
|
|
2128
|
+
* @param {?string=} codecs
|
|
2129
|
+
* @param {?string=} mimeType
|
|
2130
|
+
* @private
|
|
2131
|
+
*/
|
|
2132
|
+
async setProperties_(mediaState, timestampOffset, appendWindowStart,
|
|
2133
|
+
appendWindowEnd, reference, codecs, mimeType) {
|
|
2134
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
2135
|
+
|
|
2136
|
+
/**
|
|
2137
|
+
* @type {!Map.<shaka.util.ManifestParserUtils.ContentType,
|
|
2138
|
+
* shaka.extern.Stream>}
|
|
2139
|
+
*/
|
|
2140
|
+
const streamsByType = new Map();
|
|
2141
|
+
if (this.currentVariant_.audio) {
|
|
2142
|
+
streamsByType.set(ContentType.AUDIO, this.currentVariant_.audio);
|
|
2143
|
+
}
|
|
2144
|
+
if (this.currentVariant_.video) {
|
|
2145
|
+
streamsByType.set(ContentType.VIDEO, this.currentVariant_.video);
|
|
2146
|
+
}
|
|
2147
|
+
try {
|
|
2148
|
+
mediaState.lastAppendWindowStart = appendWindowStart;
|
|
2149
|
+
mediaState.lastAppendWindowEnd = appendWindowEnd;
|
|
2150
|
+
if (codecs) {
|
|
2151
|
+
mediaState.lastCodecs = codecs;
|
|
2152
|
+
}
|
|
2153
|
+
if (mimeType) {
|
|
2154
|
+
mediaState.lastMimeType = mimeType;
|
|
2155
|
+
}
|
|
2156
|
+
mediaState.lastTimestampOffset = timestampOffset;
|
|
2157
|
+
|
|
2158
|
+
const ignoreTimestampOffset = this.manifest_.sequenceMode ||
|
|
2159
|
+
this.manifest_.type == shaka.media.ManifestParser.HLS;
|
|
2160
|
+
|
|
2161
|
+
await this.playerInterface_.mediaSourceEngine.setStreamProperties(
|
|
2162
|
+
mediaState.type, timestampOffset, appendWindowStart,
|
|
2163
|
+
appendWindowEnd, ignoreTimestampOffset,
|
|
2164
|
+
reference.mimeType || mediaState.stream.mimeType,
|
|
2165
|
+
reference.codecs || mediaState.stream.codecs, streamsByType);
|
|
2166
|
+
} catch (error) {
|
|
2167
|
+
mediaState.lastAppendWindowStart = null;
|
|
2168
|
+
mediaState.lastAppendWindowEnd = null;
|
|
2169
|
+
mediaState.lastCodecs = null;
|
|
2170
|
+
mediaState.lastMimeType = null;
|
|
2171
|
+
mediaState.lastTimestampOffset = null;
|
|
2172
|
+
|
|
2173
|
+
throw error;
|
|
2174
|
+
}
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2149
2177
|
|
|
2150
2178
|
/**
|
|
2151
2179
|
* Appends the given segment and evicts content if required to append.
|
|
@@ -2173,7 +2201,18 @@ shaka.media.StreamingEngine = class {
|
|
|
2173
2201
|
const shouldParsePrftBox =
|
|
2174
2202
|
(this.config_.parsePrftBox && !this.parsedPrftEventRaised_);
|
|
2175
2203
|
|
|
2176
|
-
|
|
2204
|
+
const isMP4 = stream.mimeType == 'video/mp4' ||
|
|
2205
|
+
stream.mimeType == 'audio/mp4';
|
|
2206
|
+
let timescale = null;
|
|
2207
|
+
if (reference.initSegmentReference) {
|
|
2208
|
+
timescale = reference.initSegmentReference.timescale;
|
|
2209
|
+
}
|
|
2210
|
+
const shouldFixTimestampOffset = isMP4 && timescale &&
|
|
2211
|
+
stream.type === shaka.util.ManifestParserUtils.ContentType.VIDEO &&
|
|
2212
|
+
this.manifest_.type == shaka.media.ManifestParser.DASH &&
|
|
2213
|
+
this.config_.shouldFixTimestampOffset;
|
|
2214
|
+
|
|
2215
|
+
if (hasEmsg || shouldParsePrftBox || shouldFixTimestampOffset) {
|
|
2177
2216
|
parser = new shaka.util.Mp4Parser();
|
|
2178
2217
|
}
|
|
2179
2218
|
|
|
@@ -2193,8 +2232,46 @@ shaka.media.StreamingEngine = class {
|
|
|
2193
2232
|
reference, box));
|
|
2194
2233
|
}
|
|
2195
2234
|
|
|
2196
|
-
if (
|
|
2197
|
-
parser
|
|
2235
|
+
if (shouldFixTimestampOffset) {
|
|
2236
|
+
parser
|
|
2237
|
+
.box('moof', shaka.util.Mp4Parser.children)
|
|
2238
|
+
.box('traf', shaka.util.Mp4Parser.children)
|
|
2239
|
+
.fullBox('tfdt', async (box) => {
|
|
2240
|
+
goog.asserts.assert(
|
|
2241
|
+
box.version != null,
|
|
2242
|
+
'TFDT is a full box and should have a valid version.');
|
|
2243
|
+
|
|
2244
|
+
const parsedTFDT = shaka.util.Mp4BoxParsers.parseTFDTInaccurate(
|
|
2245
|
+
box.reader, box.version);
|
|
2246
|
+
|
|
2247
|
+
const baseMediaDecodeTime = parsedTFDT.baseMediaDecodeTime;
|
|
2248
|
+
|
|
2249
|
+
// In case the time is 0, it is not updated
|
|
2250
|
+
if (!baseMediaDecodeTime) {
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
goog.asserts.assert(typeof(timescale) == 'number',
|
|
2254
|
+
'Should be an number!');
|
|
2255
|
+
|
|
2256
|
+
const scaledMediaDecodeTime = -baseMediaDecodeTime / timescale;
|
|
2257
|
+
|
|
2258
|
+
const comparison1 = Number(mediaState.lastTimestampOffset) || 0;
|
|
2259
|
+
|
|
2260
|
+
if (comparison1 < scaledMediaDecodeTime) {
|
|
2261
|
+
const lastAppendWindowStart = mediaState.lastAppendWindowStart;
|
|
2262
|
+
const lastAppendWindowEnd = mediaState.lastAppendWindowEnd;
|
|
2263
|
+
goog.asserts.assert(typeof(lastAppendWindowStart) == 'number',
|
|
2264
|
+
'Should be an number!');
|
|
2265
|
+
goog.asserts.assert(typeof(lastAppendWindowEnd) == 'number',
|
|
2266
|
+
'Should be an number!');
|
|
2267
|
+
await this.setProperties_(mediaState, scaledMediaDecodeTime,
|
|
2268
|
+
lastAppendWindowStart, lastAppendWindowEnd, reference);
|
|
2269
|
+
}
|
|
2270
|
+
});
|
|
2271
|
+
}
|
|
2272
|
+
|
|
2273
|
+
if (hasEmsg || shouldParsePrftBox || shouldFixTimestampOffset) {
|
|
2274
|
+
parser.parse(segment, /* partialOkay= */ false, isChunkedData);
|
|
2198
2275
|
}
|
|
2199
2276
|
|
|
2200
2277
|
await this.evict_(mediaState, presentationTime);
|
package/lib/mss/mss_parser.js
CHANGED
|
@@ -13,6 +13,7 @@ goog.require('shaka.log');
|
|
|
13
13
|
goog.require('shaka.media.InitSegmentReference');
|
|
14
14
|
goog.require('shaka.media.ManifestParser');
|
|
15
15
|
goog.require('shaka.media.PresentationTimeline');
|
|
16
|
+
goog.require('shaka.media.QualityObserver');
|
|
16
17
|
goog.require('shaka.media.SegmentIndex');
|
|
17
18
|
goog.require('shaka.media.SegmentReference');
|
|
18
19
|
goog.require('shaka.mss.ContentProtection');
|
|
@@ -233,6 +234,11 @@ shaka.mss.MssParser = class {
|
|
|
233
234
|
// No-op
|
|
234
235
|
}
|
|
235
236
|
|
|
237
|
+
/** @override */
|
|
238
|
+
setMediaElement(mediaElement) {
|
|
239
|
+
// No-op
|
|
240
|
+
}
|
|
241
|
+
|
|
236
242
|
/**
|
|
237
243
|
* Makes a network request for the manifest and parses the resulting data.
|
|
238
244
|
*
|
|
@@ -420,6 +426,9 @@ shaka.mss.MssParser = class {
|
|
|
420
426
|
type: shaka.media.ManifestParser.MSS,
|
|
421
427
|
serviceDescription: null,
|
|
422
428
|
nextUrl: null,
|
|
429
|
+
periodCount: 1,
|
|
430
|
+
gapCount: 0,
|
|
431
|
+
isLowLatency: false,
|
|
423
432
|
};
|
|
424
433
|
|
|
425
434
|
// This is the first point where we have a meaningful presentation start
|
|
@@ -700,12 +709,14 @@ shaka.mss.MssParser = class {
|
|
|
700
709
|
initSegmentData = mp4Generator.initSegment();
|
|
701
710
|
this.initSegmentDataByStreamId_.set(stream.id, initSegmentData);
|
|
702
711
|
}
|
|
712
|
+
const qualityInfo =
|
|
713
|
+
shaka.media.QualityObserver.createQualityInfo(stream);
|
|
703
714
|
const initSegmentRef = new shaka.media.InitSegmentReference(
|
|
704
715
|
() => [],
|
|
705
716
|
/* startByte= */ 0,
|
|
706
717
|
/* endByte= */ null,
|
|
707
|
-
|
|
708
|
-
|
|
718
|
+
qualityInfo,
|
|
719
|
+
stream.mssPrivateData.timescale,
|
|
709
720
|
initSegmentData);
|
|
710
721
|
|
|
711
722
|
const segments = this.createSegments_(initSegmentRef,
|