shaka-player 3.3.1 → 3.3.2

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 (49) hide show
  1. package/.github/workflows/build_and_test.yaml +73 -0
  2. package/.github/workflows/release.yaml +53 -0
  3. package/CHANGELOG.md +13 -1
  4. package/build/checkversion.py +1 -1
  5. package/build/test.py +6 -0
  6. package/dist/controls.css +1 -1
  7. package/dist/deps.js +4 -4
  8. package/dist/locales.js +29 -29
  9. package/dist/shaka-player.compiled.d.ts +2 -1
  10. package/dist/shaka-player.compiled.debug.d.ts +2 -1
  11. package/dist/shaka-player.compiled.debug.externs.js +17 -12
  12. package/dist/shaka-player.compiled.debug.js +93 -90
  13. package/dist/shaka-player.compiled.debug.map +4 -4
  14. package/dist/shaka-player.compiled.externs.js +17 -12
  15. package/dist/shaka-player.compiled.js +687 -686
  16. package/dist/shaka-player.compiled.map +3 -3
  17. package/dist/shaka-player.ui.d.ts +4 -12
  18. package/dist/shaka-player.ui.debug.d.ts +4 -12
  19. package/dist/shaka-player.ui.debug.externs.js +19 -23
  20. package/dist/shaka-player.ui.debug.js +107 -105
  21. package/dist/shaka-player.ui.debug.map +4 -4
  22. package/dist/shaka-player.ui.externs.js +19 -23
  23. package/dist/shaka-player.ui.js +952 -952
  24. package/dist/shaka-player.ui.map +2 -2
  25. package/karma.conf.js +19 -15
  26. package/lib/ads/ad_manager.js +1 -0
  27. package/lib/cast/cast_proxy.js +3 -0
  28. package/lib/cast/cast_receiver.js +3 -0
  29. package/lib/dash/dash_parser.js +31 -19
  30. package/lib/hls/manifest_text_parser.js +1 -0
  31. package/lib/media/quality_observer.js +17 -34
  32. package/lib/media/region_observer.js +39 -33
  33. package/lib/media/region_timeline.js +17 -19
  34. package/lib/media/streaming_engine.js +1 -1
  35. package/lib/net/networking_engine.js +4 -0
  36. package/lib/offline/storage.js +8 -9
  37. package/lib/player.js +32 -10
  38. package/lib/util/fake_event_target.js +21 -1
  39. package/package.json +3 -6
  40. package/test/dash/dash_parser_live_unit.js +43 -0
  41. package/test/dash/dash_parser_manifest_unit.js +2 -1
  42. package/test/hls/hls_parser_unit.js +74 -0
  43. package/test/media/quality_observer_unit.js +4 -1
  44. package/test/media/region_observer_unit.js +40 -5
  45. package/test/media/region_timeline_unit.js +40 -22
  46. package/test/test/util/util.js +10 -7
  47. package/test/ui/text_displayer_layout_unit.js +8 -7
  48. package/ui/controls.js +3 -0
  49. package/ui/localization.js +12 -36
package/karma.conf.js CHANGED
@@ -11,6 +11,7 @@ const Jimp = require('jimp');
11
11
  const fs = require('fs');
12
12
  const path = require('path');
13
13
  const rimraf = require('rimraf');
14
+ const {ssim} = require('ssim.js');
14
15
  const util = require('karma/common/util');
15
16
  const which = require('which');
16
17
 
