@gcorevideo/player 2.22.14 → 2.22.15
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/context-menu/context_menu.ejs +14 -6
- package/assets/context-menu/context_menu.scss +18 -4
- package/dist/core.js +5 -23
- package/dist/index.css +625 -613
- package/dist/index.js +45 -60
- package/dist/plugins/index.css +1378 -1366
- package/dist/plugins/index.js +39 -60
- package/lib/Player.d.ts.map +1 -1
- package/lib/Player.js +4 -1
- package/lib/playback/hls-playback/HlsPlayback.d.ts.map +1 -1
- package/lib/playback/hls-playback/HlsPlayback.js +0 -21
- package/lib/plugins/click-to-pause/ClickToPause.js +1 -1
- package/lib/plugins/context-menu/ContextMenu.d.ts +33 -12
- package/lib/plugins/context-menu/ContextMenu.d.ts.map +1 -1
- package/lib/plugins/context-menu/ContextMenu.js +40 -37
- package/package.json +1 -1
- package/src/Player.ts +4 -3
- package/src/playback/hls-playback/HlsPlayback.ts +0 -22
- package/src/plugins/click-to-pause/ClickToPause.ts +1 -1
- package/src/plugins/context-menu/ContextMenu.ts +72 -56
- package/src/plugins/media-control/__tests__/MediaControl.test.ts +66 -30
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,20 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Events,
|
|
3
|
-
template,
|
|
4
|
-
$,
|
|
5
|
-
Container,
|
|
6
|
-
UIContainerPlugin,
|
|
7
|
-
} from '@clappr/core'
|
|
1
|
+
import { Events, template, $, Container, UIContainerPlugin } from '@clappr/core'
|
|
8
2
|
|
|
9
3
|
import { CLAPPR_VERSION } from '../../build.js'
|
|
10
4
|
|
|
11
5
|
import '../../../assets/context-menu/context_menu.scss'
|
|
12
6
|
import templateHtml from '../../../assets/context-menu/context_menu.ejs'
|
|
13
|
-
import { version } from '../../version.js'
|
|
14
7
|
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
/**
|
|
9
|
+
* @beta
|
|
10
|
+
*/
|
|
11
|
+
export type MenuOption = {
|
|
12
|
+
/**
|
|
13
|
+
* Menu item label text. One of `label` or `labelKey` must be specified.
|
|
14
|
+
*/
|
|
15
|
+
label?: string
|
|
16
|
+
/**
|
|
17
|
+
* Menu item label localisation key, if specified, the `label` will be ignored
|
|
18
|
+
*/
|
|
19
|
+
labelKey?: string
|
|
20
|
+
/**
|
|
21
|
+
* Menu item name. Must be unique.
|
|
22
|
+
*/
|
|
17
23
|
name: string
|
|
24
|
+
/**
|
|
25
|
+
* Menu item handler function
|
|
26
|
+
*/
|
|
27
|
+
handler?: () => void
|
|
28
|
+
/**
|
|
29
|
+
* Menu item icon, plain HTML string
|
|
30
|
+
*/
|
|
31
|
+
icon?: string
|
|
18
32
|
}
|
|
19
33
|
|
|
20
34
|
/**
|
|
@@ -22,23 +36,21 @@ type MenuOption = {
|
|
|
22
36
|
* @beta
|
|
23
37
|
*/
|
|
24
38
|
export interface ContextMenuPluginSettings {
|
|
25
|
-
|
|
26
|
-
url?: string
|
|
27
|
-
preventShowContextMenu?: boolean
|
|
39
|
+
options?: MenuOption[]
|
|
28
40
|
}
|
|
29
41
|
|
|
42
|
+
const T = 'plugins.context_menu'
|
|
43
|
+
|
|
30
44
|
/**
|
|
31
45
|
* `PLUGIN` that displays a small context menu when clicked on the player container.
|
|
32
46
|
* @beta
|
|
33
47
|
* @remarks
|
|
34
48
|
* Configuration options - {@link ContextMenuPluginSettings}
|
|
49
|
+
*
|
|
50
|
+
* Should not be used together with {@link ClickToPause} plugin
|
|
35
51
|
*/
|
|
36
52
|
export class ContextMenu extends UIContainerPlugin {
|
|
37
|
-
private
|
|
38
|
-
|
|
39
|
-
private _url: string = ''
|
|
40
|
-
|
|
41
|
-
private menuOptions: MenuOption[] = []
|
|
53
|
+
private open = false
|
|
42
54
|
|
|
43
55
|
/**
|
|
44
56
|
* @internal
|
|
@@ -63,38 +75,17 @@ export class ContextMenu extends UIContainerPlugin {
|
|
|
63
75
|
|
|
64
76
|
private static readonly template = template(templateHtml)
|
|
65
77
|
|
|
66
|
-
private get label() {
|
|
67
|
-
return this._label || 'Gcore player ver. ' + version().gplayer
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
private get url() {
|
|
71
|
-
return this._url || 'https://gcore.com/'
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
private get exposeVersion() {
|
|
75
|
-
return {
|
|
76
|
-
label: this.label,
|
|
77
|
-
name: 'version',
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
78
|
/**
|
|
82
79
|
* @internal
|
|
83
80
|
*/
|
|
84
81
|
override get events() {
|
|
85
82
|
return {
|
|
86
|
-
'click [
|
|
83
|
+
'click [role="menuitem"]': 'runAction',
|
|
87
84
|
}
|
|
88
85
|
}
|
|
89
86
|
|
|
90
87
|
constructor(container: Container) {
|
|
91
88
|
super(container)
|
|
92
|
-
if (this.options.contextMenu && this.options.contextMenu.label) {
|
|
93
|
-
this._label = this.options.contextMenu.label
|
|
94
|
-
}
|
|
95
|
-
if (this.options.contextMenu && this.options.contextMenu.url) {
|
|
96
|
-
this._url = this.options.contextMenu.url
|
|
97
|
-
}
|
|
98
89
|
this.render()
|
|
99
90
|
$('body').on('click', this.hideOnBodyClick)
|
|
100
91
|
}
|
|
@@ -106,9 +97,9 @@ export class ContextMenu extends UIContainerPlugin {
|
|
|
106
97
|
this.listenTo(
|
|
107
98
|
this.container,
|
|
108
99
|
Events.CONTAINER_CONTEXTMENU,
|
|
109
|
-
this.
|
|
100
|
+
this.onContextMenu,
|
|
110
101
|
)
|
|
111
|
-
this.listenTo(this.container, Events.CONTAINER_CLICK, this.
|
|
102
|
+
this.listenTo(this.container, Events.CONTAINER_CLICK, this.onContainerClick)
|
|
112
103
|
}
|
|
113
104
|
|
|
114
105
|
/**
|
|
@@ -119,41 +110,66 @@ export class ContextMenu extends UIContainerPlugin {
|
|
|
119
110
|
return super.destroy()
|
|
120
111
|
}
|
|
121
112
|
|
|
122
|
-
private
|
|
113
|
+
private onContainerClick() {
|
|
114
|
+
this.hide()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private onContextMenu(event: MouseEvent) {
|
|
118
|
+
if (!this.options.contextMenu?.options?.length) {
|
|
119
|
+
return
|
|
120
|
+
}
|
|
123
121
|
event.preventDefault()
|
|
124
|
-
|
|
122
|
+
event.stopPropagation()
|
|
125
123
|
|
|
124
|
+
const offset = this.container?.$el.offset()
|
|
126
125
|
this.show(event.pageY - offset.top, event.pageX - offset.left)
|
|
127
126
|
}
|
|
128
127
|
|
|
129
128
|
private show(top: number, left: number) {
|
|
130
|
-
this.
|
|
131
|
-
if (
|
|
132
|
-
this.options.contextMenu &&
|
|
133
|
-
this.options.contextMenu.preventShowContextMenu
|
|
134
|
-
) {
|
|
135
|
-
return
|
|
136
|
-
}
|
|
129
|
+
this.open = true
|
|
137
130
|
this.$el.css({ top, left })
|
|
138
131
|
this.$el.show()
|
|
139
132
|
}
|
|
140
133
|
|
|
141
134
|
private hide() {
|
|
135
|
+
this.open = false
|
|
142
136
|
this.$el.hide()
|
|
143
137
|
}
|
|
144
138
|
|
|
145
|
-
private
|
|
146
|
-
|
|
139
|
+
private runAction(event: MouseEvent) {
|
|
140
|
+
event.preventDefault()
|
|
141
|
+
event.stopPropagation()
|
|
142
|
+
|
|
143
|
+
const itemName = (event.currentTarget as HTMLButtonElement).dataset.name
|
|
144
|
+
if (!itemName) {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
const item = this.options.contextMenu?.options.find(
|
|
148
|
+
(option: MenuOption) => option.name === itemName,
|
|
149
|
+
)
|
|
150
|
+
if (item?.handler) {
|
|
151
|
+
item.handler()
|
|
152
|
+
}
|
|
153
|
+
this.hide()
|
|
147
154
|
}
|
|
148
155
|
|
|
149
156
|
/**
|
|
150
157
|
* @internal
|
|
151
158
|
*/
|
|
152
159
|
override render() {
|
|
153
|
-
this.
|
|
154
|
-
|
|
160
|
+
if (!this.options.contextMenu?.options?.length) {
|
|
161
|
+
return this
|
|
162
|
+
}
|
|
163
|
+
const options = this.options.contextMenu.options
|
|
164
|
+
this.$el.html(
|
|
165
|
+
ContextMenu.template({
|
|
166
|
+
options,
|
|
167
|
+
i18n: this.container.i18n,
|
|
168
|
+
iconic: options.some((option: MenuOption) => option.icon),
|
|
169
|
+
}),
|
|
170
|
+
)
|
|
155
171
|
this.container.$el.append(this.$el) // TODO append to the container, turn into a container plugin
|
|
156
|
-
this.hide()
|
|
172
|
+
this.$el.hide()
|
|
157
173
|
|
|
158
174
|
return this
|
|
159
175
|
}
|
|
@@ -56,14 +56,26 @@ describe('MediaControl', () => {
|
|
|
56
56
|
core.activePlayback.emit(Events.PLAYBACK_LOADEDMETADATA)
|
|
57
57
|
core.activeContainer.emit(Events.CONTAINER_LOADEDMETADATA)
|
|
58
58
|
})
|
|
59
|
-
it('should wait a delay before rendering anything', async () => {
|
|
59
|
+
it('should wait a delay before rendering anything', async () => {
|
|
60
60
|
expect(mediaControl.el.innerHTML).toBe('')
|
|
61
61
|
await new Promise((resolve) => setTimeout(resolve, 35))
|
|
62
62
|
expect(mediaControl.el.innerHTML).toMatchSnapshot()
|
|
63
|
-
expect(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
expect(
|
|
64
|
+
mediaControl.$el.find('.media-control-left-panel [data-playpause]')
|
|
65
|
+
.length,
|
|
66
|
+
).toBeGreaterThan(0)
|
|
67
|
+
expect(
|
|
68
|
+
mediaControl.$el.find('.media-control-left-panel [data-volume]')
|
|
69
|
+
.length,
|
|
70
|
+
).toBeGreaterThan(0)
|
|
71
|
+
expect(
|
|
72
|
+
mediaControl.$el.find('.media-control-center-panel [data-seekbar]')
|
|
73
|
+
.length,
|
|
74
|
+
).toBeGreaterThan(0)
|
|
75
|
+
expect(
|
|
76
|
+
mediaControl.$el.find('.media-control-right-panel [data-fullscreen]')
|
|
77
|
+
.length,
|
|
78
|
+
).toBeGreaterThan(0)
|
|
67
79
|
})
|
|
68
80
|
})
|
|
69
81
|
})
|
|
@@ -72,9 +84,7 @@ describe('MediaControl', () => {
|
|
|
72
84
|
mediaControl = new MediaControl(core)
|
|
73
85
|
core.emit(Events.CORE_READY)
|
|
74
86
|
core.emit(Events.CORE_ACTIVE_CONTAINER_CHANGED, core.activeContainer)
|
|
75
|
-
core
|
|
76
|
-
core.activeContainer.emit(Events.CONTAINER_LOADEDMETADATA)
|
|
77
|
-
await new Promise((resolve) => setTimeout(resolve, 25))
|
|
87
|
+
await runMetadataLoaded(core)
|
|
78
88
|
})
|
|
79
89
|
describe.each([
|
|
80
90
|
[
|
|
@@ -92,20 +102,29 @@ describe('MediaControl', () => {
|
|
|
92
102
|
core.activePlayback.emit(Events.PLAYBACK_SETTINGSUPDATE)
|
|
93
103
|
core.activeContainer.emit(Events.CONTAINER_SETTINGSUPDATE)
|
|
94
104
|
})
|
|
95
|
-
it.each(
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
105
|
+
it.each(settings.left)('should render %s control', (element) => {
|
|
106
|
+
const el = mediaControl.$el.find(
|
|
107
|
+
`.media-control-left-panel [data-${element}]`,
|
|
108
|
+
)
|
|
99
109
|
expect(el.length).toBeGreaterThan(0)
|
|
100
110
|
})
|
|
101
111
|
it.each(
|
|
102
|
-
arraySubtract(
|
|
103
|
-
|
|
104
|
-
|
|
112
|
+
arraySubtract(
|
|
113
|
+
['playpause', 'playstop', 'position', 'duration', 'volume'],
|
|
114
|
+
settings.left,
|
|
115
|
+
),
|
|
116
|
+
)('should not render %s control', (element) => {
|
|
117
|
+
const el = mediaControl.$el.find(
|
|
118
|
+
`.media-control-left-panel [data-${element}]`,
|
|
119
|
+
)
|
|
105
120
|
expect(el.length).toEqual(0)
|
|
106
121
|
})
|
|
107
|
-
it(`should ${
|
|
108
|
-
|
|
122
|
+
it(`should ${
|
|
123
|
+
settings.seekEnabled ? '' : 'not '
|
|
124
|
+
}render the seek bar`, () => {
|
|
125
|
+
const seekbar = mediaControl.$el.find(
|
|
126
|
+
'.media-control-center-panel [data-seekbar]',
|
|
127
|
+
)
|
|
109
128
|
expect(seekbar.length).toBeGreaterThan(1)
|
|
110
129
|
if (settings.seekEnabled) {
|
|
111
130
|
expect(seekbar.hasClass('seek-disabled')).toBe(false)
|
|
@@ -113,7 +132,9 @@ describe('MediaControl', () => {
|
|
|
113
132
|
expect(seekbar.hasClass('seek-disabled')).toBe(true)
|
|
114
133
|
}
|
|
115
134
|
})
|
|
116
|
-
it(`should ${
|
|
135
|
+
it(`should ${
|
|
136
|
+
settings.left.includes('volume') ? '' : 'not '
|
|
137
|
+
}render the volume control`, () => {
|
|
117
138
|
const volume = mediaControl.$el.find('.drawer-container[data-volume]')
|
|
118
139
|
if (settings.left.includes('volume')) {
|
|
119
140
|
expect(volume.length).toEqual(1)
|
|
@@ -121,8 +142,12 @@ describe('MediaControl', () => {
|
|
|
121
142
|
expect(volume.length).toEqual(0)
|
|
122
143
|
}
|
|
123
144
|
})
|
|
124
|
-
it(`should ${
|
|
125
|
-
|
|
145
|
+
it(`should ${
|
|
146
|
+
settings.right.includes('fullscreen') ? '' : 'not '
|
|
147
|
+
}render the fullscreen control`, () => {
|
|
148
|
+
const fullscreen = mediaControl.$el.find(
|
|
149
|
+
'.media-control-right-panel [data-fullscreen]',
|
|
150
|
+
)
|
|
126
151
|
if (settings.right.includes('fullscreen')) {
|
|
127
152
|
expect(fullscreen.length).toEqual(1)
|
|
128
153
|
} else {
|
|
@@ -138,12 +163,13 @@ describe('MediaControl', () => {
|
|
|
138
163
|
core.emit(Events.CORE_ACTIVE_CONTAINER_CHANGED, core.activeContainer)
|
|
139
164
|
})
|
|
140
165
|
describe('when live', () => {
|
|
141
|
-
beforeEach(() => {
|
|
166
|
+
beforeEach(async () => {
|
|
142
167
|
core.activeContainer.getPlaybackType.mockReturnValue(Playback.LIVE)
|
|
143
168
|
core.activePlayback.getPlaybackType.mockReturnValue(Playback.LIVE)
|
|
144
169
|
core.getPlaybackType.mockReturnValue(Playback.LIVE)
|
|
145
|
-
|
|
146
|
-
|
|
170
|
+
// This is not strictly necessary as the CSS class is applied on the root element and does not require rendering.
|
|
171
|
+
// However, it makes the scenario more realistic
|
|
172
|
+
await runMetadataLoaded(core)
|
|
147
173
|
// TODO playback.settings
|
|
148
174
|
core.activePlayback.emit(Events.PLAYBACK_SETTINGSUPDATE)
|
|
149
175
|
})
|
|
@@ -152,11 +178,11 @@ describe('MediaControl', () => {
|
|
|
152
178
|
})
|
|
153
179
|
})
|
|
154
180
|
describe('when vod', () => {
|
|
155
|
-
beforeEach(() => {
|
|
181
|
+
beforeEach(async () => {
|
|
156
182
|
core.activeContainer.getPlaybackType.mockReturnValue(Playback.VOD)
|
|
157
183
|
core.activePlayback.getPlaybackType.mockReturnValue(Playback.VOD)
|
|
158
184
|
core.getPlaybackType.mockReturnValue(Playback.VOD)
|
|
159
|
-
core
|
|
185
|
+
await runMetadataLoaded(core)
|
|
160
186
|
})
|
|
161
187
|
it('should not apply live style class', () => {
|
|
162
188
|
expect(mediaControl.$el.hasClass('live')).toBe(false)
|
|
@@ -164,11 +190,13 @@ describe('MediaControl', () => {
|
|
|
164
190
|
})
|
|
165
191
|
})
|
|
166
192
|
describe('putElement', () => {
|
|
167
|
-
beforeEach(() => {
|
|
193
|
+
beforeEach(async () => {
|
|
168
194
|
mediaControl = new MediaControl(core)
|
|
169
|
-
core.emit(
|
|
195
|
+
core.emit(Events.CORE_READY)
|
|
196
|
+
core.emit(Events.CORE_ACTIVE_CONTAINER_CHANGED, core.activeContainer)
|
|
170
197
|
core.activeContainer.settings = {}
|
|
171
|
-
core.emit(
|
|
198
|
+
core.emit(Events.CONTAINER_SETTINGSUPDATE)
|
|
199
|
+
await runMetadataLoaded(core)
|
|
172
200
|
})
|
|
173
201
|
describe.each([
|
|
174
202
|
['pip' as MediaControlElement],
|
|
@@ -200,12 +228,13 @@ describe('MediaControl', () => {
|
|
|
200
228
|
core.emit(Events.CORE_READY)
|
|
201
229
|
})
|
|
202
230
|
describe('dvr', () => {
|
|
203
|
-
beforeEach(() => {
|
|
231
|
+
beforeEach(async () => {
|
|
204
232
|
core.activeContainer.settings = {
|
|
205
233
|
left: ['playpause', 'position', 'duration'],
|
|
206
234
|
seekEnabled: true,
|
|
207
235
|
}
|
|
208
236
|
core.emit(Events.CORE_ACTIVE_CONTAINER_CHANGED, core.activeContainer)
|
|
237
|
+
await runMetadataLoaded(core)
|
|
209
238
|
})
|
|
210
239
|
describe('when enabled', () => {
|
|
211
240
|
beforeEach(() => {
|
|
@@ -241,10 +270,11 @@ describe('MediaControl', () => {
|
|
|
241
270
|
})
|
|
242
271
|
})
|
|
243
272
|
describe('dvr mode', () => {
|
|
244
|
-
beforeEach(() => {
|
|
273
|
+
beforeEach(async () => {
|
|
245
274
|
mediaControl = new MediaControl(core)
|
|
246
275
|
core.emit(Events.CORE_READY)
|
|
247
276
|
core.emit(Events.CORE_ACTIVE_CONTAINER_CHANGED, core.activeContainer)
|
|
277
|
+
await runMetadataLoaded(core)
|
|
248
278
|
})
|
|
249
279
|
describe('by default', () => {
|
|
250
280
|
it('should not apply DVR style class', () => {
|
|
@@ -270,3 +300,9 @@ describe('MediaControl', () => {
|
|
|
270
300
|
function arraySubtract<T extends string>(arr1: T[], arr2: T[]) {
|
|
271
301
|
return arr1.filter((item) => !arr2.includes(item))
|
|
272
302
|
}
|
|
303
|
+
|
|
304
|
+
function runMetadataLoaded(core: any) {
|
|
305
|
+
core.activePlayback.emit(Events.PLAYBACK_LOADEDMETADATA)
|
|
306
|
+
core.activeContainer.emit(Events.CONTAINER_LOADEDMETADATA)
|
|
307
|
+
return new Promise((resolve) => setTimeout(resolve, 25))
|
|
308
|
+
}
|