shaka-player 4.0.2 → 4.1.0

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 (79) hide show
  1. package/CHANGELOG.md +25 -18
  2. package/CONTRIBUTORS +1 -0
  3. package/demo/common/message_ids.js +1 -0
  4. package/demo/config.js +3 -1
  5. package/demo/locales/en.json +1 -0
  6. package/demo/locales/source.json +4 -0
  7. package/dist/demo.compiled.debug.js +4 -4
  8. package/dist/demo.compiled.debug.map +2 -2
  9. package/dist/demo.compiled.js +4 -4
  10. package/dist/demo.compiled.map +2 -2
  11. package/dist/deps.js +2 -2
  12. package/dist/locales.js +31 -31
  13. package/dist/receiver.compiled.debug.js +2 -2
  14. package/dist/receiver.compiled.debug.map +2 -2
  15. package/dist/receiver.compiled.js +2 -2
  16. package/dist/receiver.compiled.map +2 -2
  17. package/dist/shaka-player.compiled.d.ts +14 -8
  18. package/dist/shaka-player.compiled.debug.d.ts +14 -8
  19. package/dist/shaka-player.compiled.debug.externs.js +10 -2
  20. package/dist/shaka-player.compiled.debug.js +165 -157
  21. package/dist/shaka-player.compiled.debug.map +3 -3
  22. package/dist/shaka-player.compiled.externs.js +10 -2
  23. package/dist/shaka-player.compiled.js +758 -754
  24. package/dist/shaka-player.compiled.map +3 -3
  25. package/dist/shaka-player.ui.d.ts +15 -9
  26. package/dist/shaka-player.ui.debug.d.ts +15 -9
  27. package/dist/shaka-player.ui.debug.externs.js +10 -2
  28. package/dist/shaka-player.ui.debug.js +198 -186
  29. package/dist/shaka-player.ui.debug.map +3 -3
  30. package/dist/shaka-player.ui.externs.js +10 -2
  31. package/dist/shaka-player.ui.js +931 -928
  32. package/dist/shaka-player.ui.map +3 -3
  33. package/docs/design/chromecast.md +3 -1
  34. package/docs/tutorials/drm-config.md +16 -0
  35. package/docs/tutorials/ui.md +26 -0
  36. package/externs/chromecast.js +9 -2
  37. package/externs/shaka/manifest.js +7 -0
  38. package/externs/shaka/player.js +21 -2
  39. package/externs/shaka/text.js +7 -0
  40. package/lib/cast/cast_proxy.js +19 -8
  41. package/lib/cast/cast_receiver.js +3 -3
  42. package/lib/cast/cast_sender.js +12 -2
  43. package/lib/hls/hls_parser.js +12 -1
  44. package/lib/media/drm_engine.js +17 -8
  45. package/lib/media/gap_jumping_controller.js +23 -1
  46. package/lib/media/playhead.js +47 -4
  47. package/lib/media/stall_detector.js +19 -2
  48. package/lib/media/streaming_engine.js +8 -1
  49. package/lib/offline/manifest_converter.js +1 -0
  50. package/lib/player.js +175 -69
  51. package/lib/text/cue.js +6 -0
  52. package/lib/text/ui_text_displayer.js +1 -0
  53. package/lib/text/vtt_text_parser.js +4 -0
  54. package/lib/util/error.js +6 -0
  55. package/lib/util/fake_event.js +39 -0
  56. package/lib/util/player_configuration.js +2 -0
  57. package/lib/util/stats.js +28 -1
  58. package/lib/util/stream_utils.js +7 -0
  59. package/package.json +1 -1
  60. package/test/cast/cast_proxy_unit.js +3 -1
  61. package/test/cast/cast_sender_unit.js +21 -6
  62. package/test/demo/demo_unit.js +2 -1
  63. package/test/media/adaptation_set_unit.js +1 -0
  64. package/test/media/playhead_unit.js +114 -25
  65. package/test/media/stall_detector_unit.js +20 -2
  66. package/test/media/streaming_engine_integration.js +2 -1
  67. package/test/media/streaming_engine_unit.js +1 -0
  68. package/test/player_integration.js +3 -0
  69. package/test/player_unit.js +142 -1
  70. package/test/test/util/manifest_generator.js +2 -0
  71. package/test/test/util/simple_fakes.js +14 -0
  72. package/test/test/util/streaming_engine_util.js +1 -0
  73. package/test/text/vtt_text_parser_unit.js +4 -0
  74. package/test/ui/ui_unit.js +18 -5
  75. package/test/util/stream_utils_unit.js +108 -0
  76. package/ui/controls.js +12 -8
  77. package/ui/externs/ui.js +10 -1
  78. package/ui/statistics_button.js +10 -0
  79. package/ui/ui.js +12 -1
