@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.
- package/assets/media-control/container.scss +2 -3
- package/assets/poster/poster.ejs +3 -1
- package/assets/poster/poster.scss +3 -3
- package/assets/style/main.scss +1 -1
- package/assets/thumbnails/scrub-thumbnails.ejs +5 -10
- package/assets/thumbnails/style.scss +4 -5
- package/dist/core.js +1 -1
- package/dist/index.css +533 -532
- package/dist/index.js +273 -377
- package/dist/player.d.ts +63 -33
- package/docs/api/{player.seektime.bindevents.md → player.clapprstats.clearmetrics.md} +3 -3
- package/docs/api/player.clapprstats.md +14 -0
- package/docs/api/player.extendedevents.md +14 -0
- package/docs/api/player.md +13 -2
- package/docs/api/player.seektime.attributes.md +0 -1
- package/docs/api/player.seektime.md +6 -197
- package/docs/api/{player.seektime.render.md → player.seektimesettings.md} +7 -7
- package/docs/api/player.skiptime.md +3 -184
- package/lib/plugins/clips/Clips.d.ts +7 -0
- package/lib/plugins/clips/Clips.d.ts.map +1 -1
- package/lib/plugins/clips/Clips.js +8 -0
- package/lib/plugins/media-control/MediaControl.d.ts +1 -7
- package/lib/plugins/media-control/MediaControl.d.ts.map +1 -1
- package/lib/plugins/media-control/MediaControl.js +9 -18
- package/lib/plugins/poster/Poster.d.ts +24 -14
- package/lib/plugins/poster/Poster.d.ts.map +1 -1
- package/lib/plugins/poster/Poster.js +67 -97
- package/lib/plugins/thumbnails/Thumbnails.d.ts +36 -33
- package/lib/plugins/thumbnails/Thumbnails.d.ts.map +1 -1
- package/lib/plugins/thumbnails/Thumbnails.js +174 -259
- package/lib/plugins/thumbnails/utils.d.ts +5 -0
- package/lib/plugins/thumbnails/utils.d.ts.map +1 -0
- package/lib/plugins/thumbnails/utils.js +12 -0
- package/lib/testUtils.d.ts +13 -39
- package/lib/testUtils.d.ts.map +1 -1
- package/lib/testUtils.js +15 -67
- package/package.json +2 -1
- package/src/plugins/clips/Clips.ts +10 -1
- package/src/plugins/media-control/MediaControl.ts +10 -21
- package/src/plugins/media-control/__tests__/__snapshots__/MediaControl.test.ts.snap +1 -1
- package/src/plugins/poster/Poster.ts +91 -110
- package/src/plugins/poster/__tests__/Poster.test.ts +119 -0
- package/src/plugins/poster/__tests__/__snapshots__/Poster.test.ts.snap +8 -0
- package/src/plugins/source-controller/__tests__/SourceController.test.ts +1 -2
- package/src/plugins/thumbnails/Thumbnails.ts +228 -330
- package/src/plugins/thumbnails/__tests__/Thumbnails.test.ts +72 -0
- package/src/plugins/thumbnails/__tests__/__snapshots__/Thumbnails.test.ts.snap +10 -0
- package/src/plugins/thumbnails/utils.ts +12 -0
- package/src/testUtils.ts +15 -88
- package/temp/player.api.json +295 -829
- package/tsconfig.tsbuildinfo +1 -1
- package/docs/api/player.seektime.durationshown.md +0 -14
- package/docs/api/player.seektime.getseektime.md +0 -20
- package/docs/api/player.seektime.islivestreamwithdvr.md +0 -14
- package/docs/api/player.seektime.mediacontrol.md +0 -14
- package/docs/api/player.seektime.mediacontrolcontainer.md +0 -14
- package/docs/api/player.seektime.shouldbevisible.md +0 -18
- package/docs/api/player.seektime.template.md +0 -14
- package/docs/api/player.seektime.update.md +0 -18
- package/docs/api/player.skiptime.attributes.md +0 -17
- package/docs/api/player.skiptime.bindevents.md +0 -18
- package/docs/api/player.skiptime.events.md +0 -18
- package/docs/api/player.skiptime.handlerewindclicks.md +0 -18
- package/docs/api/player.skiptime.render.md +0 -18
- package/docs/api/player.skiptime.setback.md +0 -18
- package/docs/api/player.skiptime.setforward.md +0 -18
- package/docs/api/player.skiptime.setmidclick.md +0 -18
- package/docs/api/player.skiptime.template.md +0 -14
- package/docs/api/player.skiptime.togglefullscreen.md +0 -18
package/lib/testUtils.d.ts
CHANGED
|
@@ -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():
|
|
73
|
-
play():
|
|
74
|
-
pause():
|
|
75
|
-
stop():
|
|
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:
|
|
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>;
|
package/lib/testUtils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"testUtils.d.ts","sourceRoot":"","sources":["../src/testUtils.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
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 { $,
|
|
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.
|
|
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
|
-
//
|
|
650
|
-
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
138
|
+
this.listenTo(this.container, Events.CONTAINER_OPTIONS_CHANGE, this.update)
|
|
131
139
|
this.listenTo(this.container, Events.CONTAINER_ERROR, this.onError)
|
|
132
|
-
this
|
|
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(
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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(
|
|
195
|
-
trace(`${T} updatePlayButton
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
212
|
-
|
|
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
|
-
|
|
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
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
308
|
-
this.options.poster && this.options.poster.custom === undefined
|
|
295
|
+
const isCustomPoster = this.options.poster?.custom !== undefined
|
|
309
296
|
|
|
310
|
-
if (
|
|
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.$
|
|
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.$
|
|
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
|
}
|