shaka-player 2.5.4 → 2.5.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/.csslintrc +13 -0
- package/CHANGELOG.md +56 -0
- package/build/build.py +4 -1
- package/build/check.py +17 -0
- package/build/compiler.py +40 -0
- package/build/types/core +1 -0
- package/build/types/ui +4 -1
- package/demo/asset_card.js +41 -11
- package/demo/cast_receiver/receiver_app.css +19 -13
- package/demo/close_button.js +0 -6
- package/demo/common/asset.js +6 -1
- package/demo/common/assets.js +72 -14
- package/demo/demo.less +105 -16
- package/demo/index.html +8 -5
- package/demo/load.js +5 -7
- package/demo/main.js +81 -16
- package/dist/.csslintstamp +0 -0
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +46 -39
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +46 -39
- package/dist/demo.compiled.map +3 -3
- package/dist/demo.css +1 -1
- package/dist/demo.css.map +1 -1
- package/dist/deps.js +11 -7
- package/dist/receiver.compiled.debug.js +10 -6
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +10 -6
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.debug.externs.js +123 -92
- package/dist/shaka-player.compiled.debug.js +113 -105
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +123 -92
- package/dist/shaka-player.compiled.js +551 -548
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.debug.externs.js +170 -101
- package/dist/shaka-player.ui.debug.js +187 -188
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +170 -101
- package/dist/shaka-player.ui.js +651 -651
- package/dist/shaka-player.ui.map +3 -3
- package/docs/tutorials/drm-config.md +1 -0
- package/docs/tutorials/fairplay.md +77 -0
- package/docs/tutorials/index.json +1 -0
- package/docs/tutorials/ui-customization.md +3 -0
- package/docs/tutorials/ui.md +3 -1
- package/externs/mediasession.js +16 -1
- package/externs/shaka/net.js +5 -1
- package/externs/shaka/player.js +136 -90
- package/externs/webkitmediakeys.js +4 -0
- package/lib/dash/content_protection.js +33 -1
- package/lib/dash/dash_parser.js +12 -4
- package/lib/hls/hls_parser.js +20 -4
- package/lib/media/drm_engine.js +29 -2
- package/lib/media/media_source_engine.js +7 -3
- package/lib/media/playhead.js +8 -3
- package/lib/media/streaming_engine.js +24 -8
- package/lib/media/video_wrapper.js +8 -3
- package/lib/net/networking_engine.js +1 -0
- package/lib/player.js +3 -2
- package/lib/polyfill/patchedmediakeys_apple.js +2 -72
- package/lib/util/error.js +12 -0
- package/lib/util/fairplay_utils.js +122 -0
- package/lib/util/player_configuration.js +5 -0
- package/lib/util/pssh.js +41 -0
- package/package.json +4 -3
- package/shaka-player.uncompiled.js +5 -1
- package/test/dash/dash_parser_content_protection_unit.js +35 -1
- package/test/dash/dash_parser_manifest_unit.js +31 -0
- package/test/hls/hls_live_unit.js +7 -1
- package/test/hls/hls_parser_unit.js +50 -0
- package/test/media/media_source_engine_integration.js +12 -0
- package/test/media/playhead_unit.js +2 -0
- package/test/player_external.js +3 -2
- package/test/player_integration.js +75 -0
- package/test/test/externs/jasmine.js +6 -0
- package/test/test/util/test_scheme.js +32 -1
- package/test/test/util/ui_utils.js +41 -0
- package/test/ui/ui_unit.js +129 -3
- package/ui/audio_language_selection.js +17 -96
- package/ui/big_play_button.js +61 -0
- package/ui/controls.js +111 -86
- package/ui/controls.less +3 -5
- package/ui/externs/ui.js +14 -2
- package/ui/fullscreen_button.js +2 -52
- package/ui/less/buttons.less +8 -8
- package/ui/less/containers.less +21 -21
- package/ui/less/general.less +11 -0
- package/ui/less/other_elements.less +2 -1
- package/ui/less/overflow_menu.less +11 -10
- package/ui/less/range_elements.less +6 -0
- package/ui/less/spinner.less +2 -0
- package/ui/pip_button.js +1 -1
- package/ui/{play_pause_button.js → play_button.js} +17 -47
- package/ui/resolution_selection.js +41 -104
- package/ui/seek_bar.js +15 -17
- package/ui/settings_menu.js +113 -0
- package/ui/small_play_button.js +73 -0
- package/ui/text_displayer.js +2 -2
- package/ui/text_selection.js +39 -105
- package/ui/ui.js +12 -0
|
@@ -21,6 +21,7 @@ goog.require('goog.asserts');
|
|
|
21
21
|
goog.require('shaka.log');
|
|
22
22
|
goog.require('shaka.util.Error');
|
|
23
23
|
goog.require('shaka.util.ManifestParserUtils');
|
|
24
|
+
goog.require('shaka.util.Pssh');
|
|
24
25
|
goog.require('shaka.util.Uint8ArrayUtils');
|
|
25
26
|
goog.require('shaka.util.XmlUtils');
|
|
26
27
|
|
|
@@ -442,6 +443,36 @@ shaka.dash.ContentProtection.getPlayReadyLicenseUrl = function(element) {
|
|
|
442
443
|
};
|
|
443
444
|
|
|
444
445
|
|
|
446
|
+
/**
|
|
447
|
+
* Gets a PlayReady initData from a content protection element
|
|
448
|
+
* containing a PlayReady Pro Object
|
|
449
|
+
*
|
|
450
|
+
* @param {shaka.dash.ContentProtection.Element} element
|
|
451
|
+
* @return {?Array.<shaka.extern.InitDataOverride>}
|
|
452
|
+
* @private
|
|
453
|
+
*/
|
|
454
|
+
shaka.dash.ContentProtection.getInitDataFromPro_ = function(element) {
|
|
455
|
+
const proNode = shaka.util.XmlUtils.findChildNS(
|
|
456
|
+
element.node, 'urn:microsoft:playready', 'pro');
|
|
457
|
+
if (!proNode) {
|
|
458
|
+
return null;
|
|
459
|
+
}
|
|
460
|
+
const Uint8ArrayUtils = shaka.util.Uint8ArrayUtils;
|
|
461
|
+
const data = Uint8ArrayUtils.fromBase64(proNode.textContent);
|
|
462
|
+
const systemId = new Uint8Array([
|
|
463
|
+
0x9a, 0x04, 0xf0, 0x79, 0x98, 0x40, 0x42, 0x86,
|
|
464
|
+
0xab, 0x92, 0xe6, 0x5b, 0xe0, 0x88, 0x5f, 0x95,
|
|
465
|
+
]);
|
|
466
|
+
const pssh = shaka.util.Pssh.createPssh(data, systemId);
|
|
467
|
+
return [
|
|
468
|
+
{
|
|
469
|
+
initData: pssh,
|
|
470
|
+
initDataType: 'cenc',
|
|
471
|
+
keyId: element.keyId,
|
|
472
|
+
},
|
|
473
|
+
];
|
|
474
|
+
};
|
|
475
|
+
|
|
445
476
|
/**
|
|
446
477
|
* Creates DrmInfo objects from the given element.
|
|
447
478
|
*
|
|
@@ -468,7 +499,8 @@ shaka.dash.ContentProtection.convertElements_ = function(
|
|
|
468
499
|
!element.init || element.init.length,
|
|
469
500
|
'Init data must be null or non-empty.');
|
|
470
501
|
|
|
471
|
-
|
|
502
|
+
const proInitData = ContentProtection.getInitDataFromPro_(element);
|
|
503
|
+
const initData = element.init || defaultInit || proInitData;
|
|
472
504
|
const info = ManifestParserUtils.createDrmInfo(keySystem, initData);
|
|
473
505
|
const licenseParser = licenseUrlParsers.get(keySystem);
|
|
474
506
|
if (licenseParser) {
|
package/lib/dash/dash_parser.js
CHANGED
|
@@ -1016,7 +1016,7 @@ shaka.dash.DashParser.prototype.parseAdaptationSet_ = function(context, elem) {
|
|
|
1016
1016
|
case 'subtitle':
|
|
1017
1017
|
kind = value;
|
|
1018
1018
|
break;
|
|
1019
|
-
|
|
1019
|
+
}
|
|
1020
1020
|
}
|
|
1021
1021
|
}
|
|
1022
1022
|
|
|
@@ -1038,12 +1038,12 @@ shaka.dash.DashParser.prototype.parseAdaptationSet_ = function(context, elem) {
|
|
|
1038
1038
|
let closedCaptions = new Map();
|
|
1039
1039
|
for (const prop of accessibilities) {
|
|
1040
1040
|
let schemeId = prop.getAttribute('schemeIdUri');
|
|
1041
|
+
let value = prop.getAttribute('value');
|
|
1041
1042
|
if (schemeId == 'urn:scte:dash:cc:cea-608:2015' ||
|
|
1042
1043
|
schemeId == 'urn:scte:dash:cc:cea-708:2015') {
|
|
1043
1044
|
let channelId = 1;
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
closedCaptionsValue.split(';').forEach((captionStr) => {
|
|
1045
|
+
if (value != null) {
|
|
1046
|
+
value.split(';').forEach((captionStr) => {
|
|
1047
1047
|
let channel;
|
|
1048
1048
|
let language;
|
|
1049
1049
|
// Some closed caption descriptions have channel number and language,
|
|
@@ -1075,6 +1075,14 @@ shaka.dash.DashParser.prototype.parseAdaptationSet_ = function(context, elem) {
|
|
|
1075
1075
|
// 'CC1' as channel id and 'und' as language info.
|
|
1076
1076
|
closedCaptions.set('CC1', 'und');
|
|
1077
1077
|
}
|
|
1078
|
+
} else if (schemeId == 'urn:mpeg:dash:role:2011') {
|
|
1079
|
+
// See DASH IOP 3.9.2 Table 4.
|
|
1080
|
+
if (value != null) {
|
|
1081
|
+
roleValues.push(value);
|
|
1082
|
+
if (value == 'captions') {
|
|
1083
|
+
kind = ManifestParserUtils.TextStreamKind.CLOSED_CAPTION;
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1078
1086
|
}
|
|
1079
1087
|
}
|
|
1080
1088
|
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -539,8 +539,15 @@ shaka.hls.HlsParser.prototype.createPeriod_ = async function(playlist) {
|
|
|
539
539
|
return type == 'SUBTITLES';
|
|
540
540
|
}.bind(this));
|
|
541
541
|
|
|
542
|
-
let textStreamPromises = textStreamTags.map(function(tag) {
|
|
543
|
-
|
|
542
|
+
let textStreamPromises = textStreamTags.map(async function(tag) {
|
|
543
|
+
try {
|
|
544
|
+
return await this.createTextStream_(tag, playlist);
|
|
545
|
+
} catch (e) {
|
|
546
|
+
if (this.config_.hls.ignoreTextStreamFailures) {
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
throw e;
|
|
550
|
+
}
|
|
544
551
|
}.bind(this));
|
|
545
552
|
|
|
546
553
|
const closedCaptionsTags = mediaTags.filter((tag) => {
|
|
@@ -567,7 +574,7 @@ shaka.hls.HlsParser.prototype.createPeriod_ = async function(playlist) {
|
|
|
567
574
|
return {
|
|
568
575
|
startTime: 0,
|
|
569
576
|
variants: variants,
|
|
570
|
-
textStreams: textStreams,
|
|
577
|
+
textStreams: textStreams.filter((t) => t != null),
|
|
571
578
|
};
|
|
572
579
|
};
|
|
573
580
|
|
|
@@ -660,7 +667,9 @@ shaka.hls.HlsParser.prototype.createVariantsForTag_ =
|
|
|
660
667
|
// attached to the stream.
|
|
661
668
|
const textStreamInfo =
|
|
662
669
|
this.mediaTagsToStreamInfosMap_.get(textTags[0].id);
|
|
663
|
-
textStreamInfo
|
|
670
|
+
if (textStreamInfo) {
|
|
671
|
+
textStreamInfo.stream.codecs = textCodecs;
|
|
672
|
+
}
|
|
664
673
|
}
|
|
665
674
|
}
|
|
666
675
|
|
|
@@ -1672,6 +1681,13 @@ shaka.hls.HlsParser.prototype.getStartTime_ = async function(
|
|
|
1672
1681
|
return this.getStartTimeFromTextSegment_(mimeType, codecs, response.data);
|
|
1673
1682
|
}
|
|
1674
1683
|
|
|
1684
|
+
if (mimeType == 'video/webm') {
|
|
1685
|
+
shaka.log.warning(
|
|
1686
|
+
'Hls+WebM combination is not supported at the moment. Skipping.');
|
|
1687
|
+
return null;
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
|
|
1675
1691
|
// TODO(vaage): Add support for additional formats.
|
|
1676
1692
|
//
|
|
1677
1693
|
// Formats:
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -22,6 +22,7 @@ goog.require('shaka.log');
|
|
|
22
22
|
goog.require('shaka.net.NetworkingEngine');
|
|
23
23
|
goog.require('shaka.util.Error');
|
|
24
24
|
goog.require('shaka.util.EventManager');
|
|
25
|
+
goog.require('shaka.util.FairPlayUtils');
|
|
25
26
|
goog.require('shaka.util.FakeEvent');
|
|
26
27
|
goog.require('shaka.util.IDestroyable');
|
|
27
28
|
goog.require('shaka.util.Iterables');
|
|
@@ -1115,6 +1116,29 @@ shaka.media.DrmEngine.prototype.createTemporarySession_ =
|
|
|
1115
1116
|
};
|
|
1116
1117
|
this.activeSessions_.set(session, metadata);
|
|
1117
1118
|
|
|
1119
|
+
try {
|
|
1120
|
+
if (this.config_.initDataTransform) {
|
|
1121
|
+
initData = this.config_.initDataTransform(initData);
|
|
1122
|
+
} else if (this.keySystem().startsWith('com.apple.fps')) {
|
|
1123
|
+
const cert = this.currentDrmInfo_.serverCertificate;
|
|
1124
|
+
const contentId =
|
|
1125
|
+
shaka.util.FairPlayUtils.defaultGetContentId(initData);
|
|
1126
|
+
initData = shaka.util.FairPlayUtils.initDataTransform(
|
|
1127
|
+
initData, contentId, cert);
|
|
1128
|
+
}
|
|
1129
|
+
} catch (error) {
|
|
1130
|
+
let shakaError = error;
|
|
1131
|
+
if (!(error instanceof shaka.util.Error)) {
|
|
1132
|
+
shakaError = new shaka.util.Error(
|
|
1133
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
1134
|
+
shaka.util.Error.Category.DRM,
|
|
1135
|
+
shaka.util.Error.Code.INIT_DATA_TRANSFORM_ERROR,
|
|
1136
|
+
error);
|
|
1137
|
+
}
|
|
1138
|
+
this.onError_(shakaError);
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1118
1142
|
session.generateRequest(initDataType, initData.buffer).catch((error) => {
|
|
1119
1143
|
if (this.isDestroying_) { return; }
|
|
1120
1144
|
|
|
@@ -1186,13 +1210,15 @@ shaka.media.DrmEngine.prototype.sendLicenseRequest_ = function(event) {
|
|
|
1186
1210
|
request.body = event.message;
|
|
1187
1211
|
request.method = 'POST';
|
|
1188
1212
|
request.licenseRequestType = event.messageType;
|
|
1213
|
+
request.sessionId = session.sessionId;
|
|
1189
1214
|
// NOTE: allowCrossSiteCredentials can be set in a request filter.
|
|
1190
1215
|
|
|
1191
1216
|
if (this.currentDrmInfo_.keySystem == 'com.microsoft.playready' ||
|
|
1192
1217
|
this.currentDrmInfo_.keySystem == 'com.chromecast.playready') {
|
|
1193
1218
|
this.unpackPlayReadyRequest_(request);
|
|
1194
1219
|
}
|
|
1195
|
-
if (this.currentDrmInfo_.keySystem.startsWith('com.apple.fps')
|
|
1220
|
+
if (this.currentDrmInfo_.keySystem.startsWith('com.apple.fps') &&
|
|
1221
|
+
this.config_.fairPlayTransform) {
|
|
1196
1222
|
this.formatFairPlayRequest_(request);
|
|
1197
1223
|
}
|
|
1198
1224
|
|
|
@@ -1200,7 +1226,8 @@ shaka.media.DrmEngine.prototype.sendLicenseRequest_ = function(event) {
|
|
|
1200
1226
|
.then(function(response) {
|
|
1201
1227
|
if (this.isDestroying_) { return Promise.reject(); }
|
|
1202
1228
|
|
|
1203
|
-
if (this.currentDrmInfo_.keySystem.startsWith('com.apple.fps')
|
|
1229
|
+
if (this.currentDrmInfo_.keySystem.startsWith('com.apple.fps') &&
|
|
1230
|
+
this.config_.fairPlayTransform) {
|
|
1204
1231
|
this.parseFairPlayResponse_(response);
|
|
1205
1232
|
}
|
|
1206
1233
|
|
|
@@ -704,9 +704,13 @@ shaka.media.MediaSourceEngine.prototype.setStreamProperties = function(
|
|
|
704
704
|
*/
|
|
705
705
|
shaka.media.MediaSourceEngine.prototype.endOfStream = function(reason) {
|
|
706
706
|
return this.enqueueBlockingOperation_(function() {
|
|
707
|
-
//
|
|
708
|
-
//
|
|
709
|
-
|
|
707
|
+
// If endOfStream() has already been called on the media source,
|
|
708
|
+
// don't call it again.
|
|
709
|
+
if (this.ended()) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
// Tizen and IE11 won't let us pass undefined, but it will let us omit the
|
|
713
|
+
// argument.
|
|
710
714
|
if (reason) {
|
|
711
715
|
this.mediaSource_.endOfStream(reason);
|
|
712
716
|
} else {
|
package/lib/media/playhead.js
CHANGED
|
@@ -86,9 +86,14 @@ shaka.media.SrcEqualsPlayhead = class {
|
|
|
86
86
|
// We listen for the loaded-metadata-event so that we know when we can
|
|
87
87
|
// interact with |currentTime|.
|
|
88
88
|
const onLoaded = () => {
|
|
89
|
-
this.
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
if (this.startTime_ == null) {
|
|
90
|
+
this.started_ = true;
|
|
91
|
+
} else {
|
|
92
|
+
// Startup is complete only when the video element acknowledges the
|
|
93
|
+
// seek.
|
|
94
|
+
this.eventManager_.listenOnce(this.mediaElement_, 'seeking', () => {
|
|
95
|
+
this.started_ = true;
|
|
96
|
+
});
|
|
92
97
|
this.mediaElement_.currentTime = this.startTime_;
|
|
93
98
|
}
|
|
94
99
|
};
|
|
@@ -1339,11 +1339,7 @@ shaka.media.StreamingEngine.prototype.update_ = function(mediaState) {
|
|
|
1339
1339
|
// If it's a text stream and the original id starts with 'CC', it's CEA
|
|
1340
1340
|
// closed captions. Do not schedule update for closed captions text
|
|
1341
1341
|
// mediastate, since closed captions are embedded in video streams.
|
|
1342
|
-
|
|
1343
|
-
return state.type == ContentType.TEXT &&
|
|
1344
|
-
state.stream.mimeType == shaka.util.MimeUtils.CLOSED_CAPTION_MIMETYPE;
|
|
1345
|
-
};
|
|
1346
|
-
if (isCaptionState(mediaState)) {
|
|
1342
|
+
if (shaka.media.StreamingEngine.isEmbeddedText_(mediaState)) {
|
|
1347
1343
|
this.playerInterface_.mediaSourceEngine.setSelectedClosedCaptionId(
|
|
1348
1344
|
mediaState.stream.originalId || '');
|
|
1349
1345
|
return null;
|
|
@@ -1443,7 +1439,9 @@ shaka.media.StreamingEngine.prototype.update_ = function(mediaState) {
|
|
|
1443
1439
|
mediaStates.forEach((otherState) => {
|
|
1444
1440
|
// Do not consider embedded captions in this calculation. It could lead
|
|
1445
1441
|
// to hangs in streaming.
|
|
1446
|
-
|
|
1442
|
+
if (shaka.media.StreamingEngine.isEmbeddedText_(otherState)) {
|
|
1443
|
+
return;
|
|
1444
|
+
}
|
|
1447
1445
|
|
|
1448
1446
|
const timeNeeded = this.getTimeNeeded_(otherState, presentationTime);
|
|
1449
1447
|
minTimeNeeded = Math.min(minTimeNeeded, timeNeeded);
|
|
@@ -2199,13 +2197,15 @@ shaka.media.StreamingEngine.prototype.handlePeriodTransition_ = function(
|
|
|
2199
2197
|
goog.asserts.assert(
|
|
2200
2198
|
mediaStates.every(function(ms) {
|
|
2201
2199
|
return ms.needPeriodIndex == needPeriodIndex || ms.hasError ||
|
|
2202
|
-
!shaka.media.StreamingEngine.isIdle_(ms)
|
|
2200
|
+
!shaka.media.StreamingEngine.isIdle_(ms) ||
|
|
2201
|
+
shaka.media.StreamingEngine.isEmbeddedText_(ms);
|
|
2203
2202
|
}),
|
|
2204
2203
|
'All MediaStates should need the same Period or be performing updates.');
|
|
2205
2204
|
|
|
2206
2205
|
// Only call onChooseStreams() when all MediaStates need the same Period.
|
|
2207
2206
|
let needSamePeriod = mediaStates.every(function(ms) {
|
|
2208
|
-
return ms.needPeriodIndex == needPeriodIndex
|
|
2207
|
+
return ms.needPeriodIndex == needPeriodIndex ||
|
|
2208
|
+
shaka.media.StreamingEngine.isEmbeddedText_(ms);
|
|
2209
2209
|
});
|
|
2210
2210
|
if (!needSamePeriod) {
|
|
2211
2211
|
shaka.log.debug(
|
|
@@ -2239,6 +2239,10 @@ shaka.media.StreamingEngine.prototype.handlePeriodTransition_ = function(
|
|
|
2239
2239
|
let allReady = mediaStates.every(function(ms) {
|
|
2240
2240
|
let isIdle = shaka.media.StreamingEngine.isIdle_(ms);
|
|
2241
2241
|
let currentPeriodIndex = this.findPeriodContainingStream_(ms.stream);
|
|
2242
|
+
if (shaka.media.StreamingEngine.isEmbeddedText_(ms)) {
|
|
2243
|
+
// Embedded text tracks don't do Period transitions.
|
|
2244
|
+
return true;
|
|
2245
|
+
}
|
|
2242
2246
|
return isIdle && ms.needPeriodIndex == needPeriodIndex &&
|
|
2243
2247
|
currentPeriodIndex != needPeriodIndex;
|
|
2244
2248
|
}.bind(this));
|
|
@@ -2325,6 +2329,18 @@ shaka.media.StreamingEngine.prototype.handlePeriodTransition_ = function(
|
|
|
2325
2329
|
};
|
|
2326
2330
|
|
|
2327
2331
|
|
|
2332
|
+
/**
|
|
2333
|
+
* @param {shaka.media.StreamingEngine.MediaState_} mediaState
|
|
2334
|
+
* @return {boolean}
|
|
2335
|
+
* @private
|
|
2336
|
+
*/
|
|
2337
|
+
shaka.media.StreamingEngine.isEmbeddedText_ = function(mediaState) {
|
|
2338
|
+
const MimeUtils = shaka.util.MimeUtils;
|
|
2339
|
+
return mediaState.type == shaka.util.ManifestParserUtils.ContentType.TEXT &&
|
|
2340
|
+
mediaState.stream.mimeType == MimeUtils.CLOSED_CAPTION_MIMETYPE;
|
|
2341
|
+
};
|
|
2342
|
+
|
|
2343
|
+
|
|
2328
2344
|
/**
|
|
2329
2345
|
* @param {shaka.media.StreamingEngine.MediaState_} mediaState
|
|
2330
2346
|
* @return {boolean} True if the given MediaState is idle; otherwise, return
|
|
@@ -49,6 +49,9 @@ shaka.media.VideoWrapper = function(video, onSeek, startTime) {
|
|
|
49
49
|
/** @private {number} */
|
|
50
50
|
this.startTime_ = startTime;
|
|
51
51
|
|
|
52
|
+
/** @private {boolean} */
|
|
53
|
+
this.started_ = false;
|
|
54
|
+
|
|
52
55
|
/** @private {shaka.util.EventManager} */
|
|
53
56
|
this.eventManager_ = new shaka.util.EventManager();
|
|
54
57
|
|
|
@@ -92,9 +95,7 @@ shaka.media.VideoWrapper.prototype.release = function() {
|
|
|
92
95
|
* @return {number}
|
|
93
96
|
*/
|
|
94
97
|
shaka.media.VideoWrapper.prototype.getTime = function() {
|
|
95
|
-
return this.video_.
|
|
96
|
-
this.video_.currentTime :
|
|
97
|
-
this.startTime_;
|
|
98
|
+
return this.started_ ? this.video_.currentTime : this.startTime_;
|
|
98
99
|
};
|
|
99
100
|
|
|
100
101
|
|
|
@@ -186,6 +187,10 @@ shaka.media.VideoWrapper.prototype.startListeningToSeeks_ = function() {
|
|
|
186
187
|
this.video_.readyState > 0,
|
|
187
188
|
'The media element should be ready before we listen for seeking.');
|
|
188
189
|
|
|
190
|
+
// Now that any startup seeking is complete, we can trust the video element
|
|
191
|
+
// for currentTime.
|
|
192
|
+
this.started_ = true;
|
|
193
|
+
|
|
189
194
|
this.eventManager_.listen(this.video_, 'seeking', () => this.onSeek_());
|
|
190
195
|
};
|
|
191
196
|
|
package/lib/player.js
CHANGED
|
@@ -429,7 +429,7 @@ shaka.Player.prototype.destroy = async function() {
|
|
|
429
429
|
* @define {string} A version number taken from git at compile time.
|
|
430
430
|
* @export
|
|
431
431
|
*/
|
|
432
|
-
shaka.Player.version = 'v2.5.
|
|
432
|
+
shaka.Player.version = 'v2.5.5-uncompiled';
|
|
433
433
|
|
|
434
434
|
// Initialize the deprecation system using the version string we just set
|
|
435
435
|
// on the player.
|
|
@@ -1728,7 +1728,8 @@ shaka.Player.prototype.onLoad_ = async function(has, wants) {
|
|
|
1728
1728
|
// active one, update abr manager with filtered variants for the current
|
|
1729
1729
|
// period.
|
|
1730
1730
|
/** @type {shaka.extern.Period} */
|
|
1731
|
-
const currentPeriod =
|
|
1731
|
+
const currentPeriod =
|
|
1732
|
+
this.getPresentationPeriod_() || this.manifest_.periods[0];
|
|
1732
1733
|
const hasPrimary = currentPeriod.variants.some((v) => v.primary);
|
|
1733
1734
|
|
|
1734
1735
|
if (!this.config_.preferredAudioLanguage && !hasPrimary) {
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
|
|
18
18
|
goog.provide('shaka.polyfill.PatchedMediaKeysApple');
|
|
19
19
|
|
|
20
|
-
goog.require('goog.Uri');
|
|
21
20
|
goog.require('goog.asserts');
|
|
22
21
|
goog.require('shaka.log');
|
|
23
22
|
goog.require('shaka.polyfill.register');
|
|
@@ -25,7 +24,6 @@ goog.require('shaka.util.EventManager');
|
|
|
25
24
|
goog.require('shaka.util.FakeEvent');
|
|
26
25
|
goog.require('shaka.util.FakeEventTarget');
|
|
27
26
|
goog.require('shaka.util.PublicPromise');
|
|
28
|
-
goog.require('shaka.util.StringUtils');
|
|
29
27
|
goog.require('shaka.util.Uint8ArrayUtils');
|
|
30
28
|
|
|
31
29
|
|
|
@@ -455,6 +453,7 @@ shaka.polyfill.PatchedMediaKeysApple.MediaKeySession.prototype.
|
|
|
455
453
|
// initData into a Uint8Array.
|
|
456
454
|
this.nativeMediaKeySession_ = this.nativeMediaKeys_.createSession(
|
|
457
455
|
'video/mp4', new Uint8Array(initData));
|
|
456
|
+
this.sessionId = this.nativeMediaKeySession_.sessionId || '';
|
|
458
457
|
|
|
459
458
|
// Attach session event handlers here.
|
|
460
459
|
this.eventManager_.listen(this.nativeMediaKeySession_, 'webkitkeymessage',
|
|
@@ -533,71 +532,6 @@ shaka.polyfill.PatchedMediaKeysApple.MediaKeySession.prototype.
|
|
|
533
532
|
};
|
|
534
533
|
|
|
535
534
|
|
|
536
|
-
/**
|
|
537
|
-
* Rebuild FairPlay init data according to Apple's docs. It's unclear why this
|
|
538
|
-
* is not done by the browser, or if the unprefixed version will do it for us.
|
|
539
|
-
*
|
|
540
|
-
* @param {BufferSource} initData
|
|
541
|
-
* @param {BufferSource} certificate
|
|
542
|
-
* @return {BufferSource}
|
|
543
|
-
*/
|
|
544
|
-
shaka.polyfill.PatchedMediaKeysApple.rebuildInitData_ =
|
|
545
|
-
function(initData, certificate) {
|
|
546
|
-
// TODO: Move this into DrmEngine if it is still needed with unprefixed EME.
|
|
547
|
-
// FairPlay init data is in two parts and must be processed a bit.
|
|
548
|
-
// The first part is a 4 byte little-endian int, which is the length of the
|
|
549
|
-
// second part.
|
|
550
|
-
const initDataArray = new Uint8Array(initData);
|
|
551
|
-
const dataview = new DataView(initDataArray.buffer);
|
|
552
|
-
const length = dataview.getUint32(
|
|
553
|
-
/* position= */ 0, /* littleEndian= */ true);
|
|
554
|
-
if (length + 4 != initDataArray.byteLength) {
|
|
555
|
-
throw new Error('Malformed init data!');
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// The second part is a UTF-16 LE URI from the manifest.
|
|
559
|
-
const uriString = shaka.util.StringUtils.fromUTF16(
|
|
560
|
-
initDataArray.slice(4), /* littleEndian= */ true);
|
|
561
|
-
|
|
562
|
-
// The domain of that URI is the content ID according to Apple's FPS sample.
|
|
563
|
-
const uri = new goog.Uri(uriString);
|
|
564
|
-
const contentId = uri.getDomain();
|
|
565
|
-
|
|
566
|
-
// From that, we build a new init data to use in the session. This is
|
|
567
|
-
// composed of several parts. First, the raw init data we already got.
|
|
568
|
-
// Second, a 4-byte LE length followed by the content ID in UTF-16-LE.
|
|
569
|
-
// Third, a 4-byte LE length followed by the certificate.
|
|
570
|
-
const contentIdArray = new Uint8Array(
|
|
571
|
-
shaka.util.StringUtils.toUTF16(contentId, /* littleEndian= */ true));
|
|
572
|
-
|
|
573
|
-
const rebuiltInitData = new Uint8Array(
|
|
574
|
-
initDataArray.byteLength +
|
|
575
|
-
4 + contentIdArray.byteLength +
|
|
576
|
-
4 + certificate.byteLength);
|
|
577
|
-
|
|
578
|
-
let offset = 0;
|
|
579
|
-
/** @param {!Uint8Array} array */
|
|
580
|
-
const append = (array) => {
|
|
581
|
-
rebuiltInitData.set(array, offset);
|
|
582
|
-
offset += array.byteLength;
|
|
583
|
-
};
|
|
584
|
-
/** @param {!Uint8Array} array */
|
|
585
|
-
const appendWithLength = (array) => {
|
|
586
|
-
const view = new DataView(rebuiltInitData.buffer);
|
|
587
|
-
const value = array.byteLength;
|
|
588
|
-
view.setUint32(offset, value, /* littleEndian= */ true);
|
|
589
|
-
offset += 4;
|
|
590
|
-
append(array);
|
|
591
|
-
};
|
|
592
|
-
|
|
593
|
-
append(initDataArray);
|
|
594
|
-
appendWithLength(contentIdArray);
|
|
595
|
-
appendWithLength(new Uint8Array(certificate));
|
|
596
|
-
|
|
597
|
-
return rebuiltInitData;
|
|
598
|
-
};
|
|
599
|
-
|
|
600
|
-
|
|
601
535
|
/**
|
|
602
536
|
* Handler for the native media elements webkitneedkey event.
|
|
603
537
|
*
|
|
@@ -617,16 +551,12 @@ shaka.polyfill.PatchedMediaKeysApple.onWebkitNeedKey_ = function(event) {
|
|
|
617
551
|
|
|
618
552
|
goog.asserts.assert(event.initData != null, 'missing init data!');
|
|
619
553
|
|
|
620
|
-
const certificate = mediaKeys.certificate;
|
|
621
|
-
goog.asserts.assert(certificate != null, 'missing certificate!');
|
|
622
|
-
|
|
623
554
|
// NOTE: Because "this" is a real EventTarget, the event we dispatch here must
|
|
624
555
|
// also be a real Event.
|
|
625
556
|
const event2 = new Event('encrypted');
|
|
626
557
|
// TODO: validate this initDataType against the unprefixed version
|
|
627
558
|
event2.initDataType = 'cenc';
|
|
628
|
-
event2.initData =
|
|
629
|
-
event.initData, certificate);
|
|
559
|
+
event2.initData = event.initData;
|
|
630
560
|
|
|
631
561
|
this.dispatchEvent(event2);
|
|
632
562
|
};
|
package/lib/util/error.js
CHANGED
|
@@ -733,6 +733,18 @@ shaka.util.Error.Code = {
|
|
|
733
733
|
*/
|
|
734
734
|
'EXPIRED': 6014,
|
|
735
735
|
|
|
736
|
+
/**
|
|
737
|
+
* A server certificate wasn't given when it is required. FairPlay requires
|
|
738
|
+
* setting an explicit server certificate in the configuration.
|
|
739
|
+
*/
|
|
740
|
+
'SERVER_CERTIFICATE_REQUIRED': 6015,
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* An error was thrown while executing the init data transformation.
|
|
744
|
+
* <br> error.data[0] is the original error.
|
|
745
|
+
*/
|
|
746
|
+
'INIT_DATA_TRANSFORM_ERROR': 6016,
|
|
747
|
+
|
|
736
748
|
|
|
737
749
|
/**
|
|
738
750
|
* The call to Player.load() was interrupted by a call to Player.unload()
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2016 Google Inc.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
goog.provide('shaka.util.FairPlayUtils');
|
|
19
|
+
|
|
20
|
+
goog.require('goog.Uri');
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @summary A set of FairPlay utility functions.
|
|
25
|
+
* @exportInterface
|
|
26
|
+
*/
|
|
27
|
+
shaka.util.FairPlayUtils = class {
|
|
28
|
+
/**
|
|
29
|
+
* Using the default method, extract a content ID from the init data. This is
|
|
30
|
+
* based on the FairPlay example documentation.
|
|
31
|
+
*
|
|
32
|
+
* @param {!BufferSource} initData
|
|
33
|
+
* @return {string}
|
|
34
|
+
* @export
|
|
35
|
+
*/
|
|
36
|
+
static defaultGetContentId(initData) {
|
|
37
|
+
const uint8 = new Uint8Array(initData);
|
|
38
|
+
const dataview = new DataView(
|
|
39
|
+
uint8.buffer, uint8.byteOffset, uint8.byteLength);
|
|
40
|
+
// The first part is a 4 byte little-endian int, which is the length of
|
|
41
|
+
// the second part.
|
|
42
|
+
const length = dataview.getUint32(
|
|
43
|
+
/* position= */ 0, /* littleEndian= */ true);
|
|
44
|
+
if (length + 4 != uint8.byteLength) {
|
|
45
|
+
throw new RangeError('Malformed FairPlay init data');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// The second part is a UTF-16 LE URI from the manifest.
|
|
49
|
+
const uriString = shaka.util.StringUtils.fromUTF16(
|
|
50
|
+
uint8.subarray(4), /* littleEndian= */ true);
|
|
51
|
+
|
|
52
|
+
// The domain of that URI is the content ID according to Apple's FPS
|
|
53
|
+
// sample.
|
|
54
|
+
const uri = new goog.Uri(uriString);
|
|
55
|
+
return uri.getDomain();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Transforms the init data buffer using the given data. The format is:
|
|
60
|
+
*
|
|
61
|
+
* <pre>
|
|
62
|
+
* [4 bytes] initDataSize
|
|
63
|
+
* [initDataSize bytes] initData
|
|
64
|
+
* [4 bytes] contentIdSize
|
|
65
|
+
* [contentIdSize bytes] contentId
|
|
66
|
+
* [4 bytes] certSize
|
|
67
|
+
* [certSize bytes] cert
|
|
68
|
+
* </pre>
|
|
69
|
+
*
|
|
70
|
+
* @param {!BufferSource} initData
|
|
71
|
+
* @param {!BufferSource|string} contentId
|
|
72
|
+
* @param {?BufferSource} cert The server certificate; this will throw if not
|
|
73
|
+
* provided.
|
|
74
|
+
* @return {!Uint8Array}
|
|
75
|
+
* @export
|
|
76
|
+
*/
|
|
77
|
+
static initDataTransform(initData, contentId, cert) {
|
|
78
|
+
if (!cert || !cert.byteLength) {
|
|
79
|
+
throw new shaka.util.Error(
|
|
80
|
+
shaka.util.Error.Severity.CRITICAL,
|
|
81
|
+
shaka.util.Error.Category.DRM,
|
|
82
|
+
shaka.util.Error.Code.SERVER_CERTIFICATE_REQUIRED);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// From that, we build a new init data to use in the session. This is
|
|
86
|
+
// composed of several parts. First, the raw init data we already got.
|
|
87
|
+
// Second, a 4-byte LE length followed by the content ID in UTF-16-LE.
|
|
88
|
+
// Third, a 4-byte LE length followed by the certificate.
|
|
89
|
+
/** @type {!Uint8Array} */
|
|
90
|
+
let contentIdArray;
|
|
91
|
+
if (typeof contentId == 'string') {
|
|
92
|
+
contentIdArray = new Uint8Array(
|
|
93
|
+
shaka.util.StringUtils.toUTF16(contentId, /* littleEndian= */ true));
|
|
94
|
+
} else {
|
|
95
|
+
contentIdArray = new Uint8Array(contentId);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const rebuiltInitData = new Uint8Array(
|
|
99
|
+
8 + initData.byteLength + contentIdArray.byteLength + cert.byteLength);
|
|
100
|
+
|
|
101
|
+
let offset = 0;
|
|
102
|
+
/** @param {!Uint8Array} array */
|
|
103
|
+
const append = (array) => {
|
|
104
|
+
rebuiltInitData.set(array, offset);
|
|
105
|
+
offset += array.byteLength;
|
|
106
|
+
};
|
|
107
|
+
/** @param {!Uint8Array} array */
|
|
108
|
+
const appendWithLength = (array) => {
|
|
109
|
+
const view = new DataView(rebuiltInitData.buffer);
|
|
110
|
+
const value = array.byteLength;
|
|
111
|
+
view.setUint32(offset, value, /* littleEndian= */ true);
|
|
112
|
+
offset += 4;
|
|
113
|
+
append(array);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
append(new Uint8Array(initData));
|
|
117
|
+
appendWithLength(contentIdArray);
|
|
118
|
+
appendWithLength(new Uint8Array(cert));
|
|
119
|
+
|
|
120
|
+
return rebuiltInitData;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
@@ -83,6 +83,8 @@ shaka.util.PlayerConfiguration = class {
|
|
|
83
83
|
clearKeys: {}, // key is arbitrary key system ID, value must be string
|
|
84
84
|
advanced: {}, // key is arbitrary key system ID, value is a record type
|
|
85
85
|
delayLicenseRequestUntilPlayed: false,
|
|
86
|
+
initDataTransform: (initData) => initData,
|
|
87
|
+
fairPlayTransform: true,
|
|
86
88
|
};
|
|
87
89
|
|
|
88
90
|
const manifest = {
|
|
@@ -102,6 +104,9 @@ shaka.util.PlayerConfiguration = class {
|
|
|
102
104
|
ignoreMinBufferTime: false,
|
|
103
105
|
autoCorrectDrift: true,
|
|
104
106
|
},
|
|
107
|
+
hls: {
|
|
108
|
+
ignoreTextStreamFailures: false,
|
|
109
|
+
},
|
|
105
110
|
};
|
|
106
111
|
|
|
107
112
|
const streaming = {
|