@gcorevideo/player 2.22.30 → 2.23.0

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 (69) hide show
  1. package/assets/media-control/container.scss +2 -3
  2. package/assets/poster/poster.ejs +3 -1
  3. package/assets/poster/poster.scss +3 -3
  4. package/assets/style/main.scss +1 -1
  5. package/assets/thumbnails/scrub-thumbnails.ejs +5 -10
  6. package/assets/thumbnails/style.scss +4 -5
  7. package/dist/core.js +1 -1
  8. package/dist/index.css +533 -532
  9. package/dist/index.js +273 -377
  10. package/dist/player.d.ts +63 -33
  11. package/docs/api/{player.seektime.bindevents.md → player.clapprstats.clearmetrics.md} +3 -3
  12. package/docs/api/player.clapprstats.md +14 -0
  13. package/docs/api/player.extendedevents.md +14 -0
  14. package/docs/api/player.md +13 -2
  15. package/docs/api/player.seektime.attributes.md +0 -1
  16. package/docs/api/player.seektime.md +6 -197
  17. package/docs/api/{player.seektime.render.md → player.seektimesettings.md} +7 -7
  18. package/docs/api/player.skiptime.md +3 -184
  19. package/lib/plugins/clips/Clips.d.ts +7 -0
  20. package/lib/plugins/clips/Clips.d.ts.map +1 -1
  21. package/lib/plugins/clips/Clips.js +8 -0
  22. package/lib/plugins/media-control/MediaControl.d.ts +1 -7
  23. package/lib/plugins/media-control/MediaControl.d.ts.map +1 -1
  24. package/lib/plugins/media-control/MediaControl.js +9 -18
  25. package/lib/plugins/poster/Poster.d.ts +24 -14
  26. package/lib/plugins/poster/Poster.d.ts.map +1 -1
  27. package/lib/plugins/poster/Poster.js +67 -97
  28. package/lib/plugins/thumbnails/Thumbnails.d.ts +36 -33
  29. package/lib/plugins/thumbnails/Thumbnails.d.ts.map +1 -1
  30. package/lib/plugins/thumbnails/Thumbnails.js +174 -259
  31. package/lib/plugins/thumbnails/utils.d.ts +5 -0
  32. package/lib/plugins/thumbnails/utils.d.ts.map +1 -0
  33. package/lib/plugins/thumbnails/utils.js +12 -0
  34. package/lib/testUtils.d.ts +13 -39
  35. package/lib/testUtils.d.ts.map +1 -1
  36. package/lib/testUtils.js +15 -67
  37. package/package.json +2 -1
  38. package/src/plugins/clips/Clips.ts +10 -1
  39. package/src/plugins/media-control/MediaControl.ts +10 -21
  40. package/src/plugins/media-control/__tests__/__snapshots__/MediaControl.test.ts.snap +1 -1
  41. package/src/plugins/poster/Poster.ts +91 -110
  42. package/src/plugins/poster/__tests__/Poster.test.ts +119 -0
  43. package/src/plugins/poster/__tests__/__snapshots__/Poster.test.ts.snap +8 -0
  44. package/src/plugins/source-controller/__tests__/SourceController.test.ts +1 -2
  45. package/src/plugins/thumbnails/Thumbnails.ts +228 -330
  46. package/src/plugins/thumbnails/__tests__/Thumbnails.test.ts +72 -0
  47. package/src/plugins/thumbnails/__tests__/__snapshots__/Thumbnails.test.ts.snap +10 -0
  48. package/src/plugins/thumbnails/utils.ts +12 -0
  49. package/src/testUtils.ts +15 -88
  50. package/temp/player.api.json +295 -829
  51. package/tsconfig.tsbuildinfo +1 -1
  52. package/docs/api/player.seektime.durationshown.md +0 -14
  53. package/docs/api/player.seektime.getseektime.md +0 -20
  54. package/docs/api/player.seektime.islivestreamwithdvr.md +0 -14
  55. package/docs/api/player.seektime.mediacontrol.md +0 -14
  56. package/docs/api/player.seektime.mediacontrolcontainer.md +0 -14
  57. package/docs/api/player.seektime.shouldbevisible.md +0 -18
  58. package/docs/api/player.seektime.template.md +0 -14
  59. package/docs/api/player.seektime.update.md +0 -18
  60. package/docs/api/player.skiptime.attributes.md +0 -17
  61. package/docs/api/player.skiptime.bindevents.md +0 -18
  62. package/docs/api/player.skiptime.events.md +0 -18
  63. package/docs/api/player.skiptime.handlerewindclicks.md +0 -18
  64. package/docs/api/player.skiptime.render.md +0 -18
  65. package/docs/api/player.skiptime.setback.md +0 -18
  66. package/docs/api/player.skiptime.setforward.md +0 -18
  67. package/docs/api/player.skiptime.setmidclick.md +0 -18
  68. package/docs/api/player.skiptime.template.md +0 -14
  69. package/docs/api/player.skiptime.togglefullscreen.md +0 -18