@@ -336,6 +337,10 @@ module.exports = (config) => {
336
337
 
337
338
  config.set({reporters: reporters});
338
339
 
340
+ if (reporters.includes('spec') && settings.spec_hide_passed) {
341
+ config.set({specReporter: {suppressPassed: true}});
342
+ }
343
+
339
344
  if (settings.random) {
340
345
  // If --seed was specified use that value, else generate a seed so that the
341
346
  // exact order can be reproduced if it catches an issue.
@@ -518,8 +523,7 @@ function WebDriverScreenshotMiddlewareFactory(launcher) {
518
523
  *
519
524
  * @param {karma.Launcher.Browser} browser
520
525
  * @param {!Object.<string, string>} params
521
- * @return {!Promise.<number>} The number of pixels changed between the old
522
- * and new screenshots, after cropping and scaling.
526
+ * @return {!Promise.<number>} A similarity score between 0 and 1.
523
527
  */
524
528
  async function diffScreenshot(browser, params) {
525
529
  const webDriverClient = getWebDriverClient(browser);
@@ -604,22 +608,22 @@ function WebDriverScreenshotMiddlewareFactory(launcher) {
604
608
  // Compare the new screenshot to the old one and produce a diff image.
605
609
  // Initially, the image data will be raw pixels, 4 bytes per pixel.
606
610
  // The threshold parameter affects the sensitivity of individual pixel
607
- // comparisons. Setting it too low means small rendering changes in the
608
- // browser can cause failures even when a human can't see the difference,
609
- // and setting it too high means human-noticeable changes could go
610
- // undetected by a test.
611
- const diff = Jimp.diff(oldScreenshot, newScreenshot, /* threshold= */ 0.07);
611
+ // comparisons. This diff is only used for visual review, not for
612
+ // automated similarity checks, so the threshold setting is not so critical
613
+ // as it used to be.
614
+ const threshold = 0.10;
615
+ const diff = Jimp.diff(oldScreenshot, newScreenshot, threshold);
612
616
 
613
617
  // Write the diff to disk. This is used to review when there are changes.
618
+ const fullSizeDiff =
619
+ diff.image.clone().resize(width, height, Jimp.RESIZE_BICUBIC);
614
620
  fs.writeFileSync(
615
- diffScreenshotPath, await diff.image.getBufferAsync('image/png'));
616
-
617
- // "percent" is, surprisingly, a number between 0 and 1, not between 0 and
618
- // 100. Convert this to a number of pixels changed, which has been found to
619
- // be a more effective and less flaky metric.
620
- const pixelsChanged =
621
- diff.percent * diff.image.bitmap.width * diff.image.bitmap.height;
622
- return pixelsChanged;
621
+ diffScreenshotPath, await fullSizeDiff.getBufferAsync('image/png'));
622
+
623
+ // Compare with a structural similarity algorithm. This produces a
624
+ // similarity score that we will use to pass or fail the test.
625
+ const ssimResult = ssim(oldScreenshot.bitmap, newScreenshot.bitmap);
626
+ return ssimResult.mssim; // A score between 0 and 1.
623
627
  }
624
628
 
625
629
  /**
@@ -448,6 +448,7 @@ shaka.ads.AdManager = class extends shaka.util.FakeEventTarget {
448
448
  this.ssAdManager_.release();
449
449
  this.ssAdManager_ = null;
450
450
  }
451
+ super.release();
451
452
  }
452
453
 
453
454
 
@@ -120,6 +120,9 @@ shaka.cast.CastProxy = class extends shaka.util.FakeEventTarget {
120
120
  this.videoProxy_ = null;
121
121
  this.playerProxy_ = null;
122
122
 
123
+ // FakeEventTarget implements IReleasable
124
+ super.release();
125
+
123
126
  return Promise.all(waitFor);
124
127
  }
125
128
 
@@ -238,6 +238,9 @@ shaka.cast.CastReceiver = class extends shaka.util.FakeEventTarget {
238
238
  this.shakaBus_ = null;
239
239
  this.genericBus_ = null;
240
240
 
241
+ // FakeEventTarget implements IReleasable
242
+ super.release();
243
+
241
244
  await Promise.all(waitFor);
242
245
 
243
246
  const manager = cast.receiver.CastReceiverManager.getInstance();
@@ -410,6 +410,24 @@ shaka.dash.DashParser = class {
410
410
  this.config_.dash.autoCorrectDrift);
411
411
  }
412
412
 
413
+ presentationTimeline.setStatic(mpdType == 'static');
414
+
415
+ const isLive = presentationTimeline.isLive();
416
+
417
+ // If it's live, we check for an override.
418
+ if (isLive && !isNaN(this.config_.availabilityWindowOverride)) {
419
+ segmentAvailabilityDuration = this.config_.availabilityWindowOverride;
420
+ }
421
+
422
+ // If it's null, that means segments are always available. This is always
423
+ // the case for VOD, and sometimes the case for live.
424
+ if (segmentAvailabilityDuration == null) {
425
+ segmentAvailabilityDuration = Infinity;
426
+ }
427
+
428
+ presentationTimeline.setSegmentAvailabilityDuration(
429
+ segmentAvailabilityDuration);
430
+
413
431
  const profiles = mpd.getAttribute('profiles') || '';
414
432
 
415
433
  /** @type {shaka.dash.DashParser.Context} */
@@ -432,7 +450,6 @@ shaka.dash.DashParser = class {
432
450
  const duration = periodsAndDuration.duration;
433
451
  const periods = periodsAndDuration.periods;
434
452
 
435
- presentationTimeline.setStatic(mpdType == 'static');
436
453
  if (mpdType == 'static' ||
437
454
  !periodsAndDuration.durationDerivedFromPeriods) {
438
455
  // Ignore duration calculated from Period lengths if this is dynamic.
@@ -451,6 +468,7 @@ shaka.dash.DashParser = class {
451
468
  this.lowLatencyMode_ = this.playerInterface_.isLowLatencyMode();
452
469
  }
453
470
  }
471
+
454
472
  if (this.lowLatencyMode_) {
455
473
  presentationTimeline.setAvailabilityTimeOffset(
456
474
  this.minTotalAvailabilityTimeOffset_);
@@ -464,22 +482,6 @@ shaka.dash.DashParser = class {
464
482
  'https://bit.ly/3clctcj for details.');
465
483
  }
466
484
 
467
- const isLive = presentationTimeline.isLive();
468
-
469
- // If it's live, we check for an override.
470
- if (isLive && !isNaN(this.config_.availabilityWindowOverride)) {
471
- segmentAvailabilityDuration = this.config_.availabilityWindowOverride;
472
- }
473
-
474
- // If it's null, that means segments are always available. This is always
475
- // the case for VOD, and sometimes the case for live.
476
- if (segmentAvailabilityDuration == null) {
477
- segmentAvailabilityDuration = Infinity;
478
- }
479
-
480
- presentationTimeline.setSegmentAvailabilityDuration(
481
- segmentAvailabilityDuration);
482
-
483
485
  // Use @maxSegmentDuration to override smaller, derived values.
484
486
  presentationTimeline.notifyMaxSegmentDuration(maxSegmentDuration || 1);
485
487
  if (goog.DEBUG) {
@@ -714,8 +716,12 @@ shaka.dash.DashParser = class {
714
716
 
715
717
  const eventStreamNodes =
716
718
  XmlUtils.findChildren(periodInfo.node, 'EventStream');
719
+ const availabilityStart =
720
+ context.presentationTimeline.getSegmentAvailabilityStart();
721
+
717
722
  for (const node of eventStreamNodes) {
718
- this.parseEventStream_(periodInfo.start, periodInfo.duration, node);
723
+ this.parseEventStream_(
724
+ periodInfo.start, periodInfo.duration, node, availabilityStart);
719
725
  }
720
726
 
721
727
  const adaptationSetNodes =
@@ -1769,9 +1775,10 @@ shaka.dash.DashParser = class {
1769
1775
  * @param {number} periodStart
1770
1776
  * @param {?number} periodDuration
1771
1777
  * @param {!Element} elem
1778
+ * @param {number} availabilityStart
1772
1779
  * @private
1773
1780
  */
1774
- parseEventStream_(periodStart, periodDuration, elem) {
1781
+ parseEventStream_(periodStart, periodDuration, elem, availabilityStart) {
1775
1782
  const XmlUtils = shaka.util.XmlUtils;
1776
1783
  const parseNumber = XmlUtils.parseNonNegativeInt;
1777
1784
 
@@ -1794,6 +1801,11 @@ shaka.dash.DashParser = class {
1794
1801
  endTime = Math.min(endTime, periodStart + periodDuration);
1795
1802
  }
1796
1803
 
1804
+ // Don't add unavailable regions to the timeline.
1805
+ if (endTime < availabilityStart) {
1806
+ continue;
1807
+ }
1808
+
1797
1809
  /** @type {shaka.extern.TimelineRegionInfo} */
1798
1810
  const region = {
1799
1811
  schemeIdUri: schemeIdUri,
@@ -277,6 +277,7 @@ shaka.hls.ManifestTextParser = class {
277
277
  const attrValue = blockAttrs[2] || blockAttrs[3];
278
278
  const attribute = new shaka.hls.Attribute(attrName, attrValue);
279
279
  attributes.push(attribute);
280
+ parser.skipWhitespace();
280
281
  }
281
282
  }
282
283
 
@@ -8,48 +8,40 @@ goog.provide('shaka.media.QualityObserver');
8
8
 
9
9
  goog.require('shaka.media.IPlayheadObserver');
10
10
  goog.require('shaka.log');
11
+ goog.require('shaka.util.FakeEvent');
12
+ goog.require('shaka.util.FakeEventTarget');
11
13
 
12
14
  /**
13
- * Monitors the quality of content being appended to the source
14
- * buffers and fires onQualityChange events when the media quality
15
- * at the playhead changes.
15
+ * Monitors the quality of content being appended to the source buffers and
16
+ * fires 'qualitychange' events when the media quality at the playhead changes.
16
17
  *
17
18
  * @implements {shaka.media.IPlayheadObserver}
18
19
  * @final
19
20
  */
20
- shaka.media.QualityObserver = class {
21
+ shaka.media.QualityObserver = class extends shaka.util.FakeEventTarget {
21
22
  /**
22
23
  * Creates a new QualityObserver.
23
24
  *
24
25
  * @param {!function():!shaka.extern.BufferedInfo} getBufferedInfo
25
- * Buffered info is needed to purge QualityChanges that are no
26
- * longer relevant.
26
+ * Buffered info is needed to purge QualityChanges that are no
27
+ * longer relevant.
27
28
  */
28
29
  constructor(getBufferedInfo) {
30
+ super();
31
+
29
32
  /**
30
33
  * @private {!Map.<string,!shaka.media.QualityObserver.ContentTypeState>}
31
34
  */
32
35
  this.contentTypeStates_ = new Map();
33
36
 
34
- /** @private {shaka.media.QualityObserver.EventListener} */
35
- this.onQualityChange_ = (mediaQuality, position) => {};
36
-
37
37
  /** @private function():!shaka.extern.BufferedInfo */
38
38
  this.getBufferedInfo_ = getBufferedInfo;
39
39
  }
40
40
 
41
- /**
42
- * Set all the listeners. This overrides any previous calls to |setListeners|.
43
- *
44
- * @param {shaka.media.QualityObserver.EventListener} onQualityChange
45
- */
46
- setListeners(onQualityChange) {
47
- this.onQualityChange_ = onQualityChange;
48
- }
49
-
50
41
  /** @override */
51
42
  release() {
52
43
  this.contentTypeStates_.clear();
44
+ super.release();
53
45
  }
54
46
 
55
47
  /**
@@ -169,10 +161,15 @@ shaka.media.QualityObserver = class {
169
161
  if (this.positionIsBuffered_(
170
162
  positionInSeconds, qualityAtPosition.contentType)) {
171
163
  contentTypeState.currentQuality = qualityAtPosition;
164
+
172
165
  shaka.log.debug('Media quality changed at position ' +
173
166
  positionInSeconds + ' ' + JSON.stringify(qualityAtPosition));
174
- this.onQualityChange_(
175
- qualityAtPosition, positionInSeconds);
167
+
168
+ const event = new shaka.util.FakeEvent('qualitychange', new Map([
169
+ ['quality', qualityAtPosition],
170
+ ['position', positionInSeconds],
171
+ ]));
172
+ this.dispatchEvent(event);
176
173
  }
177
174
  }
178
175
  }
@@ -237,20 +234,6 @@ shaka.media.QualityObserver = class {
237
234
  }
238
235
  };
239
236
 
240
- /**
241
- * @typedef {function(shaka.extern.MediaQualityInfo, number)}
242
- *
243
- * @description
244
- * A callback function used to notify the player when media quality changes
245
- * are detected at the playhead.
246
- *
247
- * The first argument is information about media quality at the playhead
248
- * position.
249
- *
250
- * The second argument is the playhead position in seconds.
251
- */
252
- shaka.media.QualityObserver.EventListener;
253
-
254
237
  /**
255
238
  * @typedef {{
256
239
  * mediaQuality: !shaka.extern.MediaQualityInfo,
@@ -8,16 +8,19 @@ goog.provide('shaka.media.RegionObserver');
8
8
 
9
9
  goog.require('shaka.media.IPlayheadObserver');
10
10
  goog.require('shaka.media.RegionTimeline');
11
+ goog.require('shaka.util.EventManager');
12
+ goog.require('shaka.util.FakeEvent');
13
+ goog.require('shaka.util.FakeEventTarget');
11
14
 
12
15
 
13
16
  /**
14
17
  * The region observer watches a region timeline and playhead, and fires events
15
- * (onEnter, onExit, and onSkip) as the playhead moves.
18
+ * ('enter', 'exit', 'skip') as the playhead moves.
16
19
  *
17
20
  * @implements {shaka.media.IPlayheadObserver}
18
21
  * @final
19
22
  */
20
- shaka.media.RegionObserver = class {
23
+ shaka.media.RegionObserver = class extends shaka.util.FakeEventTarget {
21
24
  /**
22
25
  * Create a region observer for the given timeline. The observer does not
23
26
  * own the timeline, only uses it. This means that the observer should NOT
@@ -26,6 +29,8 @@ shaka.media.RegionObserver = class {
26
29
  * @param {!shaka.media.RegionTimeline} timeline
27
30
  */
28
31
  constructor(timeline) {
32
+ super();
33
+
29
34
  /** @private {shaka.media.RegionTimeline} */
30
35
  this.timeline_ = timeline;
31
36
 
@@ -39,13 +44,6 @@ shaka.media.RegionObserver = class {
39
44
  */
40
45
  this.oldPosition_ = new Map();
41
46
 
42
- /** @private {shaka.media.RegionObserver.EventListener} */
43
- this.onEnter_ = (region, seeking) => {};
44
- /** @private {shaka.media.RegionObserver.EventListener} */
45
- this.onExit_ = (region, seeking) => {};
46
- /** @private {shaka.media.RegionObserver.EventListener} */
47
- this.onSkip_ = (region, seeking) => {};
48
-
49
47
  // To make the rules easier to read, alias all the relative positions.
50
48
  const RelativePosition = shaka.media.RegionObserver.RelativePosition_;
51
49
  const BEFORE_THE_REGION = RelativePosition.BEFORE_THE_REGION;
@@ -62,39 +60,48 @@ shaka.media.RegionObserver = class {
62
60
  {
63
61
  weWere: null,
64
62
  weAre: IN_THE_REGION,
65
- invoke: (region, seeking) => this.onEnter_(region, seeking),
63
+ invoke: (region, seeking) => this.onEvent_('enter', region, seeking),
66
64
  },
67
65
  {
68
66
  weWere: BEFORE_THE_REGION,
69
67
  weAre: IN_THE_REGION,
70
- invoke: (region, seeking) => this.onEnter_(region, seeking),
68
+ invoke: (region, seeking) => this.onEvent_('enter', region, seeking),
71
69
  },
72
70
  {
73
71
  weWere: AFTER_THE_REGION,
74
72
  weAre: IN_THE_REGION,
75
- invoke: (region, seeking) => this.onEnter_(region, seeking),
73
+ invoke: (region, seeking) => this.onEvent_('enter', region, seeking),
76
74
  },
77
75
  {
78
76
  weWere: IN_THE_REGION,
79
77
  weAre: BEFORE_THE_REGION,
80
- invoke: (region, seeking) => this.onExit_(region, seeking),
78
+ invoke: (region, seeking) => this.onEvent_('exit', region, seeking),
81
79
  },
82
80
  {
83
81
  weWere: IN_THE_REGION,
84
82
  weAre: AFTER_THE_REGION,
85
- invoke: (region, seeking) => this.onExit_(region, seeking),
83
+ invoke: (region, seeking) => this.onEvent_('exit', region, seeking),
86
84
  },
87
85
  {
88
86
  weWere: BEFORE_THE_REGION,
89
87
  weAre: AFTER_THE_REGION,
90
- invoke: (region, seeking) => this.onSkip_(region, seeking),
88
+ invoke: (region, seeking) => this.onEvent_('skip', region, seeking),
91
89
  },
92
90
  {
93
91
  weWere: AFTER_THE_REGION,
94
92
  weAre: BEFORE_THE_REGION,
95
- invoke: (region, seeking) => this.onSkip_(region, seeking),
93
+ invoke: (region, seeking) => this.onEvent_('skip', region, seeking),
96
94
  },
97
95
  ];
96
+
97
+ /** @private {shaka.util.EventManager} */
98
+ this.eventManager_ = new shaka.util.EventManager();
99
+
100
+ this.eventManager_.listen(this.timeline_, 'regionremove', (event) => {
101
+ /** @type {shaka.extern.TimelineRegionInfo} */
102
+ const region = event['region'];
103
+ this.oldPosition_.delete(region);
104
+ });
98
105
  }
99
106
 
100
107
  /** @override */
@@ -105,11 +112,10 @@ shaka.media.RegionObserver = class {
105
112
  // needed.
106
113
  this.oldPosition_.clear();
107
114
 
108
- // Clear the callbacks so that we don't hold onto any references external
109
- // to this class.
110
- this.onEnter_ = (region, seeking) => {};
111
- this.onExit_ = (region, seeking) => {};
112
- this.onSkip_ = (region, seeking) => {};
115
+ this.eventManager_.release();
116
+ this.eventManager_ = null;
117
+
118
+ super.release();
113
119
  }
114
120
 
115
121
  /** @override */
@@ -134,20 +140,20 @@ shaka.media.RegionObserver = class {
134
140
  }
135
141
 
136
142
  /**
137
- * Set all the listeners. This overrides any previous calls to |setListeners|.
143
+ * Dispatch events of the given type. All event types in this class have the
144
+ * same parameters: region and seeking.
138
145
  *
139
- * @param {shaka.media.RegionObserver.EventListener} onEnter
140
- * The callback for when we move from outside a region to inside a region.
141
- * @param {shaka.media.RegionObserver.EventListener} onExit
142
- * The callback for when we move from inside a region to outside a region.
143
- * @param {shaka.media.RegionObserver.EventListener} onSkip
144
- * The callback for when we move from before to after a region or from
145
- * after to before a region.
146
+ * @param {string} eventType
147
+ * @param {shaka.extern.TimelineRegionInfo} region
148
+ * @param {boolean} seeking
149
+ * @private
146
150
  */
147
- setListeners(onEnter, onExit, onSkip) {
148
- this.onEnter_ = onEnter;
149
- this.onExit_ = onExit;
150
- this.onSkip_ = onSkip;
151
+ onEvent_(eventType, region, seeking) {
152
+ const event = new shaka.util.FakeEvent(eventType, new Map([
153
+ ['region', region],
154
+ ['seeking', seeking],
155
+ ]));
156
+ this.dispatchEvent(event);
151
157
  }
152
158
 
153
159
  /**
@@ -6,26 +6,30 @@
6
6
 
7
7
  goog.provide('shaka.media.RegionTimeline');
8
8
 
9
+ goog.require('shaka.util.FakeEvent');
10
+ goog.require('shaka.util.FakeEventTarget');
9
11
  goog.require('shaka.util.IReleasable');
10
12
  goog.require('shaka.util.Timer');
11
13
 
12
14
 
13
15
  /**
14
16
  * The region timeline is a set of unique timeline region info entries. When
15
- * a new entry is added, the |onAddRegion| callback will be called.
17
+ * a new entry is added, the 'regionadd' event will be fired. When an entry is
18
+ * deleted, the 'regionremove' event will be fired.
16
19
  *
17
20
  * @implements {shaka.util.IReleasable}
18
21
  * @final
19
22
  */
20
- shaka.media.RegionTimeline = class {
23
+ shaka.media.RegionTimeline = class extends shaka.util.FakeEventTarget {
21
24
  /**
22
25
  * @param {!function():{start: number, end: number}} getSeekRange
23
26
  */
24
27
  constructor(getSeekRange) {
25
- /** @private {function(shaka.extern.TimelineRegionInfo)} */
26
- this.onAddRegion_ = (region) => {};
28
+ super();
29
+
27
30
  /** @private {!Set.<shaka.extern.TimelineRegionInfo>} */
28
31
  this.regions_ = new Set();
32
+
29
33
  /** @private {!function():{start: number, end: number}} */
30
34
  this.getSeekRange_ = getSeekRange;
31
35
 
@@ -44,22 +48,9 @@ shaka.media.RegionTimeline = class {
44
48
 
45
49
  /** @override */
46
50
  release() {
47
- // Prevent us from holding onto any external references via the callback.
48
- this.onAddRegion_ = (region) => {};
49
51
  this.regions_.clear();
50
52
  this.filterTimer_.stop();
51
- }
52
-
53
- /**
54
- * Set the callbacks for events. This will override any previous calls to
55
- * |setListeners|.
56
- *
57
- * @param {function(shaka.extern.TimelineRegionInfo)} onAddRegion
58
- * Set the callback for when we add a new region. This callback will only
59
- * be called when a region is unique (we reject duplicate regions).
60
- */
61
- setListeners(onAddRegion) {
62
- this.onAddRegion_ = onAddRegion;
53
+ super.release();
63
54
  }
64
55
 
65
56
  /**
@@ -72,7 +63,10 @@ shaka.media.RegionTimeline = class {
72
63
  // instead of making the parser track it.
73
64
  if (similarRegion == null) {
74
65
  this.regions_.add(region);
75
- this.onAddRegion_(region);
66
+ const event = new shaka.util.FakeEvent('regionadd', new Map([
67
+ ['region', region],
68
+ ]));
69
+ this.dispatchEvent(event);
76
70
  }
77
71
  }
78
72
 
@@ -89,6 +83,10 @@ shaka.media.RegionTimeline = class {
89
83
  // reson to store or process them.
90
84
  if (region.endTime < seekRange.start) {
91
85
  this.regions_.delete(region);
86
+ const event = new shaka.util.FakeEvent('regionremove', new Map([
87
+ ['region', region],
88
+ ]));
89
+ this.dispatchEvent(event);
92
90
  }
93
91
  }
94
92
  }
@@ -1659,7 +1659,7 @@ shaka.media.StreamingEngine = class {
1659
1659
  // See DASH sec. 5.10.3.3.1
1660
1660
  // If a DASH client detects an event message box with a scheme that is not
1661
1661
  // defined in MPD, the client is expected to ignore it.
1662
- if (emsgSchemeIdUris.includes(schemeId) ||
1662
+ if ((emsgSchemeIdUris && emsgSchemeIdUris.includes(schemeId)) ||
1663
1663
  this.config_.dispatchAllEmsgBoxes) {
1664
1664
  // See DASH sec. 5.10.4.1
1665
1665
  // A special scheme in DASH used to signal manifest updates.
@@ -232,6 +232,10 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
232
232
  this.destroyed_ = true;
233
233
  this.requestFilters_.clear();
234
234
  this.responseFilters_.clear();
235
+
236
+ // FakeEventTarget implements IReleasable
237
+ super.release();
238
+
235
239
  return this.operationManager_.destroy();
236
240
  }
237
241
 
@@ -681,16 +681,15 @@ shaka.offline.Storage = class {
681
681
  * @private
682
682
  */
683
683
  async filterManifest_(manifest, drmEngine, config) {
684
- const StreamUtils = shaka.util.StreamUtils;
685
-
686
684
  // Filter the manifest based on the restrictions given in the player
687
685
  // configuration.
688
686
  const maxHwRes = {width: Infinity, height: Infinity};
689
- StreamUtils.filterByRestrictions(manifest, config.restrictions, maxHwRes);
687
+ shaka.util.StreamUtils.filterByRestrictions(
688
+ manifest, config.restrictions, maxHwRes);
690
689
 
691
690
  // Filter the manifest based on what we know MediaCapabilities will be able
692
691
  // to play later (no point storing something we can't play).
693
- StreamUtils.filterManifestByMediaCapabilities(
692
+ await shaka.util.StreamUtils.filterManifestByMediaCapabilities(
694
693
  manifest, config.offline.usePersistentLicense);
695
694
 
696
695
  // Gather all tracks.
@@ -702,24 +701,24 @@ shaka.offline.Storage = class {
702
701
  const preferredVideoCodecs = config.preferredVideoCodecs;
703
702
  const preferredAudioCodecs = config.preferredAudioCodecs;
704
703
 
705
- StreamUtils.chooseCodecsAndFilterManifest(
704
+ shaka.util.StreamUtils.chooseCodecsAndFilterManifest(
706
705
  manifest, preferredVideoCodecs, preferredAudioCodecs,
707
706
  preferredAudioChannelCount, preferredDecodingAttributes);
708
707
 
709
708
  for (const variant of manifest.variants) {
710
709
  goog.asserts.assert(
711
- StreamUtils.isPlayable(variant),
710
+ shaka.util.StreamUtils.isPlayable(variant),
712
711
  'We should have already filtered by "is playable"');
713
712
 
714
- allTracks.push(StreamUtils.variantToTrack(variant));
713
+ allTracks.push(shaka.util.StreamUtils.variantToTrack(variant));
715
714
  }
716
715
 
717
716
  for (const text of manifest.textStreams) {
718
- allTracks.push(StreamUtils.textStreamToTrack(text));
717
+ allTracks.push(shaka.util.StreamUtils.textStreamToTrack(text));
719
718
  }
720
719
 
721
720
  for (const image of manifest.imageStreams) {
722
- allTracks.push(StreamUtils.imageStreamToTrack(image));
721
+ allTracks.push(shaka.util.StreamUtils.imageStreamToTrack(image));
723
722
  }
724
723
 
725
724
  // Let the application choose which tracks to store.