@gcorevideo/player 2.23.3 → 2.24.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/dist/core.js +1 -1
- package/dist/index.css +1197 -1197
- package/dist/index.js +131 -4
- package/dist/player.d.ts +6 -3
- package/lib/index.plugins.d.ts +1 -0
- package/lib/index.plugins.d.ts.map +1 -1
- package/lib/index.plugins.js +1 -0
- package/lib/plugins/big-mute-button/BigMuteButton.d.ts +1 -1
- package/lib/plugins/big-mute-button/BigMuteButton.d.ts.map +1 -1
- package/lib/plugins/big-mute-button/BigMuteButton.js +3 -2
- package/lib/plugins/cmcd-config/CmcdConfig.d.ts +45 -0
- package/lib/plugins/cmcd-config/CmcdConfig.d.ts.map +1 -0
- package/lib/plugins/cmcd-config/CmcdConfig.js +114 -0
- package/lib/plugins/cmcd-config/utils.d.ts +3 -0
- package/lib/plugins/cmcd-config/utils.d.ts.map +1 -0
- package/lib/plugins/cmcd-config/utils.js +12 -0
- package/lib/testUtils.d.ts +4 -1
- package/lib/testUtils.d.ts.map +1 -1
- package/lib/testUtils.js +3 -2
- package/package.json +1 -1
- package/src/index.plugins.ts +1 -0
- package/src/plugins/big-mute-button/BigMuteButton.ts +3 -2
- package/src/plugins/cmcd-config/CmcdConfig.ts +148 -0
- package/src/plugins/cmcd-config/__tests__/CmcdConfig.test.ts +162 -0
- package/src/plugins/cmcd-config/utils.ts +13 -0
- package/src/plugins/media-control/__tests__/MediaControl.test.ts +4 -3
- package/src/testUtils.ts +3 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Core,
|
|
3
|
+
CorePlugin,
|
|
4
|
+
Events,
|
|
5
|
+
} from '@clappr/core'
|
|
6
|
+
|
|
7
|
+
import { generateContentId, generateSessionId } from './utils'
|
|
8
|
+
|
|
9
|
+
const CMCD_KEYS = [
|
|
10
|
+
'br',
|
|
11
|
+
'd',
|
|
12
|
+
'ot',
|
|
13
|
+
'tb',
|
|
14
|
+
'bl',
|
|
15
|
+
'dl',
|
|
16
|
+
'mtp',
|
|
17
|
+
'nor',
|
|
18
|
+
'nrr',
|
|
19
|
+
'su',
|
|
20
|
+
'bs',
|
|
21
|
+
'rtp',
|
|
22
|
+
'cid',
|
|
23
|
+
'pr',
|
|
24
|
+
'sf',
|
|
25
|
+
'sid',
|
|
26
|
+
'st',
|
|
27
|
+
'v',
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @beta
|
|
32
|
+
*/
|
|
33
|
+
export type CmcdConfigPluginSettings = {
|
|
34
|
+
/**
|
|
35
|
+
* Session ID. If ommitted, a random UUID will be generated
|
|
36
|
+
*/
|
|
37
|
+
sessionId: string
|
|
38
|
+
/**
|
|
39
|
+
* Content ID, either constant or derived from current source.
|
|
40
|
+
* If ommitted, a SHA-1 hash of current source URL will be used
|
|
41
|
+
*/
|
|
42
|
+
contentId?: string | ((sourceUrl: string, mimeType?: string) => (string | Promise<string>))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A `PLUGIN` that configures CMCD for playback
|
|
47
|
+
* @beta
|
|
48
|
+
* @remarks
|
|
49
|
+
* Configuration options
|
|
50
|
+
* `cmcd`: {@link CmcdConfigPluginSettings}
|
|
51
|
+
*/
|
|
52
|
+
export class CmcdConfig extends CorePlugin {
|
|
53
|
+
private sid: string
|
|
54
|
+
|
|
55
|
+
private cid = ''
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @inheritdocs
|
|
59
|
+
*/
|
|
60
|
+
get name() {
|
|
61
|
+
return 'cmcd'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
constructor(core: Core) {
|
|
65
|
+
super(core)
|
|
66
|
+
this.sid = this.options.cmcd?.sessionId ?? generateSessionId()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @inheritdocs
|
|
71
|
+
*/
|
|
72
|
+
override bindEvents() {
|
|
73
|
+
this.listenTo(this.core, Events.CORE_ACTIVE_CONTAINER_CHANGED, () =>
|
|
74
|
+
this.updateSettings(),
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async getIds(): Promise<{ sid: string; cid: string }> {
|
|
79
|
+
return {
|
|
80
|
+
sid: this.sid,
|
|
81
|
+
cid: await this.ensureContentId(),
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private updateSettings() {
|
|
86
|
+
switch (this.core.activeContainer.playback.name) {
|
|
87
|
+
case 'dash':
|
|
88
|
+
this.updateDashjsSettings()
|
|
89
|
+
break
|
|
90
|
+
case 'hls':
|
|
91
|
+
this.updateHlsjsSettings()
|
|
92
|
+
break
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
private async updateDashjsSettings() {
|
|
97
|
+
const {cid, sid} = await this.getIds()
|
|
98
|
+
const options = this.core.activePlayback.options
|
|
99
|
+
this.core.activePlayback.options = {
|
|
100
|
+
...options,
|
|
101
|
+
dash: {
|
|
102
|
+
...(options.dash ?? {}),
|
|
103
|
+
cmcd: {
|
|
104
|
+
enabled: true,
|
|
105
|
+
enabledKeys: CMCD_KEYS,
|
|
106
|
+
sid,
|
|
107
|
+
cid,
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private async updateHlsjsSettings() {
|
|
114
|
+
const { cid, sid } = await this.getIds()
|
|
115
|
+
const options = this.core.activePlayback.options
|
|
116
|
+
this.core.activePlayback.options = {
|
|
117
|
+
...options,
|
|
118
|
+
playback: {
|
|
119
|
+
hlsjsConfig: {
|
|
120
|
+
...(options.playback?.hlsjsConfig ?? {}),
|
|
121
|
+
cmcd: {
|
|
122
|
+
includeKeys: CMCD_KEYS,
|
|
123
|
+
sessionId: sid,
|
|
124
|
+
contentId: cid,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private async ensureContentId(): Promise<string> {
|
|
132
|
+
if (!this.cid) {
|
|
133
|
+
this.cid = await this.evalContentId()
|
|
134
|
+
}
|
|
135
|
+
return this.cid
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
private async evalContentId(): Promise<string> {
|
|
139
|
+
if (!this.core.activeContainer.options.cmcd?.contentId) {
|
|
140
|
+
return generateContentId(this.core.activePlayback.options.src)
|
|
141
|
+
}
|
|
142
|
+
const contentId = this.core.activeContainer.options.cmcd.contentId
|
|
143
|
+
if (typeof contentId === 'string') {
|
|
144
|
+
return contentId
|
|
145
|
+
}
|
|
146
|
+
return Promise.resolve(contentId(this.core.activePlayback.options.src))
|
|
147
|
+
}
|
|
148
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import { CmcdConfig } from '../CmcdConfig'
|
|
4
|
+
import { createMockCore } from '../../../testUtils'
|
|
5
|
+
import { Events } from '@clappr/core'
|
|
6
|
+
import { generateContentId } from '../utils'
|
|
7
|
+
|
|
8
|
+
import { createHash } from 'node:crypto'
|
|
9
|
+
|
|
10
|
+
vi.mock('../utils', () => ({
|
|
11
|
+
generateSessionId: vi.fn().mockReturnValue('123'),
|
|
12
|
+
generateContentId: vi.fn().mockResolvedValue('deadbeef'),
|
|
13
|
+
}))
|
|
14
|
+
|
|
15
|
+
const CMCD_KEYS = [
|
|
16
|
+
'br',
|
|
17
|
+
'd',
|
|
18
|
+
'ot',
|
|
19
|
+
'tb',
|
|
20
|
+
'bl',
|
|
21
|
+
'dl',
|
|
22
|
+
'mtp',
|
|
23
|
+
'nor',
|
|
24
|
+
'nrr',
|
|
25
|
+
'su',
|
|
26
|
+
'bs',
|
|
27
|
+
'rtp',
|
|
28
|
+
'cid',
|
|
29
|
+
'pr',
|
|
30
|
+
'sf',
|
|
31
|
+
'sid',
|
|
32
|
+
'st',
|
|
33
|
+
'v',
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
describe('CmcdConfig', () => {
|
|
37
|
+
let core: any
|
|
38
|
+
let plugin: CmcdConfig
|
|
39
|
+
describe('basically', () => {
|
|
40
|
+
beforeEach(() => {
|
|
41
|
+
core = createMockCore({})
|
|
42
|
+
})
|
|
43
|
+
describe('when active container is changed', () => {
|
|
44
|
+
describe('dash.js', () => {
|
|
45
|
+
beforeEach(async () => {
|
|
46
|
+
core.activeContainer.playback.name = 'dash'
|
|
47
|
+
core.activeContainer.playback.options.src = 'https://123.mpd'
|
|
48
|
+
plugin = new CmcdConfig(core)
|
|
49
|
+
core.trigger(Events.CORE_ACTIVE_CONTAINER_CHANGED)
|
|
50
|
+
await new Promise(resolve => setTimeout(resolve, 0))
|
|
51
|
+
})
|
|
52
|
+
it('should update DASH.js CMCD settings', () => {
|
|
53
|
+
expect(core.activePlayback.options).toEqual(expect.objectContaining({
|
|
54
|
+
dash: expect.objectContaining({
|
|
55
|
+
cmcd: expect.objectContaining({
|
|
56
|
+
enabled: true,
|
|
57
|
+
enabledKeys: CMCD_KEYS,
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
}))
|
|
61
|
+
})
|
|
62
|
+
it('should generate unique session ID', () => {
|
|
63
|
+
expect(core.activePlayback.options).toEqual(expect.objectContaining({
|
|
64
|
+
dash: expect.objectContaining({
|
|
65
|
+
cmcd: expect.objectContaining({
|
|
66
|
+
sid: '123',
|
|
67
|
+
})
|
|
68
|
+
})
|
|
69
|
+
}))
|
|
70
|
+
})
|
|
71
|
+
it('should compute content ID from source URL', () => {
|
|
72
|
+
expect(generateContentId).toHaveBeenCalledWith('https://123.mpd')
|
|
73
|
+
expect(core.activePlayback.options).toEqual(expect.objectContaining({
|
|
74
|
+
dash: expect.objectContaining({
|
|
75
|
+
cmcd: expect.objectContaining({
|
|
76
|
+
cid: 'deadbeef',
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
}))
|
|
80
|
+
})
|
|
81
|
+
})
|
|
82
|
+
describe('hls.js', () => {
|
|
83
|
+
beforeEach(async () => {
|
|
84
|
+
core.activeContainer.playback.name = 'hls'
|
|
85
|
+
core.activeContainer.playback.options.src = 'https://123.m3u8'
|
|
86
|
+
plugin = new CmcdConfig(core)
|
|
87
|
+
await new Promise(resolve => setTimeout(resolve, 0))
|
|
88
|
+
core.trigger(Events.CORE_ACTIVE_CONTAINER_CHANGED)
|
|
89
|
+
await new Promise(resolve => setTimeout(resolve, 0))
|
|
90
|
+
})
|
|
91
|
+
it('should update HLS.js CMCD settings', () => {
|
|
92
|
+
expect(core.activePlayback.options).toEqual(expect.objectContaining({
|
|
93
|
+
playback: expect.objectContaining({
|
|
94
|
+
hlsjsConfig: expect.objectContaining({
|
|
95
|
+
cmcd: expect.objectContaining({
|
|
96
|
+
includeKeys: CMCD_KEYS,
|
|
97
|
+
contentId: 'deadbeef',
|
|
98
|
+
sessionId: '123',
|
|
99
|
+
})
|
|
100
|
+
})
|
|
101
|
+
})
|
|
102
|
+
}))
|
|
103
|
+
expect(generateContentId).toHaveBeenCalledWith('https://123.m3u8')
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
describe('custom content ID', () => {
|
|
109
|
+
beforeEach(async () => {
|
|
110
|
+
core = createMockCore({
|
|
111
|
+
cmcd: {
|
|
112
|
+
contentId: (src: string) => new Promise(resolve => {
|
|
113
|
+
const h = createHash('sha256')
|
|
114
|
+
h.on('readable', () => {
|
|
115
|
+
const data = h.read()
|
|
116
|
+
if (data) {
|
|
117
|
+
resolve(data.toString('hex'))
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
h.update(Buffer.from('1$' + src))
|
|
121
|
+
h.end()
|
|
122
|
+
}),
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
core.activePlayback.name = 'dash'
|
|
126
|
+
core.activePlayback.options.src = 'https://123.mpd'
|
|
127
|
+
plugin = new CmcdConfig(core)
|
|
128
|
+
core.trigger(Events.CORE_ACTIVE_CONTAINER_CHANGED)
|
|
129
|
+
await new Promise(resolve => setTimeout(resolve, 0))
|
|
130
|
+
})
|
|
131
|
+
it('should use custom content ID', () => {
|
|
132
|
+
expect(core.activePlayback.options).toEqual(expect.objectContaining({
|
|
133
|
+
dash: expect.objectContaining({
|
|
134
|
+
cmcd: expect.objectContaining({
|
|
135
|
+
cid: 'e287ea99b57c09b7a185aaaf36e075f2c0b346ce90aeced72976b1732678a8c6',
|
|
136
|
+
})
|
|
137
|
+
})
|
|
138
|
+
}))
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
describe('custom session ID', () => {
|
|
142
|
+
beforeEach(async () => {
|
|
143
|
+
core = createMockCore({
|
|
144
|
+
cmcd: { sessionId: '456' },
|
|
145
|
+
})
|
|
146
|
+
core.activePlayback.name = 'dash'
|
|
147
|
+
core.activePlayback.options.src = 'https://123.mpd'
|
|
148
|
+
plugin = new CmcdConfig(core)
|
|
149
|
+
core.trigger(Events.CORE_ACTIVE_CONTAINER_CHANGED)
|
|
150
|
+
await new Promise(resolve => setTimeout(resolve, 0))
|
|
151
|
+
})
|
|
152
|
+
it('should use custom session ID', () => {
|
|
153
|
+
expect(core.activePlayback.options).toEqual(expect.objectContaining({
|
|
154
|
+
dash: expect.objectContaining({
|
|
155
|
+
cmcd: expect.objectContaining({
|
|
156
|
+
sid: '456',
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
}))
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
})
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export function generateSessionId(): string {
|
|
2
|
+
return window.crypto.randomUUID()
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export function generateContentId(sourceUrl: string): Promise<string> {
|
|
6
|
+
return window.crypto.subtle.digest('SHA-1', new TextEncoder().encode(sourceUrl))
|
|
7
|
+
.then(buffer => {
|
|
8
|
+
const hex = Array.from(new Uint8Array(buffer))
|
|
9
|
+
.map(b => b.toString(16).padStart(2, '0'))
|
|
10
|
+
.join('')
|
|
11
|
+
return hex
|
|
12
|
+
})
|
|
13
|
+
}
|
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
MediaControlSettings,
|
|
6
6
|
} from '../MediaControl'
|
|
7
7
|
import { createMockCore } from '../../../testUtils'
|
|
8
|
-
import { LogTracer, Logger, setTracer } from '@gcorevideo/utils'
|
|
9
8
|
import { $, Events, Playback } from '@clappr/core'
|
|
10
9
|
|
|
11
10
|
vi.mock('../../utils/fullscreen', () => ({
|
|
@@ -13,8 +12,10 @@ vi.mock('../../utils/fullscreen', () => ({
|
|
|
13
12
|
isFullscreen: vi.fn().mockReturnValue(false),
|
|
14
13
|
}))
|
|
15
14
|
|
|
16
|
-
Logger
|
|
17
|
-
|
|
15
|
+
// import { LogTracer, Logger, setTracer } from '@gcorevideo/utils'
|
|
16
|
+
|
|
17
|
+
// Logger.enable('*')
|
|
18
|
+
// setTracer(new LogTracer('MediaControl.test'))
|
|
18
19
|
|
|
19
20
|
describe('MediaControl', () => {
|
|
20
21
|
let core: any
|
package/src/testUtils.ts
CHANGED
|
@@ -41,7 +41,7 @@ export function createSpinnerPlugin() {
|
|
|
41
41
|
})
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export function createMockPlayback(name = 'mock') {
|
|
44
|
+
export function createMockPlayback(name = 'mock', options: Record<string, unknown> = {}) {
|
|
45
45
|
const emitter = new Events()
|
|
46
46
|
return Object.assign(emitter, {
|
|
47
47
|
name,
|
|
@@ -51,6 +51,7 @@ export function createMockPlayback(name = 'mock') {
|
|
|
51
51
|
dvrInUse: false,
|
|
52
52
|
isAudioOnly: false,
|
|
53
53
|
levels: [],
|
|
54
|
+
options: { ...options },
|
|
54
55
|
consent: vi.fn(),
|
|
55
56
|
play: vi.fn(),
|
|
56
57
|
pause: vi.fn(),
|
|
@@ -80,7 +81,7 @@ export function createMockPlayback(name = 'mock') {
|
|
|
80
81
|
|
|
81
82
|
export function createMockContainer(
|
|
82
83
|
options: Record<string, unknown> = {},
|
|
83
|
-
playback: any = createMockPlayback(),
|
|
84
|
+
playback: any = createMockPlayback('html5_video', options),
|
|
84
85
|
) {
|
|
85
86
|
const el = playback.el
|
|
86
87
|
const emitter = new Events()
|