@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,14 +1,21 @@
|
|
|
1
|
-
import { UICorePlugin, Events, template,
|
|
2
|
-
import {
|
|
1
|
+
import { UICorePlugin, Events, template, $, } from '@clappr/core';
|
|
2
|
+
import { trace } from '@gcorevideo/utils';
|
|
3
3
|
import parseSRT from 'parse-srt';
|
|
4
|
+
import assert from 'assert';
|
|
4
5
|
import { CLAPPR_VERSION } from '../../build.js';
|
|
5
6
|
import pluginHtml from '../../../assets/thumbnails/scrub-thumbnails.ejs';
|
|
6
7
|
import '../../../assets/thumbnails/style.scss';
|
|
7
|
-
import {
|
|
8
|
+
import { loadImageDimensions } from './utils.js';
|
|
8
9
|
const T = 'plugins.thumbnails';
|
|
9
10
|
/**
|
|
10
11
|
* `PLUGIN` that displays the thumbnails of the video when available.
|
|
11
12
|
* @beta
|
|
13
|
+
* @remarks
|
|
14
|
+
* The plugin needs specially crafted VTT file with a thumbnail sprite sheet to work.
|
|
15
|
+
* The VTT consist of timestamp records followed by a thumbnail area
|
|
16
|
+
*
|
|
17
|
+
* Configuration options - {@link ThumbnailsPluginSettings}
|
|
18
|
+
*
|
|
12
19
|
* @example
|
|
13
20
|
* ```ts
|
|
14
21
|
* import { Thumbnails } from '@gcorevideo/player'
|
|
@@ -29,19 +36,15 @@ const T = 'plugins.thumbnails';
|
|
|
29
36
|
* ```
|
|
30
37
|
*/
|
|
31
38
|
export class Thumbnails extends UICorePlugin {
|
|
32
|
-
|
|
33
|
-
_$backdrop = null;
|
|
34
|
-
$container = null;
|
|
35
|
-
$img = null;
|
|
36
|
-
_$carousel = null;
|
|
37
|
-
$textThumbnail = null;
|
|
38
|
-
_$backdropCarouselImgs = [];
|
|
39
|
+
$backdropCarouselImgs = [];
|
|
39
40
|
spriteSheetHeight = 0;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
spriteSheetWidth = 0;
|
|
42
|
+
hoverPosition = 0;
|
|
43
|
+
showing = false;
|
|
44
|
+
thumbsLoaded = false;
|
|
45
|
+
spotlightHeight = 0;
|
|
46
|
+
backdropHeight = 0;
|
|
47
|
+
thumbs = [];
|
|
45
48
|
/**
|
|
46
49
|
* @internal
|
|
47
50
|
*/
|
|
@@ -59,10 +62,14 @@ export class Thumbnails extends UICorePlugin {
|
|
|
59
62
|
*/
|
|
60
63
|
get attributes() {
|
|
61
64
|
return {
|
|
62
|
-
class:
|
|
65
|
+
class: 'scrub-thumbnails',
|
|
63
66
|
};
|
|
64
67
|
}
|
|
65
68
|
static template = template(pluginHtml);
|
|
69
|
+
constructor(core) {
|
|
70
|
+
super(core);
|
|
71
|
+
this.backdropHeight = this.options.thumbnails?.backdropHeight ?? 0;
|
|
72
|
+
}
|
|
66
73
|
/*
|
|
67
74
|
* Helper to build the "thumbs" property for a sprite sheet.
|
|
68
75
|
*
|
|
@@ -74,25 +81,21 @@ export class Thumbnails extends UICorePlugin {
|
|
|
74
81
|
* timeInterval- The interval (in seconds) between the thumbnails.
|
|
75
82
|
* startTime- The time (in seconds) that the first thumbnail represents. (defaults to 0)
|
|
76
83
|
*/
|
|
77
|
-
|
|
78
|
-
buildSpriteConfig(vtt, spriteSheetUrl) {
|
|
84
|
+
buildSpriteConfig(vtt, baseUrl) {
|
|
79
85
|
const thumbs = [];
|
|
80
|
-
// let coor: string[] = [];
|
|
81
86
|
for (const vt of vtt) {
|
|
82
87
|
const el = vt.text;
|
|
83
|
-
// if (el && el.search(/\d*,\d*,\d*,\d*/g) > -1) {
|
|
84
|
-
// el = el.match(/\d*,\d*,\d*,\d*/g)[0];
|
|
85
|
-
// coor = el.split(',');
|
|
86
|
-
// }
|
|
87
88
|
if (el) {
|
|
88
|
-
const m = el.match(/xywh
|
|
89
|
+
const m = el.match(/(\w+)#xywh=(\d+,\d+,\d+,\d+)/);
|
|
89
90
|
if (m) {
|
|
90
|
-
const coor = m[
|
|
91
|
+
const coor = m[2].split(',');
|
|
91
92
|
const w = parseInt(coor[2], 10);
|
|
92
93
|
const h = parseInt(coor[3], 10);
|
|
93
94
|
if (w > 0 && h > 0) {
|
|
94
95
|
thumbs.push({
|
|
95
|
-
|
|
96
|
+
// TODO handle relative URLs
|
|
97
|
+
// url: new URL(m[0], baseUrl).toString(),
|
|
98
|
+
url: baseUrl,
|
|
96
99
|
time: vt.start,
|
|
97
100
|
w,
|
|
98
101
|
h,
|
|
@@ -105,242 +108,147 @@ export class Thumbnails extends UICorePlugin {
|
|
|
105
108
|
}
|
|
106
109
|
return thumbs;
|
|
107
110
|
}
|
|
108
|
-
// TODO check if seek enabled
|
|
109
111
|
/**
|
|
110
112
|
* @internal
|
|
111
113
|
*/
|
|
112
114
|
bindEvents() {
|
|
113
|
-
this.listenToOnce(this.core, Events.CORE_READY, this.
|
|
114
|
-
this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_MOUSEMOVE_SEEKBAR, this._onMouseMove);
|
|
115
|
-
this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_MOUSELEAVE_SEEKBAR, this._onMouseLeave);
|
|
116
|
-
this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_RENDERED, this._init);
|
|
117
|
-
this.listenTo(this.core.mediaControl, Events.MEDIACONTROL_CONTAINERCHANGED, this._onMediaControlContainerChanged);
|
|
115
|
+
this.listenToOnce(this.core, Events.CORE_READY, this.onCoreReady);
|
|
118
116
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
this.stopListening(this._oldContainer, Events.CONTAINER_TIMEUPDATE, this._renderPlugin);
|
|
122
|
-
}
|
|
123
|
-
this._oldContainer = this.core.mediaControl.container;
|
|
124
|
-
this.listenTo(this.core.mediaControl.container, Events.CONTAINER_TIMEUPDATE, this._renderPlugin);
|
|
117
|
+
bindContainerEvents(container) {
|
|
118
|
+
this.listenTo(container, Events.CONTAINER_TIMEUPDATE, this.update);
|
|
125
119
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
reportError(error);
|
|
120
|
+
onCoreReady() {
|
|
121
|
+
const mediaControl = this.core.getPlugin('media_control');
|
|
122
|
+
assert(mediaControl, `MediaControl is required for ${this.name} plugin to work`);
|
|
123
|
+
if (!this.options.thumbnails ||
|
|
124
|
+
!this.options.thumbnails.sprite ||
|
|
125
|
+
!this.options.thumbnails.vtt) {
|
|
126
|
+
trace(`${T} misconfigured: options.thumbnails.sprite and options.thumbnails.vtt are required`);
|
|
127
|
+
this.destroy();
|
|
137
128
|
return;
|
|
138
129
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
130
|
+
const { sprite: spriteSheet, vtt } = this.options.thumbnails;
|
|
131
|
+
this.thumbs = this.buildSpriteConfig(parseSRT(vtt), spriteSheet);
|
|
132
|
+
if (!this.thumbs.length) {
|
|
133
|
+
trace(`${T} failed to parse the sprite sheet`);
|
|
143
134
|
this.destroy();
|
|
144
135
|
return;
|
|
145
136
|
}
|
|
137
|
+
this.spotlightHeight = this.options.thumbnails?.spotlightHeight ?? 0;
|
|
146
138
|
this.loadSpriteSheet(spriteSheet).then(() => {
|
|
147
|
-
this.
|
|
148
|
-
this.
|
|
149
|
-
|
|
139
|
+
this.thumbsLoaded = true;
|
|
140
|
+
this.spotlightHeight = this.spotlightHeight
|
|
141
|
+
? Math.min(this.spotlightHeight, this.thumbs[0].h)
|
|
142
|
+
: this.thumbs[0].h;
|
|
143
|
+
this.init();
|
|
150
144
|
});
|
|
145
|
+
this.listenTo(mediaControl, Events.MEDIACONTROL_MOUSEMOVE_SEEKBAR, this.onMouseMoveSeekbar);
|
|
146
|
+
this.listenTo(mediaControl, Events.MEDIACONTROL_MOUSELEAVE_SEEKBAR, this.onMouseLeave);
|
|
147
|
+
this.listenTo(mediaControl, Events.MEDIACONTROL_RENDERED, this.init);
|
|
148
|
+
this.listenTo(mediaControl, Events.MEDIACONTROL_CONTAINERCHANGED, () => this.onContainerChanged(mediaControl.container));
|
|
151
149
|
}
|
|
152
150
|
async loadSpriteSheet(spriteSheetUrl) {
|
|
153
|
-
return
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
this.spriteSheetHeight = img.height;
|
|
157
|
-
resolve();
|
|
158
|
-
};
|
|
159
|
-
img.onerror = reject;
|
|
160
|
-
img.src = spriteSheetUrl;
|
|
151
|
+
return loadImageDimensions(spriteSheetUrl).then(({ height, width }) => {
|
|
152
|
+
this.spriteSheetHeight = height;
|
|
153
|
+
this.spriteSheetWidth = width;
|
|
161
154
|
});
|
|
162
155
|
}
|
|
163
|
-
|
|
164
|
-
this.
|
|
156
|
+
onContainerChanged(container) {
|
|
157
|
+
this.bindContainerEvents(container);
|
|
165
158
|
}
|
|
166
|
-
|
|
167
|
-
if (!this.
|
|
168
|
-
//
|
|
159
|
+
init() {
|
|
160
|
+
if (!this.thumbsLoaded) {
|
|
161
|
+
// init() will be called when the thumbs are loaded,
|
|
169
162
|
// 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)
|
|
170
163
|
return;
|
|
171
164
|
}
|
|
172
165
|
// Init the backdropCarousel as array to keep reference of thumbnail images
|
|
173
|
-
this
|
|
174
|
-
|
|
175
|
-
this.
|
|
176
|
-
this.
|
|
177
|
-
this._renderPlugin();
|
|
166
|
+
this.$backdropCarouselImgs = [];
|
|
167
|
+
this.fixElements();
|
|
168
|
+
this.loadBackdrop();
|
|
169
|
+
this.update();
|
|
178
170
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
_appendElToMediaControl() {
|
|
186
|
-
// insert after the background
|
|
187
|
-
this.core.mediaControl.$el.find('.seek-time').css('bottom', 56);
|
|
188
|
-
this.core.mediaControl.$el.first().after(this.el);
|
|
189
|
-
}
|
|
190
|
-
_onMouseMove(e) {
|
|
191
|
-
// trace(`${T} _onMouseMove`, {
|
|
192
|
-
// e: (e as any).name,
|
|
193
|
-
// t: typeof e,
|
|
194
|
-
// t2: typeof arguments[1],
|
|
195
|
-
// });
|
|
196
|
-
this._calculateHoverPosition(e);
|
|
197
|
-
this._show = true;
|
|
198
|
-
this._renderPlugin();
|
|
171
|
+
mount() {
|
|
172
|
+
// insert after the background TODO figure out why
|
|
173
|
+
const mediaControl = this.core.getPlugin('media_control');
|
|
174
|
+
mediaControl.$el.find('.seek-time').css('bottom', 56); // TODO check
|
|
175
|
+
// TODO use mediaControl.mount? into the `layer`
|
|
176
|
+
mediaControl.$el.append(this.$el);
|
|
199
177
|
}
|
|
200
|
-
|
|
201
|
-
this.
|
|
202
|
-
this.
|
|
178
|
+
onMouseMoveSeekbar(_, pos) {
|
|
179
|
+
this.hoverPosition = pos;
|
|
180
|
+
this.showing = true;
|
|
181
|
+
this.update();
|
|
203
182
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
this._hoverPosition = Math.min(1, Math.max(offset / this.core.mediaControl.$seekBarContainer.width(), 0));
|
|
183
|
+
onMouseLeave() {
|
|
184
|
+
this.showing = false;
|
|
185
|
+
this.update();
|
|
208
186
|
}
|
|
209
|
-
// private _buildThumbsFromOptions() {
|
|
210
|
-
// const thumbs = this._thumbs;
|
|
211
|
-
// const promises = thumbs.map((thumb) => {
|
|
212
|
-
// return this._addThumbFromSrc(thumb);
|
|
213
|
-
// });
|
|
214
|
-
// return Promise.all(promises);
|
|
215
|
-
// }
|
|
216
|
-
// private _addThumbFromSrc(thumbSrc) {
|
|
217
|
-
// return new Promise((resolve, reject) => {
|
|
218
|
-
// const img = new Image();
|
|
219
|
-
// img.onload = () => {
|
|
220
|
-
// resolve(img);
|
|
221
|
-
// };
|
|
222
|
-
// img.onerror = reject;
|
|
223
|
-
// img.src = thumbSrc.url;
|
|
224
|
-
// }).then((img) => {
|
|
225
|
-
// const startTime = thumbSrc.time;
|
|
226
|
-
// // determine the thumb index
|
|
227
|
-
// let index = null;
|
|
228
|
-
// this._thumbs.some((thumb, i) => {
|
|
229
|
-
// if (startTime < thumb.time) {
|
|
230
|
-
// index = i;
|
|
231
|
-
// return true;
|
|
232
|
-
// }
|
|
233
|
-
// return false;
|
|
234
|
-
// });
|
|
235
|
-
// if (index === null) {
|
|
236
|
-
// index = this._thumbs.length;
|
|
237
|
-
// }
|
|
238
|
-
// const next = index < this._thumbs.length ? this._thumbs[index] : null;
|
|
239
|
-
// const prev = index > 0 ? this._thumbs[index - 1] : null;
|
|
240
|
-
// if (prev) {
|
|
241
|
-
// // update the duration of the previous thumbnail
|
|
242
|
-
// prev.duration = startTime - prev.time;
|
|
243
|
-
// }
|
|
244
|
-
// // the duration this thumb lasts for
|
|
245
|
-
// // if it is the last thumb then duration will be null
|
|
246
|
-
// const duration = next ? next.time - thumbSrc.time : null;
|
|
247
|
-
// const imageW = img.width;
|
|
248
|
-
// const imageH = img.height;
|
|
249
|
-
// const thumb = {
|
|
250
|
-
// imageW: imageW, // actual width of image
|
|
251
|
-
// imageH: imageH, // actual height of image
|
|
252
|
-
// x: thumbSrc.x || 0, // x coord in image of sprite
|
|
253
|
-
// y: thumbSrc.y || 0, // y coord in image of sprite
|
|
254
|
-
// w: thumbSrc.w || imageW, // width of sprite
|
|
255
|
-
// h: thumbSrc.h || imageH, // height of sprite
|
|
256
|
-
// url: thumbSrc.url,
|
|
257
|
-
// time: startTime, // time this thumb represents
|
|
258
|
-
// duration: duration, // how long (from time) this thumb represents
|
|
259
|
-
// src: thumbSrc
|
|
260
|
-
// };
|
|
261
|
-
// this._thumbs.splice(index, 0, thumb);
|
|
262
|
-
// return thumb;
|
|
263
|
-
// });
|
|
264
|
-
// }
|
|
265
187
|
// builds a dom element which represents the thumbnail
|
|
266
|
-
// scaled to the
|
|
267
|
-
|
|
188
|
+
// scaled to the given height
|
|
189
|
+
buildThumbImage(thumb, height) {
|
|
268
190
|
const scaleFactor = height / thumb.h;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
this.$container.css('width', thumb.w * scaleFactor);
|
|
278
|
-
this.$container.css('height', height);
|
|
279
|
-
this.$img.css({
|
|
280
|
-
height: this.spriteSheetHeight * scaleFactor,
|
|
281
|
-
left: -1 * thumb.x * scaleFactor,
|
|
282
|
-
top: -1 * thumb.y * scaleFactor,
|
|
191
|
+
const $container = $('<div />').addClass('thumbnail-container');
|
|
192
|
+
$container.css('width', thumb.w * scaleFactor);
|
|
193
|
+
$container.css('height', height);
|
|
194
|
+
$container.css({
|
|
195
|
+
backgroundImage: `url(${thumb.url})`,
|
|
196
|
+
backgroundSize: `${Math.floor(this.spriteSheetWidth * scaleFactor)}px ${Math.floor(this.spriteSheetHeight * scaleFactor)}px`,
|
|
197
|
+
backgroundPosition: `-${Math.floor(thumb.x * scaleFactor)}px -${Math.floor(thumb.y * scaleFactor)}px`,
|
|
283
198
|
});
|
|
284
|
-
|
|
285
|
-
this.$container.append(this.$img);
|
|
286
|
-
}
|
|
287
|
-
return this.$container;
|
|
199
|
+
return $container;
|
|
288
200
|
}
|
|
289
|
-
|
|
290
|
-
if (!this.
|
|
201
|
+
loadBackdrop() {
|
|
202
|
+
if (!this.backdropHeight) {
|
|
291
203
|
// disabled
|
|
292
204
|
return;
|
|
293
205
|
}
|
|
294
206
|
// append each of the thumbnails to the backdrop carousel
|
|
295
|
-
const $carousel = this.
|
|
296
|
-
for (const thumb of this.
|
|
297
|
-
const $img = this.
|
|
298
|
-
// Keep reference to thumbnail
|
|
299
|
-
this
|
|
207
|
+
const $carousel = this.$el.find('#thumbnails-carousel');
|
|
208
|
+
for (const thumb of this.thumbs) {
|
|
209
|
+
const $img = this.buildThumbImage(thumb, this.backdropHeight);
|
|
210
|
+
// Keep reference to the thumbnail
|
|
211
|
+
this.$backdropCarouselImgs.push($img);
|
|
300
212
|
// Add thumbnail to DOM
|
|
301
213
|
$carousel.append($img);
|
|
302
214
|
}
|
|
303
215
|
}
|
|
304
216
|
setText(time) {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
217
|
+
const clips = this.core.getPlugin('clips');
|
|
218
|
+
if (clips) {
|
|
219
|
+
const txt = clips.getText(time);
|
|
220
|
+
this.$el.find('#thumbnails-text').text(txt ?? '');
|
|
308
221
|
}
|
|
309
222
|
}
|
|
310
223
|
// calculate how far along the carousel should currently be slid
|
|
311
224
|
// depending on where the user is hovering on the progress bar
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
backdropHeight: this._getOptions().backdropHeight,
|
|
315
|
-
});
|
|
316
|
-
if (!this._getOptions().backdropHeight) {
|
|
225
|
+
updateCarousel() {
|
|
226
|
+
if (!this.backdropHeight) {
|
|
317
227
|
// disabled
|
|
318
228
|
return;
|
|
319
229
|
}
|
|
320
|
-
const
|
|
321
|
-
const videoDuration =
|
|
322
|
-
const startTimeOffset =
|
|
230
|
+
const mediaControl = this.core.getPlugin('media_control');
|
|
231
|
+
const videoDuration = mediaControl.container.getDuration();
|
|
232
|
+
const startTimeOffset = mediaControl.container.getStartTimeOffset();
|
|
323
233
|
// the time into the video at the current hover position
|
|
324
|
-
const hoverTime = startTimeOffset + videoDuration * hoverPosition;
|
|
325
|
-
const
|
|
326
|
-
const
|
|
234
|
+
const hoverTime = startTimeOffset + videoDuration * this.hoverPosition;
|
|
235
|
+
const $backdrop = this.$el.find('#thumbnails-backdrop');
|
|
236
|
+
const backdropWidth = $backdrop.width();
|
|
237
|
+
const $carousel = this.$el.find('#thumbnails-carousel');
|
|
327
238
|
const carouselWidth = $carousel.width();
|
|
328
239
|
// slide the carousel so that the image on the carousel that is above where the person
|
|
329
240
|
// is hovering maps to that position in time.
|
|
330
241
|
// Thumbnails may not be distributed at even times along the video
|
|
331
|
-
const thumbs = this._thumbs;
|
|
332
242
|
// assuming that each thumbnail has the same width
|
|
333
|
-
const thumbWidth = carouselWidth / thumbs.length;
|
|
243
|
+
const thumbWidth = carouselWidth / this.thumbs.length;
|
|
334
244
|
// determine which thumbnail applies to the current time
|
|
335
|
-
const thumbIndex = this.
|
|
336
|
-
const thumb = thumbs[thumbIndex];
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
thumbDuration = Math.max(videoDuration + startTimeOffset - thumb.time, 0);
|
|
343
|
-
}
|
|
245
|
+
const thumbIndex = this.getThumbIndexForTime(hoverTime);
|
|
246
|
+
const thumb = this.thumbs[thumbIndex];
|
|
247
|
+
// the last thumbnail duration will be null as it can't be determined
|
|
248
|
+
// e.g the duration of the video may increase over time (live stream)
|
|
249
|
+
// so calculate the duration now so this last thumbnail lasts till the end
|
|
250
|
+
const thumbDuration = thumb.duration ??
|
|
251
|
+
Math.max(videoDuration + startTimeOffset - thumb.time, 0);
|
|
344
252
|
// determine how far accross that thumbnail we are
|
|
345
253
|
const timeIntoThumb = hoverTime - thumb.time;
|
|
346
254
|
const positionInThumb = timeIntoThumb / thumbDuration;
|
|
@@ -348,12 +256,12 @@ export class Thumbnails extends UICorePlugin {
|
|
|
348
256
|
// now calculate the position along carousel that we want to be above the hover position
|
|
349
257
|
const xCoordInCarousel = thumbIndex * thumbWidth + xCoordInThumb;
|
|
350
258
|
// and finally the position of the carousel when the hover position is taken in to consideration
|
|
351
|
-
const carouselXCoord = xCoordInCarousel - hoverPosition * backdropWidth;
|
|
352
|
-
$carousel.css('left', -carouselXCoord);
|
|
353
|
-
const maxOpacity = this.
|
|
354
|
-
const minOpacity = this.
|
|
259
|
+
const carouselXCoord = xCoordInCarousel - this.hoverPosition * backdropWidth;
|
|
260
|
+
$carousel.css('left', -carouselXCoord); // TODO +px
|
|
261
|
+
const maxOpacity = this.options.thumbnails.backdropMaxOpacity ?? 0.6;
|
|
262
|
+
const minOpacity = this.options.thumbnails.backdropMinOpacity ?? 0.08;
|
|
355
263
|
// now update the transparencies so that they fade in around the active one
|
|
356
|
-
for (let i = 0; i < thumbs.length; i++) {
|
|
264
|
+
for (let i = 0; i < this.thumbs.length; i++) {
|
|
357
265
|
const thumbXCoord = thumbWidth * i;
|
|
358
266
|
let distance = thumbXCoord - xCoordInCarousel;
|
|
359
267
|
if (distance < 0) {
|
|
@@ -365,47 +273,43 @@ export class Thumbnails extends UICorePlugin {
|
|
|
365
273
|
}
|
|
366
274
|
// fade over the width of 2 thumbnails
|
|
367
275
|
const opacity = Math.max(maxOpacity - Math.abs(distance) / (2 * thumbWidth), minOpacity);
|
|
368
|
-
this
|
|
276
|
+
this.$backdropCarouselImgs[i].css('opacity', opacity);
|
|
369
277
|
}
|
|
370
278
|
}
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
spotlightHeight: this._getOptions().spotlightHeight,
|
|
374
|
-
});
|
|
375
|
-
if (!this._getOptions().spotlightHeight) {
|
|
279
|
+
updateSpotlightThumb() {
|
|
280
|
+
if (!this.spotlightHeight) {
|
|
376
281
|
// disabled
|
|
377
282
|
return;
|
|
378
283
|
}
|
|
379
|
-
const
|
|
380
|
-
const videoDuration =
|
|
284
|
+
const mediaControl = this.core.getPlugin('media_control');
|
|
285
|
+
const videoDuration = mediaControl.container.getDuration();
|
|
381
286
|
// the time into the video at the current hover position
|
|
382
|
-
const startTimeOffset =
|
|
383
|
-
const hoverTime = startTimeOffset + videoDuration * hoverPosition;
|
|
287
|
+
const startTimeOffset = mediaControl.container.getStartTimeOffset();
|
|
288
|
+
const hoverTime = startTimeOffset + videoDuration * this.hoverPosition;
|
|
384
289
|
this.setText(hoverTime);
|
|
385
290
|
// determine which thumbnail applies to the current time
|
|
386
|
-
const thumbIndex = this.
|
|
387
|
-
const thumb = this.
|
|
291
|
+
const thumbIndex = this.getThumbIndexForTime(hoverTime);
|
|
292
|
+
const thumb = this.thumbs[thumbIndex];
|
|
388
293
|
// update thumbnail
|
|
389
|
-
const $spotlight = this.
|
|
294
|
+
const $spotlight = this.$el.find('#thumbnails-spotlight');
|
|
390
295
|
$spotlight.empty();
|
|
391
|
-
$spotlight.append(this.
|
|
296
|
+
$spotlight.append(this.buildThumbImage(thumb, this.spotlightHeight));
|
|
392
297
|
const elWidth = this.$el.width();
|
|
393
298
|
const thumbWidth = $spotlight.width();
|
|
394
299
|
const thumbHeight = $spotlight.height();
|
|
395
|
-
let spotlightXPos = elWidth * hoverPosition - thumbWidth / 2;
|
|
396
300
|
// adjust so the entire thumbnail is always visible
|
|
397
|
-
spotlightXPos = Math.max(Math.min(
|
|
301
|
+
const spotlightXPos = Math.max(Math.min(elWidth * this.hoverPosition - thumbWidth / 2, elWidth - thumbWidth), 0);
|
|
398
302
|
$spotlight.css('left', spotlightXPos);
|
|
399
|
-
this.$
|
|
400
|
-
|
|
401
|
-
|
|
303
|
+
const $textThumbnail = this.$el.find('#thumbnails-text');
|
|
304
|
+
$textThumbnail.css('left', spotlightXPos);
|
|
305
|
+
$textThumbnail.css('width', thumbWidth);
|
|
306
|
+
$textThumbnail.css('bottom', thumbHeight + 1);
|
|
402
307
|
}
|
|
403
308
|
// returns the thumbnail which represents a time in the video
|
|
404
309
|
// or null if there is no thumbnail that can represent the time
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const thumb = thumbs[i];
|
|
310
|
+
getThumbIndexForTime(time) {
|
|
311
|
+
for (let i = this.thumbs.length - 1; i >= 0; i--) {
|
|
312
|
+
const thumb = this.thumbs[i];
|
|
409
313
|
if (thumb.time <= time) {
|
|
410
314
|
return i;
|
|
411
315
|
}
|
|
@@ -413,36 +317,47 @@ export class Thumbnails extends UICorePlugin {
|
|
|
413
317
|
// stretch the first thumbnail back to the start
|
|
414
318
|
return 0;
|
|
415
319
|
}
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
show: this._show,
|
|
419
|
-
thumbsLoaded: this._thumbsLoaded,
|
|
420
|
-
thumbs: this._thumbs.length,
|
|
421
|
-
});
|
|
422
|
-
if (!this._thumbsLoaded) {
|
|
320
|
+
update() {
|
|
321
|
+
if (!this.thumbsLoaded) {
|
|
423
322
|
return;
|
|
424
323
|
}
|
|
425
|
-
if (this.
|
|
324
|
+
if (this.showing && this.thumbs.length > 0) {
|
|
325
|
+
this.updateCarousel();
|
|
326
|
+
this.updateSpotlightThumb();
|
|
426
327
|
this.$el.removeClass('hidden');
|
|
427
|
-
this._updateCarousel();
|
|
428
|
-
this._updateSpotlightThumb();
|
|
429
328
|
}
|
|
430
329
|
else {
|
|
431
330
|
this.$el.addClass('hidden');
|
|
432
331
|
}
|
|
433
332
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
this
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
333
|
+
fixElements() {
|
|
334
|
+
const $spotlight = this.$el.find('#thumbnails-spotlight');
|
|
335
|
+
if (this.spotlightHeight) {
|
|
336
|
+
$spotlight.css('height', this.spotlightHeight);
|
|
337
|
+
}
|
|
338
|
+
else {
|
|
339
|
+
$spotlight.remove();
|
|
340
|
+
}
|
|
341
|
+
const $backdrop = this.$el.find('#thumbnails-backdrop');
|
|
342
|
+
if (this.backdropHeight) {
|
|
343
|
+
$backdrop.css('height', this.backdropHeight);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
$backdrop.remove();
|
|
347
|
+
}
|
|
348
|
+
this.mount();
|
|
349
|
+
}
|
|
350
|
+
get shouldRender() {
|
|
351
|
+
return (this.options.thumbnails &&
|
|
352
|
+
this.options.thumbnails.sprite &&
|
|
353
|
+
this.options.thumbnails.vtt);
|
|
354
|
+
}
|
|
355
|
+
render() {
|
|
356
|
+
if (!this.shouldRender) {
|
|
357
|
+
return this;
|
|
358
|
+
}
|
|
359
|
+
this.$el.html(Thumbnails.template());
|
|
445
360
|
this.$el.addClass('hidden');
|
|
446
|
-
this
|
|
361
|
+
return this;
|
|
447
362
|
}
|
|
448
363
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/plugins/thumbnails/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAW3F"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function loadImageDimensions(url) {
|
|
2
|
+
return new Promise((resolve, reject) => {
|
|
3
|
+
const img = new Image();
|
|
4
|
+
img.src = url;
|
|
5
|
+
img.onload = () => {
|
|
6
|
+
resolve({ width: img.width, height: img.height });
|
|
7
|
+
};
|
|
8
|
+
img.onerror = () => {
|
|
9
|
+
reject(new Error('Failed to load image'));
|
|
10
|
+
};
|
|
11
|
+
});
|
|
12
|
+
}
|