@gcorevideo/player 2.22.15 → 2.22.16
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/clips/clips.ejs +1 -0
- package/assets/clips/clips.scss +23 -3
- package/assets/level-selector/list.ejs +9 -3
- package/assets/media-control/media-control.ejs +1 -9
- package/assets/media-control/media-control.scss +0 -25
- package/assets/media-control/width370.scss +4 -4
- package/dist/core.js +1 -1
- package/dist/index.css +602 -602
- package/dist/index.js +252 -229
- package/dist/player.d.ts +211 -144
- package/dist/plugins/index.css +1198 -1198
- package/dist/plugins/index.js +185 -167
- package/docs/api/{player.audioselector.md → player.audiotracks.md} +3 -3
- package/docs/api/player.contextmenu.md +2 -0
- package/docs/api/player.contextmenupluginsettings.md +2 -40
- package/docs/api/{player.contextmenupluginsettings.label.md → player.contextmenupluginsettings.options.md} +3 -3
- package/docs/api/player.md +78 -23
- package/docs/api/player.mediacontrol.md +8 -14
- package/docs/api/player.mediacontrolelement.md +4 -2
- package/docs/api/{player.contextmenupluginsettings.preventshowcontextmenu.md → player.mediacontrollayerelement.md} +5 -3
- package/docs/api/player.mediacontrolleftelement.md +16 -0
- package/docs/api/player.mediacontrolrightelement.md +16 -0
- package/docs/api/player.mediacontrolsettings.md +23 -0
- package/docs/api/{player.contextmenupluginsettings.url.md → player.menuoption.md} +10 -3
- package/docs/api/player.playbackrate.md +1 -1
- package/docs/api/player.playerconfig.md +1 -1
- package/docs/api/player.playerconfig.playbacktype.md +1 -1
- package/docs/api/{player.levelselector.events.md → player.qualitylevels.events.md} +2 -2
- package/docs/api/{player.levelselector.md → player.qualitylevels.md} +6 -6
- package/docs/api/{player.levelselectorpluginsettings.labels.md → player.qualitylevelspluginsettings.labels.md} +2 -2
- package/docs/api/{player.levelselectorpluginsettings.md → player.qualitylevelspluginsettings.md} +6 -6
- package/docs/api/{player.levelselectorpluginsettings.restrictresolution.md → player.qualitylevelspluginsettings.restrictresolution.md} +2 -2
- package/lib/plugins/clips/Clips.d.ts +21 -16
- package/lib/plugins/clips/Clips.d.ts.map +1 -1
- package/lib/plugins/clips/Clips.js +96 -98
- package/lib/plugins/clips/types.d.ts +19 -0
- package/lib/plugins/clips/types.d.ts.map +1 -0
- package/lib/plugins/clips/types.js +1 -0
- package/lib/plugins/clips/utils.d.ts +4 -0
- package/lib/plugins/clips/utils.d.ts.map +1 -0
- package/lib/plugins/clips/utils.js +36 -0
- package/lib/plugins/media-control/MediaControl.d.ts +4 -7
- package/lib/plugins/media-control/MediaControl.d.ts.map +1 -1
- package/lib/plugins/media-control/MediaControl.js +19 -31
- package/lib/plugins/utils.d.ts +9 -1
- package/lib/plugins/utils.d.ts.map +1 -1
- package/lib/plugins/utils.js +9 -10
- package/lib/plugins/vast-ads/loaderxml.js +2 -2
- package/lib/testUtils.d.ts.map +1 -1
- package/lib/testUtils.js +2 -5
- package/package.json +1 -1
- package/src/plugins/clips/Clips.ts +116 -135
- package/src/plugins/clips/__tests__/Clips.test.ts +72 -0
- package/src/plugins/clips/__tests__/__snapshots__/Clips.test.ts.snap +14 -0
- package/src/plugins/clips/types.ts +22 -0
- package/src/plugins/clips/utils.ts +54 -0
- package/src/plugins/level-selector/__tests__/__snapshots__/QualityLevels.test.ts.snap +18 -18
- package/src/plugins/media-control/MediaControl.ts +31 -58
- package/src/plugins/media-control/__tests__/__snapshots__/MediaControl.test.ts.snap +7 -35
- package/src/plugins/utils.ts +9 -7
- package/src/plugins/vast-ads/loaderxml.ts +2 -2
- package/src/testUtils.ts +2 -5
- package/temp/player.api.json +332 -262
- package/tsconfig.tsbuildinfo +1 -1
- package/docs/api/player.mediacontrol.handlecustomarea.md +0 -52
|
@@ -1,55 +1,54 @@
|
|
|
1
|
-
import { Container, Events, UICorePlugin,
|
|
1
|
+
import { Container, Events, UICorePlugin, $, template } from '@clappr/core'
|
|
2
|
+
import { trace } from '@gcorevideo/utils'
|
|
3
|
+
import assert from 'assert'
|
|
2
4
|
|
|
3
5
|
import { TimeProgress } from '../../playback.types.js'
|
|
4
6
|
import type { ZeptoResult } from '../../types.js'
|
|
5
|
-
import { strtimeToMiliseconds } from '../utils.js'
|
|
6
7
|
import '../../../assets/clips/clips.scss'
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
start: number
|
|
11
|
-
text: string
|
|
12
|
-
end: number
|
|
13
|
-
index: number
|
|
14
|
-
}
|
|
8
|
+
import { ClipDesc } from './types.js'
|
|
9
|
+
import { buildSvg, parseClips } from './utils.js'
|
|
10
|
+
import clipsHTML from '../../../assets/clips/clips.ejs'
|
|
15
11
|
|
|
16
|
-
|
|
17
|
-
start: number
|
|
18
|
-
text: string
|
|
19
|
-
}
|
|
12
|
+
const T = 'plugins.clips'
|
|
20
13
|
|
|
21
14
|
/**
|
|
22
|
-
* Configuration options for the {@link ClipsPlugin
|
|
15
|
+
* Configuration options for the {@link ClipsPlugin} plugin.
|
|
23
16
|
* @beta
|
|
24
17
|
*/
|
|
25
18
|
export interface ClipsPluginSettings {
|
|
26
19
|
/**
|
|
27
|
-
* The text
|
|
20
|
+
* The compiled text of the clips description, one clip per line in format :
|
|
21
|
+
* `HH:MM:SS text` or `MM:SS text` or `SS text`
|
|
28
22
|
*/
|
|
29
23
|
text: string
|
|
30
24
|
}
|
|
31
25
|
|
|
26
|
+
const VERSION = '2.22.16'
|
|
27
|
+
const CLAPPR_VERSION = '0.11.4'
|
|
28
|
+
|
|
32
29
|
/**
|
|
33
|
-
* `PLUGIN` that
|
|
30
|
+
* `PLUGIN` that allows marking up the timeline of the video
|
|
34
31
|
* @beta
|
|
35
32
|
* @remarks
|
|
33
|
+
* The plugin decorates the seekbar with notches to indicate the clips of the video and displays current clip text in the left panel
|
|
34
|
+
*
|
|
36
35
|
* Depends on:
|
|
37
36
|
*
|
|
38
37
|
* - {@link MediaControl}
|
|
39
38
|
*
|
|
40
39
|
* Configuration options - {@link ClipsPluginSettings}
|
|
41
40
|
*/
|
|
42
|
-
export class
|
|
43
|
-
private
|
|
44
|
-
|
|
45
|
-
private duration: number = 0
|
|
41
|
+
export class Clips extends UICorePlugin {
|
|
42
|
+
private barStyle: HTMLStyleElement | null = null
|
|
46
43
|
|
|
47
|
-
private
|
|
44
|
+
private clips: ClipDesc[] = []
|
|
48
45
|
|
|
49
|
-
private
|
|
46
|
+
private oldContainer: Container | undefined
|
|
50
47
|
|
|
51
48
|
private svgMask: ZeptoResult | null = null
|
|
52
49
|
|
|
50
|
+
private static readonly template = template(clipsHTML)
|
|
51
|
+
|
|
53
52
|
/**
|
|
54
53
|
* @internal
|
|
55
54
|
*/
|
|
@@ -62,174 +61,156 @@ export class ClipsPlugin extends UICorePlugin {
|
|
|
62
61
|
*/
|
|
63
62
|
override get attributes() {
|
|
64
63
|
return {
|
|
65
|
-
class:
|
|
64
|
+
class: 'media-control-clips',
|
|
66
65
|
}
|
|
67
66
|
}
|
|
68
67
|
|
|
68
|
+
get version() {
|
|
69
|
+
return VERSION
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get supportedVersion() {
|
|
73
|
+
return { min: CLAPPR_VERSION }
|
|
74
|
+
}
|
|
75
|
+
|
|
69
76
|
/**
|
|
70
77
|
* @internal
|
|
71
78
|
*/
|
|
72
79
|
override bindEvents() {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
this.listenToOnce(this.core, Events.CORE_READY, this._onCoreReady)
|
|
76
|
-
// TODO listen to CORE_ACTIVE_CONTAINER_CHANGED
|
|
80
|
+
this.listenToOnce(this.core, Events.CORE_READY, this.onCoreReady)
|
|
81
|
+
this.listenTo(this.core, Events.CORE_RESIZE, this.playerResize)
|
|
77
82
|
this.listenTo(
|
|
78
|
-
|
|
79
|
-
Events.
|
|
80
|
-
this.
|
|
83
|
+
this.core,
|
|
84
|
+
Events.CORE_ACTIVE_CONTAINER_CHANGED,
|
|
85
|
+
this.onContainerChanged,
|
|
81
86
|
)
|
|
82
|
-
this.listenTo(this.core, Events.CORE_RESIZE, this.playerResize)
|
|
83
87
|
}
|
|
84
88
|
|
|
85
|
-
|
|
89
|
+
override render() {
|
|
90
|
+
trace(`${T} render`)
|
|
86
91
|
if (!this.options.clips) {
|
|
87
|
-
this
|
|
92
|
+
return this
|
|
93
|
+
}
|
|
94
|
+
this.$el.html(Clips.template())
|
|
95
|
+
this.$el.hide()
|
|
96
|
+
return this
|
|
97
|
+
}
|
|
88
98
|
|
|
89
|
-
|
|
99
|
+
override destroy() {
|
|
100
|
+
if (this.barStyle) {
|
|
101
|
+
this.barStyle.remove()
|
|
102
|
+
this.barStyle = null
|
|
90
103
|
}
|
|
104
|
+
return super.destroy()
|
|
105
|
+
}
|
|
91
106
|
|
|
92
|
-
|
|
107
|
+
override disable() {
|
|
108
|
+
if (this.barStyle) {
|
|
109
|
+
this.barStyle.remove()
|
|
110
|
+
this.barStyle = null
|
|
111
|
+
}
|
|
112
|
+
return super.disable()
|
|
93
113
|
}
|
|
94
114
|
|
|
95
|
-
|
|
96
|
-
this.
|
|
115
|
+
override enable() {
|
|
116
|
+
this.render()
|
|
117
|
+
return super.enable()
|
|
97
118
|
}
|
|
98
119
|
|
|
99
|
-
private
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
120
|
+
private onCoreReady() {
|
|
121
|
+
trace(`${T} onCoreReady`)
|
|
122
|
+
const mediaControl = this.core.getPlugin('media_control')
|
|
123
|
+
assert(mediaControl, 'media_control plugin is required')
|
|
124
|
+
|
|
125
|
+
this.parseClips(this.options.clips.text)
|
|
126
|
+
this.listenTo(mediaControl, Events.MEDIACONTROL_RENDERED, this.onMcRender)
|
|
104
127
|
}
|
|
105
128
|
|
|
106
|
-
private
|
|
107
|
-
|
|
129
|
+
private onMcRender() {
|
|
130
|
+
trace(`${T} onMcRender`)
|
|
131
|
+
const mediaControl = this.core.getPlugin('media_control')
|
|
132
|
+
mediaControl.mount('clips', this.$el)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private onContainerChanged() {
|
|
136
|
+
trace(`${T} onContainerChanged`)
|
|
137
|
+
// TODO figure out the conditions of changing the container (without destroying the previous one)
|
|
138
|
+
if (this.oldContainer) {
|
|
108
139
|
this.stopListening(
|
|
109
|
-
this.
|
|
140
|
+
this.oldContainer,
|
|
110
141
|
Events.CONTAINER_TIMEUPDATE,
|
|
111
142
|
this.onTimeUpdate,
|
|
112
143
|
)
|
|
113
144
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
145
|
+
this.oldContainer = this.core.activeContainer
|
|
146
|
+
if (this.svgMask) {
|
|
147
|
+
this.svgMask.remove()
|
|
148
|
+
this.svgMask = null
|
|
149
|
+
}
|
|
118
150
|
this.listenTo(
|
|
119
|
-
|
|
151
|
+
this.core.activeContainer,
|
|
120
152
|
Events.CONTAINER_TIMEUPDATE,
|
|
121
153
|
this.onTimeUpdate,
|
|
122
154
|
)
|
|
123
155
|
}
|
|
124
156
|
|
|
157
|
+
private playerResize() {
|
|
158
|
+
const duration = this.core.activeContainer.getDuration()
|
|
159
|
+
if (duration) {
|
|
160
|
+
this.makeSvg(duration)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
125
164
|
private onTimeUpdate(event: TimeProgress) {
|
|
126
|
-
if (!this.
|
|
127
|
-
this.duration = event.total
|
|
165
|
+
if (!this.svgMask) {
|
|
128
166
|
this.makeSvg(event.total)
|
|
129
|
-
this.durationGetting = true
|
|
130
167
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
168
|
+
for (const value of this.clips) {
|
|
169
|
+
if (
|
|
170
|
+
(event.current >= value.start && !value.end) ||
|
|
171
|
+
event.current < value.end
|
|
172
|
+
) {
|
|
134
173
|
this.setClipText(value.text)
|
|
135
174
|
break
|
|
136
175
|
}
|
|
137
176
|
}
|
|
138
177
|
}
|
|
139
178
|
|
|
140
|
-
private parseClips() {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const clipsArr = textArr
|
|
144
|
-
.map((val: string) => {
|
|
145
|
-
const matchRes = val.match(/(\d+:\d+|:\d+) (.+)/i)
|
|
146
|
-
|
|
147
|
-
return matchRes
|
|
148
|
-
? {
|
|
149
|
-
start: strtimeToMiliseconds(matchRes[1]),
|
|
150
|
-
text: matchRes[2],
|
|
151
|
-
}
|
|
152
|
-
: null
|
|
153
|
-
})
|
|
154
|
-
.filter((clip: ClipItem | null) => clip !== null)
|
|
155
|
-
|
|
156
|
-
clipsArr.sort((a: ClipDesc, b: ClipDesc) => a.start - b.start)
|
|
157
|
-
|
|
158
|
-
clipsArr.forEach((clip: ClipDesc, index: number) => {
|
|
159
|
-
this.clips.set(clip.start, {
|
|
160
|
-
index,
|
|
161
|
-
start: clip.start,
|
|
162
|
-
text: clip.text,
|
|
163
|
-
end: clipsArr[index + 1] ? clipsArr[index + 1].start : null,
|
|
164
|
-
})
|
|
165
|
-
})
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Returns the text of the current clip.
|
|
170
|
-
* @param time - The current time of the player.
|
|
171
|
-
* @returns The text of the current clip.
|
|
172
|
-
*/
|
|
173
|
-
getText(time: number) {
|
|
174
|
-
for (const [key, value] of this.clips.entries()) {
|
|
175
|
-
if (time >= value.start && time < value.end) {
|
|
176
|
-
return value.text
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return ''
|
|
179
|
+
private parseClips(text: string) {
|
|
180
|
+
this.clips = parseClips(text)
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
private makeSvg(duration: number) {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
this.clips.forEach((val) => {
|
|
189
|
-
let end = val.end
|
|
190
|
-
|
|
191
|
-
if (!end) {
|
|
192
|
-
end = val.end = duration
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const widthChunk = ((end - val.start) * widthOfSeek) / duration
|
|
196
|
-
|
|
197
|
-
svg += `<rect x="${finishValue}" y="0" width="${
|
|
198
|
-
widthChunk - 2
|
|
199
|
-
}" height="30"/>\n`
|
|
200
|
-
finishValue += widthChunk
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
svg += `<rect x="${finishValue}" y="0" width="${
|
|
204
|
-
widthOfSeek - finishValue
|
|
205
|
-
}" height="30"/>\n`
|
|
206
|
-
svg += '</clipPath>' + '</defs>' + '</svg>'
|
|
184
|
+
const svg = buildSvg(
|
|
185
|
+
this.clips,
|
|
186
|
+
duration,
|
|
187
|
+
this.core.activeContainer.$el.width(),
|
|
188
|
+
)
|
|
207
189
|
this.setSVGMask(svg)
|
|
208
190
|
}
|
|
209
191
|
|
|
210
192
|
private setSVGMask(svg: string) {
|
|
211
|
-
// this.core.mediaControl.setSVGMask(svg);
|
|
212
193
|
if (this.svgMask) {
|
|
213
194
|
this.svgMask.remove()
|
|
214
195
|
}
|
|
215
196
|
|
|
216
|
-
const mediaControl = this.core.getPlugin('media_control')
|
|
217
|
-
const $seekBarContainer =
|
|
218
|
-
mediaControl.getElement('seekBarContainer')
|
|
219
|
-
if ($seekBarContainer?.get(0)) {
|
|
220
|
-
$seekBarContainer.addClass('clips')
|
|
221
|
-
}
|
|
222
|
-
|
|
223
197
|
this.svgMask = $(svg)
|
|
224
|
-
|
|
198
|
+
this.$el.append(this.svgMask)
|
|
199
|
+
if (!this.barStyle) {
|
|
200
|
+
this.barStyle = document.createElement('style')
|
|
201
|
+
this.barStyle.textContent = `
|
|
202
|
+
.bar-container[data-seekbar] {
|
|
203
|
+
clip-path: url("#myClip");
|
|
204
|
+
}`
|
|
205
|
+
this.$el.append(this.barStyle)
|
|
206
|
+
}
|
|
225
207
|
}
|
|
226
208
|
|
|
227
209
|
private setClipText(text: string) {
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
$clipText.text(`${text}`)
|
|
210
|
+
if (text) {
|
|
211
|
+
this.$el.show().find('#clips-text').text(text)
|
|
212
|
+
} else {
|
|
213
|
+
this.$el.hide()
|
|
233
214
|
}
|
|
234
215
|
}
|
|
235
216
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { Clips } from '../Clips'
|
|
3
|
+
import { createMockCore, createMockMediaControl } from '../../../testUtils'
|
|
4
|
+
import { Events } from '@clappr/core'
|
|
5
|
+
|
|
6
|
+
// import { LogTracer, Logger, setTracer } from '@gcorevideo/utils'
|
|
7
|
+
|
|
8
|
+
// Logger.enable('*')
|
|
9
|
+
// setTracer(new LogTracer('Clips.text'))
|
|
10
|
+
|
|
11
|
+
describe('ClipsPlugin', () => {
|
|
12
|
+
let core: any
|
|
13
|
+
let mediaControl: any
|
|
14
|
+
let clips: Clips
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
core = createMockCore({
|
|
17
|
+
clips: {
|
|
18
|
+
text: `
|
|
19
|
+
00:00:00 Introduction
|
|
20
|
+
00:05:00 Main part
|
|
21
|
+
00:15:00 Conclusion
|
|
22
|
+
`,
|
|
23
|
+
},
|
|
24
|
+
})
|
|
25
|
+
mediaControl = createMockMediaControl(core)
|
|
26
|
+
core.getPlugin.mockImplementation((name: string) => {
|
|
27
|
+
if (name === 'media_control') return mediaControl
|
|
28
|
+
return null
|
|
29
|
+
})
|
|
30
|
+
clips = new Clips(core)
|
|
31
|
+
core.emit(Events.CORE_READY)
|
|
32
|
+
core.emit(Events.CORE_ACTIVE_CONTAINER_CHANGED, core.activeContainer)
|
|
33
|
+
vi.spyOn(core.activeContainer.$el, 'width').mockReturnValue(600)
|
|
34
|
+
core.activeContainer.emit(Events.CONTAINER_TIMEUPDATE, {
|
|
35
|
+
current: 0,
|
|
36
|
+
total: 1200,
|
|
37
|
+
})
|
|
38
|
+
})
|
|
39
|
+
it('should render indicator', () => {
|
|
40
|
+
expect(clips.el.innerHTML).toMatchSnapshot()
|
|
41
|
+
})
|
|
42
|
+
it('should render notches on the seek bar', () => {
|
|
43
|
+
const svg = clips.$el.find('svg')
|
|
44
|
+
expect(svg).toBeDefined()
|
|
45
|
+
expect(svg?.find('rect').length).toBe(3)
|
|
46
|
+
})
|
|
47
|
+
describe('as time progresses', () => {
|
|
48
|
+
describe.each([
|
|
49
|
+
[60, 'Introduction'],
|
|
50
|
+
[310, 'Main part'],
|
|
51
|
+
[1001, 'Conclusion'],
|
|
52
|
+
])('@%s', (time, expected) => {
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
core.activeContainer.emit(Events.CONTAINER_TIMEUPDATE, {
|
|
55
|
+
current: time,
|
|
56
|
+
total: 1200,
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
it(`text should be "${expected}"`, () => {
|
|
60
|
+
expect(clips.$el.find('#clips-text').text()).toBe(expected)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
describe('when media control is rendered', () => {
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
mediaControl.trigger(Events.MEDIACONTROL_RENDERED)
|
|
67
|
+
})
|
|
68
|
+
it('should mount the indicator', () => {
|
|
69
|
+
expect(mediaControl.mount).toHaveBeenCalledWith('clips', clips.$el)
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
})
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
|
2
|
+
|
|
3
|
+
exports[`ClipsPlugin > should render indicator 1`] = `
|
|
4
|
+
"<div class="media-clip-text" id="clips-text">Introduction</div><svg width="0" height="0">
|
|
5
|
+
<defs>
|
|
6
|
+
<clipPath id="myClip">
|
|
7
|
+
<rect x="0" y="0" width="148" height="30"></rect>
|
|
8
|
+
<rect x="150" y="0" width="298" height="30"></rect>
|
|
9
|
+
<rect x="450" y="0" width="148" height="30"></rect>
|
|
10
|
+
</clipPath></defs></svg><style>
|
|
11
|
+
.bar-container[data-seekbar] {
|
|
12
|
+
clip-path: url("#myClip");
|
|
13
|
+
}</style>"
|
|
14
|
+
`;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clip description.
|
|
3
|
+
* @beta
|
|
4
|
+
*/
|
|
5
|
+
export type ClipDesc = {
|
|
6
|
+
/**
|
|
7
|
+
* Start time of the clip in the video timeline, s.
|
|
8
|
+
*/
|
|
9
|
+
start: number
|
|
10
|
+
/**
|
|
11
|
+
* Text to display over the seekbar.
|
|
12
|
+
*/
|
|
13
|
+
text: string
|
|
14
|
+
/**
|
|
15
|
+
* End time of the clip (start time of the next clip).
|
|
16
|
+
*/
|
|
17
|
+
end: number
|
|
18
|
+
/**
|
|
19
|
+
* Index of the clip.
|
|
20
|
+
*/
|
|
21
|
+
// index: number
|
|
22
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { ClipDesc } from './types.js'
|
|
2
|
+
import { parseClipTime } from '../utils.js'
|
|
3
|
+
|
|
4
|
+
type ClipItem = {
|
|
5
|
+
start: number
|
|
6
|
+
text: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function parseClips(text: string): ClipDesc[] {
|
|
10
|
+
const clipsArr = text
|
|
11
|
+
.split('\n')
|
|
12
|
+
.map((val: string) => {
|
|
13
|
+
const matchRes = val.match(/(((\d+:)?\d+:)?\d+) (.+)/i)
|
|
14
|
+
return matchRes
|
|
15
|
+
? {
|
|
16
|
+
start: parseClipTime(matchRes[1]),
|
|
17
|
+
text: matchRes[4],
|
|
18
|
+
}
|
|
19
|
+
: null
|
|
20
|
+
})
|
|
21
|
+
.filter((clip: ClipItem | null) => clip !== null)
|
|
22
|
+
.sort((a: ClipItem, b: ClipItem) => a.start - b.start)
|
|
23
|
+
return clipsArr.map((clip: ClipItem, index: number) => ({
|
|
24
|
+
start: clip.start,
|
|
25
|
+
text: clip.text,
|
|
26
|
+
end: index < clipsArr.length - 1 ? clipsArr[index + 1].start : 0,
|
|
27
|
+
}))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function buildSvg(clips: ClipDesc[], duration: number, barWidth: number): string {
|
|
31
|
+
let svg =
|
|
32
|
+
'<svg width="0" height="0">\n' + '<defs>\n' + '<clipPath id="myClip">\n'
|
|
33
|
+
let rightEdge = 0
|
|
34
|
+
|
|
35
|
+
clips.forEach((val) => {
|
|
36
|
+
const end = val.end || duration
|
|
37
|
+
|
|
38
|
+
const chunkWidth = Math.round(((end - val.start) * barWidth) / duration)
|
|
39
|
+
|
|
40
|
+
svg += `<rect x="${rightEdge}" y="0" width="${
|
|
41
|
+
chunkWidth - 2
|
|
42
|
+
}" height="30"/>\n`
|
|
43
|
+
rightEdge += chunkWidth
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
if (rightEdge < barWidth) {
|
|
47
|
+
svg += `<rect x="${rightEdge}" y="0" width="${
|
|
48
|
+
barWidth - rightEdge
|
|
49
|
+
}" height="30"/>\n`
|
|
50
|
+
}
|
|
51
|
+
svg += '</clipPath>' + '</defs>' + '</svg>'
|
|
52
|
+
|
|
53
|
+
return svg
|
|
54
|
+
}
|
|
@@ -8,7 +8,7 @@ exports[`QualityLevels > basically > auto > should render the selected level 1`]
|
|
|
8
8
|
<ul class="gear-sub-menu quality-levels" id="level-selector-menu" role="menu">
|
|
9
9
|
|
|
10
10
|
<li class="current">
|
|
11
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color gcore-skin-active" data-id="-1" id="level_selector_auto">
|
|
11
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color gcore-skin-active" data-id="-1" id="level_selector_auto" aria-checked="true" role="menuitemradio">
|
|
12
12
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
13
13
|
auto
|
|
14
14
|
</a>
|
|
@@ -16,21 +16,21 @@ exports[`QualityLevels > basically > auto > should render the selected level 1`]
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
<li class="">
|
|
19
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="2"
|
|
19
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="2" aria-disabled="false" aria-checked="false" role="menuitemradio" id="level_selector_1080">
|
|
20
20
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
21
21
|
Full HD
|
|
22
22
|
</a>
|
|
23
23
|
</li>
|
|
24
24
|
|
|
25
25
|
<li class="">
|
|
26
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="1"
|
|
26
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="1" aria-disabled="false" aria-checked="false" role="menuitemradio" id="level_selector_720">
|
|
27
27
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
28
28
|
HD
|
|
29
29
|
</a>
|
|
30
30
|
</li>
|
|
31
31
|
|
|
32
32
|
<li class="">
|
|
33
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="0"
|
|
33
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="0" aria-disabled="false" aria-checked="false" role="menuitemradio" id="level_selector_360">
|
|
34
34
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
35
35
|
360p
|
|
36
36
|
</a>
|
|
@@ -48,7 +48,7 @@ exports[`QualityLevels > basically > custom label > should render the selected l
|
|
|
48
48
|
<ul class="gear-sub-menu quality-levels" id="level-selector-menu" role="menu">
|
|
49
49
|
|
|
50
50
|
<li class="">
|
|
51
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="-1" id="level_selector_auto">
|
|
51
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="-1" id="level_selector_auto" aria-checked="true" role="menuitemradio">
|
|
52
52
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
53
53
|
auto
|
|
54
54
|
</a>
|
|
@@ -56,21 +56,21 @@ exports[`QualityLevels > basically > custom label > should render the selected l
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
<li class="">
|
|
59
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="2"
|
|
59
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="2" aria-disabled="false" aria-checked="false" role="menuitemradio" id="level_selector_1080">
|
|
60
60
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
61
61
|
Full HD
|
|
62
62
|
</a>
|
|
63
63
|
</li>
|
|
64
64
|
|
|
65
65
|
<li class="current">
|
|
66
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color gcore-skin-active" data-id="1"
|
|
66
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color gcore-skin-active" data-id="1" aria-disabled="false" aria-checked="false" role="menuitemradio" id="level_selector_720">
|
|
67
67
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
68
68
|
HD
|
|
69
69
|
</a>
|
|
70
70
|
</li>
|
|
71
71
|
|
|
72
72
|
<li class="">
|
|
73
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="0"
|
|
73
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="0" aria-disabled="false" aria-checked="false" role="menuitemradio" id="level_selector_360">
|
|
74
74
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
75
75
|
360p
|
|
76
76
|
</a>
|
|
@@ -88,7 +88,7 @@ exports[`QualityLevels > basically > standard label > should render the selected
|
|
|
88
88
|
<ul class="gear-sub-menu quality-levels" id="level-selector-menu" role="menu">
|
|
89
89
|
|
|
90
90
|
<li class="">
|
|
91
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="-1" id="level_selector_auto">
|
|
91
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="-1" id="level_selector_auto" aria-checked="true" role="menuitemradio">
|
|
92
92
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
93
93
|
auto
|
|
94
94
|
</a>
|
|
@@ -96,21 +96,21 @@ exports[`QualityLevels > basically > standard label > should render the selected
|
|
|
96
96
|
|
|
97
97
|
|
|
98
98
|
<li class="">
|
|
99
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="2"
|
|
99
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="2" aria-disabled="false" aria-checked="false" role="menuitemradio" id="level_selector_1080">
|
|
100
100
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
101
101
|
Full HD
|
|
102
102
|
</a>
|
|
103
103
|
</li>
|
|
104
104
|
|
|
105
105
|
<li class="">
|
|
106
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="1"
|
|
106
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="1" aria-disabled="false" aria-checked="false" role="menuitemradio" id="level_selector_720">
|
|
107
107
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
108
108
|
HD
|
|
109
109
|
</a>
|
|
110
110
|
</li>
|
|
111
111
|
|
|
112
112
|
<li class="current">
|
|
113
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color gcore-skin-active" data-id="0"
|
|
113
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color gcore-skin-active" data-id="0" aria-disabled="false" aria-checked="false" role="menuitemradio" id="level_selector_360">
|
|
114
114
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
115
115
|
360p
|
|
116
116
|
</a>
|
|
@@ -129,21 +129,21 @@ exports[`QualityLevels > options.restrictResolution > given vertical video forma
|
|
|
129
129
|
|
|
130
130
|
|
|
131
131
|
<li class=" disabled">
|
|
132
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="2"
|
|
132
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="2" aria-disabled="true" aria-checked="false" role="menuitemradio" id="level_selector_1080">
|
|
133
133
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
134
134
|
1080p
|
|
135
135
|
</a>
|
|
136
136
|
</li>
|
|
137
137
|
|
|
138
138
|
<li class=" disabled">
|
|
139
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="1"
|
|
139
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="1" aria-disabled="true" aria-checked="false" role="menuitemradio" id="level_selector_720">
|
|
140
140
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
141
141
|
720p
|
|
142
142
|
</a>
|
|
143
143
|
</li>
|
|
144
144
|
|
|
145
145
|
<li class=" current">
|
|
146
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color gcore-skin-active" data-id="0"
|
|
146
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color gcore-skin-active" data-id="0" aria-disabled="false" aria-checked="true" role="menuitemradio" id="level_selector_360">
|
|
147
147
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
148
148
|
360p
|
|
149
149
|
</a>
|
|
@@ -172,21 +172,21 @@ exports[`QualityLevels > options.restrictResolution > initially > when opened >
|
|
|
172
172
|
|
|
173
173
|
|
|
174
174
|
<li class=" disabled">
|
|
175
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="2"
|
|
175
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="2" aria-disabled="true" aria-checked="false" role="menuitemradio" id="level_selector_1080">
|
|
176
176
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
177
177
|
1080p
|
|
178
178
|
</a>
|
|
179
179
|
</li>
|
|
180
180
|
|
|
181
181
|
<li class=" disabled">
|
|
182
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="1"
|
|
182
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color" data-id="1" aria-disabled="true" aria-checked="false" role="menuitemradio" id="level_selector_720">
|
|
183
183
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
184
184
|
720p
|
|
185
185
|
</a>
|
|
186
186
|
</li>
|
|
187
187
|
|
|
188
188
|
<li class=" current">
|
|
189
|
-
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color gcore-skin-active" data-id="0"
|
|
189
|
+
<a href="#" class="gear-sub-menu_btn gcore-skin-text-color gcore-skin-active" data-id="0" aria-disabled="false" aria-checked="true" role="menuitemradio" id="level_selector_360">
|
|
190
190
|
<span class="check-icon">/assets/icons/new/check.svg</span>
|
|
191
191
|
360p
|
|
192
192
|
</a>
|