shaka-player 4.7.3 → 4.7.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +3 -1
  2. package/dist/locales.js +32 -32
  3. package/dist/shaka-player.compiled.d.ts +4 -0
  4. package/dist/shaka-player.compiled.debug.d.ts +4 -0
  5. package/dist/shaka-player.compiled.debug.externs.js +5 -0
  6. package/dist/shaka-player.compiled.debug.js +121 -119
  7. package/dist/shaka-player.compiled.debug.map +3 -3
  8. package/dist/shaka-player.compiled.externs.js +5 -0
  9. package/dist/shaka-player.compiled.js +689 -687
  10. package/dist/shaka-player.compiled.map +3 -3
  11. package/dist/shaka-player.ui.d.ts +4 -0
  12. package/dist/shaka-player.ui.debug.d.ts +4 -0
  13. package/dist/shaka-player.ui.debug.externs.js +5 -0
  14. package/dist/shaka-player.ui.debug.js +145 -142
  15. package/dist/shaka-player.ui.debug.map +3 -3
  16. package/dist/shaka-player.ui.externs.js +5 -0
  17. package/dist/shaka-player.ui.js +831 -829
  18. package/dist/shaka-player.ui.map +3 -3
  19. package/lib/ads/client_side_ad.js +4 -2
  20. package/lib/ads/client_side_ad_manager.js +5 -3
  21. package/lib/cast/cast_proxy.js +18 -0
  22. package/lib/cea/cea608_memory.js +1 -1
  23. package/lib/cea/cea_decoder.js +1 -1
  24. package/lib/hls/hls_parser.js +16 -0
  25. package/lib/hls/hls_utils.js +5 -5
  26. package/lib/media/media_source_engine.js +4 -4
  27. package/lib/media/play_rate_controller.js +1 -0
  28. package/lib/media/presentation_timeline.js +11 -0
  29. package/lib/offline/session_deleter.js +0 -8
  30. package/lib/player.js +41 -6
  31. package/lib/polyfill/patchedmediakeys_apple.js +0 -6
  32. package/lib/text/ttml_text_parser.js +5 -0
  33. package/lib/text/vtt_text_parser.js +5 -0
  34. package/lib/util/language_utils.js +7 -8
  35. package/lib/util/periods.js +3 -1
  36. package/package.json +1 -1
  37. package/ui/play_button.js +17 -0
  38. package/ui/playback_rate_selection.js +4 -0
  39. package/ui/small_play_button.js +2 -2
@@ -155,6 +155,7 @@ shaka.ads.ClientSideAd = class {
155
155
  * @export
156
156
  */
157
157
  setVolume(volume) {
158
+ this.video_.volume = volume;
158
159
  return this.manager_.setVolume(volume);
159
160
  }
160
161
 
@@ -197,13 +198,14 @@ shaka.ads.ClientSideAd = class {
197
198
  * @export
198
199
  */
199
200
  setMuted(muted) {
201
+ this.video_.muted = muted;
200
202
  // Emulate the "mute" functionality, where current, pre-mute
201
203
  // volume is saved and can be restored on unmute.
202
204
  if (muted) {
203
205
  this.volume_ = this.getVolume();
204
- this.setVolume(0);
206
+ this.manager_.setVolume(0);
205
207
  } else {
206
- this.setVolume(this.volume_);
208
+ this.manager_.setVolume(this.volume_);
207
209
  }
208
210
  }
209
211
 
@@ -234,10 +234,12 @@ shaka.ads.ClientSideAdManager = class {
234
234
  videoPlayHead.currentTime = this.video_.currentTime;
235
235
  });
236
236
  this.eventManager_.listen(this.video_, 'volumechange', () => {
237
+ if (!this.ad_) {
238
+ return;
239
+ }
240
+ this.ad_.setVolume(this.video_.volume);
237
241
  if (this.video_.muted) {
238
- this.imaAdsManager_.setVolume(0);
239
- } else {
240
- this.imaAdsManager_.setVolume(this.video_.volume);
242
+ this.ad_.setMuted(true);
241
243
  }
242
244
  });
243
245
  }
