shaka-player 4.6.7 → 4.6.8

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 (37) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +3 -1
  3. package/dist/locales.js +28 -28
  4. package/dist/shaka-player.compiled.d.ts +4 -0
  5. package/dist/shaka-player.compiled.debug.d.ts +4 -0
  6. package/dist/shaka-player.compiled.debug.externs.js +5 -0
  7. package/dist/shaka-player.compiled.debug.js +72 -71
  8. package/dist/shaka-player.compiled.debug.map +3 -3
  9. package/dist/shaka-player.compiled.externs.js +5 -0
  10. package/dist/shaka-player.compiled.js +690 -690
  11. package/dist/shaka-player.compiled.map +2 -2
  12. package/dist/shaka-player.ui.d.ts +4 -0
  13. package/dist/shaka-player.ui.debug.d.ts +4 -0
  14. package/dist/shaka-player.ui.debug.externs.js +5 -0
  15. package/dist/shaka-player.ui.debug.js +92 -89
  16. package/dist/shaka-player.ui.debug.map +3 -3
  17. package/dist/shaka-player.ui.externs.js +5 -0
  18. package/dist/shaka-player.ui.js +786 -786
  19. package/dist/shaka-player.ui.map +2 -2
  20. package/lib/ads/client_side_ad.js +4 -2
  21. package/lib/ads/client_side_ad_manager.js +5 -3
  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/media/media_source_engine.js +3 -3
  26. package/lib/media/presentation_timeline.js +11 -0
  27. package/lib/offline/session_deleter.js +0 -8
  28. package/lib/player.js +12 -6
  29. package/lib/polyfill/patchedmediakeys_apple.js +0 -6
  30. package/lib/text/vtt_text_parser.js +5 -0
  31. package/package.json +1 -1
  32. package/test/cea/cea_decoder_unit.js +36 -0
  33. package/test/media/media_source_engine_unit.js +24 -1
  34. package/test/player_load_graph_integration.js +5 -0
  35. package/test/player_unit.js +17 -0
  36. package/ui/play_button.js +17 -0
  37. 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
  }