@@ -26,11 +26,13 @@ the `Player` and `HTMLMediaElement` objects. Receiver apps only have to worry
26
26
  about their UI, while the `CastReceiver` takes care of playback and
27
27
  communication.
28
28
 
29
+ To enable an Android (TV) receiver apps set `androidReceiverCompatible` to true.
30
+
29
31
 
30
32
  #### `CastProxy` API sketch
31
33
 
32
34
  ```js
33
- new shaka.cast.CastProxy(video, player, receiverAppId)
35
+ new shaka.cast.CastProxy(video, player, receiverAppId, androidReceiverCompatible)
34
36
 
35
37
  // Also destroys the underlying local Player object
36
38
  shaka.cast.CastProxy.prototype.destroy() => Promise
@@ -75,6 +75,22 @@ player.configure({
75
75
  If the browser supports it and you configured a license server URL for it, we'll
76
76
  use it.
77
77
 
78
+ Alternative there are a config for make a mapping of keysystem if you know that
79
+ is broadly supported. For example, `com.microsoft.playready.recommendation`:
80
+
81
+ ```js
82
+ player.configure({
83
+ drm: {
84
+ keySystemsMapping: {
85
+ 'com.microsoft.playready': 'com.microsoft.playready.recommendation',
86
+ }
87
+ }
88
+ });
89
+ ```
90
+
91
+ With the previous configuration you will choose the `recommendation` keySystem
92
+ when your manifest (HLS or DASH) uses PlayReady.
93
+
78
94
 
79
95
  #### Clear Key
80
96
 
@@ -130,6 +130,23 @@ Next, let's add a listener to the 'caststatuschanged' event in myapp.js:
130
130
  }
131
131
  ```
132
132
 
133
+
134
+ #### Enabling Android Receiver Apps
135
+
136
+ If you'd like to take advantage of Android Receiver App support,
137
+ you will need to provide a boolean flag to enable support for
138
+ casting to an Android receiver app.
139
+
140
+ ```html
141
+ <div data-shaka-player-container style="max-width:40em"
142
+ data-shaka-player-cast-receiver-id="E7271BEC"
143
+ data-shaka-player-cast-android-receiver-compatible="true">
144
+ <!-- The manifest url in the src attribute will be automatically loaded -->
145
+ <video autoplay data-shaka-player id="video" style="width:100%;height:100%"
146
+ src="https://storage.googleapis.com/shaka-demo-assets/angel-one/dash.mpd"></video>
147
+ </div
148
+
149
+
133
150
  #### Providing source(s) for auto load.
134
151
 
135
152
  It's also possible to provide the `src` attribute on the `<video>` element
@@ -200,6 +217,15 @@ const controls = ui.getControls();
200
217
  // your API calls will be routed to the remote playback session.
201
218
  const player = controls.getPlayer();
202
219
  const video = controls.getVideo();
220
+
221
+ // Programatically configure the Chromecast Receiver App Id and Android
222
+ // Receiver Compatability.
223
+ ui.configure({
224
+ // Set the castReceiverAppId
225
+ 'castReceiverAppId': 'BBED8D28',
226
+ // Enable casting to native Android Apps (e.g. Android TV Apps)
227
+ 'castAndroidReceiverCompatible': true,
228
+ });
203
229
  ```
204
230
 
205
231
 
