shaka-player 4.9.13 → 4.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/dist/controls.css +2 -2
- package/dist/controls.css.map +1 -1
- package/dist/deps.js +24 -19
- package/dist/locales.js +46 -35
- package/dist/shaka-player.compiled.d.ts +291 -12
- package/dist/shaka-player.compiled.debug.d.ts +291 -12
- package/dist/shaka-player.compiled.debug.externs.js +443 -6
- package/dist/shaka-player.compiled.debug.js +651 -556
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +443 -6
- package/dist/shaka-player.compiled.js +1325 -1270
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +306 -13
- package/dist/shaka-player.ui.debug.d.ts +306 -13
- package/dist/shaka-player.ui.debug.externs.js +477 -18
- package/dist/shaka-player.ui.debug.js +799 -681
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +477 -18
- package/dist/shaka-player.ui.js +1600 -1533
- package/dist/shaka-player.ui.map +4 -4
- package/externs/shaka/ads.js +21 -1
- package/externs/shaka/manifest.js +20 -1
- package/externs/shaka/manifest_parser.js +18 -1
- package/externs/shaka/player.js +164 -50
- package/lib/ads/ad_manager.js +225 -10
- package/lib/ads/ad_utils.js +323 -0
- package/lib/ads/ads_stats.js +24 -0
- package/lib/ads/client_side_ad_manager.js +42 -34
- package/lib/ads/interstitial_ad.js +292 -0
- package/lib/ads/interstitial_ad_manager.js +772 -0
- package/lib/ads/media_tailor_ad_manager.js +19 -22
- package/lib/ads/server_side_ad_manager.js +12 -15
- package/lib/cast/cast_utils.js +1 -0
- package/lib/cea/cea_utils.js +12 -0
- package/lib/cea/mp4_cea_parser.js +17 -0
- package/lib/dash/dash_parser.js +81 -28
- package/lib/dash/segment_base.js +3 -0
- package/lib/hls/hls_parser.js +429 -121
- package/lib/hls/manifest_text_parser.js +1 -0
- package/lib/media/adaptation_set_criteria.js +31 -1
- package/lib/media/drm_engine.js +16 -2
- package/lib/media/manifest_filterer.js +7 -5
- package/lib/media/preload_manager.js +8 -10
- package/lib/media/presentation_timeline.js +37 -5
- package/lib/media/quality_observer.js +68 -2
- package/lib/media/segment_reference.js +1 -1
- package/lib/media/segment_utils.js +33 -1
- package/lib/media/streaming_engine.js +120 -43
- package/lib/mss/mss_parser.js +13 -2
- package/lib/net/networking_engine.js +1 -0
- package/lib/offline/manifest_converter.js +3 -0
- package/lib/offline/offline_manifest_parser.js +5 -0
- package/lib/offline/storage.js +2 -0
- package/lib/player.js +498 -165
- package/lib/util/fake_event.js +1 -0
- package/lib/util/manifest_parser_utils.js +9 -2
- package/lib/util/mp4_box_parsers.js +32 -0
- package/lib/util/player_configuration.js +21 -9
- package/lib/util/stats.js +43 -0
- package/package.json +1 -1
- package/shaka-player.uncompiled.js +2 -0
- package/ui/ad_counter.js +3 -3
- package/ui/ad_position.js +3 -3
- package/ui/ad_statistics_button.js +237 -0
- package/ui/audio_language_selection.js +8 -4
- package/ui/controls.js +6 -3
- package/ui/element.js +3 -3
- package/ui/enums.js +1 -0
- package/ui/externs/ui.js +3 -0
- package/ui/language_utils.js +48 -3
- package/ui/less/containers.less +32 -0
- package/ui/less/thumbnails.less +13 -0
- package/ui/locales/en.json +2 -0
- package/ui/locales/es-419.json +2 -0
- package/ui/locales/es.json +3 -0
- package/ui/locales/fr.json +1 -0
- package/ui/locales/pl.json +1 -0
- package/ui/locales/pt-BR.json +1 -0
- package/ui/locales/pt-PT.json +1 -0
- package/ui/locales/source.json +8 -0
- package/ui/mute_button.js +4 -4
- package/ui/overflow_menu.js +3 -3
- package/ui/play_button.js +5 -7
- package/ui/presentation_time.js +3 -3
- package/ui/save_video_frame_button.js +196 -0
- package/ui/seek_bar.js +75 -20
- package/ui/skip_ad_button.js +4 -4
- package/ui/statistics_button.js +14 -2
- package/ui/ui.js +15 -0
- package/ui/volume_bar.js +4 -4
package/lib/hls/hls_parser.js
CHANGED
|
@@ -19,6 +19,7 @@ goog.require('shaka.log');
|
|
|
19
19
|
goog.require('shaka.media.InitSegmentReference');
|
|
20
20
|
goog.require('shaka.media.ManifestParser');
|
|
21
21
|
goog.require('shaka.media.PresentationTimeline');
|
|
22
|
+
goog.require('shaka.media.QualityObserver');
|
|
22
23
|
goog.require('shaka.media.SegmentIndex');
|
|
23
24
|
goog.require('shaka.media.SegmentReference');
|
|
24
25
|
goog.require('shaka.net.DataUriPlugin');
|
|
@@ -28,6 +29,7 @@ goog.require('shaka.util.BufferUtils');
|
|
|
28
29
|
goog.require('shaka.util.DrmUtils');
|
|
29
30
|
goog.require('shaka.util.ContentSteeringManager');
|
|
30
31
|
goog.require('shaka.util.Error');
|
|
32
|
+
goog.require('shaka.util.EventManager');
|
|
31
33
|
goog.require('shaka.util.FakeEvent');
|
|
32
34
|
goog.require('shaka.util.LanguageUtils');
|
|
33
35
|
goog.require('shaka.util.ManifestParserUtils');
|
|
@@ -37,6 +39,7 @@ goog.require('shaka.util.OperationManager');
|
|
|
37
39
|
goog.require('shaka.util.Pssh');
|
|
38
40
|
goog.require('shaka.media.SegmentUtils');
|
|
39
41
|
goog.require('shaka.util.Timer');
|
|
42
|
+
goog.require('shaka.util.TsParser');
|
|
40
43
|
goog.require('shaka.util.TXml');
|
|
41
44
|
goog.require('shaka.util.Platform');
|
|
42
45
|
goog.require('shaka.util.Uint8ArrayUtils');
|
|
@@ -172,6 +175,14 @@ shaka.hls.HlsParser = class {
|
|
|
172
175
|
* @private {shaka.util.Timer}
|
|
173
176
|
*/
|
|
174
177
|
this.updatePlaylistTimer_ = new shaka.util.Timer(() => {
|
|
178
|
+
if (this.mediaElement_ && !this.config_.continueLoadingWhenPaused) {
|
|
179
|
+
this.eventManager_.unlisten(this.mediaElement_, 'timeupdate');
|
|
180
|
+
if (this.mediaElement_.paused) {
|
|
181
|
+
this.eventManager_.listenOnce(
|
|
182
|
+
this.mediaElement_, 'timeupdate', () => this.onUpdate_());
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
175
186
|
this.onUpdate_();
|
|
176
187
|
});
|
|
177
188
|
|
|
@@ -247,6 +258,15 @@ shaka.hls.HlsParser = class {
|
|
|
247
258
|
|
|
248
259
|
/** @private {boolean} */
|
|
249
260
|
this.needsClosedCaptionsDetection_ = true;
|
|
261
|
+
|
|
262
|
+
/** @private {Set.<string>} */
|
|
263
|
+
this.dateRangeIdsEmitted_ = new Set();
|
|
264
|
+
|
|
265
|
+
/** @private {shaka.util.EventManager} */
|
|
266
|
+
this.eventManager_ = new shaka.util.EventManager();
|
|
267
|
+
|
|
268
|
+
/** @private {HTMLMediaElement} */
|
|
269
|
+
this.mediaElement_ = null;
|
|
250
270
|
}
|
|
251
271
|
|
|
252
272
|
|
|
@@ -316,11 +336,17 @@ shaka.hls.HlsParser = class {
|
|
|
316
336
|
this.aesKeyMap_.clear();
|
|
317
337
|
this.identityKeyMap_.clear();
|
|
318
338
|
this.identityKidMap_.clear();
|
|
339
|
+
this.dateRangeIdsEmitted_.clear();
|
|
319
340
|
|
|
320
341
|
if (this.contentSteeringManager_) {
|
|
321
342
|
this.contentSteeringManager_.destroy();
|
|
322
343
|
}
|
|
323
344
|
|
|
345
|
+
if (this.eventManager_) {
|
|
346
|
+
this.eventManager_.release();
|
|
347
|
+
this.eventManager_ = null;
|
|
348
|
+
}
|
|
349
|
+
|
|
324
350
|
return Promise.all(pending);
|
|
325
351
|
}
|
|
326
352
|
|
|
@@ -339,6 +365,7 @@ shaka.hls.HlsParser = class {
|
|
|
339
365
|
|
|
340
366
|
// This is necessary to calculate correctly the update time.
|
|
341
367
|
this.lastTargetDuration_ = Infinity;
|
|
368
|
+
this.manifest_.gapCount = 0;
|
|
342
369
|
|
|
343
370
|
// Only update active streams.
|
|
344
371
|
const activeStreamInfos = streamInfos.filter((s) => s.stream.segmentIndex);
|
|
@@ -368,6 +395,15 @@ shaka.hls.HlsParser = class {
|
|
|
368
395
|
if (stillLive) {
|
|
369
396
|
this.determineDuration_();
|
|
370
397
|
}
|
|
398
|
+
// Check if any playlist does not have the first reference (due to a
|
|
399
|
+
// problem in the live encoder for example), and disable the stream if
|
|
400
|
+
// necessary.
|
|
401
|
+
for (const streamInfo of activeStreamInfos) {
|
|
402
|
+
if (streamInfo.stream.segmentIndex &&
|
|
403
|
+
!streamInfo.stream.segmentIndex.earliestReference()) {
|
|
404
|
+
this.playerInterface_.disableStream(streamInfo.stream);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
371
407
|
}
|
|
372
408
|
|
|
373
409
|
/**
|
|
@@ -418,8 +454,16 @@ shaka.hls.HlsParser = class {
|
|
|
418
454
|
}
|
|
419
455
|
manifestUris.push(uriObj.toString());
|
|
420
456
|
}
|
|
421
|
-
|
|
422
|
-
|
|
457
|
+
let response;
|
|
458
|
+
try {
|
|
459
|
+
response =
|
|
460
|
+
await this.requestManifest_(manifestUris, /* isPlaylist= */ true);
|
|
461
|
+
} catch (e) {
|
|
462
|
+
if (this.playerInterface_) {
|
|
463
|
+
this.playerInterface_.disableStream(streamInfo.stream);
|
|
464
|
+
}
|
|
465
|
+
throw e;
|
|
466
|
+
}
|
|
423
467
|
if (!streamInfo.stream.segmentIndex) {
|
|
424
468
|
// The stream was closed since the update was first requested.
|
|
425
469
|
return;
|
|
@@ -472,6 +516,14 @@ shaka.hls.HlsParser = class {
|
|
|
472
516
|
stream.bandwidth = bandwidth;
|
|
473
517
|
}
|
|
474
518
|
|
|
519
|
+
const qualityInfo =
|
|
520
|
+
shaka.media.QualityObserver.createQualityInfo(stream);
|
|
521
|
+
for (const segment of segments) {
|
|
522
|
+
if (segment.initSegmentReference) {
|
|
523
|
+
segment.initSegmentReference.mediaQuality = qualityInfo;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
475
527
|
stream.segmentIndex.mergeAndEvict(
|
|
476
528
|
segments, this.presentationTimeline_.getSegmentAvailabilityStart());
|
|
477
529
|
if (segments.length) {
|
|
@@ -490,14 +542,14 @@ shaka.hls.HlsParser = class {
|
|
|
490
542
|
stream.segmentIndex.evict(playlistStartTime);
|
|
491
543
|
}
|
|
492
544
|
const oldSegment = stream.segmentIndex.earliestReference();
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
streamInfo.minTimestamp = oldSegment.startTime;
|
|
545
|
+
if (oldSegment) {
|
|
546
|
+
streamInfo.minTimestamp = oldSegment.startTime;
|
|
496
547
|
|
|
497
|
-
|
|
498
|
-
|
|
548
|
+
const newestSegment = segments[segments.length - 1];
|
|
549
|
+
goog.asserts.assert(newestSegment, 'Should have segments!');
|
|
499
550
|
|
|
500
|
-
|
|
551
|
+
streamInfo.maxTimestamp = newestSegment.endTime;
|
|
552
|
+
}
|
|
501
553
|
|
|
502
554
|
// Once the last segment has been added to the playlist,
|
|
503
555
|
// #EXT-X-ENDLIST tag will be appended.
|
|
@@ -512,6 +564,9 @@ shaka.hls.HlsParser = class {
|
|
|
512
564
|
}
|
|
513
565
|
|
|
514
566
|
this.determineLastTargetDuration_(playlist);
|
|
567
|
+
|
|
568
|
+
this.processDateRangeTags_(
|
|
569
|
+
playlist.tags, stream.type, mediaVariables, streamInfo.getUris);
|
|
515
570
|
}
|
|
516
571
|
|
|
517
572
|
|
|
@@ -541,6 +596,14 @@ shaka.hls.HlsParser = class {
|
|
|
541
596
|
}
|
|
542
597
|
}
|
|
543
598
|
|
|
599
|
+
/**
|
|
600
|
+
* @override
|
|
601
|
+
* @exportInterface
|
|
602
|
+
*/
|
|
603
|
+
setMediaElement(mediaElement) {
|
|
604
|
+
this.mediaElement_ = mediaElement;
|
|
605
|
+
}
|
|
606
|
+
|
|
544
607
|
/**
|
|
545
608
|
* Align the streams by sequence number by dropping early segments. Then
|
|
546
609
|
* offset the streams to begin at presentation time 0.
|
|
@@ -728,6 +791,7 @@ shaka.hls.HlsParser = class {
|
|
|
728
791
|
*/
|
|
729
792
|
async parseManifest_(data, uri) {
|
|
730
793
|
const Utils = shaka.hls.Utils;
|
|
794
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
731
795
|
|
|
732
796
|
goog.asserts.assert(this.masterPlaylistUri_,
|
|
733
797
|
'Master playlist URI must be set before calling parseManifest_!');
|
|
@@ -760,6 +824,12 @@ shaka.hls.HlsParser = class {
|
|
|
760
824
|
return [uri];
|
|
761
825
|
};
|
|
762
826
|
|
|
827
|
+
/** @type {?string} */
|
|
828
|
+
let mediaPlaylistType = null;
|
|
829
|
+
|
|
830
|
+
/** @type {!Map.<string, string>} */
|
|
831
|
+
let mediaVariables = new Map();
|
|
832
|
+
|
|
763
833
|
// Parsing a media playlist results in a single-variant stream.
|
|
764
834
|
if (playlist.type == shaka.hls.PlaylistType.MEDIA) {
|
|
765
835
|
this.needsClosedCaptionsDetection_ = false;
|
|
@@ -768,29 +838,19 @@ shaka.hls.HlsParser = class {
|
|
|
768
838
|
const variablesTags = shaka.hls.Utils.filterTagsByName(playlist.tags,
|
|
769
839
|
'EXT-X-DEFINE');
|
|
770
840
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
//
|
|
775
|
-
//
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
const
|
|
780
|
-
const
|
|
781
|
-
const
|
|
782
|
-
const
|
|
783
|
-
const
|
|
784
|
-
const channelsCount = basicInfo.channelCount;
|
|
785
|
-
const sampleRate = basicInfo.sampleRate;
|
|
786
|
-
const closedCaptions = basicInfo.closedCaptions;
|
|
787
|
-
const videoRange = basicInfo.videoRange;
|
|
788
|
-
const colorGamut = basicInfo.colorGamut;
|
|
789
|
-
|
|
790
|
-
// Some values we cannot figure out, and aren't important enough to ask
|
|
791
|
-
// the user to provide through config values. A lot of these are only
|
|
792
|
-
// relevant to ABR, which isn't necessary if there's only one variant.
|
|
793
|
-
// So these unknowns should be set to false or null, largely.
|
|
841
|
+
mediaVariables = this.parseMediaVariables_(
|
|
842
|
+
variablesTags, this.masterPlaylistUri_);
|
|
843
|
+
|
|
844
|
+
// By default we assume it is video, but in a later step the correct type
|
|
845
|
+
// is obtained.
|
|
846
|
+
mediaPlaylistType = ContentType.VIDEO;
|
|
847
|
+
|
|
848
|
+
// These values can be obtained later so these default values are good.
|
|
849
|
+
const codecs = '';
|
|
850
|
+
const languageValue = '';
|
|
851
|
+
const channelsCount = null;
|
|
852
|
+
const sampleRate = null;
|
|
853
|
+
const closedCaptions = new Map();
|
|
794
854
|
const spatialAudio = false;
|
|
795
855
|
const characteristics = null;
|
|
796
856
|
const forced = false; // Only relevant for text.
|
|
@@ -800,24 +860,32 @@ shaka.hls.HlsParser = class {
|
|
|
800
860
|
// Make the stream info, with those values.
|
|
801
861
|
const streamInfo = await this.convertParsedPlaylistIntoStreamInfo_(
|
|
802
862
|
this.globalId_++, mediaVariables, playlist, getUris, uri, codecs,
|
|
803
|
-
|
|
804
|
-
characteristics, forced, sampleRate, spatialAudio
|
|
863
|
+
mediaPlaylistType, languageValue, primary, name, channelsCount,
|
|
864
|
+
closedCaptions, characteristics, forced, sampleRate, spatialAudio);
|
|
805
865
|
this.uriToStreamInfosMap_.set(uri, streamInfo);
|
|
806
866
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
867
|
+
|
|
868
|
+
if (streamInfo.stream) {
|
|
869
|
+
const qualityInfo =
|
|
870
|
+
shaka.media.QualityObserver.createQualityInfo(streamInfo.stream);
|
|
871
|
+
streamInfo.stream.segmentIndex.forEachTopLevelReference(
|
|
872
|
+
(reference) => {
|
|
873
|
+
if (reference.initSegmentReference) {
|
|
874
|
+
reference.initSegmentReference.mediaQuality = qualityInfo;
|
|
875
|
+
}
|
|
876
|
+
});
|
|
811
877
|
}
|
|
812
878
|
|
|
879
|
+
mediaPlaylistType = streamInfo.stream.type;
|
|
880
|
+
|
|
813
881
|
// Wrap the stream from that stream info with a variant.
|
|
814
882
|
variants.push({
|
|
815
883
|
id: 0,
|
|
816
884
|
language: this.getLanguage_(languageValue),
|
|
817
885
|
disabledUntilTime: 0,
|
|
818
886
|
primary: true,
|
|
819
|
-
audio:
|
|
820
|
-
video:
|
|
887
|
+
audio: mediaPlaylistType == 'audio' ? streamInfo.stream : null,
|
|
888
|
+
video: mediaPlaylistType == 'video' ? streamInfo.stream : null,
|
|
821
889
|
bandwidth: streamInfo.stream.bandwidth || 0,
|
|
822
890
|
allowedByApplication: true,
|
|
823
891
|
allowedByKeySystem: true,
|
|
@@ -880,6 +948,11 @@ shaka.hls.HlsParser = class {
|
|
|
880
948
|
const streamInfos = Array.from(this.uriToStreamInfosMap_.values());
|
|
881
949
|
this.finalizeStreams_(streamInfos);
|
|
882
950
|
this.determineDuration_();
|
|
951
|
+
|
|
952
|
+
goog.asserts.assert(mediaPlaylistType,
|
|
953
|
+
'mediaPlaylistType should be non-null');
|
|
954
|
+
this.processDateRangeTags_(
|
|
955
|
+
playlist.tags, mediaPlaylistType, mediaVariables, getUris);
|
|
883
956
|
}
|
|
884
957
|
|
|
885
958
|
this.manifest_ = {
|
|
@@ -895,6 +968,9 @@ shaka.hls.HlsParser = class {
|
|
|
895
968
|
type: shaka.media.ManifestParser.HLS,
|
|
896
969
|
serviceDescription: null,
|
|
897
970
|
nextUrl: null,
|
|
971
|
+
periodCount: 1,
|
|
972
|
+
gapCount: 0,
|
|
973
|
+
isLowLatency: false,
|
|
898
974
|
};
|
|
899
975
|
|
|
900
976
|
// If there is no 'CODECS' attribute in the manifest and codec guessing is
|
|
@@ -931,26 +1007,21 @@ shaka.hls.HlsParser = class {
|
|
|
931
1007
|
}
|
|
932
1008
|
|
|
933
1009
|
/**
|
|
934
|
-
* @param {shaka.
|
|
935
|
-
* @param {function():!Array.<string>} getUris
|
|
936
|
-
* @param {?Map.<string, string>=} variables
|
|
1010
|
+
* @param {!Array.<!shaka.media.SegmentReference>} segments
|
|
937
1011
|
* @return {!Promise.<shaka.media.SegmentUtils.BasicInfo>}
|
|
938
1012
|
* @private
|
|
939
1013
|
*/
|
|
940
|
-
async
|
|
1014
|
+
async getBasicInfoFromSegments_(segments) {
|
|
941
1015
|
const HlsParser = shaka.hls.HlsParser;
|
|
942
1016
|
const defaultBasicInfo = shaka.media.SegmentUtils.getBasicInfoFromMimeType(
|
|
943
1017
|
this.config_.hls.mediaPlaylistFullMimeType);
|
|
944
|
-
if (!
|
|
1018
|
+
if (!segments.length) {
|
|
945
1019
|
return defaultBasicInfo;
|
|
946
1020
|
}
|
|
947
|
-
const
|
|
948
|
-
const
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
variables);
|
|
952
|
-
const firstSegmentUri = firstSegmentUris[0];
|
|
953
|
-
const parsedUri = new goog.Uri(firstSegmentUri);
|
|
1021
|
+
const segment = this.getAvailableSegment_(segments);
|
|
1022
|
+
const segmentUris = segment.getUris();
|
|
1023
|
+
const segmentUri = segmentUris[0];
|
|
1024
|
+
const parsedUri = new goog.Uri(segmentUri);
|
|
954
1025
|
const extension = parsedUri.getPath().split('.').pop();
|
|
955
1026
|
const rawMimeType = HlsParser.RAW_FORMATS_TO_MIME_TYPES_[extension];
|
|
956
1027
|
if (rawMimeType) {
|
|
@@ -961,33 +1032,28 @@ shaka.hls.HlsParser = class {
|
|
|
961
1032
|
const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
|
|
962
1033
|
|
|
963
1034
|
let initData = null;
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
this.mapTagToInitSegmentRefMap_.clear();
|
|
1035
|
+
let initMimeType = null;
|
|
1036
|
+
const initSegmentRef = segment.initSegmentReference;
|
|
967
1037
|
if (initSegmentRef) {
|
|
968
1038
|
const initSegmentRequest = shaka.util.Networking.createSegmentRequest(
|
|
969
|
-
initSegmentRef.getUris(),
|
|
970
|
-
initSegmentRef.
|
|
971
|
-
initSegmentRef.getEndByte(),
|
|
972
|
-
this.config_.retryParameters);
|
|
1039
|
+
initSegmentRef.getUris(), initSegmentRef.getStartByte(),
|
|
1040
|
+
initSegmentRef.getEndByte(), this.config_.retryParameters);
|
|
973
1041
|
const initType =
|
|
974
1042
|
shaka.net.NetworkingEngine.AdvancedRequestType.INIT_SEGMENT;
|
|
975
1043
|
const initResponse = await this.makeNetworkRequest_(
|
|
976
1044
|
initSegmentRequest, requestType, {type: initType});
|
|
977
1045
|
initData = initResponse.data;
|
|
978
|
-
}
|
|
979
1046
|
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
[startByte, endByte] = this.parseByteRange_(
|
|
986
|
-
/* previousReference= */ null, byterangeTag.value);
|
|
1047
|
+
initMimeType = initResponse.headers['content-type'];
|
|
1048
|
+
if (initMimeType) {
|
|
1049
|
+
// Split the MIME type in case the server sent additional parameters.
|
|
1050
|
+
initMimeType = initMimeType.split(';')[0].toLowerCase();
|
|
1051
|
+
}
|
|
987
1052
|
}
|
|
988
1053
|
|
|
989
1054
|
const segmentRequest = shaka.util.Networking.createSegmentRequest(
|
|
990
|
-
|
|
1055
|
+
segment.getUris(), segment.getStartByte(), segment.getEndByte(),
|
|
1056
|
+
this.config_.retryParameters);
|
|
991
1057
|
const type = shaka.net.NetworkingEngine.AdvancedRequestType.MEDIA_SEGMENT;
|
|
992
1058
|
const response = await this.makeNetworkRequest_(
|
|
993
1059
|
segmentRequest, requestType, {type});
|
|
@@ -998,23 +1064,49 @@ shaka.hls.HlsParser = class {
|
|
|
998
1064
|
contentMimeType = contentMimeType.split(';')[0].toLowerCase();
|
|
999
1065
|
}
|
|
1000
1066
|
|
|
1001
|
-
|
|
1067
|
+
const validMp4Extensions = [
|
|
1068
|
+
'mp4',
|
|
1069
|
+
'mp4a',
|
|
1070
|
+
'm4s',
|
|
1071
|
+
'm4i',
|
|
1072
|
+
'm4a',
|
|
1073
|
+
'm4f',
|
|
1074
|
+
'cmfa',
|
|
1075
|
+
'mp4v',
|
|
1076
|
+
'm4v',
|
|
1077
|
+
'cmfv',
|
|
1078
|
+
'fmp4',
|
|
1079
|
+
];
|
|
1080
|
+
const validMp4MimeType = [
|
|
1081
|
+
'audio/mp4',
|
|
1082
|
+
'video/mp4',
|
|
1083
|
+
'video/iso.segment',
|
|
1084
|
+
];
|
|
1085
|
+
|
|
1086
|
+
if (shaka.util.TsParser.probe(
|
|
1087
|
+
shaka.util.BufferUtils.toUint8(response.data))) {
|
|
1002
1088
|
const basicInfo =
|
|
1003
1089
|
shaka.media.SegmentUtils.getBasicInfoFromTs(response.data);
|
|
1004
1090
|
if (basicInfo) {
|
|
1005
1091
|
return basicInfo;
|
|
1006
1092
|
}
|
|
1007
|
-
} else if (extension
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
contentMimeType == 'audio/mp4' ||
|
|
1011
|
-
contentMimeType == 'video/iso.segment') {
|
|
1093
|
+
} else if (validMp4Extensions.includes(extension) ||
|
|
1094
|
+
validMp4MimeType.includes(contentMimeType) ||
|
|
1095
|
+
(initMimeType && validMp4MimeType.includes(initMimeType))) {
|
|
1012
1096
|
const basicInfo = shaka.media.SegmentUtils.getBasicInfoFromMp4(
|
|
1013
1097
|
initData, response.data);
|
|
1014
1098
|
if (basicInfo) {
|
|
1015
1099
|
return basicInfo;
|
|
1016
1100
|
}
|
|
1017
1101
|
}
|
|
1102
|
+
if (contentMimeType) {
|
|
1103
|
+
return shaka.media.SegmentUtils.getBasicInfoFromMimeType(
|
|
1104
|
+
contentMimeType);
|
|
1105
|
+
}
|
|
1106
|
+
if (initMimeType) {
|
|
1107
|
+
return shaka.media.SegmentUtils.getBasicInfoFromMimeType(
|
|
1108
|
+
initMimeType);
|
|
1109
|
+
}
|
|
1018
1110
|
return defaultBasicInfo;
|
|
1019
1111
|
}
|
|
1020
1112
|
|
|
@@ -2285,8 +2377,19 @@ shaka.hls.HlsParser = class {
|
|
|
2285
2377
|
|
|
2286
2378
|
const uris = streamInfo.getUris();
|
|
2287
2379
|
// Download the actual manifest.
|
|
2288
|
-
|
|
2289
|
-
|
|
2380
|
+
let response;
|
|
2381
|
+
try {
|
|
2382
|
+
response = await this.requestManifest_(
|
|
2383
|
+
streamInfo.getUris(), /* isPlaylist= */ true);
|
|
2384
|
+
} catch (e) {
|
|
2385
|
+
stream.closeSegmentIndex();
|
|
2386
|
+
const handled = this.playerInterface_.disableStream(stream);
|
|
2387
|
+
if (handled) {
|
|
2388
|
+
return;
|
|
2389
|
+
} else {
|
|
2390
|
+
throw e;
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2290
2393
|
if (abortSignal.aborted) {
|
|
2291
2394
|
return;
|
|
2292
2395
|
}
|
|
@@ -2316,9 +2419,9 @@ shaka.hls.HlsParser = class {
|
|
|
2316
2419
|
const mediaVariables =
|
|
2317
2420
|
this.parseMediaVariables_(variablesTags, responseUri);
|
|
2318
2421
|
|
|
2319
|
-
|
|
2422
|
+
const mimeType = undefined;
|
|
2320
2423
|
|
|
2321
|
-
let
|
|
2424
|
+
let requestBasicInfo = false;
|
|
2322
2425
|
|
|
2323
2426
|
// If no codec info was provided in the manifest and codec guessing is
|
|
2324
2427
|
// disabled we try to get necessary info from the media data.
|
|
@@ -2326,40 +2429,21 @@ shaka.hls.HlsParser = class {
|
|
|
2326
2429
|
this.config_.hls.disableCodecGuessing) ||
|
|
2327
2430
|
(this.needsClosedCaptionsDetection_ && type == ContentType.VIDEO &&
|
|
2328
2431
|
!this.config_.hls.disableClosedCaptionsDetection)) {
|
|
2329
|
-
|
|
2330
|
-
if (canRequestBasicInfo) {
|
|
2331
|
-
const segment = playlist.segments[0];
|
|
2332
|
-
if (shaka.hls.Utils.getFirstTagWithName(segment.tags, 'EXT-X-GAP')) {
|
|
2333
|
-
canRequestBasicInfo = false;
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
if (canRequestBasicInfo) {
|
|
2432
|
+
if (playlist.segments.length > 0) {
|
|
2337
2433
|
this.needsClosedCaptionsDetection_ = false;
|
|
2338
|
-
|
|
2339
|
-
playlist, getUris, mediaVariables);
|
|
2340
|
-
|
|
2341
|
-
goog.asserts.assert(
|
|
2342
|
-
type === basicInfo.type, 'Media types should match!');
|
|
2343
|
-
|
|
2344
|
-
if (basicInfo.closedCaptions.size && (!closedCaptions ||
|
|
2345
|
-
closedCaptions.size != basicInfo.closedCaptions.size)) {
|
|
2346
|
-
closedCaptions = basicInfo.closedCaptions;
|
|
2347
|
-
closedCaptionsUpdated = true;
|
|
2348
|
-
}
|
|
2349
|
-
|
|
2350
|
-
if (!this.codecInfoInManifest_ &&
|
|
2351
|
-
this.config_.hls.disableCodecGuessing) {
|
|
2352
|
-
mimeType = basicInfo.mimeType;
|
|
2353
|
-
codecs = basicInfo.codecs;
|
|
2354
|
-
}
|
|
2434
|
+
requestBasicInfo = true;
|
|
2355
2435
|
}
|
|
2356
2436
|
}
|
|
2357
2437
|
|
|
2438
|
+
const allowOverrideMimeType = !this.codecInfoInManifest_ &&
|
|
2439
|
+
this.config_.hls.disableCodecGuessing;
|
|
2440
|
+
|
|
2358
2441
|
const wasLive = this.isLive_();
|
|
2359
2442
|
const realStreamInfo = await this.convertParsedPlaylistIntoStreamInfo_(
|
|
2360
2443
|
streamId, mediaVariables, playlist, getUris, responseUri, codecs,
|
|
2361
2444
|
type, languageValue, primary, name, channelsCount, closedCaptions,
|
|
2362
|
-
characteristics, forced, sampleRate, spatialAudio, mimeType
|
|
2445
|
+
characteristics, forced, sampleRate, spatialAudio, mimeType,
|
|
2446
|
+
requestBasicInfo, allowOverrideMimeType);
|
|
2363
2447
|
if (abortSignal.aborted) {
|
|
2364
2448
|
return;
|
|
2365
2449
|
}
|
|
@@ -2392,6 +2476,17 @@ shaka.hls.HlsParser = class {
|
|
|
2392
2476
|
stream.codecs = stream.codecs || realStream.codecs;
|
|
2393
2477
|
stream.closedCaptions =
|
|
2394
2478
|
stream.closedCaptions || realStream.closedCaptions;
|
|
2479
|
+
stream.width = stream.width || realStream.width;
|
|
2480
|
+
stream.height = stream.height || realStream.height;
|
|
2481
|
+
stream.hdr = stream.hdr || realStream.hdr;
|
|
2482
|
+
stream.colorGamut = stream.colorGamut || realStream.colorGamut;
|
|
2483
|
+
if (stream.language == 'und' && realStream.language != 'und') {
|
|
2484
|
+
stream.language = realStream.language;
|
|
2485
|
+
}
|
|
2486
|
+
stream.language = stream.language || realStream.language;
|
|
2487
|
+
stream.channelsCount = stream.channelsCount || realStream.channelsCount;
|
|
2488
|
+
stream.audioSamplingRate =
|
|
2489
|
+
stream.audioSamplingRate || realStream.audioSamplingRate;
|
|
2395
2490
|
this.setFullTypeForStream_(stream);
|
|
2396
2491
|
|
|
2397
2492
|
// Since we lazy-loaded this content, the player may need to create new
|
|
@@ -2400,6 +2495,13 @@ shaka.hls.HlsParser = class {
|
|
|
2400
2495
|
this.playerInterface_.newDrmInfo(stream);
|
|
2401
2496
|
}
|
|
2402
2497
|
|
|
2498
|
+
let closedCaptionsUpdated = false;
|
|
2499
|
+
if ((!closedCaptions && stream.closedCaptions) ||
|
|
2500
|
+
(closedCaptions && stream.closedCaptions &&
|
|
2501
|
+
closedCaptions.size != stream.closedCaptions.size)) {
|
|
2502
|
+
closedCaptionsUpdated = true;
|
|
2503
|
+
}
|
|
2504
|
+
|
|
2403
2505
|
if (this.manifest_ && closedCaptionsUpdated) {
|
|
2404
2506
|
this.playerInterface_.makeTextStreamsForClosedCaptions(this.manifest_);
|
|
2405
2507
|
}
|
|
@@ -2423,6 +2525,14 @@ shaka.hls.HlsParser = class {
|
|
|
2423
2525
|
}
|
|
2424
2526
|
}
|
|
2425
2527
|
|
|
2528
|
+
const qualityInfo =
|
|
2529
|
+
shaka.media.QualityObserver.createQualityInfo(stream);
|
|
2530
|
+
stream.segmentIndex.forEachTopLevelReference((reference) => {
|
|
2531
|
+
if (reference.initSegmentReference) {
|
|
2532
|
+
reference.initSegmentReference.mediaQuality = qualityInfo;
|
|
2533
|
+
}
|
|
2534
|
+
});
|
|
2535
|
+
|
|
2426
2536
|
// Add finishing touches to the stream that can only be done once we have
|
|
2427
2537
|
// more full context on the media as a whole.
|
|
2428
2538
|
if (this.hasEnoughInfoToFinalizeStreams_()) {
|
|
@@ -2451,6 +2561,9 @@ shaka.hls.HlsParser = class {
|
|
|
2451
2561
|
this.finalizeStreams_([streamInfo]);
|
|
2452
2562
|
}
|
|
2453
2563
|
}
|
|
2564
|
+
|
|
2565
|
+
this.processDateRangeTags_(
|
|
2566
|
+
playlist.tags, stream.type, mediaVariables, getUris);
|
|
2454
2567
|
};
|
|
2455
2568
|
|
|
2456
2569
|
/** @type {Promise} */
|
|
@@ -2631,13 +2744,16 @@ shaka.hls.HlsParser = class {
|
|
|
2631
2744
|
* @param {?number} sampleRate
|
|
2632
2745
|
* @param {boolean} spatialAudio
|
|
2633
2746
|
* @param {(string|undefined)} mimeType
|
|
2747
|
+
* @param {boolean=} requestBasicInfo
|
|
2748
|
+
* @param {boolean=} allowOverrideMimeType
|
|
2634
2749
|
* @return {!Promise.<!shaka.hls.HlsParser.StreamInfo>}
|
|
2635
2750
|
* @private
|
|
2636
2751
|
*/
|
|
2637
2752
|
async convertParsedPlaylistIntoStreamInfo_(streamId, variables, playlist,
|
|
2638
2753
|
getUris, responseUri, codecs, type, languageValue, primary, name,
|
|
2639
2754
|
channelsCount, closedCaptions, characteristics, forced, sampleRate,
|
|
2640
|
-
spatialAudio, mimeType = undefined
|
|
2755
|
+
spatialAudio, mimeType = undefined, requestBasicInfo = true,
|
|
2756
|
+
allowOverrideMimeType = true) {
|
|
2641
2757
|
goog.asserts.assert(playlist.segments != null,
|
|
2642
2758
|
'Media playlist should have segments!');
|
|
2643
2759
|
|
|
@@ -2653,6 +2769,30 @@ shaka.hls.HlsParser = class {
|
|
|
2653
2769
|
const {segments, bandwidth} = this.createSegments_(
|
|
2654
2770
|
playlist, mediaSequenceToStartTime, variables, getUris, type);
|
|
2655
2771
|
|
|
2772
|
+
let width = null;
|
|
2773
|
+
let height = null;
|
|
2774
|
+
let videoRange = null;
|
|
2775
|
+
let colorGamut = null;
|
|
2776
|
+
if (segments.length > 0 && requestBasicInfo) {
|
|
2777
|
+
const basicInfo = await this.getBasicInfoFromSegments_(segments);
|
|
2778
|
+
|
|
2779
|
+
type = basicInfo.type;
|
|
2780
|
+
languageValue = basicInfo.language;
|
|
2781
|
+
channelsCount = basicInfo.channelCount;
|
|
2782
|
+
sampleRate = basicInfo.sampleRate;
|
|
2783
|
+
closedCaptions = basicInfo.closedCaptions;
|
|
2784
|
+
|
|
2785
|
+
height = basicInfo.height;
|
|
2786
|
+
width = basicInfo.width;
|
|
2787
|
+
videoRange = basicInfo.videoRange;
|
|
2788
|
+
colorGamut = basicInfo.colorGamut;
|
|
2789
|
+
|
|
2790
|
+
if (allowOverrideMimeType) {
|
|
2791
|
+
mimeType = basicInfo.mimeType;
|
|
2792
|
+
codecs = basicInfo.codecs;
|
|
2793
|
+
}
|
|
2794
|
+
}
|
|
2795
|
+
|
|
2656
2796
|
if (!mimeType) {
|
|
2657
2797
|
mimeType = await this.guessMimeType_(type, codecs, segments);
|
|
2658
2798
|
}
|
|
@@ -2679,6 +2819,13 @@ shaka.hls.HlsParser = class {
|
|
|
2679
2819
|
}
|
|
2680
2820
|
this.setFullTypeForStream_(stream);
|
|
2681
2821
|
|
|
2822
|
+
if (type == shaka.util.ManifestParserUtils.ContentType.VIDEO &&
|
|
2823
|
+
(width || height || videoRange || colorGamut)) {
|
|
2824
|
+
this.addVideoAttributes_(stream, width, height,
|
|
2825
|
+
/* frameRate= */ null, videoRange, /* videoLayout= */ null,
|
|
2826
|
+
colorGamut);
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2682
2829
|
// This new calculation is necessary for Low Latency streams.
|
|
2683
2830
|
if (this.isLive_()) {
|
|
2684
2831
|
this.determineLastTargetDuration_(playlist);
|
|
@@ -3325,11 +3472,8 @@ shaka.hls.HlsParser = class {
|
|
|
3325
3472
|
let startByte = 0;
|
|
3326
3473
|
let endByte = null;
|
|
3327
3474
|
|
|
3328
|
-
if (hlsSegment.partialSegments.length
|
|
3329
|
-
|
|
3330
|
-
'low-latency streaming mode is not enabled in Shaka ' +
|
|
3331
|
-
'Player. Set streaming.lowLatencyMode configuration to ' +
|
|
3332
|
-
'true, and see https://bit.ly/3clctcj for details.');
|
|
3475
|
+
if (hlsSegment.partialSegments.length) {
|
|
3476
|
+
this.manifest_.isLowLatency = true;
|
|
3333
3477
|
}
|
|
3334
3478
|
|
|
3335
3479
|
let syncTime = null;
|
|
@@ -3345,6 +3489,7 @@ shaka.hls.HlsParser = class {
|
|
|
3345
3489
|
|
|
3346
3490
|
let status = shaka.media.SegmentReference.Status.AVAILABLE;
|
|
3347
3491
|
if (shaka.hls.Utils.getFirstTagWithName(tags, 'EXT-X-GAP')) {
|
|
3492
|
+
this.manifest_.gapCount++;
|
|
3348
3493
|
status = shaka.media.SegmentReference.Status.MISSING;
|
|
3349
3494
|
}
|
|
3350
3495
|
|
|
@@ -3427,6 +3572,7 @@ shaka.hls.HlsParser = class {
|
|
|
3427
3572
|
|
|
3428
3573
|
let partialStatus = shaka.media.SegmentReference.Status.AVAILABLE;
|
|
3429
3574
|
if (item.getAttributeValue('GAP') == 'YES') {
|
|
3575
|
+
this.manifest_.gapCount++;
|
|
3430
3576
|
partialStatus = shaka.media.SegmentReference.Status.MISSING;
|
|
3431
3577
|
somePartialSegmentWithGap = true;
|
|
3432
3578
|
}
|
|
@@ -3621,6 +3767,156 @@ shaka.hls.HlsParser = class {
|
|
|
3621
3767
|
return [startByte, endByte];
|
|
3622
3768
|
}
|
|
3623
3769
|
|
|
3770
|
+
/**
|
|
3771
|
+
* @param {!Array.<!shaka.hls.Tag>} tags
|
|
3772
|
+
* @param {string} contentType
|
|
3773
|
+
* @param {!Map.<string, string>} variables
|
|
3774
|
+
* @param {function():!Array.<string>} getUris
|
|
3775
|
+
* @private
|
|
3776
|
+
*/
|
|
3777
|
+
processDateRangeTags_(tags, contentType, variables, getUris) {
|
|
3778
|
+
const ContentType = shaka.util.ManifestParserUtils.ContentType;
|
|
3779
|
+
if (contentType != ContentType.VIDEO && contentType != ContentType.AUDIO) {
|
|
3780
|
+
// DATE-RANGE should only appear in AUDIO or VIDEO playlists.
|
|
3781
|
+
// We ignore those that appear in other playlists.
|
|
3782
|
+
return;
|
|
3783
|
+
}
|
|
3784
|
+
const Utils = shaka.hls.Utils;
|
|
3785
|
+
const initialProgramDateTime =
|
|
3786
|
+
this.presentationTimeline_.getInitialProgramDateTime();
|
|
3787
|
+
if (!initialProgramDateTime ||
|
|
3788
|
+
this.ignoreManifestProgramDateTimeFor_(contentType)) {
|
|
3789
|
+
return;
|
|
3790
|
+
}
|
|
3791
|
+
let dateRangeTags =
|
|
3792
|
+
shaka.hls.Utils.filterTagsByName(tags, 'EXT-X-DATERANGE');
|
|
3793
|
+
dateRangeTags = dateRangeTags.sort((a, b) => {
|
|
3794
|
+
const aStartDateValue = a.getRequiredAttrValue('START-DATE');
|
|
3795
|
+
const bStartDateValue = b.getRequiredAttrValue('START-DATE');
|
|
3796
|
+
if (aStartDateValue < bStartDateValue) {
|
|
3797
|
+
return -1;
|
|
3798
|
+
}
|
|
3799
|
+
if (aStartDateValue > bStartDateValue) {
|
|
3800
|
+
return 1;
|
|
3801
|
+
}
|
|
3802
|
+
return 0;
|
|
3803
|
+
});
|
|
3804
|
+
for (let i = 0; i < dateRangeTags.length; i++) {
|
|
3805
|
+
const tag = dateRangeTags[i];
|
|
3806
|
+
const id = tag.getRequiredAttrValue('ID');
|
|
3807
|
+
if (this.dateRangeIdsEmitted_.has(id)) {
|
|
3808
|
+
continue;
|
|
3809
|
+
}
|
|
3810
|
+
const startDateValue = tag.getRequiredAttrValue('START-DATE');
|
|
3811
|
+
const startDate = shaka.util.TXml.parseDate(startDateValue);
|
|
3812
|
+
if (isNaN(startDate)) {
|
|
3813
|
+
// Invalid START-DATE
|
|
3814
|
+
continue;
|
|
3815
|
+
}
|
|
3816
|
+
goog.asserts.assert(startDate != null, 'Start date should not be null!');
|
|
3817
|
+
const startTime = Math.max(0, startDate - initialProgramDateTime);
|
|
3818
|
+
|
|
3819
|
+
let endTime = null;
|
|
3820
|
+
const endDateValue = tag.getAttributeValue('END-DATE');
|
|
3821
|
+
if (endDateValue) {
|
|
3822
|
+
const endDate = shaka.util.TXml.parseDate(endDateValue);
|
|
3823
|
+
if (!isNaN(endDate)) {
|
|
3824
|
+
goog.asserts.assert(endDate != null, 'End date should not be null!');
|
|
3825
|
+
endTime = endDate - initialProgramDateTime;
|
|
3826
|
+
if (endTime < 0) {
|
|
3827
|
+
// Date range in the past
|
|
3828
|
+
continue;
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
}
|
|
3832
|
+
if (endTime == null) {
|
|
3833
|
+
const durationValue = tag.getAttributeValue('DURATION') ||
|
|
3834
|
+
tag.getAttributeValue('PLANNED-DURATION');
|
|
3835
|
+
if (durationValue) {
|
|
3836
|
+
const duration = parseFloat(durationValue);
|
|
3837
|
+
if (!isNaN(duration)) {
|
|
3838
|
+
endTime = startTime + duration;
|
|
3839
|
+
}
|
|
3840
|
+
const realEndTime = startDate - initialProgramDateTime + duration;
|
|
3841
|
+
if (realEndTime < 0) {
|
|
3842
|
+
// Date range in the past
|
|
3843
|
+
continue;
|
|
3844
|
+
}
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
const type = tag.getAttributeValue('CLASS') || 'com.apple.quicktime.HLS';
|
|
3848
|
+
|
|
3849
|
+
const endOnNext = tag.getAttributeValue('END-ON-NEXT') == 'YES';
|
|
3850
|
+
if (endTime == null && endOnNext) {
|
|
3851
|
+
for (let j = i + 1; j < dateRangeTags.length; j++) {
|
|
3852
|
+
const otherDateRangeType =
|
|
3853
|
+
dateRangeTags[j].getAttributeValue('CLASS') ||
|
|
3854
|
+
'com.apple.quicktime.HLS';
|
|
3855
|
+
if (type != otherDateRangeType) {
|
|
3856
|
+
continue;
|
|
3857
|
+
}
|
|
3858
|
+
const otherDateRangeStartDateValue =
|
|
3859
|
+
dateRangeTags[j].getRequiredAttrValue('START-DATE');
|
|
3860
|
+
const otherDateRangeStartDate =
|
|
3861
|
+
shaka.util.TXml.parseDate(otherDateRangeStartDateValue);
|
|
3862
|
+
if (isNaN(otherDateRangeStartDate)) {
|
|
3863
|
+
// Invalid START-DATE
|
|
3864
|
+
continue;
|
|
3865
|
+
}
|
|
3866
|
+
if (otherDateRangeStartDate && otherDateRangeStartDate > startDate) {
|
|
3867
|
+
endTime = Math.max(0,
|
|
3868
|
+
otherDateRangeStartDate - initialProgramDateTime);
|
|
3869
|
+
break;
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
if (endTime == null) {
|
|
3873
|
+
// Since we cannot know when it ends, we omit it for now and in the
|
|
3874
|
+
// future with an update we will be able to have more information.
|
|
3875
|
+
continue;
|
|
3876
|
+
}
|
|
3877
|
+
}
|
|
3878
|
+
|
|
3879
|
+
// Exclude these attributes from the metadata since they already go into
|
|
3880
|
+
// other fields (eg: startTime or endTime) or are not necessary..
|
|
3881
|
+
const excludedAttributes = [
|
|
3882
|
+
'ID',
|
|
3883
|
+
'CLASS',
|
|
3884
|
+
'START-DATE',
|
|
3885
|
+
'END-DATE',
|
|
3886
|
+
'DURATION',
|
|
3887
|
+
'END-ON-NEXT',
|
|
3888
|
+
];
|
|
3889
|
+
|
|
3890
|
+
/* @type {!Array.<shaka.extern.MetadataFrame>} */
|
|
3891
|
+
const values = [];
|
|
3892
|
+
for (const attribute of tag.attributes) {
|
|
3893
|
+
if (excludedAttributes.includes(attribute.name)) {
|
|
3894
|
+
continue;
|
|
3895
|
+
}
|
|
3896
|
+
let data = Utils.variableSubstitution(attribute.value, variables);
|
|
3897
|
+
if (attribute.name == 'X-ASSET-URI' ||
|
|
3898
|
+
attribute.name == 'X-ASSET-LIST') {
|
|
3899
|
+
data = Utils.constructSegmentUris(
|
|
3900
|
+
getUris(), attribute.value, variables)[0];
|
|
3901
|
+
}
|
|
3902
|
+
const metadataFrame = {
|
|
3903
|
+
key: attribute.name,
|
|
3904
|
+
description: '',
|
|
3905
|
+
data,
|
|
3906
|
+
mimeType: null,
|
|
3907
|
+
pictureType: null,
|
|
3908
|
+
};
|
|
3909
|
+
values.push(metadataFrame);
|
|
3910
|
+
}
|
|
3911
|
+
|
|
3912
|
+
if (values.length) {
|
|
3913
|
+
this.playerInterface_.onMetadata(type, startTime, endTime, values);
|
|
3914
|
+
}
|
|
3915
|
+
|
|
3916
|
+
this.dateRangeIdsEmitted_.add(id);
|
|
3917
|
+
}
|
|
3918
|
+
}
|
|
3919
|
+
|
|
3624
3920
|
/**
|
|
3625
3921
|
* Parses shaka.hls.Segment objects into shaka.media.SegmentReferences and
|
|
3626
3922
|
* get the bandwidth necessary for this segments If it's defined in the
|
|
@@ -3925,6 +4221,28 @@ shaka.hls.HlsParser = class {
|
|
|
3925
4221
|
return map['mp4'];
|
|
3926
4222
|
}
|
|
3927
4223
|
|
|
4224
|
+
/**
|
|
4225
|
+
* @param {!Array.<!shaka.media.SegmentReference>} segments
|
|
4226
|
+
* @return {!shaka.media.SegmentReference}
|
|
4227
|
+
* @private
|
|
4228
|
+
*/
|
|
4229
|
+
getAvailableSegment_(segments) {
|
|
4230
|
+
goog.asserts.assert(segments.length, 'Should have segments!');
|
|
4231
|
+
|
|
4232
|
+
// If you wait long enough, requesting the first segment can fail
|
|
4233
|
+
// because it has fallen off the left edge of DVR, so to be safer,
|
|
4234
|
+
// let's request the middle segment.
|
|
4235
|
+
let segmentIndex = this.isLive_() ?
|
|
4236
|
+
Math.trunc((segments.length - 1) / 2) : 0;
|
|
4237
|
+
let segment = segments[segmentIndex];
|
|
4238
|
+
while (segment.getStatus() == shaka.media.SegmentReference.Status.MISSING &&
|
|
4239
|
+
(segmentIndex + 1) < segments.length) {
|
|
4240
|
+
segmentIndex ++;
|
|
4241
|
+
segment = segments[segmentIndex];
|
|
4242
|
+
}
|
|
4243
|
+
return segment;
|
|
4244
|
+
}
|
|
4245
|
+
|
|
3928
4246
|
/**
|
|
3929
4247
|
* Attempts to guess stream's mime type.
|
|
3930
4248
|
*
|
|
@@ -3938,17 +4256,7 @@ shaka.hls.HlsParser = class {
|
|
|
3938
4256
|
const HlsParser = shaka.hls.HlsParser;
|
|
3939
4257
|
const requestType = shaka.net.NetworkingEngine.RequestType.SEGMENT;
|
|
3940
4258
|
|
|
3941
|
-
|
|
3942
|
-
// because it has fallen off the left edge of DVR, so to be safer,
|
|
3943
|
-
// let's request the middle segment.
|
|
3944
|
-
goog.asserts.assert(segments.length, 'Should have segments!');
|
|
3945
|
-
let segmentIndex = Math.trunc((segments.length - 1) / 2);
|
|
3946
|
-
let segment = segments[segmentIndex];
|
|
3947
|
-
while (segment.status == shaka.media.SegmentReference.Status.MISSING &&
|
|
3948
|
-
segmentIndex < segments.length) {
|
|
3949
|
-
segmentIndex ++;
|
|
3950
|
-
segment = segments[segmentIndex];
|
|
3951
|
-
}
|
|
4259
|
+
const segment = this.getAvailableSegment_(segments);
|
|
3952
4260
|
|
|
3953
4261
|
if (segment.status == shaka.media.SegmentReference.Status.MISSING) {
|
|
3954
4262
|
return this.guessMimeTypeFallback_(contentType);
|
|
@@ -4264,7 +4572,7 @@ shaka.hls.HlsParser = class {
|
|
|
4264
4572
|
const drmInfo = shaka.util.ManifestParserUtils.createDrmInfo(
|
|
4265
4573
|
'com.apple.fps', encryptionScheme, [
|
|
4266
4574
|
{initDataType: 'sinf', initData: new Uint8Array(0), keyId: null},
|
|
4267
|
-
]);
|
|
4575
|
+
], drmTag.getRequiredAttrValue('URI'));
|
|
4268
4576
|
|
|
4269
4577
|
return drmInfo;
|
|
4270
4578
|
}
|