shaka-player 3.2.19 → 3.2.21

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 (80) hide show
  1. package/.github/workflows/build-and-test.yaml +4 -4
  2. package/.github/workflows/demo-version-index.yaml +6 -0
  3. package/.github/workflows/selenium-lab-tests.yaml +78 -17
  4. package/.github/workflows/update-screenshots.yaml +142 -0
  5. package/AUTHORS +2 -1
  6. package/CHANGELOG.md +40 -0
  7. package/CONTRIBUTORS +4 -1
  8. package/demo/common/asset.js +15 -0
  9. package/demo/common/assets.js +4 -2
  10. package/demo/common/message_ids.js +1 -0
  11. package/demo/custom.js +19 -0
  12. package/demo/locales/en.json +1 -0
  13. package/demo/locales/source.json +4 -0
  14. package/demo/main.js +14 -0
  15. package/dist/controls.css +1 -1
  16. package/dist/demo.compiled.debug.js +150 -148
  17. package/dist/demo.compiled.debug.map +3 -3
  18. package/dist/demo.compiled.js +150 -148
  19. package/dist/demo.compiled.map +3 -3
  20. package/dist/deps.js +1 -1
  21. package/dist/locales.js +30 -30
  22. package/dist/receiver.compiled.debug.js +19 -19
  23. package/dist/receiver.compiled.debug.map +2 -2
  24. package/dist/receiver.compiled.js +19 -19
  25. package/dist/receiver.compiled.map +2 -2
  26. package/dist/shaka-player.compiled.d.ts +13 -2
  27. package/dist/shaka-player.compiled.debug.d.ts +13 -2
  28. package/dist/shaka-player.compiled.debug.js +178 -176
  29. package/dist/shaka-player.compiled.debug.map +3 -3
  30. package/dist/shaka-player.compiled.js +294 -292
  31. package/dist/shaka-player.compiled.map +3 -3
  32. package/dist/shaka-player.ui.d.ts +13 -2
  33. package/dist/shaka-player.ui.debug.d.ts +13 -2
  34. package/dist/shaka-player.ui.debug.js +117 -115
  35. package/dist/shaka-player.ui.debug.map +3 -3
  36. package/dist/shaka-player.ui.js +318 -316
  37. package/dist/shaka-player.ui.map +3 -3
  38. package/docs/tutorials/errors.md +94 -0
  39. package/docs/tutorials/fairplay.md +2 -2
  40. package/docs/tutorials/index.json +2 -0
  41. package/docs/tutorials/widevine-service-certs.md +45 -0
  42. package/externs/ima.js +9 -0
  43. package/externs/shaka/net.js +1 -0
  44. package/externs/shaka/player.js +13 -7
  45. package/lib/ads/client_side_ad_manager.js +8 -5
  46. package/lib/hls/hls_parser.js +2 -0
  47. package/lib/media/media_source_engine.js +9 -4
  48. package/lib/media/streaming_engine.js +8 -3
  49. package/lib/media/transmuxer.js +13 -7
  50. package/lib/media/video_wrapper.js +3 -3
  51. package/lib/net/http_fetch_plugin.js +10 -0
  52. package/lib/net/networking_engine.js +1 -0
  53. package/lib/offline/indexeddb/storage_mechanism.js +1 -1
  54. package/lib/player.js +19 -2
  55. package/lib/polyfill/media_capabilities.js +15 -8
  56. package/lib/polyfill/mediasource.js +7 -2
  57. package/lib/text/mp4_vtt_parser.js +4 -0
  58. package/lib/text/text_engine.js +1 -0
  59. package/lib/text/ui_text_displayer.js +1 -0
  60. package/lib/text/vtt_text_parser.js +4 -5
  61. package/lib/util/manifest_parser_utils.js +1 -2
  62. package/lib/util/mime_utils.js +12 -1
  63. package/lib/util/stream_utils.js +28 -16
  64. package/maintained-branches.md +3 -4
  65. package/package.json +1 -1
  66. package/test/media/content_workarounds_unit.js +43 -0
  67. package/test/media/drm_engine_integration.js +4 -17
  68. package/test/media/streaming_engine_unit.js +6 -1
  69. package/test/net/networking_engine_unit.js +6 -1
  70. package/test/player_integration.js +39 -0
  71. package/test/player_unit.js +54 -0
  72. package/test/polyfill/media_capabilities_unit.js +20 -23
  73. package/test/test/assets/text-clip-alt.vtt +63 -0
  74. package/test/test/util/fake_drm_engine.js +3 -0
  75. package/test/test/util/simple_fakes.js +3 -0
  76. package/test/test/util/test_scheme.js +9 -14
  77. package/test/text/mp4_vtt_parser_unit.js +8 -0
  78. package/test/text/vtt_text_parser_unit.js +26 -2
  79. package/test/text/web_vtt_layout_integration.js +0 -3
  80. package/test/util/mime_utils_unit.js +10 -0