@@ -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_());
@@ -165,9 +165,6 @@ shaka.media.MediaSourceEngine = class {
165
165
 
166
166
  const mediaSource = new ManagedMediaSource();
167
167
 
168
- this.eventManager_.listenOnce(
169
- mediaSource, 'sourceopen', () => this.onSourceOpen_(p));
170
-
171
168
  this.eventManager_.listen(
172
169
  mediaSource, 'startstreaming', () => {
173
170
  this.streamingAllowed_ = true;
@@ -178,6 +175,9 @@ shaka.media.MediaSourceEngine = class {
178
175
  this.streamingAllowed_ = false;
179
176
  });
180
177
 
178
+ this.eventManager_.listenOnce(
179
+ mediaSource, 'sourceopen', () => this.onSourceOpen_(p));
180
+
181
181
  // Correctly set when playback has begun.
182
182
  this.eventManager_.listenOnce(this.video_, 'playing', () => {
183
183
  this.playbackHasBegun_ = true;
@@ -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
@@ -4768,12 +4768,18 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4768
4768
 
4769
4769
  const references = [];
4770
4770
  for (const cue of cues) {
4771
- const imageUri = shaka.util.ManifestParserUtils.resolveUris(
4772
- [uri], [cue.payload])[0];
4771
+ let uris = null;
4772
+ const getUris = () => {
4773
+ if (uris == null) {
4774
+ uris = shaka.util.ManifestParserUtils.resolveUris(
4775
+ [uri], [cue.payload]);
4776
+ }
4777
+ return uris || [];
4778
+ };
4773
4779
  const reference = new shaka.media.SegmentReference(
4774
4780
  cue.startTime,
4775
4781
  cue.endTime,
4776
- () => [imageUri],
4782
+ getUris,
4777
4783
  /* startByte= */ 0,
4778
4784
  /* endByte= */ null,
4779
4785
  /* initSegmentReference= */ null,
@@ -4781,8 +4787,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
4781
4787
  /* appendWindowStart= */ 0,
4782
4788
  /* appendWindowEnd= */ Infinity,
4783
4789
  );
4784
- if (imageUri.includes('#xywh')) {
4785
- const spriteInfo = imageUri.split('#xywh=')[1].split(',');
4790
+ if (cue.payload.includes('#xywh')) {
4791
+ const spriteInfo = cue.payload.split('#xywh=')[1].split(',');
4786
4792
  if (spriteInfo.length === 4) {
4787
4793
  reference.setThumbnailSprite({
4788
4794
  height: parseInt(spriteInfo[3], 10),
@@ -6779,7 +6785,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6779
6785
  * @export
6780
6786
  */
6781
6787
  // eslint-disable-next-line no-useless-concat
6782
- shaka.Player.version = 'v4.6.7' + '-uncompiled'; // x-release-please-version
6788
+ shaka.Player.version = 'v4.6.8' + '-uncompiled'; // x-release-please-version
6783
6789
 
6784
6790
  // Initialize the deprecation system using the version string we just set
6785
6791
  // 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
- }
@@ -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
  }
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.6.7",
4
+ "version": "4.6.8",
5
5
  "homepage": "https://github.com/shaka-project/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -44,6 +44,42 @@ describe('CeaDecoder', () => {
44
44
  decoder.clear();
45
45
  });
46
46
 
47
+ it('painton captions on CC4', () => {
48
+ const controlCount = 0x03;
49
+ const captionData = 0xc0 | controlCount;
50
+ const paintonCaptionCC4Packet = new Uint8Array([
51
+ ...atscCaptionInitBytes, captionData, /* padding= */ 0xff,
52
+ 0xfd, 0x9d, 0x29, // Paint-on mode (RDC control code).
53
+ 0xfd, 0xf4, 0xe5, // t, e
54
+ 0xfd, 0x73, 0xf4, // s, t
55
+ ]);
56
+
57
+ const startTimeCaption1 = 1;
58
+ const startTimeCaption2 = 2;
59
+ const expectedText = 'test';
60
+
61
+ const topLevelCue = new shaka.text.Cue(startTimeCaption1,
62
+ startTimeCaption2, '');
63
+ topLevelCue.line = 10;
64
+ topLevelCue.lineInterpretation =
65
+ shaka.text.Cue.lineInterpretation.PERCENTAGE;
66
+ topLevelCue.nestedCues = [
67
+ CeaUtils.createDefaultCue(
68
+ startTimeCaption1, startTimeCaption2, expectedText),
69
+ ];
70
+
71
+ const expectedCaptions = [{
72
+ stream: 'CC4',
73
+ cue: topLevelCue,
74
+ }];
75
+
76
+ decoder.extract(paintonCaptionCC4Packet, startTimeCaption1);
77
+ decoder.extract(eraseDisplayedMemory, startTimeCaption2);
78
+ const captions = decoder.decode();
79
+
80
+ expect(captions).toEqual(expectedCaptions);
81
+ });
82
+
47
83
  it('green and underlined popon caption data on CC3', () => {
48
84
  const controlCount = 0x08;
49
85
  const captionData = 0xc0 | controlCount;
@@ -38,6 +38,10 @@ describe('MediaSourceEngine', () => {
38
38
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
39
39
 
40
40
  const originalIsTypeSupported = window.MediaSource.isTypeSupported;
41
+ let originalIsTypeSupportedManaged;
42
+ if (window.ManagedMediaSource) {
43
+ originalIsTypeSupportedManaged = window.ManagedMediaSource.isTypeSupported;
44
+ }
41
45
  const originalTextEngine = shaka.text.TextEngine;
42
46
  const originalCreateMediaSource =
43
47
  // eslint-disable-next-line no-restricted-syntax
@@ -98,10 +102,20 @@ describe('MediaSourceEngine', () => {
98
102
  const type = mimeType.split('/')[0];
99
103
  return type == 'video' || type == 'audio';
100
104
  };
105
+ if (window.ManagedMediaSource) {
106
+ window.ManagedMediaSource.isTypeSupported = (mimeType) => {
107
+ const type = mimeType.split('/')[0];
108
+ return type == 'video' || type == 'audio';
109
+ };
110
+ }
101
111
  });
102
112
 
103
113
  afterAll(() => {
104
114
  window.MediaSource.isTypeSupported = originalIsTypeSupported;
115
+ if (window.ManagedMediaSource) {
116
+ window.ManagedMediaSource.isTypeSupported =
117
+ originalIsTypeSupportedManaged;
118
+ }
105
119
  shaka.transmuxer.TransmuxerEngine.findTransmuxer =
106
120
  originalFindTransmuxer;
107
121
  shaka.transmuxer.TransmuxerEngine.convertCodecs =
@@ -206,6 +220,7 @@ describe('MediaSourceEngine', () => {
206
220
  shaka.media.MediaSourceEngine.createObjectURL;
207
221
  const originalRevokeObjectURL = window.URL.revokeObjectURL;
208
222
  const originalMediaSource = window.MediaSource;
223
+ const originalManagedMediaSource = window.ManagedMediaSource;
209
224
  /** @type {jasmine.Spy} */
210
225
  let createObjectURLSpy;
211
226
  /** @type {jasmine.Spy} */
@@ -237,6 +252,9 @@ describe('MediaSourceEngine', () => {
237
252
  return mockMediaSource;
238
253
  });
239
254
  window.MediaSource = Util.spyFunc(mediaSourceSpy);
255
+ if (window.ManagedMediaSource) {
256
+ window.ManagedMediaSource = Util.spyFunc(mediaSourceSpy);
257
+ }
240
258
 
241
259
  await mediaSourceEngine.destroy();
242
260
  });
@@ -244,6 +262,7 @@ describe('MediaSourceEngine', () => {
244
262
  afterAll(() => {
245
263
  shaka.media.MediaSourceEngine.createObjectURL = originalCreateObjectURL;
246
264
  window.MediaSource = originalMediaSource;
265
+ window.ManagedMediaSource = originalManagedMediaSource;
247
266
  window.URL.revokeObjectURL = originalRevokeObjectURL;
248
267
  });
249
268
 
@@ -270,7 +289,11 @@ describe('MediaSourceEngine', () => {
270
289
  video,
271
290
  new shaka.test.FakeTextDisplayer());
272
291
 
273
- expect(mockMediaSource.addEventListener).toHaveBeenCalledTimes(1);
292
+ if (window.ManagedMediaSource) {
293
+ expect(mockMediaSource.addEventListener).toHaveBeenCalledTimes(3);
294
+ } else {
295
+ expect(mockMediaSource.addEventListener).toHaveBeenCalledTimes(1);
296
+ }
274
297
  expect(mockMediaSource.addEventListener.calls.mostRecent().args[0])
275
298
  .toBe('sourceopen');
276
299
  expect(typeof onSourceOpenListener).toBe(typeof Function);
@@ -394,12 +394,16 @@ describe('Player Load Graph', () => {
394
394
  // that the player will behave as expected when media source is missing.
395
395
  describe('without media source', () => {
396
396
  let mediaSource;
397
+ let managedMediaSource;
397
398
 
398
399
  beforeEach(async () => {
399
400
  // Remove our media source support. In order to remove it, we need to set
400
401
  // it via [] notation or else closure will stop us.
401
402
  mediaSource = window.MediaSource;
402
403
  window['MediaSource'] = undefined;
404
+ managedMediaSource = window.ManagedMediaSource;
405
+ window['ManagedMediaSource'] = undefined;
406
+
403
407
 
404
408
  createPlayer();
405
409
  await shaka.test.Util.delay(/* seconds= */ 0.25);
@@ -409,6 +413,7 @@ describe('Player Load Graph', () => {
409
413
  // Restore our media source support to what it was before. If we did not
410
414
  // have support before, this will do nothing.
411
415
  window['MediaSource'] = mediaSource;
416
+ window['ManagedMediaSource'] = managedMediaSource;
412
417
  });
413
418
 
414
419
  it('attaching ignores init media source flag', async () => {
@@ -12,6 +12,10 @@ describe('Player', () => {
12
12
  const originalLogWarn = shaka.log.warning;
13
13
  const originalLogAlwaysWarn = shaka.log.alwaysWarn;
14
14
  const originalIsTypeSupported = window.MediaSource.isTypeSupported;
15
+ let originalIsTypeSupportedManaged;
16
+ if (window.ManagedMediaSource) {
17
+ originalIsTypeSupportedManaged = window.ManagedMediaSource.isTypeSupported;
18
+ }
15
19
  const originalDecodingInfo = navigator.mediaCapabilities.decodingInfo;
16
20
 
17
21
  const fakeManifestUri = 'fake-manifest-uri';
@@ -67,6 +71,12 @@ describe('Player', () => {
67
71
  const type = mimeType.split('/')[0];
68
72
  return type == 'video' || type == 'audio';
69
73
  };
74
+ if (window.ManagedMediaSource) {
75
+ window.ManagedMediaSource.isTypeSupported = (mimeType) => {
76
+ const type = mimeType.split('/')[0];
77
+ return type == 'video' || type == 'audio';
78
+ };
79
+ }
70
80
 
71
81
  // Since this is not an integration test, we don't want MediaCapabilities to
72
82
  // fail assertions based on browser support for types. Pretend that all
@@ -178,6 +188,10 @@ describe('Player', () => {
178
188
  shaka.log.warning = originalLogWarn;
179
189
  shaka.log.alwaysWarn = originalLogAlwaysWarn;
180
190
  window.MediaSource.isTypeSupported = originalIsTypeSupported;
191
+ if (window.ManagedMediaSource) {
192
+ window.ManagedMediaSource.isTypeSupported =
193
+ originalIsTypeSupportedManaged;
194
+ }
181
195
  shaka.media.ManifestParser.unregisterParserByMime(fakeMimeType);
182
196
  navigator.mediaCapabilities.decodingInfo = originalDecodingInfo;
183
197
  onError.calls.reset();
@@ -2900,6 +2914,9 @@ describe('Player', () => {
2900
2914
  describe('unplayable content', () => {
2901
2915
  it('throws CONTENT_UNSUPPORTED_BY_BROWSER', async () => {
2902
2916
  window.MediaSource.isTypeSupported = (mimeType) => false;
2917
+ if (window.ManagedMediaSource) {
2918
+ window.ManagedMediaSource.isTypeSupported = (mimeType) => false;
2919
+ }
2903
2920
 
2904
2921
  navigator.mediaCapabilities.decodingInfo = async (config) => {
2905
2922
  await Promise.resolve();
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.
@@ -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;