@@ -270,6 +270,13 @@ chrome.cast.Session = class {
270
270
 
271
271
 
272
272
  chrome.cast.SessionRequest = class {
273
- /** @param {string} appId */
274
- constructor(appId) {}
273
+ /**
274
+ * @param {string} appId
275
+ * @param {Array.<Object>} capabilities
276
+ * @param {?number} timeout
277
+ * @param {boolean} androidReceiverCompatible
278
+ * @param {Object} credentialsData
279
+ */
280
+ constructor(appId, capabilities, timeout, androidReceiverCompatible,
281
+ credentialsData) {}
275
282
  };
@@ -176,6 +176,7 @@ shaka.extern.DrmInfo;
176
176
  * @typedef {{
177
177
  * id: number,
178
178
  * language: string,
179
+ * disabledUntilTime: number,
179
180
  * primary: boolean,
180
181
  * audio: ?shaka.extern.Stream,
181
182
  * video: ?shaka.extern.Stream,
@@ -198,6 +199,12 @@ shaka.extern.DrmInfo;
198
199
  * The Variant's language, specified as a language code. <br>
199
200
  * See {@link https://tools.ietf.org/html/rfc5646} <br>
200
201
  * See {@link http://www.iso.org/iso/home/standards/language_codes.htm}
202
+ * @property {number} disabledUntilTime
203
+ * <i>Defaults to 0.</i> <br>
204
+ * 0 means the variant is enabled. The Player will set this value to
205
+ * "(Date.now() / 1000) + config.streaming.maxDisabledTime" and once this
206
+ * maxDisabledTime has passed Player will set the value to 0 in order to
207
+ * reenable the variant.
201
208
  * @property {boolean} primary
202
209
  * <i>Defaults to false.</i> <br>
203
210
  * True indicates that the player should use this Variant over others if user
@@ -82,6 +82,9 @@ shaka.extern.StateChange;
82
82
  *
83
83
  * maxSegmentDuration: number,
84
84
  *
85
+ * gapsJumped: number,
86
+ * stallsDetected: number,
87
+ *
85
88
  * switchHistory: !Array.<shaka.extern.TrackChoice>,
86
89
  * stateHistory: !Array.<shaka.extern.StateChange>
87
90
  * }}
@@ -112,6 +115,11 @@ shaka.extern.StateChange;
112
115
  * @property {number} estimatedBandwidth
113
116
  * The current estimated network bandwidth (in bit/sec).
114
117
  *
118
+ * @property {number} gapsJumped
119
+ * The total number of playback gaps jumped by the GapJumpingController.
120
+ * @property {number} stallsDetected
121
+ * The total number of playback stalls detected by the StallDetector.
122
+ *
115
123
  * @property {number} completionPercent
116
124
  * This is the greatest completion percent that the user has experienced in
117
125
  * playback. Also known as the "high water mark". Is NaN when there is no
@@ -623,7 +631,8 @@ shaka.extern.AdvancedDrmConfiguration;
623
631
  * undefined),
624
632
  * logLicenseExchange: boolean,
625
633
  * updateExpirationTime: number,
626
- * preferredKeySystems: !Array.<string>
634
+ * preferredKeySystems: !Array.<string>,
635
+ * keySystemsMapping: !Object.<string, string>
627
636
  * }}
628
637
  *
629
638
  * @property {shaka.extern.RetryParameters} retryParameters
@@ -664,6 +673,8 @@ shaka.extern.AdvancedDrmConfiguration;
664
673
  * @property {!Array.<string>} preferredKeySystems
665
674
  * <i>Defaults to an empty array. </i> <br>
666
675
  * Specifies the priorties of available DRM key systems.
676
+ * @property {Object.<string, string>} keySystemsMapping
677
+ * A map of key system name to key system name.
667
678
  *
668
679
  * @exportDoc
669
680
  */
@@ -863,7 +874,8 @@ shaka.extern.ManifestConfiguration;
863
874
  * preferNativeHls: boolean,
864
875
  * updateIntervalSeconds: number,
865
876
  * dispatchAllEmsgBoxes: boolean,
866
- * observeQualityChanges: boolean
877
+ * observeQualityChanges: boolean,
878
+ * maxDisabledTime: number
867
879
  * }}
868
880
  *
869
881
  * @description
@@ -968,6 +980,10 @@ shaka.extern.ManifestConfiguration;
968
980
  * @property {boolean} observeQualityChanges
969
981
  * If true, monitor media quality changes and emit
970
982
  * <code.shaka.Player.MediaQualityChangedEvent</code>.
983
+ * @property {number} maxDisabledTime
984
+ * The maximum time a variant can be disabled when NETWORK HTTP_ERROR
985
+ * is reached, in seconds.
986
+ * If all variants are disabled this way, NETWORK HTTP_ERROR will be thrown.
971
987
  * @exportDoc