@@ -0,0 +1,94 @@
1
+ # Error Handling
2
+
3
+ The basics for handling native Error and {@link shaka.util.Error} thrown by Shaka.
4
+
5
+ ## Listening To and Handling Errors
6
+
7
+ ```javascript
8
+ const handleError = (error) => {
9
+ if (error instanceof Error) {
10
+ // shaka crashed with an unhandled native error
11
+ }
12
+
13
+ if (error.severity === shaka.util.Error.Severity.CRITICAL) {
14
+ // handle fatal error, playback can not continue
15
+ } else {
16
+ // handle non-fatal error, playback can continue
17
+ }
18
+ };
19
+
20
+ const player = new shaka.Player(video);
21
+
22
+ // handle errors that occur after load
23
+ player.addEventListener('error', handleError);
24
+
25
+ // there are two options for catching errors that occur during load
26
+
27
+ // it's possible to listen directly on the promise
28
+ player.load(url).catch(handleError);
29
+
30
+ // or to use async/await with a try/catch
31
+ try {
32
+ await player.load(url);
33
+ } catch (e) {
34
+ handleError(e);
35
+ }
36
+ ```
37
+
38
+ ## Custom Handling of Streaming Errors
39
+
40
+ The `streaming.failureCallback` property of {@link shaka.extern.PlayerConfiguration} can be used to add custom handling or error conversion of errors that occur during streaming.
41
+
42
+ ```javascript
43
+ player.configure('streaming.failureCallback', (error) => {
44
+ if (error.severity === shaka.util.Error.Severity.CRITICAL) {
45
+ // custom handling of critical error
46
+ // e.g. player.retryStreaming();
47
+ } else {
48
+ // custom handling of recoverable error
49
+ }
50
+ });
51
+ ```
52
+
53
+ ## Custom Handling of Retries
54
+
55
+ When configuring retry parameters in Shaka there may be known error codes that should not be retried, or the need to break out of an infinite retry loop in a live context. Retries for failed network requests are not reported as `RECOVERABLE` errors.
56
+
57
+ For example, if a VOD manifest is missing, unlike when a LIVE manifest is missing, it can be expected to not show up and there is no need to retry.
58
+
59
+ This is how to convert a retry into a critical error, see {@link shaka.net.NetworkingEngine.RetryEvent} and {@link shaka.util.Error} to decipher how the event can be parsed:
60
+
61
+ ```javascript
62
+ const nwEngine = player.getNetworkingEngine();
63
+
64
+ const vodManifestNotFoundHandler = (event /* shaka.net.NetworkingEngine.RetryEvent */) => {
65
+ const code = event.error.code;
66
+ const data = event.error.data;
67
+
68
+ if (code === shaka.util.Error.Code.BAD_HTTP_STATUS) {
69
+ if (
70
+ // each type of error has its own data structure (or none at all), tread with care
71
+ Array.isArray(data) &&
72
+ data[1] === 404 &&
73
+ data[4] === shaka.net.NetworkingEngine.RequestType.MANIFEST
74
+ ) {
75
+ // Throwing inside a retry callback will immediately stop retries
76
+ throw error;
77
+
78
+ // A proprietary error code can also be thrown
79
+ // throw new shaka.util.Error(
80
+ // shaka.util.Error.Severity.CRITICAL,
81
+ // shaka.util.Error.Category.NETWORK,
82
+ // 'RECOGNIZABLE_ERROR_MESSAGE'
83
+ // );
84
+ }
85
+ }
86
+ };
87
+
88
+ nwEngine.addEventListener('retry', vodManifestNotFoundHandler);
89
+
90
+ player.addEventListener('load'() => {
91
+ nwEngine.removeEventListener('retry', vodManifestNotFoundHandler);
92
+ });
93
+
94
+ ```
@@ -25,14 +25,14 @@ is used by the browser to generate the license request. If you don't use the
25
25
  default content ID derivation, you need to specify a custom init data transform:
26
26
 
27
27
  ```js
28
- player.configure('drm.initDataTransform', (initData, initDataType) => {
28
+ player.configure('drm.initDataTransform', (initData, initDataType, drmInfo) => {
29
29
  if (initDataType != 'skd')
30
30
  return initData;
31
31
 
32
32
  // 'initData' is a buffer containing an 'skd://' URL as a UTF-8 string.
33
33
  const skdUri = shaka.util.StringUtils.fromBytesAutoDetect(initData);
34
34
  const contentId = getMyContentId(skdUri);
35
- const cert = player.drmInfo().serverCertificate;
35
+ const cert = drmInfo.serverCertificate;
36
36
  return shaka.util.FairPlayUtils.initDataTransform(initData, contentId, cert);
37
37
  });
38
38
  ```
@@ -9,6 +9,7 @@
9
9
  { "license-wrapping": { "title": "License Wrapping" } },
10
10
  { "ui": { "title": "UI Library" } },
11
11
  { "ui-customization": { "title": "Configuring the UI" } },
12
+ { "errors": { "title": "Error Handling" } },
12
13
  { "a11y": { "title": "Creating accessible buttons" } },
13
14
  { "ad_monetization": { "title": "Serving ads with our IMA SDK integration" } },
14
15
  { "plugins": { "title": "Plugins and Customizing the Build" } },
@@ -16,6 +17,7 @@
16
17
  { "architecture": { "title": "Architecture Diagrams" } },
17
18
  { "service-worker": { "title": "Service Worker Caching" } },
18
19
  { "offline": { "title": "Offline Storage and Playback" } },
20
+ { "widevine-service-certs": { "title": "Widevine Service Certificates" } },
19
21
  { "fairplay": { "title": "FairPlay support" } },
20
22
  { "application-level-redirects": { "title": "Application-Level Redirects" } },
21
23
  { "selenium-grid-config": { "title": "Selenium Grid Config" } },
@@ -0,0 +1,45 @@
1
+ # Widevine Service Certificates
2
+
3
+ With Widevine, you can eliminate one round-trip to your license server or proxy
4
+ per session by preloading a Widevine service certificate. You can either provide
5
+ it directly or set a URI and let Shaka fetch it for you. Do this before calling
6
+ `player.load()`. The values will persist across multiple calls to `load()` on
7
+ the same `shaka.Player` instance.
8
+
9
+ ```js
10
+ // This is an example of loading the certificate from your site at runtime.
11
+ // You could also choose to bundle it into your JavaScript as a Uint8Array.
12
+ const req = await fetch('https://example.com/service.cert');
13
+ const cert = new Uint8Array(await req.arrayBuffer());
14
+
15
+ // This is the short form for configuration of a certificate:
16
+ player.configure('drm.advanced.com\\.widevine\\.alpha.serverCertificate',
17
+ cert);
18
+
19
+ // This is the long form:
20
+ player.configure({
21
+ drm: {
22
+ advanced: {
23
+ 'com.widevine.alpha': {
24
+ 'serverCertificate': cert,
25
+ },
26
+ },
27
+ },
28
+ });
29
+
30
+
31
+ // This is the short form for configuration of a certificate URI:
32
+ player.configure('drm.advanced.com\\.widevine\\.alpha.serverCertificateUri',
33
+ 'https://example.com/service.cert');
34
+
35
+ // This is the long form:
36
+ player.configure({
37
+ drm: {
38
+ advanced: {
39
+ 'com.widevine.alpha': {
40
+ 'serverCertificateUri': 'https://example.com/service.cert',
41
+ },
42
+ },
43
+ },
44
+ });
45
+ ```
package/externs/ima.js CHANGED
@@ -498,6 +498,15 @@ google.ima.dai.api.StreamRequest.prototype.streamActivityMonitorId;
498
498
  google.ima.dai.api.StreamRequest.prototype.format;
499
499
 
500
500
 
501
+ /**
502
+ * @enum {string}
503
+ */
504
+ google.ima.dai.api.StreamRequest.StreamFormat = {
505
+ DASH: 'dash',
506
+ HLS: 'hls',
507
+ };
508
+
509
+
501
510
  /** @const */
502
511
  google.ima.dai.api.VODStreamRequest =
503
512
  class extends google.ima.dai.api.StreamRequest {};
