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.
Files changed (71) hide show
  1. package/README.md +47 -0
  2. package/dist/deps.js +8 -8
  3. package/dist/locales.js +35 -35
  4. package/dist/shaka-player.compiled.d.ts +36 -13
  5. package/dist/shaka-player.compiled.debug.d.ts +36 -13
  6. package/dist/shaka-player.compiled.debug.externs.js +46 -11
  7. package/dist/shaka-player.compiled.debug.js +543 -517
  8. package/dist/shaka-player.compiled.debug.map +4 -4
  9. package/dist/shaka-player.compiled.externs.js +46 -11
  10. package/dist/shaka-player.compiled.js +1348 -1331
  11. package/dist/shaka-player.compiled.map +4 -4
  12. package/dist/shaka-player.ui.d.ts +37 -14
  13. package/dist/shaka-player.ui.debug.d.ts +37 -14
  14. package/dist/shaka-player.ui.debug.externs.js +46 -11
  15. package/dist/shaka-player.ui.debug.js +629 -598
  16. package/dist/shaka-player.ui.debug.map +4 -4
  17. package/dist/shaka-player.ui.externs.js +46 -11
  18. package/dist/shaka-player.ui.js +1624 -1603
  19. package/dist/shaka-player.ui.map +4 -4
  20. package/externs/shaka/ads.js +9 -0
  21. package/externs/shaka/manifest.js +26 -1
  22. package/externs/shaka/offline.js +8 -2
  23. package/externs/shaka/player.js +80 -28
  24. package/externs/shaka/text.js +9 -0
  25. package/lib/ads/ad_manager.js +6 -0
  26. package/lib/ads/ad_utils.js +1 -0
  27. package/lib/ads/client_side_ad.js +8 -0
  28. package/lib/ads/interstitial_ad.js +25 -4
  29. package/lib/ads/interstitial_ad_manager.js +67 -8
  30. package/lib/ads/media_tailor_ad.js +8 -0
  31. package/lib/ads/server_side_ad.js +8 -0
  32. package/lib/dash/content_protection.js +31 -0
  33. package/lib/dash/dash_parser.js +22 -12
  34. package/lib/hls/hls_parser.js +63 -49
  35. package/lib/media/adaptation_set.js +11 -17
  36. package/lib/media/adaptation_set_criteria.js +4 -9
  37. package/lib/media/drm_engine.js +3 -1
  38. package/lib/media/manifest_filterer.js +2 -1
  39. package/lib/media/media_source_engine.js +286 -20
  40. package/lib/media/playhead.js +3 -1
  41. package/lib/media/preload_manager.js +10 -7
  42. package/lib/media/segment_prefetch.js +32 -13
  43. package/lib/media/segment_reference.js +3 -3
  44. package/lib/media/streaming_engine.js +87 -261
  45. package/lib/mss/mss_parser.js +1 -0
  46. package/lib/offline/download_info.js +5 -1
  47. package/lib/offline/indexeddb/storage_mechanism.js +4 -3
  48. package/lib/offline/indexeddb/v1_storage_cell.js +2 -0
  49. package/lib/offline/indexeddb/v2_storage_cell.js +2 -0
  50. package/lib/offline/manifest_converter.js +4 -0
  51. package/lib/offline/storage.js +277 -5
  52. package/lib/player.js +224 -78
  53. package/lib/text/simple_text_displayer.js +7 -0
  54. package/lib/text/stub_text_displayer.js +7 -0
  55. package/lib/text/ui_text_displayer.js +12 -0
  56. package/lib/util/dom_utils.js +14 -0
  57. package/lib/util/error.js +5 -0
  58. package/lib/util/periods.js +2 -0
  59. package/lib/util/platform.js +15 -0
  60. package/lib/util/player_configuration.js +11 -4
  61. package/lib/util/stream_utils.js +20 -1
  62. package/package.json +1 -1
  63. package/ui/ad_statistics_button.js +12 -0
  64. package/ui/controls.js +31 -7
  65. package/ui/externs/ui.js +8 -1
  66. package/ui/fullscreen_button.js +9 -0
  67. package/ui/locales/be.json +49 -0
  68. package/ui/mute_button.js +42 -11
  69. package/ui/skip_ad_button.js +16 -11
  70. package/ui/statistics_button.js +12 -0
  71. 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.video_.disableRemotePlayback = true;
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 = this.url_;
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_.removeAttribute('src');
485
+ this.video_.removeChild(this.source_);
419
486
  this.video_.load();
420
- this.video_ = null;
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 {?shaka.media.SegmentReference} reference The segment reference
786
- * we are appending, or null for init segments
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
- getTimestampAndDispatchMetadata_(contentType, data, reference, mimeType) {
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 && reference.timestampOffset == 0 &&
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.getTimestampAndDispatchMetadata_(
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;
@@ -517,7 +517,9 @@ shaka.media.MediaSourcePlayhead = class {
517
517
 
518
518
  if (currentTime > end) {
519
519
  shaka.log.v1('Playhead past end.');
520
- return end;
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.makePrefetchForStream_(variant.video, isLive);
662
+ promises.push(this.prefetchStream_(variant.video, isLive));
662
663
  }
663
664
  if (variant.audio) {
664
- this.makePrefetchForStream_(variant.audio, isLive);
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 makePrefetchForStream_(stream, isLive) {
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(prefetchSegment.initSegmentReference);
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(reference.initSegmentReference);
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(reference, this.stream_);
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 (!someReference) {
156
- const segmentPrefetchOperation =
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.media.SegmentReference.ThumbnailSprite} */
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.media.SegmentReference.ThumbnailSprite} thumbnailSprite
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.media.SegmentReference.ThumbnailSprite}
563
+ * @return {?shaka.extern.ThumbnailSprite}
564
564
  * @export
565
565
  */
566
566
  getThumbnailSprite() {