@gcorevideo/player 2.28.19 → 2.28.21

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 (37) hide show
  1. package/assets/bottom-gear/gear.scss +2 -2
  2. package/assets/icons/new/arrow-right.svg +1 -1
  3. package/assets/icons/new/hd.svg +1 -1
  4. package/assets/icons/new/speed.svg +1 -1
  5. package/dist/core.js +1 -1
  6. package/dist/index.css +303 -303
  7. package/dist/index.embed.js +118 -37
  8. package/dist/index.js +118 -38
  9. package/lib/plugins/audio-selector/AudioTracks.d.ts +2 -0
  10. package/lib/plugins/audio-selector/AudioTracks.d.ts.map +1 -1
  11. package/lib/plugins/audio-selector/AudioTracks.js +11 -0
  12. package/lib/plugins/bottom-gear/BottomGear.d.ts +2 -0
  13. package/lib/plugins/bottom-gear/BottomGear.d.ts.map +1 -1
  14. package/lib/plugins/bottom-gear/BottomGear.js +11 -0
  15. package/lib/plugins/level-selector/QualityLevels.d.ts.map +1 -1
  16. package/lib/plugins/level-selector/QualityLevels.js +4 -0
  17. package/lib/plugins/media-control/MediaControl.d.ts +10 -0
  18. package/lib/plugins/media-control/MediaControl.d.ts.map +1 -1
  19. package/lib/plugins/media-control/MediaControl.js +21 -22
  20. package/lib/plugins/subtitles/ClosedCaptions.d.ts +3 -0
  21. package/lib/plugins/subtitles/ClosedCaptions.d.ts.map +1 -1
  22. package/lib/plugins/subtitles/ClosedCaptions.js +30 -11
  23. package/lib/testUtils.d.ts.map +1 -1
  24. package/lib/testUtils.js +2 -0
  25. package/lib/utils/clickaway.d.ts +15 -0
  26. package/lib/utils/clickaway.d.ts.map +1 -0
  27. package/lib/utils/clickaway.js +40 -0
  28. package/package.json +1 -1
  29. package/src/plugins/audio-selector/AudioTracks.ts +15 -1
  30. package/src/plugins/bottom-gear/BottomGear.ts +13 -0
  31. package/src/plugins/level-selector/QualityLevels.ts +4 -0
  32. package/src/plugins/media-control/MediaControl.ts +21 -24
  33. package/src/plugins/subtitles/ClosedCaptions.ts +34 -12
  34. package/src/plugins/subtitles/__tests__/ClosedCaptions.test.ts +9 -3
  35. package/src/testUtils.ts +2 -0
  36. package/src/utils/clickaway.ts +43 -0
  37. package/tsconfig.tsbuildinfo +1 -1
@@ -20,6 +20,7 @@ import volumeMaxIcon from '../../../assets/icons/new/volume-max.svg';
20
20
  import volumeOffIcon from '../../../assets/icons/new/volume-off.svg';
21
21
  import fullscreenOffIcon from '../../../assets/icons/new/fullscreen-off.svg';
22
22
  import fullscreenOnIcon from '../../../assets/icons/new/fullscreen-on.svg';