@@ -100,6 +100,7 @@ shaka.extern.Request;
100
100
  /**
101
101
  * @typedef {{
102
102
  * uri: string,
103
+ * originalUri: string,
103
104
  * data: BufferSource,
104
105
  * headers: !Object.<string, string>,
105
106
  * timeMs: (number|undefined),
@@ -571,9 +571,7 @@ shaka.extern.AdvancedDrmConfiguration;
571
571
  * clearKeys: !Object.<string, string>,
572
572
  * delayLicenseRequestUntilPlayed: boolean,
573
573
  * advanced: Object.<string, shaka.extern.AdvancedDrmConfiguration>,
574
- * initDataTransform:
575
- * ((function(!Uint8Array, string, ?shaka.extern.DrmInfo):!Uint8Array)|
576
- * undefined),
574
+ * initDataTransform:(shaka.extern.InitDataTransform|undefined),
577
575
  * logLicenseExchange: boolean,
578
576
  * updateExpirationTime: number,
579
577
  * preferredKeySystems: !Array.<string>
@@ -597,10 +595,7 @@ shaka.extern.AdvancedDrmConfiguration;
597
595
  * <i>Optional.</i> <br>
598
596
  * A dictionary which maps key system IDs to advanced DRM configuration for
599
597
  * those key systems.
600
- * @property
601
- * {((function(!Uint8Array, string, ?shaka.extern.DrmInfo):!Uint8Array)|
602
- * undefined)}
603
- * initDataTransform
598
+ * @property {shaka.extern.InitDataTransform|undefined} initDataTransform
604
599
  * <i>Optional.</i><br>
605
600
  * If given, this function is called with the init data from the
606
601
  * manifest/media and should return the (possibly transformed) init data to
@@ -622,6 +617,17 @@ shaka.extern.AdvancedDrmConfiguration;
622
617
  */
623
618
  shaka.extern.DrmConfiguration;
624
619
 
620
+ /**
621
+ * @typedef {function(!Uint8Array, string, ?shaka.extern.DrmInfo):!Uint8Array}
622
+ *
623
+ * @description
624
+ * A callback function to handle custom content ID signaling for FairPlay
625
+ * content.
626
+ *
627
+ * @exportDoc
628
+ */
629
+ shaka.extern.InitDataTransform;
630
+
625
631
 