972
988
  */
973
989
  shaka.extern.StreamingConfiguration;
@@ -1264,11 +1280,14 @@ shaka.extern.Thumbnail;
1264
1280
 
1265
1281
  /**
1266
1282
  * @typedef {{
1283
+ * id: string,
1267
1284
  * title: string,
1268
1285
  * startTime: number,
1269
1286
  * endTime: number
1270
1287
  * }}
1271
1288
  *
1289
+ * @property {string} id
1290
+ * The id of the chapter.
1272
1291
  * @property {string} title
1273
1292
  * The title of the chapter.
1274
1293
  * @property {number} startTime
@@ -297,6 +297,13 @@ shaka.extern.Cue = class {
297
297
  */
298
298
  this.fontFamily;
299
299
 
300
+ /**
301
+ * Text shadow color as a CSS text-shadow value.
302
+ * @type {string}
303
+ * @exportDoc
304
+ */
305
+ this.textShadow = '';
306
+
300
307
  /**
301
308
  * Text stroke color as a CSS color, e.g. "#FFFFFF" or "white".
302
309
  * @type {string}
@@ -43,8 +43,11 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
43
43
  * @param {string} receiverAppId The ID of the cast receiver application.
44
44
  * If blank, casting will not be available, but the proxy will still
45
45
  * function otherwise.
46
+ * @param {boolean} androidReceiverCompatible Indicates if the app is
47
+ * compatible with an Android Receiver.
46
48
  */
47
- constructor(video, player, receiverAppId) {
49
+ constructor(video, player, receiverAppId,
50
+ androidReceiverCompatible = false) {
48
51
  super();
49
52
 
50
53
  /** @private {HTMLMediaElement} */
@@ -71,6 +74,9 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
71
74
  /** @private {string} */
72
75
  this.receiverAppId_ = receiverAppId;
73
76
 
77
+ /** @private {boolean} */
78
+ this.androidReceiverCompatible_ = androidReceiverCompatible;
79
+
74
80
  /** @private {!Map} */
75
81
  this.compiledToExternNames_ = new Map();
76
82
 
@@ -81,7 +87,8 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
81
87
  () => this.onFirstCastStateUpdate_(),
82
88
  (targetName, event) => this.onRemoteEvent_(targetName, event),
83
89
  () => this.onResumeLocal_(),
84
- () => this.getInitState_());
90
+ () => this.getInitState_(),
91
+ androidReceiverCompatible);
85
92
 
86
93
 
87
94
  this.init_();
@@ -225,15 +232,18 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
225
232
 
226
233
  /**
227
234
  * @param {string} newAppId
235
+ * @param {boolean=} newCastAndroidReceiver
228
236
  * @export
229
237
  */
230
- async changeReceiverId(newAppId) {
231
- if (newAppId == this.receiverAppId_) {
238
+ async changeReceiverId(newAppId, newCastAndroidReceiver = false) {
239
+ if (newAppId == this.receiverAppId_ &&
240
+ newCastAndroidReceiver == this.androidReceiverCompatible_) {
232
241
  // Nothing to change
233
242
  return;
234
243
  }
235
244
 
236
245
  this.receiverAppId_ = newAppId;
246
+ this.androidReceiverCompatible_ = newCastAndroidReceiver;
237
247
 
238
248
  // Destroy the old sender
239
249
  this.sender_.forceDisconnect();
@@ -248,7 +258,8 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
248
258
  () => this.onFirstCastStateUpdate_(),
249
259
  (targetName, event) => this.onRemoteEvent_(targetName, event),
250
260
  () => this.onResumeLocal_(),
251
- () => this.getInitState_());
261
+ () => this.getInitState_(),
262
+ newCastAndroidReceiver);
252
263
 
253
264
  this.sender_.init();
254
265
  }
@@ -267,8 +278,8 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
267
278
  (event) => this.videoProxyLocalEvent_(event));
268
279
  }
269
280
 
