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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "shaka-player",
3
3
  "description": "DASH/EME video player library",
4
- "version": "3.2.3",
4
+ "version": "3.2.4",
5
5
  "homepage": "https://github.com/google/shaka-player",
6
6
  "author": "Google",
7
7
  "maintainers": [
@@ -11,8 +11,6 @@
11
11
  }
12
12
  ],
13
13
  "devDependencies": {
14
- "@chiragrupani/karma-chromium-edge-launcher": "^2.1.0",
15
- "@onslip/karma-safari-launcher": "^1.0.1",
16
14
  "@teppeis/clutz": "^1.0.29-4c95e12.v20190929",
17
15
  "awesomplete": "^1.1.4",
18
16
  "babel-core": "^6.26.3",
@@ -37,13 +35,11 @@
37
35
  "jsdoc": "github:joeyparrish/jsdoc#45c271fa",
38
36
  "karma": "^6.2.0",
39
37
  "karma-babel-preprocessor": "^7.0.0",
40
- "karma-chrome-launcher": "^2.2.0",
41
38
  "karma-coverage": "^1.1.2",
42
- "karma-edge-launcher": "^0.4.2",
43
- "karma-firefox-launcher": "^1.1.0",
44
39
  "karma-ie-launcher": "^1.0.0",
45
40
  "karma-jasmine": "^3.3.1",
46
41
  "karma-jasmine-ajax": "^0.1.13",
42
+ "karma-local-wd-launcher": "^1.4.1",
47
43
  "karma-opera-launcher": "^1.0.0",
48
44
  "karma-sourcemap-loader": "^0.3.7",
49
45
  "karma-spec-reporter": "0.0.32",
@@ -55,6 +51,7 @@
55
51
  "pwacompat": "^2.0.10",
56
52
  "rimraf": "^2.6.3",
57
53
  "sprintf-js": "^1.1.2",
54
+ "ssim.js": "^3.5.0",
58
55
  "stylelint": "^13.8.0",
59
56
  "stylelint-config-standard": "^20.0.0",
60
57
  "tippy.js": "^4.3.1",
@@ -1289,6 +1289,49 @@ describe('DashParser Live', () => {
1289
1289
  .toHaveBeenCalledWith(
1290
1290
  jasmine.objectContaining({id: '2', startTime: 10, endTime: 25}));
1291
1291
  });
1292
+
1293
+ it('will not add timeline regions outside the DVR window', async () => {
1294
+ const manifest = [
1295
+ '<MPD type="dynamic" minimumUpdatePeriod="PT' + updateTime + 'S"',
1296
+ ' availabilityStartTime="1970-01-01T00:00:00Z"',
1297
+ ' suggestedPresentationDelay="PT0S"',
1298
+ ' timeShiftBufferDepth="PT30S">',
1299
+ ' <Period id="1">',
1300
+ ' <EventStream schemeIdUri="http://example.com" timescale="1">',
1301
+ ' <Event id="0" presentationTime="0" duration="10"/>',
1302
+ ' <Event id="1" presentationTime="10" duration="10"/>',
1303
+ ' <Event id="2" presentationTime="20" duration="10"/>',
1304
+ ' <Event id="3" presentationTime="30" duration="10"/>',
1305
+ ' <Event id="4" presentationTime="40" duration="10"/>',
1306
+ ' </EventStream>',
1307
+ ' <AdaptationSet mimeType="video/mp4">',
1308
+ ' <Representation bandwidth="1">',
1309
+ ' <SegmentTemplate startNumber="1" media="s$Number$.mp4"',
1310
+ ' duration="2" />',
1311
+ ' </Representation>',
1312
+ ' </AdaptationSet>',
1313
+ ' </Period>',
1314
+ '</MPD>',
1315
+ ].join('\n');
1316
+
1317
+ // 45 seconds after the epoch, which is also 45 seconds after the
1318
+ // presentation started. Only the last 30 seconds are available, from
1319
+ // time 15 to 45.
1320
+ Date.now = () => 45 * 1000;
1321
+
1322
+ fakeNetEngine.setResponseText('dummy://foo', manifest);
1323
+ await parser.start('dummy://foo', playerInterface);
1324
+
1325
+ expect(onTimelineRegionAddedSpy).toHaveBeenCalledTimes(4);
1326
+ expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith(
1327
+ jasmine.objectContaining({id: '1'}));
1328
+ expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith(
1329
+ jasmine.objectContaining({id: '2'}));
1330
+ expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith(
1331
+ jasmine.objectContaining({id: '3'}));
1332
+ expect(onTimelineRegionAddedSpy).toHaveBeenCalledWith(
1333
+ jasmine.objectContaining({id: '4'}));
1334
+ });
1292
1335
  });
