@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/player.js
DELETED
|
@@ -1,1029 +0,0 @@
|
|
|
1
|
-
/* eslint-disable no-underscore-dangle */
|
|
2
|
-
/* eslint-disable import/no-named-as-default-member */
|
|
3
|
-
/*
|
|
4
|
-
Eslint rules disabled for lines like these:
|
|
5
|
-
|
|
6
|
-
this.props.target && this.props.target.appendChild(this.videoEl); //validity check before expression
|
|
7
|
-
document.getElementsByClassName('vjs-volume-panel')[0] //rule asks for destructuring
|
|
8
|
-
this.videojs.hasStarted_ //dangling underscore produced by video.js in this case
|
|
9
|
-
*/
|
|
10
|
-
// eslint-disable-next-line max-classes-per-file
|
|
11
|
-
import mousetrap from 'mousetrap';
|
|
12
|
-
import videojs from 'video.js/dist/alt/video.core.min';
|
|
13
|
-
|
|
14
|
-
import constants from './constants';
|
|
15
|
-
import { reactiveSetters } from './helpers';
|
|
16
|
-
import HelpOverlay from './helpOverlay';
|
|
17
|
-
import Menu from './menu';
|
|
18
|
-
import OSD from './osd';
|
|
19
|
-
import SeekBar from './seekBar';
|
|
20
|
-
import TimeCodeDisplay from './timeCodeDisplay';
|
|
21
|
-
import VideoTime from './videoTime';
|
|
22
|
-
|
|
23
|
-
import './player.scss';
|
|
24
|
-
|
|
25
|
-
const correctMimeTypes = {
|
|
26
|
-
'audio/x-aac': 'audio/aac',
|
|
27
|
-
};
|
|
28
|
-
const sanitizeMimeType = (mimeType) => correctMimeTypes[mimeType] || mimeType;
|
|
29
|
-
|
|
30
|
-
export default class Player {
|
|
31
|
-
constructor(props) {
|
|
32
|
-
this.props = props; // save props
|
|
33
|
-
reactiveSetters.apply(this); // ability to handle new props via setters
|
|
34
|
-
|
|
35
|
-
const classNames = [
|
|
36
|
-
// add conditional css classes here for the whole container
|
|
37
|
-
'video-js',
|
|
38
|
-
'vjs-big-play-centered',
|
|
39
|
-
this.props.posterUrl ? constants.classNames.poster : '',
|
|
40
|
-
this.props.subtitles && this.props.subtitles.length > 0 ? constants.classNames.subtitles : '',
|
|
41
|
-
'init', // make player height 0 while no metadata for video size available with css
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
this.externalTimecodes = {
|
|
45
|
-
// either one of these can hold an array of TimeCodeDisplay instances
|
|
46
|
-
duration: [],
|
|
47
|
-
playback: [],
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
this.timeConverter = new VideoTime({
|
|
51
|
-
frameRate: this.props.frameRate || constants.defaults.frameRate,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
/*
|
|
55
|
-
video.js requires a html video to initialize
|
|
56
|
-
*/
|
|
57
|
-
|
|
58
|
-
this.videoEl = document.createElement('video');
|
|
59
|
-
this.videoEl.crossOrigin = this.props.crossOrigin;
|
|
60
|
-
this.videoEl.id = 'vdt-video';
|
|
61
|
-
this.videoEl.className = classNames.join(' ');
|
|
62
|
-
this.videoEl.controls = true; // remove this line for no-controls videojs player
|
|
63
|
-
|
|
64
|
-
// attach sources, sort mp4 on top of source list
|
|
65
|
-
if (this.props.sources) {
|
|
66
|
-
// sort mp4 at the top
|
|
67
|
-
if (this.props.sources.length > 1) {
|
|
68
|
-
this.props.sources.sort((a) => (a.type.indexOf('mp4') > 0 ? 1 : -1));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// construct <source> els
|
|
72
|
-
this.props.sources.forEach((source) => {
|
|
73
|
-
const aSource = document.createElement('source');
|
|
74
|
-
aSource.src = source.src;
|
|
75
|
-
aSource.type = sanitizeMimeType(source.type);
|
|
76
|
-
this.videoEl.appendChild(aSource);
|
|
77
|
-
});
|
|
78
|
-
} // handle else with err later
|
|
79
|
-
|
|
80
|
-
this.videoEl.onloadedmetadata = () => {
|
|
81
|
-
// read-only duration timecode
|
|
82
|
-
if (this.durationTimeCode) {
|
|
83
|
-
this.setDurationTimeCode();
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (this.videojs.el_) this.videojs.el_.classList.remove('init');
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
// if video has not loaded by now, show the player w/ errors
|
|
90
|
-
setTimeout(() => {
|
|
91
|
-
if (this.videojs.el_) this.videojs.el_.classList.remove('init');
|
|
92
|
-
}, 2000);
|
|
93
|
-
|
|
94
|
-
// attach subtitle tracks (these should come in last in the <video /> node)
|
|
95
|
-
if (this.props.subtitles) {
|
|
96
|
-
this.props.subtitles.forEach((sub) => {
|
|
97
|
-
const subtitleTag = document.createElement('track');
|
|
98
|
-
subtitleTag.kind = 'subtitles';
|
|
99
|
-
subtitleTag.srclang = sub.lang;
|
|
100
|
-
subtitleTag.label = sub.label;
|
|
101
|
-
// subtitleTag.default = (index === 0) //can use this later to set a default track
|
|
102
|
-
|
|
103
|
-
if (typeof sub.src === 'string') {
|
|
104
|
-
subtitleTag.src = sub.src; // external file
|
|
105
|
-
} else {
|
|
106
|
-
subtitleTag.src = window.URL.createObjectURL(sub.src); // file url from blob
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
this.videoEl.appendChild(subtitleTag);
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// attach to dom and construct videojs player
|
|
114
|
-
if (this.props.target) this.props.target.appendChild(this.videoEl);
|
|
115
|
-
|
|
116
|
-
const showDefaultTimeDisplay = this.props.timecode !== true;
|
|
117
|
-
const controlsEnabled = this.props.controls !== false;
|
|
118
|
-
const options = this.props.options || {};
|
|
119
|
-
|
|
120
|
-
// eslint-disable-next-line new-cap
|
|
121
|
-
this.videojs = new videojs('vdt-video', {
|
|
122
|
-
responsive: true,
|
|
123
|
-
fluid: true,
|
|
124
|
-
controls: controlsEnabled,
|
|
125
|
-
alwaysCaptureHotkeys: false,
|
|
126
|
-
controlBar: {
|
|
127
|
-
RemainingTimeDisplay: false,
|
|
128
|
-
timeDivider: showDefaultTimeDisplay, // relates to built-in time display
|
|
129
|
-
durationDisplay: showDefaultTimeDisplay,
|
|
130
|
-
currentTimeDisplay: showDefaultTimeDisplay,
|
|
131
|
-
},
|
|
132
|
-
inactivityTimeout: this.audioOnly
|
|
133
|
-
? constants.defaults.audioInactivityTimeout
|
|
134
|
-
: constants.defaults.inactivityTimeout,
|
|
135
|
-
preload: 'auto',
|
|
136
|
-
captionsButton: false,
|
|
137
|
-
subsCapsButton: false,
|
|
138
|
-
subtitlesButton: false,
|
|
139
|
-
textTrackSettings: false,
|
|
140
|
-
html5: {
|
|
141
|
-
nativeTextTracks: false,
|
|
142
|
-
},
|
|
143
|
-
languages: {
|
|
144
|
-
en: {
|
|
145
|
-
'captions off': 'off',
|
|
146
|
-
'subtitles off': 'off',
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
...options,
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
// set the poster
|
|
153
|
-
if (this.props.posterUrl) this.videojs.poster(this.props.posterUrl);
|
|
154
|
-
|
|
155
|
-
// set other props that are passed
|
|
156
|
-
this.handleNewProps(this.props);
|
|
157
|
-
|
|
158
|
-
// lifecycle
|
|
159
|
-
this.videojs.ready(() => {
|
|
160
|
-
this.bindHotkeys();
|
|
161
|
-
this.bindEvents();
|
|
162
|
-
if (this.props.onReady) this.props.onReady(this.videojs);
|
|
163
|
-
|
|
164
|
-
this.restoreSettings();
|
|
165
|
-
this.setupTimeCode();
|
|
166
|
-
this.doEsothericWork();
|
|
167
|
-
this.setupMenu();
|
|
168
|
-
this.setupCustomSeekbar();
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
setupCustomSeekbar() {
|
|
173
|
-
if (this.props.seekBar) {
|
|
174
|
-
// disable the built-in videojs seekbar
|
|
175
|
-
this.videojs.controlBar.progressControl.disable();
|
|
176
|
-
this.videojs.controlBar.progressControl.hide();
|
|
177
|
-
|
|
178
|
-
const target = this.container.getElementsByClassName('vjs-control-bar')[0];
|
|
179
|
-
const props = {
|
|
180
|
-
target,
|
|
181
|
-
player: this,
|
|
182
|
-
onDark: this.props.onDark,
|
|
183
|
-
...this.props.seekBar,
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
this.seekBar = new SeekBar(props);
|
|
187
|
-
|
|
188
|
-
// show & hide markers when controls hide
|
|
189
|
-
this.videojs.on('userinactive', () => {
|
|
190
|
-
if (!this.controlsBelowPlayer) {
|
|
191
|
-
if (this.seekBar) this.seekBar.hideMarkers?.();
|
|
192
|
-
} else {
|
|
193
|
-
this.seekBar?.showMarkers?.();
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
this.videojs.on('useractive', () => {
|
|
198
|
-
if (this.seekBar) this.seekBar.showMarkers();
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
setupTimeCode(externalTimecodeInstance = null) {
|
|
204
|
-
const props = {
|
|
205
|
-
onInput: (e, nodeName, values) => {
|
|
206
|
-
// TODO - check for out of bounds input
|
|
207
|
-
this.seekTo(this.timeConverter.smpteToCurrentTimeFromStart(values));
|
|
208
|
-
},
|
|
209
|
-
onInputFocus: () => {
|
|
210
|
-
/*
|
|
211
|
-
lock the control control bar in place to
|
|
212
|
-
prevent it from unexpectedly animating away while
|
|
213
|
-
doing timecode input
|
|
214
|
-
*/
|
|
215
|
-
this.videojs.options_.inactivityTimeout = 0;
|
|
216
|
-
this.videojs.cache_.inactivityTimeout = 0;
|
|
217
|
-
this.videojs.reportUserActivity();
|
|
218
|
-
|
|
219
|
-
// pause the video, expected behaviour when editing timecode position
|
|
220
|
-
this.videojs.pause();
|
|
221
|
-
},
|
|
222
|
-
onInputBlur: () => {
|
|
223
|
-
this.videojs.options().inactivityTimeout = constants.defaults.inactivityTimeout;
|
|
224
|
-
this.videojs.cache_.inactivityTimeout = constants.defaults.inactivityTimeout;
|
|
225
|
-
},
|
|
226
|
-
};
|
|
227
|
-
|
|
228
|
-
if (externalTimecodeInstance !== null) {
|
|
229
|
-
// setting up an external playback timecode
|
|
230
|
-
// eslint-disable-next-line no-param-reassign
|
|
231
|
-
externalTimecodeInstance.props = Object.assign(externalTimecodeInstance.props, props);
|
|
232
|
-
this.externalTimecodes.playback.push(externalTimecodeInstance);
|
|
233
|
-
} else if (this.props.timecode === true) {
|
|
234
|
-
// player uses timecode
|
|
235
|
-
// construct an in-frame timecode, if controls are enabled
|
|
236
|
-
if (this.props.controls !== false) {
|
|
237
|
-
const target = document.getElementsByClassName('vjs-volume-panel')[0];
|
|
238
|
-
// main timecode representing playhead position
|
|
239
|
-
this.timeCode = new TimeCodeDisplay(Object.assign(props, { target, appendAfter: true }));
|
|
240
|
-
|
|
241
|
-
// read-only timecode representing video duration
|
|
242
|
-
this.durationTimeCode = new TimeCodeDisplay({
|
|
243
|
-
target: this.timeCode.container,
|
|
244
|
-
appendAfter: true,
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (this.timeCode || this.externalTimecodes.playback.length > 0) {
|
|
250
|
-
// subscribe to events
|
|
251
|
-
this.videojs.on('timeupdate', () => {
|
|
252
|
-
this.updateTimeCode();
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
this.videojs.on('play', () => {
|
|
256
|
-
this.updateTimeCodeFrame();
|
|
257
|
-
});
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
updateTimeCode() {
|
|
262
|
-
// HH:MM:SS (the usual)
|
|
263
|
-
const currentSecond = this.videojs.currentTime();
|
|
264
|
-
const props = this.timeConverter.secondsFromStartToSMPTE(currentSecond);
|
|
265
|
-
|
|
266
|
-
if (this.timeCode) this.timeCode.update(props);
|
|
267
|
-
|
|
268
|
-
if (this.externalTimecodes.playback.length > 0) {
|
|
269
|
-
this.externalTimecodes.playback.forEach((timecode) => {
|
|
270
|
-
timecode.update(props);
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
updateTimeCodeFrame() {
|
|
276
|
-
// update the frame in this second -> 00:00:00:XX
|
|
277
|
-
const frame = this.timeConverter.currentFrame(this.videojs.currentTime()); // -> :XX
|
|
278
|
-
|
|
279
|
-
if (this.timeCode) this.timeCode.update({ frame }); // TODO - figure out a way to drop frames
|
|
280
|
-
|
|
281
|
-
if (this.externalTimecodes.playback.length > 0) {
|
|
282
|
-
this.externalTimecodes.playback.forEach((timecode) => {
|
|
283
|
-
timecode.update({ frame });
|
|
284
|
-
});
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// recursive invocation if still playing
|
|
288
|
-
if (!this.videojs.paused()) {
|
|
289
|
-
this.rafID = requestAnimationFrame(() => {
|
|
290
|
-
this.updateTimeCodeFrame();
|
|
291
|
-
});
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
setDurationTimeCode() {
|
|
296
|
-
// happens once
|
|
297
|
-
const durationInSeconds = this.videojs.duration();
|
|
298
|
-
const props = this.timeConverter.secondsFromStartToSMPTE(durationInSeconds);
|
|
299
|
-
props.frame = this.timeConverter.secondsToFrame(durationInSeconds);
|
|
300
|
-
|
|
301
|
-
this.durationTimeCode.update(props);
|
|
302
|
-
this.durationTimeCode.container.classList.add('duration');
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
bindHotkeys() {
|
|
306
|
-
// keyboard shortcuts
|
|
307
|
-
this.bindings = [
|
|
308
|
-
{
|
|
309
|
-
key: ['space', 'k'],
|
|
310
|
-
method: (e) => {
|
|
311
|
-
// prevents page scroll on space, just like YT
|
|
312
|
-
if (e.preventDefault) e.preventDefault();
|
|
313
|
-
this.togglePlayback();
|
|
314
|
-
},
|
|
315
|
-
},
|
|
316
|
-
{
|
|
317
|
-
key: 'j',
|
|
318
|
-
method: () => {
|
|
319
|
-
if (this.videojs.hasStarted_) this.jumpBackward();
|
|
320
|
-
},
|
|
321
|
-
},
|
|
322
|
-
{
|
|
323
|
-
key: 'l',
|
|
324
|
-
method: () => {
|
|
325
|
-
if (this.videojs.hasStarted_) this.jumpForward();
|
|
326
|
-
},
|
|
327
|
-
},
|
|
328
|
-
{
|
|
329
|
-
key: ['.', 'right'],
|
|
330
|
-
method: () => {
|
|
331
|
-
this.frameForward();
|
|
332
|
-
},
|
|
333
|
-
},
|
|
334
|
-
{
|
|
335
|
-
key: [',', 'left'],
|
|
336
|
-
method: () => {
|
|
337
|
-
this.frameBackward();
|
|
338
|
-
},
|
|
339
|
-
},
|
|
340
|
-
{
|
|
341
|
-
key: 'f',
|
|
342
|
-
method: () => {
|
|
343
|
-
this.toggleFullscreen();
|
|
344
|
-
},
|
|
345
|
-
},
|
|
346
|
-
{
|
|
347
|
-
key: 'm',
|
|
348
|
-
method: () => {
|
|
349
|
-
this.toggleMute();
|
|
350
|
-
},
|
|
351
|
-
},
|
|
352
|
-
{
|
|
353
|
-
key: 'up',
|
|
354
|
-
method: (e) => {
|
|
355
|
-
if (e.preventDefault) e.preventDefault(); // prevent page scroll
|
|
356
|
-
this.volumeUp();
|
|
357
|
-
},
|
|
358
|
-
},
|
|
359
|
-
{
|
|
360
|
-
key: 'down',
|
|
361
|
-
method: (e) => {
|
|
362
|
-
if (e.preventDefault) e.preventDefault(); // prevent page scroll
|
|
363
|
-
this.volumeDown();
|
|
364
|
-
},
|
|
365
|
-
},
|
|
366
|
-
{
|
|
367
|
-
key: 'h',
|
|
368
|
-
method: () => {
|
|
369
|
-
this.help.toggle();
|
|
370
|
-
},
|
|
371
|
-
},
|
|
372
|
-
{
|
|
373
|
-
key: 'n',
|
|
374
|
-
method: () => {
|
|
375
|
-
if (this.menu) this.menu.toggle();
|
|
376
|
-
},
|
|
377
|
-
},
|
|
378
|
-
];
|
|
379
|
-
|
|
380
|
-
if (this.props.subtitles) {
|
|
381
|
-
this.bindings.push({
|
|
382
|
-
key: 'c',
|
|
383
|
-
method: () => {
|
|
384
|
-
this.toggleSubs();
|
|
385
|
-
},
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// bind
|
|
390
|
-
this.bindings.forEach((binding) => {
|
|
391
|
-
mousetrap.bind(binding.key, (e) => {
|
|
392
|
-
binding.method(e);
|
|
393
|
-
this.onHotkeyPressed(binding.key);
|
|
394
|
-
});
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
unbindHotKeys() {
|
|
399
|
-
if (this.bindings)
|
|
400
|
-
this.bindings.forEach((binding) => {
|
|
401
|
-
mousetrap.unbind(binding.key);
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
bindEvents() {
|
|
406
|
-
this.videojs.on('volumechange', () => {
|
|
407
|
-
// persist volume changes
|
|
408
|
-
this.persistSetting(constants.settings.volume, this.volume);
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
this.videojs.textTracks().on('change', () => {
|
|
412
|
-
// determine subtitles state
|
|
413
|
-
const tracks = this.videojs.textTracks().tracks_;
|
|
414
|
-
const selected = tracks.filter((track) => track.mode === 'showing');
|
|
415
|
-
const areAnyTxtTracksActive = selected.length > 0; // -> true if any text tracks are showing
|
|
416
|
-
|
|
417
|
-
if (areAnyTxtTracksActive) {
|
|
418
|
-
// update container with appropriate className
|
|
419
|
-
this.videojs.el_.classList.add(constants.classNames.subtitlesActive);
|
|
420
|
-
this.persistSetting(constants.settings.subs, true);
|
|
421
|
-
this.persistSetting(constants.settings.subLang, selected[0].language);
|
|
422
|
-
} else {
|
|
423
|
-
this.videojs.el_.classList.remove(constants.classNames.subtitlesActive);
|
|
424
|
-
this.persistSetting(constants.settings.subs, false);
|
|
425
|
-
}
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
volumeUp() {
|
|
430
|
-
this.videojs.controlBar.volumePanel.volumeControl.volumeBar.stepForward();
|
|
431
|
-
this.osd.feedback(constants.osd.volchange, { volume: this.volume });
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
volumeDown() {
|
|
435
|
-
this.videojs.controlBar.volumePanel.volumeControl.volumeBar.stepBack();
|
|
436
|
-
this.osd.feedback(constants.osd.volchange, { volume: this.volume });
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
toggleMute() {
|
|
440
|
-
if (!this.videojs.muted()) {
|
|
441
|
-
this.videojs.muted(true);
|
|
442
|
-
this.osd.feedback(constants.osd.volchange, { volume: this.volume });
|
|
443
|
-
} else {
|
|
444
|
-
this.videojs.muted(false);
|
|
445
|
-
this.osd.feedback(constants.osd.volchange, { volume: this.volume });
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
toggleFullscreen() {
|
|
450
|
-
if (this.videojs.isFullscreen()) {
|
|
451
|
-
this.videojs.exitFullscreen();
|
|
452
|
-
} else {
|
|
453
|
-
this.videojs.requestFullscreen();
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
seek(secs) {
|
|
458
|
-
// relative to current playback time
|
|
459
|
-
let time = this.videojs.currentTime() + secs;
|
|
460
|
-
|
|
461
|
-
if (time < 0) {
|
|
462
|
-
time = 0;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
this.videojs.currentTime(time);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
seekTo(sec) {
|
|
469
|
-
if (!this.videojs.hasStarted()) this.videojs.hasStarted(true);
|
|
470
|
-
this.videojs.currentTime(sec);
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
jumpForward() {
|
|
474
|
-
this.seek(constants.defaults.jumpSeconds);
|
|
475
|
-
this.osd.feedback(constants.osd.jumpForward);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
jumpBackward() {
|
|
479
|
-
this.seek(-constants.defaults.jumpSeconds);
|
|
480
|
-
this.osd.feedback(constants.osd.jumpBackward);
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
frameForward() {
|
|
484
|
-
const thisFrame = this.timeConverter.secondsToFramesFromStart(this.videojs.currentTime());
|
|
485
|
-
const nextFrame = Math.round(thisFrame) + 1;
|
|
486
|
-
|
|
487
|
-
this.videojs.currentTime(this.timeConverter.frameToCurrentTimeFromStart(nextFrame));
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
frameBackward() {
|
|
491
|
-
const thisFrame = this.timeConverter.secondsToFramesFromStart(this.videojs.currentTime());
|
|
492
|
-
const previousFrame = Math.round(thisFrame) - 1;
|
|
493
|
-
|
|
494
|
-
this.videojs.currentTime(this.timeConverter.frameToCurrentTimeFromStart(previousFrame));
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
secondsToFramesFromStart(seconds) {
|
|
498
|
-
const frameRate = this.props.frameRate || constants.defaults.frameRate;
|
|
499
|
-
let frame = seconds * frameRate;
|
|
500
|
-
frame = Math.round(frame);
|
|
501
|
-
return frame; // we're on this frame, could be for example 23.45
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
doEsothericWork() {
|
|
505
|
-
// non critical parts, that are nice to have
|
|
506
|
-
if (this.props.color) {
|
|
507
|
-
// change the seekbar color from props
|
|
508
|
-
document.getElementsByClassName('vjs-play-progress')[0].style.backgroundColor =
|
|
509
|
-
this.props.color;
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// feedback gizmo, mainly for feedback on hotkey-presses
|
|
513
|
-
this.osd = new OSD({
|
|
514
|
-
target: this.videoEl.parentElement,
|
|
515
|
-
});
|
|
516
|
-
|
|
517
|
-
// add a cinema button if a callback is passed in props
|
|
518
|
-
if (this.props.onCinema) {
|
|
519
|
-
this.videoEl.parentElement.classList.add('has-cinema-button');
|
|
520
|
-
this.cinema = false;
|
|
521
|
-
|
|
522
|
-
this.addButtonToControlBar({
|
|
523
|
-
onClick: () => {
|
|
524
|
-
this.props.onCinema(!this.cinema);
|
|
525
|
-
this.cinema = !this.cinema;
|
|
526
|
-
},
|
|
527
|
-
componentName: 'CinemaButton',
|
|
528
|
-
className: 'vdt-cinema-button',
|
|
529
|
-
title: 'Cinema-mode',
|
|
530
|
-
});
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// add a screenshot button if a callback is passed in props
|
|
534
|
-
if (this.props.onScreenshot) {
|
|
535
|
-
this.videoEl.parentElement.classList.add('has-screenshot-button');
|
|
536
|
-
|
|
537
|
-
this.addButtonToControlBar({
|
|
538
|
-
onClick: () => {
|
|
539
|
-
this.props.onScreenshot(this.videojs.currentTime());
|
|
540
|
-
},
|
|
541
|
-
componentName: 'ScreenshotButton',
|
|
542
|
-
className: 'vdt-screenshot-button',
|
|
543
|
-
title: 'Screenshot',
|
|
544
|
-
});
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// add a help overlay explaining keyboard controls
|
|
548
|
-
this.help = new HelpOverlay({
|
|
549
|
-
target: this.videoEl.parentElement,
|
|
550
|
-
onToggle: (willShow) => {
|
|
551
|
-
if (this.menu && this.menu.opened) {
|
|
552
|
-
this.menu.close();
|
|
553
|
-
}
|
|
554
|
-
if (willShow && !this.controlsBelowPlayer) {
|
|
555
|
-
this.hideControls();
|
|
556
|
-
} else {
|
|
557
|
-
this.showControls();
|
|
558
|
-
}
|
|
559
|
-
},
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
restoreSettings() {
|
|
564
|
-
// restore settings, like volume level or language track
|
|
565
|
-
// get values from storage
|
|
566
|
-
const volume = localStorage.getItem(constants.settings.volume);
|
|
567
|
-
const subsEnabled = localStorage.getItem(constants.settings.subs);
|
|
568
|
-
const subLang = localStorage.getItem(constants.settings.subLang);
|
|
569
|
-
|
|
570
|
-
// values vs default behaviours in a neatly readable map
|
|
571
|
-
this.settings = {
|
|
572
|
-
// avoid .99999999 and other float pecularities in js
|
|
573
|
-
[constants.settings.volume]: volume !== null ? Math.round(volume * 100) / 100 : 1,
|
|
574
|
-
[constants.settings.subLang]: subLang !== null ? subLang : 'en',
|
|
575
|
-
[constants.settings.subs]: subsEnabled !== null ? JSON.parse(subsEnabled) : false,
|
|
576
|
-
};
|
|
577
|
-
|
|
578
|
-
// apply values (settings)
|
|
579
|
-
this.volume = this.settings[constants.settings.volume]; // volume
|
|
580
|
-
|
|
581
|
-
if (this.settings[constants.settings.subs]) {
|
|
582
|
-
// subtitles
|
|
583
|
-
this.enableSubs();
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
persistSetting(key, val) {
|
|
588
|
-
this.settings[key] = val;
|
|
589
|
-
localStorage.setItem(key, val);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
enableSubs() {
|
|
593
|
-
const subs = this.videojs.textTracks();
|
|
594
|
-
|
|
595
|
-
for (let i = 0; i < subs.length; i += 1) {
|
|
596
|
-
const sub = subs[i];
|
|
597
|
-
if (sub.language === this.settings[constants.settings.subLang]) {
|
|
598
|
-
sub.mode = 'showing';
|
|
599
|
-
} else {
|
|
600
|
-
sub.mode = 'hidden';
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
disableSubs() {
|
|
606
|
-
const subs = this.videojs.textTracks();
|
|
607
|
-
|
|
608
|
-
for (let i = 0; i < subs.length; i += 1) {
|
|
609
|
-
const sub = subs[i];
|
|
610
|
-
sub.mode = 'hidden';
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
toggleSubs() {
|
|
615
|
-
if (this.videojs.textTracks().length > 0) {
|
|
616
|
-
// check if subtitles exist
|
|
617
|
-
if (
|
|
618
|
-
this.videojs.textTracks().tracks_.filter((track) => track.mode === 'showing').length > 0
|
|
619
|
-
) {
|
|
620
|
-
// a certain subtitle track is enabled
|
|
621
|
-
this.disableSubs();
|
|
622
|
-
} else {
|
|
623
|
-
this.enableSubs();
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
/*
|
|
629
|
-
A quick example
|
|
630
|
-
options = {
|
|
631
|
-
onClick: ()=>{},
|
|
632
|
-
componentName: 'ScreenshotButton',
|
|
633
|
-
className: 'vdt-screenshot-button'
|
|
634
|
-
}
|
|
635
|
-
*/
|
|
636
|
-
addButtonToControlBar(options) {
|
|
637
|
-
const Button = videojs.getComponent('Button');
|
|
638
|
-
const CustomButton = videojs.extend(Button, {
|
|
639
|
-
/* eslint-disable func-names, object-shorthand */
|
|
640
|
-
constructor: function () {
|
|
641
|
-
// eslint-disable-next-line prefer-rest-params
|
|
642
|
-
Button.apply(this, arguments);
|
|
643
|
-
if (options.className) this.el_.classList.add(options.className);
|
|
644
|
-
if (options.title) {
|
|
645
|
-
this.el_.title = options.title;
|
|
646
|
-
}
|
|
647
|
-
},
|
|
648
|
-
handleClick: () => {
|
|
649
|
-
if (options.onClick) options.onClick();
|
|
650
|
-
},
|
|
651
|
-
});
|
|
652
|
-
|
|
653
|
-
videojs.registerComponent(options.componentName, CustomButton);
|
|
654
|
-
this.videojs.getChild('controlBar').addChild(options.componentName, {}, 7);
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
// convenient getters / setters
|
|
658
|
-
get volume() {
|
|
659
|
-
return this.videojs.controlBar.volumePanel.volumeControl.volumeBar.getPercent();
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
set volume(floatVal) {
|
|
663
|
-
if (floatVal === 0) {
|
|
664
|
-
this.videojs.muted(true);
|
|
665
|
-
} else {
|
|
666
|
-
this.videojs.muted(false);
|
|
667
|
-
this.videojs.volume(floatVal);
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
setupMenu() {
|
|
672
|
-
const menuItems = this.props.menuItems || [];
|
|
673
|
-
const sourcesMenuItems = [];
|
|
674
|
-
if (this.props.sources.length > 1) {
|
|
675
|
-
// show only if more than 1 source
|
|
676
|
-
this.props.sources.forEach((source) => {
|
|
677
|
-
if (source.label) {
|
|
678
|
-
sourcesMenuItems.push({
|
|
679
|
-
title: source.label,
|
|
680
|
-
onClick: () => {
|
|
681
|
-
this.src = source;
|
|
682
|
-
},
|
|
683
|
-
});
|
|
684
|
-
}
|
|
685
|
-
});
|
|
686
|
-
|
|
687
|
-
menuItems.push({
|
|
688
|
-
title: 'Sources',
|
|
689
|
-
getActiveIndex: () => {
|
|
690
|
-
const matchingSource = this.props.sources.find(
|
|
691
|
-
(source) => source.src === this.videojs.src(),
|
|
692
|
-
);
|
|
693
|
-
return sourcesMenuItems.indexOf(
|
|
694
|
-
sourcesMenuItems.find((menuItem) => menuItem.title === matchingSource.label),
|
|
695
|
-
);
|
|
696
|
-
},
|
|
697
|
-
items: sourcesMenuItems,
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
// subtitles
|
|
702
|
-
if (this.props.subtitles && this.props.subtitles.length > 0) {
|
|
703
|
-
// off option for subs
|
|
704
|
-
const subtitleMenuItems = [
|
|
705
|
-
{
|
|
706
|
-
title: 'Off',
|
|
707
|
-
onClick: () => {
|
|
708
|
-
this.disableSubs();
|
|
709
|
-
},
|
|
710
|
-
},
|
|
711
|
-
];
|
|
712
|
-
|
|
713
|
-
// quick toggle in the control bar
|
|
714
|
-
// this.addButtonToControlBar({
|
|
715
|
-
// onClick: () => {
|
|
716
|
-
// this.props.onCinema(!this.cinema);
|
|
717
|
-
// this.cinema = !this.cinema;
|
|
718
|
-
// },
|
|
719
|
-
// componentName: 'CinemaButton',
|
|
720
|
-
// className: 'vdt-cinema-button',
|
|
721
|
-
// });
|
|
722
|
-
|
|
723
|
-
this.props.subtitles.forEach((subtitle, index) => {
|
|
724
|
-
subtitleMenuItems.push({
|
|
725
|
-
title: subtitle.label,
|
|
726
|
-
onClick: () => {
|
|
727
|
-
const subs = this.videojs.textTracks();
|
|
728
|
-
for (let i = 0; i < subs.length; i += 1) {
|
|
729
|
-
const sub = subs[i];
|
|
730
|
-
if (i === index) {
|
|
731
|
-
sub.mode = 'showing';
|
|
732
|
-
} else {
|
|
733
|
-
sub.mode = 'hidden';
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
},
|
|
737
|
-
});
|
|
738
|
-
});
|
|
739
|
-
|
|
740
|
-
menuItems.push({
|
|
741
|
-
title: 'Subtitles',
|
|
742
|
-
getActiveIndex: () => {
|
|
743
|
-
const subs = this.videojs.textTracks();
|
|
744
|
-
for (let i = 0; i < subs.length; i += 1) {
|
|
745
|
-
const sub = subs[i];
|
|
746
|
-
if (sub.mode === 'showing') {
|
|
747
|
-
return i + 1; // offset "off" option
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
|
|
751
|
-
return 0; // if none are showing, then probably "off"
|
|
752
|
-
},
|
|
753
|
-
items: subtitleMenuItems,
|
|
754
|
-
});
|
|
755
|
-
}
|
|
756
|
-
|
|
757
|
-
if (menuItems.length > 0) {
|
|
758
|
-
const target = this.videojs.el_;
|
|
759
|
-
|
|
760
|
-
this.menu = new Menu({
|
|
761
|
-
target,
|
|
762
|
-
items: menuItems,
|
|
763
|
-
onOpen: () => {
|
|
764
|
-
if (!this.controlsBelowPlayer) this.hideControls();
|
|
765
|
-
},
|
|
766
|
-
onClose: () => {
|
|
767
|
-
if (!this.controlsBelowPlayer) this.videojs.controls(true);
|
|
768
|
-
},
|
|
769
|
-
});
|
|
770
|
-
|
|
771
|
-
// add a cogwheel button to the player
|
|
772
|
-
this.videoEl.parentElement.classList.add('has-vdt-menu');
|
|
773
|
-
|
|
774
|
-
this.addButtonToControlBar({
|
|
775
|
-
onClick: () => {
|
|
776
|
-
this.menu.toggle();
|
|
777
|
-
},
|
|
778
|
-
componentName: 'SettingsMenuButton',
|
|
779
|
-
className: 'vdt-settings-menu-button',
|
|
780
|
-
title: 'Settings',
|
|
781
|
-
});
|
|
782
|
-
} else {
|
|
783
|
-
// no menu items, no menu
|
|
784
|
-
}
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
hideControls() {
|
|
788
|
-
this.videojs.userActive(false);
|
|
789
|
-
this.videojs.controls(false);
|
|
790
|
-
this.videojs.fluid(true);
|
|
791
|
-
|
|
792
|
-
this.videoEl.parentElement.classList.remove('vjs-user-active');
|
|
793
|
-
this.videojs.userActive(false);
|
|
794
|
-
}
|
|
795
|
-
|
|
796
|
-
showControls() {
|
|
797
|
-
this.videojs.fluid(!this.controlsBelowPlayer);
|
|
798
|
-
this.videojs.controls(true);
|
|
799
|
-
this.videojs.userActive(true);
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
toggleControls() {
|
|
803
|
-
if (this.videojs.controls()) {
|
|
804
|
-
this.hideControls();
|
|
805
|
-
} else {
|
|
806
|
-
this.showControls();
|
|
807
|
-
}
|
|
808
|
-
}
|
|
809
|
-
|
|
810
|
-
onHotkeyPressed(key) {
|
|
811
|
-
if (key !== 'h' && this.help.state.active) {
|
|
812
|
-
this.help.hide();
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
// eslint-disable-next-line class-methods-use-this
|
|
817
|
-
triggerHotkey(key) {
|
|
818
|
-
mousetrap.trigger(key);
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
toStart() {
|
|
822
|
-
this.videojs.currentTime(0);
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
toEnd() {
|
|
826
|
-
this.videojs.currentTime(this.videojs.duration());
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
replay() {
|
|
830
|
-
this.toStart();
|
|
831
|
-
this.videojs.play();
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
play() {
|
|
835
|
-
this.videojs.play();
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
pause() {
|
|
839
|
-
if (!this.videojs.paused()) this.videojs.pause();
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
togglePlayback() {
|
|
843
|
-
if (this.videojs.paused()) {
|
|
844
|
-
this.play();
|
|
845
|
-
if (this.videojs.hasStarted_) this.osd.feedback(constants.osd.play);
|
|
846
|
-
} else {
|
|
847
|
-
this.pause();
|
|
848
|
-
this.osd.feedback(constants.osd.pause);
|
|
849
|
-
}
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
updateSmpte(valuesObj) {
|
|
853
|
-
// Object.keys and array.forEach are too slow, classic for is faster
|
|
854
|
-
const startCount = this.nodeNames.length - 1;
|
|
855
|
-
let i;
|
|
856
|
-
for (i = startCount; i > -1; i -= 1) {
|
|
857
|
-
// updates follow frame -> seconds -> minutes -> hours order
|
|
858
|
-
const key = this.nodeNames[i];
|
|
859
|
-
|
|
860
|
-
if (
|
|
861
|
-
typeof valuesObj[key] !== 'undefined' &&
|
|
862
|
-
valuesObj[key] !== parseInt(this.nodes[key].value, 10) &&
|
|
863
|
-
key !== this.state.selectedNode
|
|
864
|
-
) {
|
|
865
|
-
// re-render node if value is defineed and is different from current value
|
|
866
|
-
const str = valuesObj[key].toString();
|
|
867
|
-
this.nodes[key].value = this.toDigitPairStr(str);
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
destroy() {
|
|
873
|
-
// clean up
|
|
874
|
-
this.videojs.paused(true);
|
|
875
|
-
if (this.rafID) cancelAnimationFrame(this.rafID);
|
|
876
|
-
this.videojs.dispose();
|
|
877
|
-
this.unbindHotKeys();
|
|
878
|
-
if (this.seekBar) this.seekBar.destroy();
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
get container() {
|
|
882
|
-
return this.videoEl.parentElement;
|
|
883
|
-
}
|
|
884
|
-
|
|
885
|
-
set onDark(bool) {
|
|
886
|
-
if (bool) {
|
|
887
|
-
this.container.classList.add('on-dark');
|
|
888
|
-
} else {
|
|
889
|
-
this.container.classList.remove('on-dark');
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
if (this.seekBar) {
|
|
893
|
-
this.seekBar.onDark = bool;
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
get onDark() {
|
|
898
|
-
return this.container.classList.contains('on-dark');
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
set controlsBelowPlayer(bool) {
|
|
902
|
-
if (this.videojs.controls()) {
|
|
903
|
-
if (bool) {
|
|
904
|
-
this.container.classList.add('controls-below-player');
|
|
905
|
-
this.videojs.fluid(false);
|
|
906
|
-
} else {
|
|
907
|
-
this.container.classList.remove('controls-below-player');
|
|
908
|
-
this.videojs.fluid(true);
|
|
909
|
-
}
|
|
910
|
-
}
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
get controlsBelowPlayer() {
|
|
914
|
-
return this.container.classList.contains('controls-below-player');
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
get smpte() {
|
|
918
|
-
const currentSecond = this.videojs.currentTime();
|
|
919
|
-
return this.timeConverter.secondsFromStartToSMPTE(currentSecond);
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
set smpte(o) {
|
|
923
|
-
this.updateSmpte(o);
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
set src({ src, type = 'video/mp4', label = null }) {
|
|
927
|
-
const currentTime = this.videojs.currentTime();
|
|
928
|
-
// iterate trough sources, find existing matches
|
|
929
|
-
const source = this.props.sources.find((aSource) => aSource.src === src);
|
|
930
|
-
// if no match try adding source
|
|
931
|
-
|
|
932
|
-
const onReady = () => {
|
|
933
|
-
let initdone = false;
|
|
934
|
-
// wait for video metadata to load, then set time
|
|
935
|
-
this.videojs.on('loadedmetadata', () => {
|
|
936
|
-
this.videojs.currentTime(currentTime);
|
|
937
|
-
});
|
|
938
|
-
|
|
939
|
-
// iPhone/iPad need to play first, then set the time
|
|
940
|
-
// events: https://www.w3.org/TR/html5/embedded-content-0.html#mediaevents
|
|
941
|
-
this.videojs.on('canplaythrough', () => {
|
|
942
|
-
if (!initdone) {
|
|
943
|
-
this.videojs.currentTime(currentTime);
|
|
944
|
-
initdone = true;
|
|
945
|
-
this.videojs.play();
|
|
946
|
-
}
|
|
947
|
-
});
|
|
948
|
-
};
|
|
949
|
-
|
|
950
|
-
if (source && Array.isArray(source) && source.length > 0) {
|
|
951
|
-
// TODO - if(source.length > 1) - try one by one until a successful load happens
|
|
952
|
-
this.videojs.addClass('vjs-waiting');
|
|
953
|
-
this.videojs.src({
|
|
954
|
-
...source[0],
|
|
955
|
-
});
|
|
956
|
-
onReady();
|
|
957
|
-
} else {
|
|
958
|
-
this.videojs.addClass('vjs-waiting');
|
|
959
|
-
this.videojs.src(this.addSrc(src, type, label));
|
|
960
|
-
onReady();
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
get src() {
|
|
965
|
-
return this.videojs.src();
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
addSrc(src, type, label) {
|
|
969
|
-
const newSource = {
|
|
970
|
-
src,
|
|
971
|
-
type,
|
|
972
|
-
label,
|
|
973
|
-
};
|
|
974
|
-
const index = this.props.sources.push(newSource) - 1;
|
|
975
|
-
|
|
976
|
-
return this.props.sources[index];
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
// eslint-disable-next-line class-methods-use-this, no-unused-vars
|
|
980
|
-
removeSrc(url) {
|
|
981
|
-
// should remove all url matching sources
|
|
982
|
-
}
|
|
983
|
-
|
|
984
|
-
set controls(bool) {
|
|
985
|
-
if (bool) {
|
|
986
|
-
this.showControls();
|
|
987
|
-
} else {
|
|
988
|
-
this.hideControls();
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
get percentLoaded() {
|
|
993
|
-
const r = this.videojs.buffered();
|
|
994
|
-
|
|
995
|
-
const percentLoaded = (r.end(0) / this.videojs.duration()) * 100;
|
|
996
|
-
return percentLoaded;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
/*
|
|
1001
|
-
Some video.js prototype overrides, that
|
|
1002
|
-
help with undesired behaviour
|
|
1003
|
-
*/
|
|
1004
|
-
|
|
1005
|
-
// take over space button, prevent video.js default behaviour
|
|
1006
|
-
const button = videojs.prototype.constructor.getComponent('Button');
|
|
1007
|
-
// eslint-disable-next-line func-names
|
|
1008
|
-
button.prototype.handleKeyDown = function (e) {
|
|
1009
|
-
if (e.keyCode === 32) {
|
|
1010
|
-
e.preventDefault();
|
|
1011
|
-
}
|
|
1012
|
-
};
|
|
1013
|
-
videojs.prototype.constructor.registerComponent('Button', button);
|
|
1014
|
-
|
|
1015
|
-
// video.js opens menu on hover by default, this prevents that
|
|
1016
|
-
const subsButton = videojs.prototype.constructor.getComponent('SubsCapsButton');
|
|
1017
|
-
class CustomSubsCapsButton extends subsButton {
|
|
1018
|
-
constructor(player, options = {}) {
|
|
1019
|
-
super(player, options);
|
|
1020
|
-
this.menuButton_.off('mouseenter');
|
|
1021
|
-
this.menuButton_.off('mouseleave');
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
|
|
1025
|
-
/*
|
|
1026
|
-
TODO - see what can be done about strange behaviour
|
|
1027
|
-
when cc button is clicked with the menu showing
|
|
1028
|
-
*/
|
|
1029
|
-
videojs.prototype.constructor.registerComponent('SubsCapsButton', CustomSubsCapsButton);
|