@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.
Files changed (69) hide show
  1. package/assets/media-control/container.scss +2 -3
  2. package/assets/poster/poster.ejs +3 -1
  3. package/assets/poster/poster.scss +3 -3
  4. package/assets/style/main.scss +1 -1
  5. package/assets/thumbnails/scrub-thumbnails.ejs +5 -10
  6. package/assets/thumbnails/style.scss +4 -5
  7. package/dist/core.js +1 -1
  8. package/dist/index.css +533 -532
  9. package/dist/index.js +273 -377
  10. package/dist/player.d.ts +63 -33
  11. package/docs/api/{player.seektime.bindevents.md → player.clapprstats.clearmetrics.md} +3 -3
  12. package/docs/api/player.clapprstats.md +14 -0
  13. package/docs/api/player.extendedevents.md +14 -0
  14. package/docs/api/player.md +13 -2
  15. package/docs/api/player.seektime.attributes.md +0 -1
  16. package/docs/api/player.seektime.md +6 -197
  17. package/docs/api/{player.seektime.render.md → player.seektimesettings.md} +7 -7
  18. package/docs/api/player.skiptime.md +3 -184
  19. package/lib/plugins/clips/Clips.d.ts +7 -0
  20. package/lib/plugins/clips/Clips.d.ts.map +1 -1
  21. package/lib/plugins/clips/Clips.js +8 -0
  22. package/lib/plugins/media-control/MediaControl.d.ts +1 -7
  23. package/lib/plugins/media-control/MediaControl.d.ts.map +1 -1
  24. package/lib/plugins/media-control/MediaControl.js +9 -18
  25. package/lib/plugins/poster/Poster.d.ts +24 -14
  26. package/lib/plugins/poster/Poster.d.ts.map +1 -1
  27. package/lib/plugins/poster/Poster.js +67 -97
  28. package/lib/plugins/thumbnails/Thumbnails.d.ts +36 -33
  29. package/lib/plugins/thumbnails/Thumbnails.d.ts.map +1 -1
  30. package/lib/plugins/thumbnails/Thumbnails.js +174 -259
  31. package/lib/plugins/thumbnails/utils.d.ts +5 -0
  32. package/lib/plugins/thumbnails/utils.d.ts.map +1 -0
  33. package/lib/plugins/thumbnails/utils.js +12 -0
  34. package/lib/testUtils.d.ts +13 -39
  35. package/lib/testUtils.d.ts.map +1 -1
  36. package/lib/testUtils.js +15 -67
  37. package/package.json +2 -1
  38. package/src/plugins/clips/Clips.ts +10 -1
  39. package/src/plugins/media-control/MediaControl.ts +10 -21
  40. package/src/plugins/media-control/__tests__/__snapshots__/MediaControl.test.ts.snap +1 -1
  41. package/src/plugins/poster/Poster.ts +91 -110
  42. package/src/plugins/poster/__tests__/Poster.test.ts +119 -0
  43. package/src/plugins/poster/__tests__/__snapshots__/Poster.test.ts.snap +8 -0
  44. package/src/plugins/source-controller/__tests__/SourceController.test.ts +1 -2
  45. package/src/plugins/thumbnails/Thumbnails.ts +228 -330
  46. package/src/plugins/thumbnails/__tests__/Thumbnails.test.ts +72 -0
  47. package/src/plugins/thumbnails/__tests__/__snapshots__/Thumbnails.test.ts.snap +10 -0
  48. package/src/plugins/thumbnails/utils.ts +12 -0
  49. package/src/testUtils.ts +15 -88
  50. package/temp/player.api.json +295 -829
  51. package/tsconfig.tsbuildinfo +1 -1
  52. package/docs/api/player.seektime.durationshown.md +0 -14
  53. package/docs/api/player.seektime.getseektime.md +0 -20
  54. package/docs/api/player.seektime.islivestreamwithdvr.md +0 -14
  55. package/docs/api/player.seektime.mediacontrol.md +0 -14
  56. package/docs/api/player.seektime.mediacontrolcontainer.md +0 -14
  57. package/docs/api/player.seektime.shouldbevisible.md +0 -18
  58. package/docs/api/player.seektime.template.md +0 -14
  59. package/docs/api/player.seektime.update.md +0 -18
  60. package/docs/api/player.skiptime.attributes.md +0 -17
  61. package/docs/api/player.skiptime.bindevents.md +0 -18
  62. package/docs/api/player.skiptime.events.md +0 -18
  63. package/docs/api/player.skiptime.handlerewindclicks.md +0 -18
  64. package/docs/api/player.skiptime.render.md +0 -18
  65. package/docs/api/player.skiptime.setback.md +0 -18
  66. package/docs/api/player.skiptime.setforward.md +0 -18
  67. package/docs/api/player.skiptime.setmidclick.md +0 -18
  68. package/docs/api/player.skiptime.template.md +0 -14
  69. package/docs/api/player.skiptime.togglefullscreen.md +0 -18