626
632
  /**
627
633
  * @typedef {{
@@ -79,9 +79,9 @@ shaka.ads.ClientSideAdManager = class {
79
79
  });
80
80
 
81
81
  // Notify the SDK when the video has ended, so it can play post-roll ads.
82
- this.video_.onended = () => {
82
+ this.eventManager_.listen(this.video_, 'ended', () => {
83
83
  this.adsLoader_.contentComplete();
84
- };
84
+ });
85
85
  }
86
86
 
87
87
  /**
@@ -212,9 +212,9 @@ shaka.ads.ClientSideAdManager = class {
212
212
  // seen the ads actually play until requestAds() is called.
213
213
  // Note: We listen for a play event to avoid autoplay issues that might
214
214
  // crash IMA.
215
- this.video_.addEventListener('play', () => {
215
+ this.eventManager_.listenOnce(this.video_, 'play', () => {
216
216
  this.imaAdsManager_.start();
217
- }, {once: true});
217
+ });
218
218
  } catch (adError) {
219
219
  // If there was a problem with the VAST response,
220
220
  // we we won't be getting an ad. Hide ad UI if we showed it already
@@ -401,6 +401,7 @@ shaka.ads.ClientSideAdManager = class {
401
401
  shaka.ads.AdManager.AD_STARTED, data));
402
402
  this.adContainer_.setAttribute('ad-active', 'true');
403
403
  this.video_.pause();
404
+ this.ad_.setVolume(this.video_.muted ? 0 : this.video_.volume);
404
405
  }
405
406
 
406
407
  /**
@@ -411,6 +412,8 @@ shaka.ads.ClientSideAdManager = class {
411
412
  this.onEvent_(new shaka.util.FakeEvent(shaka.ads.AdManager.AD_STOPPED,
412
413
  (new Map()).set('originalEvent', e)));
413
414
  this.adContainer_.removeAttribute('ad-active');
414
- this.video_.play();
415
+ if (!this.video_.ended) {
416
+ this.video_.play();
417
+ }
415
418
  }
416
419
  };
@@ -2925,6 +2925,7 @@ shaka.hls.HlsParser.AUDIO_EXTENSIONS_TO_MIME_TYPES_ = {
2925
2925
  'cmfa': 'audio/mp4',
2926
2926
  // MPEG2-TS also uses video/ for audio: https://bit.ly/TsMse
2927
2927
  'ts': 'video/mp2t',
2928
+ 'tsa': 'video/mp2t',
2928
2929
 
2929
2930
  // Raw formats:
2930
2931
  'aac': 'audio/aac',
@@ -2962,6 +2963,7 @@ shaka.hls.HlsParser.VIDEO_EXTENSIONS_TO_MIME_TYPES_ = {
2962
2963
  'm4f': 'video/mp4',
2963
2964
  'cmfv': 'video/mp4',
2964
2965
  'ts': 'video/mp2t',
2966
+ 'tsv': 'video/mp2t',
2965
2967
  };
2966
2968
 
2967
2969
 
@@ -182,6 +182,9 @@ shaka.media.MediaSourceEngine = class {
182
182
  'audio/mp4; codecs="ec-3"',
183
183
  'audio/mp4; codecs="opus"',
184
184
  'audio/mp4; codecs="flac"',
185
+ 'audio/mp4; codecs="dtsc"', // DTS Digital Surround
186
+ 'audio/mp4; codecs="dtse"', // DTS Express
187
+ 'audio/mp4; codecs="dtsx"', // DTS:X
185
188
  // WebM types
186
189
  'video/webm; codecs="vp8"',
187
190
  'video/webm; codecs="vp9"',
@@ -515,7 +518,7 @@ shaka.media.MediaSourceEngine = class {
515
518
  // For HLS CEA-608/708 CLOSED-CAPTIONS, text data is embedded in
516
519
  // the video stream, so textEngine may not have been initialized.
517
520
  if (!this.textEngine_) {
518
- this.reinitText('text/vtt');
521
+ this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE);
519
522
  }
520
523
 
521
524
  if (transmuxedData.metadata) {
@@ -542,7 +545,7 @@ shaka.media.MediaSourceEngine = class {
542
545
  contentType, () => this.append_(contentType, transmuxedSegment));
543
546
  } else if (hasClosedCaptions) {
544
547
  if (!this.textEngine_) {
545
- this.reinitText('text/vtt');
548
+ this.reinitText(shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE);
546
549
  }
547
550
  // If it is the init segment for closed captions, initialize the closed
548
551
  // caption parser.
@@ -711,8 +714,10 @@ shaka.media.MediaSourceEngine = class {
711
714
  async endOfStream(reason) {
712
715
  await this.enqueueBlockingOperation_(() => {
713
716
  // If endOfStream() has already been called on the media source,
714
- // don't call it again.
715
- if (this.ended()) {
717
+ // don't call it again. Also do not call if readyState is
718
+ // 'closed' (not attached to video element) since it is not a
719
+ // valid operation.
720
+ if (this.ended() || this.mediaSource_.readyState === 'closed') {
716
721
  return;
717
722
  }
718
723
  // Tizen won't let us pass undefined, but it will let us omit the
@@ -77,6 +77,9 @@ shaka.media.StreamingEngine = class {
77
77
  /** @private {?shaka.extern.Stream} */
78
78
  this.currentTextStream_ = null;
79
79
 
