@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
|
@@ -1,6 +1,14 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
UICorePlugin,
|
|
3
|
+
Events,
|
|
4
|
+
template,
|
|
5
|
+
$,
|
|
6
|
+
Container,
|
|
7
|
+
Core,
|
|
8
|
+
} from '@clappr/core'
|
|
9
|
+
import { trace } from '@gcorevideo/utils'
|
|
3
10
|
import parseSRT, { type ParsedSRT } from 'parse-srt'
|
|
11
|
+
import assert from 'assert'
|
|
4
12
|
|
|
5
13
|
import { TimeValue } from '../../playback.types.js'
|
|
6
14
|
|
|
@@ -9,22 +17,24 @@ import { CLAPPR_VERSION } from '../../build.js'
|
|
|
9
17
|
import pluginHtml from '../../../assets/thumbnails/scrub-thumbnails.ejs'
|
|
10
18
|
import '../../../assets/thumbnails/style.scss'
|
|
11
19
|
import { ZeptoResult } from '../../types.js'
|
|
12
|
-
import {
|
|
20
|
+
import { MediaControl } from '../media-control/MediaControl.js'
|
|
21
|
+
import { Clips } from '../clips/Clips.js'
|
|
22
|
+
import { loadImageDimensions } from './utils.js'
|
|
13
23
|
|
|
14
24
|
/**
|
|
15
25
|
* Plugin configuration options for the thumbnails plugin.
|
|
16
26
|
* @beta
|
|
17
27
|
*/
|
|
18
28
|
export type ThumbnailsPluginSettings = {
|
|
19
|
-
backdropHeight
|
|
20
|
-
backdropMaxOpacity
|
|
21
|
-
backdropMinOpacity
|
|
22
|
-
spotlightHeight
|
|
29
|
+
backdropHeight?: number
|
|
30
|
+
backdropMaxOpacity?: number
|
|
31
|
+
backdropMinOpacity?: number
|
|
32
|
+
spotlightHeight?: number
|
|
23
33
|
sprite: string
|
|
24
34
|
vtt: string
|
|
25
35
|
}
|
|
26
36
|
|
|
27
|
-
type
|
|
37
|
+
type ThumbnailDesc = {
|
|
28
38
|
url: string
|
|
29
39
|
time: number
|
|
30
40
|
w: number
|
|
@@ -40,6 +50,12 @@ const T = 'plugins.thumbnails'
|
|
|
40
50
|
/**
|
|
41
51
|
* `PLUGIN` that displays the thumbnails of the video when available.
|
|
42
52
|
* @beta
|
|
53
|
+
* @remarks
|
|
54
|
+
* The plugin needs specially crafted VTT file with a thumbnail sprite sheet to work.
|
|
55
|
+
* The VTT consist of timestamp records followed by a thumbnail area
|
|
56
|
+
*
|
|
57
|
+
* Configuration options - {@link ThumbnailsPluginSettings}
|
|
58
|
+
*
|
|
43
59
|
* @example
|
|
44
60
|
* ```ts
|
|
45
61
|
* import { Thumbnails } from '@gcorevideo/player'
|
|
@@ -60,31 +76,23 @@ const T = 'plugins.thumbnails'
|
|
|
60
76
|
* ```
|
|
61
77
|
*/
|
|
62
78
|
export class Thumbnails extends UICorePlugin {
|
|
63
|
-
private
|
|
64
|
-
|
|
65
|
-
private _$backdrop: ZeptoResult | null = null
|
|
66
|
-
|
|
67
|
-
private $container: ZeptoResult | null = null
|
|
68
|
-
|
|
69
|
-
private $img: ZeptoResult | null = null
|
|
79
|
+
private $backdropCarouselImgs: ZeptoResult[] = []
|
|
70
80
|
|
|
71
|
-
private
|
|
72
|
-
|
|
73
|
-
private $textThumbnail: ZeptoResult | null = null
|
|
81
|
+
private spriteSheetHeight: number = 0
|
|
74
82
|
|
|
75
|
-
private
|
|
83
|
+
private spriteSheetWidth: number = 0
|
|
76
84
|
|
|
77
|
-
private
|
|
85
|
+
private hoverPosition = 0
|
|
78
86
|
|
|
79
|
-
private
|
|
87
|
+
private showing = false
|
|
80
88
|
|
|
81
|
-
private
|
|
89
|
+
private thumbsLoaded = false
|
|
82
90
|
|
|
83
|
-
private
|
|
91
|
+
private spotlightHeight = 0
|
|
84
92
|
|
|
85
|
-
private
|
|
93
|
+
private backdropHeight = 0
|
|
86
94
|
|
|
87
|
-
private
|
|
95
|
+
private thumbs: ThumbnailDesc[] = []
|
|
88
96
|
|
|
89
97
|
/**
|
|
90
98
|
* @internal
|
|
@@ -105,12 +113,17 @@ export class Thumbnails extends UICorePlugin {
|
|
|
105
113
|
*/
|
|
106
114
|
override get attributes() {
|
|
107
115
|
return {
|
|
108
|
-
class:
|
|
116
|
+
class: 'scrub-thumbnails',
|
|
109
117
|
}
|
|
110
118
|
}
|
|
111
119
|
|
|
112
120
|
private static readonly template = template(pluginHtml)
|
|
113
121
|
|
|
122
|
+
constructor(core: Core) {
|
|
123
|
+
super(core)
|
|
124
|
+
this.backdropHeight = this.options.thumbnails?.backdropHeight ?? 0
|
|
125
|
+
}
|
|
126
|
+
|
|
114
127
|
/*
|
|
115
128
|
* Helper to build the "thumbs" property for a sprite sheet.
|
|
116
129
|
*
|
|
@@ -122,26 +135,25 @@ export class Thumbnails extends UICorePlugin {
|
|
|
122
135
|
* timeInterval- The interval (in seconds) between the thumbnails.
|
|
123
136
|
* startTime- The time (in seconds) that the first thumbnail represents. (defaults to 0)
|
|
124
137
|
*/
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
138
|
+
private buildSpriteConfig(
|
|
139
|
+
vtt: ParsedSRT[],
|
|
140
|
+
baseUrl: string,
|
|
141
|
+
): ThumbnailDesc[] {
|
|
142
|
+
const thumbs: ThumbnailDesc[] = []
|
|
129
143
|
|
|
130
144
|
for (const vt of vtt) {
|
|
131
145
|
const el = vt.text
|
|
132
|
-
// if (el && el.search(/\d*,\d*,\d*,\d*/g) > -1) {
|
|
133
|
-
// el = el.match(/\d*,\d*,\d*,\d*/g)[0];
|
|
134
|
-
// coor = el.split(',');
|
|
135
|
-
// }
|
|
136
146
|
if (el) {
|
|
137
|
-
const m = el.match(/xywh
|
|
147
|
+
const m = el.match(/(\w+)#xywh=(\d+,\d+,\d+,\d+)/)
|
|
138
148
|
if (m) {
|
|
139
|
-
const coor = m[
|
|
149
|
+
const coor = m[2].split(',')
|
|
140
150
|
const w = parseInt(coor[2], 10)
|
|
141
151
|
const h = parseInt(coor[3], 10)
|
|
142
152
|
if (w > 0 && h > 0) {
|
|
143
153
|
thumbs.push({
|
|
144
|
-
|
|
154
|
+
// TODO handle relative URLs
|
|
155
|
+
// url: new URL(m[0], baseUrl).toString(),
|
|
156
|
+
url: baseUrl,
|
|
145
157
|
time: vt.start,
|
|
146
158
|
w,
|
|
147
159
|
h,
|
|
@@ -156,318 +168,193 @@ export class Thumbnails extends UICorePlugin {
|
|
|
156
168
|
return thumbs
|
|
157
169
|
}
|
|
158
170
|
|
|
159
|
-
// TODO check if seek enabled
|
|
160
|
-
|
|
161
171
|
/**
|
|
162
172
|
* @internal
|
|
163
173
|
*/
|
|
164
174
|
override bindEvents() {
|
|
165
|
-
this.listenToOnce(this.core, Events.CORE_READY, this.
|
|
166
|
-
this.listenTo(
|
|
167
|
-
this.core.mediaControl,
|
|
168
|
-
Events.MEDIACONTROL_MOUSEMOVE_SEEKBAR,
|
|
169
|
-
this._onMouseMove,
|
|
170
|
-
)
|
|
171
|
-
this.listenTo(
|
|
172
|
-
this.core.mediaControl,
|
|
173
|
-
Events.MEDIACONTROL_MOUSELEAVE_SEEKBAR,
|
|
174
|
-
this._onMouseLeave,
|
|
175
|
-
)
|
|
176
|
-
this.listenTo(
|
|
177
|
-
this.core.mediaControl,
|
|
178
|
-
Events.MEDIACONTROL_RENDERED,
|
|
179
|
-
this._init,
|
|
180
|
-
)
|
|
181
|
-
this.listenTo(
|
|
182
|
-
this.core.mediaControl,
|
|
183
|
-
Events.MEDIACONTROL_CONTAINERCHANGED,
|
|
184
|
-
this._onMediaControlContainerChanged,
|
|
185
|
-
)
|
|
175
|
+
this.listenToOnce(this.core, Events.CORE_READY, this.onCoreReady)
|
|
186
176
|
}
|
|
187
177
|
|
|
188
|
-
private
|
|
189
|
-
|
|
190
|
-
this.stopListening(
|
|
191
|
-
this._oldContainer,
|
|
192
|
-
Events.CONTAINER_TIMEUPDATE,
|
|
193
|
-
this._renderPlugin,
|
|
194
|
-
)
|
|
195
|
-
}
|
|
196
|
-
this._oldContainer = this.core.mediaControl.container
|
|
197
|
-
this.listenTo(
|
|
198
|
-
this.core.mediaControl.container,
|
|
199
|
-
Events.CONTAINER_TIMEUPDATE,
|
|
200
|
-
this._renderPlugin,
|
|
201
|
-
)
|
|
178
|
+
private bindContainerEvents(container: Container) {
|
|
179
|
+
this.listenTo(container, Events.CONTAINER_TIMEUPDATE, this.update)
|
|
202
180
|
}
|
|
203
181
|
|
|
204
|
-
private
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
return
|
|
214
|
-
}
|
|
215
|
-
} catch (error) {
|
|
216
|
-
reportError(error)
|
|
182
|
+
private onCoreReady() {
|
|
183
|
+
const mediaControl = this.core.getPlugin('media_control') as
|
|
184
|
+
| MediaControl
|
|
185
|
+
| undefined
|
|
186
|
+
assert(
|
|
187
|
+
mediaControl,
|
|
188
|
+
`MediaControl is required for ${this.name} plugin to work`,
|
|
189
|
+
)
|
|
217
190
|
|
|
191
|
+
if (
|
|
192
|
+
!this.options.thumbnails ||
|
|
193
|
+
!this.options.thumbnails.sprite ||
|
|
194
|
+
!this.options.thumbnails.vtt
|
|
195
|
+
) {
|
|
196
|
+
trace(
|
|
197
|
+
`${T} misconfigured: options.thumbnails.sprite and options.thumbnails.vtt are required`,
|
|
198
|
+
)
|
|
199
|
+
this.destroy()
|
|
218
200
|
return
|
|
219
201
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
this.
|
|
223
|
-
|
|
224
|
-
spriteSheet,
|
|
225
|
-
)
|
|
226
|
-
if (!this._thumbs.length) {
|
|
202
|
+
const { sprite: spriteSheet, vtt } = this.options.thumbnails
|
|
203
|
+
this.thumbs = this.buildSpriteConfig(parseSRT(vtt), spriteSheet)
|
|
204
|
+
if (!this.thumbs.length) {
|
|
205
|
+
trace(`${T} failed to parse the sprite sheet`)
|
|
227
206
|
this.destroy()
|
|
228
207
|
return
|
|
229
208
|
}
|
|
209
|
+
this.spotlightHeight = this.options.thumbnails?.spotlightHeight ?? 0
|
|
230
210
|
this.loadSpriteSheet(spriteSheet).then(() => {
|
|
231
|
-
this.
|
|
232
|
-
this.
|
|
233
|
-
|
|
211
|
+
this.thumbsLoaded = true
|
|
212
|
+
this.spotlightHeight = this.spotlightHeight
|
|
213
|
+
? Math.min(this.spotlightHeight, this.thumbs[0].h)
|
|
214
|
+
: this.thumbs[0].h
|
|
215
|
+
this.init()
|
|
234
216
|
})
|
|
217
|
+
this.listenTo(
|
|
218
|
+
mediaControl,
|
|
219
|
+
Events.MEDIACONTROL_MOUSEMOVE_SEEKBAR,
|
|
220
|
+
this.onMouseMoveSeekbar,
|
|
221
|
+
)
|
|
222
|
+
this.listenTo(
|
|
223
|
+
mediaControl,
|
|
224
|
+
Events.MEDIACONTROL_MOUSELEAVE_SEEKBAR,
|
|
225
|
+
this.onMouseLeave,
|
|
226
|
+
)
|
|
227
|
+
this.listenTo(mediaControl, Events.MEDIACONTROL_RENDERED, this.init)
|
|
228
|
+
this.listenTo(mediaControl, Events.MEDIACONTROL_CONTAINERCHANGED, () =>
|
|
229
|
+
this.onContainerChanged(mediaControl.container),
|
|
230
|
+
)
|
|
235
231
|
}
|
|
236
232
|
|
|
237
233
|
private async loadSpriteSheet(spriteSheetUrl: string): Promise<void> {
|
|
238
|
-
return
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
this.spriteSheetHeight = img.height
|
|
242
|
-
resolve()
|
|
243
|
-
}
|
|
244
|
-
img.onerror = reject
|
|
245
|
-
img.src = spriteSheetUrl
|
|
234
|
+
return loadImageDimensions(spriteSheetUrl).then(({ height, width }) => {
|
|
235
|
+
this.spriteSheetHeight = height
|
|
236
|
+
this.spriteSheetWidth = width
|
|
246
237
|
})
|
|
247
238
|
}
|
|
248
239
|
|
|
249
|
-
private
|
|
250
|
-
this.
|
|
240
|
+
private onContainerChanged(container: Container) {
|
|
241
|
+
this.bindContainerEvents(container)
|
|
251
242
|
}
|
|
252
243
|
|
|
253
|
-
private
|
|
254
|
-
if (!this.
|
|
255
|
-
//
|
|
244
|
+
private init() {
|
|
245
|
+
if (!this.thumbsLoaded) {
|
|
246
|
+
// init() will be called when the thumbs are loaded,
|
|
256
247
|
// and whenever the media control rendered event is fired as just before this the dom elements get wiped in IE (https://github.com/tjenkinson/clappr-thumbnails-plugin/issues/5)
|
|
257
248
|
return
|
|
258
249
|
}
|
|
259
250
|
// Init the backdropCarousel as array to keep reference of thumbnail images
|
|
260
|
-
this
|
|
261
|
-
|
|
262
|
-
this.
|
|
263
|
-
this.
|
|
264
|
-
this._renderPlugin()
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
private _getOptions(): ThumbnailsPluginSettings {
|
|
268
|
-
if (!('thumbnails' in this.core.options)) {
|
|
269
|
-
throw "'thumbnail property missing from options object."
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return this.core.options.thumbnails
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
private _appendElToMediaControl() {
|
|
276
|
-
// insert after the background
|
|
277
|
-
this.core.mediaControl.$el.find('.seek-time').css('bottom', 56)
|
|
278
|
-
this.core.mediaControl.$el.first().after(this.el)
|
|
251
|
+
this.$backdropCarouselImgs = []
|
|
252
|
+
this.fixElements()
|
|
253
|
+
this.loadBackdrop()
|
|
254
|
+
this.update()
|
|
279
255
|
}
|
|
280
256
|
|
|
281
|
-
private
|
|
282
|
-
//
|
|
283
|
-
|
|
284
|
-
//
|
|
285
|
-
//
|
|
286
|
-
|
|
287
|
-
this._calculateHoverPosition(e)
|
|
288
|
-
this._show = true
|
|
289
|
-
this._renderPlugin()
|
|
257
|
+
private mount() {
|
|
258
|
+
// insert after the background TODO figure out why
|
|
259
|
+
const mediaControl = this.core.getPlugin('media_control') as MediaControl
|
|
260
|
+
mediaControl.$el.find('.seek-time').css('bottom', 56) // TODO check
|
|
261
|
+
// TODO use mediaControl.mount? into the `layer`
|
|
262
|
+
mediaControl.$el.append(this.$el)
|
|
290
263
|
}
|
|
291
264
|
|
|
292
|
-
private
|
|
293
|
-
this.
|
|
294
|
-
this.
|
|
265
|
+
private onMouseMoveSeekbar(_: MouseEvent, pos: number) {
|
|
266
|
+
this.hoverPosition = pos
|
|
267
|
+
this.showing = true
|
|
268
|
+
this.update()
|
|
295
269
|
}
|
|
296
270
|
|
|
297
|
-
private
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
// proportion into the seek bar that the mouse is hovered over 0-1
|
|
302
|
-
this._hoverPosition = Math.min(
|
|
303
|
-
1,
|
|
304
|
-
Math.max(offset / this.core.mediaControl.$seekBarContainer.width(), 0),
|
|
305
|
-
)
|
|
271
|
+
private onMouseLeave() {
|
|
272
|
+
this.showing = false
|
|
273
|
+
this.update()
|
|
306
274
|
}
|
|
307
275
|
|
|
308
|
-
// private _buildThumbsFromOptions() {
|
|
309
|
-
// const thumbs = this._thumbs;
|
|
310
|
-
// const promises = thumbs.map((thumb) => {
|
|
311
|
-
// return this._addThumbFromSrc(thumb);
|
|
312
|
-
// });
|
|
313
|
-
|
|
314
|
-
// return Promise.all(promises);
|
|
315
|
-
// }
|
|
316
|
-
|
|
317
|
-
// private _addThumbFromSrc(thumbSrc) {
|
|
318
|
-
// return new Promise((resolve, reject) => {
|
|
319
|
-
// const img = new Image();
|
|
320
|
-
|
|
321
|
-
// img.onload = () => {
|
|
322
|
-
// resolve(img);
|
|
323
|
-
// };
|
|
324
|
-
// img.onerror = reject;
|
|
325
|
-
// img.src = thumbSrc.url;
|
|
326
|
-
// }).then((img) => {
|
|
327
|
-
// const startTime = thumbSrc.time;
|
|
328
|
-
// // determine the thumb index
|
|
329
|
-
// let index = null;
|
|
330
|
-
|
|
331
|
-
// this._thumbs.some((thumb, i) => {
|
|
332
|
-
// if (startTime < thumb.time) {
|
|
333
|
-
// index = i;
|
|
334
|
-
|
|
335
|
-
// return true;
|
|
336
|
-
// }
|
|
337
|
-
|
|
338
|
-
// return false;
|
|
339
|
-
// });
|
|
340
|
-
// if (index === null) {
|
|
341
|
-
// index = this._thumbs.length;
|
|
342
|
-
// }
|
|
343
|
-
|
|
344
|
-
// const next = index < this._thumbs.length ? this._thumbs[index] : null;
|
|
345
|
-
// const prev = index > 0 ? this._thumbs[index - 1] : null;
|
|
346
|
-
|
|
347
|
-
// if (prev) {
|
|
348
|
-
// // update the duration of the previous thumbnail
|
|
349
|
-
// prev.duration = startTime - prev.time;
|
|
350
|
-
// }
|
|
351
|
-
// // the duration this thumb lasts for
|
|
352
|
-
// // if it is the last thumb then duration will be null
|
|
353
|
-
// const duration = next ? next.time - thumbSrc.time : null;
|
|
354
|
-
// const imageW = img.width;
|
|
355
|
-
// const imageH = img.height;
|
|
356
|
-
// const thumb = {
|
|
357
|
-
// imageW: imageW, // actual width of image
|
|
358
|
-
// imageH: imageH, // actual height of image
|
|
359
|
-
// x: thumbSrc.x || 0, // x coord in image of sprite
|
|
360
|
-
// y: thumbSrc.y || 0, // y coord in image of sprite
|
|
361
|
-
// w: thumbSrc.w || imageW, // width of sprite
|
|
362
|
-
// h: thumbSrc.h || imageH, // height of sprite
|
|
363
|
-
// url: thumbSrc.url,
|
|
364
|
-
// time: startTime, // time this thumb represents
|
|
365
|
-
// duration: duration, // how long (from time) this thumb represents
|
|
366
|
-
// src: thumbSrc
|
|
367
|
-
// };
|
|
368
|
-
|
|
369
|
-
// this._thumbs.splice(index, 0, thumb);
|
|
370
|
-
|
|
371
|
-
// return thumb;
|
|
372
|
-
// });
|
|
373
|
-
// }
|
|
374
|
-
|
|
375
276
|
// builds a dom element which represents the thumbnail
|
|
376
|
-
// scaled to the
|
|
377
|
-
private
|
|
277
|
+
// scaled to the given height
|
|
278
|
+
private buildThumbImage(thumb: ThumbnailDesc, height: number) {
|
|
378
279
|
const scaleFactor = height / thumb.h
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
this.$container.css('height', height)
|
|
392
|
-
this.$img.css({
|
|
393
|
-
height: this.spriteSheetHeight * scaleFactor,
|
|
394
|
-
left: -1 * thumb.x * scaleFactor,
|
|
395
|
-
top: -1 * thumb.y * scaleFactor,
|
|
280
|
+
const $container = $('<div />').addClass('thumbnail-container')
|
|
281
|
+
|
|
282
|
+
$container.css('width', thumb.w * scaleFactor)
|
|
283
|
+
$container.css('height', height)
|
|
284
|
+
$container.css({
|
|
285
|
+
backgroundImage: `url(${thumb.url})`,
|
|
286
|
+
backgroundSize: `${Math.floor(
|
|
287
|
+
this.spriteSheetWidth * scaleFactor,
|
|
288
|
+
)}px ${Math.floor(this.spriteSheetHeight * scaleFactor)}px`,
|
|
289
|
+
backgroundPosition: `-${Math.floor(
|
|
290
|
+
thumb.x * scaleFactor,
|
|
291
|
+
)}px -${Math.floor(thumb.y * scaleFactor)}px`,
|
|
396
292
|
})
|
|
397
|
-
if (this.$container.find(this.$img).length === 0) {
|
|
398
|
-
this.$container.append(this.$img)
|
|
399
|
-
}
|
|
400
293
|
|
|
401
|
-
return
|
|
294
|
+
return $container
|
|
402
295
|
}
|
|
403
296
|
|
|
404
|
-
private
|
|
405
|
-
if (!this.
|
|
297
|
+
private loadBackdrop() {
|
|
298
|
+
if (!this.backdropHeight) {
|
|
406
299
|
// disabled
|
|
407
300
|
return
|
|
408
301
|
}
|
|
409
302
|
|
|
410
303
|
// append each of the thumbnails to the backdrop carousel
|
|
411
|
-
const $carousel = this.
|
|
304
|
+
const $carousel = this.$el.find('#thumbnails-carousel')
|
|
412
305
|
|
|
413
|
-
for (const thumb of this.
|
|
414
|
-
const $img = this.
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
this._$backdropCarouselImgs.push($img)
|
|
306
|
+
for (const thumb of this.thumbs) {
|
|
307
|
+
const $img = this.buildThumbImage(thumb, this.backdropHeight)
|
|
308
|
+
// Keep reference to the thumbnail
|
|
309
|
+
this.$backdropCarouselImgs.push($img)
|
|
418
310
|
// Add thumbnail to DOM
|
|
419
311
|
$carousel.append($img)
|
|
420
312
|
}
|
|
421
313
|
}
|
|
422
314
|
|
|
423
315
|
private setText(time: TimeValue) {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
this.$
|
|
316
|
+
const clips = this.core.getPlugin('clips') as Clips
|
|
317
|
+
if (clips) {
|
|
318
|
+
const txt = clips.getText(time)
|
|
319
|
+
this.$el.find('#thumbnails-text').text(txt ?? '')
|
|
428
320
|
}
|
|
429
321
|
}
|
|
430
322
|
|
|
431
323
|
// calculate how far along the carousel should currently be slid
|
|
432
324
|
// depending on where the user is hovering on the progress bar
|
|
433
|
-
private
|
|
434
|
-
|
|
435
|
-
backdropHeight: this._getOptions().backdropHeight,
|
|
436
|
-
})
|
|
437
|
-
if (!this._getOptions().backdropHeight) {
|
|
325
|
+
private updateCarousel() {
|
|
326
|
+
if (!this.backdropHeight) {
|
|
438
327
|
// disabled
|
|
439
328
|
return
|
|
440
329
|
}
|
|
441
330
|
|
|
442
|
-
const
|
|
443
|
-
|
|
444
|
-
const
|
|
445
|
-
|
|
331
|
+
const mediaControl = this.core.getPlugin('media_control') as MediaControl
|
|
332
|
+
|
|
333
|
+
const videoDuration = mediaControl.container.getDuration()
|
|
334
|
+
const startTimeOffset = mediaControl.container.getStartTimeOffset()
|
|
446
335
|
// the time into the video at the current hover position
|
|
447
|
-
const hoverTime = startTimeOffset + videoDuration * hoverPosition
|
|
448
|
-
const
|
|
449
|
-
const
|
|
336
|
+
const hoverTime = startTimeOffset + videoDuration * this.hoverPosition
|
|
337
|
+
const $backdrop = this.$el.find('#thumbnails-backdrop')
|
|
338
|
+
const backdropWidth = $backdrop.width()
|
|
339
|
+
const $carousel = this.$el.find('#thumbnails-carousel')
|
|
450
340
|
const carouselWidth = $carousel.width()
|
|
451
341
|
|
|
452
342
|
// slide the carousel so that the image on the carousel that is above where the person
|
|
453
343
|
// is hovering maps to that position in time.
|
|
454
344
|
// Thumbnails may not be distributed at even times along the video
|
|
455
|
-
const thumbs = this._thumbs
|
|
456
345
|
|
|
457
346
|
// assuming that each thumbnail has the same width
|
|
458
|
-
const thumbWidth = carouselWidth / thumbs.length
|
|
347
|
+
const thumbWidth = carouselWidth / this.thumbs.length
|
|
459
348
|
|
|
460
349
|
// determine which thumbnail applies to the current time
|
|
461
|
-
const thumbIndex = this.
|
|
462
|
-
const thumb = thumbs[thumbIndex]
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
thumbDuration = Math.max(videoDuration + startTimeOffset - thumb.time, 0)
|
|
470
|
-
}
|
|
350
|
+
const thumbIndex = this.getThumbIndexForTime(hoverTime)
|
|
351
|
+
const thumb = this.thumbs[thumbIndex]
|
|
352
|
+
// the last thumbnail duration will be null as it can't be determined
|
|
353
|
+
// e.g the duration of the video may increase over time (live stream)
|
|
354
|
+
// so calculate the duration now so this last thumbnail lasts till the end
|
|
355
|
+
const thumbDuration =
|
|
356
|
+
thumb.duration ??
|
|
357
|
+
Math.max(videoDuration + startTimeOffset - thumb.time, 0)
|
|
471
358
|
|
|
472
359
|
// determine how far accross that thumbnail we are
|
|
473
360
|
const timeIntoThumb = hoverTime - thumb.time
|
|
@@ -477,15 +364,15 @@ export class Thumbnails extends UICorePlugin {
|
|
|
477
364
|
// now calculate the position along carousel that we want to be above the hover position
|
|
478
365
|
const xCoordInCarousel = thumbIndex * thumbWidth + xCoordInThumb
|
|
479
366
|
// and finally the position of the carousel when the hover position is taken in to consideration
|
|
480
|
-
const carouselXCoord = xCoordInCarousel - hoverPosition * backdropWidth
|
|
367
|
+
const carouselXCoord = xCoordInCarousel - this.hoverPosition * backdropWidth
|
|
481
368
|
|
|
482
|
-
$carousel.css('left', -carouselXCoord)
|
|
369
|
+
$carousel.css('left', -carouselXCoord) // TODO +px
|
|
483
370
|
|
|
484
|
-
const maxOpacity = this.
|
|
485
|
-
const minOpacity = this.
|
|
371
|
+
const maxOpacity = this.options.thumbnails.backdropMaxOpacity ?? 0.6
|
|
372
|
+
const minOpacity = this.options.thumbnails.backdropMinOpacity ?? 0.08
|
|
486
373
|
|
|
487
374
|
// now update the transparencies so that they fade in around the active one
|
|
488
|
-
for (let i = 0; i < thumbs.length; i++) {
|
|
375
|
+
for (let i = 0; i < this.thumbs.length; i++) {
|
|
489
376
|
const thumbXCoord = thumbWidth * i
|
|
490
377
|
let distance = thumbXCoord - xCoordInCarousel
|
|
491
378
|
|
|
@@ -502,61 +389,60 @@ export class Thumbnails extends UICorePlugin {
|
|
|
502
389
|
minOpacity,
|
|
503
390
|
)
|
|
504
391
|
|
|
505
|
-
this
|
|
392
|
+
this.$backdropCarouselImgs[i].css('opacity', opacity)
|
|
506
393
|
}
|
|
507
394
|
}
|
|
508
395
|
|
|
509
|
-
private
|
|
510
|
-
|
|
511
|
-
spotlightHeight: this._getOptions().spotlightHeight,
|
|
512
|
-
})
|
|
513
|
-
if (!this._getOptions().spotlightHeight) {
|
|
396
|
+
private updateSpotlightThumb() {
|
|
397
|
+
if (!this.spotlightHeight) {
|
|
514
398
|
// disabled
|
|
515
399
|
return
|
|
516
400
|
}
|
|
517
401
|
|
|
518
|
-
const
|
|
519
|
-
const videoDuration =
|
|
402
|
+
const mediaControl = this.core.getPlugin('media_control') as MediaControl
|
|
403
|
+
const videoDuration = mediaControl.container.getDuration()
|
|
520
404
|
// the time into the video at the current hover position
|
|
521
|
-
const startTimeOffset =
|
|
522
|
-
|
|
523
|
-
const hoverTime = startTimeOffset + videoDuration * hoverPosition
|
|
405
|
+
const startTimeOffset = mediaControl.container.getStartTimeOffset()
|
|
406
|
+
const hoverTime = startTimeOffset + videoDuration * this.hoverPosition
|
|
524
407
|
|
|
525
408
|
this.setText(hoverTime)
|
|
526
409
|
|
|
527
410
|
// determine which thumbnail applies to the current time
|
|
528
|
-
const thumbIndex = this.
|
|
529
|
-
const thumb = this.
|
|
411
|
+
const thumbIndex = this.getThumbIndexForTime(hoverTime)
|
|
412
|
+
const thumb = this.thumbs[thumbIndex]
|
|
530
413
|
|
|
531
414
|
// update thumbnail
|
|
532
|
-
const $spotlight = this.
|
|
415
|
+
const $spotlight = this.$el.find('#thumbnails-spotlight')
|
|
533
416
|
|
|
534
417
|
$spotlight.empty()
|
|
535
|
-
$spotlight.append(this.
|
|
418
|
+
$spotlight.append(this.buildThumbImage(thumb, this.spotlightHeight))
|
|
536
419
|
|
|
537
420
|
const elWidth = this.$el.width()
|
|
538
421
|
const thumbWidth = $spotlight.width()
|
|
539
422
|
const thumbHeight = $spotlight.height()
|
|
540
423
|
|
|
541
|
-
let spotlightXPos = elWidth * hoverPosition - thumbWidth / 2
|
|
542
|
-
|
|
543
424
|
// adjust so the entire thumbnail is always visible
|
|
544
|
-
spotlightXPos = Math.max(
|
|
425
|
+
const spotlightXPos = Math.max(
|
|
426
|
+
Math.min(
|
|
427
|
+
elWidth * this.hoverPosition - thumbWidth / 2,
|
|
428
|
+
elWidth - thumbWidth,
|
|
429
|
+
),
|
|
430
|
+
0,
|
|
431
|
+
)
|
|
545
432
|
|
|
546
433
|
$spotlight.css('left', spotlightXPos)
|
|
547
434
|
|
|
548
|
-
this.$
|
|
549
|
-
|
|
550
|
-
|
|
435
|
+
const $textThumbnail = this.$el.find('#thumbnails-text')
|
|
436
|
+
$textThumbnail.css('left', spotlightXPos)
|
|
437
|
+
$textThumbnail.css('width', thumbWidth)
|
|
438
|
+
$textThumbnail.css('bottom', thumbHeight + 1)
|
|
551
439
|
}
|
|
552
440
|
|
|
553
441
|
// returns the thumbnail which represents a time in the video
|
|
554
442
|
// or null if there is no thumbnail that can represent the time
|
|
555
|
-
private
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
for (let i = thumbs.length - 1; i >= 0; i--) {
|
|
559
|
-
const thumb = thumbs[i]
|
|
443
|
+
private getThumbIndexForTime(time: TimeValue) {
|
|
444
|
+
for (let i = this.thumbs.length - 1; i >= 0; i--) {
|
|
445
|
+
const thumb = this.thumbs[i]
|
|
560
446
|
|
|
561
447
|
if (thumb.time <= time) {
|
|
562
448
|
return i
|
|
@@ -567,38 +453,50 @@ export class Thumbnails extends UICorePlugin {
|
|
|
567
453
|
return 0
|
|
568
454
|
}
|
|
569
455
|
|
|
570
|
-
private
|
|
571
|
-
|
|
572
|
-
show: this._show,
|
|
573
|
-
thumbsLoaded: this._thumbsLoaded,
|
|
574
|
-
thumbs: this._thumbs.length,
|
|
575
|
-
})
|
|
576
|
-
if (!this._thumbsLoaded) {
|
|
456
|
+
private update() {
|
|
457
|
+
if (!this.thumbsLoaded) {
|
|
577
458
|
return
|
|
578
459
|
}
|
|
579
|
-
if (this.
|
|
460
|
+
if (this.showing && this.thumbs.length > 0) {
|
|
461
|
+
this.updateCarousel()
|
|
462
|
+
this.updateSpotlightThumb()
|
|
580
463
|
this.$el.removeClass('hidden')
|
|
581
|
-
this._updateCarousel()
|
|
582
|
-
this._updateSpotlightThumb()
|
|
583
464
|
} else {
|
|
584
465
|
this.$el.addClass('hidden')
|
|
585
466
|
}
|
|
586
467
|
}
|
|
587
468
|
|
|
588
|
-
private
|
|
589
|
-
|
|
590
|
-
this
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
469
|
+
private fixElements() {
|
|
470
|
+
const $spotlight = this.$el.find('#thumbnails-spotlight')
|
|
471
|
+
if (this.spotlightHeight) {
|
|
472
|
+
$spotlight.css('height', this.spotlightHeight)
|
|
473
|
+
} else {
|
|
474
|
+
$spotlight.remove()
|
|
475
|
+
}
|
|
476
|
+
const $backdrop = this.$el.find('#thumbnails-backdrop')
|
|
477
|
+
if (this.backdropHeight) {
|
|
478
|
+
$backdrop.css('height', this.backdropHeight)
|
|
479
|
+
} else {
|
|
480
|
+
$backdrop.remove()
|
|
481
|
+
}
|
|
482
|
+
this.mount()
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
private get shouldRender() {
|
|
486
|
+
return (
|
|
487
|
+
this.options.thumbnails &&
|
|
488
|
+
this.options.thumbnails.sprite &&
|
|
489
|
+
this.options.thumbnails.vtt
|
|
595
490
|
)
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
override render() {
|
|
494
|
+
if (!this.shouldRender) {
|
|
495
|
+
return this
|
|
496
|
+
}
|
|
497
|
+
this.$el.html(Thumbnails.template())
|
|
601
498
|
this.$el.addClass('hidden')
|
|
602
|
-
|
|
499
|
+
|
|
500
|
+
return this
|
|
603
501
|
}
|
|
604
502
|
}
|