@@ -496,6 +496,16 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
496
496
  startTime = this.sender_.get('video', 'currentTime');
497
497
  }
498
498
 
499
+ let activeTextTrack;
500
+ const textTracks = this.sender_.get('player', 'getTextTracks')();
501
+
502
+ if (textTracks && textTracks.length) {
503
+ activeTextTrack = textTracks.find((t) => t.active);
504
+ }
505
+
506
+ const isTextTrackVisible =
507
+ this.sender_.get('player', 'isTextTrackVisible')();
508
+
499
509
  // Now load the manifest, if present.
500
510
  if (assetUri) {
501
511
  // Don't autoplay the content until we finish setting up initial state.
@@ -527,6 +537,14 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
527
537
  /** @type {Object} */(this.localPlayer_)[setter](value);
528
538
  }
529
539
 
540
+ this.localPlayer_.setTextTrackVisibility(isTextTrackVisible);
541
+ if (activeTextTrack) {
542
+ this.localPlayer_.selectTextLanguage(
543
+ activeTextTrack.language,
544
+ activeTextTrack.roles,
545
+ activeTextTrack.forced);
546
+ }
547
+
530
548
  // Restore the original autoplay setting.
531
549
  this.localVideo_.autoplay = autoplay;
532
550
  if (assetUri) {
@@ -79,7 +79,7 @@ shaka.cea.Cea608Memory = class {
79
79
  * @return {?shaka.extern.ICaptionDecoder.ClosedCaption}
80
80
  */
81
81
  forceEmit(startTime, endTime) {
82
- const stream = `CC${(this.fieldNum_<< 1) | this.channelNum_ +1}`;
82
+ const stream = `CC${((this.fieldNum_<< 1) | this.channelNum_) + 1}`;
83
83
  const topLevelCue = new shaka.text.Cue(
84
84
  startTime, endTime, /* payload= */ '');
85
85
  topLevelCue.lineInterpretation =
@@ -283,7 +283,7 @@ shaka.cea.CeaDecoder = class {
283
283
  // Get the correct stream for this caption packet (CC1, ..., CC4)
284
284
  const selectedChannel = fieldNum ?
285
285
  this.currentField2Channel_ : this.currentField1Channel_;
286
- const selectedMode = `CC${(fieldNum << 1) | selectedChannel + 1}`;
286
+ const selectedMode = `CC${((fieldNum << 1) | selectedChannel) + 1}`;
287
287
  const selectedStream = this.cea608ModeToStream_.get(selectedMode);
288
288
 
289
289
  // Check for bad frames (bad pairs). This can be two 0xff, two 0x00, or any
@@ -979,6 +979,22 @@ shaka.hls.HlsParser = class {
979
979
  this.presentationTimeline_.setSegmentAvailabilityDuration(
980
980
  segmentAvailabilityDuration);
981
981
  }
982
+
983
+ if (!this.presentationTimeline_.isStartTimeLocked()) {
984
+ const streamInfos = Array.from(this.uriToStreamInfosMap_.values());
985
+ const activeStreamInfos =
986
+ streamInfos.filter((s) => s.stream.segmentIndex);
987
+ for (const streamInfo of activeStreamInfos) {
988
+ if (streamInfo.type != 'audio' && streamInfo.type != 'video') {
989
+ continue;
990
+ }
991
+ const firstReference = streamInfo.stream.segmentIndex.get(0);
992
+ if (firstReference && firstReference.syncTime) {
993
+ const syncTime = firstReference.syncTime;
994
+ this.presentationTimeline_.setPresentationStartTime(syncTime);
995
+ }
996
+ }
997
+ }
982
998
  } else {
983
999
  // Use the minimum duration as the presentation duration.
984
1000
  this.presentationTimeline_.setDuration(this.getMinDuration_());
@@ -47,12 +47,12 @@ shaka.hls.Utils = class {
47
47
  * @return {?shaka.hls.Tag}
48
48
  */
49
49
  static getFirstTagWithName(tags, name) {
50
- const tagsWithName = shaka.hls.Utils.filterTagsByName(tags, name);
51
- if (!tagsWithName.length) {
52
- return null;
50
+ for (const tag of tags) {
51
+ if (tag.name === name) {
52
+ return tag;
53
+ }
53
54
  }
54
-
55
- return tagsWithName[0];
55
+ return null;
56
56
  }
57
57
 
58
58
  /**
@@ -168,9 +168,6 @@ shaka.media.MediaSourceEngine = class {
168
168
 
169
169
  const mediaSource = new ManagedMediaSource();
170
170
 
171
- this.eventManager_.listenOnce(
172
- mediaSource, 'sourceopen', () => this.onSourceOpen_(p));
173
-
174
171
  this.eventManager_.listen(
175
172
  mediaSource, 'startstreaming', () => {
176
173
  this.streamingAllowed_ = true;
@@ -181,6 +178,9 @@ shaka.media.MediaSourceEngine = class {
181
178
  this.streamingAllowed_ = false;
182
179
  });
183
180
 
181
+ this.eventManager_.listenOnce(
182
+ mediaSource, 'sourceopen', () => this.onSourceOpen_(p));
183
+
184
184
  // Correctly set when playback has begun.
185
185
  this.eventManager_.listenOnce(this.video_, 'playing', () => {
186
186
  this.playbackHasBegun_ = true;
@@ -1805,7 +1805,7 @@ shaka.media.MediaSourceEngine = class {
1805
1805
 
1806
1806
  this.destroyer_.ensureNotDestroyed();
1807
1807
 
1808
- this.eventManager_.listenOnce(this.video_, 'canplay', () => {
1808
+ this.eventManager_.listenOnce(this.video_, 'canplaythrough', () => {
1809
1809
  this.destroyer_.ensureNotDestroyed();
1810
1810
 
1811
1811
  this.video_.autoplay = previousAutoPlayState;
@@ -41,6 +41,7 @@ shaka.media.PlayRateController = class {
41
41
 
42
42
  /** @override */
43
43
  release() {
44
+ this.set(this.getDefaultRate());
44
45
  if (this.timer_) {
45
46
  this.timer_.stop();
46
47
  this.timer_ = null;
@@ -328,6 +328,17 @@ shaka.media.PresentationTimeline = class {
328
328
  }
329
329
 
330
330
 
331
+ /**
332
+ * Returns if the presentation timeline's start time is locked.
333
+ *
334
+ * @return {boolean}
335
+ * @export
336
+ */
337
+ isStartTimeLocked() {
338
+ return this.startTimeLocked_;
339
+ }
340
+
341
+
331
342
  /**
332
343
  * Gives PresentationTimeline a Stream's minimum segment start time.
333
344
  *
@@ -71,14 +71,6 @@ shaka.offline.SessionDeleter = class {
71
71
  return [];
72
72
  }
73
73
 
74
- try {
75
- await drmEngine.setServerCertificate();
76
- } catch (e) {
77
- shaka.log.warning('Error setting server certificate', e);
78
- await drmEngine.destroy();
79
- return [];
80
- }
81
-
82
74
  /** @type {!Array.<string>} */
83
75
  const sessionIds = [];
84
76
  await Promise.all(bucket.sessionIds.map(async (sessionId) => {
package/lib/player.js CHANGED
@@ -550,6 +550,12 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
550
550
  */
551
551
  this.loadEventManager_ = new shaka.util.EventManager();
552
552
 
553
+ /**
554
+ * For listeners scoped to the lifetime of the loaded content.
555
+ * @private {shaka.util.EventManager}
556
+ */
557
+ this.trickPlayEventManager_ = new shaka.util.EventManager();
558
+
553
559
  /** @private {shaka.net.NetworkingEngine} */
554
560
  this.networkingEngine_ = null;
555
561
 
@@ -848,6 +854,10 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
848
854
  this.loadEventManager_.release();
849
855
  this.loadEventManager_ = null;
850
856
  }
857
+ if (this.trickPlayEventManager_) {
858
+ this.trickPlayEventManager_.release();
859
+ this.trickPlayEventManager_ = null;
860
+ }
851
861
 
852
862
  this.abrManagerFactory_ = null;
853
863
  this.config_ = null;
@@ -1166,6 +1176,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1166
1176
  // element.
1167
1177
  if (this.video_) {
1168
1178
  this.loadEventManager_.removeAll();
1179
+ this.trickPlayEventManager_.removeAll();
1169
1180
  }
1170
1181
 
1171
1182
  // Stop the variant checker timer
@@ -3569,6 +3580,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3569
3580
  shaka.log.alwaysWarn('A trick play rate of 0 is unsupported!');
3570
3581
  return;
3571
3582
  }
3583
+ this.trickPlayEventManager_.removeAll();
3572
3584
 
3573
3585
  if (this.video_.paused) {
3574
3586
  // Our fast forward is implemented with playbackRate and needs the video
@@ -3582,6 +3594,22 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3582
3594
  this.abrManager_.playbackRateChanged(rate);
3583
3595
  this.streamingEngine_.setTrickPlay(Math.abs(rate) > 1);
3584
3596
  }
3597
+ if (this.isLive()) {
3598
+ this.trickPlayEventManager_.listen(this.video_, 'timeupdate', () => {
3599
+ const currentTime = this.video_.currentTime;
3600
+ const seekRange = this.seekRange();
3601
+ const safeSeekOffset = this.config_.streaming.safeSeekOffset;
3602
+ if (rate > 0) {
3603
+ if (currentTime >= seekRange.end) {
3604
+ this.cancelTrickPlay();
3605
+ }
3606
+ } else {
3607
+ if (currentTime <= (seekRange.start + safeSeekOffset)) {
3608
+ this.cancelTrickPlay();
3609
+ }
3610
+ }
3611
+ });
3612
+ }
3585
3613
  }
3586
3614
 
3587
3615
  /**
@@ -3601,6 +3629,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3601
3629
  this.abrManager_.playbackRateChanged(defaultPlaybackRate);
3602
3630
  this.streamingEngine_.setTrickPlay(false);
3603
3631
  }
3632
+ this.trickPlayEventManager_.removeAll();
3604
3633
  }
3605
3634
 
3606
3635
  /**
@@ -4837,12 +4866,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4837
4866
 
4838
4867
  const references = [];
4839
4868
  for (const cue of cues) {
4840
- const imageUri = shaka.util.ManifestParserUtils.resolveUris(
4841
- [uri], [cue.payload])[0];
4869
+ let uris = null;
4870
+ const getUris = () => {
4871
+ if (uris == null) {
4872
+ uris = shaka.util.ManifestParserUtils.resolveUris(
4873
+ [uri], [cue.payload]);
4874
+ }
4875
+ return uris || [];
4876
+ };
4842
4877
  const reference = new shaka.media.SegmentReference(
4843
4878
  cue.startTime,
4844
4879
  cue.endTime,
4845
- () => [imageUri],
4880
+ getUris,
4846
4881
  /* startByte= */ 0,
4847
4882
  /* endByte= */ null,
4848
4883
  /* initSegmentReference= */ null,
@@ -4850,8 +4885,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4850
4885
  /* appendWindowStart= */ 0,
4851
4886
  /* appendWindowEnd= */ Infinity,
4852
4887
  );
4853
- if (imageUri.includes('#xywh')) {
4854
- const spriteInfo = imageUri.split('#xywh=')[1].split(',');
4888
+ if (cue.payload.includes('#xywh')) {
4889
+ const spriteInfo = cue.payload.split('#xywh=')[1].split(',');
4855
4890
  if (spriteInfo.length === 4) {
4856
4891
  reference.setThumbnailSprite({
4857
4892
  height: parseInt(spriteInfo[3], 10),
@@ -6865,7 +6900,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6865
6900
  * @export
6866
6901
  */
6867
6902
  // eslint-disable-next-line no-useless-concat
6868
- shaka.Player.version = 'v4.7.3' + '-uncompiled'; // x-release-please-version
6903
+ shaka.Player.version = 'v4.7.5' + '-uncompiled'; // x-release-please-version
6869
6904
 
6870
6905
  // Initialize the deprecation system using the version string we just set
6871
6906
  // on the player.
@@ -791,9 +791,3 @@ shaka.polyfill.PatchedMediaKeysApple.MediaKeyStatusMap = class {
791
791
  goog.asserts.assert(false, 'Not used! Provided only for the compiler.');
792
792
  }
793
793
  };
794
-
795
- if (!navigator.requestMediaKeySystemAccess ||
796
- // eslint-disable-next-line no-restricted-syntax
797
- !MediaKeySystemAccess.prototype.getConfiguration) {
798
- shaka.polyfill.register(shaka.polyfill.PatchedMediaKeysApple.install);
799
- }
@@ -491,6 +491,11 @@ shaka.text.TtmlTextParser = class {
491
491
  region.viewportAnchorX = region.viewportAnchorX * 100 /
492
492
  globalWidth;
493
493
  }
494
+ } else if (!extent) {
495
+ region.width = 100 - region.viewportAnchorX;
496
+ region.widthUnits = shaka.text.CueRegion.units.PERCENTAGE;
497
+ region.height = 100 - region.viewportAnchorY;
498
+ region.heightUnits = shaka.text.CueRegion.units.PERCENTAGE;
494
499
  }
495
500
 
496
501
  region.viewportAnchorUnits = percentage || globalWidth != null ?
@@ -451,6 +451,11 @@ shaka.text.VttTextParser = class {
451
451
  */
452
452
  static parseCueStyles(payload, rootCue, styles) {
453
453
  const VttTextParser = shaka.text.VttTextParser;
454
+ // Optimization for unstyled payloads.
455
+ if (!payload.includes('<')) {
456
+ rootCue.payload = VttTextParser.htmlUnescape_(payload);
457
+ return;
458
+ }
454
459
  if (styles.size === 0) {
455
460
  VttTextParser.addDefaultTextColor_(styles);
456
461
  }
@@ -192,25 +192,24 @@ shaka.util.LanguageUtils = class {
192
192
  const LanguageUtils = shaka.util.LanguageUtils;
193
193
 
194
194
  const privateusePrefix = 'x-';
195
- const components = locale.split(`-${privateusePrefix}`);
196
- const languageAndRegion = components[0].split('-');
195
+ const [languageRegion = '', privateuseSuffix = ''] =
196
+ locale.split(`-${privateusePrefix}`);
197
+ const [languageCode = '', regionCode = ''] = languageRegion.split('-');
197
198
 
198
199
  // We are only going to use the language, the region and the "privateuse" part (as per https://datatracker.ietf.org/doc/html/rfc5646).
199
200
  // Anything else is thrown away.
200
- let language = languageAndRegion[0] || '';
201
- let region = languageAndRegion[1] || '';
202
- const privateuse = components[1] ?
203
- `${privateusePrefix}${components[1]}` : '';
201
+ const privateuse = privateuseSuffix ?
202
+ `${privateusePrefix}${privateuseSuffix}` : '';
204
203
 
205
204
  // Convert the language to lower case. It is standard for the language code
206
205
  // to be in lower case, but it will also make the map look-up easier.
207
- language = language.toLowerCase();
206
+ let language = languageCode.toLowerCase();
208
207
  language = LanguageUtils.isoMap_.get(language) || language;
209
208
 
210
209
  // Convert the region to upper case. It is standard for the region to be in
211
210
  // upper case. If there is no upper code, then it will be an empty string
212
211
  // and this will be a no-op.
213
- region = region.toUpperCase();
212
+ const region = regionCode.toUpperCase();
214
213
 
215
214
  return `${region ? `${language}-${region}` : language}${
216
215
  privateuse ? `-${privateuse}` : ''}`;
@@ -724,7 +724,9 @@ shaka.util.PeriodCombiner = class {
724
724
  // Add the codec of this stream to the output stream's codecs.
725
725
  const codecs = new Set(outputStream.codecs.split(','));
726
726
  for (const codec of match.codecs.split(',')) {
727
- codecs.add(codec);
727
+ if (codec) {
728
+ codecs.add(codec);
729
+ }
728
730
  }
729
731
  const PeriodCombiner = shaka.util.PeriodCombiner;
730
732
  outputStream.codecs = PeriodCombiner.filterDuplicateCodecs_(
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "4.7.3",
4
+ "version": "4.7.5",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
package/ui/play_button.js CHANGED
@@ -72,6 +72,11 @@ shaka.ui.PlayButton = class extends shaka.ui.Element {
72
72
  this.updateIcon();
73
73
  });
74
74
 
75
+ this.eventManager.listen(this.adManager, AdManager.AD_STOPPED, () => {
76
+ this.updateAriaLabel();
77
+ this.updateIcon();
78
+ });
79
+
75
80
  this.eventManager.listen(this.button, 'click', () => {
76
81
  if (this.ad && this.ad.isLinear()) {
77
82
  this.controls.playPauseAd();
@@ -99,6 +104,18 @@ shaka.ui.PlayButton = class extends shaka.ui.Element {
99
104
  return this.controls.presentationIsPaused();
100
105
  }
101
106
 
107
+ /**
108
+ * @return {boolean}
109
+ * @protected
110
+ */
111
+ isEnded() {
112
+ if (this.ad && this.ad.isLinear()) {
113
+ return false;
114
+ }
115
+
116
+ return this.video.ended;
117
+ }
118
+
102
119
  /**
103
120
  * Called when the button's aria label needs to change.
104
121
  * To be overridden by subclasses.
@@ -44,6 +44,10 @@ shaka.ui.PlaybackRateSelection = class extends shaka.ui.SettingsMenu {
44
44
  this.updateLocalizedStrings_();
45
45
  });
46
46
 
47
+ this.eventManager.listen(this.player, 'loaded', () => {
48
+ this.updatePlaybackRateSelection_(this.player.getPlaybackRate());
49
+ });
50
+
47
51
  this.eventManager.listen(this.player, 'ratechange', () => {
48
52
  this.updatePlaybackRateSelection_(this.player.getPlaybackRate());
49
53
  });
@@ -38,7 +38,7 @@ shaka.ui.SmallPlayButton = class extends shaka.ui.PlayButton {
38
38
  /** @override */
39
39
  updateIcon() {
40
40
  const Icons = shaka.ui.Enums.MaterialDesignIcons;
41
- if (this.video.ended) {
41
+ if (this.isEnded()) {
42
42
  this.button.textContent = Icons.REPLAY;
43
43
  } else {
44
44
  this.button.textContent = this.isPaused() ? Icons.PLAY : Icons.PAUSE;
@@ -48,7 +48,7 @@ shaka.ui.SmallPlayButton = class extends shaka.ui.PlayButton {
48
48
  /** @override */
49
49
  updateAriaLabel() {
50
50
  const LocIds = shaka.ui.Locales.Ids;
51
- if (this.video.ended) {
51
+ if (this.isEnded()) {
52
52
  this.button.ariaLabel = this.localization.resolve(LocIds.REPLAY);
53
53
  } else {
54
54
  const label = this.isPaused() ? LocIds.PLAY : LocIds.PAUSE;