1293
1336
 
1294
1337
  it('honors clockSyncUri for in-progress recordings', async () => {
@@ -983,7 +983,8 @@ describe('DashParser Manifest', () => {
983
983
 
984
984
  it('duplicate Representation ids with live', async () => {
985
985
  const source = [
986
- '<MPD minBufferTime="PT75S" type="dynamic">',
986
+ '<MPD minBufferTime="PT75S" type="dynamic"',
987
+ ' availabilityStartTime="1970-01-01T00:00:00Z">',
987
988
  ' <Period id="1" duration="PT30S">',
988
989
  ' <AdaptationSet mimeType="video/mp4">',
989
990
  ' <Representation id="1" bandwidth="1">',
@@ -209,6 +209,80 @@ describe('HlsParser', () => {
209
209
  expect(actual).toEqual(manifest);
210
210
  });
211
211
 
212
+ it('parses manifest attributes with space', async () => {
213
+ const master = [
214
+ '#EXTM3U\n',
215
+ '#EXT-X-MEDIA:TYPE=AUDIO, GROUP-ID="aud1", LANGUAGE="eng", ',
216
+ 'CHANNELS="16/JOC", URI="audio"\n',
217
+ '#EXT-X-MEDIA:TYPE=SUBTITLES, GROUP-ID="sub1", LANGUAGE="eng", ',
218
+ 'URI="text"\n',
219
+ '#EXT-X-MEDIA:TYPE=SUBTITLES, GROUP-ID="sub2", LANGUAGE="es", ',
220
+ 'URI="text2"\n',
221
+ '#EXT-X-STREAM-INF:BANDWIDTH=200, CODECS="avc1,mp4a", ',
222
+ 'RESOLUTION=960x540, FRAME-RATE=60, AUDIO="aud1"\n',
223
+ 'video\n',
224
+ ].join('');
225
+
226
+ const media = [
227
+ '#EXTM3U\n',
228
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
229
+ '#EXT-X-MAP:URI="init.mp4",BYTERANGE="616@0"\n',
230
+ '#EXTINF:5,\n',
231
+ '#EXT-X-BYTERANGE:121090@616\n',
232
+ 'main.mp4',
233
+ ].join('');
234
+
235
+ const textMedia = [
236
+ '#EXTM3U\n',
237
+ '#EXT-X-PLAYLIST-TYPE:VOD\n',
238
+ '#EXTINF:5,\n',
239
+ '#EXT-X-BYTERANGE:121090@616\n',
240
+ 'main.vtt',
241
+ ].join('');
242
+
243
+ const manifest = shaka.test.ManifestGenerator.generate((manifest) => {
244
+ manifest.anyTimeline();
245
+ manifest.addPartialVariant((variant) => {
246
+ variant.language = 'en';
247
+ variant.bandwidth = 200;
248
+ variant.addPartialStream(ContentType.VIDEO, (stream) => {
249
+ stream.frameRate = 60;
250
+ stream.mime('video/mp4', 'avc1');
251
+ stream.size(960, 540);
252
+ });
253
+ variant.addPartialStream(ContentType.AUDIO, (stream) => {
254
+ stream.language = 'en';
255
+ stream.channelsCount = 16;
256
+ stream.spatialAudio = true;
257
+ stream.mime('audio/mp4', 'mp4a');
258
+ });
259
+ });
260
+ manifest.addPartialTextStream((stream) => {
261
+ stream.language = 'en';
262
+ stream.kind = TextStreamKind.SUBTITLE;
263
+ stream.mime('text/vtt', '');
264
+ });
265
+ manifest.addPartialTextStream((stream) => {
266
+ stream.language = 'es';
267
+ stream.kind = TextStreamKind.SUBTITLE;
268
+ stream.mime('text/vtt', '');
269
+ });
270
+ });
271
+
272
+ fakeNetEngine
273
+ .setResponseText('test:/master', master)
274
+ .setResponseText('test:/audio', media)
275
+ .setResponseText('test:/video', media)
276
+ .setResponseText('test:/text', textMedia)
277
+ .setResponseText('test:/text2', textMedia)
278
+ .setResponseText('test:/main.vtt', vttText)
279
+ .setResponseValue('test:/init.mp4', initSegmentData)
280
+ .setResponseValue('test:/main.mp4', segmentData);
281
+
282
+ const actual = await parser.start('test:/master', playerInterface);
283
+ expect(actual).toEqual(manifest);
284
+ });
285
+
212
286
  it('prioritize AVERAGE-BANDWIDTH to BANDWIDTH', async () => {
213
287
  const master = [
214
288
  '#EXTM3U\n',
@@ -15,6 +15,9 @@ describe('RegionObserver', () => {
15
15
  /** @type {!shaka.media.RegionObserver} */
16
16
  let observer;
17
17
 
18
+ /** @type {!jasmine.Spy} */
19
+ let onSeekRange;
20
+
18
21
  /** @type {!jasmine.Spy} */
19
22
  let onEnterRegion;
20
23
  /** @type {!jasmine.Spy} */
@@ -23,18 +26,26 @@ describe('RegionObserver', () => {
23
26
  let onSkipRegion;
24
27
 
25
28
  beforeEach(() => {
29
+ onSeekRange = jasmine.createSpy('onSeekRange');
30
+ onSeekRange.and.returnValue({start: 0, end: 100});
31
+
26
32
  onEnterRegion = jasmine.createSpy('onEnterRegion');
27
33
  onExitRegion = jasmine.createSpy('onExitRegion');
28
34
  onSkipRegion = jasmine.createSpy('onSkipRegion');
29
35
 
30
36
  timeline = new shaka.media.RegionTimeline(
31
- () => { return {start: 0, end: 100}; });
37
+ shaka.test.Util.spyFunc(onSeekRange));
32
38
 
33
39
  observer = new shaka.media.RegionObserver(timeline);
34
- observer.setListeners(
35
- /* onEnter= */ shaka.test.Util.spyFunc(onEnterRegion),
36
- /* onExit= */ shaka.test.Util.spyFunc(onExitRegion),
37
- /* onSkip= */ shaka.test.Util.spyFunc(onSkipRegion));
40
+ observer.addEventListener('enter', (event) => {
41
+ shaka.test.Util.spyFunc(onEnterRegion)(event['region'], event['seeking']);
42
+ });
43
+ observer.addEventListener('exit', (event) => {
44
+ shaka.test.Util.spyFunc(onExitRegion)(event['region'], event['seeking']);
45
+ });
46
+ observer.addEventListener('skip', (event) => {
47
+ shaka.test.Util.spyFunc(onSkipRegion)(event['region'], event['seeking']);
48
+ });
38
49
  });
39
50
 
40
51
  it('fires enter event when adding a region the playhead is in', () => {
@@ -313,6 +324,30 @@ describe('RegionObserver', () => {
313
324
  expect(onSkipRegion).not.toHaveBeenCalled();
314
325
  });
315
326
 
327
+ // See https://github.com/google/shaka-player/issues/3949
328
+ it('cleans up references to regions', async () => {
329
+ const region = createRegion('my-region', 0, 10);
330
+ timeline.addRegion(region);
331
+
332
+ // Poll so that RegionObserver will store regions.
333
+ poll(observer,
334
+ /* timeInSeconds= */ 5,
335
+ /* seeking= */ false);
336
+
337
+ // Hack to get a private member without the compiler complaining:
338
+ /** @type {Map} */
339
+ const regionMap = (/** @type {?} */(observer))['oldPosition_'];
340
+ expect(regionMap.size).not.toBe(0);
341
+
342
+ // Move the seek range so that the region will be removed.
343
+ onSeekRange.and.returnValue({start: 20, end: 100});
344
+ // Give the timeline time to filter regions
345
+ await shaka.test.Util.delay(
346
+ shaka.media.RegionTimeline.REGION_FILTER_INTERVAL * 2);
347
+
348
+ expect(regionMap.size).toBe(0);
349
+ });
350
+
316
351
  /**
317
352
  * @param {string} id
318
353
  * @param {number} startTimeSeconds
@@ -14,18 +14,27 @@ describe('RegionTimeline', () => {
14
14
  /** @type {!jasmine.Spy} */
15
15
  let onNewRegion;
16
16
 
17
+ /** @type {!jasmine.Spy} */
18
+ let onRemoveRegion;
19
+
17
20
  /** @type {!jasmine.Spy} */
18
21
  let onSeekRange;
19
22
 
20
23
  beforeEach(() => {
21
- onNewRegion = jasmine.createSpy('onNewRegion');
22
-
23
24
  onSeekRange = jasmine.createSpy('onSeekRange');
24
25
  onSeekRange.and.returnValue({start: 0, end: 100});
25
-
26
26
  timeline = new shaka.media.RegionTimeline(
27
27
  shaka.test.Util.spyFunc(onSeekRange));
28
- timeline.setListeners(shaka.test.Util.spyFunc(onNewRegion));
28
+
29
+ onNewRegion = jasmine.createSpy('onNewRegion');
30
+ timeline.addEventListener('regionadd', (event) => {
31
+ shaka.test.Util.spyFunc(onNewRegion)(event['region']);
32
+ });
33
+
34
+ onRemoveRegion = jasmine.createSpy('onRemoveRegion');
35
+ timeline.addEventListener('regionremove', (event) => {
36
+ shaka.test.Util.spyFunc(onRemoveRegion)(event['region']);
37
+ });
29
38
  });
30
39
 
31
40
  afterEach(() => {
@@ -85,24 +94,33 @@ describe('RegionTimeline', () => {
85
94
  ]);
86
95
  });
87
96
 
88
- it('filters out regions that end before the start of the seek range',
89
- async () => {
90
- onSeekRange.and.returnValue({start: 5, end: 100});
91
- const region1 = createRegion('urn:foo', 'my-region', 0, 3);
92
- const region2 = createRegion('urn:foo', 'my-region', 3, 10);
93
- const region3 = createRegion('urn:foo', 'my-region', 5, 10);
94
- timeline.addRegion(region1);
95
- timeline.addRegion(region2);
96
- timeline.addRegion(region3);
97
- expect(onNewRegion).toHaveBeenCalledTimes(3);
98
- let regions = Array.from(timeline.regions());
99
- expect(regions.length).toBe(3);
100
- // Give the timeline time to filter regions
101
- await shaka.test.Util.delay(
102
- shaka.media.RegionTimeline.REGION_FILTER_INTERVAL * 2);
103
- regions = Array.from(timeline.regions());
104
- expect(regions).toEqual([region2, region3]);
105
- });
97
+ it('filters regions that end before the seek range', async () => {
98
+ onSeekRange.and.returnValue({start: 5, end: 100});
99
+
100
+ const region1 = createRegion('urn:foo', 'my-region', 0, 3);
101
+ const region2 = createRegion('urn:foo', 'my-region', 3, 10);
102
+ const region3 = createRegion('urn:foo', 'my-region', 5, 10);
103
+
104
+ timeline.addRegion(region1);
105
+ timeline.addRegion(region2);
106
+ timeline.addRegion(region3);
107
+ expect(onNewRegion).toHaveBeenCalledTimes(3);
108
+ expect(onNewRegion).toHaveBeenCalledWith(region1);
109
+ expect(onNewRegion).toHaveBeenCalledWith(region2);
110
+ expect(onNewRegion).toHaveBeenCalledWith(region3);
111
+
112
+ let regions = Array.from(timeline.regions());
113
+ expect(regions.length).toBe(3);
114
+
115
+ // Give the timeline time to filter regions
116
+ await shaka.test.Util.delay(
117
+ shaka.media.RegionTimeline.REGION_FILTER_INTERVAL * 2);
118
+
119
+ regions = Array.from(timeline.regions());
120
+ expect(regions).toEqual([region2, region3]);
121
+
122
+ expect(onRemoveRegion).toHaveBeenCalledWith(region1);
123
+ });
106
124
 
107
125
  /**
108
126
  * @param {string} schemeIdUri
@@ -401,10 +401,10 @@ shaka.test.Util = class {
401
401
  * within the bounds of the viewport.
402
402
  * @param {string} name An identifier for the screenshot. Use alphanumeric
403
403
  * plus dash and underscore only.
404
- * @param {number} threshold A change threshold in pixels.
404
+ * @param {number} minSimilarity A minimum similarity score between 0 and 1.
405
405
  * @return {!Promise}
406
406
  */
407
- static async checkScreenshot(element, name, threshold=0) {
407
+ static async checkScreenshot(element, name, minSimilarity=1) {
408
408
  // Make sure the DOM is up-to-date and layout has settled before continuing.
409
409
  // Without this delay, or with a shorter delay, we sometimes get missing
410
410
  // elements in our UITextDisplayer tests on some platforms.
@@ -448,13 +448,16 @@ shaka.test.Util = class {
448
448
  const buffer = await shaka.test.Util.fetch(
449
449
  '/screenshot/diff' + parentUrlParams + paramsString);
450
450
  const json = shaka.util.StringUtils.fromUTF8(buffer);
451
- const pixelsChanged = /** @type {number} */(JSON.parse(json));
451
+ const similarity = /** @type {number} */(JSON.parse(json));
452
452
 
453
- // If the change threshold is exceeded, you can review the new screenshot
453
+ // If the minimum similarity is not met, you can review the new screenshot
454
454
  // and the diff image in the screenshots folder. Look for images that end
455
- // with "-new" and "-diff". If cropping doesn't work right, you can view
456
- // the full-page screenshot in the image that ends with "-full".
457
- expect(pixelsChanged).withContext(name).not.toBeGreaterThan(threshold);
455
+ // with "-new" and "-diff". (NOTE: The diff is a pixel-wise diff for human
456
+ // review, and is not produced with the same structural similarity
457
+ // algorithm used to detect changes in the test.) If cropping doesn't work
458
+ // right, you can view the full-page screenshot in the image that ends with
459
+ // "-full".
460
+ expect(similarity).withContext(name).not.toBeLessThan(minSimilarity);
458
461
  }
459
462
  };
460
463
 
@@ -34,11 +34,8 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
34
34
  */
35
35
  let beforeScreenshot;
36
36
 
37
- // Legacy Edge seems to have inconsistent font kerning. A one-pixel offset in
38
- // the position of one character appears about 60% of the time, requiring us
39
- // to have this change tolerance in our tests. So far, all past bugs in our
40
- // implementation that we have tests for would exceed this threshold by a lot.
41
- const threshold = 160; // px
37
+ // A minimum similarity score for screenshots, between 0 and 1.
38
+ const minSimilarity = 0.95;
42
39
 
43
40
  const originalCast = window.chrome && window.chrome.cast;
44
41
 
@@ -196,6 +193,7 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
196
193
 
197
194
  // The video must be played a little now, after the cues were appended,
198
195
  // but before the screenshot.
196
+ video.playbackRate = 1;
199
197
  video.play();
200
198
  await waiter.failOnTimeout(false).timeoutAfter(5)
201
199
  .waitForMovement(video);
@@ -203,10 +201,13 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
203
201
 
204
202
  // Seek to a time when cues should be showing.
205
203
  video.currentTime = time;
204
+ // Get into a playing state, but without movement.
205
+ video.playbackRate = 0;
206
+ video.play();
206
207
 
207
208
  // Add a short delay to ensure that the system has caught up and that
208
209
  // native text displayers have been updated by the browser.
209
- await Util.shortDelay();
210
+ await Util.delay(0.1);
210
211
  };
211
212
  });
212
213
 
@@ -475,6 +476,6 @@ filterDescribe('TextDisplayer layout', supportsScreenshots, () => {
475
476
  return Util.checkScreenshot(
476
477
  /* element= */ videoContainer,
477
478
  prefix + '-' + baseName,
478
- threshold);
479
+ minSimilarity);
479
480
  }
480
481
  });
package/ui/controls.js CHANGED
@@ -253,6 +253,9 @@ shaka.ui.Controls = class extends shaka.util.FakeEventTarget {
253
253
 
254
254
  this.localization_ = null;
255
255
  this.pressedKeys_.clear();
256
+
257
+ // FakeEventTarget implements IReleasable
258
+ super.release();
256
259
  }
257
260
 
258
261
 
@@ -23,17 +23,18 @@ goog.require('shaka.util.LanguageUtils');
23
23
  * If a string is not available, it will return the localized
24
24
  * form in the closest related locale.
25
25
  *
26
- * @implements {EventTarget}
27
26
  * @final
28
27
  * @export
29
28
  */
30
- shaka.ui.Localization = class {
29
+ shaka.ui.Localization = class extends shaka.util.FakeEventTarget {
31
30
  /**
32
31
  * @param {string} fallbackLocale
33
32
  * The fallback locale that should be used. It will be assumed that this
34
33
  * locale should have entries for just about every request.
35
34
  */
36
35
  constructor(fallbackLocale) {
36
+ super();
37
+
37
38
  /** @private {string} */
38
39
  this.fallbackLocale_ = shaka.util.LanguageUtils.normalize(fallbackLocale);
39
40
 
@@ -62,41 +63,16 @@ shaka.ui.Localization = class {
62
63
  * @private {!Map.<string, !Map.<string, string>>}
63
64
  */
64
65
  this.localizations_ = new Map();
65
-
66
- /**
67
- * The event target that we will wrap so that we can fire events
68
- * without having to manage the listeners directly.
69
- *
70
- * @private {!EventTarget}
71
- */
72
- this.events_ = new shaka.util.FakeEventTarget();
73
- }
74
-
75
- /**
76
- * @override
77
- * @export
78
- */
79
- addEventListener(type, listener, options) {
80
- this.events_.addEventListener(type, listener, options);
81
- }
82
-
83
- /**
84
- * @override
85
- * @export
86
- */
87
- removeEventListener(type, listener, options) {
88
- // Apparently Closure says we can be passed a null |option|, but we can't
89
- // pass a null option, so if we get have a null-like |option|, force it to
90
- // be undefined.
91
- this.events_.removeEventListener(type, listener, options || undefined);
92
66
  }
93
67
 
94
68
  /**
95
69
  * @override
96
70
  * @export
97
71
  */
98
- dispatchEvent(event) {
99
- return this.events_.dispatchEvent(event);
72
+ release() {
73
+ // Placeholder so that readers know this implements IReleasable (via
74
+ // FakeEventTarget)
75
+ super.release();
100
76
  }
101
77
 
102
78
  /**
@@ -130,7 +106,7 @@ shaka.ui.Localization = class {
130
106
  (locale) => !this.localizations_.has(locale));
131
107
 
132
108
  if (missing.length) {
133
- this.events_.dispatchEvent(new shaka.util.FakeEvent(
109
+ this.dispatchEvent(new shaka.util.FakeEvent(
134
110
  Class.UNKNOWN_LOCALES,
135
111
  (new Map()).set('locales', missing)));
136
112
  }
@@ -141,7 +117,7 @@ shaka.ui.Localization = class {
141
117
 
142
118
  const data = (new Map()).set(
143
119
  'locales', found.length ? found : [this.fallbackLocale_]);
144
- this.events_.dispatchEvent(new shaka.util.FakeEvent(
120
+ this.dispatchEvent(new shaka.util.FakeEvent(
145
121
  Class.LOCALE_CHANGED,
146
122
  data));
147
123
  }
@@ -193,7 +169,7 @@ shaka.ui.Localization = class {
193
169
  // data from.
194
170
  this.updateCurrentMap_();
195
171
 
196
- this.events_.dispatchEvent(new FakeEvent(Class.LOCALE_UPDATED));
172
+ this.dispatchEvent(new FakeEvent(Class.LOCALE_UPDATED));
197
173
 
198
174
  return this;
199
175
  }
@@ -249,7 +225,7 @@ shaka.ui.Localization = class {
249
225
  // Make a copy to avoid leaking references.
250
226
  .set('locales', Array.from(this.currentLocales_))
251
227
  .set('missing', id);
252
- this.events_.dispatchEvent(new FakeEvent(Class.UNKNOWN_LOCALIZATION, data));
228
+ this.dispatchEvent(new FakeEvent(Class.UNKNOWN_LOCALIZATION, data));
253
229
 
254
230
  return '';
255
231
  }
@@ -377,7 +353,7 @@ shaka.ui.Localization = class {
377
353
  // an array.
378
354
  .set('missing', Array.from(missing));
379
355
 
380
- this.events_.dispatchEvent(new shaka.util.FakeEvent(
356
+ this.dispatchEvent(new shaka.util.FakeEvent(
381
357
  shaka.ui.Localization.MISSING_LOCALIZATIONS,
382
358
  data));
383
359
  }