80
+ /** @private {number} */
81
+ this.textStreamSequenceId_ = 0;
82
+
80
83
  /**
81
84
  * Maps a content type, e.g., 'audio', 'video', or 'text', to a MediaState.
82
85
  *
@@ -220,6 +223,8 @@ shaka.media.StreamingEngine = class {
220
223
  const ContentType = shaka.util.ManifestParserUtils.ContentType;
221
224
  goog.asserts.assert(!this.mediaStates_.has(ContentType.TEXT),
222
225
  'Should not call loadNewTextStream_ while streaming text!');
226
+ this.textStreamSequenceId_++;
227
+ const currentSequenceId = this.textStreamSequenceId_;
223
228
 
224
229
  try {
225
230
  // Clear MediaSource's buffered text, so that the new text stream will
@@ -241,7 +246,7 @@ shaka.media.StreamingEngine = class {
241
246
  const streamText =
242
247
  textDisplayer.isTextVisible() || this.config_.alwaysStreamText;
243
248
 
244
- if (streamText) {
249
+ if (streamText && (this.textStreamSequenceId_ == currentSequenceId)) {
245
250
  const state = this.createMediaState_(stream);
246
251
  this.mediaStates_.set(ContentType.TEXT, state);
247
252
  this.scheduleUpdate_(state, 0);
@@ -1477,10 +1482,10 @@ shaka.media.StreamingEngine = class {
1477
1482
  'waiting for another stream to recover...');
1478
1483
  }
1479
1484
 
1480
- // QuotaExceededError gets thrown if evication didn't help to make room
1485
+ // QuotaExceededError gets thrown if eviction didn't help to make room
1481
1486
  // for a segment. We want to wait for a while (4 seconds is just an
1482
1487
  // arbitrary number) before updating to give the playhead a chance to
1483
- // advance, so we don't immidiately throw again.
1488
+ // advance, so we don't immediately throw again.
1484
1489
  this.scheduleUpdate_(mediaState, 4);
1485
1490
  }
1486
1491
 
@@ -29,9 +29,7 @@ shaka.media.Transmuxer = class {
29
29
  this.muxjs_ = shaka.dependencies.muxjs();
30
30
 
31
31
  /** @private {muxjs.mp4.Transmuxer} */
32
- this.muxTransmuxer_ = new this.muxjs_.mp4.Transmuxer({
33
- 'keepOriginalTimestamps': true,
34
- });
32
+ this.muxTransmuxer_ = null;
35
33
 
36
34
  /** @private {shaka.util.PublicPromise} */
37
35
  this.transmuxPromise_ = null;
@@ -48,16 +46,22 @@ shaka.media.Transmuxer = class {
48
46
  /** @private {boolean} */
49
47
  this.isTransmuxing_ = false;
50
48
 
51
- this.muxTransmuxer_.on('data', (segment) => this.onTransmuxed_(segment));
52
-
53
- this.muxTransmuxer_.on('done', () => this.onTransmuxDone_());
49
+ if (this.muxjs_) {
50
+ this.muxTransmuxer_ = new this.muxjs_.mp4.Transmuxer({
51
+ 'keepOriginalTimestamps': true,
52
+ });
53
+ this.muxTransmuxer_.on('data', (segment) => this.onTransmuxed_(segment));
54
+ this.muxTransmuxer_.on('done', () => this.onTransmuxDone_());
55
+ }
54
56
  }
55
57
 
56
58
  /**
57
59
  * @override
58
60
  */
59
61
  destroy() {
60
- this.muxTransmuxer_.dispose();
62
+ if (this.muxTransmuxer_) {
63
+ this.muxTransmuxer_.dispose();
64
+ }
61
65
  this.muxTransmuxer_ = null;
62
66
  return Promise.resolve();
63
67
  }
@@ -156,6 +160,8 @@ shaka.media.Transmuxer = class {
156
160
  * metadata: !Array.<!Object>}>}
157
161
  */
158
162
  transmux(data) {
163
+ goog.asserts.assert(this.muxTransmuxer_,
164
+ 'mux.js should be available.');
159
165
  goog.asserts.assert(!this.isTransmuxing_,
160
166
  'No transmuxing should be in progress.');
161
167
  this.isTransmuxing_ = true;
@@ -135,15 +135,15 @@ shaka.media.VideoWrapper = class {
135
135
  // calling |Player.load|, meaning that |currentTime| is more meaningful than
136
136
  // |startTime|.
137
137
  //
138
- // Seeking to the current time is a work around for Issue 1298. If we don't
139
- // do this, the video may get stuck and not play.
138
+ // Seeking to the current time is a work around for Issue 1298 and 4888.
139
+ // If we don't do this, the video may get stuck and not play.
140
140
  //
141
141
  // TODO: Need further investigation why it happens. Before and after
142
142
  // setting the current time, video.readyState is 1, video.paused is true,
143
143
  // and video.buffered's TimeRanges length is 0.
144
144
  // See: https://github.com/shaka-project/shaka-player/issues/1298
145
145
  this.mover_.moveTo(
146
- this.video_.currentTime == 0 ?
146
+ (!this.video_.currentTime || this.video_.currentTime == 0) ?
147
147
  startTime :
148
148
  this.video_.currentTime);
149
149
  }
@@ -227,6 +227,16 @@ shaka.net.HttpFetchPlugin = class {
227
227
  } else {
228
228
  return false;
229
229
  }
230
+ // Old fetch implementations hasn't body and ReadableStream implementation
231
+ // See: https://github.com/shaka-project/shaka-player/issues/5088
232
+ if (window.Response) {
233
+ const response = new Response('');
234
+ if (!response.body) {
235
+ return false;
236
+ }
237
+ } else {
238
+ return false;
239
+ }
230
240
  return !!(window.fetch && window.AbortController);
231
241
  }
