shaka-player 3.1.1 → 3.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AUTHORS +7 -2
- package/CHANGELOG.md +199 -0
- package/CONTRIBUTORS +9 -3
- package/build/generateExterns.js +1 -1
- package/build/misspellings.txt +1 -0
- package/build/shakaBuildHelpers.py +6 -5
- package/build/subprocessWindowsPatch.py +103 -0
- package/demo/common/assets.js +1 -1
- package/dist/controls.css +2 -2
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +1 -1
- package/dist/demo.compiled.js +1 -1
- package/dist/deps.js +7 -7
- package/dist/locales.js +35 -33
- package/dist/receiver.compiled.debug.js +1 -1
- package/dist/receiver.compiled.js +1 -1
- package/dist/shaka-player.compiled.d.ts +171 -3
- package/dist/shaka-player.compiled.debug.d.ts +171 -3
- package/dist/shaka-player.compiled.debug.externs.js +179 -1
- package/dist/shaka-player.compiled.debug.js +365 -344
- package/dist/shaka-player.compiled.debug.map +3 -3
- package/dist/shaka-player.compiled.externs.js +179 -1
- package/dist/shaka-player.compiled.js +806 -798
- package/dist/shaka-player.compiled.map +3 -3
- package/dist/shaka-player.ui.d.ts +171 -3
- package/dist/shaka-player.ui.debug.d.ts +171 -3
- package/dist/shaka-player.ui.debug.externs.js +181 -3
- package/dist/shaka-player.ui.debug.js +458 -435
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +181 -3
- package/dist/shaka-player.ui.js +927 -918
- package/dist/shaka-player.ui.map +4 -4
- package/docs/tutorials/fairplay.md +1 -1
- package/docs/tutorials/faq.md +14 -0
- package/docs/tutorials/index.json +2 -2
- package/docs/tutorials/upgrade-v3.md +38 -0
- package/docs/tutorials/upgrade.md +3 -3
- package/externs/ima.js +4 -0
- package/externs/shaka/ads.js +2 -0
- package/externs/shaka/player.js +6 -0
- package/externs/shaka/text.js +10 -0
- package/karma.conf.js +1 -0
- package/lib/ads/ad_manager.js +29 -11
- package/lib/ads/client_side_ad_manager.js +76 -86
- package/lib/ads/server_side_ad_manager.js +26 -11
- package/lib/cast/cast_proxy.js +3 -2
- package/lib/cast/cast_receiver.js +2 -1
- package/lib/cast/cast_sender.js +2 -2
- package/lib/cea/sei_processor.js +24 -17
- package/lib/dash/dash_parser.js +100 -5
- package/lib/dash/segment_list.js +1 -1
- package/lib/dash/segment_template.js +7 -4
- package/lib/hls/hls_parser.js +44 -17
- package/lib/media/closed_caption_parser.js +3 -1
- package/lib/media/content_workarounds.js +12 -0
- package/lib/media/drm_engine.js +20 -1
- package/lib/media/gap_jumping_controller.js +8 -4
- package/lib/media/media_source_engine.js +1 -2
- package/lib/media/playhead.js +1 -1
- package/lib/media/presentation_timeline.js +3 -1
- package/lib/media/segment_reference.js +8 -0
- package/lib/media/streaming_engine.js +28 -19
- package/lib/net/networking_engine.js +2 -1
- package/lib/player.js +108 -54
- package/lib/polyfill/encryption_scheme.js +2 -0
- package/lib/polyfill/fullscreen.js +2 -0
- package/lib/polyfill/mathround.js +2 -0
- package/lib/polyfill/media_capabilities.js +76 -22
- package/lib/polyfill/mediasource.js +8 -0
- package/lib/polyfill/orientation.js +3 -1
- package/lib/polyfill/patchedmediakeys_apple.js +6 -4
- package/lib/polyfill/patchedmediakeys_ms.js +6 -4
- package/lib/polyfill/patchedmediakeys_nop.js +2 -0
- package/lib/polyfill/patchedmediakeys_webkit.js +6 -4
- package/lib/polyfill/pip_webkit.js +2 -0
- package/lib/polyfill/storage_estimate.js +2 -0
- package/lib/polyfill/video_play_promise.js +2 -0
- package/lib/polyfill/videoplaybackquality.js +2 -0
- package/lib/polyfill/vttcue.js +2 -0
- package/lib/text/cue.js +17 -0
- package/lib/text/mp4_ttml_parser.js +3 -0
- package/lib/text/text_engine.js +14 -2
- package/lib/text/ttml_text_parser.js +135 -87
- package/lib/text/ui_text_displayer.js +233 -83
- package/lib/text/vtt_text_parser.js +4 -2
- package/lib/util/fake_event.js +53 -7
- package/lib/util/mp4_box_parsers.js +1 -1
- package/lib/util/periods.js +111 -35
- package/lib/util/platform.js +13 -1
- package/lib/util/stream_utils.js +79 -10
- package/lib/util/xml_utils.js +19 -0
- package/package.json +2 -2
- package/test/cast/cast_proxy_unit.js +12 -6
- package/test/cast/cast_receiver_integration.js +1 -1
- package/test/cast/cast_utils_unit.js +1 -1
- package/test/cea/mp4_cea_parser_unit.js +26 -0
- package/test/dash/dash_parser_live_unit.js +1 -0
- package/test/dash/dash_parser_manifest_unit.js +220 -2
- package/test/dash/dash_parser_segment_template_unit.js +3 -3
- package/test/hls/hls_parser_unit.js +5 -1
- package/test/media/closed_caption_parser_unit.js +20 -0
- package/test/media/drm_engine_unit.js +56 -0
- package/test/media/playhead_unit.js +27 -1
- package/test/media/presentation_timeline_unit.js +19 -0
- package/test/media/streaming_engine_integration.js +2 -1
- package/test/media/streaming_engine_unit.js +148 -3
- package/test/player_integration.js +38 -0
- package/test/player_unit.js +101 -0
- package/test/test/assets/3675/chunk-stream0-00001.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00002.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00003.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00004.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00005.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00006.m4s +0 -0
- package/test/test/assets/3675/chunk-stream0-00007.m4s +0 -0
- package/test/test/assets/3675/dash_0.mpd +30 -0
- package/test/test/assets/3675/dash_1.mpd +30 -0
- package/test/test/assets/3675/dash_2.mpd +30 -0
- package/test/test/assets/3675/dash_3.mpd +30 -0
- package/test/test/assets/3675/dash_4.mpd +30 -0
- package/test/test/assets/3675/dash_5.mpd +26 -0
- package/test/test/assets/3675/init-stream0.m4s +0 -0
- package/test/test/assets/empty_caption_video_init.mp4 +0 -0
- package/test/test/assets/empty_caption_video_segment.mp4 +0 -0
- package/test/test/util/fake_ad_manager.js +4 -1
- package/test/test/util/simple_fakes.js +5 -3
- package/test/test/util/ttml_utils.js +81 -0
- package/test/text/mp4_ttml_parser_unit.js +11 -18
- package/test/text/ttml_text_parser_unit.js +542 -220
- package/test/text/ui_text_displayer_unit.js +95 -18
- package/test/text/vtt_text_parser_unit.js +17 -1
- package/test/ui/localization_unit.js +15 -14
- package/test/ui/text_displayer_layout_unit.js +52 -1
- package/test/util/mp4_box_parsers_unit.js +28 -0
- package/test/util/periods_unit.js +190 -2
- package/test/util/stream_utils_unit.js +59 -2
- package/ui/airplay_button.js +5 -0
- package/ui/big_play_button.js +10 -0
- package/ui/cast_button.js +2 -3
- package/ui/controls.js +8 -6
- package/ui/less/buttons.less +0 -4
- package/ui/less/containers.less +10 -5
- package/ui/locales/en.json +1 -0
- package/ui/locales/ru.json +6 -6
- package/ui/locales/source.json +4 -0
- package/ui/localization.js +28 -51
- package/ui/pip_button.js +2 -3
- package/ui/play_button.js +10 -10
- package/ui/small_play_button.js +12 -0
- package/ui/text_selection.js +1 -0
- package/docs/tutorials/upgrade-v2-4.md +0 -481
|
@@ -154,8 +154,10 @@ shaka.media.GapJumpingController = class {
|
|
|
154
154
|
}
|
|
155
155
|
// Don't gap jump while paused, so that you don't constantly jump ahead
|
|
156
156
|
// while paused on a livestream. We make an exception for time 0, since we
|
|
157
|
-
// may be _required_ to seek on startup before play can begin
|
|
158
|
-
|
|
157
|
+
// may be _required_ to seek on startup before play can begin, but only if
|
|
158
|
+
// autoplay is enabled.
|
|
159
|
+
if (this.video_.paused && (this.video_.currentTime != 0 ||
|
|
160
|
+
(!this.video_.autoplay && this.video_.currentTime == 0))) {
|
|
159
161
|
return;
|
|
160
162
|
}
|
|
161
163
|
|
|
@@ -215,8 +217,10 @@ shaka.media.GapJumpingController = class {
|
|
|
215
217
|
this.didFireLargeGap_ = true;
|
|
216
218
|
|
|
217
219
|
// Event firing is synchronous.
|
|
218
|
-
const
|
|
219
|
-
'
|
|
220
|
+
const data = new Map()
|
|
221
|
+
.set('currentTime', currentTime)
|
|
222
|
+
.set('gapSize', jumpSize);
|
|
223
|
+
const event = new shaka.util.FakeEvent('largegap', data);
|
|
220
224
|
event.cancelable = true;
|
|
221
225
|
this.onEvent_(event);
|
|
222
226
|
|
|
@@ -48,8 +48,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
48
48
|
* @param {!function(!Array.<shaka.extern.ID3Metadata>, number, ?number)=}
|
|
49
49
|
* onMetadata
|
|
50
50
|
*/
|
|
51
|
-
constructor(video, closedCaptionParser, textDisplayer,
|
|
52
|
-
onMetadata) {
|
|
51
|
+
constructor(video, closedCaptionParser, textDisplayer, onMetadata) {
|
|
53
52
|
/** @private {HTMLMediaElement} */
|
|
54
53
|
this.video_ = video;
|
|
55
54
|
|
package/lib/media/playhead.js
CHANGED
|
@@ -80,7 +80,7 @@ shaka.media.SrcEqualsPlayhead = class {
|
|
|
80
80
|
// We listen for the loaded-data-event so that we know when we can
|
|
81
81
|
// interact with |currentTime|.
|
|
82
82
|
const onLoaded = () => {
|
|
83
|
-
if (this.startTime_ == null) {
|
|
83
|
+
if (this.startTime_ == null || this.startTime_ == 0) {
|
|
84
84
|
this.started_ = true;
|
|
85
85
|
} else {
|
|
86
86
|
// Startup is complete only when the video element acknowledges the
|
|
@@ -361,8 +361,10 @@ shaka.media.PresentationTimeline = class {
|
|
|
361
361
|
*/
|
|
362
362
|
getSegmentAvailabilityEnd() {
|
|
363
363
|
if (!this.isLive() && !this.isInProgress()) {
|
|
364
|
-
|
|
364
|
+
// It's a static manifest (can also be a dynamic->static conversion)
|
|
365
|
+
return this.maxSegmentEndTime_ || this.duration_;
|
|
365
366
|
}
|
|
367
|
+
// Can be either live or "in-progress recording" (live with known duration)
|
|
366
368
|
return Math.min(this.getLiveEdge_() + this.availabilityTimeOffset_,
|
|
367
369
|
this.duration_);
|
|
368
370
|
}
|
|
@@ -153,6 +153,14 @@ shaka.media.SegmentReference = class {
|
|
|
153
153
|
/** @type {number} */
|
|
154
154
|
this.endTime = endTime;
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* The "true" end time of the segment, without considering the period end
|
|
158
|
+
* time. This is necessary for thumbnail segments, where timing requires us
|
|
159
|
+
* to know the original segment duration as described in the manifest.
|
|
160
|
+
* @type {number}
|
|
161
|
+
*/
|
|
162
|
+
this.trueEndTime = endTime;
|
|
163
|
+
|
|
156
164
|
/** @type {function():!Array.<string>} */
|
|
157
165
|
this.getUrisInner = uris;
|
|
158
166
|
|
|
@@ -317,8 +317,10 @@ shaka.media.StreamingEngine = class {
|
|
|
317
317
|
* @param {shaka.extern.Variant} variant
|
|
318
318
|
* @param {boolean=} clearBuffer
|
|
319
319
|
* @param {number=} safeMargin
|
|
320
|
+
* @param {boolean=} force
|
|
321
|
+
* If true, reload the variant even if it did not change.
|
|
320
322
|
*/
|
|
321
|
-
switchVariant(variant, clearBuffer = false, safeMargin = 0) {
|
|
323
|
+
switchVariant(variant, clearBuffer = false, safeMargin = 0, force = false) {
|
|
322
324
|
this.currentVariant_ = variant;
|
|
323
325
|
|
|
324
326
|
if (!this.startupComplete_) {
|
|
@@ -329,12 +331,12 @@ shaka.media.StreamingEngine = class {
|
|
|
329
331
|
if (variant.video) {
|
|
330
332
|
this.switchInternal_(
|
|
331
333
|
variant.video, /* clearBuffer= */ clearBuffer,
|
|
332
|
-
/* safeMargin= */ safeMargin, /* force= */
|
|
334
|
+
/* safeMargin= */ safeMargin, /* force= */ force);
|
|
333
335
|
}
|
|
334
336
|
if (variant.audio) {
|
|
335
337
|
this.switchInternal_(
|
|
336
338
|
variant.audio, /* clearBuffer= */ clearBuffer,
|
|
337
|
-
/* safeMargin= */ safeMargin, /* force= */
|
|
339
|
+
/* safeMargin= */ safeMargin, /* force= */ force);
|
|
338
340
|
}
|
|
339
341
|
}
|
|
340
342
|
|
|
@@ -621,7 +623,7 @@ shaka.media.StreamingEngine = class {
|
|
|
621
623
|
|
|
622
624
|
// Don't clear the buffer unless something is buffered. This extra
|
|
623
625
|
// check prevents extra, useless calls to clear the buffer.
|
|
624
|
-
if (somethingBuffered) {
|
|
626
|
+
if (somethingBuffered || mediaState.performingUpdate) {
|
|
625
627
|
this.forceClearBuffer_(mediaState);
|
|
626
628
|
streamCleared = true;
|
|
627
629
|
}
|
|
@@ -975,7 +977,10 @@ shaka.media.StreamingEngine = class {
|
|
|
975
977
|
unscaledBufferingGoal * this.bufferingGoalScale_;
|
|
976
978
|
|
|
977
979
|
// Check if we've buffered to the end of the presentation.
|
|
978
|
-
|
|
980
|
+
const timeUntilEnd =
|
|
981
|
+
this.manifest_.presentationTimeline.getDuration() - timeNeeded;
|
|
982
|
+
const oneMicrosecond = 1e-6;
|
|
983
|
+
if (timeUntilEnd < oneMicrosecond) {
|
|
979
984
|
// We shouldn't rebuffer if the playhead is close to the end of the
|
|
980
985
|
// presentation.
|
|
981
986
|
shaka.log.debug(logPrefix, 'buffered to end of presentation');
|
|
@@ -1188,10 +1193,14 @@ shaka.media.StreamingEngine = class {
|
|
|
1188
1193
|
|
|
1189
1194
|
mediaState.performingUpdate = true;
|
|
1190
1195
|
|
|
1191
|
-
const initSourceBuffer = this.initSourceBuffer_(mediaState, reference);
|
|
1192
|
-
|
|
1193
|
-
shaka.log.v2(logPrefix, 'fetching segment');
|
|
1194
1196
|
try {
|
|
1197
|
+
await this.initSourceBuffer_(mediaState, reference);
|
|
1198
|
+
this.destroyer_.ensureNotDestroyed();
|
|
1199
|
+
if (this.fatalError_) {
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
shaka.log.v2(logPrefix, 'fetching segment');
|
|
1195
1204
|
const isMP4 = stream.mimeType == 'video/mp4' ||
|
|
1196
1205
|
stream.mimeType == 'audio/mp4';
|
|
1197
1206
|
const isReadableStreamSupported = window.ReadableStream;
|
|
@@ -1199,7 +1208,6 @@ shaka.media.StreamingEngine = class {
|
|
|
1199
1208
|
if (this.config_.lowLatencyMode && isReadableStreamSupported && isMP4) {
|
|
1200
1209
|
let remaining = new Uint8Array(0);
|
|
1201
1210
|
const streamDataCallback = async (data) => {
|
|
1202
|
-
await initSourceBuffer;
|
|
1203
1211
|
this.destroyer_.ensureNotDestroyed();
|
|
1204
1212
|
if (this.fatalError_) {
|
|
1205
1213
|
return;
|
|
@@ -1237,7 +1245,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1237
1245
|
'ReadableStream is not supported by the browser.');
|
|
1238
1246
|
}
|
|
1239
1247
|
const fetchSegment = this.fetch_(mediaState, reference);
|
|
1240
|
-
const
|
|
1248
|
+
const result = await fetchSegment;
|
|
1241
1249
|
this.destroyer_.ensureNotDestroyed();
|
|
1242
1250
|
if (this.fatalError_) {
|
|
1243
1251
|
return;
|
|
@@ -1254,7 +1262,7 @@ shaka.media.StreamingEngine = class {
|
|
|
1254
1262
|
return;
|
|
1255
1263
|
}
|
|
1256
1264
|
await this.append_(
|
|
1257
|
-
mediaState, presentationTime, stream, reference,
|
|
1265
|
+
mediaState, presentationTime, stream, reference, result);
|
|
1258
1266
|
}
|
|
1259
1267
|
|
|
1260
1268
|
this.destroyer_.ensureNotDestroyed();
|
|
@@ -1292,7 +1300,13 @@ shaka.media.StreamingEngine = class {
|
|
|
1292
1300
|
|
|
1293
1301
|
mediaState.performingUpdate = false;
|
|
1294
1302
|
|
|
1295
|
-
if (
|
|
1303
|
+
if (error.code == shaka.util.Error.Code.OPERATION_ABORTED) {
|
|
1304
|
+
// If the network slows down, abort the current fetch request and start
|
|
1305
|
+
// a new one, and ignore the error message.
|
|
1306
|
+
mediaState.performingUpdate = false;
|
|
1307
|
+
mediaState.updateTimer = null;
|
|
1308
|
+
this.scheduleUpdate_(mediaState, 0);
|
|
1309
|
+
} else if (mediaState.type == ContentType.TEXT &&
|
|
1296
1310
|
this.config_.ignoreTextStreamFailures) {
|
|
1297
1311
|
if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS) {
|
|
1298
1312
|
shaka.log.warning(logPrefix,
|
|
@@ -1302,12 +1316,6 @@ shaka.media.StreamingEngine = class {
|
|
|
1302
1316
|
'Text stream failed to parse. Proceeding without it.');
|
|
1303
1317
|
}
|
|
1304
1318
|
this.mediaStates_.delete(ContentType.TEXT);
|
|
1305
|
-
} else if (error.code == shaka.util.Error.Code.OPERATION_ABORTED) {
|
|
1306
|
-
// If the network slows down, abort the current fetch request and start
|
|
1307
|
-
// a new one, and ignore the error message.
|
|
1308
|
-
mediaState.performingUpdate = false;
|
|
1309
|
-
mediaState.updateTimer = null;
|
|
1310
|
-
this.scheduleUpdate_(mediaState, 0);
|
|
1311
1319
|
} else if (error.code == shaka.util.Error.Code.QUOTA_EXCEEDED_ERROR) {
|
|
1312
1320
|
this.handleQuotaExceeded_(mediaState, error);
|
|
1313
1321
|
} else if (error.code == shaka.util.Error.Code.BAD_HTTP_STATUS &&
|
|
@@ -1657,7 +1665,8 @@ shaka.media.StreamingEngine = class {
|
|
|
1657
1665
|
|
|
1658
1666
|
// Dispatch an event to notify the application about the emsg box.
|
|
1659
1667
|
const eventName = shaka.Player.EventName.Emsg;
|
|
1660
|
-
const
|
|
1668
|
+
const data = (new Map()).set('detail', emsg);
|
|
1669
|
+
const event = new shaka.util.FakeEvent(eventName, data);
|
|
1661
1670
|
this.playerInterface_.onEvent(event);
|
|
1662
1671
|
}
|
|
1663
1672
|
}
|
|
@@ -544,7 +544,8 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
|
|
|
544
544
|
}
|
|
545
545
|
|
|
546
546
|
if (error.severity == shaka.util.Error.Severity.RECOVERABLE) {
|
|
547
|
-
const
|
|
547
|
+
const data = (new Map()).set('error', error);
|
|
548
|
+
const event = new shaka.util.FakeEvent('retry', data);
|
|
548
549
|
this.dispatchEvent(event);
|
|
549
550
|
|
|
550
551
|
// Move to the next URI.
|
package/lib/player.js
CHANGED
|
@@ -596,7 +596,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
596
596
|
enterNode: (node, has, wants) => {
|
|
597
597
|
this.dispatchEvent(this.makeEvent_(
|
|
598
598
|
/* name= */ shaka.Player.EventName.OnStateChange,
|
|
599
|
-
/* data= */
|
|
599
|
+
/* data= */ (new Map()).set('state', node.name)));
|
|
600
600
|
|
|
601
601
|
const action = actions.get(node);
|
|
602
602
|
return action(has, wants);
|
|
@@ -627,7 +627,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
627
627
|
onIdle: (node) => {
|
|
628
628
|
this.dispatchEvent(this.makeEvent_(
|
|
629
629
|
/* name= */ shaka.Player.EventName.OnStateIdle,
|
|
630
|
-
/* data= */
|
|
630
|
+
/* data= */ (new Map()).set('state', node.name)));
|
|
631
631
|
},
|
|
632
632
|
};
|
|
633
633
|
|
|
@@ -647,7 +647,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
647
647
|
|
|
648
648
|
/**
|
|
649
649
|
* @param {!shaka.Player.EventName} name
|
|
650
|
-
* @param {Object
|
|
650
|
+
* @param {Map.<string, Object>=} data
|
|
651
651
|
* @return {!shaka.util.FakeEvent}
|
|
652
652
|
* @private
|
|
653
653
|
*/
|
|
@@ -1155,6 +1155,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1155
1155
|
'mpg': 'video/mpeg',
|
|
1156
1156
|
'mpeg': 'video/mpeg',
|
|
1157
1157
|
'm3u8': 'application/x-mpegurl',
|
|
1158
|
+
'mpd': 'application/dash+xml',
|
|
1158
1159
|
'mp3': 'audio/mpeg',
|
|
1159
1160
|
'aac': 'audio/aac',
|
|
1160
1161
|
'flac': 'audio/flac',
|
|
@@ -1285,6 +1286,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1285
1286
|
has.mediaElement = null;
|
|
1286
1287
|
}
|
|
1287
1288
|
|
|
1289
|
+
if (this.adManager_) {
|
|
1290
|
+
// The ad manager is specific to the video, so detach it too.
|
|
1291
|
+
this.adManager_.release();
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1288
1294
|
// Clear our cached copy of the media element.
|
|
1289
1295
|
this.video_ = null;
|
|
1290
1296
|
|
|
@@ -1844,6 +1850,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1844
1850
|
|
|
1845
1851
|
this.currentTextLanguage_ = this.config_.preferredTextLanguage;
|
|
1846
1852
|
this.currentTextRole_ = this.config_.preferredTextRole;
|
|
1853
|
+
this.currentTextForced_ = this.config_.preferForcedSubs;
|
|
1847
1854
|
|
|
1848
1855
|
shaka.Player.applyPlayRange_(this.manifest_.presentationTimeline,
|
|
1849
1856
|
this.config_.playRangeStart,
|
|
@@ -1883,36 +1890,55 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1883
1890
|
this.dispatchEvent(this.makeEvent_(shaka.Player.EventName.Streaming));
|
|
1884
1891
|
|
|
1885
1892
|
// Pick the initial streams to play.
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1893
|
+
// however, we would skip switch to initial variant
|
|
1894
|
+
// if user already pick variant track (via selectVariantTrack api)
|
|
1895
|
+
let initialVariant = null;
|
|
1896
|
+
const activeVariantTrack = this.getVariantTracks().find((t) => t.active);
|
|
1897
|
+
|
|
1898
|
+
if (!activeVariantTrack) {
|
|
1899
|
+
initialVariant = this.chooseVariant_();
|
|
1900
|
+
goog.asserts.assert(initialVariant, 'Must choose an initial variant!');
|
|
1901
|
+
this.switchVariant_(initialVariant, /* fromAdaptation= */ true,
|
|
1902
|
+
/* clearBuffer= */ false, /* safeMargin= */ 0);
|
|
1903
|
+
|
|
1904
|
+
// Now that we have initial streams, we may adjust the start time to align
|
|
1905
|
+
// to a segment boundary.
|
|
1906
|
+
if (this.config_.streaming.startAtSegmentBoundary) {
|
|
1907
|
+
const startTime = this.playhead_.getTime();
|
|
1908
|
+
const adjustedTime =
|
|
1909
|
+
await this.adjustStartTime_(initialVariant, startTime);
|
|
1910
|
+
|
|
1911
|
+
this.playhead_.setStartTime(adjustedTime);
|
|
1912
|
+
}
|
|
1892
1913
|
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
if (initialTextStream) {
|
|
1896
|
-
this.addTextStreamToSwitchHistory_(
|
|
1897
|
-
initialTextStream, /* fromAdaptation= */ true);
|
|
1914
|
+
// Since the first streams just became active, send an adaptation event.
|
|
1915
|
+
this.onAdaptation_();
|
|
1898
1916
|
}
|
|
1899
1917
|
|
|
1900
|
-
|
|
1901
|
-
//
|
|
1902
|
-
if (
|
|
1903
|
-
|
|
1904
|
-
}
|
|
1918
|
+
// Decide if text should be shown automatically.
|
|
1919
|
+
// similar to video/audio track, we would skip switch initial text track
|
|
1920
|
+
// if user already pick text track (via selectTextTrack api)
|
|
1921
|
+
const activeTextTrack = this.getTextTracks().find((t) => t.active);
|
|
1905
1922
|
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
if (this.config_.streaming.startAtSegmentBoundary) {
|
|
1909
|
-
const startTime = this.playhead_.getTime();
|
|
1910
|
-
const adjustedTime =
|
|
1911
|
-
await this.adjustStartTime_(initialVariant, startTime);
|
|
1923
|
+
if (!activeTextTrack) {
|
|
1924
|
+
const initialTextStream = this.chooseTextStream_();
|
|
1912
1925
|
|
|
1913
|
-
|
|
1926
|
+
if (initialTextStream) {
|
|
1927
|
+
this.addTextStreamToSwitchHistory_(
|
|
1928
|
+
initialTextStream, /* fromAdaptation= */ true);
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
if (initialVariant) {
|
|
1932
|
+
this.setInitialTextState_(initialVariant, initialTextStream);
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
// Don't initialize with a text stream unless we should be streaming text.
|
|
1936
|
+
if (initialTextStream && this.shouldStreamText_()) {
|
|
1937
|
+
this.streamingEngine_.switchTextStream(initialTextStream);
|
|
1938
|
+
}
|
|
1914
1939
|
}
|
|
1915
1940
|
|
|
1941
|
+
|
|
1916
1942
|
// Start streaming content. This will start the flow of content down to
|
|
1917
1943
|
// media source.
|
|
1918
1944
|
await this.streamingEngine_.start();
|
|
@@ -1926,8 +1952,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
1926
1952
|
this.filterManifestByCurrentVariant_();
|
|
1927
1953
|
// Dispatch a 'trackschanged' event now that all initial filtering is done.
|
|
1928
1954
|
this.onTracksChanged_();
|
|
1929
|
-
// Since the first streams just became active, send an adaptation event.
|
|
1930
|
-
this.onAdaptation_();
|
|
1931
1955
|
|
|
1932
1956
|
// Now that we've filtered out variants that aren't compatible with the
|
|
1933
1957
|
// active one, update abr manager with filtered variants.
|
|
@@ -2046,6 +2070,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2046
2070
|
decodingInfos: [],
|
|
2047
2071
|
};
|
|
2048
2072
|
|
|
2073
|
+
this.drmEngine_.setSrcEquals(/* srcEquals= */ true);
|
|
2049
2074
|
await this.drmEngine_.initForPlayback(
|
|
2050
2075
|
[variant], /* offlineSessionIds= */ []);
|
|
2051
2076
|
await this.drmEngine_.attach(has.mediaElement);
|
|
@@ -2391,12 +2416,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2391
2416
|
goog.asserts.assert(!endTime || startTime <= endTime,
|
|
2392
2417
|
'Metadata start time should be less or equal to the end time!');
|
|
2393
2418
|
const eventName = shaka.Player.EventName.Metadata;
|
|
2394
|
-
const data =
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
};
|
|
2419
|
+
const data = new Map()
|
|
2420
|
+
.set('startTime', startTime)
|
|
2421
|
+
.set('endTime', endTime)
|
|
2422
|
+
.set('metadataType', metadataType)
|
|
2423
|
+
.set('payload', payload);
|
|
2400
2424
|
this.dispatchEvent(this.makeEvent_(eventName, data));
|
|
2401
2425
|
}
|
|
2402
2426
|
|
|
@@ -2458,7 +2482,6 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
2458
2482
|
this.abrManager_.segmentDownloaded(deltaTimeMs, bytesDownloaded);
|
|
2459
2483
|
}
|
|
2460
2484
|
};
|
|
2461
|
-
|
|
2462
2485
|
return new shaka.net.NetworkingEngine(onProgressUpdated_);
|
|
2463
2486
|
}
|
|
2464
2487
|
|
|
@@ -3217,6 +3240,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3217
3240
|
|
|
3218
3241
|
const tracks = [];
|
|
3219
3242
|
|
|
3243
|
+
let activeTracks = 0;
|
|
3244
|
+
|
|
3220
3245
|
// Convert each variant to a track.
|
|
3221
3246
|
for (const variant of this.manifest_.variants) {
|
|
3222
3247
|
if (!shaka.util.StreamUtils.isPlayable(variant)) {
|
|
@@ -3225,10 +3250,22 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3225
3250
|
|
|
3226
3251
|
const track = shaka.util.StreamUtils.variantToTrack(variant);
|
|
3227
3252
|
track.active = variant == currentVariant;
|
|
3253
|
+
if (!track.active && activeTracks != 1 && currentVariant != null &&
|
|
3254
|
+
variant.video == currentVariant.video &&
|
|
3255
|
+
variant.audio == currentVariant.audio) {
|
|
3256
|
+
track.active = true;
|
|
3257
|
+
}
|
|
3258
|
+
|
|
3259
|
+
if (track.active) {
|
|
3260
|
+
activeTracks++;
|
|
3261
|
+
}
|
|
3228
3262
|
|
|
3229
3263
|
tracks.push(track);
|
|
3230
3264
|
}
|
|
3231
3265
|
|
|
3266
|
+
goog.asserts.assert(activeTracks <= 1,
|
|
3267
|
+
'It should only have one active track');
|
|
3268
|
+
|
|
3232
3269
|
return tracks;
|
|
3233
3270
|
} else if (this.video_ && this.video_.audioTracks) {
|
|
3234
3271
|
// Safari's native HLS always shows a single element in videoTracks.
|
|
@@ -3333,9 +3370,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3333
3370
|
const rows = parseInt(match[2], 10);
|
|
3334
3371
|
const width = fullImageWidth / columns;
|
|
3335
3372
|
const height = fullImageHeight / rows;
|
|
3373
|
+
const totalImages = columns * rows;
|
|
3374
|
+
const segmentDuration = reference.trueEndTime - reference.startTime;
|
|
3375
|
+
const thumbnailDuration = segmentDuration / totalImages;
|
|
3376
|
+
let thumbnailTime = reference.startTime;
|
|
3336
3377
|
let positionX = 0;
|
|
3337
3378
|
let positionY = 0;
|
|
3338
|
-
const totalImages = columns * rows;
|
|
3339
3379
|
// If the number of images in the segment is greater than 1, we have to
|
|
3340
3380
|
// find the correct image. For that we will return to the app the
|
|
3341
3381
|
// coordinates of the position of the correct image.
|
|
@@ -3343,21 +3383,23 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
3343
3383
|
// Note: The time between images within the segment is always
|
|
3344
3384
|
// equidistant.
|
|
3345
3385
|
//
|
|
3346
|
-
// Eg: Total images 5, tileLayout 5x1,
|
|
3386
|
+
// Eg: Total images 5, tileLayout 5x1, segmentDuration 5, thumbnailTime 2
|
|
3347
3387
|
// positionX = 0.4 * fullImageWidth
|
|
3348
3388
|
// positionY = 0
|
|
3349
3389
|
if (totalImages > 1) {
|
|
3350
|
-
const thumbnailTime = time - reference.startTime;
|
|
3351
|
-
const segmentTime = reference.endTime - reference.startTime;
|
|
3352
3390
|
const thumbnailPosition =
|
|
3353
|
-
Math.floor(
|
|
3354
|
-
|
|
3355
|
-
|
|
3391
|
+
Math.floor((time - reference.startTime) / thumbnailDuration);
|
|
3392
|
+
thumbnailTime = reference.startTime +
|
|
3393
|
+
(thumbnailPosition * thumbnailDuration);
|
|
3394
|
+
positionX = (thumbnailPosition % columns) * width;
|
|
3395
|
+
positionY = Math.floor(thumbnailPosition / columns) * height;
|
|
3356
3396
|
}
|
|
3357
3397
|
return {
|
|
3358
3398
|
height: height,
|
|
3359
3399
|
positionX: positionX,
|
|
3360
3400
|
positionY: positionY,
|
|
3401
|
+
startTime: thumbnailTime,
|
|
3402
|
+
duration: thumbnailDuration,
|
|
3361
3403
|
uris: reference.getUris(),
|
|
3362
3404
|
width: width,
|
|
3363
3405
|
};
|
|
@@ -4773,7 +4815,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4773
4815
|
// Surface the buffering event so that the app knows if/when we are
|
|
4774
4816
|
// buffering.
|
|
4775
4817
|
const eventName = shaka.Player.EventName.Buffering;
|
|
4776
|
-
|
|
4818
|
+
const data = (new Map()).set('buffering', isBuffering);
|
|
4819
|
+
this.dispatchEvent(this.makeEvent_(eventName, data));
|
|
4777
4820
|
}
|
|
4778
4821
|
|
|
4779
4822
|
/**
|
|
@@ -4958,6 +5001,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4958
5001
|
const currentVariant = this.streamingEngine_.getCurrentVariant();
|
|
4959
5002
|
if (variant == currentVariant) {
|
|
4960
5003
|
shaka.log.debug('Variant already selected.');
|
|
5004
|
+
// If you want to clear the buffer, we force to reselect the same variant
|
|
5005
|
+
if (clearBuffer) {
|
|
5006
|
+
this.streamingEngine_.switchVariant(variant, clearBuffer, safeMargin,
|
|
5007
|
+
/* force= */ true);
|
|
5008
|
+
}
|
|
4961
5009
|
return;
|
|
4962
5010
|
}
|
|
4963
5011
|
|
|
@@ -4986,6 +5034,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
4986
5034
|
// This will reset the "enabled" of other tracks to false.
|
|
4987
5035
|
track.enabled = true;
|
|
4988
5036
|
|
|
5037
|
+
// AirPlay does not reset the "enabled" of other tracks to false, so
|
|
5038
|
+
// it must be changed by hand.
|
|
5039
|
+
if (track.id !== currentTrack.id) {
|
|
5040
|
+
currentTrack.enabled = false;
|
|
5041
|
+
}
|
|
5042
|
+
|
|
4989
5043
|
const oldTrack =
|
|
4990
5044
|
shaka.util.StreamUtils.html5AudioTrackToTrack(currentTrack);
|
|
4991
5045
|
const newTrack =
|
|
@@ -5144,10 +5198,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5144
5198
|
onVariantChanged_(from, to) {
|
|
5145
5199
|
// Delay the 'variantchanged' event so StreamingEngine has time to absorb
|
|
5146
5200
|
// the changes before the user tries to query it.
|
|
5147
|
-
const
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5201
|
+
const data = new Map()
|
|
5202
|
+
.set('oldTrack', from)
|
|
5203
|
+
.set('newTrack', to);
|
|
5204
|
+
const event = this.makeEvent_(shaka.Player.EventName.VariantChanged, data);
|
|
5151
5205
|
this.delayDispatchEvent_(event);
|
|
5152
5206
|
}
|
|
5153
5207
|
|
|
@@ -5170,10 +5224,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5170
5224
|
|
|
5171
5225
|
/** @private */
|
|
5172
5226
|
onAbrStatusChanged_() {
|
|
5173
|
-
const
|
|
5174
|
-
|
|
5175
|
-
|
|
5176
|
-
this.delayDispatchEvent_(event);
|
|
5227
|
+
const data = (new Map()).set('newStatus', this.config_.abr.enabled);
|
|
5228
|
+
this.delayDispatchEvent_(this.makeEvent_(
|
|
5229
|
+
shaka.Player.EventName.AbrStatusChanged, data));
|
|
5177
5230
|
}
|
|
5178
5231
|
|
|
5179
5232
|
/**
|
|
@@ -5190,7 +5243,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5190
5243
|
}
|
|
5191
5244
|
|
|
5192
5245
|
const eventName = shaka.Player.EventName.Error;
|
|
5193
|
-
const event = this.makeEvent_(eventName,
|
|
5246
|
+
const event = this.makeEvent_(eventName, (new Map()).set('detail', error));
|
|
5194
5247
|
this.dispatchEvent(event);
|
|
5195
5248
|
if (event.defaultPrevented) {
|
|
5196
5249
|
error.handled = true;
|
|
@@ -5219,7 +5272,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
|
|
|
5219
5272
|
eventElement: region.eventElement,
|
|
5220
5273
|
};
|
|
5221
5274
|
|
|
5222
|
-
|
|
5275
|
+
const data = (new Map()).set('detail', clone);
|
|
5276
|
+
this.dispatchEvent(this.makeEvent_(eventName, data));
|
|
5223
5277
|
}
|
|
5224
5278
|
|
|
5225
5279
|
/**
|
|
@@ -6048,7 +6102,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
|
|
|
6048
6102
|
* @define {string} A version number taken from git at compile time.
|
|
6049
6103
|
* @export
|
|
6050
6104
|
*/
|
|
6051
|
-
shaka.Player.version = 'v3.1.
|
|
6105
|
+
shaka.Player.version = 'v3.1.5-uncompiled';
|
|
6052
6106
|
|
|
6053
6107
|
// Initialize the deprecation system using the version string we just set
|
|
6054
6108
|
// on the player.
|
|
@@ -13,12 +13,14 @@ goog.require('shaka.polyfill');
|
|
|
13
13
|
* @see https://wicg.github.io/encrypted-media-encryption-scheme/
|
|
14
14
|
* @see https://github.com/w3c/encrypted-media/pull/457
|
|
15
15
|
* @see https://github.com/google/eme-encryption-scheme-polyfill
|
|
16
|
+
* @export
|
|
16
17
|
*/
|
|
17
18
|
shaka.polyfill.EncryptionScheme = class {
|
|
18
19
|
/**
|
|
19
20
|
* Install the polyfill if needed.
|
|
20
21
|
*
|
|
21
22
|
* @suppress {missingRequire}
|
|
23
|
+
* @export
|
|
22
24
|
*/
|
|
23
25
|
static install() {
|
|
24
26
|
EncryptionSchemePolyfills.install();
|
|
@@ -14,10 +14,12 @@ goog.require('shaka.polyfill');
|
|
|
14
14
|
* Many browsers have prefixed fullscreen methods on Element and document.
|
|
15
15
|
* See {@link https://mzl.la/2K0xcHo Using fullscreen mode} on MDN for more
|
|
16
16
|
* information.
|
|
17
|
+
* @export
|
|
17
18
|
*/
|
|
18
19
|
shaka.polyfill.Fullscreen = class {
|
|
19
20
|
/**
|
|
20
21
|
* Install the polyfill if needed.
|
|
22
|
+
* @export
|
|
21
23
|
*/
|
|
22
24
|
static install() {
|
|
23
25
|
if (!window.Document) {
|
|
@@ -11,10 +11,12 @@ goog.require('shaka.polyfill');
|
|
|
11
11
|
/**
|
|
12
12
|
* @summary A polyfill to patch math round bug on some browsers.
|
|
13
13
|
* @see https://stackoverflow.com/q/12830742
|
|
14
|
+
* @export
|
|
14
15
|
*/
|
|
15
16
|
shaka.polyfill.MathRound = class {
|
|
16
17
|
/**
|
|
17
18
|
* Install the polyfill if needed.
|
|
19
|
+
* @export
|
|
18
20
|
*/
|
|
19
21
|
static install() {
|
|
20
22
|
shaka.log.debug('mathRound.install');
|