shaka-player 4.2.0 → 4.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/build-and-test.yaml +31 -16
- package/.github/workflows/custom-actions/set-commit-status/action.yaml +1 -5
- package/.github/workflows/deflake.yaml +28 -0
- package/.github/workflows/selenium-lab-tests.yaml +168 -54
- package/AUTHORS +3 -0
- package/CHANGELOG.md +30 -0
- package/CONTRIBUTORS +3 -0
- package/demo/common/message_ids.js +1 -0
- package/demo/config.js +3 -1
- package/demo/locales/en.json +1 -0
- package/demo/locales/source.json +4 -0
- package/dist/controls.css +1 -1
- package/dist/controls.css.map +1 -1
- package/dist/demo.compiled.debug.js +7 -6
- package/dist/demo.compiled.debug.map +3 -3
- package/dist/demo.compiled.js +7 -6
- package/dist/demo.compiled.map +3 -3
- package/dist/deps.js +2 -2
- package/dist/locales.js +29 -29
- package/dist/receiver.compiled.debug.js +4 -3
- package/dist/receiver.compiled.debug.map +3 -3
- package/dist/receiver.compiled.js +4 -3
- package/dist/receiver.compiled.map +3 -3
- package/dist/shaka-player.compiled.d.ts +2 -4
- package/dist/shaka-player.compiled.debug.d.ts +2 -4
- package/dist/shaka-player.compiled.debug.externs.js +24 -26
- package/dist/shaka-player.compiled.debug.js +267 -260
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +24 -26
- package/dist/shaka-player.compiled.js +809 -807
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +2 -4
- package/dist/shaka-player.ui.debug.d.ts +2 -4
- package/dist/shaka-player.ui.debug.externs.js +24 -26
- package/dist/shaka-player.ui.debug.js +244 -236
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +24 -26
- package/dist/shaka-player.ui.js +973 -971
- package/dist/shaka-player.ui.map +4 -4
- package/docs/jsdoc-template/publish.js +33 -0
- package/docs/jsdoc-template/tmpl/container.tmpl +4 -1
- package/docs/jsdoc.conf.json +1 -0
- package/externs/shaka/manifest_parser.js +4 -1
- package/externs/shaka/player.js +7 -1
- package/lib/ads/client_side_ad_manager.js +7 -0
- package/lib/cast/cast_sender.js +39 -14
- package/lib/cea/cea608_memory.js +4 -0
- package/lib/hls/hls_parser.js +54 -82
- package/lib/media/drm_engine.js +52 -1
- package/lib/media/media_source_engine.js +43 -15
- package/lib/media/segment_index.js +4 -1
- package/lib/media/streaming_engine.js +79 -14
- package/lib/offline/storage.js +1 -0
- package/lib/player.js +29 -5
- package/lib/text/ttml_text_parser.js +6 -0
- package/lib/text/ui_text_displayer.js +8 -0
- package/lib/util/error.js +2 -10
- package/lib/util/platform.js +24 -0
- package/lib/util/player_configuration.js +5 -0
- package/lib/util/pssh.js +2 -1
- package/lib/util/stream_utils.js +2 -1
- package/package.json +1 -1
- package/test/cea/cea608_memory_unit.js +88 -0
- package/test/dash/dash_parser_content_protection_unit.js +1 -0
- package/test/dash/dash_parser_live_unit.js +1 -0
- package/test/dash/dash_parser_manifest_unit.js +1 -0
- package/test/dash/dash_parser_segment_base_unit.js +1 -0
- package/test/dash/dash_parser_segment_list_unit.js +1 -0
- package/test/dash/dash_parser_segment_template_unit.js +1 -0
- package/test/hls/hls_live_unit.js +169 -247
- package/test/hls/hls_parser_unit.js +112 -3
- package/test/media/drm_engine_unit.js +90 -0
- package/test/media/media_source_engine_integration.js +36 -0
- package/test/media/media_source_engine_unit.js +118 -1
- package/test/media/segment_index_unit.js +28 -2
- package/test/media/streaming_engine_integration.js +9 -7
- package/test/media/streaming_engine_unit.js +29 -0
- package/test/offline/offline_integration.js +3 -3
- package/test/player_integration.js +2 -2
- package/test/player_src_equals_integration.js +2 -0
- package/test/player_unit.js +39 -1
- package/test/test/assets/small.mp4 +0 -0
- package/test/test/util/dash_parser_util.js +2 -0
- package/test/test/util/ttml_utils.js +0 -2
- package/test/text/ttml_text_parser_unit.js +46 -0
- package/test/ui/text_displayer_layout_unit.js +17 -2
- package/ui/controls.js +0 -7
- package/ui/less/ad_controls.less +4 -0
|
@@ -440,6 +440,7 @@ function buildNav(members) {
|
|
|
440
440
|
[members.externals, 'Externals', seen, linktoExternal],
|
|
441
441
|
[members.namespaces, 'Namespaces', seen, linkto],
|
|
442
442
|
[members.classes, 'Classes', seen, linkto],
|
|
443
|
+
[members.enums, 'Enums', seen, linkto],
|
|
443
444
|
[members.interfaces, 'Interfaces', seen, linkto],
|
|
444
445
|
[members.events, 'Events', seen, linkto],
|
|
445
446
|
[members.mixins, 'Mixins', seen, linkto],
|
|
@@ -484,6 +485,7 @@ function fixUpType(type) {
|
|
|
484
485
|
exports.publish = (taffyData, opts, tutorials) => {
|
|
485
486
|
let classes;
|
|
486
487
|
let conf;
|
|
488
|
+
let enums;
|
|
487
489
|
let externals;
|
|
488
490
|
let files;
|
|
489
491
|
let fromDir;
|
|
@@ -733,6 +735,31 @@ exports.publish = (taffyData, opts, tutorials) => {
|
|
|
733
735
|
});
|
|
734
736
|
|
|
735
737
|
members = helper.getMembers(data);
|
|
738
|
+
members.enums = [];
|
|
739
|
+
|
|
740
|
+
data().each(doclet => {
|
|
741
|
+
if (doclet.isEnum) {
|
|
742
|
+
const hasParent = !!docletMap[doclet.memberof];
|
|
743
|
+
|
|
744
|
+
// Enums without parents (not attached to a class, but directly to a
|
|
745
|
+
// namespace) aren't recognized by jsdoc by default and need to be
|
|
746
|
+
// massaged into the docs separately.
|
|
747
|
+
if (!hasParent) {
|
|
748
|
+
// Temporarily call this a "class", so that we can fix the URL.
|
|
749
|
+
doclet.kind = 'class';
|
|
750
|
+
// Regenerate the URL, so that this enum gets its own page
|
|
751
|
+
// instead of a URL that links to an ID within a non-existent
|
|
752
|
+
// parent page.
|
|
753
|
+
delete helper.longnameToUrl[doclet.longname];
|
|
754
|
+
helper.createLink(doclet);
|
|
755
|
+
// Mark this as an enum (custom type), so that it doesn't get
|
|
756
|
+
// slotted with the classes.
|
|
757
|
+
doclet.kind = 'enum';
|
|
758
|
+
members.enums.push(doclet);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
});
|
|
762
|
+
|
|
736
763
|
members.tutorials = tutorials.children;
|
|
737
764
|
if (conf.default.sortTutorialsByConfigOrder) {
|
|
738
765
|
members.tutorials.sort((a, b) => {
|
|
@@ -778,6 +805,7 @@ exports.publish = (taffyData, opts, tutorials) => {
|
|
|
778
805
|
|
|
779
806
|
// set up the lists that we'll use to generate pages
|
|
780
807
|
classes = taffy(members.classes);
|
|
808
|
+
enums = taffy(members.enums);
|
|
781
809
|
modules = taffy(members.modules);
|
|
782
810
|
namespaces = taffy(members.namespaces);
|
|
783
811
|
mixins = taffy(members.mixins);
|
|
@@ -786,6 +814,7 @@ exports.publish = (taffyData, opts, tutorials) => {
|
|
|
786
814
|
|
|
787
815
|
Object.keys(helper.longnameToUrl).forEach(longname => {
|
|
788
816
|
const myClasses = helper.find(classes, {longname: longname});
|
|
817
|
+
const myEnums = helper.find(enums, {longname: longname});
|
|
789
818
|
const myExternals = helper.find(externals, {longname: longname});
|
|
790
819
|
const myInterfaces = helper.find(interfaces, {longname: longname});
|
|
791
820
|
const myMixins = helper.find(mixins, {longname: longname});
|
|
@@ -800,6 +829,10 @@ exports.publish = (taffyData, opts, tutorials) => {
|
|
|
800
829
|
generate(`Class: ${myClasses[0].longname}`, myClasses, helper.longnameToUrl[longname]);
|
|
801
830
|
}
|
|
802
831
|
|
|
832
|
+
if (myEnums.length) {
|
|
833
|
+
generate(`Enum: ${myEnums[0].longname}`, myEnums, helper.longnameToUrl[longname]);
|
|
834
|
+
}
|
|
835
|
+
|
|
803
836
|
if (myNamespaces.length) {
|
|
804
837
|
generate(`Namespace: ${myNamespaces[0].longname}`, myNamespaces, helper.longnameToUrl[longname]);
|
|
805
838
|
}
|
|
@@ -144,7 +144,10 @@
|
|
|
144
144
|
return m.longname && m.longname.indexOf('module:') !== 0;
|
|
145
145
|
});
|
|
146
146
|
}
|
|
147
|
-
|
|
147
|
+
|
|
148
|
+
// Skip enums, since they already have their values listed under
|
|
149
|
+
// "Properties".
|
|
150
|
+
if (members && members.length && members.forEach && doc.kind != 'enum') {
|
|
148
151
|
?>
|
|
149
152
|
<h3 class="subsection-title">Members</h3>
|
|
150
153
|
|
package/docs/jsdoc.conf.json
CHANGED
|
@@ -111,7 +111,8 @@ shaka.extern.ManifestParser = class {
|
|
|
111
111
|
* onError: function(!shaka.util.Error),
|
|
112
112
|
* isLowLatencyMode: function():boolean,
|
|
113
113
|
* isAutoLowLatencyMode: function():boolean,
|
|
114
|
-
* enableLowLatencyMode: function()
|
|
114
|
+
* enableLowLatencyMode: function(),
|
|
115
|
+
* updateDuration: function()
|
|
115
116
|
* }}
|
|
116
117
|
*
|
|
117
118
|
* @description
|
|
@@ -147,6 +148,8 @@ shaka.extern.ManifestParser = class {
|
|
|
147
148
|
* Return true if auto low latency streaming mode is enabled.
|
|
148
149
|
* @property {function()} enableLowLatencyMode
|
|
149
150
|
* Enable low latency streaming mode.
|
|
151
|
+
* @property {function()} updateDuration
|
|
152
|
+
* Update the presentation duration based on PresentationTimeline.
|
|
150
153
|
* @exportDoc
|
|
151
154
|
*/
|
|
152
155
|
shaka.extern.ManifestParser.PlayerInterface;
|
package/externs/shaka/player.js
CHANGED
|
@@ -632,7 +632,8 @@ shaka.extern.AdvancedDrmConfiguration;
|
|
|
632
632
|
* logLicenseExchange: boolean,
|
|
633
633
|
* updateExpirationTime: number,
|
|
634
634
|
* preferredKeySystems: !Array.<string>,
|
|
635
|
-
* keySystemsMapping: !Object.<string, string
|
|
635
|
+
* keySystemsMapping: !Object.<string, string>,
|
|
636
|
+
* parseInbandPsshEnabled: boolean
|
|
636
637
|
* }}
|
|
637
638
|
*
|
|
638
639
|
* @property {shaka.extern.RetryParameters} retryParameters
|
|
@@ -675,6 +676,11 @@ shaka.extern.AdvancedDrmConfiguration;
|
|
|
675
676
|
* Specifies the priorties of available DRM key systems.
|
|
676
677
|
* @property {Object.<string, string>} keySystemsMapping
|
|
677
678
|
* A map of key system name to key system name.
|
|
679
|
+
* @property {boolean} parseInbandPsshEnabled
|
|
680
|
+
* <i>Defaults to true on Xbox One, and false for all other browsers.</i><br>
|
|
681
|
+
* When true parse DRM init data from pssh boxes in media and init segments
|
|
682
|
+
* and ignore 'encrypted' events.
|
|
683
|
+
* This is required when using in-band key rotation on Xbox One.
|
|
678
684
|
*
|
|
679
685
|
* @exportDoc
|
|
680
686
|
*/
|
|
@@ -205,6 +205,13 @@ shaka.ads.ClientSideAdManager = class {
|
|
|
205
205
|
this.video_.offsetHeight, viewMode);
|
|
206
206
|
});
|
|
207
207
|
this.resizeObserver_.observe(this.video_);
|
|
208
|
+
} else {
|
|
209
|
+
this.eventManager_.listen(document, 'fullscreenchange', () => {
|
|
210
|
+
const viewMode = this.isFullScreenEnabled_() ?
|
|
211
|
+
google.ima.ViewMode.FULLSCREEN : google.ima.ViewMode.NORMAL;
|
|
212
|
+
this.imaAdsManager_.resize(this.video_.offsetWidth,
|
|
213
|
+
this.video_.offsetHeight, viewMode);
|
|
214
|
+
});
|
|
208
215
|
}
|
|
209
216
|
|
|
210
217
|
// Single video and overlay ads will start at this time
|
package/lib/cast/cast_sender.js
CHANGED
|
@@ -192,21 +192,14 @@ shaka.cast.CastSender = class {
|
|
|
192
192
|
// If the API becomes available before this instance dies, init will be
|
|
193
193
|
// called again.
|
|
194
194
|
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
// be overwritten.
|
|
202
|
-
if (!window.__onGCastApiAvailable) {
|
|
203
|
-
window.__onGCastApiAvailable = shaka.cast.CastSender.onSdkLoaded_;
|
|
204
|
-
}
|
|
205
|
-
if (window.__onGCastApiAvailable != shaka.cast.CastSender.onSdkLoaded_) {
|
|
206
|
-
shaka.log.alwaysWarn('A global Cast SDK hook is already installed! ' +
|
|
207
|
-
'Shaka Player will be unable to receive a notification when the ' +
|
|
208
|
-
'Cast SDK is ready.');
|
|
195
|
+
// Check if our callback is already installed.
|
|
196
|
+
if (window.__onGCastApiAvailable !== CastSender.onGCastApiAvailable_) {
|
|
197
|
+
// Save pre-existing __onGCastApiAvailable in order to restore later.
|
|
198
|
+
CastSender.__onGCastApiAvailable_ =
|
|
199
|
+
window.__onGCastApiAvailable || null;
|
|
200
|
+
window.__onGCastApiAvailable = CastSender.onGCastApiAvailable_;
|
|
209
201
|
}
|
|
202
|
+
|
|
210
203
|
return;
|
|
211
204
|
}
|
|
212
205
|
|
|
@@ -757,6 +750,9 @@ shaka.cast.CastSender.hasReceivers_ = false;
|
|
|
757
750
|
/** @private {chrome.cast.Session} */
|
|
758
751
|
shaka.cast.CastSender.session_ = null;
|
|
759
752
|
|
|
753
|
+
/** @private {?function(boolean)} */
|
|
754
|
+
shaka.cast.CastSender.__onGCastApiAvailable_ = null;
|
|
755
|
+
|
|
760
756
|
/**
|
|
761
757
|
* A set of all living CastSender instances. The constructor and destroy
|
|
762
758
|
* methods will add and remove instances from this set.
|
|
@@ -784,3 +780,32 @@ shaka.cast.CastSender.onSdkLoaded_ = (loaded) => {
|
|
|
784
780
|
}
|
|
785
781
|
}
|
|
786
782
|
};
|
|
783
|
+
|
|
784
|
+
/**
|
|
785
|
+
* @param {boolean} available
|
|
786
|
+
* @private
|
|
787
|
+
*/
|
|
788
|
+
shaka.cast.CastSender.onGCastApiAvailable_ = (available) => {
|
|
789
|
+
// Restore callback from saved.
|
|
790
|
+
if (shaka.cast.CastSender.__onGCastApiAvailable_) {
|
|
791
|
+
window.__onGCastApiAvailable =
|
|
792
|
+
shaka.cast.CastSender.__onGCastApiAvailable_;
|
|
793
|
+
} else {
|
|
794
|
+
delete window.__onGCastApiAvailable;
|
|
795
|
+
}
|
|
796
|
+
shaka.cast.CastSender.__onGCastApiAvailable_ = null;
|
|
797
|
+
|
|
798
|
+
// A note about this value: In our testing environment, we load both
|
|
799
|
+
// uncompiled and compiled code. This global callback in uncompiled mode
|
|
800
|
+
// can be overwritten by the same in compiled mode. The two versions will
|
|
801
|
+
// each have their own instances_ map. Therefore the callback must have a
|
|
802
|
+
// name, as opposed to being anonymous. This way, the CastSender tests
|
|
803
|
+
// can invoke the named static method instead of using a global that could
|
|
804
|
+
// be overwritten.
|
|
805
|
+
shaka.cast.CastSender.onSdkLoaded_(available);
|
|
806
|
+
|
|
807
|
+
// call restored callback (if any)
|
|
808
|
+
if (typeof window.__onGCastApiAvailable === 'function') {
|
|
809
|
+
window.__onGCastApiAvailable(available);
|
|
810
|
+
}
|
|
811
|
+
};
|
package/lib/cea/cea608_memory.js
CHANGED
|
@@ -184,6 +184,10 @@ shaka.cea.Cea608Memory = class {
|
|
|
184
184
|
* @param {number} count Count of rows to move.
|
|
185
185
|
*/
|
|
186
186
|
moveRows(dst, src, count) {
|
|
187
|
+
if (src < 0 || dst < 0) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
187
191
|
if (dst >= src) {
|
|
188
192
|
for (let i = count-1; i >= 0; i--) {
|
|
189
193
|
this.rows_[dst + i] = this.rows_[src + i].map((e) => e);
|
package/lib/hls/hls_parser.js
CHANGED
|
@@ -180,10 +180,6 @@ shaka.hls.HlsParser = class {
|
|
|
180
180
|
*/
|
|
181
181
|
this.groupIdToClosedCaptionsMap_ = new Map();
|
|
182
182
|
|
|
183
|
-
/** True if some of the variants in the playlist is encrypted with AES-128.
|
|
184
|
-
* @private {boolean} */
|
|
185
|
-
this.aesEncrypted_ = false;
|
|
186
|
-
|
|
187
183
|
/** @private {Map.<string, string>} */
|
|
188
184
|
this.groupIdToCodecsMap_ = new Map();
|
|
189
185
|
|
|
@@ -288,6 +284,21 @@ shaka.hls.HlsParser = class {
|
|
|
288
284
|
}
|
|
289
285
|
|
|
290
286
|
await Promise.all(updates);
|
|
287
|
+
|
|
288
|
+
this.notifySegments_();
|
|
289
|
+
|
|
290
|
+
// If any hasEndList is false, the stream is still live.
|
|
291
|
+
const stillLive = streamInfos.some((s) => s.hasEndList == false);
|
|
292
|
+
if (!stillLive) {
|
|
293
|
+
// Convert the presentation to VOD and set the duration.
|
|
294
|
+
const PresentationType = shaka.hls.HlsParser.PresentationType_;
|
|
295
|
+
this.setPresentationType_(PresentationType.VOD);
|
|
296
|
+
|
|
297
|
+
const maxTimestamps = streamInfos.map((s) => s.maxTimestamp);
|
|
298
|
+
// The duration is the minimum of the end times of all streams.
|
|
299
|
+
this.presentationTimeline_.setDuration(Math.min(...maxTimestamps));
|
|
300
|
+
this.playerInterface_.updateDuration();
|
|
301
|
+
}
|
|
291
302
|
}
|
|
292
303
|
|
|
293
304
|
/**
|
|
@@ -298,7 +309,6 @@ shaka.hls.HlsParser = class {
|
|
|
298
309
|
* @private
|
|
299
310
|
*/
|
|
300
311
|
async updateStream_(streamInfo) {
|
|
301
|
-
const PresentationType = shaka.hls.HlsParser.PresentationType_;
|
|
302
312
|
const manifestUri = streamInfo.absoluteMediaPlaylistUri;
|
|
303
313
|
const uriObj = new goog.Uri(manifestUri);
|
|
304
314
|
if (this.lowLatencyMode_ && streamInfo.canSkipSegments) {
|
|
@@ -331,6 +341,7 @@ shaka.hls.HlsParser = class {
|
|
|
331
341
|
streamInfo.verbatimMediaPlaylistUri, playlist, stream.type,
|
|
332
342
|
stream.mimeType, streamInfo.mediaSequenceToStartTime, mediaVariables,
|
|
333
343
|
stream.codecs);
|
|
344
|
+
this.segmentsToNotifyByStream_.push(segments);
|
|
334
345
|
|
|
335
346
|
stream.segmentIndex.mergeAndEvict(
|
|
336
347
|
segments, this.presentationTimeline_.getSegmentAvailabilityStart());
|
|
@@ -351,10 +362,10 @@ shaka.hls.HlsParser = class {
|
|
|
351
362
|
shaka.hls.Utils.getFirstTagWithName(playlist.tags, 'EXT-X-ENDLIST');
|
|
352
363
|
|
|
353
364
|
if (endListTag) {
|
|
354
|
-
//
|
|
355
|
-
//
|
|
356
|
-
|
|
357
|
-
|
|
365
|
+
// Flag this for later. We don't convert the whole presentation into VOD
|
|
366
|
+
// until we've seen the ENDLIST tag for all active playlists.
|
|
367
|
+
streamInfo.hasEndList = true;
|
|
368
|
+
streamInfo.maxTimestamp = newestSegment.endTime;
|
|
358
369
|
}
|
|
359
370
|
}
|
|
360
371
|
|
|
@@ -576,7 +587,6 @@ shaka.hls.HlsParser = class {
|
|
|
576
587
|
playlist, uri, uri, codecs, type, language, primary, name,
|
|
577
588
|
channelsCount, closedCaptions, characteristics, forced, spatialAudio,
|
|
578
589
|
mimeType);
|
|
579
|
-
goog.asserts.assert(streamInfo != null, 'StreamInfo is null!');
|
|
580
590
|
this.uriToStreamInfosMap_.set(uri, streamInfo);
|
|
581
591
|
|
|
582
592
|
// Wrap the stream from that stream info with a variant.
|
|
@@ -654,17 +664,6 @@ shaka.hls.HlsParser = class {
|
|
|
654
664
|
this.syncStreamsWithProgramDateTime_();
|
|
655
665
|
}
|
|
656
666
|
|
|
657
|
-
if (this.aesEncrypted_ && variants.length == 0) {
|
|
658
|
-
// We do not support AES-128 encryption with HLS yet. Variants is null
|
|
659
|
-
// when the playlist is encrypted with AES-128.
|
|
660
|
-
shaka.log.info('No stream is created, because we don\'t support AES-128',
|
|
661
|
-
'encryption yet');
|
|
662
|
-
throw new shaka.util.Error(
|
|
663
|
-
shaka.util.Error.Severity.CRITICAL,
|
|
664
|
-
shaka.util.Error.Category.MANIFEST,
|
|
665
|
-
shaka.util.Error.Code.HLS_AES_128_ENCRYPTION_NOT_SUPPORTED);
|
|
666
|
-
}
|
|
667
|
-
|
|
668
667
|
// Find the min and max timestamp of the earliest segment in all streams.
|
|
669
668
|
// Find the minimum duration of all streams as well.
|
|
670
669
|
let minDuration = Infinity;
|
|
@@ -863,8 +862,6 @@ shaka.hls.HlsParser = class {
|
|
|
863
862
|
}
|
|
864
863
|
try {
|
|
865
864
|
const streamInfo = await this.createStreamInfoFromMediaTag_(tag);
|
|
866
|
-
goog.asserts.assert(
|
|
867
|
-
streamInfo, 'Should always have a streamInfo for text');
|
|
868
865
|
return streamInfo.stream;
|
|
869
866
|
} catch (e) {
|
|
870
867
|
if (this.config_.hls.ignoreTextStreamFailures) {
|
|
@@ -907,8 +904,6 @@ shaka.hls.HlsParser = class {
|
|
|
907
904
|
}
|
|
908
905
|
try {
|
|
909
906
|
const streamInfo = await this.createStreamInfoFromImageTag_(tag);
|
|
910
|
-
goog.asserts.assert(
|
|
911
|
-
streamInfo, 'Should always have a streamInfo for image');
|
|
912
907
|
return streamInfo.stream;
|
|
913
908
|
} catch (e) {
|
|
914
909
|
if (this.config_.hls.ignoreImageStreamFailures) {
|
|
@@ -960,22 +955,17 @@ shaka.hls.HlsParser = class {
|
|
|
960
955
|
const streamInfos = await this.createStreamInfosForVariantTag_(tag,
|
|
961
956
|
resolution, frameRate);
|
|
962
957
|
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
videoRange);
|
|
975
|
-
}
|
|
976
|
-
// We do not support AES-128 encryption with HLS yet. If the streamInfos
|
|
977
|
-
// is null because of AES-128 encryption, do not create variants for that.
|
|
978
|
-
return [];
|
|
958
|
+
goog.asserts.assert(streamInfos.audio.length ||
|
|
959
|
+
streamInfos.video.length, 'We should have created a stream!');
|
|
960
|
+
|
|
961
|
+
return this.createVariants_(
|
|
962
|
+
streamInfos.audio,
|
|
963
|
+
streamInfos.video,
|
|
964
|
+
bandwidth,
|
|
965
|
+
width,
|
|
966
|
+
height,
|
|
967
|
+
frameRate,
|
|
968
|
+
videoRange);
|
|
979
969
|
});
|
|
980
970
|
|
|
981
971
|
const allVariants = await Promise.all(variantsPromises);
|
|
@@ -992,7 +982,7 @@ shaka.hls.HlsParser = class {
|
|
|
992
982
|
* @param {!shaka.hls.Tag} tag
|
|
993
983
|
* @param {?string} resolution
|
|
994
984
|
* @param {?string} frameRate
|
|
995
|
-
* @return {!Promise
|
|
985
|
+
* @return {!Promise.<!shaka.hls.HlsParser.StreamInfos>}
|
|
996
986
|
* @private
|
|
997
987
|
*/
|
|
998
988
|
async createStreamInfosForVariantTag_(tag, resolution, frameRate) {
|
|
@@ -1062,17 +1052,10 @@ shaka.hls.HlsParser = class {
|
|
|
1062
1052
|
type = ContentType.VIDEO;
|
|
1063
1053
|
}
|
|
1064
1054
|
|
|
1065
|
-
let streamInfo;
|
|
1066
1055
|
if (!ignoreStream) {
|
|
1067
|
-
streamInfo =
|
|
1056
|
+
const streamInfo =
|
|
1068
1057
|
await this.createStreamInfoFromVariantTag_(tag, allCodecs, type);
|
|
1069
|
-
}
|
|
1070
|
-
if (streamInfo) {
|
|
1071
1058
|
res[streamInfo.stream.type] = [streamInfo];
|
|
1072
|
-
} else if (streamInfo === null) {
|
|
1073
|
-
// Triple-equals for undefined.
|
|
1074
|
-
shaka.log.debug('streamInfo is null');
|
|
1075
|
-
return null;
|
|
1076
1059
|
}
|
|
1077
1060
|
this.filterLegacyCodecs_(res);
|
|
1078
1061
|
return res;
|
|
@@ -1364,7 +1347,7 @@ shaka.hls.HlsParser = class {
|
|
|
1364
1347
|
* Parse EXT-X-MEDIA media tag into a Stream object.
|
|
1365
1348
|
*
|
|
1366
1349
|
* @param {shaka.hls.Tag} tag
|
|
1367
|
-
* @return {!Promise
|
|
1350
|
+
* @return {!Promise.<!shaka.hls.HlsParser.StreamInfo>}
|
|
1368
1351
|
* @private
|
|
1369
1352
|
*/
|
|
1370
1353
|
async createStreamInfoFromMediaTag_(tag) {
|
|
@@ -1418,9 +1401,6 @@ shaka.hls.HlsParser = class {
|
|
|
1418
1401
|
} else {
|
|
1419
1402
|
this.groupIdToStreamInfosMap_.set(groupId, [streamInfo]);
|
|
1420
1403
|
}
|
|
1421
|
-
if (streamInfo == null) {
|
|
1422
|
-
return null;
|
|
1423
|
-
}
|
|
1424
1404
|
|
|
1425
1405
|
// TODO: This check is necessary because of the possibility of multiple
|
|
1426
1406
|
// calls to createStreamInfoFromMediaTag_ before either has resolved.
|
|
@@ -1435,7 +1415,7 @@ shaka.hls.HlsParser = class {
|
|
|
1435
1415
|
* Parse EXT-X-MEDIA media tag into a Stream object.
|
|
1436
1416
|
*
|
|
1437
1417
|
* @param {shaka.hls.Tag} tag
|
|
1438
|
-
* @return {!Promise
|
|
1418
|
+
* @return {!Promise.<!shaka.hls.HlsParser.StreamInfo>}
|
|
1439
1419
|
* @private
|
|
1440
1420
|
*/
|
|
1441
1421
|
async createStreamInfoFromImageTag_(tag) {
|
|
@@ -1463,9 +1443,6 @@ shaka.hls.HlsParser = class {
|
|
|
1463
1443
|
verbatimImagePlaylistUri, codecs, type, language, /* primary= */ false,
|
|
1464
1444
|
name, /* channelsCount= */ null, /* closedCaptions= */ null,
|
|
1465
1445
|
characteristics, /* forced= */ false, /* spatialAudio= */ false);
|
|
1466
|
-
if (streamInfo == null) {
|
|
1467
|
-
return null;
|
|
1468
|
-
}
|
|
1469
1446
|
|
|
1470
1447
|
// TODO: This check is necessary because of the possibility of multiple
|
|
1471
1448
|
// calls to createStreamInfoFromImageTag_ before either has resolved.
|
|
@@ -1506,7 +1483,7 @@ shaka.hls.HlsParser = class {
|
|
|
1506
1483
|
* @param {!shaka.hls.Tag} tag
|
|
1507
1484
|
* @param {!Array.<string>} allCodecs
|
|
1508
1485
|
* @param {string} type
|
|
1509
|
-
* @return {!Promise
|
|
1486
|
+
* @return {!Promise.<!shaka.hls.HlsParser.StreamInfo>}
|
|
1510
1487
|
* @private
|
|
1511
1488
|
*/
|
|
1512
1489
|
async createStreamInfoFromVariantTag_(tag, allCodecs, type) {
|
|
@@ -1526,9 +1503,6 @@ shaka.hls.HlsParser = class {
|
|
|
1526
1503
|
/* name= */ null, /* channelcount= */ null, closedCaptions,
|
|
1527
1504
|
/* characteristics= */ null, /* forced= */ false,
|
|
1528
1505
|
/* spatialAudio= */ false);
|
|
1529
|
-
if (streamInfo == null) {
|
|
1530
|
-
return null;
|
|
1531
|
-
}
|
|
1532
1506
|
// TODO: This check is necessary because of the possibility of multiple
|
|
1533
1507
|
// calls to createStreamInfoFromVariantTag_ before either has resolved.
|
|
1534
1508
|
if (this.uriToStreamInfosMap_.has(verbatimMediaPlaylistUri)) {
|
|
@@ -1552,7 +1526,7 @@ shaka.hls.HlsParser = class {
|
|
|
1552
1526
|
* @param {?string} characteristics
|
|
1553
1527
|
* @param {boolean} forced
|
|
1554
1528
|
* @param {boolean} spatialAudio
|
|
1555
|
-
* @return {!Promise
|
|
1529
|
+
* @return {!Promise.<!shaka.hls.HlsParser.StreamInfo>}
|
|
1556
1530
|
* @private
|
|
1557
1531
|
*/
|
|
1558
1532
|
async createStreamInfo_(verbatimMediaPlaylistUri, codecs, type, language,
|
|
@@ -1592,7 +1566,7 @@ shaka.hls.HlsParser = class {
|
|
|
1592
1566
|
* @param {boolean} forced
|
|
1593
1567
|
* @param {boolean} spatialAudio
|
|
1594
1568
|
* @param {(string|undefined)} mimeType
|
|
1595
|
-
* @return {!Promise
|
|
1569
|
+
* @return {!Promise.<!shaka.hls.HlsParser.StreamInfo>}
|
|
1596
1570
|
* @private
|
|
1597
1571
|
*/
|
|
1598
1572
|
async convertParsedPlaylistIntoStreamInfo_(playlist, verbatimMediaPlaylistUri,
|
|
@@ -1635,6 +1609,8 @@ shaka.hls.HlsParser = class {
|
|
|
1635
1609
|
}
|
|
1636
1610
|
|
|
1637
1611
|
let encrypted = false;
|
|
1612
|
+
let aesEncrypted = false;
|
|
1613
|
+
|
|
1638
1614
|
/** @type {!Array.<shaka.extern.DrmInfo>}*/
|
|
1639
1615
|
const drmInfos = [];
|
|
1640
1616
|
const keyIds = new Set();
|
|
@@ -1647,7 +1623,7 @@ shaka.hls.HlsParser = class {
|
|
|
1647
1623
|
|
|
1648
1624
|
if (method == 'AES-128') {
|
|
1649
1625
|
// These keys are handled separately.
|
|
1650
|
-
|
|
1626
|
+
aesEncrypted = true;
|
|
1651
1627
|
} else {
|
|
1652
1628
|
const keyFormat = drmTag.getRequiredAttrValue('KEYFORMAT');
|
|
1653
1629
|
const drmParser =
|
|
@@ -1668,7 +1644,7 @@ shaka.hls.HlsParser = class {
|
|
|
1668
1644
|
}
|
|
1669
1645
|
}
|
|
1670
1646
|
|
|
1671
|
-
if (encrypted && !drmInfos.length && !
|
|
1647
|
+
if (encrypted && !drmInfos.length && !aesEncrypted) {
|
|
1672
1648
|
throw new shaka.util.Error(
|
|
1673
1649
|
shaka.util.Error.Severity.CRITICAL,
|
|
1674
1650
|
shaka.util.Error.Category.MANIFEST,
|
|
@@ -1683,19 +1659,8 @@ shaka.hls.HlsParser = class {
|
|
|
1683
1659
|
/** @type {!Map.<number, number>} */
|
|
1684
1660
|
const mediaSequenceToStartTime = new Map();
|
|
1685
1661
|
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
segments = this.createSegments_(verbatimMediaPlaylistUri, playlist, type,
|
|
1689
|
-
mimeType, mediaSequenceToStartTime, mediaVariables, codecs);
|
|
1690
|
-
} catch (error) {
|
|
1691
|
-
if (error.code == shaka.util.Error.Code.HLS_INTERNAL_SKIP_STREAM) {
|
|
1692
|
-
shaka.log.alwaysWarn('Skipping unsupported HLS stream',
|
|
1693
|
-
mimeType, verbatimMediaPlaylistUri);
|
|
1694
|
-
return null;
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
throw error;
|
|
1698
|
-
}
|
|
1662
|
+
const segments = this.createSegments_(verbatimMediaPlaylistUri, playlist,
|
|
1663
|
+
type, mimeType, mediaSequenceToStartTime, mediaVariables, codecs);
|
|
1699
1664
|
|
|
1700
1665
|
const lastEndTime = segments[segments.length - 1].endTime;
|
|
1701
1666
|
/** @type {!shaka.media.SegmentIndex} */
|
|
@@ -1757,6 +1722,7 @@ shaka.hls.HlsParser = class {
|
|
|
1757
1722
|
maxTimestamp: lastEndTime,
|
|
1758
1723
|
mediaSequenceToStartTime,
|
|
1759
1724
|
canSkipSegments,
|
|
1725
|
+
hasEndList: false,
|
|
1760
1726
|
};
|
|
1761
1727
|
}
|
|
1762
1728
|
|
|
@@ -2607,8 +2573,11 @@ shaka.hls.HlsParser = class {
|
|
|
2607
2573
|
try {
|
|
2608
2574
|
await this.update();
|
|
2609
2575
|
|
|
2610
|
-
|
|
2611
|
-
this.
|
|
2576
|
+
// This may have converted to VOD, in which case we stop updating.
|
|
2577
|
+
if (this.isLive_()) {
|
|
2578
|
+
const delay = this.updatePlaylistDelay_;
|
|
2579
|
+
this.updatePlaylistTimer_.tickAfter(/* seconds= */ delay);
|
|
2580
|
+
}
|
|
2612
2581
|
} catch (error) {
|
|
2613
2582
|
// Detect a call to stop() during this.update()
|
|
2614
2583
|
if (!this.playerInterface_) {
|
|
@@ -2799,7 +2768,8 @@ shaka.hls.HlsParser = class {
|
|
|
2799
2768
|
* absoluteMediaPlaylistUri: string,
|
|
2800
2769
|
* maxTimestamp: number,
|
|
2801
2770
|
* mediaSequenceToStartTime: !Map.<number, number>,
|
|
2802
|
-
* canSkipSegments: boolean
|
|
2771
|
+
* canSkipSegments: boolean,
|
|
2772
|
+
* hasEndList: boolean
|
|
2803
2773
|
* }}
|
|
2804
2774
|
*
|
|
2805
2775
|
* @description
|
|
@@ -2822,6 +2792,8 @@ shaka.hls.HlsParser = class {
|
|
|
2822
2792
|
* @property {boolean} canSkipSegments
|
|
2823
2793
|
* True if the server supports delta playlist updates, and we can send a
|
|
2824
2794
|
* request for a playlist that can skip older media segments.
|
|
2795
|
+
* @property {boolean} hasEndList
|
|
2796
|
+
* True if the stream has an EXT-X-ENDLIST tag.
|
|
2825
2797
|
*/
|
|
2826
2798
|
shaka.hls.HlsParser.StreamInfo;
|
|
2827
2799
|
|
package/lib/media/drm_engine.js
CHANGED
|
@@ -18,9 +18,11 @@ goog.require('shaka.util.FakeEvent');
|
|
|
18
18
|
goog.require('shaka.util.IDestroyable');
|
|
19
19
|
goog.require('shaka.util.Iterables');
|
|
20
20
|
goog.require('shaka.util.Lazy');
|
|
21
|
+
goog.require('shaka.util.ManifestParserUtils');
|
|
21
22
|
goog.require('shaka.util.MapUtils');
|
|
22
23
|
goog.require('shaka.util.MimeUtils');
|
|
23
24
|
goog.require('shaka.util.Platform');
|
|
25
|
+
goog.require('shaka.util.Pssh');
|
|
24
26
|
goog.require('shaka.util.PublicPromise');
|
|
25
27
|
goog.require('shaka.util.StreamUtils');
|
|
26
28
|
goog.require('shaka.util.StringUtils');
|
|
@@ -497,7 +499,11 @@ shaka.media.DrmEngine = class {
|
|
|
497
499
|
|
|
498
500
|
// Explicit init data for any one stream or an offline session is
|
|
499
501
|
// sufficient to suppress 'encrypted' events for all streams.
|
|
500
|
-
|
|
502
|
+
// Also suppress 'encrypted' events when parsing in-band ppsh
|
|
503
|
+
// from media segments because that serves the same purpose as the
|
|
504
|
+
// 'encrypted' events.
|
|
505
|
+
if (!manifestInitData && !this.offlineSessionIds_.length &&
|
|
506
|
+
!this.config_.parseInbandPsshEnabled) {
|
|
501
507
|
this.eventManager_.listen(
|
|
502
508
|
this.video_, 'encrypted', (e) => this.onEncryptedEvent_(e));
|
|
503
509
|
}
|
|
@@ -668,6 +674,14 @@ shaka.media.DrmEngine = class {
|
|
|
668
674
|
}
|
|
669
675
|
}
|
|
670
676
|
|
|
677
|
+
// If there are pre-existing sessions that have all been loaded
|
|
678
|
+
// then reset the allSessionsLoaded_ promise, which can now be
|
|
679
|
+
// used to wait for new sesssions to be loaded
|
|
680
|
+
if (this.activeSessions_.size > 0 && this.areAllSessionsLoaded_()) {
|
|
681
|
+
this.allSessionsLoaded_.resolve();
|
|
682
|
+
this.allSessionsLoaded_ = new shaka.util.PublicPromise();
|
|
683
|
+
this.allSessionsLoaded_.catch(() => {});
|
|
684
|
+
}
|
|
671
685
|
this.createSession(initDataType, initData,
|
|
672
686
|
this.currentDrmInfo_.sessionType);
|
|
673
687
|
}
|
|
@@ -2327,6 +2341,43 @@ shaka.media.DrmEngine = class {
|
|
|
2327
2341
|
}
|
|
2328
2342
|
}
|
|
2329
2343
|
}
|
|
2344
|
+
|
|
2345
|
+
/**
|
|
2346
|
+
* Parse pssh from a media segment and announce new initData
|
|
2347
|
+
*
|
|
2348
|
+
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
|
|
2349
|
+
* @param {!BufferSource} mediaSegment
|
|
2350
|
+
* @return {!Promise<void>}
|
|
2351
|
+
*/
|
|
2352
|
+
parseInbandPssh(contentType, mediaSegment) {
|
|
2353
|
+
if (!this.config_.parseInbandPsshEnabled) {
|
|
2354
|
+
return Promise.resolve();
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
2358
|
+
if (![ContentType.AUDIO, ContentType.VIDEO].includes(contentType)) {
|
|
2359
|
+
return Promise.resolve();
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
const pssh = new shaka.util.Pssh(
|
|
2363
|
+
shaka.util.BufferUtils.toUint8(mediaSegment));
|
|
2364
|
+
|
|
2365
|
+
let totalLength = 0;
|
|
2366
|
+
for (const data of pssh.data) {
|
|
2367
|
+
totalLength += data.length;
|
|
2368
|
+
}
|
|
2369
|
+
if (totalLength == 0) {
|
|
2370
|
+
return Promise.resolve();
|
|
2371
|
+
}
|
|
2372
|
+
const combinedData = new Uint8Array(totalLength);
|
|
2373
|
+
let pos = 0;
|
|
2374
|
+
for (const data of pssh.data) {
|
|
2375
|
+
combinedData.set(data, pos);
|
|
2376
|
+
pos += data.length;
|
|
2377
|
+
}
|
|
2378
|
+
this.newInitData('cenc', combinedData);
|
|
2379
|
+
return this.allSessionsLoaded_;
|
|
2380
|
+
}
|
|
2330
2381
|
};
|
|
2331
2382
|
|
|
2332
2383
|
|