@@ -1,39 +1,5 @@
1
1
  import { UICorePlugin } from '@clappr/core';
2
2
  import Events from 'eventemitter3';
3
- /**
4
- * @internal
5
- * @deprecated
6
- * TODO use createMockPlayback() instead
7
- */
8
- export declare class _MockPlayback extends Events {
9
- protected options: any;
10
- readonly i18n: any;
11
- protected playerError?: any | undefined;
12
- constructor(options: any, i18n: any, playerError?: any | undefined);
13
- get name(): string;
14
- consent(): void;
15
- play(): void;
16
- pause(): void;
17
- stop(): void;
18
- destroy(): void;
19
- seek(): void;
20
- seekPercentage(): void;
21
- getDuration(): number;
22
- enterPiP(): void;
23
- exitPiP(): void;
24
- getPlaybackType(): string;
25
- getStartTimeOffset(): number;
26
- getCurrentTime(): number;
27
- isHighDefinitionInUse(): boolean;
28
- mute(): void;
29
- unmute(): void;
30
- volume(): void;
31
- configure(): void;
32
- attemptAutoPlay(): boolean;
33
- canAutoPlay(): boolean;
34
- onResize(): boolean;
35
- trigger(event: string, ...args: any[]): void;
36
- }
37
3
  export declare function createMockCore(options?: Record<string, unknown>, container?: any): Events<string | symbol, any> & {
38
4
  el: HTMLDivElement;
39
5
  $el: any;
@@ -68,11 +34,12 @@ export declare function createMockPlayback(name?: string): Events<string | symbo
68
34
  el: HTMLVideoElement;
69
35
  dvrEnabled: boolean;
70
36
  dvrInUse: boolean;
37
+ isAudioOnly: boolean;
71
38
  levels: never[];
72
- consent(): void;
73
- play(): void;
74
- pause(): void;
75
- stop(): void;
39
+ consent: import("vitest").Mock<(...args: any[]) => any>;
40
+ play: import("vitest").Mock<(...args: any[]) => any>;
41
+ pause: import("vitest").Mock<(...args: any[]) => any>;
42
+ stop: import("vitest").Mock<(...args: any[]) => any>;
76
43
  destroy: import("vitest").Mock<(...args: any[]) => any>;
77
44
  seek: import("vitest").Mock<(...args: any[]) => any>;
78
45
  seekPercentage: import("vitest").Mock<(...args: any[]) => any>;
@@ -97,13 +64,20 @@ export declare function createMockPlayback(name?: string): Events<string | symbo
97
64
  export declare function createMockContainer(options?: Record<string, unknown>, playback?: any): Events<string | symbol, any> & {
98
65
  el: any;
99
66
  playback: any;
100
- options: Record<string, unknown>;
67
+ options: {
68
+ [x: string]: unknown;
69
+ };
101
70
  $el: any;
71
+ disableMediaControl: import("vitest").Mock<(...args: any[]) => any>;
72
+ enableMediaControl: import("vitest").Mock<(...args: any[]) => any>;
73
+ enterPiP: import("vitest").Mock<(...args: any[]) => any>;
74
+ exitPiP: import("vitest").Mock<(...args: any[]) => any>;
102
75
  getDuration: import("vitest").Mock<(...args: any[]) => any>;
103
76
  getPlugin: import("vitest").Mock<(...args: any[]) => any>;
104
77
  getPlaybackType: import("vitest").Mock<(...args: any[]) => any>;
105
78
  isDvrInUse: import("vitest").Mock<(...args: any[]) => any>;
106
79
  isDvrEnabled: import("vitest").Mock<(...args: any[]) => any>;
80
+ isHighDefinitionInUse: import("vitest").Mock<(...args: any[]) => any>;
107
81
  isPlaying: import("vitest").Mock<(...args: any[]) => any>;
108
82
  play: import("vitest").Mock<(...args: any[]) => any>;
109
83
  seek: import("vitest").Mock<(...args: any[]) => any>;
@@ -1 +1 @@
1
- {"version":3,"file":"testUtils.d.ts","sourceRoot":"","sources":["../src/testUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,YAAY,EAAE,MAAM,cAAc,CAAA;AACxD,OAAO,MAAM,MAAM,eAAe,CAAA;AAElC;;;;GAIG;AACH,qBAAa,aAAc,SAAQ,MAAM;IAErC,SAAS,CAAC,OAAO,EAAE,GAAG;IACtB,QAAQ,CAAC,IAAI,EAAE,GAAG;IAClB,SAAS,CAAC,WAAW,CAAC,EAAE,GAAG;gBAFjB,OAAO,EAAE,GAAG,EACb,IAAI,EAAE,GAAG,EACR,WAAW,CAAC,EAAE,GAAG,YAAA;IAK7B,IAAI,IAAI,WAEP;IAED,OAAO;IAEP,IAAI;IAEJ,KAAK;IAEL,IAAI;IAEJ,OAAO;IAEP,IAAI;IAEJ,cAAc;IAEd,WAAW;IAIX,QAAQ;IAER,OAAO;IAEP,eAAe;IAIf,kBAAkB;IAIlB,cAAc;IAId,qBAAqB;IAIrB,IAAI;IAEJ,MAAM;IAEN,MAAM;IAEN,SAAS;IAET,eAAe;IAIf,WAAW;IAIX,QAAQ;IAIR,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE;CAGtC;AAED,wBAAgB,cAAc,CAC5B,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACrC,SAAS,GAAE,GAAkC;;;;;;;;;;;;;;;;EAqB9C;AAED,wBAAgB,gBAAgB;;;EAK/B;AAED,wBAAgB,mBAAmB;;;;;;EAKlC;AAED,wBAAgB,kBAAkB,CAAC,IAAI,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAkC/C;AAED,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACrC,QAAQ,GAAE,GAA0B;;;;;;;;;;;;;;;;EAqBrC;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,GAAG,gBAe/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;;;;;;;;;;;;;;;;EAqB9C;AAED,wBAAgB,gBAAgB;;;EAK/B;AAED,wBAAgB,mBAAmB;;;;;;EAKlC;AAED,wBAAgB,kBAAkB,CAAC,IAAI,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAmC/C;AAED,wBAAgB,mBAAmB,CACjC,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,EACrC,QAAQ,GAAE,GAA0B;;;;;;;;;;;;;;;;;;;;;;;EA4BrC;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,GAAG,gBAc/C;AAED,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,GAAG,OAe7C"}
package/lib/testUtils.js CHANGED
@@ -1,65 +1,6 @@
1
- import { $, Playback, UICorePlugin } from '@clappr/core';
1
+ import { $, UICorePlugin } from '@clappr/core';
2
2
  import Events from 'eventemitter3';
3
3
  import { vi } from 'vitest';
4
- /**
5
- * @internal
6
- * @deprecated
7
- * TODO use createMockPlayback() instead
8
- */
9
- export class _MockPlayback extends Events {
10
- options;
11
- i18n;
12
- playerError;
13
- constructor(options, i18n, playerError) {
14
- super();
15
- this.options = options;
16
- this.i18n = i18n;
17
- this.playerError = playerError;
18
- }
19
- get name() {
20
- return 'mock';
21
- }
22
- consent() { }
23
- play() { }
24
- pause() { }
25
- stop() { }
26
- destroy() { }
27
- seek() { }
28
- seekPercentage() { }
29
- getDuration() {
30
- return 100;
31
- }
32
- enterPiP() { }
33
- exitPiP() { }
34
- getPlaybackType() {
35
- return Playback.LIVE;
36
- }
37
- getStartTimeOffset() {
38
- return 0;
39
- }
40
- getCurrentTime() {
41
- return 0;
42
- }
43
- isHighDefinitionInUse() {
44
- return false;
45
- }
46
- mute() { }
47
- unmute() { }
48
- volume() { }
49
- configure() { }
50
- attemptAutoPlay() {
51
- return true;
52
- }
53
- canAutoPlay() {
54
- return true;
55
- }
56
- onResize() {
57
- return true;
58
- }
59
- trigger(event, ...args) {
60
- this.emit(event, ...args);
61
- }
62
- }
63
4
  export function createMockCore(options = {}, container = createMockContainer(options)) {
64
5
  const el = document.createElement('div');
65
6
  const emitter = new Events();
@@ -101,11 +42,12 @@ export function createMockPlayback(name = 'mock') {
101
42
  el: document.createElement('video'),
102
43
  dvrEnabled: false,
103
44
  dvrInUse: false,
45
+ isAudioOnly: false,
104
46
  levels: [],
105
- consent() { },
106
- play() { },
107
- pause() { },
108
- stop() { },
47
+ consent: vi.fn(),
48
+ play: vi.fn(),
49
+ pause: vi.fn(),
50
+ stop: vi.fn(),
109
51
  destroy: vi.fn(),
110
52
  seek: vi.fn(),
111
53
  seekPercentage: vi.fn(),
@@ -134,13 +76,20 @@ export function createMockContainer(options = {}, playback = createMockPlayback(
134
76
  return Object.assign(emitter, {
135
77
  el,
136
78
  playback,
137
- options,
79
+ options: {
80
+ ...options,
81
+ },
138
82
  $el: $(el),
83
+ disableMediaControl: vi.fn(),
84
+ enableMediaControl: vi.fn(),
85
+ enterPiP: vi.fn(),
86
+ exitPiP: vi.fn(),
139
87
  getDuration: vi.fn().mockReturnValue(0),
140
88
  getPlugin: vi.fn(),
141
89
  getPlaybackType: vi.fn(),
142
90
  isDvrInUse: vi.fn().mockReturnValue(false),
143
91
  isDvrEnabled: vi.fn().mockReturnValue(false),
92
+ isHighDefinitionInUse: vi.fn().mockReturnValue(false),
144
93
  isPlaying: vi.fn().mockReturnValue(false),
145
94
  play: vi.fn(),
146
95
  seek: vi.fn(),
@@ -151,12 +100,11 @@ export function createMockContainer(options = {}, playback = createMockPlayback(
151
100
  }
152
101
  export function createMockMediaControl(core) {
153
102
  const mediaControl = new UICorePlugin(core);
103
+ // TODO <div class="media-control-layer">
154
104
  mediaControl.$el.html(`<div class="media-control-left-panel" data-media-control></div>
155
105
  <div class="media-control-right-panel" data-media-control></div>
156
106
  <div class="media-control-center-panel" data-media-control></div>`);
157
107
  // @ts-ignore
158
- mediaControl.putElement = vi.fn();
159
- // @ts-ignore
160
108
  mediaControl.mount = vi.fn();
161
109
  // @ts-ignore
162
110
  mediaControl.toggleElement = vi.fn();
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@gcorevideo/player",
3
- "version": "2.22.30",
3
+ "version": "2.23.0",
4
4
  "description": "Gcore JavaScript video player",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
7
7
  "typings": "lib/index.d.ts",
8
+ "keywords": ["player", "video streaming"],
8
9
  "scripts": {
9
10
  "build": "npm run build:ts && npm run build:bundle",
10
11
  "build:1": "npm run build:ts && npm run build:bundle && date",
@@ -2,7 +2,7 @@ import { Container, Events, UICorePlugin, $, template } from '@clappr/core'
2
2
  import { trace } from '@gcorevideo/utils'
3
3
  import assert from 'assert'
4
4
 
5
- import { TimeProgress } from '../../playback.types.js'
5
+ import { TimeProgress, TimeValue } from '../../playback.types.js'
6
6
  import type { ZeptoResult } from '../../types.js'
7
7
  import '../../../assets/clips/clips.scss'
8
8
  import { ClipDesc } from './types.js'
@@ -117,6 +117,15 @@ export class Clips extends UICorePlugin {
117
117
  return super.enable()
118
118
  }
119
119
 
120
+ /**
121
+ * Get the text of the clip at the given time
122
+ * @param time - The time to get the text for
123
+ * @returns The text of the clip at the given time
124
+ */
125
+ getText(time: TimeValue): string | undefined {
126
+ return this.clips.find((clip) => clip.start <= time && clip.end >= time)?.text
127
+ }
128
+
120
129
  private onCoreReady() {
121
130
  trace(`${T} onCoreReady`)
122
131
  const mediaControl = this.core.getPlugin('media_control')
@@ -279,8 +279,8 @@ export class MediaControl extends UICorePlugin {
279
279
  }
280
280
 
281
281
  /**
282
+ * Use in mediacontrol-based plugins to access the active container
282
283
  * @internal
283
- * @deprecated Use core.activeContainer directly
284
284
  */
285
285
  get container() {
286
286
  return this.core.activeContainer
@@ -645,18 +645,17 @@ export class MediaControl extends UICorePlugin {
645
645
  }
646
646
 
647
647
  private mousemoveOnSeekBar(event: MouseEvent) {
648
+ const offset = MediaControl.getPageX(event) -
649
+ (this.$seekBarContainer.offset().left ?? 0) // TODO check if the result can be negative
650
+ const hoverOffset =
651
+ offset -
652
+ (this.$seekBarHover.width() ?? 0) / 2
653
+ const pos = offset ? Math.min(1, Math.max(offset / this.$seekBarContainer.width(), 0)) : 0
648
654
  if (this.settings.seekEnabled) {
649
- // assert.ok(this.$seekBarHover && this.$seekBarContainer, 'seek bar elements must be present');
650
- if (this.$seekBarHover && this.$seekBarContainer) {
651
- const offsetX =
652
- MediaControl.getPageX(event) -
653
- this.$seekBarContainer.offset().left -
654
- this.$seekBarHover.width() / 2
655
-
656
- this.$seekBarHover.css({ left: offsetX })
657
- }
655
+ // TODO test that it works when the element does not exist
656
+ this.$seekBarHover.css({ left: hoverOffset })
658
657
  }
659
- this.trigger(Events.MEDIACONTROL_MOUSEMOVE_SEEKBAR, event)
658
+ this.trigger(Events.MEDIACONTROL_MOUSEMOVE_SEEKBAR, event, pos)
660
659
  }
661
660
 
662
661
  private mouseleaveOnSeekBar(event: MouseEvent) {
@@ -1194,19 +1193,9 @@ export class MediaControl extends UICorePlugin {
1194
1193
  } else {
1195
1194
  panel.append(element)
1196
1195
  }
1197
- return
1198
1196
  }
1199
1197
  }
1200
1198
 
1201
- /**
1202
- * @deprecated Use {@link MediaControl.mount} instead
1203
- * @param name
1204
- * @param element
1205
- */
1206
- putElement(name: MediaControlElement, element: ZeptoResult) {
1207
- this.mount(name, element)
1208
- }
1209
-
1210
1199
  /**
1211
1200
  * Toggle the visibility of a media control element
1212
1201
  * @param name - The name of the media control element
@@ -246,7 +246,7 @@ exports[`MediaControl > rendering timing > once metadata is loaded > should wait
246
246
  <span class="drawer-text" data-volume=""></span>
247
247
  </div>
248
248
 
249
- <div class="bar-container volume-bar-hide" data-volume="">
249
+ <div class="bar-container" data-volume="">
250
250
  <div class="bar-background" data-volume="">
251
251
  <div class="bar-fill-1 gcore-skin-main-color" data-volume="" style="width: 100%;"></div>
252
252
  </div>
@@ -8,7 +8,6 @@ import {
8
8
  PlayerError,
9
9
  UIContainerPlugin,
10
10
  template,
11
- $,
12
11
  } from '@clappr/core'
13
12
  import { trace } from '@gcorevideo/utils'
14
13
 
@@ -20,25 +19,38 @@ import posterHTML from '../../../assets/poster/poster.ejs'
20
19
  import playIcon from '../../../assets/icons/new/play.svg'
21
20
  import { PlaybackError } from '../../playback.types.js'
22
21
 
22
+ export type PosterPluginSettings = {
23
+ /**
24
+ * Custom CSS background
25
+ */
26
+ custom?: string
27
+ /**
28
+ * Whether to show the poster image when the playback is noop (i.e., when there is no appropriate video playback engine for current media sources set or the media sources are not set at all)
29
+ */
30
+ showForNoOp?: boolean
31
+ /**
32
+ * Poster image URL
33
+ */
34
+ url?: string
35
+ /**
36
+ * Whether to show the poster after playback has ended @default true
37
+ */
38
+ showOnVideoEnd?: boolean
39
+ }
40
+
23
41
  const T = 'plugins.poster'
24
42
 
25
43
  /**
26
44
  * `PLUGIN` that displays a poster image in the background and a big play button on top when playback is stopped
27
45
  * @beta
28
46
  * @remarks
29
- * When the playback is stopped, media control UI is disabled.
47
+ * When the playback is stopped or not yet started, the media control UI is disabled and hidden.
48
+ * Media control gets activated once the metadata is loaded after playback is initiated.
49
+ * This plugin displays a big play button on top of the poster image to allow user to start playback.
30
50
  * Note that the poster image, if specified via the player config, will be used to update video element's poster attribute by the
31
51
  * HTML5-video-based playback module.
32
52
  *
33
- * Configuration options:
34
- *
35
- * - `poster.custom` - custom CSS background
36
- *
37
- * - `poster.showForNoOp` - whether to show the poster when the playback is not started
38
- *
39
- * - `poster.url` - the URL of the poster image
40
- *
41
- * - `poster.showOnVideoEnd` - whether to show the poster when the playback is ended
53
+ * Configuration options - {@link PosterPluginSettings}
42
54
  *
43
55
  * @example
44
56
  * ```ts
@@ -56,14 +68,12 @@ export class Poster extends UIContainerPlugin {
56
68
 
57
69
  private hasFatalError = false
58
70
 
59
- private hasStartedPlaying = false
71
+ private playing = false
60
72
 
61
73
  private playRequested = false
62
74
 
63
75
  private $playButton: ZeptoResult | null = null
64
76
 
65
- private $playWrapper: ZeptoResult | null = null
66
-
67
77
  /**
68
78
  * @internal
69
79
  */
@@ -87,18 +97,20 @@ export class Poster extends UIContainerPlugin {
87
97
  const showForNoOp = !!this.options.poster?.showForNoOp
88
98
  return (
89
99
  this.container.playback.name !== 'html_img' &&
90
- (this.container.playback.getPlaybackType() !== Playback.NO_OP ||
91
- showForNoOp)
100
+ (!this.isNoOp || showForNoOp)
92
101
  )
93
102
  }
94
103
 
104
+ private get isNoOp() {
105
+ return this.container.playback.getPlaybackType() === Playback.NO_OP
106
+ }
107
+
95
108
  /**
96
109
  * @internal
97
110
  */
98
111
  override get attributes() {
99
112
  return {
100
113
  class: 'player-poster',
101
- 'data-poster': '',
102
114
  }
103
115
  }
104
116
 
@@ -111,10 +123,6 @@ export class Poster extends UIContainerPlugin {
111
123
  }
112
124
  }
113
125
 
114
- private get showOnVideoEnd() {
115
- return this.options.poster?.showOnVideoEnd !== false
116
- }
117
-
118
126
  /**
119
127
  * @internal
120
128
  */
@@ -127,20 +135,27 @@ export class Poster extends UIContainerPlugin {
127
135
  Events.CONTAINER_STATE_BUFFERFULL,
128
136
  this.update,
129
137
  )
130
- this.listenTo(this.container, Events.CONTAINER_OPTIONS_CHANGE, this.render)
138
+ this.listenTo(this.container, Events.CONTAINER_OPTIONS_CHANGE, this.update)
131
139
  this.listenTo(this.container, Events.CONTAINER_ERROR, this.onError)
132
- this.showOnVideoEnd &&
140
+ // TODO check if this event is always accompanied with the CONTAINER_STOP
141
+ if (this.options.poster?.showOnVideoEnd !== false) {
133
142
  this.listenTo(this.container, Events.CONTAINER_ENDED, this.onStop)
143
+ }
134
144
  this.listenTo(this.container, Events.CONTAINER_READY, this.render)
135
- this.listenTo(this.container, Events.PLAYBACK_PLAY_INTENT, this.onPlayIntent)
145
+ this.listenTo(
146
+ this.container.playback,
147
+ Events.PLAYBACK_PLAY_INTENT,
148
+ this.onPlayIntent,
149
+ )
136
150
  }
137
151
 
138
152
  /**
139
153
  * Reenables earlier disabled plugin
140
154
  */
141
155
  override enable() {
156
+ trace(`${T} enable`)
142
157
  super.enable()
143
- this.hasStartedPlaying = this.container.playback.isPlaying()
158
+ this.playing = this.container.playback.isPlaying()
144
159
  this.update()
145
160
  }
146
161
 
@@ -149,7 +164,7 @@ export class Poster extends UIContainerPlugin {
149
164
  */
150
165
  override disable() {
151
166
  trace(`${T} disable`)
152
- this.hasStartedPlaying = false
167
+ this.playing = false
153
168
  this.playRequested = false
154
169
  super.disable()
155
170
  }
@@ -159,19 +174,16 @@ export class Poster extends UIContainerPlugin {
159
174
  error,
160
175
  enabled: this.enabled,
161
176
  })
162
- this.hasFatalError = error.level === PlayerError.Levels.FATAL
163
-
164
177
  if (this.hasFatalError) {
165
- this.hasStartedPlaying = false
166
- if (!this.playRequested) {
167
- this.showPlayButton()
168
- }
178
+ return
169
179
  }
180
+ this.hasFatalError = error.level === PlayerError.Levels.FATAL
181
+ // this.hasFatalError is reset on container recreate
170
182
  }
171
183
 
172
184
  private onPlay() {
173
185
  trace(`${T} onPlay`)
174
- this.hasStartedPlaying = true
186
+ this.playing = true
175
187
  this.playRequested = false
176
188
  this.update()
177
189
  }
@@ -183,24 +195,23 @@ export class Poster extends UIContainerPlugin {
183
195
  }
184
196
 
185
197
  private onStop() {
186
- trace(`${T} onStop`, {
187
- enabled: this.enabled,
188
- })
189
- this.hasStartedPlaying = false
198
+ trace(`${T} onStop`)
199
+ this.playing = false
190
200
  this.playRequested = false
191
201
  this.update()
192
202
  }
193
203
 
194
- private updatePlayButton(show: boolean) {
195
- trace(`${T} updatePlayButton`, {
196
- show,
197
- chromeless: this.options.chromeless,
198
- allowUserInteraction: this.options.allowUserInteraction,
199
- })
200
- if (
201
- show &&
202
- (!this.options.chromeless || this.options.allowUserInteraction)
203
- ) {
204
+ private updatePlayButton() {
205
+ trace(`${T} updatePlayButton`)
206
+ const show =
207
+ !this.isNoOp &&
208
+ !(this.options.chromeless && !this.options.allowUserInteraction) &&
209
+ !this.playRequested &&
210
+ !this.playing &&
211
+ !this.container.buffering &&
212
+ !this.hasFatalError &&
213
+ !this.options.disableMediaControl
214
+ if (show) {
204
215
  this.showPlayButton()
205
216
  } else {
206
217
  this.hidePlayButton()
@@ -208,40 +219,31 @@ export class Poster extends UIContainerPlugin {
208
219
  }
209
220
 
210
221
  private showPlayButton() {
211
- if (this.options.disableMediaControl) {
212
- return
213
- }
214
- if (this.hasFatalError && !this.options.disableErrorScreen) {
215
- return
216
- }
217
-
218
- this.$playButton?.show()
222
+ trace(`${T} showPlayButton`)
223
+ this.$el.find('#poster-play').show()
219
224
  this.$el.addClass('clickable')
220
225
  this.container.$el.addClass('container-with-poster-clickable')
221
226
  }
222
227
 
223
228
  private hidePlayButton() {
224
- this.$playButton.hide()
229
+ trace(`${T} hidePlayButton`)
230
+ this.$el.find('#poster-play').hide()
225
231
  this.$el.removeClass('clickable')
226
232
  }
227
233
 
228
- private clicked() {
229
- trace(`${T} clicked`, {
230
- hasStartedPlaying: this.hasStartedPlaying,
231
- chromeless: this.options.chromeless,
232
- allowUserInteraction: this.options.allowUserInteraction,
233
- })
234
+ private clicked(e: MouseEvent) {
235
+ trace(`${T} clicked`)
236
+ e.preventDefault()
237
+ e.stopPropagation()
238
+ if (this.options.chromeless && !this.options.allowUserInteraction) {
239
+ return
240
+ }
234
241
  // Let "click_to_pause" plugin handle click event if media has started playing
235
- if (!this.hasStartedPlaying) {
236
- if (!this.options.chromeless || this.options.allowUserInteraction) {
237
- this.playRequested = true
238
- this.update()
239
- this.container.playback.consent()
240
- this.container.playback.play()
241
- }
242
+ if (!this.playing) {
243
+ this.playRequested = true
244
+ this.update()
245
+ this.container.play()
242
246
  }
243
-
244
- return false
245
247
  }
246
248
 
247
249
  private shouldHideOnPlay() {
@@ -250,27 +252,15 @@ export class Poster extends UIContainerPlugin {
250
252
  }
251
253
 
252
254
  private update() {
253
- trace(`${T} update`, {
254
- shouldRender: this.shouldRender,
255
- })
256
- if (!this.shouldRender) {
257
- return
258
- }
255
+ trace(`${T} update`)
259
256
 
260
- const showPlayButton =
261
- !this.playRequested &&
262
- !this.hasStartedPlaying &&
263
- !this.container.buffering
264
-
265
- this.updatePlayButton(showPlayButton)
257
+ this.updatePlayButton()
266
258
  this.updatePoster()
267
259
  }
268
260
 
269
261
  private updatePoster() {
270
- trace(`${T} updatePoster`, {
271
- hasStartedPlaying: this.hasStartedPlaying,
272
- })
273
- if (!this.hasStartedPlaying) {
262
+ trace(`${T} updatePoster`)
263
+ if (!this.playing) {
274
264
  this.showPoster()
275
265
  } else {
276
266
  this.hidePoster()
@@ -283,9 +273,7 @@ export class Poster extends UIContainerPlugin {
283
273
  }
284
274
 
285
275
  private hidePoster() {
286
- trace(`${T} hidePoster`, {
287
- shouldHideOnPlay: this.shouldHideOnPlay(),
288
- })
276
+ trace(`${T} hidePoster`)
289
277
  if (!this.options.disableMediaControl) {
290
278
  this.container.enableMediaControl()
291
279
  }
@@ -304,34 +292,27 @@ export class Poster extends UIContainerPlugin {
304
292
 
305
293
  this.$el.html(Poster.template())
306
294
 
307
- const isRegularPoster =
308
- this.options.poster && this.options.poster.custom === undefined
295
+ const isCustomPoster = this.options.poster?.custom !== undefined
309
296
 
310
- if (isRegularPoster) {
311
- const posterUrl = this.options.poster.url || this.options.poster
312
-
313
- this.$el.css({ 'background-image': 'url(' + posterUrl + ')' })
314
- } else if (this.options.poster) {
297
+ if (isCustomPoster) {
315
298
  this.$el.css({ background: this.options.poster.custom })
299
+ } else {
300
+ const posterUrl =
301
+ typeof this.options.poster === 'string'
302
+ ? this.options.poster
303
+ : this.options.poster?.url
304
+ if (posterUrl) {
305
+ this.$el.css({ 'background-image': 'url(' + posterUrl + ')' })
306
+ }
316
307
  }
317
308
 
318
309
  this.container.$el.removeClass('container-with-poster-clickable')
319
310
  this.container.$el.append(this.el)
320
- this.$playWrapper = this.$el.find('.play-wrapper')
321
- this.$playWrapper.addClass('control-need-disable')
322
- this.$playButton = $(
323
- "<div class='circle-poster gcore-skin-button-color gcore-skin-border-color'></div>",
324
- )
325
- this.$playWrapper.append(this.$playButton)
326
- this.$playButton.append(playIcon)
311
+ this.$el.find('#poster-play').append(playIcon)
327
312
 
328
- if (this.options.autoPlay) {
329
- this.$playButton.hide()
313
+ if (this.options.autoPlay || this.isNoOp) {
314
+ this.$el.find('#poster-play').hide()
330
315
  }
331
- this.$playButton.addClass('poster-icon')
332
- this.$playButton.attr('data-poster', '')
333
-
334
- this.update()
335
316
 
336
317
  return this
337
318
  }