270
- for (const key in shaka.Player.EventName) {
271
- const name = shaka.Player.EventName[key];
281
+ for (const key in shaka.util.FakeEvent.EventName) {
282
+ const name = shaka.util.FakeEvent.EventName[key];
272
283
  this.eventManager_.listen(this.localPlayer_, name,
273
284
  (event) => this.playerProxyLocalEvent_(event));
274
285
  }
@@ -526,7 +537,7 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
526
537
  // Pass any errors through to the app.
527
538
  goog.asserts.assert(error instanceof shaka.util.Error,
528
539
  'Wrong error type!');
529
- const eventType = shaka.Player.EventName.Error;
540
+ const eventType = shaka.util.FakeEvent.EventName.Error;
530
541
  const data = (new Map()).set('detail', error);
531
542
  const event = new shaka.util.FakeEvent(eventType, data);
532
543
  this.localPlayer_.dispatchEvent(event);
@@ -281,8 +281,8 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget {
281
281
  this.video_, name, (event) => this.proxyEvent_('video', event));
282
282
  }
283
283
 
284
- for (const key in shaka.Player.EventName) {
285
- const name = shaka.Player.EventName[key];
284
+ for (const key in shaka.util.FakeEvent.EventName) {
285
+ const name = shaka.util.FakeEvent.EventName[key];
286
286
  this.eventManager_.listen(
287
287
  this.player_, name, (event) => this.proxyEvent_('player', event));
288
288
  }
@@ -409,7 +409,7 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget {
409
409
  // Pass any errors through to the app.
410
410
  goog.asserts.assert(error instanceof shaka.util.Error,
411
411
  'Wrong error type!');
412
- const eventType = shaka.Player.EventName.Error;
412
+ const eventType = shaka.util.FakeEvent.EventName.Error;
413
413
  const data = (new Map()).set('detail', error);
414
414
  const event = new shaka.util.FakeEvent(eventType, data);
415
415
  // Only dispatch the event if the player still exists.
@@ -32,12 +32,18 @@ shaka.cast.CastSender = class {
32
32
  * should resume playback. Called before the cached remote state is wiped.
33
33
  * @param {function()} onInitStateRequired A callback to get local player's.
34
34
  * state. Invoked when casting is initiated from Chrome's cast button.
35
+ * @param {boolean} androidReceiverCompatible Indicates if the app is
36
+ * compatible with an Android Receiver.
35
37
  */
36
38
  constructor(receiverAppId, onStatusChanged, onFirstCastStateUpdate,
37
- onRemoteEvent, onResumeLocal, onInitStateRequired) {
39
+ onRemoteEvent, onResumeLocal, onInitStateRequired,
40
+ androidReceiverCompatible) {
38
41
  /** @private {string} */
39
42
  this.receiverAppId_ = receiverAppId;
40
43
 
44
+ /** @private {boolean} */
45
+ this.androidReceiverCompatible_ = androidReceiverCompatible;
46
+
41
47
  /** @private {shaka.util.Timer} */
42
48
  this.statusChangeTimer_ = new shaka.util.Timer(onStatusChanged);
43
49
 
@@ -211,7 +217,11 @@ shaka.cast.CastSender = class {
211
217
  // Use static versions of the API callbacks, since the ChromeCast API is
212
218
  // static. If we used local versions, we might end up retaining references
213
219
  // to destroyed players here.
214
- const sessionRequest = new chrome.cast.SessionRequest(this.receiverAppId_);
220
+ const sessionRequest = new chrome.cast.SessionRequest(this.receiverAppId_,
221
+ /* capabilities= */ [],
222
+ /* timeout= */ null,
223
+ this.androidReceiverCompatible_,
224
+ /* credentialsData= */null);
215
225
  const apiConfig = new chrome.cast.ApiConfig(sessionRequest,
216
226
  (session) => CastSender.onExistingSessionJoined_(session),
217
227
  (availability) => CastSender.onReceiverStatusChanged_(availability),
@@ -460,6 +460,7 @@ shaka.hls.HlsParser = class {
460
460
  variants.push({
461
461
  id: 0,
462
462
  language: 'und',
463
+ disabledUntilTime: 0,
463
464
  primary: true,
464
465
  audio: type == 'audio' ? streamInfo.stream : null,
465
466
  video: type == 'video' ? streamInfo.stream : null,
@@ -1926,6 +1927,11 @@ shaka.hls.HlsParser = class {
1926
1927
  const pAbsoluteUri = shaka.hls.Utils.constructAbsoluteUri(
1927
1928
  absoluteMediaPlaylistUri, pUri);
1928
1929
 
1930
+ let partialStatus = shaka.media.SegmentReference.Status.AVAILABLE;
1931
+ if (item.getAttributeValue('GAP') == 'YES') {
1932
+ partialStatus = shaka.media.SegmentReference.Status.MISSING;
1933
+ }
1934
+
1929
1935
  const partial = new shaka.media.SegmentReference(
1930
1936
  pStartTime,
1931
1937
  pEndTime,
@@ -1935,7 +1941,12 @@ shaka.hls.HlsParser = class {
1935
1941
  initSegmentReference,
1936
1942
  /* timestampOffset= */ 0, // This value is ignored in sequence mode.
1937
1943
  /* appendWindowStart= */ 0,
1938
- /* appendWindowEnd= */ Infinity);
1944
+ /* appendWindowEnd= */ Infinity,
1945
+ /* partialReferences= */ [],
1946
+ /* tilesLayout= */ '',
1947
+ /* tileDuration= */ null,
1948
+ /* syncTime= */ null,
1949
+ partialStatus);
1939
1950
  partialSegmentRefs.push(partial);
1940
1951
  } // for-loop of hlsSegment.partialSegments
1941
1952
  }
@@ -355,7 +355,8 @@ shaka.media.DrmEngine = class {
355
355
  shaka.media.DrmEngine.fillInDrmInfoDefaults_(
356
356
  info,
357
357
  shaka.util.MapUtils.asMap(this.config_.servers),
358
- shaka.util.MapUtils.asMap(this.config_.advanced || {}));
358
+ shaka.util.MapUtils.asMap(this.config_.advanced || {}),
359
+ this.config_.keySystemsMapping);
359
360
  }
360
361
  }
361
362
 
@@ -2224,16 +2225,20 @@ shaka.media.DrmEngine = class {
2224
2225
  * that the parser left blank. Before working with any drmInfo, it should be
2225
2226
  * passed through here as it is uncommon for drmInfo to be complete when
2226
2227
  * fetched from a manifest because most manifest formats do not have the
2227
- * required information.
2228
+ * required information. Also applies the key systems mapping.
2228
2229
  *
2229
2230
  * @param {shaka.extern.DrmInfo} drmInfo
2230
2231
  * @param {!Map.<string, string>} servers
2231
2232
  * @param {!Map.<string, shaka.extern.AdvancedDrmConfiguration>}
2232
2233
  * advancedConfigs
2234
+ * @param {!Object.<string, string>} keySystemsMapping
2233
2235
  * @private
2234
2236
  */
2235
- static fillInDrmInfoDefaults_(drmInfo, servers, advancedConfigs) {
2236
- if (!drmInfo.keySystem) {
2237
+ static fillInDrmInfoDefaults_(drmInfo, servers, advancedConfigs,
2238
+ keySystemsMapping) {
2239
+ const originalKeySystem = drmInfo.keySystem;
2240
+
2241
+ if (!originalKeySystem) {
2237
2242
  // This is a placeholder from the manifest parser for an unrecognized key
2238
2243
  // system. Skip this entry, to avoid logging nonsensical errors.
2239
2244
  return;
@@ -2254,14 +2259,14 @@ shaka.media.DrmEngine = class {
2254
2259
  // The only way to get license servers from the manifest is not to specify
2255
2260
  // any in your player config.
2256
2261
 
2257
- if (drmInfo.keySystem == 'org.w3.clearkey' && drmInfo.licenseServerUri) {
2262
+ if (originalKeySystem == 'org.w3.clearkey' && drmInfo.licenseServerUri) {
2258
2263
  // Preference 1: Clear Key with pre-configured keys will have a data URI
2259
2264
  // assigned as its license server. Don't change anything.
2260
2265
  return;
2261
2266
  } else if (servers.size) {
2262
2267
  // Preference 2: If anything is configured at the application level,
2263
2268
  // override whatever was in the manifest.
2264
- const server = servers.get(drmInfo.keySystem) || '';
2269
+ const server = servers.get(originalKeySystem) || '';
2265
2270
 
2266
2271
  drmInfo.licenseServerUri = server;
2267
2272
  } else {
@@ -2273,7 +2278,7 @@ shaka.media.DrmEngine = class {
2273
2278
  drmInfo.keyIds = new Set();
2274
2279
  }
2275
2280
 
2276
- const advancedConfig = advancedConfigs.get(drmInfo.keySystem);
2281
+ const advancedConfig = advancedConfigs.get(originalKeySystem);
2277
2282
 
2278
2283
  if (advancedConfig) {
2279
2284
  if (!drmInfo.distinctiveIdentifierRequired) {
@@ -2307,6 +2312,10 @@ shaka.media.DrmEngine = class {
2307
2312
  }
2308
2313
  }
2309
2314
 
2315
+ if (keySystemsMapping[originalKeySystem]) {
2316
+ drmInfo.keySystem = keySystemsMapping[originalKeySystem];
2317
+ }
2318
+
2310
2319
  // Chromecast has a variant of PlayReady that uses a different key
2311
2320
  // system ID. Since manifest parsers convert the standard PlayReady
2312
2321
  // UUID to the standard PlayReady key system ID, here we will switch
@@ -2315,7 +2324,7 @@ shaka.media.DrmEngine = class {
2315
2324
  // player config uses the standard PlayReady ID for license server
2316
2325
  // configuration.
2317
2326
  if (window.cast && window.cast.__platform__) {
2318
- if (drmInfo.keySystem == 'com.microsoft.playready') {
2327
+ if (originalKeySystem == 'com.microsoft.playready') {
2319
2328
  drmInfo.keySystem = 'com.chromecast.playready';
2320
2329
  }
2321
2330
  }
@@ -11,6 +11,7 @@ goog.require('shaka.media.PresentationTimeline');
11
11
  goog.require('shaka.media.StallDetector');
12
12
  goog.require('shaka.media.TimeRangesUtils');
13
13
  goog.require('shaka.util.EventManager');
14
+ goog.require('shaka.util.FakeEvent');
14
15
  goog.require('shaka.util.IReleasable');
15
16
  goog.require('shaka.util.Timer');
16
17
 
@@ -32,8 +33,13 @@ shaka.media.GapJumpingController = class {
32
33
  * playable region. The gap jumping controller takes ownership over the
33
34
  * stall detector.
34
35
  * If no stall detection logic is desired, |null| may be provided.
36
+ * @param {function(!Event)} onEvent
37
+ * Called when an event is raised to be sent to the application.
35
38
  */
36
- constructor(video, timeline, config, stallDetector) {
39
+ constructor(video, timeline, config, stallDetector, onEvent) {
40
+ /** @private {?function(!Event)} */
41
+ this.onEvent_ = onEvent;
42
+
37
43
  /** @private {HTMLMediaElement} */
38
44
  this.video_ = video;
39
45
 
@@ -52,6 +58,9 @@ shaka.media.GapJumpingController = class {
52
58
  /** @private {number} */
53
59
  this.prevReadyState_ = video.readyState;
54
60
 
61
+ /** @private {number} */
62
+ this.gapsJumped_ = 0;
63
+
55
64
  /**
56
65
  * The stall detector tries to keep the playhead moving forward. It is
57
66
  * managed by the gap-jumping controller to avoid conflicts. On some
@@ -98,6 +107,7 @@ shaka.media.GapJumpingController = class {
98
107
  this.stallDetector_ = null;
99
108
  }
100
109
 
110
+ this.onEvent_ = null;
101
111
  this.timeline_ = null;
102
112
  this.video_ = null;
103
113
  }
@@ -121,6 +131,15 @@ shaka.media.GapJumpingController = class {
121
131
  }
122
132
 
123
133
 
134
+ /**
135
+ * Returns the total number of playback gaps jumped.
136
+ * @return {number}
137
+ */
138
+ getGapsJumped() {
139
+ return this.gapsJumped_;
140
+ }
141
+
142
+
124
143
  /**
125
144
  * Called on a recurring timer to check for gaps in the media. This is also
126
145
  * called in a 'waiting' event.
@@ -209,6 +228,9 @@ shaka.media.GapJumpingController = class {
209
228
  }
210
229
 
211
230
  this.video_.currentTime = jumpTo;
231
+ this.gapsJumped_++;
232
+ this.onEvent_(
233
+ new shaka.util.FakeEvent(shaka.util.FakeEvent.EventName.GapJumped));
212
234
  }
213
235
  };
214
236
 
@@ -41,6 +41,20 @@ shaka.media.Playhead = class {
41
41
  */
42
42
  setStartTime(startTime) {}
43
43
 
44
+ /**
45
+ * Get the number of playback stalls detected by the StallDetector.
46
+ *
47
+ * @return {number}
48
+ */
49
+ getStallsDetected() {}
50
+
51
+ /**
52
+ * Get the number of playback gaps jumped by the GapJumpingController.
53
+ *
54
+ * @return {number}
55
+ */
56
+ getGapsJumped() {}
57
+
44
58
  /**
45
59
  * Get the current playhead position. The position will be restricted to valid
46
60
  * time ranges.
@@ -133,6 +147,16 @@ shaka.media.SrcEqualsPlayhead = class {
133
147
  return time || 0;
134
148
  }
135
149
 
150
+ /** @override */
151
+ getStallsDetected() {
152
+ return 0;
153
+ }
154
+
155
+ /** @override */
156
+ getGapsJumped() {
157
+ return 0;
158
+ }
159
+
136
160
  /** @override */
137
161
  notifyOfBufferingChange() {}
138
162
  };
@@ -160,8 +184,10 @@ shaka.media.MediaSourcePlayhead = class {
160
184
  * @param {function()} onSeek
161
185
  * Called when the user agent seeks to a time within the presentation
162
186
  * timeline.
187
+ * @param {function(!Event)} onEvent
188
+ * Called when an event is raised to be sent to the application.
163
189
  */
164
- constructor(mediaElement, manifest, config, startTime, onSeek) {
190
+ constructor(mediaElement, manifest, config, startTime, onSeek, onEvent) {
165
191
  /**
166
192
  * The seek range must be at least this number of seconds long. If it is
167
193
  * smaller than this, change it to be this big so we don't repeatedly seek
@@ -192,12 +218,17 @@ shaka.media.MediaSourcePlayhead = class {
192
218
  /** @private {?number} */
193
219
  this.lastCorrectiveSeek_ = null;
194
220
 
221
+ /** @private {shaka.media.StallDetector} */
222
+ this.stallDetector_ =
223
+ this.createStallDetector_(mediaElement, config, onEvent);
224
+
195
225
  /** @private {shaka.media.GapJumpingController} */
196
226
  this.gapController_ = new shaka.media.GapJumpingController(
197
227
  mediaElement,
198
228
  manifest.presentationTimeline,
199
229
  config,
200
- this.createStallDetector_(mediaElement, config));
230
+ this.stallDetector_,
231
+ onEvent);
201
232
 
202
233
  /** @private {shaka.media.VideoWrapper} */
203
234
  this.videoWrapper_ = new shaka.media.VideoWrapper(
@@ -261,6 +292,16 @@ shaka.media.MediaSourcePlayhead = class {
261
292
  return time;
262
293
  }
263
294
 
295
+ /** @override */
296
+ getStallsDetected() {
297
+ return this.stallDetector_.getStallsDetected();
298
+ }
299
+
300
+ /** @override */
301
+ getGapsJumped() {
302
+ return this.gapController_.getGapsJumped();
303
+ }
304
+
264
305
  /**
265
306
  * Gets the playhead's initial position in seconds.
266
307
  *
@@ -475,10 +516,12 @@ shaka.media.MediaSourcePlayhead = class {
475
516
  *
476
517
  * @param {!HTMLMediaElement} mediaElement
477
518
  * @param {shaka.extern.StreamingConfiguration} config
519
+ * @param {function(!Event)} onEvent
520
+ * Called when an event is raised to be sent to the application.
478
521
  * @return {shaka.media.StallDetector}
479
522
  * @private
480
523
  */
481
- createStallDetector_(mediaElement, config) {
524
+ createStallDetector_(mediaElement, config, onEvent) {
482
525
  if (!config.stallEnabled) {
483
526
  return null;
484
527
  }
@@ -492,7 +535,7 @@ shaka.media.MediaSourcePlayhead = class {
492
535
  // playhead forward.
493
536
  const detector = new shaka.media.StallDetector(
494
537
  new shaka.media.StallDetector.MediaElementImplementation(mediaElement),
495
- threshold);
538
+ threshold, onEvent);
496
539
 
497
540
  detector.onStall((at, duration) => {
498
541
  shaka.log.debug(`Stall detected at ${at} for ${duration} seconds.`);