232
242
  };
@@ -299,6 +299,7 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
299
299
  if (!responseAndGotProgress.gotProgress &&
300
300
  this.onProgressUpdated_ &&
301
301
  !response.fromCache &&
302
+ request.method != 'HEAD' &&
302
303
  type == shaka.net.NetworkingEngine.RequestType.SEGMENT) {
303
304
  this.onProgressUpdated_(response.timeMs, response.data.byteLength);
304
305
  }
@@ -67,7 +67,7 @@ shaka.offline.indexeddb.StorageMechanism = class {
67
67
  shaka.util.Error.Category.STORAGE,
68
68
  shaka.util.Error.Code.INDEXED_DB_INIT_TIMED_OUT));
69
69
  });
70
- timeOutTimer.tickAfter(2);
70
+ timeOutTimer.tickAfter(5);
71
71
 
72
72
  const open = window.indexedDB.open(name, version);
73
73
  open.onsuccess = (event) => {
package/lib/player.js CHANGED
@@ -1148,6 +1148,7 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1148
1148
  */
1149
1149
  shouldUseSrcEquals_(payload) {
1150
1150
  const Platform = shaka.util.Platform;
1151
+ const MimeUtils = shaka.util.MimeUtils;
1151
1152
 
1152
1153
  // If we are using a platform that does not support media source, we will
1153
1154
  // fall back to src= to handle all playback.
@@ -1202,7 +1203,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1202
1203
  // version there.
1203
1204
 
1204
1205
  // Native HLS can be preferred on any platform via this flag:
1205
- if (this.config_.streaming.preferNativeHls) {
1206
+ if (MimeUtils.isHlsType(mimeType) &&
1207
+ this.config_.streaming.preferNativeHls) {
1206
1208
  return true;
1207
1209
  }
1208
1210
 
@@ -2297,6 +2299,8 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2297
2299
  // goes wrong.
2298
2300
  new shaka.util.Timer(resolve).tickAfter(1);
2299
2301
  });
2302
+ } else if (textTracks.length > 0) {
2303
+ this.isTextVisible_ = true;
2300
2304
  }
2301
2305
 
2302
2306
  // If we have moved on to another piece of content while waiting for
@@ -3896,6 +3900,19 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
3896
3900
  firstVariantWithLabel.language, '', 0, label);
3897
3901
 
3898
3902
  this.chooseVariantAndSwitch_();
3903
+ } else if (this.video_ && this.video_.audioTracks) {
3904
+ const audioTracks = Array.from(this.video_.audioTracks);
3905
+
3906
+ let trackMatch = null;
3907
+
3908
+ for (const audioTrack of audioTracks) {
3909
+ if (audioTrack.label == label) {
3910
+ trackMatch = audioTrack;
3911
+ }
3912
+ }
3913
+ if (trackMatch) {
3914
+ this.switchHtml5Track_(trackMatch);
3915
+ }
3899
3916
  }
3900
3917
  }
3901
3918
 
@@ -6459,7 +6476,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6459
6476
  * @define {string} A version number taken from git at compile time.
6460
6477
  * @export
6461
6478
  */
6462
- shaka.Player.version = 'v3.2.19-uncompiled';
6479
+ shaka.Player.version = 'v3.2.21-uncompiled';
6463
6480
 
6464
6481
  // Initialize the deprecation system using the version string we just set
6465
6482
  // on the player.
@@ -8,7 +8,6 @@ goog.provide('shaka.polyfill.MediaCapabilities');
8
8
 
9
9
  goog.require('shaka.log');
10
10
  goog.require('shaka.polyfill');
11
- goog.require('shaka.util.Error');
12
11
  goog.require('shaka.util.Platform');
13
12
 
14
13
 
