shaka-player 3.2.3 → 3.2.4

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 +30 -30
  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 +95 -93
  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 +610 -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 +109 -108
  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 +774 -773
  24. package/dist/shaka-player.ui.map +3 -3
  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 +8 -9
  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
  /**
@@ -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();
@@ -401,6 +401,24 @@ shaka.dash.DashParser = class {
401
401
  this.config_.dash.autoCorrectDrift);
402
402
  }
403
403
 
404
+ presentationTimeline.setStatic(mpdType == 'static');
405
+
406
+ const isLive = presentationTimeline.isLive();
407
+
408
+ // If it's live, we check for an override.
409
+ if (isLive && !isNaN(this.config_.availabilityWindowOverride)) {
410
+ segmentAvailabilityDuration = this.config_.availabilityWindowOverride;
411
+ }
412
+
413
+ // If it's null, that means segments are always available. This is always
414
+ // the case for VOD, and sometimes the case for live.
415
+ if (segmentAvailabilityDuration == null) {
416
+ segmentAvailabilityDuration = Infinity;
417
+ }
418
+
419
+ presentationTimeline.setSegmentAvailabilityDuration(
420
+ segmentAvailabilityDuration);
421
+
404
422
  const profiles = mpd.getAttribute('profiles') || '';
405
423
 
406
424
  /** @type {shaka.dash.DashParser.Context} */
