shaka-player 3.1.5 → 3.1.6

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 (47) 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 +3 -3
  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 +5 -0
  12. package/dist/shaka-player.compiled.debug.js +200 -198
  13. package/dist/shaka-player.compiled.debug.map +3 -3
  14. package/dist/shaka-player.compiled.externs.js +5 -0
  15. package/dist/shaka-player.compiled.js +609 -608
  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 +77 -76
  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 +901 -901
  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/region_observer.js +39 -33
  32. package/lib/media/region_timeline.js +17 -19
  33. package/lib/media/streaming_engine.js +1 -1
  34. package/lib/net/networking_engine.js +4 -0
  35. package/lib/offline/storage.js +10 -11
  36. package/lib/player.js +25 -9
  37. package/lib/util/fake_event_target.js +21 -1
  38. package/package.json +3 -6
  39. package/test/dash/dash_parser_live_unit.js +43 -0
  40. package/test/dash/dash_parser_manifest_unit.js +2 -1
  41. package/test/hls/hls_parser_unit.js +74 -0
  42. package/test/media/region_observer_unit.js +40 -5
  43. package/test/media/region_timeline_unit.js +40 -22
  44. package/test/test/util/util.js +10 -7
  45. package/test/ui/text_displayer_layout_unit.js +8 -7
  46. package/ui/controls.js +3 -0
  47. 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
  /**
@@ -449,6 +449,7 @@ shaka.ads.AdManager = class extends shaka.util.FakeEventTarget {
449
449
  this.ssAdManager_.release();
450
450
  this.ssAdManager_ = null;
451
451
  }
452
+ super.release();
452
453
  }
453
454
 
454
455
 
@@ -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();
@@ -396,6 +396,24 @@ shaka.dash.DashParser = class {
396
396
  this.config_.dash.autoCorrectDrift);
397
397
  }
398
398
 
399
+ presentationTimeline.setStatic(mpdType == 'static');
400
+
401
+ const isLive = presentationTimeline.isLive();
402
+
403
+ // If it's live, we check for an override.
404
+ if (isLive && !isNaN(this.config_.availabilityWindowOverride)) {
405
+ segmentAvailabilityDuration = this.config_.availabilityWindowOverride;
406
+ }
407
+
408
+ // If it's null, that means segments are always available. This is always
409
+ // the case for VOD, and sometimes the case for live.
410
+ if (segmentAvailabilityDuration == null) {
411
+ segmentAvailabilityDuration = Infinity;
412
+ }
413
+
414
+ presentationTimeline.setSegmentAvailabilityDuration(
415
+ segmentAvailabilityDuration);
416
+
399
417
  const profiles = mpd.getAttribute('profiles') || '';
400
418
 
401
419
  /** @type {shaka.dash.DashParser.Context} */
