@gcorevideo/player 2.25.7 → 2.25.9

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 (33) hide show
  1. package/assets/bottom-gear/gear-sub-menu.scss +4 -9
  2. package/assets/media-control/container.scss +0 -13
  3. package/assets/media-control/media-control.scss +14 -12
  4. package/assets/media-control/width270.scss +3 -0
  5. package/assets/media-control/width370.scss +4 -0
  6. package/assets/multi-camera/style.scss +0 -5
  7. package/assets/subtitles/combobox.ejs +27 -6
  8. package/assets/subtitles/string.ejs +1 -1
  9. package/assets/subtitles/style.scss +16 -69
  10. package/dist/core.js +1 -1
  11. package/dist/index.css +1036 -1090
  12. package/dist/index.embed.js +139 -101
  13. package/dist/index.js +80 -46
  14. package/lib/plugins/bottom-gear/BottomGear.d.ts +1 -1
  15. package/lib/plugins/bottom-gear/BottomGear.d.ts.map +1 -1
  16. package/lib/plugins/bottom-gear/BottomGear.js +3 -4
  17. package/lib/plugins/media-control/MediaControl.d.ts +4 -0
  18. package/lib/plugins/media-control/MediaControl.d.ts.map +1 -1
  19. package/lib/plugins/media-control/MediaControl.js +7 -0
  20. package/lib/plugins/subtitles/ClosedCaptions.d.ts +8 -5
  21. package/lib/plugins/subtitles/ClosedCaptions.d.ts.map +1 -1
  22. package/lib/plugins/subtitles/ClosedCaptions.js +67 -38
  23. package/lib/testUtils.d.ts.map +1 -1
  24. package/lib/testUtils.js +2 -0
  25. package/package.json +1 -1
  26. package/src/plugins/bottom-gear/BottomGear.ts +3 -4
  27. package/src/plugins/bottom-gear/__tests__/BottomGear.test.ts +1 -1
  28. package/src/plugins/media-control/MediaControl.ts +10 -0
  29. package/src/plugins/subtitles/ClosedCaptions.ts +73 -39
  30. package/src/plugins/subtitles/__tests__/ClosedCaptions.test.ts +220 -35
  31. package/src/plugins/subtitles/__tests__/__snapshots__/ClosedCaptions.test.ts.snap +8 -19
  32. package/src/testUtils.ts +2 -0
  33. package/tsconfig.tsbuildinfo +1 -1