@@ -1,6 +1,14 @@
1
- import { UICorePlugin, Events, template, $, Container } from '@clappr/core'
2
- import { reportError, trace } from '@gcorevideo/utils'
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 { getPageX } from '../utils.js'
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: number
20
- backdropMaxOpacity: number
21
- backdropMinOpacity: number
22
- spotlightHeight: number
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 Thumb = {
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 _$spotlight: ZeptoResult | null = null
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 _$carousel: ZeptoResult | null = null
72
-
73
- private $textThumbnail: ZeptoResult | null = null
81
+ private spriteSheetHeight: number = 0
74
82
 
75
- private _$backdropCarouselImgs: ZeptoResult[] = []
83
+ private spriteSheetWidth: number = 0
76
84
 
77
- private spriteSheetHeight: number = 0
85
+ private hoverPosition = 0
78
86
 
79
- private _hoverPosition = 0
87
+ private showing = false
80
88
 
81
- private _show = false
89
+ private thumbsLoaded = false
82
90
 
83
- private _thumbsLoaded = false
91
+ private spotlightHeight = 0
84
92
 
85
- private _oldContainer: Container | null = null
93
+ private backdropHeight = 0
86
94
 
87
- private _thumbs: Thumb[] = []
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: this.name,
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
- // buildSpriteConfig(vtt, spriteSheetUrl, numThumbs, thumbWidth, thumbHeight, numColumns, timeInterval, startTime) {
126
- private buildSpriteConfig(vtt: ParsedSRT[], spriteSheetUrl: string): Thumb[] {
127
- const thumbs: Thumb[] = []
128
- // let coor: string[] = [];
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=\d*,\d*,\d*,\d*/g)
147
+ const m = el.match(/(\w+)#xywh=(\d+,\d+,\d+,\d+)/)
138
148
  if (m) {
139
- const coor = m[0].split(',')
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
- url: spriteSheetUrl,
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._onCoreReady)
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 _bindContainerEvents() {
189
- if (this._oldContainer) {
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 _onCoreReady() {
205
- try {
206
- if (
207
- !this.options.thumbnails ||
208
- !this.options.thumbnails.sprite ||
209
- !this.options.thumbnails.vtt
210
- ) {
211
- this.destroy()
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
- // TODO options
221
- const spriteSheet = this.options.thumbnails.sprite
222
- this._thumbs = this.buildSpriteConfig(
223
- parseSRT(this.options.thumbnails.vtt),
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._thumbsLoaded = true
232
- this.core.options.thumbnails.spotlightHeight = this._thumbs[0].h
233
- this._init()
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 new Promise((resolve, reject) => {
239
- const img = new Image()
240
- img.onload = () => {
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 _onMediaControlContainerChanged() {
250
- this._bindContainerEvents()
240
+ private onContainerChanged(container: Container) {
241
+ this.bindContainerEvents(container)
251
242
  }
252
243
 
253
- private _init() {
254
- if (!this._thumbsLoaded) {
255
- // _init() will be called when the thumbs are loaded,
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._$backdropCarouselImgs = []
261
- // create/recreate the dom elements for the plugin
262
- this._createElements()
263
- this._loadBackdrop()
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 _onMouseMove(e: MouseEvent) {
282
- // trace(`${T} _onMouseMove`, {
283
- // e: (e as any).name,
284
- // t: typeof e,
285
- // t2: typeof arguments[1],
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 _onMouseLeave() {
293
- this._show = false
294
- this._renderPlugin()
265
+ private onMouseMoveSeekbar(_: MouseEvent, pos: number) {
266
+ this.hoverPosition = pos
267
+ this.showing = true
268
+ this.update()
295
269
  }
296
270
 
297
- private _calculateHoverPosition(e: MouseEvent) {
298
- const offset =
299
- getPageX(e) - this.core.mediaControl.$seekBarContainer.offset().left
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 provided height
377
- private _buildImg(thumb: Thumb, height: number) {
277
+ // scaled to the given height
278
+ private buildThumbImage(thumb: ThumbnailDesc, height: number) {
378
279
  const scaleFactor = height / thumb.h
379
-
380
- if (!this.$img) {
381
- this.$img = $('<img />').addClass('thumbnail-img').attr('src', thumb.url)
382
- }
383
-
384
- // the container will contain the image positioned so that the correct sprite
385
- // is visible
386
- if (!this.$container) {
387
- this.$container = $('<div />').addClass('thumbnail-container')
388
- }
389
-
390
- this.$container.css('width', thumb.w * scaleFactor)
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 this.$container
294
+ return $container
402
295
  }
403
296
 
404
- private _loadBackdrop() {
405
- if (!this._getOptions().backdropHeight) {
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._$carousel
304
+ const $carousel = this.$el.find('#thumbnails-carousel')
412
305
 
413
- for (const thumb of this._thumbs) {
414
- const $img = this._buildImg(thumb, this._getOptions().backdropHeight)
415
-
416
- // Keep reference to thumbnail
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
- if (this.core.getPlugin('clips')) {
425
- const txt = this.core.getPlugin('clips').getText(time)
426
-
427
- this.$textThumbnail.text(txt)
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 _updateCarousel() {
434
- trace(`${T} _updateCarousel`, {
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 hoverPosition = this._hoverPosition
443
- const videoDuration = this.core.mediaControl.container.getDuration()
444
- const startTimeOffset =
445
- this.core.mediaControl.container.getStartTimeOffset()
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 backdropWidth = this._$backdrop.width()
449
- const $carousel = this._$carousel
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._getThumbIndexForTime(hoverTime)
462
- const thumb = thumbs[thumbIndex]
463
- let thumbDuration = thumb.duration
464
-
465
- if (!thumbDuration) {
466
- // the last thumbnail duration will be null as it can't be determined
467
- // e.g the duration of the video may increase over time (live stream)
468
- // so calculate the duration now so this last thumbnail lasts till the end
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._getOptions().backdropMaxOpacity || 0.6
485
- const minOpacity = this._getOptions().backdropMinOpacity || 0.08
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._$backdropCarouselImgs[i].css('opacity', opacity)
392
+ this.$backdropCarouselImgs[i].css('opacity', opacity)
506
393
  }
507
394
  }
508
395
 
509
- private _updateSpotlightThumb() {
510
- trace(`${T} _updateSpotlightThumb`, {
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 hoverPosition = this._hoverPosition
519
- const videoDuration = this.core.mediaControl.container.getDuration()
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
- this.core.mediaControl.container.getStartTimeOffset()
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._getThumbIndexForTime(hoverTime)
529
- const thumb = this._thumbs[thumbIndex]
411
+ const thumbIndex = this.getThumbIndexForTime(hoverTime)
412
+ const thumb = this.thumbs[thumbIndex]
530
413
 
531
414
  // update thumbnail
532
- const $spotlight = this._$spotlight
415
+ const $spotlight = this.$el.find('#thumbnails-spotlight')
533
416
 
534
417
  $spotlight.empty()
535
- $spotlight.append(this._buildImg(thumb, this._getOptions().spotlightHeight))
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(Math.min(spotlightXPos, elWidth - thumbWidth), 0)
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.$textThumbnail.css('left', spotlightXPos)
549
- this.$textThumbnail.css('width', thumbWidth)
550
- this.$textThumbnail.css('bottom', thumbHeight + 1)
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 _getThumbIndexForTime(time: TimeValue) {
556
- const thumbs = this._thumbs
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 _renderPlugin() {
571
- trace(`${T} _renderPlugin`, {
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._show && this._thumbs.length > 0) {
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 _createElements() {
589
- trace(`${T} _createElements`)
590
- this.$el.html(
591
- Thumbnails.template({
592
- backdropHeight: this._getOptions().backdropHeight,
593
- spotlightHeight: this._getOptions().spotlightHeight,
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
- // cache dom references
597
- this._$spotlight = this.$el.find('.spotlight')
598
- this._$backdrop = this.$el.find('.backdrop')
599
- this._$carousel = this._$backdrop.find('.carousel')
600
- this.$textThumbnail = this.$el.find('.thumbnails-text')
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
- this._appendElToMediaControl()
499
+
500
+ return this
603
501
  }
604
502
  }