@vidispine/vdt-videojs 23.1.0 → 23.2.0-pre.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.css +1 -456
- package/dist/index.js +1915 -342
- package/dist/index.umd.cjs +460 -0
- package/package.json +24 -22
- package/deprecated-app/README.md +0 -21
- package/deprecated-app/index.html +0 -298
- package/deprecated-app/index.js +0 -614
- package/deprecated-app/samples/birds-es.vvt +0 -53
- package/deprecated-app/samples/birds.vvt +0 -53
- package/deprecated-app/tests.js +0 -365
- package/dist/index.es.js +0 -335
- package/dist/index.umd.js +0 -351
- package/rollup.config.js +0 -58
- package/screenshot.png +0 -0
- package/src/constants.js +0 -51
- package/src/externalControls.js +0 -318
- package/src/helpOverlay.js +0 -110
- package/src/helpers.js +0 -39
- package/src/index.js +0 -7
- package/src/index.stories.mdx +0 -52
- package/src/menu.api.stories.mdx +0 -53
- package/src/menu.js +0 -373
- package/src/menu.usage.stories.mdx +0 -78
- package/src/osd.js +0 -105
- package/src/player.api.stories.mdx +0 -114
- package/src/player.js +0 -1029
- package/src/player.scss +0 -604
- package/src/player.usage.stories.mdx +0 -62
- package/src/seekBar.js +0 -904
- package/src/timeCodeDisplay.js +0 -274
- package/src/videoTime.js +0 -66
package/src/seekBar.js
DELETED
|
@@ -1,904 +0,0 @@
|
|
|
1
|
-
/* eslint-disable max-len */
|
|
2
|
-
/* eslint-disable no-multi-assign */
|
|
3
|
-
/* eslint-disable no-unused-expressions */
|
|
4
|
-
/* eslint-disable no-param-reassign */
|
|
5
|
-
/* eslint-disable max-classes-per-file */
|
|
6
|
-
|
|
7
|
-
import mousetrap from 'mousetrap';
|
|
8
|
-
|
|
9
|
-
import { classNames, colors } from './constants';
|
|
10
|
-
import { truncate, sanitize, reactiveSetters } from './helpers';
|
|
11
|
-
|
|
12
|
-
const constants = Object.freeze({
|
|
13
|
-
className: 'vdt-seekbar',
|
|
14
|
-
defaults: {
|
|
15
|
-
bgColor: '#fafafa',
|
|
16
|
-
fillColor: '#f00',
|
|
17
|
-
regionColor: '#f6e58d', // yellow
|
|
18
|
-
markerWidth: 16,
|
|
19
|
-
regionAccentColor: '#00a8ff',
|
|
20
|
-
transitionTime: 300, // ms
|
|
21
|
-
},
|
|
22
|
-
markerTypes: {
|
|
23
|
-
in: 'in',
|
|
24
|
-
out: 'out',
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
const CSS = (bgColor, fillColor) => `
|
|
29
|
-
.${constants.className}{
|
|
30
|
-
user-select: none;
|
|
31
|
-
background: ${colors.light};
|
|
32
|
-
height: 6px;
|
|
33
|
-
width: 100%;
|
|
34
|
-
position: relative;
|
|
35
|
-
cursor: pointer;
|
|
36
|
-
z-index: 5;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
.${classNames.onDark}.${constants.className}{
|
|
40
|
-
background: ${colors.gray};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
.fill, .load-indicator{
|
|
44
|
-
height: 100%;
|
|
45
|
-
display: inline-block;
|
|
46
|
-
position: absolute;
|
|
47
|
-
left: 0px;
|
|
48
|
-
top: 0px;
|
|
49
|
-
background: ${fillColor};
|
|
50
|
-
transition: width ${constants.defaults.transitionTime}ms linear;
|
|
51
|
-
z-index: 2;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
.load-indicator{
|
|
55
|
-
background: #e1e1e1;
|
|
56
|
-
z-index: 1;
|
|
57
|
-
opacity: .2;
|
|
58
|
-
transition: none;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.${classNames.onDark}.${constants.className} .load-indicator{
|
|
62
|
-
background: white;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.${constants.className}.dragging .fill{
|
|
66
|
-
transition: none;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
.marker, .region{
|
|
71
|
-
position: absolute;
|
|
72
|
-
top: 0px;
|
|
73
|
-
height: 100%;
|
|
74
|
-
background: cyan;
|
|
75
|
-
width: 1px;
|
|
76
|
-
display: inline-block;
|
|
77
|
-
z-index: 4;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
.marker{
|
|
81
|
-
transition: opacity ${constants.defaults.transitionTime}ms linear;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
.marker{ display: none; }
|
|
85
|
-
.marker.active{ display: initial; }
|
|
86
|
-
|
|
87
|
-
.marker .handle{
|
|
88
|
-
position: absolute;
|
|
89
|
-
bottom: 10px; right: 0px;
|
|
90
|
-
width: ${constants.defaults.markerWidth}px;
|
|
91
|
-
height: 28px;
|
|
92
|
-
background: #999;
|
|
93
|
-
border-radius: 3px;
|
|
94
|
-
cursor: col-resize;
|
|
95
|
-
z-index: 3;
|
|
96
|
-
box-sizing: border-box;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.marker.regional .handle{
|
|
100
|
-
background: ${constants.defaults.regionAccentColor};
|
|
101
|
-
box-shadow: -2px 1px 1px rgba(0,0,0,.1);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.marker.regional.in .handle{
|
|
105
|
-
box-shadow: 2px 1px 1px rgba(0,0,0,.1);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
.marker.in .handle{
|
|
109
|
-
border-bottom-left-radius: 40px;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.marker.out .handle{
|
|
113
|
-
left: 1px;
|
|
114
|
-
right: initial;
|
|
115
|
-
border-bottom-right-radius: 20px;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.handle:active{
|
|
119
|
-
background: ${constants.defaults.regionColor};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
.region{
|
|
123
|
-
position: absolute;
|
|
124
|
-
width: auto;
|
|
125
|
-
background: ${constants.defaults.regionColor};
|
|
126
|
-
opacity: .7;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
.region.nav{
|
|
130
|
-
background: transparent;
|
|
131
|
-
opacity: 1;
|
|
132
|
-
border-left: 1px solid cyan;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
.region.nav .bubble,
|
|
136
|
-
.region.nav:after{
|
|
137
|
-
content: " ";
|
|
138
|
-
height: 16px;
|
|
139
|
-
bottom: 10px;
|
|
140
|
-
width: 16px;
|
|
141
|
-
border-radius: 16px;
|
|
142
|
-
background: ${constants.defaults.regionAccentColor};
|
|
143
|
-
position: absolute;
|
|
144
|
-
margin-left: -8px;
|
|
145
|
-
transition: transform 200ms ease-out;
|
|
146
|
-
transform: translate3d(0px, 0px, 0px);
|
|
147
|
-
transform-origin: bottom center;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
.region.nav .popout{
|
|
151
|
-
width: 160px;
|
|
152
|
-
z-index: 4;
|
|
153
|
-
background: rgba(28, 28, 28, .7);
|
|
154
|
-
color: white;
|
|
155
|
-
font-size: 14px;
|
|
156
|
-
min-height: 100px;
|
|
157
|
-
bottom: 16px;
|
|
158
|
-
left: -80px;
|
|
159
|
-
position: absolute;
|
|
160
|
-
border-radius: 8px;
|
|
161
|
-
-webkit-backdrop-filter: blur(10px);
|
|
162
|
-
font-family: Arial, Helvetica, sans-serif;
|
|
163
|
-
overflow: hidden;
|
|
164
|
-
opacity: 0;
|
|
165
|
-
visibility: hidden;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
.region.nav.right-aligned .popout{
|
|
169
|
-
right: 0px;
|
|
170
|
-
left: initial;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
.region.nav.left-aligned .popout{
|
|
174
|
-
left: -32px;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
.region.nav .popout .title{
|
|
178
|
-
padding: 4px 14px;
|
|
179
|
-
text-align: center;
|
|
180
|
-
color: black;
|
|
181
|
-
background: white;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
.region.nav .popout .annotation{
|
|
185
|
-
padding: 8px 12px;
|
|
186
|
-
font-weight: light;
|
|
187
|
-
color: rgba(255,255,255,.9);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
.region.nav:after{
|
|
191
|
-
width: 1px;
|
|
192
|
-
left: 8px;
|
|
193
|
-
height: 10px;
|
|
194
|
-
bottom: 0px;
|
|
195
|
-
background: #00a8ff;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
.region.nav:hover{
|
|
199
|
-
background: rgba(28, 28, 28, .5);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
.region.nav.has-popout:hover .bubble{
|
|
203
|
-
background: rgba(28, 28, 28, .8);
|
|
204
|
-
opacity: 1;
|
|
205
|
-
z-index: 3;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.region.nav:hover:after,
|
|
209
|
-
.region.nav:hover:before{
|
|
210
|
-
background: rgba(28, 28, 28, .8);
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
.region.nav:hover .popout{
|
|
214
|
-
opacity: 1;
|
|
215
|
-
visibility: visible;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
.${constants.className}.dragging .bubble{
|
|
219
|
-
transform: scale3d(.75,.75,.75);
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
.${constants.className}.hide-markers .marker{
|
|
223
|
-
opacity: 0;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
`;
|
|
227
|
-
|
|
228
|
-
export class Region {
|
|
229
|
-
constructor(props) {
|
|
230
|
-
const regionDefaultProps = {
|
|
231
|
-
in: null,
|
|
232
|
-
out: null,
|
|
233
|
-
title: '',
|
|
234
|
-
annotation: '',
|
|
235
|
-
el: null, // can store a
|
|
236
|
-
};
|
|
237
|
-
|
|
238
|
-
Object.assign(this, regionDefaultProps, props); // apply props
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export default class SeekBar {
|
|
243
|
-
constructor(props) {
|
|
244
|
-
this.props = props;
|
|
245
|
-
reactiveSetters.apply(this); // ability to handle new props via setters
|
|
246
|
-
|
|
247
|
-
this.container = document.createElement('div');
|
|
248
|
-
this.container.className = `${constants.className}-wrapper`;
|
|
249
|
-
|
|
250
|
-
this.state = {
|
|
251
|
-
in: null, // in seconds from 0
|
|
252
|
-
out: null,
|
|
253
|
-
markersEnabled: this.props.markersEnabled === undefined ? true : this.props.markersEnabled,
|
|
254
|
-
regions: [], // array with regions providing {in: 0, out: 12} and possibly more
|
|
255
|
-
dragging: false,
|
|
256
|
-
};
|
|
257
|
-
|
|
258
|
-
this.markers = {};
|
|
259
|
-
|
|
260
|
-
if (this.props.target) {
|
|
261
|
-
this.shadow = this.container.attachShadow({ mode: 'open' });
|
|
262
|
-
this.props.target.appendChild(this.container);
|
|
263
|
-
// attach styles
|
|
264
|
-
this.style = document.createElement('style');
|
|
265
|
-
this.style.type = 'text/css';
|
|
266
|
-
|
|
267
|
-
// colors from props
|
|
268
|
-
const bgColor = this.props.bgColor ? this.props.bgColor : constants.defaults.bgColor;
|
|
269
|
-
const fillColor = this.props.fillColor ? this.props.fillColor : constants.defaults.fillColor;
|
|
270
|
-
|
|
271
|
-
this.style.innerHTML = CSS(sanitize(bgColor), sanitize(fillColor));
|
|
272
|
-
this.shadow.appendChild(this.style);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
this.bindEvents();
|
|
276
|
-
this.draw();
|
|
277
|
-
this.bindHotkeys();
|
|
278
|
-
|
|
279
|
-
/*
|
|
280
|
-
run this for "in" and "out" points set in constructor
|
|
281
|
-
when the player metadata is ready (duration data is there)
|
|
282
|
-
*/
|
|
283
|
-
const { player } = this.props;
|
|
284
|
-
if (player) {
|
|
285
|
-
player.videojs.on('loadedmetadata', () => {
|
|
286
|
-
this.handleNewProps(this.props);
|
|
287
|
-
});
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/*
|
|
292
|
-
Video.js seekbar works a bit differently - it
|
|
293
|
-
sets an interval and checks the playback position
|
|
294
|
-
very frequently, could do it that way -
|
|
295
|
-
then could use requestAnimationFrame and
|
|
296
|
-
get a smoother animation at more expense
|
|
297
|
-
*/
|
|
298
|
-
bindEvents() {
|
|
299
|
-
// bind to vdt player instance
|
|
300
|
-
if (this.props.player) {
|
|
301
|
-
const { player } = this.props;
|
|
302
|
-
player.videojs.on('timeupdate', () => {
|
|
303
|
-
const progressFraction = player.videojs.currentTime() / player.videojs.duration();
|
|
304
|
-
this.fill.style.width = `${100 * progressFraction}%`;
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
player.videojs.on('progress', () => {
|
|
308
|
-
this.percentLoaded = player.percentLoaded;
|
|
309
|
-
});
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
bindHotkeys() {
|
|
314
|
-
this.bindings = [];
|
|
315
|
-
|
|
316
|
-
// bindings related to player
|
|
317
|
-
if (this.props.player) {
|
|
318
|
-
const { player } = this.props;
|
|
319
|
-
this.bindings = [
|
|
320
|
-
{
|
|
321
|
-
key: ['i'],
|
|
322
|
-
method: () => {
|
|
323
|
-
if (this.markersEnabled) this.in = player.videojs.currentTime();
|
|
324
|
-
},
|
|
325
|
-
},
|
|
326
|
-
{
|
|
327
|
-
key: ['o'],
|
|
328
|
-
method: () => {
|
|
329
|
-
if (this.markersEnabled) this.out = player.videojs.currentTime();
|
|
330
|
-
},
|
|
331
|
-
},
|
|
332
|
-
{
|
|
333
|
-
key: ['shift+i'],
|
|
334
|
-
method: () => {
|
|
335
|
-
if (this.markersEnabled) this.navigateToIn();
|
|
336
|
-
},
|
|
337
|
-
},
|
|
338
|
-
{
|
|
339
|
-
key: ['shift+o'],
|
|
340
|
-
method: () => {
|
|
341
|
-
if (this.markersEnabled) this.navigateToOut();
|
|
342
|
-
},
|
|
343
|
-
},
|
|
344
|
-
];
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (this.props.player) {
|
|
348
|
-
const { player } = this.props;
|
|
349
|
-
mousetrap.bind('i', () => {
|
|
350
|
-
if (this.markersEnabled) this.in = player.videojs.currentTime();
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
mousetrap.bind('o', () => {
|
|
354
|
-
if (this.markersEnabled) this.out = player.videojs.currentTime();
|
|
355
|
-
});
|
|
356
|
-
|
|
357
|
-
mousetrap.bind('shift+i', () => {
|
|
358
|
-
if (this.markersEnabled) this.navigateToIn();
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
mousetrap.bind('shift+o', () => {
|
|
362
|
-
if (this.markersEnabled) this.navigateToOut();
|
|
363
|
-
});
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// bind
|
|
367
|
-
this.bindings.forEach((binding) => {
|
|
368
|
-
mousetrap.bind(binding.key, (e) => {
|
|
369
|
-
binding.method(e);
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
unbindHotKeys() {
|
|
375
|
-
this.bindings &&
|
|
376
|
-
this.bindings.forEach((binding) => {
|
|
377
|
-
mousetrap.unbind(binding.key);
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
handleSeek(event) {
|
|
382
|
-
// check if target is a region
|
|
383
|
-
if (this.props.player) {
|
|
384
|
-
const { player } = this.props;
|
|
385
|
-
if (this.state.regions) {
|
|
386
|
-
let clickedOnRegion = false;
|
|
387
|
-
let preventSeek = false; // prevent seek from happening
|
|
388
|
-
this.state.regions.forEach((region) => {
|
|
389
|
-
if (region.el.contains(event.target)) {
|
|
390
|
-
// player.seekTo(region.in);
|
|
391
|
-
|
|
392
|
-
// callback for clicking on a region, if callback returns false, no seek happens
|
|
393
|
-
// TODO - make a convention of returning true / false to break from seek
|
|
394
|
-
if (this.props.onRegionClick) {
|
|
395
|
-
preventSeek = this.props.onRegionClick(region);
|
|
396
|
-
if (!preventSeek) {
|
|
397
|
-
player.seekTo(region.in);
|
|
398
|
-
}
|
|
399
|
-
} else {
|
|
400
|
-
player.seekTo(region.in);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
clickedOnRegion = true;
|
|
404
|
-
}
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
/*
|
|
408
|
-
exit from method, TODO - implement foreach
|
|
409
|
-
for breaking more efficiently
|
|
410
|
-
*/
|
|
411
|
-
|
|
412
|
-
if (clickedOnRegion) return;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const seekFractionX =
|
|
416
|
-
(event.clientX - this.bar.getBoundingClientRect().left) / this.bar.offsetWidth;
|
|
417
|
-
// -> 0.34732
|
|
418
|
-
|
|
419
|
-
const timeToSeek = seekFractionX * player.videojs.duration();
|
|
420
|
-
// -> something like 32.2323 (in seconds)
|
|
421
|
-
player.seekTo(timeToSeek);
|
|
422
|
-
} // TODO - make a callback for more generic use case (non-vdt player)
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
handleSeekDrag(mouseDownEvent) {
|
|
426
|
-
const barOffset = this.bar.getBoundingClientRect().left; // px from page left side
|
|
427
|
-
const barWidth = this.bar.getBoundingClientRect().width; // seek bar width
|
|
428
|
-
const duration = this.props.player.videojs.duration(); // in secondsā
|
|
429
|
-
let left;
|
|
430
|
-
|
|
431
|
-
const drag = (e) => {
|
|
432
|
-
left = e.clientX - barOffset;
|
|
433
|
-
const pxDragged = Math.abs(mouseDownEvent.clientX - e.clientX);
|
|
434
|
-
|
|
435
|
-
if (left >= 0 && left < barWidth && pxDragged > 1) {
|
|
436
|
-
// prevent out-of-bounds
|
|
437
|
-
this.dragging = true;
|
|
438
|
-
const fractionFromStart = parseFloat(left) / barWidth;
|
|
439
|
-
const seconds = fractionFromStart * duration;
|
|
440
|
-
this.props.player.videojs.currentTime(seconds);
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
e.preventDefault();
|
|
444
|
-
};
|
|
445
|
-
|
|
446
|
-
const stopDrag = (e) => {
|
|
447
|
-
this.dragging = false;
|
|
448
|
-
|
|
449
|
-
// remove listeners
|
|
450
|
-
document.removeEventListener('mousemove', drag, false);
|
|
451
|
-
document.removeEventListener('mouseup', stopDrag, false);
|
|
452
|
-
|
|
453
|
-
const fractionFromStart = parseFloat(left) / barWidth;
|
|
454
|
-
const seconds = fractionFromStart * duration;
|
|
455
|
-
this.props.player.videojs.currentTime(seconds);
|
|
456
|
-
|
|
457
|
-
e.stopPropagation();
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
// add listeners
|
|
461
|
-
document.addEventListener('mousemove', drag, false);
|
|
462
|
-
document.addEventListener('mouseup', stopDrag, false);
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
draw() {
|
|
466
|
-
this.bar = document.createElement('div');
|
|
467
|
-
this.bar.className = constants.className;
|
|
468
|
-
this.bar.onclick = (e) => {
|
|
469
|
-
if (!e.target.classList.contains('marker') && !e.target.classList.contains('handle')) {
|
|
470
|
-
this.handleSeek(e);
|
|
471
|
-
}
|
|
472
|
-
};
|
|
473
|
-
|
|
474
|
-
// handle drag
|
|
475
|
-
|
|
476
|
-
this.bar.addEventListener(
|
|
477
|
-
'mousedown',
|
|
478
|
-
(e) => {
|
|
479
|
-
e.preventDefault(); // prevents selection cursor
|
|
480
|
-
|
|
481
|
-
if (!this.dragging) {
|
|
482
|
-
// don't attach listeners if something is already dragging
|
|
483
|
-
this.handleSeekDrag(e);
|
|
484
|
-
}
|
|
485
|
-
},
|
|
486
|
-
false,
|
|
487
|
-
);
|
|
488
|
-
|
|
489
|
-
this.fill = document.createElement('div');
|
|
490
|
-
this.fill.className = 'fill';
|
|
491
|
-
|
|
492
|
-
this.loadIndicator = document.createElement('div');
|
|
493
|
-
this.loadIndicator.className = 'load-indicator';
|
|
494
|
-
|
|
495
|
-
this.region = document.createElement('div');
|
|
496
|
-
this.region.className = 'region';
|
|
497
|
-
|
|
498
|
-
this.bar.appendChild(this.fill);
|
|
499
|
-
this.bar.appendChild(this.loadIndicator);
|
|
500
|
-
this.bar.appendChild(this.region);
|
|
501
|
-
this.shadow.appendChild(this.bar);
|
|
502
|
-
|
|
503
|
-
this.drawMarkers();
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
drawMarkers() {
|
|
507
|
-
// TODO - add touch events
|
|
508
|
-
if (this.markersEnabled) {
|
|
509
|
-
const markerIn = (this.markers[constants.markerTypes.in] = document.createElement('div'));
|
|
510
|
-
const markerOut = (this.markers[constants.markerTypes.out] = document.createElement('div'));
|
|
511
|
-
markerIn.className = `marker ${constants.markerTypes.in}`;
|
|
512
|
-
markerOut.className = `marker ${constants.markerTypes.out}`;
|
|
513
|
-
|
|
514
|
-
const handleIn = document.createElement('div');
|
|
515
|
-
const handleOut = document.createElement('div');
|
|
516
|
-
handleIn.className = handleOut.className = 'handle';
|
|
517
|
-
|
|
518
|
-
handleIn.addEventListener(
|
|
519
|
-
'mousedown',
|
|
520
|
-
(e) => {
|
|
521
|
-
e.preventDefault(); // prevents selection cursor
|
|
522
|
-
e.stopPropagation(); // prevent triggering seek bar drag
|
|
523
|
-
|
|
524
|
-
this.handleMarkerDrag(markerIn, constants.markerTypes.in, e);
|
|
525
|
-
},
|
|
526
|
-
false,
|
|
527
|
-
);
|
|
528
|
-
|
|
529
|
-
handleOut.addEventListener(
|
|
530
|
-
'mousedown',
|
|
531
|
-
(e) => {
|
|
532
|
-
e.preventDefault(); // prevents selection cursor
|
|
533
|
-
e.stopPropagation(); // prevent triggering seek bar drag
|
|
534
|
-
|
|
535
|
-
this.handleMarkerDrag(markerOut, constants.markerTypes.out, e);
|
|
536
|
-
},
|
|
537
|
-
false,
|
|
538
|
-
);
|
|
539
|
-
|
|
540
|
-
markerIn.appendChild(handleIn);
|
|
541
|
-
markerOut.appendChild(handleOut);
|
|
542
|
-
|
|
543
|
-
this.bar.appendChild(markerIn);
|
|
544
|
-
this.bar.appendChild(markerOut);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
updateMarkers(marker) {
|
|
549
|
-
this.markers[marker.type].style.left = `${marker.value * 100}%`;
|
|
550
|
-
this.markers[marker.type].classList.add('active');
|
|
551
|
-
|
|
552
|
-
// check if marker mathes a region and add a classname to it
|
|
553
|
-
if (this.state.regions) {
|
|
554
|
-
// check if any region contains in / out matching value
|
|
555
|
-
const matchingRegions = [];
|
|
556
|
-
const isRegional = matchingRegions.length > 0;
|
|
557
|
-
if (isRegional) {
|
|
558
|
-
this.markers[marker.type].classList.add('regional');
|
|
559
|
-
} else {
|
|
560
|
-
this.markers[marker.type].classList.remove('regional');
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
this.drawInAndOutRegion();
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
drawInAndOutRegion() {
|
|
568
|
-
// triggered after drawMarkers if in + out is set
|
|
569
|
-
if (this.state.in !== null && this.state.out !== null) {
|
|
570
|
-
this.region.style.left = this.markers[constants.markerTypes.in].style.left;
|
|
571
|
-
this.region.style.right = `${
|
|
572
|
-
100 - parseFloat(this.markers[constants.markerTypes.out].style.left.replace('%', ''))
|
|
573
|
-
}%`;
|
|
574
|
-
} else {
|
|
575
|
-
// remove region div or hide it
|
|
576
|
-
}
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
/*
|
|
580
|
-
TODO - handle touch events,
|
|
581
|
-
but find out if there is a mobile use
|
|
582
|
-
case before that
|
|
583
|
-
*/
|
|
584
|
-
handleMarkerDrag(markerNode, type, mouseDownEvent) {
|
|
585
|
-
// marker node, marker type (constants.markerTypes)
|
|
586
|
-
const barOffset = this.bar.getBoundingClientRect().left; // px from page left side
|
|
587
|
-
const barWidth = this.bar.getBoundingClientRect().width; // seek bar width
|
|
588
|
-
const duration = this.props.player.videojs.duration(); // in secondsā
|
|
589
|
-
let dragHappened = false;
|
|
590
|
-
|
|
591
|
-
const drag = (e) => {
|
|
592
|
-
const pxDragged = Math.abs(mouseDownEvent.clientX - e.clientX);
|
|
593
|
-
const markerOffset =
|
|
594
|
-
type === constants.markerTypes.in
|
|
595
|
-
? constants.defaults.markerWidth / 2
|
|
596
|
-
: -(constants.defaults.markerWidth / 2);
|
|
597
|
-
const left = e.clientX - barOffset + markerOffset;
|
|
598
|
-
|
|
599
|
-
if (left >= 0 && left < barWidth && pxDragged > 1) {
|
|
600
|
-
// prevent out-of-bounds
|
|
601
|
-
dragHappened = this.dragging = true;
|
|
602
|
-
markerNode.style.left = `${left}px`;
|
|
603
|
-
markerNode.classList.add('active');
|
|
604
|
-
|
|
605
|
-
const fractionFromStart =
|
|
606
|
-
parseFloat(markerNode.getBoundingClientRect().left - barOffset) / barWidth;
|
|
607
|
-
const seconds = fractionFromStart * duration;
|
|
608
|
-
this.props.player.videojs.currentTime(seconds);
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
e.preventDefault();
|
|
612
|
-
};
|
|
613
|
-
|
|
614
|
-
const stopDrag = (e) => {
|
|
615
|
-
this.dragging = false;
|
|
616
|
-
|
|
617
|
-
// remove listeners
|
|
618
|
-
document.removeEventListener('mousemove', drag, false);
|
|
619
|
-
document.removeEventListener('mouseup', stopDrag, false);
|
|
620
|
-
|
|
621
|
-
const fractionFromStart =
|
|
622
|
-
parseFloat(markerNode.getBoundingClientRect().left - barOffset) / barWidth;
|
|
623
|
-
const seconds = fractionFromStart * duration;
|
|
624
|
-
this[type] = seconds;
|
|
625
|
-
this.props.player.videojs.currentTime(seconds);
|
|
626
|
-
|
|
627
|
-
/*
|
|
628
|
-
if drag did not take place,
|
|
629
|
-
user just clicked the marker -
|
|
630
|
-
jump to the time of marker in player
|
|
631
|
-
*/
|
|
632
|
-
if (!dragHappened) {
|
|
633
|
-
if (type === constants.markerTypes.in) {
|
|
634
|
-
this.navigateToIn();
|
|
635
|
-
} else {
|
|
636
|
-
this.navigateToOut();
|
|
637
|
-
}
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
this.props.onDragFinish && this.props.onDragFinish({ type, seconds });
|
|
641
|
-
e.stopPropagation();
|
|
642
|
-
};
|
|
643
|
-
|
|
644
|
-
// add listeners
|
|
645
|
-
document.addEventListener('mousemove', drag, false);
|
|
646
|
-
document.addEventListener('mouseup', stopDrag, false);
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
/*
|
|
650
|
-
hide dead markers (for state parity) or ones that
|
|
651
|
-
make no sense (like out point before in point)
|
|
652
|
-
*/
|
|
653
|
-
cleanupMarkers() {
|
|
654
|
-
let removedMarker = false;
|
|
655
|
-
if (this.state.in === null) {
|
|
656
|
-
this.markers[constants.markerTypes.in].classList.remove('active');
|
|
657
|
-
removedMarker = true;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
if (this.state.out === null) {
|
|
661
|
-
this.markers[constants.markerTypes.out].classList.remove('active');
|
|
662
|
-
removedMarker = true;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
if (removedMarker) {
|
|
666
|
-
// if a marker was removed, region no longer is valid
|
|
667
|
-
this.region.style.left = 'initial';
|
|
668
|
-
this.region.style.right = 'initial';
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
clearMarkers() {
|
|
673
|
-
this.in = null;
|
|
674
|
-
this.out = null;
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
// utilizes player instance from props
|
|
678
|
-
setInFromCurrent() {
|
|
679
|
-
this.in = this.props.player.videojs.currentTime();
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
setOutFromCurrent() {
|
|
683
|
-
this.out = this.props.player.videojs.currentTime();
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
navigateToIn() {
|
|
687
|
-
const { player } = this.props;
|
|
688
|
-
player.seekTo(this.in);
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
navigateToOut() {
|
|
692
|
-
const { player } = this.props;
|
|
693
|
-
player.seekTo(this.out);
|
|
694
|
-
}
|
|
695
|
-
|
|
696
|
-
// setters and getters
|
|
697
|
-
set in(val) {
|
|
698
|
-
if (!this.state.markersEnabled) return;
|
|
699
|
-
|
|
700
|
-
this.state.in = val;
|
|
701
|
-
|
|
702
|
-
if (val !== null && val !== undefined) {
|
|
703
|
-
const { player } = this.props;
|
|
704
|
-
this.updateMarkers({
|
|
705
|
-
type: constants.markerTypes.in,
|
|
706
|
-
value: val / player.videojs.duration(),
|
|
707
|
-
});
|
|
708
|
-
|
|
709
|
-
// make sure in point is not set after out point
|
|
710
|
-
if (this.state.out !== null && val > this.state.out) {
|
|
711
|
-
this.out = null;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
this.props.onIn && this.props.onIn(this.state.in);
|
|
715
|
-
} else {
|
|
716
|
-
this.cleanupMarkers();
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
get in() {
|
|
721
|
-
return this.state.in;
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
set out(val) {
|
|
725
|
-
if (!this.state.markersEnabled) return;
|
|
726
|
-
|
|
727
|
-
this.state.out = val;
|
|
728
|
-
|
|
729
|
-
if (val !== null && val !== undefined) {
|
|
730
|
-
const { player } = this.props;
|
|
731
|
-
this.updateMarkers({
|
|
732
|
-
type: constants.markerTypes.out,
|
|
733
|
-
value: val / player.videojs.duration(),
|
|
734
|
-
});
|
|
735
|
-
|
|
736
|
-
// make sure out point is not set before in point
|
|
737
|
-
if (this.state.in != null && val < this.state.in) {
|
|
738
|
-
this.in = null;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
this.props.onOut && this.props.onOut(this.state.out);
|
|
742
|
-
} else {
|
|
743
|
-
this.cleanupMarkers();
|
|
744
|
-
}
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
get out() {
|
|
748
|
-
return this.state.out;
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
get markersEnabled() {
|
|
752
|
-
return this.state.markersEnabled;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
set markersEnabled(bool) {
|
|
756
|
-
if (typeof bool === 'boolean') {
|
|
757
|
-
if (!bool) {
|
|
758
|
-
this.clearMarkers();
|
|
759
|
-
this.state.markersEnabled = false;
|
|
760
|
-
} else {
|
|
761
|
-
!this.markersEnabled && this.drawMarkers();
|
|
762
|
-
this.state.markersEnabled = true;
|
|
763
|
-
}
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
set regions(regionArray) {
|
|
768
|
-
// different than i / o markers
|
|
769
|
-
if (this.state.regions.length < 1) {
|
|
770
|
-
// first time add
|
|
771
|
-
regionArray &&
|
|
772
|
-
regionArray.forEach((region) => {
|
|
773
|
-
this.addRegion(region);
|
|
774
|
-
});
|
|
775
|
-
} else {
|
|
776
|
-
// state redraw
|
|
777
|
-
this.state.regions.forEach((region, index) => {
|
|
778
|
-
this.removeRegion(index);
|
|
779
|
-
});
|
|
780
|
-
|
|
781
|
-
this.state.regions = [];
|
|
782
|
-
this.regions = regionArray; // run again
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
get regions() {
|
|
787
|
-
return this.state.regions;
|
|
788
|
-
}
|
|
789
|
-
|
|
790
|
-
set dragging(bool) {
|
|
791
|
-
// update only if worth updating
|
|
792
|
-
if (bool && !this.state.dragging) {
|
|
793
|
-
this.state.dragging = true;
|
|
794
|
-
this.bar.classList.add('dragging');
|
|
795
|
-
} else if (!bool && this.state.dragging) {
|
|
796
|
-
this.state.dragging = false;
|
|
797
|
-
this.bar.classList.remove('dragging');
|
|
798
|
-
}
|
|
799
|
-
}
|
|
800
|
-
|
|
801
|
-
get dragging() {
|
|
802
|
-
return this.state.dragging;
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
addRegion(regionObj) {
|
|
806
|
-
if (regionObj.constructor.name === Region.name) {
|
|
807
|
-
this.state.regions.push(regionObj);
|
|
808
|
-
this.drawRegion(regionObj);
|
|
809
|
-
} else {
|
|
810
|
-
// try to construct region if has accepted properties
|
|
811
|
-
this.addRegion(new Region(regionObj));
|
|
812
|
-
}
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
removeRegion(param) {
|
|
816
|
-
if (typeof param === typeof Region) {
|
|
817
|
-
// TODO - implement this - find by region obj
|
|
818
|
-
// find said matching region
|
|
819
|
-
} else {
|
|
820
|
-
// it's an index
|
|
821
|
-
this.state.regions[param].el.remove();
|
|
822
|
-
this.state.regions[param] = null;
|
|
823
|
-
delete this.state.regions[param];
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
drawRegion(region) {
|
|
828
|
-
const duration = this.props.player.videojs.duration(); // seconds
|
|
829
|
-
const inPoint = region.in;
|
|
830
|
-
|
|
831
|
-
const leftPercentage = (inPoint / duration) * 100;
|
|
832
|
-
const rightPercentage = ((duration - region.out) / duration) * 100;
|
|
833
|
-
|
|
834
|
-
const regionEl = document.createElement('div');
|
|
835
|
-
regionEl.className = 'region nav ';
|
|
836
|
-
regionEl.innerHTML = '<div class="bubble"></div>';
|
|
837
|
-
|
|
838
|
-
if (region.title && region.annotation) {
|
|
839
|
-
let alignmentClassName = '';
|
|
840
|
-
if (leftPercentage > 70) alignmentClassName = 'right-aligned';
|
|
841
|
-
if (leftPercentage < 30) alignmentClassName = 'left-aligned';
|
|
842
|
-
regionEl.className += ` has-popout ${alignmentClassName}`;
|
|
843
|
-
|
|
844
|
-
const popout = document.createElement('div');
|
|
845
|
-
|
|
846
|
-
popout.className = 'popout';
|
|
847
|
-
popout.innerHTML = `
|
|
848
|
-
<div class="title">
|
|
849
|
-
${sanitize(region.title)}
|
|
850
|
-
</div>
|
|
851
|
-
<div class="annotation">
|
|
852
|
-
${truncate(sanitize(region.annotation), 72)}
|
|
853
|
-
</div>
|
|
854
|
-
`;
|
|
855
|
-
|
|
856
|
-
regionEl.appendChild(popout);
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
regionEl.style.left = `${leftPercentage}%`;
|
|
860
|
-
regionEl.style.right = `${rightPercentage}%`;
|
|
861
|
-
|
|
862
|
-
region.el = regionEl;
|
|
863
|
-
|
|
864
|
-
this.bar.appendChild(regionEl);
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
set onDark(bool) {
|
|
868
|
-
if (bool) {
|
|
869
|
-
this.bar.classList.add(classNames.onDark);
|
|
870
|
-
} else {
|
|
871
|
-
this.bar.classList.remove(classNames.onDark);
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
get onDark() {
|
|
876
|
-
return this.container.classList.contains(classNames.onDark);
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
set percentLoaded(val) {
|
|
880
|
-
this.loadIndicator.style.width = `${val}%`;
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
destroy() {
|
|
884
|
-
this.unbindHotKeys();
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
toggleMarkers() {
|
|
888
|
-
if (this.bar.classList.contains('hide-markers')) {
|
|
889
|
-
this.showMarkers();
|
|
890
|
-
} else {
|
|
891
|
-
this.hideMarkers();
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
hideMarkers() {
|
|
896
|
-
this.bar.classList.add('hide-markers');
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
showMarkers() {
|
|
900
|
-
this.bar.classList.remove('hide-markers');
|
|
901
|
-
}
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
SeekBar.Region = Region;
|