@@ -418,7 +436,6 @@ shaka.dash.DashParser = class {
418
436
  const duration = periodsAndDuration.duration;
419
437
  const periods = periodsAndDuration.periods;
420
438
 
421
- presentationTimeline.setStatic(mpdType == 'static');
422
439
  if (mpdType == 'static' ||
423
440
  !periodsAndDuration.durationDerivedFromPeriods) {
424
441
  // Ignore duration calculated from Period lengths if this is dynamic.
@@ -437,6 +454,7 @@ shaka.dash.DashParser = class {
437
454
  this.lowLatencyMode_ = this.playerInterface_.isLowLatencyMode();
438
455
  }
439
456
  }
457
+
440
458
  if (this.lowLatencyMode_) {
441
459
  presentationTimeline.setAvailabilityTimeOffset(
442
460
  this.minTotalAvailabilityTimeOffset_);
@@ -450,22 +468,6 @@ shaka.dash.DashParser = class {
450
468
  'https://bit.ly/3clctcj for details.');
451
469
  }
452
470
 
453
- const isLive = presentationTimeline.isLive();
454
-
455
- // If it's live, we check for an override.
456
- if (isLive && !isNaN(this.config_.availabilityWindowOverride)) {
457
- segmentAvailabilityDuration = this.config_.availabilityWindowOverride;
458
- }
459
-
460
- // If it's null, that means segments are always available. This is always
461
- // the case for VOD, and sometimes the case for live.
462
- if (segmentAvailabilityDuration == null) {
463
- segmentAvailabilityDuration = Infinity;
464
- }
465
-
466
- presentationTimeline.setSegmentAvailabilityDuration(
467
- segmentAvailabilityDuration);
468
-
469
471
  // Use @maxSegmentDuration to override smaller, derived values.
470
472
  presentationTimeline.notifyMaxSegmentDuration(maxSegmentDuration || 1);
471
473
  if (goog.DEBUG) {
@@ -700,8 +702,12 @@ shaka.dash.DashParser = class {
700
702
 
701
703
  const eventStreamNodes =
702
704
  XmlUtils.findChildren(periodInfo.node, 'EventStream');
705
+ const availabilityStart =
706
+ context.presentationTimeline.getSegmentAvailabilityStart();
707
+
703
708
  for (const node of eventStreamNodes) {
704
- this.parseEventStream_(periodInfo.start, periodInfo.duration, node);
709
+ this.parseEventStream_(
710
+ periodInfo.start, periodInfo.duration, node, availabilityStart);
705
711
  }
706
712
 
707
713
  const adaptationSetNodes =
@@ -1732,9 +1738,10 @@ shaka.dash.DashParser = class {
1732
1738
  * @param {number} periodStart
1733
1739
  * @param {?number} periodDuration
1734
1740
  * @param {!Element} elem
1741
+ * @param {number} availabilityStart
1735
1742
  * @private
1736
1743
  */
1737
- parseEventStream_(periodStart, periodDuration, elem) {
1744
+ parseEventStream_(periodStart, periodDuration, elem, availabilityStart) {
1738
1745
  const XmlUtils = shaka.util.XmlUtils;
1739
1746
  const parseNumber = XmlUtils.parseNonNegativeInt;
1740
1747
 
@@ -1757,6 +1764,11 @@ shaka.dash.DashParser = class {
1757
1764
  endTime = Math.min(endTime, periodStart + periodDuration);
1758
1765
  }
1759
1766
 
1767
+ // Don't add unavailable regions to the timeline.
1768
+ if (endTime < availabilityStart) {
1769
+ continue;
1770
+ }
1771
+
1760
1772
  /** @type {shaka.extern.TimelineRegionInfo} */
1761
1773
  const region = {
1762
1774
  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,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
  }
@@ -1644,7 +1644,7 @@ shaka.media.StreamingEngine = class {
1644
1644
  // See DASH sec. 5.10.3.3.1
1645
1645
  // If a DASH client detects an event message box with a scheme that is not
1646
1646
  // defined in MPD, the client is expected to ignore it.
1647
- if (emsgSchemeIdUris.includes(schemeId)) {
1647
+ if (emsgSchemeIdUris && emsgSchemeIdUris.includes(schemeId)) {
1648
1648
  // See DASH sec. 5.10.4.1
1649
1649
  // A special scheme in DASH used to signal manifest updates.
1650
1650
  if (schemeId == 'urn:mpeg:dash:event:2012') {
@@ -222,6 +222,10 @@ shaka.net.NetworkingEngine = class extends shaka.util.FakeEventTarget {
222
222
  this.destroyed_ = true;
223
223
  this.requestFilters_.clear();
224
224
  this.responseFilters_.clear();
225
+
226
+ // FakeEventTarget implements IReleasable
227
+ super.release();
228
+
225
229
  return this.operationManager_.destroy();
226
230
  }
227
231
 
@@ -477,23 +477,22 @@ shaka.offline.Storage = class {
477
477
  * @private
478
478
  */
479
479
  async filterManifest_(manifest, drmEngine, config) {
480
- const StreamUtils = shaka.util.StreamUtils;
481
-
482
480
  // Filter the manifest based on the restrictions given in the player
483
481
  // configuration.
484
482
  const maxHwRes = {width: Infinity, height: Infinity};
485
- StreamUtils.filterByRestrictions(manifest, config.restrictions, maxHwRes);
483
+ shaka.util.StreamUtils.filterByRestrictions(
484
+ manifest, config.restrictions, maxHwRes);
486
485
 
487
486
  // Filter the manifest based on what we know media source will be able to
488
487
  // play later (no point storing something we can't play).
489
488
  if (config.useMediaCapabilities) {
490
- StreamUtils.filterManifestByMediaCapabilities(
489
+ await shaka.util.StreamUtils.filterManifestByMediaCapabilities(
491
490
  manifest, config.offline.usePersistentLicense);
492
491
  } else {
493
- StreamUtils.filterManifestByMediaSource(manifest);
492
+ shaka.util.StreamUtils.filterManifestByMediaSource(manifest);
494
493
  // Filter the manifest based on what we know our drm system will support
495
494
  // playing later.
496
- StreamUtils.filterManifestByDrm(manifest, drmEngine);
495
+ shaka.util.StreamUtils.filterManifestByDrm(manifest, drmEngine);
497
496
  }
498
497
 
499
498
  // Gather all tracks.
@@ -501,23 +500,23 @@ shaka.offline.Storage = class {
501
500
 
502
501
  // Choose the codec that has the lowest average bandwidth.
503
502
  const preferredAudioChannelCount = config.preferredAudioChannelCount;
504
- StreamUtils.chooseCodecsAndFilterManifest(
503
+ shaka.util.StreamUtils.chooseCodecsAndFilterManifest(
505
504
  manifest, preferredAudioChannelCount);
506
505
 
507
506
  for (const variant of manifest.variants) {
508
507
  goog.asserts.assert(
509
- StreamUtils.isPlayable(variant),
508
+ shaka.util.StreamUtils.isPlayable(variant),
510
509
  'We should have already filtered by "is playable"');
511
510
 
512
- allTracks.push(StreamUtils.variantToTrack(variant));
511
+ allTracks.push(shaka.util.StreamUtils.variantToTrack(variant));
513
512
  }
514
513
 
515
514
  for (const text of manifest.textStreams) {
516
- allTracks.push(StreamUtils.textStreamToTrack(text));
515
+ allTracks.push(shaka.util.StreamUtils.textStreamToTrack(text));
517
516
  }
518
517
 
519
518
  for (const image of manifest.imageStreams) {
520
- allTracks.push(StreamUtils.imageStreamToTrack(image));
519
+ allTracks.push(shaka.util.StreamUtils.imageStreamToTrack(image));
521
520
  }
522
521
 
523
522
  // Let the application choose which tracks to store.
package/lib/player.js CHANGED
@@ -726,6 +726,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
726
726
  await this.networkingEngine_.destroy();
727
727
  this.networkingEngine_ = null;
728
728
  }
729
+
730
+ // FakeEventTarget implements IReleasable
731
+ super.release();
729
732
  }
730
733
 
731
734
  /**
@@ -1627,8 +1630,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1627
1630
  // initialize it now even through it appears a little out-of-place.
1628
1631
  this.regionTimeline_ =
1629
1632
  new shaka.media.RegionTimeline(() => this.seekRange());
1630
- this.regionTimeline_.setListeners(/* onRegionAdded= */ (region) => {
1633
+ this.regionTimeline_.addEventListener('regionadd', (event) => {
1634
+ /** @type {shaka.extern.TimelineRegionInfo} */
1635
+ const region = event['region'];
1631
1636
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionAdded, region);
1637
+
1632
1638
  if (this.adManager_) {
1633
1639
  this.adManager_.onDashTimedMetadata(region);
1634
1640
  }
@@ -2519,21 +2525,31 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2519
2525
  // Create the region observer. This will allow us to notify the app when we
2520
2526
  // move in and out of timeline regions.
2521
2527
  const regionObserver = new shaka.media.RegionObserver(this.regionTimeline_);
2522
- const onEnterRegion = (region, seeking) => {
2528
+
2529
+ regionObserver.addEventListener('enter', (event) => {
2530
+ /** @type {shaka.extern.TimelineRegionInfo} */
2531
+ const region = event['region'];
2523
2532
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionEnter, region);
2524
- };
2525
- const onExitRegion = (region, seeking) => {
2533
+ });
2534
+
2535
+ regionObserver.addEventListener('exit', (event) => {
2536
+ /** @type {shaka.extern.TimelineRegionInfo} */
2537
+ const region = event['region'];
2526
2538
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionExit, region);
2527
- };
2528
- const onSkipRegion = (region, seeking) => {
2539
+ });
2540
+
2541
+ regionObserver.addEventListener('skip', (event) => {
2542
+ /** @type {shaka.extern.TimelineRegionInfo} */
2543
+ const region = event['region'];
2544
+ /** @type {boolean} */
2545
+ const seeking = event['seeking'];
2529
2546
  // If we are seeking, we don't want to surface the enter/exit events since
2530
2547
  // they didn't play through them.
2531
2548
  if (!seeking) {
2532
2549
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionEnter, region);
2533
2550
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionExit, region);
2534
2551
  }
2535
- };
2536
- regionObserver.setListeners(onEnterRegion, onExitRegion, onSkipRegion);
2552
+ });
2537
2553
 
2538
2554
  // Now that we have all our observers, create a manager for them.
2539
2555
  const manager = new shaka.media.PlayheadObserverManager(this.video_);
@@ -6102,7 +6118,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6102
6118
  * @define {string} A version number taken from git at compile time.
6103
6119
  * @export
6104
6120
  */
6105
- shaka.Player.version = 'v3.1.5-uncompiled';
6121
+ shaka.Player.version = 'v3.1.6-uncompiled'; // x-release-please-version
6106
6122
 
6107
6123
  // Initialize the deprecation system using the version string we just set
6108
6124
  // on the player.
@@ -9,6 +9,7 @@ goog.provide('shaka.util.FakeEventTarget');
9
9
  goog.require('goog.asserts');
10
10
  goog.require('shaka.log');
11
11
  goog.require('shaka.util.FakeEvent');
12
+ goog.require('shaka.util.IReleasable');
12
13
  goog.require('shaka.util.MultiMap');
13
14
 
14
15
 
@@ -18,13 +19,14 @@ goog.require('shaka.util.MultiMap');
18
19
  * to non-DOM classes. Only FakeEvents should be dispatched.
19
20
  *
20
21
  * @implements {EventTarget}
22
+ * @implements {shaka.util.IReleasable}
21
23
  * @exportInterface
22
24
  */
23
25
  shaka.util.FakeEventTarget = class {
24
26
  /** */
25
27
  constructor() {
26
28
  /**
27
- * @private {!shaka.util.MultiMap.<shaka.util.FakeEventTarget.ListenerType>}
29
+ * @private {shaka.util.MultiMap.<shaka.util.FakeEventTarget.ListenerType>}
28
30
  */
29
31
  this.listeners_ = new shaka.util.MultiMap();
30
32
 
@@ -46,6 +48,9 @@ shaka.util.FakeEventTarget = class {
46
48
  * @exportInterface
47
49
  */
48
50
  addEventListener(type, listener, options) {
51
+ if (!this.listeners_) {
52
+ return;
53
+ }
49
54
  this.listeners_.push(type, listener);
50
55
  }
51
56
 
@@ -73,6 +78,9 @@ shaka.util.FakeEventTarget = class {
73
78
  * @exportInterface
74
79
  */
75
80
  removeEventListener(type, listener, options) {
81
+ if (!this.listeners_) {
82
+ return;
83
+ }
76
84
  this.listeners_.remove(type, listener);
77
85
  }
78
86
 
@@ -90,6 +98,10 @@ shaka.util.FakeEventTarget = class {
90
98
  goog.asserts.assert(event instanceof shaka.util.FakeEvent,
91
99
  'FakeEventTarget can only dispatch FakeEvents!');
92
100
 
101
+ if (!this.listeners_) {
102
+ return true;
103
+ }
104
+
93
105
  let listeners = this.listeners_.get(event.type) || [];
94
106
  const universalListeners =
95
107
  this.listeners_.get(shaka.util.FakeEventTarget.ALL_EVENTS_);
@@ -129,6 +141,14 @@ shaka.util.FakeEventTarget = class {
129
141
 
130
142
  return event.defaultPrevented;
131
143
  }
144
+
145
+ /**
146
+ * @override
147
+ * @exportInterface
148
+ */
149
+ release() {
150
+ this.listeners_ = null;
151
+ }
132
152
  };
133
153
 
134
154
  /**