shaka-player 4.11.13 → 4.12.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 +47 -0
- package/dist/deps.js +8 -8
- package/dist/locales.js +35 -35
- package/dist/shaka-player.compiled.d.ts +36 -13
- package/dist/shaka-player.compiled.debug.d.ts +36 -13
- package/dist/shaka-player.compiled.debug.externs.js +46 -11
- package/dist/shaka-player.compiled.debug.js +543 -517
- package/dist/shaka-player.compiled.debug.map +4 -4
- package/dist/shaka-player.compiled.externs.js +46 -11
- package/dist/shaka-player.compiled.js +1348 -1331
- package/dist/shaka-player.compiled.map +4 -4
- package/dist/shaka-player.ui.d.ts +37 -14
- package/dist/shaka-player.ui.debug.d.ts +37 -14
- package/dist/shaka-player.ui.debug.externs.js +46 -11
- package/dist/shaka-player.ui.debug.js +629 -598
- package/dist/shaka-player.ui.debug.map +4 -4
- package/dist/shaka-player.ui.externs.js +46 -11
- package/dist/shaka-player.ui.js +1624 -1603
- package/dist/shaka-player.ui.map +4 -4
- package/externs/shaka/ads.js +9 -0
- package/externs/shaka/manifest.js +26 -1
- package/externs/shaka/offline.js +8 -2
- package/externs/shaka/player.js +80 -28
- package/externs/shaka/text.js +9 -0
- package/lib/ads/ad_manager.js +6 -0
- package/lib/ads/ad_utils.js +1 -0
- package/lib/ads/client_side_ad.js +8 -0
- package/lib/ads/interstitial_ad.js +25 -4
- package/lib/ads/interstitial_ad_manager.js +67 -8
- package/lib/ads/media_tailor_ad.js +8 -0
- package/lib/ads/server_side_ad.js +8 -0
- package/lib/dash/content_protection.js +31 -0
- package/lib/dash/dash_parser.js +22 -12
- package/lib/hls/hls_parser.js +63 -49
- package/lib/media/adaptation_set.js +11 -17
- package/lib/media/adaptation_set_criteria.js +4 -9
- package/lib/media/drm_engine.js +3 -1
- package/lib/media/manifest_filterer.js +2 -1
- package/lib/media/media_source_engine.js +286 -20
- package/lib/media/playhead.js +3 -1
- package/lib/media/preload_manager.js +10 -7
- package/lib/media/segment_prefetch.js +32 -13
- package/lib/media/segment_reference.js +3 -3
- package/lib/media/streaming_engine.js +87 -261
- package/lib/mss/mss_parser.js +1 -0
- package/lib/offline/download_info.js +5 -1
- package/lib/offline/indexeddb/storage_mechanism.js +4 -3
- package/lib/offline/indexeddb/v1_storage_cell.js +2 -0
- package/lib/offline/indexeddb/v2_storage_cell.js +2 -0
- package/lib/offline/manifest_converter.js +4 -0
- package/lib/offline/storage.js +277 -5
- package/lib/player.js +224 -78
- package/lib/text/simple_text_displayer.js +7 -0
- package/lib/text/stub_text_displayer.js +7 -0
- package/lib/text/ui_text_displayer.js +12 -0
- package/lib/util/dom_utils.js +14 -0
- package/lib/util/error.js +5 -0
- package/lib/util/periods.js +2 -0
- package/lib/util/platform.js +15 -0
- package/lib/util/player_configuration.js +11 -4
- package/lib/util/stream_utils.js +20 -1
- package/package.json +1 -1
- package/ui/ad_statistics_button.js +12 -0
- package/ui/controls.js +31 -7
- package/ui/externs/ui.js +8 -1
- package/ui/fullscreen_button.js +9 -0
- package/ui/locales/be.json +49 -0
- package/ui/mute_button.js +42 -11
- package/ui/skip_ad_button.js +16 -11
- package/ui/statistics_button.js +12 -0
- package/ui/ui.js +10 -5
|
@@ -20,8 +20,10 @@ goog.require('shaka.text.TextEngine');
|
|
|
20
20
|
goog.require('shaka.transmuxer.TransmuxerEngine');
|
|
21
21
|
goog.require('shaka.util.BufferUtils');
|
|
22
22
|
goog.require('shaka.util.Destroyer');
|
|
23
|
+
goog.require('shaka.util.Dom');
|
|
23
24
|
goog.require('shaka.util.Error');
|
|
24
25
|
goog.require('shaka.util.EventManager');
|
|
26
|
+
goog.require('shaka.util.FakeEvent');
|
|
25
27
|
goog.require('shaka.util.Functional');
|
|
26
28
|
goog.require('shaka.util.IDestroyable');
|
|
27
29
|
goog.require('shaka.util.Id3Utils');
|
|
@@ -116,6 +118,22 @@ shaka.media.MediaSourceEngine = class {
|
|
|
116
118
|
/** @private {boolean} */
|
|
117
119
|
this.playbackHasBegun_ = false;
|
|
118
120
|
|
|
121
|
+
/** @private {boolean} */
|
|
122
|
+
this.streamingAllowed_ = true;
|
|
123
|
+
|
|
124
|
+
/** @private {boolean} */
|
|
125
|
+
this.usingRemotePlayback_ = false;
|
|
126
|
+
|
|
127
|
+
/** @private {HTMLSourceElement} */
|
|
128
|
+
this.source_ = null;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Fallback source element with direct media URI, used for casting
|
|
132
|
+
* purposes.
|
|
133
|
+
* @private {HTMLSourceElement}
|
|
134
|
+
*/
|
|
135
|
+
this.secondarySource_ = null;
|
|
136
|
+
|
|
119
137
|
/** @private {MediaSource} */
|
|
120
138
|
this.mediaSource_ = this.createMediaSource(this.mediaSourceOpen_);
|
|
121
139
|
|
|
@@ -143,9 +161,6 @@ shaka.media.MediaSourceEngine = class {
|
|
|
143
161
|
/** @private {boolean} */
|
|
144
162
|
this.needSplitMuxedContent_ = false;
|
|
145
163
|
|
|
146
|
-
/** @private {boolean} */
|
|
147
|
-
this.streamingAllowed_ = true;
|
|
148
|
-
|
|
149
164
|
/** @private {?number} */
|
|
150
165
|
this.lastDuration_ = null;
|
|
151
166
|
|
|
@@ -167,6 +182,22 @@ shaka.media.MediaSourceEngine = class {
|
|
|
167
182
|
|
|
168
183
|
/** @private {!shaka.util.PublicPromise.<number>} */
|
|
169
184
|
this.audioCompensation_ = new shaka.util.PublicPromise();
|
|
185
|
+
|
|
186
|
+
if (this.video_.remote) {
|
|
187
|
+
this.usingRemotePlayback_ = this.video_.remote.state != 'disconnected';
|
|
188
|
+
|
|
189
|
+
this.eventManager_.listen(this.video_.remote, 'connect', () => {
|
|
190
|
+
this.usingRemotePlayback_ = this.video_.remote.state != 'disconnected';
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
this.eventManager_.listen(this.video_.remote, 'connecting', () => {
|
|
194
|
+
this.usingRemotePlayback_ = this.video_.remote.state != 'disconnected';
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
this.eventManager_.listen(this.video_.remote, 'disconnect', () => {
|
|
198
|
+
this.usingRemotePlayback_ = this.video_.remote.state != 'disconnected';
|
|
199
|
+
});
|
|
200
|
+
}
|
|
170
201
|
}
|
|
171
202
|
|
|
172
203
|
/**
|
|
@@ -185,7 +216,9 @@ shaka.media.MediaSourceEngine = class {
|
|
|
185
216
|
let mediaSource;
|
|
186
217
|
|
|
187
218
|
if (window.ManagedMediaSource) {
|
|
188
|
-
this.
|
|
219
|
+
if (!this.secondarySource_) {
|
|
220
|
+
this.video_.disableRemotePlayback = true;
|
|
221
|
+
}
|
|
189
222
|
|
|
190
223
|
mediaSource = new ManagedMediaSource();
|
|
191
224
|
|
|
@@ -216,11 +249,41 @@ shaka.media.MediaSourceEngine = class {
|
|
|
216
249
|
// Store the object URL for releasing it later.
|
|
217
250
|
this.url_ = shaka.media.MediaSourceEngine.createObjectURL(mediaSource);
|
|
218
251
|
|
|
219
|
-
this.video_.src
|
|
252
|
+
this.video_.removeAttribute('src');
|
|
253
|
+
if (this.source_) {
|
|
254
|
+
this.video_.removeChild(this.source_);
|
|
255
|
+
}
|
|
256
|
+
if (this.secondarySource_) {
|
|
257
|
+
this.video_.removeChild(this.secondarySource_);
|
|
258
|
+
}
|
|
259
|
+
this.source_ = shaka.util.Dom.createSourceElement(this.url_);
|
|
260
|
+
this.video_.appendChild(this.source_);
|
|
261
|
+
if (this.secondarySource_) {
|
|
262
|
+
this.video_.appendChild(this.secondarySource_);
|
|
263
|
+
}
|
|
264
|
+
this.video_.load();
|
|
220
265
|
|
|
221
266
|
return mediaSource;
|
|
222
267
|
}
|
|
223
268
|
|
|
269
|
+
/**
|
|
270
|
+
* @param {string} uri
|
|
271
|
+
* @param {string} mimeType
|
|
272
|
+
*/
|
|
273
|
+
addSecondarySource(uri, mimeType) {
|
|
274
|
+
if (!this.video_ || !(this.mediaSource_ instanceof ManagedMediaSource)) {
|
|
275
|
+
shaka.log.warning(
|
|
276
|
+
'Secondary source is used only with ManagedMediaSource');
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (this.secondarySource_) {
|
|
280
|
+
this.video_.removeChild(this.secondarySource_);
|
|
281
|
+
}
|
|
282
|
+
this.secondarySource_ = shaka.util.Dom.createSourceElement(uri, mimeType);
|
|
283
|
+
this.video_.appendChild(this.secondarySource_);
|
|
284
|
+
this.video_.disableRemotePlayback = false;
|
|
285
|
+
}
|
|
286
|
+
|
|
224
287
|
/**
|
|
225
288
|
* @param {shaka.util.PublicPromise} p
|
|
226
289
|
* @private
|
|
@@ -317,6 +380,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
317
380
|
'video/mp4; codecs="vp9"',
|
|
318
381
|
'video/mp4; codecs="vp09.00.10.08"',
|
|
319
382
|
'video/mp4; codecs="av01.0.01M.08"',
|
|
383
|
+
'video/mp4; codecs="dvh1.05.01"',
|
|
320
384
|
'video/mp4; codecs="dvh1.20.01"',
|
|
321
385
|
'audio/mp4; codecs="mp4a.40.2"',
|
|
322
386
|
'audio/mp4; codecs="ac-3"',
|
|
@@ -413,13 +477,19 @@ shaka.media.MediaSourceEngine = class {
|
|
|
413
477
|
this.eventManager_ = null;
|
|
414
478
|
}
|
|
415
479
|
|
|
416
|
-
if (this.video_) {
|
|
480
|
+
if (this.video_ && this.secondarySource_) {
|
|
481
|
+
this.video_.removeChild(this.secondarySource_);
|
|
482
|
+
}
|
|
483
|
+
if (this.video_ && this.source_) {
|
|
417
484
|
// "unload" the video element.
|
|
418
|
-
this.video_.
|
|
485
|
+
this.video_.removeChild(this.source_);
|
|
419
486
|
this.video_.load();
|
|
420
|
-
this.video_ =
|
|
487
|
+
this.video_.disableRemotePlayback = false;
|
|
421
488
|
}
|
|
422
489
|
|
|
490
|
+
this.video_ = null;
|
|
491
|
+
this.source_ = null;
|
|
492
|
+
this.secondarySource_ = null;
|
|
423
493
|
this.config_ = null;
|
|
424
494
|
this.mediaSource_ = null;
|
|
425
495
|
this.textEngine_ = null;
|
|
@@ -492,6 +562,11 @@ shaka.media.MediaSourceEngine = class {
|
|
|
492
562
|
!this.ignoreManifestTimestampsInSegmentsMode_;
|
|
493
563
|
|
|
494
564
|
this.tsParsers_ = {};
|
|
565
|
+
this.firstVideoTimestamp_ = null;
|
|
566
|
+
this.firstVideoReferenceStartTime_ = null;
|
|
567
|
+
this.firstAudioTimestamp_ = null;
|
|
568
|
+
this.firstAudioReferenceStartTime_ = null;
|
|
569
|
+
this.audioCompensation_ = new shaka.util.PublicPromise();
|
|
495
570
|
|
|
496
571
|
for (const contentType of streamsByType.keys()) {
|
|
497
572
|
const stream = streamsByType.get(contentType);
|
|
@@ -504,6 +579,10 @@ shaka.media.MediaSourceEngine = class {
|
|
|
504
579
|
this.queues_[contentType] = [];
|
|
505
580
|
}
|
|
506
581
|
}
|
|
582
|
+
const audio = streamsByType.get(ContentType.AUDIO);
|
|
583
|
+
if (audio && audio.isAudioMuxedInVideo) {
|
|
584
|
+
this.needSplitMuxedContent_ = true;
|
|
585
|
+
}
|
|
507
586
|
}
|
|
508
587
|
|
|
509
588
|
/**
|
|
@@ -613,7 +692,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
613
692
|
* @return {boolean}
|
|
614
693
|
*/
|
|
615
694
|
isStreamingAllowed() {
|
|
616
|
-
return this.streamingAllowed_;
|
|
695
|
+
return this.streamingAllowed_ && !this.usingRemotePlayback_;
|
|
617
696
|
}
|
|
618
697
|
|
|
619
698
|
/**
|
|
@@ -752,7 +831,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
752
831
|
* @private
|
|
753
832
|
*/
|
|
754
833
|
getBuffered_(contentType) {
|
|
755
|
-
if (this.reloadingMediaSource_) {
|
|
834
|
+
if (this.reloadingMediaSource_ || this.usingRemotePlayback_) {
|
|
756
835
|
return null;
|
|
757
836
|
}
|
|
758
837
|
try {
|
|
@@ -780,15 +859,18 @@ shaka.media.MediaSourceEngine = class {
|
|
|
780
859
|
}
|
|
781
860
|
|
|
782
861
|
/**
|
|
862
|
+
* This method is only public for testing.
|
|
863
|
+
*
|
|
783
864
|
* @param {shaka.util.ManifestParserUtils.ContentType} contentType
|
|
784
865
|
* @param {!BufferSource} data
|
|
785
|
-
* @param {
|
|
786
|
-
* we are appending
|
|
866
|
+
* @param {!shaka.media.SegmentReference} reference The segment reference
|
|
867
|
+
* we are appending
|
|
868
|
+
* @param {shaka.extern.Stream} stream
|
|
787
869
|
* @param {!string} mimeType
|
|
788
870
|
* @return {{timestamp: ?number, metadata: !Array.<shaka.extern.ID3Metadata>}}
|
|
789
|
-
* @private
|
|
790
871
|
*/
|
|
791
|
-
|
|
872
|
+
getTimestampAndDispatchMetadata(contentType, data, reference, stream,
|
|
873
|
+
mimeType) {
|
|
792
874
|
let timestamp = null;
|
|
793
875
|
let metadata = [];
|
|
794
876
|
|
|
@@ -815,15 +897,23 @@ shaka.media.MediaSourceEngine = class {
|
|
|
815
897
|
[id3Metadata], /* offset= */ 0, reference.endTime);
|
|
816
898
|
}
|
|
817
899
|
} else if (mimeType.includes('/mp4') &&
|
|
818
|
-
reference &&
|
|
900
|
+
reference &&
|
|
819
901
|
reference.initSegmentReference &&
|
|
820
902
|
reference.initSegmentReference.timescale) {
|
|
821
903
|
const timescale = reference.initSegmentReference.timescale;
|
|
822
904
|
if (!isNaN(timescale)) {
|
|
905
|
+
const hasEmsg = ((stream.emsgSchemeIdUris != null &&
|
|
906
|
+
stream.emsgSchemeIdUris.length > 0) ||
|
|
907
|
+
this.config_.dispatchAllEmsgBoxes);
|
|
823
908
|
const Mp4Parser = shaka.util.Mp4Parser;
|
|
824
909
|
let startTime = 0;
|
|
825
910
|
let parsedMedia = false;
|
|
826
|
-
new Mp4Parser()
|
|
911
|
+
const parser = new Mp4Parser();
|
|
912
|
+
if (hasEmsg) {
|
|
913
|
+
parser.fullBox('emsg', (box) =>
|
|
914
|
+
this.parseEMSG_(reference, stream.emsgSchemeIdUris, box));
|
|
915
|
+
}
|
|
916
|
+
parser.fullBox('prft', (box) => this.parsePrft_(timescale, box))
|
|
827
917
|
.box('moof', Mp4Parser.children)
|
|
828
918
|
.box('traf', Mp4Parser.children)
|
|
829
919
|
.fullBox('tfdt', (box) => {
|
|
@@ -836,7 +926,7 @@ shaka.media.MediaSourceEngine = class {
|
|
|
836
926
|
parsedMedia = true;
|
|
837
927
|
box.parser.stop();
|
|
838
928
|
}).parse(data, /* partialOkay= */ true);
|
|
839
|
-
if (parsedMedia) {
|
|
929
|
+
if (parsedMedia && reference.timestampOffset == 0) {
|
|
840
930
|
timestamp = startTime;
|
|
841
931
|
}
|
|
842
932
|
}
|
|
@@ -857,6 +947,172 @@ shaka.media.MediaSourceEngine = class {
|
|
|
857
947
|
return {timestamp, metadata};
|
|
858
948
|
}
|
|
859
949
|
|
|
950
|
+
|
|
951
|
+
/**
|
|
952
|
+
* Parse the EMSG box from a MP4 container.
|
|
953
|
+
*
|
|
954
|
+
* @param {!shaka.media.SegmentReference} reference
|
|
955
|
+
* @param {?Array.<string>} emsgSchemeIdUris Array of emsg
|
|
956
|
+
* scheme_id_uri for which emsg boxes should be parsed.
|
|
957
|
+
* @param {!shaka.extern.ParsedBox} box
|
|
958
|
+
* @private
|
|
959
|
+
* https://dashif-documents.azurewebsites.net/Events/master/event.html#emsg-format
|
|
960
|
+
* aligned(8) class DASHEventMessageBox
|
|
961
|
+
* extends FullBox(‘emsg’, version, flags = 0){
|
|
962
|
+
* if (version==0) {
|
|
963
|
+
* string scheme_id_uri;
|
|
964
|
+
* string value;
|
|
965
|
+
* unsigned int(32) timescale;
|
|
966
|
+
* unsigned int(32) presentation_time_delta;
|
|
967
|
+
* unsigned int(32) event_duration;
|
|
968
|
+
* unsigned int(32) id;
|
|
969
|
+
* } else if (version==1) {
|
|
970
|
+
* unsigned int(32) timescale;
|
|
971
|
+
* unsigned int(64) presentation_time;
|
|
972
|
+
* unsigned int(32) event_duration;
|
|
973
|
+
* unsigned int(32) id;
|
|
974
|
+
* string scheme_id_uri;
|
|
975
|
+
* string value;
|
|
976
|
+
* }
|
|
977
|
+
* unsigned int(8) message_data[];
|
|
978
|
+
*/
|
|
979
|
+
parseEMSG_(reference, emsgSchemeIdUris, box) {
|
|
980
|
+
let timescale;
|
|
981
|
+
let id;
|
|
982
|
+
let eventDuration;
|
|
983
|
+
let schemeId;
|
|
984
|
+
let startTime;
|
|
985
|
+
let presentationTimeDelta;
|
|
986
|
+
let value;
|
|
987
|
+
|
|
988
|
+
if (box.version === 0) {
|
|
989
|
+
schemeId = box.reader.readTerminatedString();
|
|
990
|
+
value = box.reader.readTerminatedString();
|
|
991
|
+
timescale = box.reader.readUint32();
|
|
992
|
+
presentationTimeDelta = box.reader.readUint32();
|
|
993
|
+
eventDuration = box.reader.readUint32();
|
|
994
|
+
id = box.reader.readUint32();
|
|
995
|
+
startTime = reference.startTime + (presentationTimeDelta / timescale);
|
|
996
|
+
} else {
|
|
997
|
+
timescale = box.reader.readUint32();
|
|
998
|
+
const pts = box.reader.readUint64();
|
|
999
|
+
startTime = (pts / timescale) + reference.timestampOffset;
|
|
1000
|
+
presentationTimeDelta = startTime - reference.startTime;
|
|
1001
|
+
eventDuration = box.reader.readUint32();
|
|
1002
|
+
id = box.reader.readUint32();
|
|
1003
|
+
schemeId = box.reader.readTerminatedString();
|
|
1004
|
+
value = box.reader.readTerminatedString();
|
|
1005
|
+
}
|
|
1006
|
+
const messageData = box.reader.readBytes(
|
|
1007
|
+
box.reader.getLength() - box.reader.getPosition());
|
|
1008
|
+
|
|
1009
|
+
// See DASH sec. 5.10.3.3.1
|
|
1010
|
+
// If a DASH client detects an event message box with a scheme that is not
|
|
1011
|
+
// defined in MPD, the client is expected to ignore it.
|
|
1012
|
+
if ((emsgSchemeIdUris && emsgSchemeIdUris.includes(schemeId)) ||
|
|
1013
|
+
this.config_.dispatchAllEmsgBoxes) {
|
|
1014
|
+
// See DASH sec. 5.10.4.1
|
|
1015
|
+
// A special scheme in DASH used to signal manifest updates.
|
|
1016
|
+
if (schemeId == 'urn:mpeg:dash:event:2012') {
|
|
1017
|
+
this.playerInterface_.onManifestUpdate();
|
|
1018
|
+
} else {
|
|
1019
|
+
// All other schemes are dispatched as a general 'emsg' event.
|
|
1020
|
+
const endTime = startTime + (eventDuration / timescale);
|
|
1021
|
+
/** @type {shaka.extern.EmsgInfo} */
|
|
1022
|
+
const emsg = {
|
|
1023
|
+
startTime: startTime,
|
|
1024
|
+
endTime: endTime,
|
|
1025
|
+
schemeIdUri: schemeId,
|
|
1026
|
+
value: value,
|
|
1027
|
+
timescale: timescale,
|
|
1028
|
+
presentationTimeDelta: presentationTimeDelta,
|
|
1029
|
+
eventDuration: eventDuration,
|
|
1030
|
+
id: id,
|
|
1031
|
+
messageData: messageData,
|
|
1032
|
+
};
|
|
1033
|
+
|
|
1034
|
+
// Dispatch an event to notify the application about the emsg box.
|
|
1035
|
+
const eventName = shaka.util.FakeEvent.EventName.Emsg;
|
|
1036
|
+
const data = (new Map()).set('detail', emsg);
|
|
1037
|
+
const event = new shaka.util.FakeEvent(eventName, data);
|
|
1038
|
+
// A user can call preventDefault() on a cancelable event.
|
|
1039
|
+
event.cancelable = true;
|
|
1040
|
+
|
|
1041
|
+
this.playerInterface_.onEvent(event);
|
|
1042
|
+
|
|
1043
|
+
if (event.defaultPrevented) {
|
|
1044
|
+
// If the caller uses preventDefault() on the 'emsg' event, don't
|
|
1045
|
+
// process any further, and don't generate an ID3 'metadata' event
|
|
1046
|
+
// for the same data.
|
|
1047
|
+
return;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// Additionally, ID3 events generate a 'metadata' event. This is a
|
|
1051
|
+
// pre-parsed version of the metadata blob already dispatched in the
|
|
1052
|
+
// 'emsg' event.
|
|
1053
|
+
if (schemeId == 'https://aomedia.org/emsg/ID3' ||
|
|
1054
|
+
schemeId == 'https://developer.apple.com/streaming/emsg-id3') {
|
|
1055
|
+
// See https://aomediacodec.github.io/id3-emsg/
|
|
1056
|
+
const frames = shaka.util.Id3Utils.getID3Frames(messageData);
|
|
1057
|
+
if (frames.length) {
|
|
1058
|
+
/** @private {shaka.extern.ID3Metadata} */
|
|
1059
|
+
const metadata = {
|
|
1060
|
+
cueTime: startTime,
|
|
1061
|
+
data: messageData,
|
|
1062
|
+
frames: frames,
|
|
1063
|
+
dts: startTime,
|
|
1064
|
+
pts: startTime,
|
|
1065
|
+
};
|
|
1066
|
+
this.playerInterface_.onMetadata(
|
|
1067
|
+
[metadata], /* offset= */ 0, endTime);
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* Parse PRFT box.
|
|
1076
|
+
* @param {number} timescale
|
|
1077
|
+
* @param {!shaka.extern.ParsedBox} box
|
|
1078
|
+
* @private
|
|
1079
|
+
*/
|
|
1080
|
+
parsePrft_(timescale, box) {
|
|
1081
|
+
goog.asserts.assert(
|
|
1082
|
+
box.version == 0 || box.version == 1,
|
|
1083
|
+
'PRFT version can only be 0 or 1');
|
|
1084
|
+
const parsed = shaka.util.Mp4BoxParsers.parsePRFTInaccurate(
|
|
1085
|
+
box.reader, box.version);
|
|
1086
|
+
|
|
1087
|
+
const wallClockTime = this.convertNtp_(parsed.ntpTimestamp);
|
|
1088
|
+
const programStartDate = new Date(wallClockTime -
|
|
1089
|
+
(parsed.mediaTime / timescale) * 1000);
|
|
1090
|
+
/** @type {shaka.extern.ProducerReferenceTime} */
|
|
1091
|
+
const prftInfo = {
|
|
1092
|
+
wallClockTime,
|
|
1093
|
+
programStartDate,
|
|
1094
|
+
};
|
|
1095
|
+
|
|
1096
|
+
const eventName = shaka.util.FakeEvent.EventName.Prft;
|
|
1097
|
+
const data = (new Map()).set('detail', prftInfo);
|
|
1098
|
+
const event = new shaka.util.FakeEvent(
|
|
1099
|
+
eventName, data);
|
|
1100
|
+
this.playerInterface_.onEvent(event);
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* Convert Ntp ntpTimeStamp to UTC Time
|
|
1106
|
+
*
|
|
1107
|
+
* @param {number} ntpTimeStamp
|
|
1108
|
+
* @return {number} utcTime
|
|
1109
|
+
* @private
|
|
1110
|
+
*/
|
|
1111
|
+
convertNtp_(ntpTimeStamp) {
|
|
1112
|
+
const start = new Date(Date.UTC(1900, 0, 1, 0, 0, 0));
|
|
1113
|
+
return new Date(start.getTime() + ntpTimeStamp).getTime();
|
|
1114
|
+
}
|
|
1115
|
+
|
|
860
1116
|
/**
|
|
861
1117
|
* Enqueue an operation to append data to the SourceBuffer.
|
|
862
1118
|
* Start and end times are needed for TextEngine, but not for MediaSource.
|
|
@@ -918,8 +1174,8 @@ shaka.media.MediaSourceEngine = class {
|
|
|
918
1174
|
mimeType = this.transmuxers_[contentType].getOriginalMimeType();
|
|
919
1175
|
}
|
|
920
1176
|
if (reference) {
|
|
921
|
-
const {timestamp, metadata} = this.
|
|
922
|
-
contentType, data, reference, mimeType);
|
|
1177
|
+
const {timestamp, metadata} = this.getTimestampAndDispatchMetadata(
|
|
1178
|
+
contentType, data, reference, stream, mimeType);
|
|
923
1179
|
if (timestamp != null) {
|
|
924
1180
|
if (this.firstVideoTimestamp_ == null &&
|
|
925
1181
|
contentType == ContentType.VIDEO) {
|
|
@@ -1940,6 +2196,10 @@ shaka.media.MediaSourceEngine = class {
|
|
|
1940
2196
|
this.queues_[contentType] = [];
|
|
1941
2197
|
}
|
|
1942
2198
|
}
|
|
2199
|
+
const audio = streamsByType.get(ContentType.AUDIO);
|
|
2200
|
+
if (audio && audio.isAudioMuxedInVideo) {
|
|
2201
|
+
this.needSplitMuxedContent_ = true;
|
|
2202
|
+
}
|
|
1943
2203
|
|
|
1944
2204
|
// Fake a seek to catchup the playhead.
|
|
1945
2205
|
this.video_.currentTime = currentTime;
|
|
@@ -2215,7 +2475,9 @@ shaka.media.MediaSourceEngine.SourceBufferMode_ = {
|
|
|
2215
2475
|
/**
|
|
2216
2476
|
* @typedef {{
|
|
2217
2477
|
* getKeySystem: function():?string,
|
|
2218
|
-
* onMetadata: function(!Array<shaka.extern.ID3Metadata>, number, ?number)
|
|
2478
|
+
* onMetadata: function(!Array<shaka.extern.ID3Metadata>, number, ?number),
|
|
2479
|
+
* onEvent: function(!Event),
|
|
2480
|
+
* onManifestUpdate: function()
|
|
2219
2481
|
* }}
|
|
2220
2482
|
*
|
|
2221
2483
|
* @summary Player interface
|
|
@@ -2224,5 +2486,9 @@ shaka.media.MediaSourceEngine.SourceBufferMode_ = {
|
|
|
2224
2486
|
* @property {function(
|
|
2225
2487
|
* !Array<shaka.extern.ID3Metadata>, number, ?number)} onMetadata
|
|
2226
2488
|
* Callback to use when metadata arrives.
|
|
2489
|
+
* @property {function(!Event)} onEvent
|
|
2490
|
+
* Called when an event occurs that should be sent to the app.
|
|
2491
|
+
* @property {function()} onManifestUpdate
|
|
2492
|
+
* Called when an embedded 'emsg' box should trigger a manifest update.
|
|
2227
2493
|
*/
|
|
2228
2494
|
shaka.media.MediaSourceEngine.PlayerInterface;
|
package/lib/media/playhead.js
CHANGED
|
@@ -517,7 +517,9 @@ shaka.media.MediaSourcePlayhead = class {
|
|
|
517
517
|
|
|
518
518
|
if (currentTime > end) {
|
|
519
519
|
shaka.log.v1('Playhead past end.');
|
|
520
|
-
|
|
520
|
+
// We remove the safeSeekEndOffset of the seek end to avoid the player
|
|
521
|
+
// to be block at the edge in a live stream
|
|
522
|
+
return end - this.config_.safeSeekEndOffset;
|
|
521
523
|
}
|
|
522
524
|
|
|
523
525
|
if (currentTime < start) {
|
|
@@ -607,9 +607,10 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
|
|
|
607
607
|
* Performs a final filtering of the manifest, and chooses the initial
|
|
608
608
|
* variant.
|
|
609
609
|
*
|
|
610
|
+
* @return {!Promise}
|
|
610
611
|
* @private
|
|
611
612
|
*/
|
|
612
|
-
chooseInitialVariantInner_() {
|
|
613
|
+
async chooseInitialVariantInner_() {
|
|
613
614
|
goog.asserts.assert(
|
|
614
615
|
this.manifest_, 'The manifest should already be parsed.');
|
|
615
616
|
|
|
@@ -630,7 +631,6 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
|
|
|
630
631
|
this.config_.preferredAudioLabel,
|
|
631
632
|
this.config_.preferredVideoLabel,
|
|
632
633
|
this.config_.mediaSource.codecSwitchingStrategy,
|
|
633
|
-
this.config_.manifest.dash.enableAudioGroups,
|
|
634
634
|
/* audioCodec= */ '');
|
|
635
635
|
}
|
|
636
636
|
|
|
@@ -656,13 +656,15 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
|
|
|
656
656
|
this.abrManager_.setVariants(Array.from(adaptationSet.values()));
|
|
657
657
|
const variant = this.abrManager_.chooseVariant();
|
|
658
658
|
if (variant) {
|
|
659
|
+
const promises = [];
|
|
659
660
|
this.prefetchedVariant_ = variant;
|
|
660
661
|
if (variant.video) {
|
|
661
|
-
this.
|
|
662
|
+
promises.push(this.prefetchStream_(variant.video, isLive));
|
|
662
663
|
}
|
|
663
664
|
if (variant.audio) {
|
|
664
|
-
this.
|
|
665
|
+
promises.push(this.prefetchStream_(variant.audio, isLive));
|
|
665
666
|
}
|
|
667
|
+
await Promise.all(promises);
|
|
666
668
|
}
|
|
667
669
|
}
|
|
668
670
|
}
|
|
@@ -673,7 +675,7 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
|
|
|
673
675
|
* @return {!Promise}
|
|
674
676
|
* @private
|
|
675
677
|
*/
|
|
676
|
-
async
|
|
678
|
+
async prefetchStream_(stream, isLive) {
|
|
677
679
|
// Use the prefetch limit from the config if this is set, otherwise use 2.
|
|
678
680
|
const prefetchLimit = this.config_.streaming.segmentPrefetchLimit || 2;
|
|
679
681
|
const prefetch = new shaka.media.SegmentPrefetch(
|
|
@@ -701,13 +703,14 @@ shaka.media.PreloadManager = class extends shaka.util.FakeEventTarget {
|
|
|
701
703
|
if (isLive) {
|
|
702
704
|
// Preload only the init segment for Live
|
|
703
705
|
if (prefetchSegment.initSegmentReference) {
|
|
704
|
-
prefetch.prefetchInitSegment(
|
|
706
|
+
await prefetch.prefetchInitSegment(
|
|
707
|
+
prefetchSegment.initSegmentReference);
|
|
705
708
|
}
|
|
706
709
|
} else {
|
|
707
710
|
// Preload a segment, too... either the first segment, or the segment
|
|
708
711
|
// that corresponds with this.startTime_, as appropriate.
|
|
709
712
|
// Note: this method also preload the init segment
|
|
710
|
-
prefetch.prefetchSegmentsByTime(prefetchSegment.startTime);
|
|
713
|
+
await prefetch.prefetchSegmentsByTime(prefetchSegment.startTime);
|
|
711
714
|
}
|
|
712
715
|
}
|
|
713
716
|
}
|
|
@@ -12,6 +12,7 @@ goog.require('shaka.media.InitSegmentReference');
|
|
|
12
12
|
goog.require('shaka.media.SegmentIterator');
|
|
13
13
|
goog.require('shaka.media.SegmentReference');
|
|
14
14
|
goog.require('shaka.net.NetworkingEngine');
|
|
15
|
+
goog.require('shaka.util.Error');
|
|
15
16
|
goog.require('shaka.util.Uint8ArrayUtils');
|
|
16
17
|
|
|
17
18
|
|
|
@@ -74,6 +75,7 @@ shaka.media.SegmentPrefetch = class {
|
|
|
74
75
|
*
|
|
75
76
|
* @param {number} currTime
|
|
76
77
|
* @param {boolean=} skipFirst
|
|
78
|
+
* @return {!Promise}
|
|
77
79
|
* @public
|
|
78
80
|
*/
|
|
79
81
|
prefetchSegmentsByTime(currTime, skipFirst = false) {
|
|
@@ -83,7 +85,7 @@ shaka.media.SegmentPrefetch = class {
|
|
|
83
85
|
const logPrefix = shaka.media.SegmentPrefetch.logPrefix_(this.stream_);
|
|
84
86
|
if (!this.stream_.segmentIndex) {
|
|
85
87
|
shaka.log.debug(logPrefix, 'missing segmentIndex');
|
|
86
|
-
return;
|
|
88
|
+
return Promise.resolve();
|
|
87
89
|
}
|
|
88
90
|
if (!this.iterator_) {
|
|
89
91
|
this.iterator_ = this.stream_.segmentIndex.getIteratorForTime(
|
|
@@ -91,11 +93,12 @@ shaka.media.SegmentPrefetch = class {
|
|
|
91
93
|
}
|
|
92
94
|
if (!this.iterator_) {
|
|
93
95
|
shaka.log.debug(logPrefix, 'missing iterator');
|
|
94
|
-
return;
|
|
96
|
+
return Promise.resolve();
|
|
95
97
|
}
|
|
96
98
|
if (skipFirst) {
|
|
97
99
|
this.iterator_.next();
|
|
98
100
|
}
|
|
101
|
+
const promises = [];
|
|
99
102
|
while (this.segmentPrefetchMap_.size < this.prefetchLimit_) {
|
|
100
103
|
const reference = this.iterator_.next().value;
|
|
101
104
|
if (!reference) {
|
|
@@ -115,22 +118,26 @@ shaka.media.SegmentPrefetch = class {
|
|
|
115
118
|
prefetchAllowed = false;
|
|
116
119
|
}
|
|
117
120
|
if (prefetchAllowed && reference.initSegmentReference) {
|
|
118
|
-
this.prefetchInitSegment(
|
|
121
|
+
promises.push(this.prefetchInitSegment(
|
|
122
|
+
reference.initSegmentReference));
|
|
119
123
|
}
|
|
120
124
|
if (prefetchAllowed && !this.segmentPrefetchMap_.has(reference)) {
|
|
121
125
|
const segmentPrefetchOperation =
|
|
122
126
|
new shaka.media.SegmentPrefetchOperation(this.fetchDispatcher_);
|
|
123
|
-
segmentPrefetchOperation.dispatchFetch(
|
|
127
|
+
promises.push(segmentPrefetchOperation.dispatchFetch(
|
|
128
|
+
reference, this.stream_));
|
|
124
129
|
this.segmentPrefetchMap_.set(reference, segmentPrefetchOperation);
|
|
125
130
|
}
|
|
126
131
|
}
|
|
127
132
|
this.clearInitSegments_();
|
|
133
|
+
return Promise.all(promises);
|
|
128
134
|
}
|
|
129
135
|
|
|
130
136
|
/**
|
|
131
137
|
* Fetch init segment.
|
|
132
138
|
*
|
|
133
139
|
* @param {!shaka.media.InitSegmentReference} initSegmentReference
|
|
140
|
+
* @return {!Promise}
|
|
134
141
|
*/
|
|
135
142
|
prefetchInitSegment(initSegmentReference) {
|
|
136
143
|
goog.asserts.assert(this.prefetchLimit_ > 0,
|
|
@@ -139,11 +146,11 @@ shaka.media.SegmentPrefetch = class {
|
|
|
139
146
|
const logPrefix = shaka.media.SegmentPrefetch.logPrefix_(this.stream_);
|
|
140
147
|
if (!this.stream_.segmentIndex) {
|
|
141
148
|
shaka.log.debug(logPrefix, 'missing segmentIndex');
|
|
142
|
-
return;
|
|
149
|
+
return Promise.resolve();
|
|
143
150
|
}
|
|
144
151
|
|
|
145
152
|
if (initSegmentReference.getSegmentData()) {
|
|
146
|
-
return;
|
|
153
|
+
return Promise.resolve();
|
|
147
154
|
}
|
|
148
155
|
|
|
149
156
|
// init segments are ignored from the prefetch limit
|
|
@@ -152,14 +159,16 @@ shaka.media.SegmentPrefetch = class {
|
|
|
152
159
|
return shaka.media.InitSegmentReference.equal(
|
|
153
160
|
reference, initSegmentReference);
|
|
154
161
|
});
|
|
155
|
-
if (
|
|
156
|
-
|
|
157
|
-
new shaka.media.SegmentPrefetchOperation(this.fetchDispatcher_);
|
|
158
|
-
segmentPrefetchOperation.dispatchFetch(
|
|
159
|
-
initSegmentReference, this.stream_);
|
|
160
|
-
this.initSegmentPrefetchMap_.set(
|
|
161
|
-
initSegmentReference, segmentPrefetchOperation);
|
|
162
|
+
if (someReference) {
|
|
163
|
+
return Promise.resolve();
|
|
162
164
|
}
|
|
165
|
+
const segmentPrefetchOperation = new shaka.media.SegmentPrefetchOperation(
|
|
166
|
+
this.fetchDispatcher_);
|
|
167
|
+
const promise = segmentPrefetchOperation.dispatchFetch(
|
|
168
|
+
initSegmentReference, this.stream_);
|
|
169
|
+
this.initSegmentPrefetchMap_.set(
|
|
170
|
+
initSegmentReference, segmentPrefetchOperation);
|
|
171
|
+
return promise;
|
|
163
172
|
}
|
|
164
173
|
|
|
165
174
|
/**
|
|
@@ -415,6 +424,7 @@ shaka.media.SegmentPrefetchOperation = class {
|
|
|
415
424
|
* @param {!(shaka.media.SegmentReference|shaka.media.InitSegmentReference)}
|
|
416
425
|
* reference
|
|
417
426
|
* @param {!shaka.extern.Stream} stream
|
|
427
|
+
* @return {!Promise}
|
|
418
428
|
* @public
|
|
419
429
|
*/
|
|
420
430
|
dispatchFetch(reference, stream) {
|
|
@@ -433,6 +443,15 @@ shaka.media.SegmentPrefetchOperation = class {
|
|
|
433
443
|
buffered = new Uint8Array(0);
|
|
434
444
|
}
|
|
435
445
|
});
|
|
446
|
+
return this.operation_.promise.catch((e) => {
|
|
447
|
+
// Ignore OPERATION_ABORTED errors.
|
|
448
|
+
if (e instanceof shaka.util.Error &&
|
|
449
|
+
e.code == shaka.util.Error.Code.OPERATION_ABORTED) {
|
|
450
|
+
return Promise.resolve();
|
|
451
|
+
}
|
|
452
|
+
// Continue to surface other errors.
|
|
453
|
+
return Promise.reject(e);
|
|
454
|
+
});
|
|
436
455
|
}
|
|
437
456
|
|
|
438
457
|
/**
|
|
@@ -291,7 +291,7 @@ shaka.media.SegmentReference = class {
|
|
|
291
291
|
/** @type {?shaka.extern.aesKey} */
|
|
292
292
|
this.aesKey = aesKey;
|
|
293
293
|
|
|
294
|
-
/** @type {?shaka.
|
|
294
|
+
/** @type {?shaka.extern.ThumbnailSprite} */
|
|
295
295
|
this.thumbnailSprite = null;
|
|
296
296
|
|
|
297
297
|
/** @type {number} */
|
|
@@ -550,7 +550,7 @@ shaka.media.SegmentReference = class {
|
|
|
550
550
|
/**
|
|
551
551
|
* Set the segment's thumbnail sprite.
|
|
552
552
|
*
|
|
553
|
-
* @param {shaka.
|
|
553
|
+
* @param {shaka.extern.ThumbnailSprite} thumbnailSprite
|
|
554
554
|
* @export
|
|
555
555
|
*/
|
|
556
556
|
setThumbnailSprite(thumbnailSprite) {
|
|
@@ -560,7 +560,7 @@ shaka.media.SegmentReference = class {
|
|
|
560
560
|
/**
|
|
561
561
|
* Returns the segment's thumbnail sprite.
|
|
562
562
|
*
|
|
563
|
-
* @return {?shaka.
|
|
563
|
+
* @return {?shaka.extern.ThumbnailSprite}
|
|
564
564
|
* @export
|
|
565
565
|
*/
|
|
566
566
|
getThumbnailSprite() {
|