23
+ import { mediaControlClickaway } from '../../utils/clickaway.js';
23
24
  const STANDARD_MEDIA_CONTROL_ELEMENTS = [
24
25
  'duration',
25
26
  'fullscreen',
@@ -268,6 +269,7 @@ export class MediaControl extends UICorePlugin {
268
269
  }
269
270
  /**
270
271
  * @internal
272
+ * The methods declared here will be exposed via the main player object API
271
273
  */
272
274
  getExternalInterface() {
273
275
  return {
@@ -312,9 +314,7 @@ export class MediaControl extends UICorePlugin {
312
314
  this.listenTo(this.core.activeContainer, Events.CONTAINER_PAUSE, this.changeTogglePlay);
313
315
  this.listenTo(this.core.activeContainer, Events.CONTAINER_STOP, this.changeTogglePlay);
314
316
  this.listenTo(this.core.activeContainer, Events.CONTAINER_DBLCLICK, this.toggleFullscreen);
315
- this.listenTo(this.core.activeContainer, Events.CONTAINER_CLICK, () => {
316
- this.clickaway(this.core.activeContainer.$el[0]);
317
- });
317
+ this.listenTo(this.core.activeContainer, Events.CONTAINER_CLICK, () => this.clickaway(this.core.activeContainer.$el[0]));
318
318
  this.listenTo(this.core.activeContainer, Events.CONTAINER_TIMEUPDATE, this.onTimeUpdate);
319
319
  this.listenTo(this.core.activeContainer, Events.CONTAINER_PROGRESS, this.updateProgressBar);
320
320
  this.listenTo(this.core.activeContainer, Events.CONTAINER_SETTINGSUPDATE, this.updateSettings);
@@ -938,6 +938,23 @@ export class MediaControl extends UICorePlugin {
938
938
  mount(name, element) {
939
939
  mountTo(this.getMountParent(name), element);
940
940
  }
941
+ /**
942
+ * Set or reset the keep visibility state
943
+ *
944
+ * Keep visibility state controls whether the media control is hidden automatically after a delay.
945
+ * Keep visibility prevents the the auto-hide behaviour
946
+ *
947
+ * @param keepVisible - The state
948
+ */
949
+ setKeepVisible(keepVisible) {
950
+ this.keepVisible = keepVisible;
951
+ if (keepVisible) {
952
+ this.clickaway(this.core.activeContainer.$el[0]);
953
+ }
954
+ else {
955
+ this.clickaway(null);
956
+ }
957
+ }
941
958
  getMountParent(name) {
942
959
  switch (name) {
943
960
  case 'root':
@@ -1237,9 +1254,7 @@ export class MediaControl extends UICorePlugin {
1237
1254
  delayHide() {
1238
1255
  this.hide(this.options.hideMediaControlDelay || DEFAULT_HIDE_DELAY);
1239
1256
  }
1240
- // 2 seconds delay is needed since on mobile devices mouse(touch)move events are not dispatched immediately
1241
- // as opposed to the click event
1242
- clickaway = clickaway(() => setTimeout(this.resetUserKeepVisible, 0));
1257
+ clickaway = mediaControlClickaway(() => this.resetUserKeepVisible());
1243
1258
  }
1244
1259
  MediaControl.extend = function (properties) {
1245
1260
  return extend(MediaControl, properties);
@@ -1280,19 +1295,3 @@ function mergeElements(a, b) {
1280
1295
  return acc;
1281
1296
  }, a);
1282
1297
  }
1283
- function clickaway(callback) {
1284
- let handler = (event) => { };
1285
- return (node) => {
1286
- window.removeEventListener('click', handler);
1287
- if (!node) {
1288
- return;
1289
- }
1290
- handler = (event) => {
1291
- if (!node.contains(event.target)) {
1292
- window.removeEventListener('click', handler);
1293
- callback();
1294
- }
1295
- };
1296
- window.addEventListener('click', handler);
1297
- };
1298
- }
@@ -114,6 +114,7 @@ export declare class ClosedCaptions extends UICorePlugin {
114
114
  private applyPreselectedSubtitles;
115
115
  private hideMenu;
116
116
  private toggleMenu;
117
+ private setKeepVisible;
117
118
  private itemElement;
118
119
  private allItemElements;
119
120
  private selectSubtitles;
@@ -125,5 +126,7 @@ export declare class ClosedCaptions extends UICorePlugin {
125
126
  private renderIcon;
126
127
  private clampPopup;
127
128
  private mount;
129
+ private get shouldKeepVisible();
130
+ private clickaway;
128
131
  }
129
132
  //# sourceMappingURL=ClosedCaptions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ClosedCaptions.d.ts","sourceRoot":"","sources":["../../../src/plugins/subtitles/ClosedCaptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,YAAY,EAAwB,MAAM,cAAc,CAAA;AAOzE,OAAO,sCAAsC,CAAA;AAgB7C;;;GAGG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACzC;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,qBAAa,cAAe,SAAQ,YAAY;IAC9C,OAAO,CAAC,oBAAoB,CAAQ;IAEpC,OAAO,CAAC,MAAM,CAAQ;IAEtB,OAAO,CAAC,IAAI,CAAQ;IAEpB,OAAO,CAAC,KAAK,CAA6B;IAE1C,OAAO,CAAC,MAAM,CAAsB;IAEpC,OAAO,CAAC,KAAK,CAA2B;IAExC;;OAEG;IACH,IAAI,IAAI,WAEP;IAED;;OAEG;IACH,IAAI,gBAAgB;;MAEnB;IAED;;OAEG;IACH,MAAM,KAAK,OAAO,WAEjB;IAED,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAyB;IAEhE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAuB;IAE3D;;OAEG;IACH,IAAa,UAAU;;MAItB;IAED;;OAEG;IACH,IAAa,MAAM;;;MAKlB;IAED,OAAO,KAAK,mBAAmB,GAM9B;IAED;;OAEG;IACM,UAAU;IASnB,OAAO,CAAC,WAAW;IAkBnB,OAAO,CAAC,kBAAkB;IAgD1B,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,iBAAiB;IA+BzB,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,iBAAiB;IAqBzB;;OAEG;IACH,IAAI;IAcJ;;OAEG;IACH,IAAI;IAiBJ,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,UAAU;IAUlB;;OAEG;IACM,MAAM;IA4Bf,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,yBAAyB;IAgBjC,OAAO,CAAC,QAAQ;IAMhB,OAAO,CAAC,UAAU;IAalB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,eAAe;IAMvB,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,yBAAyB;IAiBjC,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,KAAK;CAMd"}
1
+ {"version":3,"file":"ClosedCaptions.d.ts","sourceRoot":"","sources":["../../../src/plugins/subtitles/ClosedCaptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,YAAY,EAAwB,MAAM,cAAc,CAAA;AAOzE,OAAO,sCAAsC,CAAA;AAiB7C;;;GAGG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACzC;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,qBAAa,cAAe,SAAQ,YAAY;IAC9C,OAAO,CAAC,oBAAoB,CAAQ;IAEpC,OAAO,CAAC,MAAM,CAAQ;IAEtB,OAAO,CAAC,IAAI,CAAQ;IAEpB,OAAO,CAAC,KAAK,CAA6B;IAE1C,OAAO,CAAC,MAAM,CAAsB;IAEpC,OAAO,CAAC,KAAK,CAA2B;IAExC;;OAEG;IACH,IAAI,IAAI,WAEP;IAED;;OAEG;IACH,IAAI,gBAAgB;;MAEnB;IAED;;OAEG;IACH,MAAM,KAAK,OAAO,WAEjB;IAED,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAyB;IAEhE,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAuB;IAE3D;;OAEG;IACH,IAAa,UAAU;;MAItB;IAED;;OAEG;IACH,IAAa,MAAM;;;MAKlB;IAED,OAAO,KAAK,mBAAmB,GAK9B;IAED;;OAEG;IACM,UAAU;IASnB,OAAO,CAAC,WAAW;IAkBnB,OAAO,CAAC,kBAAkB;IAmD1B,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,iBAAiB;IAkCzB,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,UAAU;IASlB,OAAO,CAAC,iBAAiB;IAqBzB;;OAEG;IACH,IAAI;IAcJ;;OAEG;IACH,IAAI;IAiBJ,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,UAAU;IAUlB;;OAEG;IACM,MAAM;IA4Bf,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,yBAAyB;IAejC,OAAO,CAAC,QAAQ;IAOhB,OAAO,CAAC,UAAU;IAclB,OAAO,CAAC,cAAc;IAOtB,OAAO,CAAC,WAAW;IAKnB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,eAAe;IAQvB,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,eAAe;IAIvB,OAAO,CAAC,iBAAiB;IAIzB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,yBAAyB;IAiBjC,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,UAAU;IAOlB,OAAO,CAAC,KAAK;IAOb,OAAO,KAAK,iBAAiB,GAE5B;IAED,OAAO,CAAC,SAAS,CAA+C;CACjE"}
@@ -9,6 +9,7 @@ import comboboxHTML from '../../../assets/subtitles/combobox.ejs';
9
9
  import stringHTML from '../../../assets/subtitles/string.ejs';
10
10
  import { isFullscreen } from '../utils/fullscreen.js';
11
11
  import { ExtendedEvents } from '../media-control/MediaControl.js';
12
+ import { mediaControlClickaway } from '../../utils/clickaway.js';
12
13
  const VERSION = '2.19.14';
13
14
  const LOCAL_STORAGE_CC_ID = 'gplayer.plugins.cc.selected';
14
15
  const T = 'plugins.cc';
@@ -93,8 +94,7 @@ export class ClosedCaptions extends UICorePlugin {
93
94
  }
94
95
  get preselectedLanguage() {
95
96
  return (this.core.options.cc?.language ??
96
- this.core.options.subtitles?.language ??
97
- '');
97
+ this.core.options.subtitles?.language);
98
98
  }
99
99
  /**
100
100
  * @internal
@@ -119,6 +119,9 @@ export class ClosedCaptions extends UICorePlugin {
119
119
  onContainerChanged() {
120
120
  this.listenTo(this.core.activeContainer, Events.CONTAINER_FULLSCREEN, this.onContainerResize);
121
121
  this.listenTo(this.core.activeContainer, Events.CONTAINER_RESIZE, this.onContainerResize);
122
+ this.listenTo(this.core.activeContainer, Events.CONTAINER_DESTROYED, () => {
123
+ this.clickaway(null);
124
+ });
122
125
  this.listenTo(this.core.activeContainer, 'container:advertisement:start', this.onStartAd);
123
126
  this.listenTo(this.core.activePlayback, Events.PLAYBACK_SUBTITLE_AVAILABLE, this.onSubtitleAvailable);
124
127
  this.listenTo(this.core.activePlayback, Events.PLAYBACK_SUBTITLE_CHANGED, this.onSubtitleChanged);
@@ -147,14 +150,17 @@ export class ClosedCaptions extends UICorePlugin {
147
150
  this.applyTracks();
148
151
  this.mount();
149
152
  }
150
- onSubtitleChanged({ id }) {
151
- trace(`${T} onSubtitleChanged`, { id });
153
+ onSubtitleChanged({ id: _ }) {
154
+ // ignoring the subtitle selected by the playback engine or user agent
155
+ const id = this.track?.id ?? -1;
152
156
  if (id === -1) {
153
157
  this.clearSubtitleText();
154
158
  }
155
159
  for (const track of this.tracks) {
160
+ // Native subtitles are always hidden
161
+ track.track.mode = 'hidden';
156
162
  if (track.id === id) {
157
- track.track.mode = 'showing';
163
+ // track.track.mode = 'showing'
158
164
  this.setSubtitleText(this.getSubtitleText(track.track));
159
165
  track.track.oncuechange = (e) => {
160
166
  try {
@@ -173,7 +179,7 @@ export class ClosedCaptions extends UICorePlugin {
173
179
  }
174
180
  else {
175
181
  track.track.oncuechange = null;
176
- track.track.mode = 'hidden';
182
+ // track.track.mode = 'hidden'
177
183
  }
178
184
  }
179
185
  }
@@ -302,18 +308,18 @@ export class ClosedCaptions extends UICorePlugin {
302
308
  applyPreselectedSubtitles() {
303
309
  if (!this.isPreselectedApplied) {
304
310
  this.isPreselectedApplied = true;
305
- if (!this.preselectedLanguage) {
306
- return;
307
- }
311
+ // if the language is undefined, then let the engine decide
312
+ // to hide the subtitles forcefully, set the language to 'none'
308
313
  setTimeout(() => {
309
314
  this.selectItem(this.tracks.find((t) => t.track.language === this.preselectedLanguage) ?? null);
310
- }, 300); // TODO why delay?
315
+ }, 0);
311
316
  }
312
317
  }
313
318
  hideMenu() {
314
319
  this.open = false;
315
320
  this.$el.find('#gplayer-cc-menu').hide();
316
321
  this.$el.find('#gplayer-cc-button').attr('aria-expanded', 'false');
322
+ this.setKeepVisible(false);
317
323
  }
318
324
  toggleMenu() {
319
325
  this.core
@@ -327,6 +333,13 @@ export class ClosedCaptions extends UICorePlugin {
327
333
  this.$el.find('#gplayer-cc-menu').hide();
328
334
  }
329
335
  this.$el.find('#gplayer-cc-button').attr('aria-expanded', this.open);
336
+ this.setKeepVisible(this.open);
337
+ }
338
+ setKeepVisible(keepVisible) {
339
+ if (this.shouldKeepVisible) {
340
+ this.core.getPlugin('media_control').setKeepVisible(keepVisible);
341
+ this.clickaway(keepVisible ? this.core.activeContainer.$el[0] : null);
342
+ }
330
343
  }
331
344
  itemElement(id) {
332
345
  // TODO fix semantically
@@ -337,7 +350,9 @@ export class ClosedCaptions extends UICorePlugin {
337
350
  }
338
351
  selectSubtitles() {
339
352
  const trackId = this.track ? this.track.id : -1;
340
- this.core.activePlayback.closedCaptionsTrackId = trackId; // TODO test
353
+ // TODO find out if this is needed
354
+ // this.core.activePlayback.closedCaptionsTrackId = trackId
355
+ this.core.activePlayback.closedCaptionsTrackId = -1;
341
356
  }
342
357
  getSubtitleText(track) {
343
358
  const currentTime = this.core.activePlayback?.getCurrentTime() ?? 0;
@@ -399,4 +414,8 @@ export class ClosedCaptions extends UICorePlugin {
399
414
  mediaControl.slot('cc', this.$el);
400
415
  }
401
416
  }
417
+ get shouldKeepVisible() {
418
+ return !!this.options.cc?.keepVisible;
419
+ }
420
+ clickaway = mediaControlClickaway(() => this.hideMenu());
402
421
  }
@@ -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,gBAuB/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,gBAyB/C;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,GAAG,OAe7C"}
package/lib/testUtils.js CHANGED
@@ -120,6 +120,8 @@ export function createMockMediaControl(core) {
120
120
  mediaControl.getAvailablePopupHeight = vi.fn().mockReturnValue(286);
121
121
  // @ts-ignore
122
122
  mediaControl.toggleElement = vi.fn();
123
+ // @ts-ignore
124
+ mediaControl.setKeepVisible = vi.fn();
123
125
  vi.spyOn(mediaControl, 'trigger');
124
126
  core.$el.append(mediaControl.$el);
125
127
  return mediaControl;
@@ -0,0 +1,15 @@
1
+ /**
2
+ *
3
+ * @param {() => void} callback - The callback to call when the user clicks away from the element
4
+ * @returns {(HTMLElement | null) => void}
5
+ */
6
+ export declare function clickaway(callback: () => void): (node: HTMLElement | null) => void;
7
+ /**
8
+ * Sets up a clickaway handler for the media control on mobile devices.
9
+ * The handler is deferred to ensure it is called after the next event loop tick.
10
+ *
11
+ * @param {() => void} callback - The callback to call when the user clicks away from the media control
12
+ * @returns {(HTMLElement | null) => void}
13
+ */
14
+ export declare function mediaControlClickaway(callback: () => void): (node: HTMLElement | null) => void;
15
+ //# sourceMappingURL=clickaway.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"clickaway.d.ts","sourceRoot":"","sources":["../../src/utils/clickaway.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,IAGlC,MAAM,WAAW,GAAG,IAAI,UAanC;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,IAAI,UAKxC,WAAW,GAAG,IAAI,UAKnC"}
@@ -0,0 +1,40 @@
1
+ import { Browser } from '@clappr/core';
2
+ /**
3
+ *
4
+ * @param {() => void} callback - The callback to call when the user clicks away from the element
5
+ * @returns {(HTMLElement | null) => void}
6
+ */
7
+ export function clickaway(callback) {
8
+ let handler = (event) => { };
9
+ return (node) => {
10
+ window.removeEventListener('click', handler);
11
+ if (!node) {
12
+ return;
13
+ }
14
+ handler = (event) => {
15
+ if (!node.contains(event.target)) {
16
+ window.removeEventListener('click', handler);
17
+ callback();
18
+ }
19
+ };
20
+ window.addEventListener('click', handler);
21
+ };
22
+ }
23
+ /**
24
+ * Sets up a clickaway handler for the media control on mobile devices.
25
+ * The handler is deferred to ensure it is called after the next event loop tick.
26
+ *
27
+ * @param {() => void} callback - The callback to call when the user clicks away from the media control
28
+ * @returns {(HTMLElement | null) => void}
29
+ */
30
+ export function mediaControlClickaway(callback) {
31
+ if (!Browser.isMobile) {
32
+ return () => { };
33
+ }
34
+ const cw = clickaway(callback);
35
+ return (node) => {
36
+ setTimeout(() => {
37
+ cw(node);
38
+ }, 0);
39
+ };
40
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gcorevideo/player",
3
- "version": "2.28.19",
3
+ "version": "2.28.21",
4
4
  "description": "Gcore JavaScript video player",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -9,6 +9,7 @@ import pluginHtml from '../../../assets/audio-tracks/template.ejs'
9
9
  import audioArrow from '../../../assets/icons/old/quality-arrow.svg'
10
10
  import { ZeptoResult } from '../../types.js'
11
11
  import { ExtendedEvents, MediaControl } from '../media-control/MediaControl.js'
12
+ import { mediaControlClickaway } from '../../utils/clickaway.js'
12
13
 
13
14
  const VERSION: string = '2.22.4'
14
15
 
@@ -132,6 +133,9 @@ export class AudioTracks extends UICorePlugin {
132
133
  this.listenTo(this.core.activeContainer, Events.CONTAINER_CLICK, () => {
133
134
  this.hideMenu()
134
135
  })
136
+ this.listenTo(this.core.activeContainer, Events.CONTAINER_DESTROYED, () => {
137
+ this.clickaway(null)
138
+ })
135
139
  }
136
140
 
137
141
  private shouldRender() {
@@ -179,6 +183,7 @@ export class AudioTracks extends UICorePlugin {
179
183
  this.open = false
180
184
  this.$el.find('#gplayer-audiotracks-menu').hide()
181
185
  this.$el.find('#gplayer-audiotracks-button').attr('aria-expanded', 'false')
186
+ this.setKeepVisible(false)
182
187
  }
183
188
 
184
189
  private toggleMenu() {
@@ -195,6 +200,13 @@ export class AudioTracks extends UICorePlugin {
195
200
  this.$el
196
201
  .find('#gplayer-audiotracks-button')
197
202
  .attr('aria-expanded', this.open)
203
+
204
+ this.setKeepVisible(this.open)
205
+ }
206
+
207
+ private setKeepVisible(keepVisible: boolean) {
208
+ this.core.getPlugin('media_control').setKeepVisible(keepVisible)
209
+ this.clickaway(keepVisible ? this.core.activeContainer.$el[0] : null)
198
210
  }
199
211
 
200
212
  private buttonElement(): ZeptoResult {
@@ -209,7 +221,7 @@ export class AudioTracks extends UICorePlugin {
209
221
  return (
210
222
  this.$(
211
223
  '#gplayer-audiotracks-menu a' +
212
- (id !== undefined ? `[data-item="${id}"]` : ''),
224
+ (id !== undefined ? `[data-item="${id}"]` : ''),
213
225
  ) as ZeptoResult
214
226
  ).parent()
215
227
  }
@@ -253,4 +265,6 @@ export class AudioTracks extends UICorePlugin {
253
265
  this.core.getPlugin('media_control')?.slot('audiotracks', this.$el)
254
266
  }
255
267
  }
268
+
269
+ private clickaway = mediaControlClickaway(() => this.hideMenu())
256
270
  }
@@ -17,6 +17,7 @@ import gearIcon from '../../../assets/icons/new/gear.svg'
17
17
  import gearHdIcon from '../../../assets/icons/new/gear-hd.svg'
18
18
  import { ZeptoResult } from '../../types.js'
19
19
  import { ExtendedEvents } from '../media-control/MediaControl.js'
20
+ import { mediaControlClickaway } from '../../utils/clickaway.js'
20
21
 
21
22
  const VERSION = '2.19.12'
22
23
 
@@ -214,6 +215,9 @@ export class BottomGear extends UICorePlugin {
214
215
  this.listenTo(container, ClapprEvents.CONTAINER_CLICK, () => {
215
216
  this.collapse()
216
217
  })
218
+ this.listenTo(container, ClapprEvents.CONTAINER_DESTROYED, () => {
219
+ this.clickaway(null)
220
+ })
217
221
  }
218
222
 
219
223
  private highDefinitionUpdate(isHd: boolean) {
@@ -272,6 +276,12 @@ export class BottomGear extends UICorePlugin {
272
276
  this.$el
273
277
  .find('#gear-button')
274
278
  .attr('aria-expanded', (!this.collapsed).toString())
279
+ this.setKeepVisible(!this.collapsed)
280
+ }
281
+
282
+ private setKeepVisible(keepVisible: boolean) {
283
+ this.core.getPlugin('media_control').setKeepVisible(keepVisible)
284
+ this.clickaway(keepVisible ? this.core.activeContainer.$el[0] : null)
275
285
  }
276
286
 
277
287
  private collapse() {
@@ -280,6 +290,7 @@ export class BottomGear extends UICorePlugin {
280
290
  this.$el.find('#gear-button').attr('aria-expanded', 'false')
281
291
  // TODO hide submenus
282
292
  this.collapseSubmenus()
293
+ this.setKeepVisible(false)
283
294
  }
284
295
 
285
296
  private onCoreReady() {
@@ -328,4 +339,6 @@ export class BottomGear extends UICorePlugin {
328
339
  .find('.gear-sub-menu')
329
340
  .css('max-height', `${availableHeight - MENU_BACKLINK_HEIGHT}px`)
330
341
  }
342
+
343
+ private clickaway = mediaControlClickaway(() => this.collapse())
331
344
  }
@@ -217,6 +217,7 @@ export class QualityLevels extends UICorePlugin {
217
217
  */
218
218
  override render() {
219
219
  if (!this.shouldRender()) {
220
+ this.$el.hide()
220
221
  return this
221
222
  }
222
223
  this.renderDropdown()
@@ -241,6 +242,9 @@ export class QualityLevels extends UICorePlugin {
241
242
  }
242
243
 
243
244
  private updateButton() {
245
+ if (!this.shouldRender()) {
246
+ return
247
+ }
244
248
  ;(this.core.getPlugin('bottom_gear') as BottomGear)
245
249
  ?.addItem('quality', this.$el)
246
250
  .html(
@@ -37,6 +37,7 @@ import volumeMaxIcon from '../../../assets/icons/new/volume-max.svg'
37
37
  import volumeOffIcon from '../../../assets/icons/new/volume-off.svg'
38
38
  import fullscreenOffIcon from '../../../assets/icons/new/fullscreen-off.svg'
39
39
  import fullscreenOnIcon from '../../../assets/icons/new/fullscreen-on.svg'
40
+ import { mediaControlClickaway } from '../../utils/clickaway.js'
40
41
 
41
42
  const STANDARD_MEDIA_CONTROL_ELEMENTS: string[] = [
42
43
  'duration',
@@ -399,6 +400,7 @@ export class MediaControl extends UICorePlugin {
399
400
 
400
401
  /**
401
402
  * @internal
403
+ * The methods declared here will be exposed via the main player object API
402
404
  */
403
405
  override getExternalInterface() {
404
406
  return {
@@ -471,9 +473,7 @@ export class MediaControl extends UICorePlugin {
471
473
  this.listenTo(
472
474
  this.core.activeContainer,
473
475
  Events.CONTAINER_CLICK,
474
- () => {
475
- this.clickaway(this.core.activeContainer.$el[0])
476
- },
476
+ () => this.clickaway(this.core.activeContainer.$el[0]),
477
477
  )
478
478
  this.listenTo(
479
479
  this.core.activeContainer,
@@ -1269,6 +1269,23 @@ export class MediaControl extends UICorePlugin {
1269
1269
  mountTo(this.getMountParent(name), element)
1270
1270
  }
1271
1271
 
1272
+ /**
1273
+ * Set or reset the keep visibility state
1274
+ *
1275
+ * Keep visibility state controls whether the media control is hidden automatically after a delay.
1276
+ * Keep visibility prevents the the auto-hide behaviour
1277
+ *
1278
+ * @param keepVisible - The state
1279
+ */
1280
+ setKeepVisible(keepVisible: boolean) {
1281
+ this.keepVisible = keepVisible
1282
+ if (keepVisible) {
1283
+ this.clickaway(this.core.activeContainer.$el[0])
1284
+ } else {
1285
+ this.clickaway(null)
1286
+ }
1287
+ }
1288
+
1272
1289
  private getMountParent(name: MediaControlSlotMountPoint): ZeptoResult {
1273
1290
  switch (name) {
1274
1291
  case 'root':
@@ -1630,9 +1647,7 @@ export class MediaControl extends UICorePlugin {
1630
1647
  this.hide(this.options.hideMediaControlDelay || DEFAULT_HIDE_DELAY)
1631
1648
  }
1632
1649
 
1633
- // 2 seconds delay is needed since on mobile devices mouse(touch)move events are not dispatched immediately
1634
- // as opposed to the click event
1635
- private clickaway = clickaway(() => setTimeout(this.resetUserKeepVisible, 0))
1650
+ private clickaway = mediaControlClickaway(() => this.resetUserKeepVisible())
1636
1651
  }
1637
1652
 
1638
1653
  MediaControl.extend = function (properties) {
@@ -1686,21 +1701,3 @@ function mergeElements(
1686
1701
  return acc
1687
1702
  }, a)
1688
1703
  }
1689
-
1690
- function clickaway(callback: () => void) {
1691
- let handler = (event: MouseEvent | TouchEvent) => {}
1692
-
1693
- return (node: HTMLElement | null) => {
1694
- window.removeEventListener('click', handler)
1695
- if (!node) {
1696
- return
1697
- }
1698
- handler = (event: MouseEvent | TouchEvent) => {
1699
- if (!node.contains(event.target as Node)) {
1700
- window.removeEventListener('click', handler)
1701
- callback()
1702
- }
1703
- }
1704
- window.addEventListener('click', handler)
1705
- }
1706
- }
@@ -14,6 +14,7 @@ import stringHTML from '../../../assets/subtitles/string.ejs'
14
14
  import { isFullscreen } from '../utils/fullscreen.js'
15
15
  import type { ZeptoResult } from '../../types.js'
16
16
  import { ExtendedEvents } from '../media-control/MediaControl.js'
17
+ import { mediaControlClickaway } from '../../utils/clickaway.js'
17
18
 
18
19
  const VERSION: string = '2.19.14'
19
20
 
@@ -124,11 +125,10 @@ export class ClosedCaptions extends UICorePlugin {
124
125
  }
125
126
  }
126
127
 
127
- private get preselectedLanguage(): string {
128
+ private get preselectedLanguage(): string | undefined {
128
129
  return (
129
130
  this.core.options.cc?.language ??
130
- this.core.options.subtitles?.language ??
131
- ''
131
+ this.core.options.subtitles?.language
132
132
  )
133
133
  }
134
134
 
@@ -173,6 +173,9 @@ export class ClosedCaptions extends UICorePlugin {
173
173
  Events.CONTAINER_RESIZE,
174
174
  this.onContainerResize,
175
175
  )
176
+ this.listenTo(this.core.activeContainer, Events.CONTAINER_DESTROYED, () => {
177
+ this.clickaway(null)
178
+ })
176
179
  this.listenTo(
177
180
  this.core.activeContainer,
178
181
  'container:advertisement:start',
@@ -218,14 +221,17 @@ export class ClosedCaptions extends UICorePlugin {
218
221
  this.mount()
219
222
  }
220
223
 
221
- private onSubtitleChanged({ id }: { id: number }) {
222
- trace(`${T} onSubtitleChanged`, { id })
224
+ private onSubtitleChanged({ id: _ }: { id: number }) {
225
+ // ignoring the subtitle selected by the playback engine or user agent
226
+ const id = this.track?.id ?? -1
223
227
  if (id === -1) {
224
228
  this.clearSubtitleText()
225
229
  }
226
230
  for (const track of this.tracks) {
231
+ // Native subtitles are always hidden
232
+ track.track.mode = 'hidden'
227
233
  if (track.id === id) {
228
- track.track.mode = 'showing'
234
+ // track.track.mode = 'showing'
229
235
 
230
236
  this.setSubtitleText(this.getSubtitleText(track.track))
231
237
 
@@ -244,7 +250,7 @@ export class ClosedCaptions extends UICorePlugin {
244
250
  }
245
251
  } else {
246
252
  track.track.oncuechange = null
247
- track.track.mode = 'hidden'
253
+ // track.track.mode = 'hidden'
248
254
  }
249
255
  }
250
256
  }
@@ -408,16 +414,15 @@ export class ClosedCaptions extends UICorePlugin {
408
414
  private applyPreselectedSubtitles() {
409
415
  if (!this.isPreselectedApplied) {
410
416
  this.isPreselectedApplied = true
411
- if (!this.preselectedLanguage) {
412
- return
413
- }
417
+ // if the language is undefined, then let the engine decide
418
+ // to hide the subtitles forcefully, set the language to 'none'
414
419
  setTimeout(() => {
415
420
  this.selectItem(
416
421
  this.tracks.find(
417
422
  (t) => t.track.language === this.preselectedLanguage,
418
423
  ) ?? null,
419
424
  )
420
- }, 300) // TODO why delay?
425
+ }, 0)
421
426
  }
422
427
  }
423
428
 
@@ -425,6 +430,7 @@ export class ClosedCaptions extends UICorePlugin {
425
430
  this.open = false
426
431
  this.$el.find('#gplayer-cc-menu').hide()
427
432
  this.$el.find('#gplayer-cc-button').attr('aria-expanded', 'false')
433
+ this.setKeepVisible(false)
428
434
  }
429
435
 
430
436
  private toggleMenu() {
@@ -438,6 +444,14 @@ export class ClosedCaptions extends UICorePlugin {
438
444
  this.$el.find('#gplayer-cc-menu').hide()
439
445
  }
440
446
  this.$el.find('#gplayer-cc-button').attr('aria-expanded', this.open)
447
+ this.setKeepVisible(this.open)
448
+ }
449
+
450
+ private setKeepVisible(keepVisible: boolean) {
451
+ if (this.shouldKeepVisible) {
452
+ this.core.getPlugin('media_control').setKeepVisible(keepVisible)
453
+ this.clickaway(keepVisible ? this.core.activeContainer.$el[0] : null)
454
+ }
441
455
  }
442
456
 
443
457
  private itemElement(id: number): ZeptoResult {
@@ -452,7 +466,9 @@ export class ClosedCaptions extends UICorePlugin {
452
466
  private selectSubtitles() {
453
467
  const trackId = this.track ? this.track.id : -1
454
468
 
455
- this.core.activePlayback.closedCaptionsTrackId = trackId // TODO test
469
+ // TODO find out if this is needed
470
+ // this.core.activePlayback.closedCaptionsTrackId = trackId
471
+ this.core.activePlayback.closedCaptionsTrackId = -1
456
472
  }
457
473
 
458
474
  private getSubtitleText(track: TextTrack) {
@@ -526,4 +542,10 @@ export class ClosedCaptions extends UICorePlugin {
526
542
  mediaControl.slot('cc', this.$el)
527
543
  }
528
544
  }
545
+
546
+ private get shouldKeepVisible() {
547
+ return !!this.options.cc?.keepVisible
548
+ }
549
+
550
+ private clickaway = mediaControlClickaway(() => this.hideMenu())
529
551
  }
@@ -132,6 +132,7 @@ describe('ClosedCaptions', () => {
132
132
  describe('when subtitle is changed', () => {
133
133
  beforeEach(async () => {
134
134
  emitSubtitleAvailable(core)
135
+ cc.$el.find('#gplayer-cc-button').click()
135
136
  await new Promise((resolve) => setTimeout(resolve, 100))
136
137
  core.activePlayback.getCurrentTime = vi.fn().mockReturnValue(7)
137
138
  core.activeContainer.getCurrentTime = vi.fn().mockReturnValue(7)
@@ -167,11 +168,16 @@ describe('ClosedCaptions', () => {
167
168
  ),
168
169
  },
169
170
  ]
171
+ cc.$el.find('#gplayer-cc-menu li:nth-child(2) a').click()
172
+ await new Promise((resolve) => setTimeout(resolve, 100))
173
+ // TODO test explicitly that PLAYBACK_SUBTITLE_CHANGED event does not cause track switch
170
174
  core.activePlayback.emit(Events.PLAYBACK_SUBTITLE_CHANGED, { id: 2 })
175
+ await new Promise((resolve) => setTimeout(resolve, 100))
176
+
171
177
  })
172
178
  it('should activate subtitle track', () => {
173
179
  expect(core.activePlayback.closedCaptionsTracks[1].track.mode).toBe(
174
- 'showing',
180
+ 'hidden',
175
181
  )
176
182
  expect(core.activePlayback.closedCaptionsTracks[0].track.mode).toBe(
177
183
  'hidden',
@@ -188,8 +194,8 @@ describe('ClosedCaptions', () => {
188
194
  emitSubtitleAvailable(core)
189
195
  cc.$el.find('#gplayer-cc-menu li:nth-child(2) a').click()
190
196
  })
191
- it('should activate subtitle track', () => {
192
- expect(core.activePlayback.closedCaptionsTrackId).toEqual(2)
197
+ it('should deactivate native subtitles track', () => {
198
+ expect(core.activePlayback.closedCaptionsTrackId).toEqual(-1)
193
199
  })
194
200
  it('should highlight selected menu item', () => {
195
201
  expect(