@@ -102,6 +101,19 @@ shaka.polyfill.MediaCapabilities = class {
102
101
  if (shaka.util.Platform.isChromecast()) {
103
102
  isSupported =
104
103
  shaka.polyfill.MediaCapabilities.canCastDisplayType_(videoConfig);
104
+ } else if (shaka.util.Platform.isTizen()) {
105
+ let extendedType = videoConfig.contentType;
106
+ if (videoConfig.width && videoConfig.height) {
107
+ extendedType += `; width=${videoConfig.width}`;
108
+ extendedType += `; height=${videoConfig.height}`;
109
+ }
110
+ if (videoConfig.framerate) {
111
+ extendedType += `; framerate=${videoConfig.framerate}`;
112
+ }
113
+ if (videoConfig.bitrate) {
114
+ extendedType += `; bitrate=${videoConfig.bitrate}`;
115
+ }
116
+ isSupported = MediaSource.isTypeSupported(extendedType);
105
117
  } else {
106
118
  isSupported = MediaSource.isTypeSupported(videoConfig.contentType);
107
119
  }
@@ -212,13 +224,8 @@ shaka.polyfill.MediaCapabilities = class {
212
224
  * @private
213
225
  */
214
226
  static canCastDisplayType_(videoConfig) {
215
- if (!(window.cast)) {
216
- shaka.log.error('Expected cast namespace to be available!');
217
- throw new shaka.util.Error(
218
- shaka.util.Error.Severity.CRITICAL,
219
- shaka.util.Error.Category.CAST,
220
- shaka.util.Error.Code.CAST_API_UNAVAILABLE);
221
- } else if (!(cast.__platform__ && cast.__platform__.canDisplayType)) {
227
+ if (!(window.cast &&
228
+ cast.__platform__ && cast.__platform__.canDisplayType)) {
222
229
  shaka.log.warning('Expected cast APIs to be available! Falling back to ' +
223
230
  'MediaSource.isTypeSupported() for type support.');
224
231
  return MediaSource.isTypeSupported(videoConfig.contentType);
@@ -58,14 +58,19 @@ shaka.polyfill.MediaSource = class {
58
58
  // Offsetting the end of the removal range seems to help.
59
59
  // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=177884
60
60
  shaka.polyfill.MediaSource.patchRemovalRange_();
61
- } else {
62
- shaka.log.info('Patching Safari 13 MSE bugs.');
61
+ } else if (safariVersion <= 15) {
62
+ shaka.log.info('Patching Safari 13 & 14 & 15 MSE bugs.');
63
63
  // Safari 13 does not correctly implement abort() on SourceBuffer.
64
64
  // Calling abort() before appending a segment causes that segment to be
65
65
  // incomplete in the buffer.
66
66
  // Bug filed: https://bugs.webkit.org/show_bug.cgi?id=165342
67
67
  shaka.polyfill.MediaSource.stubAbort_();
68
68
  }
69
+ } else if (shaka.util.Platform.isEdge()) {
70
+ shaka.log.info('Rejecting TS container.');
71
+ // TS content is broken on Edge in general.
72
+ // See https://github.com/shaka-project/shaka-player/issues/4955
73
+ shaka.polyfill.MediaSource.rejectTsContent_();
69
74
  } else if (shaka.util.Platform.isTizen2() ||
70
75
  shaka.util.Platform.isTizen3() ||
71
76
  shaka.util.Platform.isTizen4()) {
@@ -89,6 +89,10 @@ shaka.text.Mp4VttParser = class {
89
89
  * @export
90
90
  */
91
91
  parseMedia(data, time) {
92
+ if (!data.length) {
93
+ return [];
94
+ }
95
+
92
96
  if (!this.timescale_) {
93
97
  // Missing timescale for VTT content. We should have seen the init
94
98
  // segment.
@@ -134,6 +134,7 @@ shaka.text.TextEngine = class {
134
134
  // into TextEngine and TextDisplayer.
135
135
  if (mimeType == shaka.util.MimeUtils.CEA608_CLOSED_CAPTION_MIMETYPE ||
136
136
  mimeType == shaka.util.MimeUtils.CEA708_CLOSED_CAPTION_MIMETYPE) {
137
+ this.parser_ = null;
137
138
  return;
138
139
  }
139
140
 
@@ -480,6 +480,7 @@ shaka.text.UITextDisplayer = class {
480
480
  wrapper = shaka.util.Dom.createHTMLElement('span');
481
481
  wrapper.classList.add('shaka-text-wrapper');
482
482
  wrapper.style.backgroundColor = cue.backgroundColor;
483
+ wrapper.style.lineHeight = 'normal';
483
484
  cueElement.appendChild(wrapper);
484
485
  }
485
486