@@ -42,6 +42,7 @@ const T = 'plugins.cc';
42
42
  export class ClosedCaptions extends UICorePlugin {
43
43
  isPreselectedApplied = false;
44
44
  active = false;
45
+ open = false;
45
46
  track = null;
46
47
  tracks = [];
47
48
  $line = null;
@@ -63,14 +64,14 @@ export class ClosedCaptions extends UICorePlugin {
63
64
  static get version() {
64
65
  return VERSION;
65
66
  }
66
- static template = template(comboboxHTML);
67
- static templateString = template(stringHTML);
67
+ static templateControl = template(comboboxHTML);
68
+ static templateLine = template(stringHTML);
68
69
  /**
69
70
  * @internal
70
71
  */
71
72
  get attributes() {
72
73
  return {
73
- class: 'media-control-cc',
74
+ class: 'media-control-cc media-control-dd__wrap',
74
75
  };
75
76
  }
76
77
  /**
@@ -78,8 +79,8 @@ export class ClosedCaptions extends UICorePlugin {
78
79
  */
79
80
  get events() {
80
81
  return {
81
- 'click #cc-select li a': 'onItemSelect',
82
- 'click #cc-button': 'toggleMenu',
82
+ 'click #gplayer-cc-menu [data-item]': 'onItemSelect',
83
+ 'click #gplayer-cc-button': 'toggleMenu',
83
84
  };
84
85
  }
85
86
  get preselectedLanguage() {
@@ -92,14 +93,12 @@ export class ClosedCaptions extends UICorePlugin {
92
93
  */
93
94
  bindEvents() {
94
95
  this.listenTo(this.core, Events.CORE_READY, this.onCoreReady);
95
- this.listenTo(this.core, Events.CORE_RESIZE, this.playerResize);
96
96
  this.listenTo(this.core, Events.CORE_ACTIVE_CONTAINER_CHANGED, this.onContainerChanged);
97
97
  }
98
98
  onCoreReady() {
99
- trace(`${T} onCoreReady`);
100
99
  const mediaControl = this.core.getPlugin('media_control');
101
100
  assert(mediaControl, 'media_control plugin is required');
102
- this.listenTo(mediaControl, Events.MEDIACONTROL_RENDERED, this.render); // TODO mount to media control
101
+ this.listenTo(mediaControl, Events.MEDIACONTROL_RENDERED, this.mount);
103
102
  this.listenTo(mediaControl, Events.MEDIACONTROL_HIDE, () => {
104
103
  this.hideMenu();
105
104
  });
@@ -110,11 +109,15 @@ export class ClosedCaptions extends UICorePlugin {
110
109
  });
111
110
  }
112
111
  onContainerChanged() {
113
- trace(`${T} onContainerChanged`);
114
- this.listenTo(this.core.activeContainer, Events.CONTAINER_FULLSCREEN, this.playerResize);
112
+ this.listenTo(this.core.activeContainer, Events.CONTAINER_FULLSCREEN, this.onContainerResize);
113
+ this.listenTo(this.core.activeContainer, Events.CONTAINER_RESIZE, this.onContainerResize);
115
114
  this.listenTo(this.core.activeContainer, 'container:advertisement:start', this.onStartAd);
116
115
  this.listenTo(this.core.activePlayback, Events.PLAYBACK_SUBTITLE_AVAILABLE, this.onSubtitleAvailable);
117
116
  this.listenTo(this.core.activePlayback, Events.PLAYBACK_SUBTITLE_CHANGED, this.onSubtitleChanged);
117
+ this.listenTo(this.core.activeContainer, Events.CONTAINER_CLICK, () => {
118
+ // TODO test
119
+ this.hideMenu();
120
+ });
118
121
  // fix for iOS
119
122
  const video = this.core.activePlayback.el;
120
123
  assert(video, 'video element is required');
@@ -132,6 +135,7 @@ export class ClosedCaptions extends UICorePlugin {
132
135
  onSubtitleAvailable() {
133
136
  trace(`${T} onSubtitleAvailable`);
134
137
  this.applyTracks();
138
+ this.mount();
135
139
  }
136
140
  onSubtitleChanged({ id }) {
137
141
  trace(`${T} onSubtitleChanged`, { id });
@@ -183,8 +187,7 @@ export class ClosedCaptions extends UICorePlugin {
183
187
  this.show();
184
188
  this.stopListening(this.core.activeContainer, 'container:advertisement:finish', this.onFinishAd);
185
189
  }
186
- playerResize() {
187
- trace(`${T} playerResize`);
190
+ onContainerResize() {
188
191
  const shouldShow = this.core.activeContainer &&
189
192
  isFullscreen(this.core.activeContainer.el) &&
190
193
  this.track &&
@@ -196,6 +199,7 @@ export class ClosedCaptions extends UICorePlugin {
196
199
  }
197
200
  try {
198
201
  this.resizeFont();
202
+ this.clampPopup();
199
203
  }
200
204
  catch (error) {
201
205
  reportError(error);
@@ -206,7 +210,10 @@ export class ClosedCaptions extends UICorePlugin {
206
210
  */
207
211
  hide() {
208
212
  this.active = false;
213
+ this.open = false;
209
214
  this.renderIcon();
215
+ this.$el.find('#gplayer-cc-menu').hide();
216
+ this.$el.find('#gplayer-cc-button').attr('aria-expanded', 'false');
210
217
  this.$line.hide();
211
218
  if (this.tracks) {
212
219
  for (const t of this.tracks) {
@@ -249,17 +256,18 @@ export class ClosedCaptions extends UICorePlugin {
249
256
  if (!this.core.activeContainer) {
250
257
  return this;
251
258
  }
252
- if (!this.shouldRender()) {
253
- return this;
254
- }
255
- const mediaControl = this.core.getPlugin('media_control');
256
- this.$el.html(ClosedCaptions.template({ tracks: this.tracks, i18n: this.core.i18n }));
257
- this.$el.find('#cc-select').hide();
258
- this.core.activeContainer.$el.find('#cc-line').remove();
259
- this.$line = $(ClosedCaptions.templateString());
259
+ this.$el.html(ClosedCaptions.templateControl({
260
+ tracks: this.tracks ?? [],
261
+ i18n: this.core.i18n,
262
+ current: this.track?.id ?? -1,
263
+ }));
264
+ this.$el.find('#gplayer-cc-menu').hide();
265
+ this.open = false;
266
+ this.core.activeContainer.$el.find('#gplayer-cc-line').remove();
267
+ this.$line = $(ClosedCaptions.templateLine());
260
268
  this.resizeFont();
269
+ this.clampPopup();
261
270
  this.core.activeContainer.$el.append(this.$line);
262
- mediaControl.slot('cc', this.$el);
263
271
  this.updateSelection();
264
272
  this.renderIcon();
265
273
  return this;
@@ -273,9 +281,10 @@ export class ClosedCaptions extends UICorePlugin {
273
281
  this.updateSelection();
274
282
  }
275
283
  onItemSelect(event) {
276
- const id = event.target.dataset.ccSelect ?? '-1';
277
- trace(`${T} onItemSelect`, { id });
278
- localStorage.setItem(LOCAL_STORAGE_CC_ID, id);
284
+ // event.target does not exist for some reason in tests
285
+ const id = (event.target ?? event.currentTarget).dataset?.item ??
286
+ '-1';
287
+ localStorage.setItem(LOCAL_STORAGE_CC_ID, id); // TODO store language instead
279
288
  this.selectItem(this.findById(Number(id)));
280
289
  this.hideMenu();
281
290
  return false;
@@ -292,26 +301,33 @@ export class ClosedCaptions extends UICorePlugin {
292
301
  }
293
302
  }
294
303
  hideMenu() {
295
- trace(`${T} hideMenu`);
296
- this.$el.find('#cc-select').hide();
304
+ this.open = false;
305
+ this.$el.find('#gplayer-cc-menu').hide();
306
+ this.$el.find('#gplayer-cc-button').attr('aria-expanded', 'false');
297
307
  }
298
308
  toggleMenu() {
299
- trace(`${T} toggleMenu`, { display: this.$el.find('#cc-select').css('display') });
300
309
  this.core
301
310
  .getPlugin('media_control')
302
311
  .trigger(ExtendedEvents.MEDIACONTROL_MENU_COLLAPSE, this.name);
303
- this.$el.find('#cc-select').toggle();
304
- // TODO hold state, add aria-expanded to the button, add active state to the button
312
+ this.open = !this.open;
313
+ if (this.open) {
314
+ this.$el.find('#gplayer-cc-menu').show();
315
+ }
316
+ else {
317
+ this.$el.find('#gplayer-cc-menu').hide();
318
+ }
319
+ this.$el.find('#gplayer-cc-button').attr('aria-expanded', this.open);
305
320
  }
306
321
  itemElement(id) {
307
- return this.$el.find(`#cc-select li a[data-cc-select="${id}"]`).parent();
322
+ // TODO fix semantically
323
+ return this.$el.find(`#gplayer-cc-menu [data-item="${id}"]`).parent();
308
324
  }
309
325
  allItemElements() {
310
- return this.$('#cc-select li');
326
+ return this.$el.find('#gplayer-cc-menu li'); // TODO fix semantically
311
327
  }
312
328
  selectSubtitles() {
313
329
  const trackId = this.track ? this.track.id : -1;
314
- this.core.activePlayback.closedCaptionsTrackId = trackId;
330
+ this.core.activePlayback.closedCaptionsTrackId = trackId; // TODO test
315
331
  }
316
332
  getSubtitleText(track) {
317
333
  const currentTime = this.core.activePlayback?.getCurrentTime() ?? 0;
@@ -321,6 +337,7 @@ export class ClosedCaptions extends UICorePlugin {
321
337
  for (const cue of cues) {
322
338
  if (currentTime >= cue.startTime && currentTime <= cue.endTime) {
323
339
  lines.push(cue.getCueAsHTML().textContent);
340
+ // TODO break?
324
341
  }
325
342
  }
326
343
  }
@@ -346,18 +363,30 @@ export class ClosedCaptions extends UICorePlugin {
346
363
  this.allItemElements()
347
364
  .removeClass('current')
348
365
  .find('a')
349
- .removeClass('gcore-skin-active');
350
- trace(`${T} highlightCurrentSubtitles`, {
351
- track: this.track?.id,
352
- });
366
+ .removeClass('gcore-skin-active')
367
+ .attr('aria-checked', 'false');
353
368
  const currentLevelElement = this.itemElement(this.track ? this.track.id : -1);
354
369
  currentLevelElement
355
370
  .addClass('current')
356
371
  .find('a')
357
- .addClass('gcore-skin-active');
372
+ .addClass('gcore-skin-active')
373
+ .attr('aria-checked', 'true');
358
374
  }
359
375
  renderIcon() {
376
+ // render both icons at once
360
377
  const icon = this.active ? subtitlesOnIcon : subtitlesOffIcon;
361
- this.$el.find('span.cc-text').html(icon);
378
+ this.$el.find('#gplayer-cc-button').html(icon);
379
+ }
380
+ clampPopup() {
381
+ const availableHeight = this.core
382
+ .getPlugin('media_control')
383
+ .getAvailablePopupHeight();
384
+ this.$el.find('#gplayer-cc-menu').css('max-height', `${availableHeight}px`);
385
+ }
386
+ mount() {
387
+ if (this.shouldRender()) {
388
+ const mediaControl = this.core.getPlugin('media_control');
389
+ mediaControl.slot('cc', this.$el);
390
+ }
362
391
  }
363
392
  }
@@ -1 +1 @@
1
- {"version":3,"file":"testUtils.d.ts","sourceRoot":"","sources":["../src/testUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,MAAM,MAAM,eAAe,CAAA;AAGlC,wBAAgB,cAAc,CAC5B,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACrC,SAAS,GAAE,GAAkC;;;;;;;;;;;;;;;;;EAsB9C;AAED,wBAAgB,gBAAgB;;;EAK/B;AAED,wBAAgB,mBAAmB;;;;;;EAKlC;AAED,wBAAgB,kBAAkB,CAChC,IAAI,SAAS,EACb,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqCtC;AAED,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACrC,QAAQ,GAAE,GAAgD;;;;;;;;;;;;;;;;;;;;;;;;;EA8B3D;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,GAAG,gBAqB/C;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,GAAG,OAe7C"}
1
+ {"version":3,"file":"testUtils.d.ts","sourceRoot":"","sources":["../src/testUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAC9C,OAAO,MAAM,MAAM,eAAe,CAAA;AAGlC,wBAAgB,cAAc,CAC5B,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACrC,SAAS,GAAE,GAAkC;;;;;;;;;;;;;;;;;EAsB9C;AAED,wBAAgB,gBAAgB;;;EAK/B;AAED,wBAAgB,mBAAmB;;;;;;EAKlC;AAED,wBAAgB,kBAAkB,CAChC,IAAI,SAAS,EACb,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAqCtC;AAED,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACrC,QAAQ,GAAE,GAAgD;;;;;;;;;;;;;;;;;;;;;;;;;EA8B3D;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,GAAG,gBAuB/C;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,GAAG,OAe7C"}
package/lib/testUtils.js CHANGED
@@ -117,6 +117,8 @@ export function createMockMediaControl(core) {
117
117
  // @ts-ignore
118
118
  mediaControl.getAvailableHeight = vi.fn().mockReturnValue(300);
119
119
  // @ts-ignore
120
+ mediaControl.getAvailablePopupHeight = vi.fn().mockReturnValue(286);
121
+ // @ts-ignore
120
122
  mediaControl.toggleElement = vi.fn();
121
123
  vi.spyOn(mediaControl, 'trigger');
122
124
  core.$el.append(mediaControl.$el);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gcorevideo/player",
3
- "version": "2.25.7",
3
+ "version": "2.25.9",
4
4
  "description": "Gcore JavaScript video player",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -198,7 +198,7 @@ export class BottomGear extends UICorePlugin {
198
198
  .appendTo(this.$el.find('#gear-options-wrapper'))
199
199
  $item.on('click', (e: MouseEvent) => {
200
200
  e.stopPropagation()
201
- this.alignSubmenu($subMenu)
201
+ this.clampPopup($subMenu)
202
202
  $subMenu.show()
203
203
  this.$el.find('#gear-options').hide()
204
204
  })
@@ -323,10 +323,9 @@ export class BottomGear extends UICorePlugin {
323
323
  mediaControl.slot('gear', this.$el)
324
324
  }
325
325
 
326
- private alignSubmenu($subMenu: ZeptoResult) {
326
+ private clampPopup($subMenu: ZeptoResult) {
327
327
  const availableHeight =
328
- this.core.getPlugin('media_control').getAvailableHeight() -
329
- MENU_VMARGIN * 2
328
+ this.core.getPlugin('media_control').getAvailablePopupHeight()
330
329
  $subMenu.css('max-height', `${availableHeight}px`)
331
330
  $subMenu
332
331
  .find('.gear-sub-menu')
@@ -110,7 +110,7 @@ describe('BottomGear', () => {
110
110
  })
111
111
  describe('when submenu is open', () => {
112
112
  beforeEach(async () => {
113
- mediaControl.getAvailableHeight.mockReturnValue(198)
113
+ mediaControl.getAvailablePopupHeight.mockReturnValue(174)
114
114
  bottomGear.$el.find('#gear-button').click()
115
115
  await new Promise((resolve) => setTimeout(resolve, 0))
116
116
  bottomGear.$el.find('#more-button').click()
@@ -49,6 +49,8 @@ const STANDARD_MEDIA_CONTROL_ELEMENTS: string[] = [
49
49
  'volume',
50
50
  ]
51
51
 
52
+ const MENU_VMARGIN = 12
53
+
52
54
  /**
53
55
  * Built-in media control elements.
54
56
  * @beta
@@ -562,6 +564,13 @@ export class MediaControl extends UICorePlugin {
562
564
  )
563
565
  }
564
566
 
567
+ /**
568
+ * @returns Vertical space available to render a popup menu
569
+ */
570
+ getAvailablePopupHeight() {
571
+ return this.getAvailableHeight() - MENU_VMARGIN * 2
572
+ }
573
+
565
574
  /**
566
575
  * Set the initial volume, which is preserved when playback is interrupted by an advertisement
567
576
  */
@@ -681,6 +690,7 @@ export class MediaControl extends UICorePlugin {
681
690
  const pos = offset
682
691
  ? Math.min(1, Math.max(offset / this.$seekBarContainer.width(), 0))
683
692
  : 0
693
+
684
694
  if (this.settings.seekEnabled) {
685
695
  // TODO test that it works when the element does not exist
686
696
  this.$seekBarHover.css({ left: hoverOffset })
@@ -64,6 +64,8 @@ export class ClosedCaptions extends UICorePlugin {
64
64
 
65
65
  private active = false
66
66
 
67
+ private open = false
68
+
67
69
  private track: TextTrackItem | null = null
68
70
 
69
71
  private tracks: TextTrackItem[] = []
@@ -91,16 +93,16 @@ export class ClosedCaptions extends UICorePlugin {
91
93
  return VERSION
92
94
  }
93
95
 
94
- private static readonly template = template(comboboxHTML)
96
+ private static readonly templateControl = template(comboboxHTML)
95
97
 
96
- private static readonly templateString = template(stringHTML)
98
+ private static readonly templateLine = template(stringHTML)
97
99
 
98
100
  /**
99
101
  * @internal
100
102
  */
101
103
  override get attributes() {
102
104
  return {
103
- class: 'media-control-cc',
105
+ class: 'media-control-cc media-control-dd__wrap',
104
106
  }
105
107
  }
106
108
 
@@ -109,8 +111,8 @@ export class ClosedCaptions extends UICorePlugin {
109
111
  */
110
112
  override get events() {
111
113
  return {
112
- 'click #cc-select li a': 'onItemSelect',
113
- 'click #cc-button': 'toggleMenu',
114
+ 'click #gplayer-cc-menu [data-item]': 'onItemSelect',
115
+ 'click #gplayer-cc-button': 'toggleMenu',
114
116
  }
115
117
  }
116
118
 
@@ -127,7 +129,6 @@ export class ClosedCaptions extends UICorePlugin {
127
129
  */
128
130
  override bindEvents() {
129
131
  this.listenTo(this.core, Events.CORE_READY, this.onCoreReady)
130
- this.listenTo(this.core, Events.CORE_RESIZE, this.playerResize)
131
132
  this.listenTo(
132
133
  this.core,
133
134
  Events.CORE_ACTIVE_CONTAINER_CHANGED,
@@ -136,10 +137,9 @@ export class ClosedCaptions extends UICorePlugin {
136
137
  }
137
138
 
138
139
  private onCoreReady() {
139
- trace(`${T} onCoreReady`)
140
140
  const mediaControl = this.core.getPlugin('media_control')
141
141
  assert(mediaControl, 'media_control plugin is required')
142
- this.listenTo(mediaControl, Events.MEDIACONTROL_RENDERED, this.render) // TODO mount to media control
142
+ this.listenTo(mediaControl, Events.MEDIACONTROL_RENDERED, this.mount)
143
143
  this.listenTo(mediaControl, Events.MEDIACONTROL_HIDE, () => {
144
144
  this.hideMenu()
145
145
  })
@@ -155,11 +155,15 @@ export class ClosedCaptions extends UICorePlugin {
155
155
  }
156
156
 
157
157
  private onContainerChanged() {
158
- trace(`${T} onContainerChanged`)
159
158
  this.listenTo(
160
159
  this.core.activeContainer,
161
160
  Events.CONTAINER_FULLSCREEN,
162
- this.playerResize,
161
+ this.onContainerResize,
162
+ )
163
+ this.listenTo(
164
+ this.core.activeContainer,
165
+ Events.CONTAINER_RESIZE,
166
+ this.onContainerResize,
163
167
  )
164
168
  this.listenTo(
165
169
  this.core.activeContainer,
@@ -176,6 +180,10 @@ export class ClosedCaptions extends UICorePlugin {
176
180
  Events.PLAYBACK_SUBTITLE_CHANGED,
177
181
  this.onSubtitleChanged,
178
182
  )
183
+ this.listenTo(this.core.activeContainer, Events.CONTAINER_CLICK, () => {
184
+ // TODO test
185
+ this.hideMenu()
186
+ })
179
187
 
180
188
  // fix for iOS
181
189
  const video = this.core.activePlayback.el
@@ -197,6 +205,7 @@ export class ClosedCaptions extends UICorePlugin {
197
205
  private onSubtitleAvailable() {
198
206
  trace(`${T} onSubtitleAvailable`)
199
207
  this.applyTracks()
208
+ this.mount()
200
209
  }
201
210
 
202
211
  private onSubtitleChanged({ id }: { id: number }) {
@@ -260,8 +269,7 @@ export class ClosedCaptions extends UICorePlugin {
260
269
  )
261
270
  }
262
271
 
263
- private playerResize() {
264
- trace(`${T} playerResize`)
272
+ private onContainerResize() {
265
273
  const shouldShow =
266
274
  this.core.activeContainer &&
267
275
  isFullscreen(this.core.activeContainer.el) &&
@@ -276,6 +284,7 @@ export class ClosedCaptions extends UICorePlugin {
276
284
 
277
285
  try {
278
286
  this.resizeFont()
287
+ this.clampPopup()
279
288
  } catch (error) {
280
289
  reportError(error)
281
290
  }
@@ -286,7 +295,10 @@ export class ClosedCaptions extends UICorePlugin {
286
295
  */
287
296
  hide() {
288
297
  this.active = false
298
+ this.open = false
289
299
  this.renderIcon()
300
+ this.$el.find('#gplayer-cc-menu').hide()
301
+ this.$el.find('#gplayer-cc-button').attr('aria-expanded', 'false')
290
302
  this.$line.hide()
291
303
  if (this.tracks) {
292
304
  for (const t of this.tracks) {
@@ -337,20 +349,21 @@ export class ClosedCaptions extends UICorePlugin {
337
349
  return this
338
350
  }
339
351
 
340
- if (!this.shouldRender()) {
341
- return this
342
- }
343
-
344
- const mediaControl = this.core.getPlugin('media_control')
345
-
346
- this.$el.html(ClosedCaptions.template({ tracks: this.tracks, i18n: this.core.i18n }))
347
- this.$el.find('#cc-select').hide()
348
- this.core.activeContainer.$el.find('#cc-line').remove()
349
- this.$line = $(ClosedCaptions.templateString())
352
+ this.$el.html(
353
+ ClosedCaptions.templateControl({
354
+ tracks: this.tracks ?? [],
355
+ i18n: this.core.i18n,
356
+ current: this.track?.id ?? -1,
357
+ }),
358
+ )
359
+ this.$el.find('#gplayer-cc-menu').hide()
360
+ this.open = false
361
+ this.core.activeContainer.$el.find('#gplayer-cc-line').remove()
362
+ this.$line = $(ClosedCaptions.templateLine())
350
363
  this.resizeFont()
364
+ this.clampPopup()
351
365
 
352
366
  this.core.activeContainer.$el.append(this.$line)
353
- mediaControl.slot('cc', this.$el)
354
367
 
355
368
  this.updateSelection()
356
369
 
@@ -371,11 +384,12 @@ export class ClosedCaptions extends UICorePlugin {
371
384
  }
372
385
 
373
386
  private onItemSelect(event: MouseEvent) {
374
- const id = (event.target as HTMLElement).dataset.ccSelect ?? '-1'
375
-
376
- trace(`${T} onItemSelect`, { id })
387
+ // event.target does not exist for some reason in tests
388
+ const id =
389
+ ((event.target ?? event.currentTarget) as HTMLElement).dataset?.item ??
390
+ '-1'
377
391
 
378
- localStorage.setItem(LOCAL_STORAGE_CC_ID, id)
392
+ localStorage.setItem(LOCAL_STORAGE_CC_ID, id) // TODO store language instead
379
393
  this.selectItem(this.findById(Number(id)))
380
394
  this.hideMenu()
381
395
  return false
@@ -398,31 +412,37 @@ export class ClosedCaptions extends UICorePlugin {
398
412
  }
399
413
 
400
414
  private hideMenu() {
401
- trace(`${T} hideMenu`)
402
- this.$el.find('#cc-select').hide()
415
+ this.open = false
416
+ this.$el.find('#gplayer-cc-menu').hide()
417
+ this.$el.find('#gplayer-cc-button').attr('aria-expanded', 'false')
403
418
  }
404
419
 
405
420
  private toggleMenu() {
406
- trace(`${T} toggleMenu`, {display: this.$el.find('#cc-select').css('display')})
407
421
  this.core
408
422
  .getPlugin('media_control')
409
423
  .trigger(ExtendedEvents.MEDIACONTROL_MENU_COLLAPSE, this.name)
410
- this.$el.find('#cc-select').toggle()
411
- // TODO hold state, add aria-expanded to the button, add active state to the button
424
+ this.open = !this.open
425
+ if (this.open) {
426
+ this.$el.find('#gplayer-cc-menu').show()
427
+ } else {
428
+ this.$el.find('#gplayer-cc-menu').hide()
429
+ }
430
+ this.$el.find('#gplayer-cc-button').attr('aria-expanded', this.open)
412
431
  }
413
432
 
414
433
  private itemElement(id: number): ZeptoResult {
415
- return this.$el.find(`#cc-select li a[data-cc-select="${id}"]`).parent()
434
+ // TODO fix semantically
435
+ return this.$el.find(`#gplayer-cc-menu [data-item="${id}"]`).parent()
416
436
  }
417
437
 
418
438
  private allItemElements(): ZeptoResult {
419
- return this.$('#cc-select li')
439
+ return this.$el.find('#gplayer-cc-menu li') // TODO fix semantically
420
440
  }
421
441
 
422
442
  private selectSubtitles() {
423
443
  const trackId = this.track ? this.track.id : -1
424
444
 
425
- this.core.activePlayback.closedCaptionsTrackId = trackId
445
+ this.core.activePlayback.closedCaptionsTrackId = trackId // TODO test
426
446
  }
427
447
 
428
448
  private getSubtitleText(track: TextTrack) {
@@ -434,6 +454,7 @@ export class ClosedCaptions extends UICorePlugin {
434
454
  for (const cue of cues) {
435
455
  if (currentTime >= cue.startTime && currentTime <= cue.endTime) {
436
456
  lines.push((cue as VTTCue).getCueAsHTML().textContent)
457
+ // TODO break?
437
458
  }
438
459
  }
439
460
  }
@@ -464,10 +485,8 @@ export class ClosedCaptions extends UICorePlugin {
464
485
  .removeClass('current')
465
486
  .find('a')
466
487
  .removeClass('gcore-skin-active')
488
+ .attr('aria-checked', 'false')
467
489
 
468
- trace(`${T} highlightCurrentSubtitles`, {
469
- track: this.track?.id,
470
- })
471
490
  const currentLevelElement = this.itemElement(
472
491
  this.track ? this.track.id : -1,
473
492
  )
@@ -475,11 +494,26 @@ export class ClosedCaptions extends UICorePlugin {
475
494
  .addClass('current')
476
495
  .find('a')
477
496
  .addClass('gcore-skin-active')
497
+ .attr('aria-checked', 'true')
478
498
  }
479
499
 
480
500
  private renderIcon() {
501
+ // render both icons at once
481
502
  const icon = this.active ? subtitlesOnIcon : subtitlesOffIcon
503
+ this.$el.find('#gplayer-cc-button').html(icon)
504
+ }
482
505
 
483
- this.$el.find('span.cc-text').html(icon)
506
+ private clampPopup() {
507
+ const availableHeight = this.core
508
+ .getPlugin('media_control')
509
+ .getAvailablePopupHeight()
510
+ this.$el.find('#gplayer-cc-menu').css('max-height', `${availableHeight}px`)
511
+ }
512
+
513
+ private mount() {
514
+ if (this.shouldRender()) {
515
+ const mediaControl = this.core.getPlugin('media_control')
516
+ mediaControl.slot('cc', this.$el)
517
+ }
484
518
  }
485
519
  }