@@ -423,7 +441,6 @@ shaka.dash.DashParser = class {
423
441
  const duration = periodsAndDuration.duration;
424
442
  const periods = periodsAndDuration.periods;
425
443
 
426
- presentationTimeline.setStatic(mpdType == 'static');
427
444
  if (mpdType == 'static' ||
428
445
  !periodsAndDuration.durationDerivedFromPeriods) {
429
446
  // Ignore duration calculated from Period lengths if this is dynamic.
@@ -442,6 +459,7 @@ shaka.dash.DashParser = class {
442
459
  this.lowLatencyMode_ = this.playerInterface_.isLowLatencyMode();
443
460
  }
444
461
  }
462
+
445
463
  if (this.lowLatencyMode_) {
446
464
  presentationTimeline.setAvailabilityTimeOffset(
447
465
  this.minTotalAvailabilityTimeOffset_);
@@ -455,22 +473,6 @@ shaka.dash.DashParser = class {
455
473
  'https://bit.ly/3clctcj for details.');
456
474
  }
457
475
 
458
- const isLive = presentationTimeline.isLive();
459
-
460
- // If it's live, we check for an override.
461
- if (isLive && !isNaN(this.config_.availabilityWindowOverride)) {
462
- segmentAvailabilityDuration = this.config_.availabilityWindowOverride;
463
- }
464
-
465
- // If it's null, that means segments are always available. This is always
466
- // the case for VOD, and sometimes the case for live.
467
- if (segmentAvailabilityDuration == null) {
468
- segmentAvailabilityDuration = Infinity;
469
- }
470
-
471
- presentationTimeline.setSegmentAvailabilityDuration(
472
- segmentAvailabilityDuration);
473
-
474
476
  // Use @maxSegmentDuration to override smaller, derived values.
475
477
  presentationTimeline.notifyMaxSegmentDuration(maxSegmentDuration || 1);
476
478
  if (goog.DEBUG) {
@@ -705,8 +707,12 @@ shaka.dash.DashParser = class {
705
707
 
706
708
  const eventStreamNodes =
707
709
  XmlUtils.findChildren(periodInfo.node, 'EventStream');
710
+ const availabilityStart =
711
+ context.presentationTimeline.getSegmentAvailabilityStart();
712
+
708
713
  for (const node of eventStreamNodes) {
709
- this.parseEventStream_(periodInfo.start, periodInfo.duration, node);
714
+ this.parseEventStream_(
715
+ periodInfo.start, periodInfo.duration, node, availabilityStart);
710
716
  }
711
717
 
712
718
  const adaptationSetNodes =
@@ -1737,9 +1743,10 @@ shaka.dash.DashParser = class {
1737
1743
  * @param {number} periodStart
1738
1744
  * @param {?number} periodDuration
1739
1745
  * @param {!Element} elem
1746
+ * @param {number} availabilityStart
1740
1747
  * @private
1741
1748
  */
1742
- parseEventStream_(periodStart, periodDuration, elem) {
1749
+ parseEventStream_(periodStart, periodDuration, elem, availabilityStart) {
1743
1750
  const XmlUtils = shaka.util.XmlUtils;
1744
1751
  const parseNumber = XmlUtils.parseNonNegativeInt;
1745
1752
 
@@ -1762,6 +1769,11 @@ shaka.dash.DashParser = class {
1762
1769
  endTime = Math.min(endTime, periodStart + periodDuration);
1763
1770
  }
1764
1771
 
1772
+ // Don't add unavailable regions to the timeline.
1773
+ if (endTime < availabilityStart) {
1774
+ continue;
1775
+ }
1776
+
1765
1777
  /** @type {shaka.extern.TimelineRegionInfo} */
1766
1778
  const region = {
1767
1779
  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,16 +477,15 @@ 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 MediaCapabilities will be able
488
487
  // to play later (no point storing something we can't play).
489
- StreamUtils.filterManifestByMediaCapabilities(
488
+ await shaka.util.StreamUtils.filterManifestByMediaCapabilities(
490
489
  manifest, config.offline.usePersistentLicense);
491
490
 
492
491
  // Gather all tracks.
@@ -498,24 +497,24 @@ shaka.offline.Storage = class {
498
497
  const preferredVideoCodecs = config.preferredVideoCodecs;
499
498
  const preferredAudioCodecs = config.preferredAudioCodecs;
500
499
 
501
- StreamUtils.chooseCodecsAndFilterManifest(
500
+ shaka.util.StreamUtils.chooseCodecsAndFilterManifest(
502
501
  manifest, preferredVideoCodecs, preferredAudioCodecs,
503
502
  preferredAudioChannelCount, preferredDecodingAttributes);
504
503
 
505
504
  for (const variant of manifest.variants) {
506
505
  goog.asserts.assert(
507
- StreamUtils.isPlayable(variant),
506
+ shaka.util.StreamUtils.isPlayable(variant),
508
507
  'We should have already filtered by "is playable"');
509
508
 
510
- allTracks.push(StreamUtils.variantToTrack(variant));
509
+ allTracks.push(shaka.util.StreamUtils.variantToTrack(variant));
511
510
  }
512
511
 
513
512
  for (const text of manifest.textStreams) {
514
- allTracks.push(StreamUtils.textStreamToTrack(text));
513
+ allTracks.push(shaka.util.StreamUtils.textStreamToTrack(text));
515
514
  }
516
515
 
517
516
  for (const image of manifest.imageStreams) {
518
- allTracks.push(StreamUtils.imageStreamToTrack(image));
517
+ allTracks.push(shaka.util.StreamUtils.imageStreamToTrack(image));
519
518
  }
520
519
 
521
520
  // Let the application choose which tracks to store.
package/lib/player.js CHANGED
@@ -728,6 +728,9 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
728
728
  await this.networkingEngine_.destroy();
729
729
  this.networkingEngine_ = null;
730
730
  }
731
+
732
+ // FakeEventTarget implements IReleasable
733
+ super.release();
731
734
  }
732
735
 
733
736
  /**
@@ -1611,8 +1614,11 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
1611
1614
  // initialize it now even through it appears a little out-of-place.
1612
1615
  this.regionTimeline_ =
1613
1616
  new shaka.media.RegionTimeline(() => this.seekRange());
1614
- this.regionTimeline_.setListeners(/* onRegionAdded= */ (region) => {
1617
+ this.regionTimeline_.addEventListener('regionadd', (event) => {
1618
+ /** @type {shaka.extern.TimelineRegionInfo} */
1619
+ const region = event['region'];
1615
1620
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionAdded, region);
1621
+
1616
1622
  if (this.adManager_) {
1617
1623
  this.adManager_.onDashTimedMetadata(region);
1618
1624
  }
@@ -2547,21 +2553,31 @@ shaka.Player = class extends shaka.util.FakeEventTarget {
2547
2553
  // Create the region observer. This will allow us to notify the app when we
2548
2554
  // move in and out of timeline regions.
2549
2555
  const regionObserver = new shaka.media.RegionObserver(this.regionTimeline_);
2550
- const onEnterRegion = (region, seeking) => {
2556
+
2557
+ regionObserver.addEventListener('enter', (event) => {
2558
+ /** @type {shaka.extern.TimelineRegionInfo} */
2559
+ const region = event['region'];
2551
2560
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionEnter, region);
2552
- };
2553
- const onExitRegion = (region, seeking) => {
2561
+ });
2562
+
2563
+ regionObserver.addEventListener('exit', (event) => {
2564
+ /** @type {shaka.extern.TimelineRegionInfo} */
2565
+ const region = event['region'];
2554
2566
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionExit, region);
2555
- };
2556
- const onSkipRegion = (region, seeking) => {
2567
+ });
2568
+
2569
+ regionObserver.addEventListener('skip', (event) => {
2570
+ /** @type {shaka.extern.TimelineRegionInfo} */
2571
+ const region = event['region'];
2572
+ /** @type {boolean} */
2573
+ const seeking = event['seeking'];
2557
2574
  // If we are seeking, we don't want to surface the enter/exit events since
2558
2575
  // they didn't play through them.
2559
2576
  if (!seeking) {
2560
2577
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionEnter, region);
2561
2578
  this.onRegionEvent_(shaka.Player.EventName.TimelineRegionExit, region);
2562
2579
  }
2563
- };
2564
- regionObserver.setListeners(onEnterRegion, onExitRegion, onSkipRegion);
2580
+ });
2565
2581
 
2566
2582
  // Now that we have all our observers, create a manager for them.
2567
2583
  const manager = new shaka.media.PlayheadObserverManager(this.video_);
@@ -6338,7 +6354,7 @@ shaka.Player.TYPICAL_BUFFERING_THRESHOLD_ = 0.5;
6338
6354
  * @define {string} A version number taken from git at compile time.
6339
6355
  * @export
6340
6356
  */
6341
- shaka.Player.version = 'v3.2.3-uncompiled';
6357
+ shaka.Player.version = 'v3.2.4-uncompiled'; // x-release-please-version
6342
6358
 
6343
6359
  // Initialize the deprecation system using the version string we just set
6344
6360
  // 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
  /**