@vidispine/vdt-videojs 21.4.0-pre.1 → 22.1.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.
Files changed (54) hide show
  1. package/README.md +4 -4
  2. package/deprecated-app/README.md +21 -0
  3. package/{demo.html → deprecated-app/index.html} +1 -1
  4. package/{demo.js → deprecated-app/index.js} +69 -41
  5. package/{samples → deprecated-app/samples}/birds-es.vvt +0 -0
  6. package/{samples → deprecated-app/samples}/birds.vvt +0 -0
  7. package/{tests.js → deprecated-app/tests.js} +87 -54
  8. package/{lib/style.css → dist/index.css} +621 -6
  9. package/dist/index.es.js +2099 -0
  10. package/dist/index.js +2112 -0
  11. package/{lib → dist}/index.umd.js +910 -2039
  12. package/package.json +29 -85
  13. package/rollup.config.js +54 -0
  14. package/src/constants.js +1 -1
  15. package/src/externalControls.js +14 -18
  16. package/src/helpOverlay.js +12 -16
  17. package/src/helpers.js +3 -3
  18. package/src/index.js +3 -9
  19. package/src/index.stories.mdx +59 -0
  20. package/src/menu.api.stories.mdx +59 -0
  21. package/src/menu.js +23 -19
  22. package/src/{menu.example.md → menu.usage.stories.mdx} +31 -21
  23. package/src/osd.js +24 -18
  24. package/src/player.api.stories.mdx +120 -0
  25. package/src/player.js +157 -108
  26. package/src/player.scss +2 -0
  27. package/src/{player.example.md → player.usage.stories.mdx} +18 -8
  28. package/src/seekBar.js +85 -48
  29. package/src/timeCodeDisplay.js +39 -31
  30. package/src/videoTime.js +4 -4
  31. package/build.js +0 -233
  32. package/lib/index.cjs.js +0 -3241
  33. package/lib/index.esm.js +0 -3228
  34. package/lib/package.json +0 -13
  35. package/lib/src/constants.js +0 -51
  36. package/lib/src/externalControls.js +0 -307
  37. package/lib/src/helpOverlay.js +0 -106
  38. package/lib/src/helpers.js +0 -34
  39. package/lib/src/index.js +0 -13
  40. package/lib/src/menu.js +0 -365
  41. package/lib/src/osd.js +0 -91
  42. package/lib/src/player.js +0 -943
  43. package/lib/src/seekBar.js +0 -833
  44. package/lib/src/timeCodeDisplay.js +0 -253
  45. package/lib/src/videoTime.js +0 -66
  46. package/log.js +0 -24
  47. package/src/_index.stories.js +0 -24
  48. package/src/externalControls.stories.js +0 -6
  49. package/src/helpOverlay.stories.js +0 -6
  50. package/src/menu.classMethods.md +0 -47
  51. package/src/player.classMethods.md +0 -108
  52. package/src/player.stories.js +0 -14
  53. package/src/seekBar.stories.js +0 -8
  54. package/tmp/player.css +0 -1
@@ -1,112 +1,55 @@
1
1
  (function (global, factory) {
2
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('video.js/dist/alt/video.core.min'), require('mousetrap')) :
3
- typeof define === 'function' && define.amd ? define(['exports', 'video.js/dist/alt/video.core.min', 'mousetrap'], factory) :
4
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.vdtVideojs = {}, global.videojs, global.mousetrap));
5
- })(this, (function (exports, videojs, mousetrap) { 'use strict';
2
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('mousetrap'), require('video.js/dist/alt/video.core.min')) :
3
+ typeof define === 'function' && define.amd ? define(['exports', 'mousetrap', 'video.js/dist/alt/video.core.min'], factory) :
4
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.vdtVideojs = {}, global.mousetrap, global.videojs));
5
+ })(this, (function (exports, mousetrap, videojs) { 'use strict';
6
6
 
7
7
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
8
8
 
9
- var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
10
9
  var mousetrap__default = /*#__PURE__*/_interopDefaultLegacy(mousetrap);
10
+ var videojs__default = /*#__PURE__*/_interopDefaultLegacy(videojs);
11
11
 
12
12
  var name = "@vidispine/vdt-videojs";
13
- var version = "21.4.0-pre.1";
13
+ var version = "22.1.0-pre.2";
14
14
  var license = "UNLICENSED";
15
- var main = "lib/index.cjs.js";
16
- var module = "lib/index.es.js";
15
+ var main = "dist/index.js";
16
+ var module = "dist/index.es.js";
17
+ var browser = "dist/index.umd.js";
17
18
  var author = "Oto Inša <otoinsa@bertelsmann.de>";
18
19
  var scripts = {
19
- cleanup: "rm -rf ./dist && rm -rf ./.cache",
20
- start: "npm run cleanup && parcel ./demo.html --no-source-maps --no-hmr -p 2222 -t browser --open --out-dir ./dev",
21
- "build-legacy": "parcel build ./src/player.js --out-dir ./lib --no-source-maps",
22
- build: "node ./build.js",
23
- "build:watch": "node ./build.js --watch",
24
- "build:demo": "rm -rf ./demo/ && parcel build ./demo.html --out-file index.html --out-dir ./demo --no-source-maps --public-url ./",
25
- docs: "jsdoc ./src/player.js -d ./docs/"
26
- };
27
- var devDependencies = {
28
- "@babel/core": "^7.7.7",
29
- "babel-plugin-transform-runtime": "^6.23.0",
30
- chokidar: "^3.3.1",
31
- colors: "^1.4.0",
32
- "parcel-bundler": "^1.12.4",
33
- rollup: "^2.20.0",
34
- "rollup-plugin-json": "^4.0.0",
35
- sass: "^1.24.3",
36
- "strip-comments": "^2.0.1"
20
+ build: "rm -rf dist/ && rollup -c",
21
+ "build:watch": "rm -rf dist/*.js* && rollup -c -w --environment DEVELOPMENT",
22
+ lint: "eslint \"./{src/**/*,*}.{js,md}\"",
23
+ "lint:fix": "yarn lint --fix"
37
24
  };
38
25
  var dependencies = {
39
26
  mousetrap: "^1.6.3",
40
27
  "video.js": "^7.6.6"
41
28
  };
29
+ var devDependencies = {
30
+ "@babel/core": "^7.17.0",
31
+ "@rollup/plugin-babel": "^5.3.0",
32
+ "@rollup/plugin-commonjs": "^21.0.1",
33
+ "@rollup/plugin-json": "^4.1.0",
34
+ "@rollup/plugin-node-resolve": "^13.1.3",
35
+ "@vidispine/eslint-config-base": "^22.1.0-pre.2",
36
+ "@vidispine/eslint-config-storybook": "*",
37
+ "babel-plugin-transform-runtime": "^6.23.0",
38
+ rollup: "^2.67.0",
39
+ "rollup-plugin-cleanup": "^3.2.1",
40
+ "rollup-plugin-peer-deps-external": "^2.2.4",
41
+ "rollup-plugin-sass": "^1.2.10"
42
+ };
42
43
  var browserslist = [
43
44
  "since 2017-06"
44
45
  ];
45
- var changelog = {
46
- "0.1.2": [
47
- "Removed unwanted videojs markup",
48
- "Implemented playback hotkeys",
49
- "Borrowed styling ideas from a popular online video platform",
50
- "Visual feedback gizmo as hotkey feedback",
51
- "Subtitle support (VVT)",
52
- "Persistance - volume level",
53
- "Custom timecode display (SMPTE)",
54
- "Frame forward / frame backward 6unctionality"
55
- ],
56
- "0.1.3": [
57
- "Renamed 'subtitles off' to just 'off'",
58
- "Timecode SMPTE - current time in SMPTE with input capabilities",
59
- "Timecode SMPTE - total video duration timecode (read-only)",
60
- "Settings persistance - storing wether subtitles should be on and the selected language as well"
61
- ],
62
- "0.1.4": [
63
- "Subtitles toggle hotkey - c",
64
- "Now accepts Blob as subtitles source file",
65
- "Timecode input improvements",
66
- "Screenshot button with currentTime callback",
67
- "Moved OSD in its own class"
68
- ],
69
- "0.1.6": [
70
- "Help overlay for keyboard controls",
71
- "External player controls component (js, React)",
72
- "External seek-bar component (js, React)"
73
- ],
74
- "0.1.7": [
75
- "In / Out markers for seek bar"
76
- ],
77
- "0.1.8": [
78
- "External TimeCodeDisplay support for player"
79
- ],
80
- "0.1.9": [
81
- "Multiple regions with annotations for SeekBar"
82
- ],
83
- "0.2.2": [
84
- "Overall version bump (improvements)"
85
- ],
86
- "0.3.0": [
87
- "controlsBelowPlayer - reactive option",
88
- "onDark - used in conjuction with controlsBelowPlayer for dark backgrounds",
89
- "Bug fixes, convenience class methods"
90
- ],
91
- "0.3.1": [
92
- "SeekBar - Seek animation, dark mode improvements",
93
- "Bug fixes, refactor & code sharing improvements"
94
- ],
95
- "0.5.3": [
96
- "vdt-videojs now provides a commonjs module with named exports"
97
- ],
98
- "0.6.1": [
99
- "Internal refactor before splitting main features into videojs plugins",
100
- "More bugfixes"
46
+ var eslintConfig = {
47
+ "extends": [
48
+ "@vidispine/eslint-config-base",
49
+ "@vidispine/eslint-config-storybook"
101
50
  ]
102
51
  };
103
- var wishlist = [
104
- "Timecode offset capabilities - i.e. start time from 10:00:00:00"
105
- ];
106
- var backlog = [
107
- "External player controls component",
108
- "Check for hijacked hotkeys and player focus"
109
- ];
52
+ var prettier = "@vidispine/prettier-config";
110
53
  var pkgInfo = {
111
54
  name: name,
112
55
  version: version,
@@ -114,565 +57,295 @@
114
57
  "private": false,
115
58
  main: main,
116
59
  module: module,
60
+ browser: browser,
117
61
  author: author,
118
62
  scripts: scripts,
119
- devDependencies: devDependencies,
120
63
  dependencies: dependencies,
64
+ devDependencies: devDependencies,
121
65
  browserslist: browserslist,
122
- changelog: changelog,
123
- wishlist: wishlist,
124
- backlog: backlog
66
+ eslintConfig: eslintConfig,
67
+ prettier: prettier
125
68
  };
126
69
 
127
70
  const defaults = Object.freeze({
128
- frameRate: (30000 / 1001),
71
+ frameRate: 30000 / 1001,
129
72
  jumpSeconds: 10,
130
- inactivityTimeout: 1200, // hide player controls if user inactive for this duration
131
- audioInactivityTimeout: 0,
73
+ inactivityTimeout: 1200,
74
+ audioInactivityTimeout: 0
132
75
  });
133
-
134
76
  const osd = Object.freeze({
135
77
  play: 0,
136
78
  pause: 1,
137
79
  volchange: 2,
138
80
  jumpForward: 3,
139
- jumpBackward: 4,
81
+ jumpBackward: 4
140
82
  });
141
-
142
83
  const settings = Object.freeze({
143
84
  volume: 'volume-setting',
144
85
  subLang: 'subtitles-language',
145
- subs: 'subtitles-enabled',
86
+ subs: 'subtitles-enabled'
146
87
  });
147
-
148
88
  const classNames = Object.freeze({
149
89
  poster: 'has-poster',
150
90
  subtitles: 'has-subtitles',
151
91
  subtitlesActive: 'subtitles-active',
152
92
  onDark: 'on-dark',
153
- loading: 'loading',
93
+ loading: 'loading'
154
94
  });
155
-
156
95
  const colors = Object.freeze({
157
96
  gray: '#787878',
158
- light: 'rgba(255,255,255,.75)',
97
+ light: 'rgba(255,255,255,.75)'
159
98
  });
160
-
161
99
  const packageInfo = Object.freeze({
162
100
  name: pkgInfo.name.split('/')[1],
163
- vendor: pkgInfo.name.split('/')[0],
101
+ vendor: pkgInfo.name.split('/')[0]
164
102
  });
165
-
166
103
  const constants$2 = Object.freeze({
167
104
  defaults,
168
105
  settings,
169
106
  classNames,
170
107
  colors,
171
108
  osd,
172
- packageInfo,
109
+ packageInfo
173
110
  });
174
111
 
175
112
  function truncate(input, len) {
176
- if (input.length > len) return `${input.substring(0, len)}...`;
113
+ if (input.length > len) return "".concat(input.substring(0, len), "...");
177
114
  return input;
178
115
  }
179
-
180
-
181
- /*
182
- Make certain component props React-ive
183
- by being able to call respective component .handleNewProps()
184
- method in react wrappers
185
- */
186
116
  function reactiveSetters() {
187
- // will try to find a matching setter in context
188
- // TODO - switch to for loop for more efficient updates
189
117
  function handleNewProps(props) {
190
118
  if (typeof props === 'object') {
191
119
  const keys = Object.getOwnPropertyNames(props);
192
120
  const proto = Object.getPrototypeOf(this);
193
- keys.forEach((key) => {
194
- // check for own setters / getters
121
+ keys.forEach(key => {
195
122
  const descriptor = Object.getOwnPropertyDescriptor(proto, key);
196
-
197
- if (descriptor && descriptor.set) { // if a setter is defined, apply the prop
123
+ if (descriptor && descriptor.set) {
198
124
  this[key] = props[key];
199
125
  }
200
126
  });
201
127
  }
202
128
  }
203
-
204
- // extends a context with handleNewProps method
205
129
  this.handleNewProps = handleNewProps;
206
130
  }
207
-
208
- // an elegant way to prevent xss attacks
209
- function sanitize(str) {
131
+ function sanitize(str) {
210
132
  const temp = document.createElement('div');
211
133
  temp.textContent = str;
212
134
  return temp.innerHTML;
213
135
  }
214
136
 
215
- /* eslint-disable prefer-destructuring */
216
-
217
- class OSD {
218
- constructor(props) {
219
- this.props = props;
220
-
221
- this.state = {
222
- enabled: true,
223
- };
224
-
225
- this.container = document.createElement('div');
226
- this.container.className = 'vdt-player-osd';
227
-
228
- // vertically center with css
229
- this.container.innerHTML = `
230
- <table>
231
- <tbody>
232
- <td>
233
- <div class="osd-feedback-icon"></div>
234
- </td>
235
- </tbody>
236
- </table>
237
- `;
238
-
239
- this.iconEl = this.container.getElementsByClassName('osd-feedback-icon')[0];
240
-
241
- this.props.target && this.props.target.appendChild(this.container);
242
- }
243
-
244
- feedback(feedbackType, details = null) { // details - {} - can contain additional data
245
- if (!this.state.enabled) {
246
- return;
247
- }
248
-
249
- let className;
250
-
251
- switch (feedbackType) {
252
- case constants$2.osd.pause:
253
- className = 'pause';
254
- break;
255
- case constants$2.osd.play:
256
- className = 'play';
257
- break;
258
- case constants$2.osd.volchange:
259
- const currentVolumeLevel = details.volume;
260
- let perceivedVolumeLevel; // 'low' | 'mid' | 'high' string to utilize video js eisting icons
261
-
262
- /*
263
- case switch will be too slow for this, if's are 2x more performant for this case
264
- (Except in IE)
265
- so... no case switch inside case switch this time :/
266
- */
267
-
268
- // alues here are a bit of a guess, but they seem to match videojs thresholds
269
- if (currentVolumeLevel === 0) { perceivedVolumeLevel = 'null'; } else
270
- if (currentVolumeLevel > 0.65) { perceivedVolumeLevel = 'high'; } else
271
- if (currentVolumeLevel < 0.35) { perceivedVolumeLevel = 'low'; } else { perceivedVolumeLevel = 'mid'; }
272
-
273
- className = `volchange ${perceivedVolumeLevel}`;
274
- break;
275
- case constants$2.osd.jumpForward:
276
- className = 'forward-jump';
277
- break;
278
- case constants$2.osd.jumpBackward:
279
- className = 'backward-jump';
280
- break;
281
- }
282
-
283
-
284
- if (this.osdTimeout) { // should clear previous timeout/classname if in progress
285
- this.container.classList.remove('active');
286
- clearTimeout(this.osdTimeout);
287
- }
288
-
289
- // do feedback
290
- this.container.classList.add('active');
291
- this.osdTimeout = setTimeout(() => {
292
- this.container.classList.remove('active');
293
- }, 200);
294
-
295
- // get specific with classnames
296
- this.iconEl.className = `osd-feedback-icon ${className}`; // gets an overwrite each time
297
- }
298
-
299
- disable() {
300
- this.state.enabled = false;
301
- }
302
-
303
- enable() {
304
- this.state.enabled = true;
305
- }
306
- }
307
-
308
- /* eslint-disable no-restricted-globals */
309
- /* eslint-disable class-methods-use-this */
310
- /* eslint-disable no-plusplus */
311
- /* eslint-disable no-unused-expressions */
312
-
313
- const timeCodeNodeNames = [
314
- 'hours',
315
- 'minutes',
316
- 'seconds',
317
- 'frame',
318
- ];
319
-
320
- class TimeCodeDisplay {// displays and renders smpte timecodes
137
+ const buttonNames = Object.freeze({
138
+ play: 'play-pause',
139
+ start: 'start',
140
+ fb: 'frame-backward',
141
+ ff: 'frame-forward',
142
+ end: 'end'
143
+ });
144
+ const buttons = [
145
+ buttonNames.start, buttonNames.fb, buttonNames.play, buttonNames.ff, buttonNames.end];
146
+ const CSS$2 = "\n .vdt-external-controls{\n display: flex;\n justify-content: center;\n align-items: center;\n }\n\n .control{\n flex: 1;\n flex-grow: 1;\n height: 100%;\n align-contents: center;\n align-self: center;\n text-align: center;\n vertical-align: middle;\n cursor: pointer;\n user-select: none;\n height: 20px;\n padding-top: 10px;\n padding-bottom: 10px;\n }\n\n .control .icon{\n display: inline-block;\n height: 20px;\n white-space: nowrap;\n opacity: .9;\n }\n\n .control:hover .icon{ opacity: 1; }\n\n .control .icon:before{\n opacity: 1;\n flex: 1;\n align-self: center;\n content: \" \";\n display: inline-block;\n margin: 0 auto;\n\n /* triangles */\n width: 0;\n height: 0;\n border-style: solid;\n border-width: 16px 0 16px 23px;\n border-color: transparent transparent transparent ".concat(colors.gray, ";\n }\n\n .vdt-external-controls.").concat(classNames.onDark, " .control .icon:before{\n border-color: transparent transparent transparent ").concat(colors.light, ";\n }\n\n /* specific buttons */\n\n .control.").concat(buttonNames.start, ",\n .control.").concat(buttonNames.end, "{\n height: 20px;\n }\n\n .control.").concat(buttonNames.play, "{\n height: 30px;\n padding-top: 0px;\n padding-bottom: 0px;\n }\n\n .control.").concat(buttonNames.play, " .icon{\n height: 30px;\n }\n\n .control.").concat(buttonNames.ff, " .icon:before,\n .control.").concat(buttonNames.fb, " .icon:before,\n .control.").concat(buttonNames.end, " .icon:before,\n .control.").concat(buttonNames.start, " .icon:before{\n border-width: 10px 0 10px 16px;\n }\n\n .control.").concat(buttonNames.fb, " .icon,\n .control.").concat(buttonNames.start, "{\n transform: rotate(180deg);\n }\n\n .control.").concat(buttonNames.end, " .icon:after,\n .control.").concat(buttonNames.start, " .icon:after{\n content: \" \";\n width: 4px;\n height: 20px;\n display: inline-block;\n background: ").concat(colors.gray, ";\n }\n\n .vdt-external-controls.").concat(classNames.onDark, " .control.").concat(buttonNames.end, " .icon:after,\n .vdt-external-controls.").concat(classNames.onDark, " .control.").concat(buttonNames.start, " .icon:after{\n background: ").concat(colors.light, ";\n }\n\n /* stateful styles */\n .control.").concat(buttonNames.play, ".playing .icon:before{\n height: 30px;\n width: 6px;\n border-width: 0px 8px 0px 8px;\n border-color: transparent ").concat(colors.gray, " transparent ").concat(colors.gray, ";\n }\n\n .vdt-external-controls.").concat(classNames.onDark, " .control.").concat(buttonNames.play, ".playing .icon:before{\n border-color: transparent ").concat(colors.light, " transparent ").concat(colors.light, ";\n }\n\n .vdt-external-controls.").concat(classNames.loading, " .control.").concat(buttonNames.play, "{\n border-color: transparent #f00 transparent #f00;\n }\n");
147
+ class ExternalControls {
321
148
  constructor(props) {
322
149
  this.props = props;
323
-
150
+ reactiveSetters.apply(this);
324
151
  this.state = {
325
- selectedNode: null, // avoid updating values in a selected node
152
+ playing: false,
153
+ onDark: false,
154
+ loading: false
326
155
  };
327
-
328
- const classNames = [
329
- 'vdt-timecode',
330
- 'vjs-time-control', // will apply default videojs style to the whole container, centers things vertically
331
- (!(this.props.onInput)) ? 'duration' : '', // if no input capability, assume it's a duration timecode
332
- ];
156
+ this.shadowRoot = document.createElement('div');
157
+ this.shadowRoot.className = 'vdt-external-controls-wrapper';
333
158
  this.container = document.createElement('div');
334
- this.container.className = classNames.join(' ');
335
-
336
- this.nodeNames = [].concat(timeCodeNodeNames); // used internally
337
- this.nodes = {}; // store sub-element nodes for mapped access here
338
-
339
- this.nodeNames.forEach((nodeName, index) => {
340
- const node = document.createElement('input');
341
- node.className = nodeName;
342
- node.tabIndex = -1; // prevent default Tab behaviour for cycling inputs
343
-
344
- if (this.props.onInput) { // optional input capabilities
345
- node.maxLength = 2;
346
-
347
- node.onfocus = (e) => {
348
- this.handleInputFocus(e, nodeName);
349
- };
350
-
351
- node.onblur = (e) => {
352
- this.handleInputBlur(e, nodeName);
353
- };
354
-
355
- node.onmouseup = (e) => {
356
- e.preventDefault();
357
- e.currentTarget.select();
358
- };
359
-
360
- node.onkeydown = (e) => {
361
- this.handleTimeCodeInput(e, nodeName);
362
- };
363
-
364
- node.onkeyup = (e) => {
365
- this.handleTimeCodeInputOnKeyUp(e, nodeName);
366
- };
367
- } else {
368
- node.readOnly = true; // display-only timecode
369
- }
370
-
371
- this.nodes[nodeName] = node;
372
-
373
-
374
- const colon = document.createElement('span');
375
- colon.innerHTML = ':';
376
-
377
-
378
- this.container.appendChild(node);
379
- (index !== this.nodeNames.length - 1) && this.container.appendChild(colon);
380
- });
381
-
159
+ this.container.className = 'vdt-external-controls';
382
160
  if (this.props.target) {
383
- if (this.props.appendAfter) { // so it's possible to append after volume control, for example
384
- this.props.target.parentNode.insertBefore(this.container, this.props.target.nextSibling);
385
- } else {
386
- this.props.target.appendChild(this.container);
387
- }
388
-
389
- // start at 0 values for now, if player will "remember" playback position, revisit this
390
- this.reset();
391
- } else {
392
- // eslint-disable-next-line no-console
393
- console.error(`Unable to instantiate a new ${this.constructor.name} without target DOM node`); // no joy
161
+ this.props.target.appendChild(this.shadowRoot);
162
+ this.shadow = this.shadowRoot.attachShadow({
163
+ mode: 'open'
164
+ });
165
+ this.style = document.createElement('style');
166
+ this.style.type = 'text/css';
167
+ this.style.appendChild(document.createTextNode(CSS$2));
168
+ this.shadow.appendChild(this.style);
169
+ this.shadow.appendChild(this.container);
394
170
  }
171
+ this.handleNewProps(props);
172
+ this.bindEvents();
173
+ this.draw();
395
174
  }
396
-
397
- update(valuesObj) { // Object.keys and array.forEach are too slow, classic for is faster
398
- const startCount = this.nodeNames.length - 1;
399
- let i;
400
- for (i = startCount; i > -1; i--) { // updates follow frame -> seconds -> minutes -> hours order
401
- const key = this.nodeNames[i];
402
-
403
- if (typeof valuesObj[key] !== 'undefined' && (valuesObj[key] !== parseInt(this.nodes[key].value, 10)) && key !== this.state.selectedNode) { // re-render node if value is defineed and is different from current value
404
- const str = valuesObj[key].toString();
405
- this.nodes[key].value = this.toDigitPairStr(str);
175
+ draw() {
176
+ this.buttons = {};
177
+ const excludes = this.props.excludes || [];
178
+ buttons.forEach(buttonName => {
179
+ if (excludes.indexOf(buttonName) < 0) {
180
+ const btn = document.createElement('div');
181
+ btn.className = "control ".concat(buttonName);
182
+ btn.innerHTML = '<span class="icon"></span>';
183
+ btn.onclick = () => {
184
+ this.handlePress(buttonName);
185
+ };
186
+ this.buttons[buttonName] = btn;
187
+ this.container.appendChild(btn);
406
188
  }
407
- }
408
- }
409
-
410
- reset() {
411
- this.update({
412
- hours: 0,
413
- minutes: 0,
414
- seconds: 0,
415
- frame: 0,
416
189
  });
417
190
  }
418
-
419
- handleInputFocus(e, nodeName) {
420
- this.state.selectedNode = nodeName; // prevent updates on this input
421
- this.nodes[nodeName].select();
422
- this.props.onInputFocus && this.props.onInputFocus(nodeName);
423
- }
424
-
425
- handleInputBlur(e, nodeName) {
426
- this.state.selectedNode = null; // allow updates on this input
427
- this.props.onInputBlur && this.props.onInputBlur(nodeName);
428
- }
429
-
430
- sanitize(input) {
431
- const output = input.replace(/[^0-9]/g, '');
432
- return output;
433
- }
434
-
435
- toDigitPairStr(strValue) {
436
- return (strValue.length < 2) ? `0${strValue}` : strValue;
437
- }
438
-
439
- handleTimeCodeInput(e, nodeName) {
440
- const nodeIndex = this.nodeNames.indexOf(nodeName);
441
-
442
- switch (e.code) {
443
- case 'Enter': // do work before submitting input
444
- this.checkAndNormalizeAllInputs();
445
-
446
- this.props.onInput && this.props.onInput(e, nodeName, {
447
- hours: this.hours,
448
- minutes: this.minutes,
449
- seconds: this.seconds,
450
- frame: this.frame,
451
- });
452
-
453
- this[nodeName] = e.currentTarget.value;
454
- e.currentTarget.blur(); // done
455
- break;
456
- case 'Tab': // custom tab behaviour
457
- e.preventDefault();
458
-
459
- // tab trough inputs in a loop fashion
460
- if (!e.shiftKey) {
461
- if ((nodeIndex + 1) <= (this.nodeNames.length - 1)) {
462
- this.nodes[this.nodeNames[nodeIndex + 1]].focus(); // focus next
463
- } else {
464
- this.nodes[this.nodeNames[0]].focus(); // focus first
465
- }
466
- } else if ((nodeIndex - 1) >= 0) {
467
- this.nodes[this.nodeNames[nodeIndex - 1]].focus(); // focus previous
468
- } else {
469
- this.nodes[this.nodeNames[this.nodeNames.length - 1]].focus(); // focus last
470
- }
471
- break;
191
+ bindEvents() {
192
+ if (this.props.player && this.props.player.videojs) {
193
+ const {
194
+ player
195
+ } = this.props;
196
+ player.videojs.on('play', () => {
197
+ this.playing = true;
198
+ });
199
+ player.videojs.on('pause', () => {
200
+ this.playing = false;
201
+ });
202
+ player.videojs.on('stop', () => {
203
+ this.playing = false;
204
+ });
472
205
  }
473
206
  }
474
-
475
- handleTimeCodeInputOnKeyUp(e, nodeName) {
476
- /*
477
- cycle to next input if this one is done being populated,
478
- has to happen onkeyup, since the timecode is already populated
479
- with valid values most of the time, keydown would always trigger
480
- false positive length check.
481
-
482
- Also check if the input is a number (!isNaN),
483
- to prevent double-triggering Tab key or other logic
484
- */
485
- if (this.nodes[nodeName].value.length === 2 && !isNaN(e.key)) {
486
- const nodeIndex = this.nodeNames.indexOf(nodeName); // determine position in timecode
487
-
488
- if ((nodeIndex + 1) <= (this.nodeNames.length - 1)) { // check bounds
489
- this.nodes[this.nodeNames[nodeIndex + 1]].focus(); // focus next
207
+ handlePress(buttonName) {
208
+ if (this.props.player) {
209
+ const {
210
+ player
211
+ } = this.props;
212
+ if (player.osd) player.osd.disable();
213
+ switch (buttonName) {
214
+ case buttonNames.start:
215
+ player.toStart();
216
+ break;
217
+ case buttonNames.fb:
218
+ player.triggerHotkey('left');
219
+ break;
220
+ case buttonNames.play:
221
+ player.triggerHotkey('k');
222
+ break;
223
+ case buttonNames.ff:
224
+ player.triggerHotkey('right');
225
+ break;
226
+ case buttonNames.end:
227
+ player.toEnd();
228
+ break;
490
229
  }
491
- }
492
- }
493
-
494
- checkAndNormalizeAllInputs() {
495
- this.nodeNames.forEach((nodeName) => {
496
- if (this.nodes[nodeName].value.length < 1) { // somebody left the input blank
497
- this.nodes[nodeName].value = '00';
230
+ if (player.osd) player.osd.enable();
231
+ } else {
232
+ switch (buttonName) {
233
+ case buttonNames.start:
234
+ if (this.props.onPrev) this.props.onPrev();
235
+ break;
236
+ case buttonNames.fb:
237
+ if (this.props.onFB) this.props.onFB();
238
+ break;
239
+ case buttonNames.play:
240
+ if (this.playing) {
241
+ if (this.props.onPause) this.props.onPause();
242
+ } else if (this.props.onPlay) this.props.onPlay();
243
+ break;
244
+ case buttonNames.ff:
245
+ if (this.props.onFF) this.props.onFF();
246
+ break;
247
+ case buttonNames.end:
248
+ if (this.props.onNext) this.props.onNext();
249
+ break;
498
250
  }
499
-
500
- // make sure displayed timecode stays consistent after input and before time-based updates
501
- this.nodes[nodeName].value = this.toDigitPairStr(this.sanitize(this.nodes[nodeName].value));
502
- });
503
- }
504
-
505
- // setters and getters
506
-
507
- set frame(val) {
508
- this.nodes.frame.value = this.toDigitPairStr(val);
509
- }
510
-
511
- get frame() {
512
- return parseInt(this.nodes.frame.value, 10);
513
- }
514
-
515
- set seconds(val) {
516
- this.nodes.seconds.value = this.toDigitPairStr(val);
517
- }
518
-
519
- get seconds() {
520
- return parseInt(this.nodes.seconds.value, 10);
521
- }
522
-
523
- set minutes(val) {
524
- this.nodes.minutes.value = this.toDigitPairStr(val);
525
- }
526
-
527
- get minutes() {
528
- return parseInt(this.nodes.minutes.value, 10);
529
- }
530
-
531
- set hours(val) {
532
- this.nodes.hours.value = this.toDigitPairStr(val);
533
- }
534
-
535
- get hours() {
536
- return parseInt(this.nodes.hours.value, 10);
251
+ }
537
252
  }
538
-
539
- getFormatted(key) {
540
- return this.toDigitPairStr(this[key].toString());
253
+ set playing(bool) {
254
+ if (bool) {
255
+ this.state.playing = true;
256
+ this.buttons[buttonNames.play].classList.add('playing');
257
+ } else {
258
+ this.state.playing = false;
259
+ this.buttons[buttonNames.play].classList.remove('playing');
260
+ }
541
261
  }
542
-
543
- get smpte() {
544
- return {
545
- hours: this.hours,
546
- minutes: this.minutes,
547
- seconds: this.seconds,
548
- frame: this.frame,
549
- };
262
+ get playing() {
263
+ return this.state.playing;
550
264
  }
551
-
552
- set spmte(o) {
553
- this.update(o);
265
+ set onDark(bool) {
266
+ if (bool) {
267
+ this.state.onDark = true;
268
+ this.container.classList.add(classNames.onDark);
269
+ } else {
270
+ this.state.onDark = false;
271
+ this.container.classList.remove(classNames.onDark);
272
+ }
554
273
  }
555
-
556
- get smpteString() {
557
- return `${this.getFormatted('hours')}:${this.getFormatted('minutes')}:${this.getFormatted('seconds')}:${this.getFormatted('frame')}`;
274
+ get onDark() {
275
+ return this.container.classList.contains(classNames.onDark);
558
276
  }
559
-
560
- set external(bool) {
277
+ set loading(bool) {
561
278
  if (bool) {
562
- this.container.classList.add('external');
279
+ this.state.loading = true;
280
+ this.container.classList.add(classNames.loading);
563
281
  } else {
564
- this.container.classList.remove('external');
282
+ this.state.loading = false;
283
+ this.container.classList.remove(classNames.loading);
565
284
  }
566
285
  }
286
+ get loading() {
287
+ return this.state.loading;
288
+ }
289
+ }
290
+ ExternalControls.buttonNames = buttonNames;
567
291
 
568
- get external() {
569
- return (this.container.classList.contains('external'));
292
+ function _defineProperty(obj, key, value) {
293
+ if (key in obj) {
294
+ Object.defineProperty(obj, key, {
295
+ value: value,
296
+ enumerable: true,
297
+ configurable: true,
298
+ writable: true
299
+ });
300
+ } else {
301
+ obj[key] = value;
570
302
  }
303
+ return obj;
571
304
  }
572
305
 
573
- /* eslint-disable no-plusplus */
574
-
575
306
  class HelpOverlay {
576
307
  constructor(props) {
577
308
  this.props = props;
578
-
579
309
  this.state = {
580
- active: this.props.active || false,
310
+ active: this.props.active || false
581
311
  };
582
-
583
312
  this.container = document.createElement('div');
584
313
  this.container.className = 'vdt-help-overlay';
585
314
  this.container.innerHTML = '';
586
315
  this.container.onclick = () => {
587
316
  this.hide();
588
317
  };
589
-
590
- this.props.target && this.props.target.appendChild(this.container);
591
-
318
+ if (this.props.target) this.props.target.appendChild(this.container);
592
319
  this.draw();
593
320
  }
594
-
595
- draw() { // draws keyboard controls
596
- let tableRowMarkup = ''; // will insert a DOM string here
597
- const rows = [
598
- ['⎵', 'Play / Pause'],
599
- ['⇧', 'Increase volume '],
600
- ['⇩', 'Decrease volume'],
601
- ['⇨', 'Step frame forward'],
602
- ['⇦', 'Step frame backward'],
603
- ['M', 'Mute / Unmute'],
604
-
605
- ['J', 'Seek 10 seconds backward'],
606
- ['K', 'Play / Pause'],
607
- ['L', 'Seek 10 seconds forward'],
608
- ['C', 'Toggle subtitles'],
609
- ['N', 'Toggle settings menu'],
610
- ['H', 'Toggle this help overlay'],
611
- ];
612
-
613
-
614
- // split the data for 2 columns
321
+ draw() {
322
+ let tableRowMarkup = '';
323
+ const rows = [['', 'Play / Pause'], ['⇧', 'Increase volume '], ['⇩', 'Decrease volume'], ['⇨', 'Step frame forward'], ['⇦', 'Step frame backward'], ['M', 'Mute / Unmute'], ['J', 'Seek 10 seconds backward'], ['K', 'Play / Pause'], ['L', 'Seek 10 seconds forward'], ['C', 'Toggle subtitles'], ['N', 'Toggle settings menu'], ['H', 'Toggle this help overlay']];
615
324
  const halfpointIndex = Math.round(rows.length / 2);
616
- const columns = [
617
- rows.slice(0, halfpointIndex),
618
- rows.slice(halfpointIndex, rows.length),
619
- ];
620
-
621
- // table will niceley auto adjust td's according to length
622
- for (let i = 0; i < halfpointIndex; i++) {
623
- tableRowMarkup += `
624
- <tr>
625
- <td><div>${columns[0][i][0]}</div></td>
626
- <td>- ${columns[0][i][1]}</td>
627
-
628
- <td>${(columns[1][i]) ? `<div>${columns[1][i][0]}</div>` : ''}</td>
629
- <td>${(columns[1][i]) ? `- ${columns[1][i][1]}` : ''}</td>
630
- </tr>
631
- `;
325
+ const columns = [rows.slice(0, halfpointIndex), rows.slice(halfpointIndex, rows.length)];
326
+ for (let i = 0; i < halfpointIndex; i += 1) {
327
+ tableRowMarkup += "\n <tr>\n <td><div>".concat(columns[0][i][0], "</div></td>\n <td>- ").concat(columns[0][i][1], "</td>\n\n <td>").concat(columns[1][i] ? "<div>".concat(columns[1][i][0], "</div>") : '', "</td>\n <td>").concat(columns[1][i] ? "- ".concat(columns[1][i][1]) : '', "</td>\n </tr>\n ");
632
328
  }
633
-
634
- /*
635
- insert table row markup in the table;
636
- note that there are two tables going
637
- on here - one is for vertically centering the
638
- content of help overlay (the other table)
639
- */
640
-
641
- this.container.innerHTML = `
642
- <table>
643
- <tbody>
644
- <tr>
645
- <td class="vdt-help-contents">
646
- <table><tbody>${tableRowMarkup}</tbody></table>
647
- </td>
648
- </tr>
649
- </tbody>
650
- </table>
651
- `;
329
+ this.container.innerHTML = "\n <table>\n <tbody>\n <tr>\n <td class=\"vdt-help-contents\">\n <table><tbody>".concat(tableRowMarkup, "</tbody></table>\n </td>\n </tr>\n </tbody>\n </table>\n ");
652
330
  }
653
-
654
331
  show() {
655
332
  if (!this.state.active) {
656
333
  this.container.classList.add('active');
657
334
  this.state.active = true;
658
-
659
- mousetrap__default["default"].bind('esc', () => { this.hide(); });
660
-
661
- this.props.onToggle && this.props.onToggle(true);
335
+ mousetrap__default["default"].bind('esc', () => {
336
+ this.hide();
337
+ });
338
+ if (this.props.onToggle) this.props.onToggle(true);
662
339
  }
663
340
  }
664
-
665
341
  hide() {
666
342
  if (this.state.active) {
667
343
  this.container.classList.remove('active');
668
344
  this.state.active = false;
669
-
670
345
  mousetrap__default["default"].unbind('esc');
671
-
672
- this.props.onToggle && this.props.onToggle(false);
346
+ if (this.props.onToggle) this.props.onToggle(false);
673
347
  }
674
348
  }
675
-
676
349
  toggle() {
677
350
  if (this.state.active) {
678
351
  this.hide();
@@ -682,9 +355,8 @@
682
355
  }
683
356
  }
684
357
 
685
- /* eslint-disable no-param-reassign */
686
-
687
-
358
+ function ownKeys$1(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
359
+ function _objectSpread$1(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys$1(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys$1(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
688
360
  const constants$1 = Object.freeze({
689
361
  className: 'vdt-menu',
690
362
  menuClassName: 'menu',
@@ -697,192 +369,50 @@
697
369
  textColorDark: '#333',
698
370
  textColorLight: 'rgba(255,255,255)',
699
371
  bgColorLight: 'rgba(255,255,255,.2)',
700
- bgColorSelected: 'rgba(255,255,255,.1)',
701
- },
702
- });
703
-
704
-
705
- const CSS$2 = () => `
706
- .${constants$1.menuClassName}{
707
- position: relative;
708
- height: 100%;
709
- width: 200%;
710
- // min-width: 200px;
711
- right: 0px;
712
- bottom: 0px;
713
- background: ${constants$1.defaults.bgColor};
714
- z-index: 1px;
715
- transform: translate3d(100%, 0px, 0px);
716
- transition: all 300ms ease-in-out;
717
- box-shadow: 0px 0px 30px rgb;
718
- -webkit-backdrop-filter: blur(10px);
719
- overflow: hidden;
720
- }
721
-
722
- .${constants$1.menuClassName} .level-1{
723
- float: left;
724
- }
725
-
726
- .${constants$1.menuClassName} div h3{
727
- padding: 16px 24px;
728
- font-size: 12px;
729
- border-bottom: 1px solid ${constants$1.defaults.bgColorLight}
730
- }
731
-
732
- .${constants$1.menuClassName} .level-2 h3{
733
- cursor: pointer;
734
- position: relative;
735
- }
736
- .${constants$1.menuClassName} .level-2 h3:before{
737
- content: '◂';
738
- margin-left: -12px;
739
- width: 12px;
740
- display: inline-block;
741
- position: absolute;
742
- margin-top: -1px;
743
- }
744
-
745
- .${constants$1.menuClassName} .level-1,
746
- .${constants$1.menuClassName} .level-2{
747
- transform: translate3d(0px, 0px, 0px);
748
- transition: all 300ms ease-in-out;
749
- width: 50%;
750
- height: 100%;
751
- float: left;
752
- }
753
-
754
- .${constants$1.menuClassName}.sub-level .level-1,
755
- .${constants$1.menuClassName}.sub-level .level-2{
756
- transform: translate3d(-100%, 0px, 0px);
757
- }
758
-
759
- .${constants$1.menuClassName} table{
760
- border-collapse: collapse;
761
- width: 100%;
762
- }
763
-
764
- .${constants$1.menuClassName}.open{
765
- transform: translate3d(0px, 0px, 0px);
766
- }
767
-
768
- .${constants$1.menuClassName} tr{
769
- cursor: pointer;
770
- height: 40px;
771
- }
772
-
773
- .${constants$1.menuClassName} tr.selected{
774
- background: ${constants$1.defaults.bgColorSelected};
775
- }
776
-
777
-
778
- .${constants$1.menuClassName} tr td{
779
- color: white;
780
- font-size: 15px;
781
- vertical-align: middle;
782
- }
783
-
784
-
785
- .${constants$1.menuClassName} tr:hover td{
786
- color: white;
787
- background: ${constants$1.defaults.bgColorLight};
788
- }
789
-
790
- .${constants$1.menuClassName} tr td.title{
791
- padding-top: 12px;
792
- padding-bottom: 12px;
793
- font-weight: 300;
794
- font-family: Helvetica, Arial, sans-serif;
795
- }
796
-
797
- .${constants$1.menuClassName} tr td.caret{
798
- font-size: 12px;
799
- }
800
-
801
- .${constants$1.menuClassName} tr td:first-child{
802
- padding-left: 24px;
803
- }
804
-
805
- .${constants$1.menuClassName} tr td:last-child{
806
- padding-right: 16px;
807
- }
808
-
809
- .${constants$1.menuClassName} tr.has-items td:last-child{
810
- text-align: right;
372
+ bgColorSelected: 'rgba(255,255,255,.1)'
811
373
  }
812
-
813
- .${constants$1.menuClassName} tr.has-items td.subtitle{
814
- font-size: 12px;
815
- text-align: right;
816
- color: ${constants$1.defaults.textColorLight};
817
- }
818
- `;
819
-
374
+ });
375
+ const CSS$1 = () => "\n .".concat(constants$1.menuClassName, "{\n position: relative;\n height: 100%;\n width: 200%;\n // min-width: 200px;\n right: 0px;\n bottom: 0px;\n background: ").concat(constants$1.defaults.bgColor, ";\n z-index: 1px;\n transform: translate3d(100%, 0px, 0px);\n transition: all 300ms ease-in-out;\n box-shadow: 0px 0px 30px rgb;\n -webkit-backdrop-filter: blur(10px);\n overflow: hidden;\n }\n\n .").concat(constants$1.menuClassName, " .level-1{\n float: left;\n }\n\n .").concat(constants$1.menuClassName, " div h3{\n padding: 16px 24px;\n font-size: 12px;\n border-bottom: 1px solid ").concat(constants$1.defaults.bgColorLight, "\n }\n\n .").concat(constants$1.menuClassName, " .level-2 h3{\n cursor: pointer;\n position: relative;\n }\n .").concat(constants$1.menuClassName, " .level-2 h3:before{\n content: '\u25C2';\n margin-left: -12px;\n width: 12px;\n display: inline-block;\n position: absolute;\n margin-top: -1px;\n }\n\n .").concat(constants$1.menuClassName, " .level-1,\n .").concat(constants$1.menuClassName, " .level-2{\n transform: translate3d(0px, 0px, 0px);\n transition: all 300ms ease-in-out;\n width: 50%;\n height: 100%;\n float: left;\n }\n\n .").concat(constants$1.menuClassName, ".sub-level .level-1,\n .").concat(constants$1.menuClassName, ".sub-level .level-2{\n transform: translate3d(-100%, 0px, 0px);\n }\n\n .").concat(constants$1.menuClassName, " table{\n border-collapse: collapse;\n width: 100%;\n }\n\n .").concat(constants$1.menuClassName, ".open{\n transform: translate3d(0px, 0px, 0px);\n }\n\n .").concat(constants$1.menuClassName, " tr{\n cursor: pointer;\n height: 40px;\n }\n\n .").concat(constants$1.menuClassName, " tr.selected{\n background: ").concat(constants$1.defaults.bgColorSelected, ";\n }\n\n\n .").concat(constants$1.menuClassName, " tr td{\n color: white;\n font-size: 15px;\n vertical-align: middle;\n }\n\n \n .").concat(constants$1.menuClassName, " tr:hover td{\n color: white;\n background: ").concat(constants$1.defaults.bgColorLight, ";\n }\n\n .").concat(constants$1.menuClassName, " tr td.title{\n padding-top: 12px;\n padding-bottom: 12px;\n font-weight: 300;\n font-family: Helvetica, Arial, sans-serif; \n }\n\n .").concat(constants$1.menuClassName, " tr td.caret{\n font-size: 12px;\n }\n\n .").concat(constants$1.menuClassName, " tr td:first-child{\n padding-left: 24px;\n }\n\n .").concat(constants$1.menuClassName, " tr td:last-child{\n padding-right: 16px;\n }\n\n .").concat(constants$1.menuClassName, " tr.has-items td:last-child{\n text-align: right;\n }\n\n .").concat(constants$1.menuClassName, " tr.has-items td.subtitle{\n font-size: 12px;\n text-align: right;\n color: ").concat(constants$1.defaults.textColorLight, ";\n }\n");
820
376
  class MenuDisplayItem {
821
377
  constructor(props) {
822
378
  this.props = props;
823
-
824
- const classNames = [
825
- constants$1.menuItemClassName,
826
- this.props.items && 'has-items',
827
- this.props.selected && 'selected',
828
- ];
379
+ const classNames = [constants$1.menuItemClassName, this.props.items && 'has-items', this.props.selected && 'selected'];
829
380
  this.container = document.createElement('tr');
830
381
  this.container.className = classNames.join(' ');
831
-
832
-
833
- this.props.target && this.props.target.appendChild(this.container);
834
-
835
- // bind click
836
- this.container.onclick = (event) => {
837
- this.props.onClick && this.props.onClick(event);
382
+ if (this.props.target) this.props.target.appendChild(this.container);
383
+ this.container.onclick = event => {
384
+ if (this.props.onClick) this.props.onClick(event);
838
385
  };
839
-
840
386
  this.render();
841
387
  }
842
-
843
388
  render() {
844
- if (this.props.items) { // will transition to 2nd level
845
- this.container.innerHTML = `
846
- <td class="title"></td>
847
- <td class="subtitle"></td>
848
- <td class="caret">▷</td>
849
- `;
389
+ if (this.props.items) {
390
+ this.container.innerHTML = "\n <td class=\"title\"></td>\n <td class=\"subtitle\"></td>\n <td class=\"caret\">\u25B7</td>\n ";
850
391
  } else {
851
- this.container.innerHTML = `
852
- <td class="title"></td>
853
- `;
392
+ this.container.innerHTML = "\n <td class=\"title\"></td>\n ";
854
393
  }
855
-
856
394
  const titleNode = this.container.getElementsByClassName('title')[0];
857
- const subtitleNode = (this.props.items) ? this.container.getElementsByClassName('subtitle')[0] : false;
858
-
395
+ const subtitleNode = this.props.items ? this.container.getElementsByClassName('subtitle')[0] : false;
859
396
  titleNode.textContent = this.props.title;
860
- subtitleNode && (subtitleNode.textContent = this.props.subtitle);
397
+ if (subtitleNode) subtitleNode.textContent = this.props.subtitle;
861
398
  }
862
399
  }
863
-
864
400
  class Menu {
865
401
  constructor(props) {
866
402
  this.props = props;
867
-
868
403
  this.container = document.createElement('div');
869
- this.container.className = `${constants$1.className}`;
870
-
404
+ this.container.className = "".concat(constants$1.className);
871
405
  this.state = {
872
406
  opened: false,
873
407
  selectedItem: 0,
874
408
  items: [],
875
- subItems: [],
409
+ subItems: []
876
410
  };
877
-
878
411
  this.mount();
879
412
  this.draw();
880
413
  }
881
-
882
414
  mount() {
883
- // this.props.target && this.props.target.appendChild(this.container)
884
415
  if (this.props.target) {
885
- // container node, acts as an overlay in is stylable from outside
886
416
  this.container.style.position = 'absolute';
887
417
  this.container.style.bottom = this.container.style.right = '0px';
888
418
  this.container.style.height = '100%';
@@ -890,69 +420,53 @@
890
420
  this.container.style.width = '30%';
891
421
  this.container.style.minWidth = '250px';
892
422
  this.container.style.maxWidth = '320px';
893
-
894
- // shadow node encapsulates style and prevents css collisions
895
- this.shadow = this.container.attachShadow({ mode: 'open' });
896
- this.props.target && this.props.target.appendChild(this.container);
897
-
898
-
899
- // node for menu itself
423
+ this.shadow = this.container.attachShadow({
424
+ mode: 'open'
425
+ });
426
+ if (this.props.target) this.props.target.appendChild(this.container);
900
427
  this.menuContainer = document.createElement('div');
901
428
  this.menuContainer.className = 'menu';
902
-
903
429
  this.shadow.appendChild(this.menuContainer);
904
-
905
- // attach styles
906
430
  this.style = document.createElement('style');
907
431
  this.style.type = 'text/css';
908
- this.style.innerHTML = CSS$2();
432
+ this.style.innerHTML = CSS$1();
909
433
  this.shadow.appendChild(this.style);
910
434
  }
911
435
  }
912
-
913
- draw() { // first draw
436
+ draw() {
914
437
  const mainDiv = document.createElement('div');
915
438
  mainDiv.className = 'level-1';
916
439
  this.mainLevel = document.createElement('table');
917
440
  mainDiv.innerHTML = '<h3>Settings</h3>';
918
441
  mainDiv.appendChild(this.mainLevel);
919
-
920
442
  const subDiv = document.createElement('div');
921
443
  subDiv.className = 'level-2';
922
444
  this.subLvlTitle = document.createElement('h3');
923
- this.subLvlTitle.onclick = () => { this.up(); };
445
+ this.subLvlTitle.onclick = () => {
446
+ this.up();
447
+ };
924
448
  subDiv.appendChild(this.subLvlTitle);
925
449
  this.secondaryLevel = document.createElement('table');
926
450
  subDiv.appendChild(this.secondaryLevel);
927
-
928
-
929
451
  this.menuContainer.appendChild(mainDiv);
930
452
  this.menuContainer.appendChild(subDiv);
931
-
932
- this.props.items.forEach((menuItem) => {
933
- // get selected item title if such exists
453
+ this.props.items.forEach(menuItem => {
934
454
  const activeTitle = menuItem.items[menuItem.getActiveIndex(menuItem.items)].title || '';
935
- const displayItem = new MenuDisplayItem({
455
+ const displayItem = new MenuDisplayItem(_objectSpread$1({
936
456
  target: this.mainLevel,
937
457
  subtitle: activeTitle,
938
458
  onClick: () => {
939
459
  this.handleSupItemSelected(menuItem);
940
- },
941
- ...menuItem,
942
- });
943
-
460
+ }
461
+ }, menuItem));
944
462
  this.state.items.push(displayItem);
945
463
  });
946
464
  }
947
-
948
465
  open() {
949
466
  this.render();
950
-
951
467
  this.menuContainer.classList.add('open');
952
- this.props.onOpen && this.props.onOpen();
468
+ if (this.props.onOpen) this.props.onOpen();
953
469
  this.state.opened = true;
954
-
955
- // close the menu when user clicks outside it
956
470
  if (!this.handleClicksWhenMenuOpened) {
957
471
  this.handleClicksWhenMenuOpened = function (event) {
958
472
  if (this.state.opened) {
@@ -964,25 +478,19 @@
964
478
  }
965
479
  }.bind(this);
966
480
  }
967
-
968
481
  this.handleClicksWhenMenuOpenedTimeout = setTimeout(() => {
969
482
  document.addEventListener('click', this.handleClicksWhenMenuOpened);
970
- mousetrap__default["default"].bind('esc', this.close.bind(this)); // esc closes menu aswell
483
+ mousetrap__default["default"].bind('esc', this.close.bind(this));
971
484
  }, constants$1.animationLength);
972
485
  }
973
-
974
486
  close() {
975
487
  this.menuContainer.classList.remove('open');
976
488
  this.state.opened = false;
977
-
978
- this.handleClicksWhenMenuOpenedTimeout && clearTimeout(this.handleClicksWhenMenuOpenedTimeout);
489
+ if (this.handleClicksWhenMenuOpenedTimeout) clearTimeout(this.handleClicksWhenMenuOpenedTimeout);
979
490
  document.removeEventListener('click', this.handleClicksWhenMenuOpened);
980
-
981
- this.props.onClose && this.props.onClose();
982
-
491
+ if (this.props.onClose) this.props.onClose();
983
492
  mousetrap__default["default"].unbind('esc');
984
493
  }
985
-
986
494
  toggle() {
987
495
  if (this.state.opened) {
988
496
  this.close();
@@ -990,56 +498,48 @@
990
498
  this.open();
991
499
  }
992
500
  }
993
-
994
- up() { // go to level 1
501
+ up() {
995
502
  this.menuContainer.classList.remove(constants$1.subLvlClassName);
996
503
  this.render();
997
504
  }
998
-
999
505
  handleSupItemSelected(item) {
1000
506
  this.subLvlTitle.textContent = item.title;
1001
507
  this.renderSubItems(item.items, item.getActiveIndex(item.items));
1002
508
  this.menuContainer.classList.add(constants$1.subLvlClassName);
1003
509
  }
1004
-
1005
- renderSubItems(items, activeIndex = false) {
510
+ renderSubItems(items) {
511
+ let activeIndex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
1006
512
  this.secondaryLevel.innerHTML = '';
1007
-
1008
513
  const subItemRefs = [];
1009
514
  items.forEach((menuItem, index) => {
1010
- const displayItem = new MenuDisplayItem({
1011
- target: this.secondaryLevel,
1012
- ...menuItem,
1013
- onClick: (event) => {
515
+ const displayItem = new MenuDisplayItem(_objectSpread$1(_objectSpread$1({
516
+ target: this.secondaryLevel
517
+ }, menuItem), {}, {
518
+ onClick: event => {
1014
519
  this.handleSubItemSelected(event, menuItem);
1015
520
  },
1016
- selected: (index === activeIndex), // mark selected
1017
- });
1018
-
521
+ selected: index === activeIndex
522
+ }));
1019
523
  subItemRefs.push(displayItem);
1020
524
  });
1021
-
1022
525
  this.state.subItems = subItemRefs;
1023
526
  }
1024
-
1025
527
  handleSubItemSelected(event, item) {
1026
528
  this.close();
1027
- item.onClick && item.onClick(event, item);
529
+ if (item.onClick) item.onClick(event, item);
1028
530
  this.up();
1029
531
  }
1030
-
1031
532
  get opened() {
1032
533
  return this.state.opened;
1033
534
  }
1034
-
1035
535
  set opened(bool) {
1036
536
  this.state.opened = bool;
1037
537
  }
1038
-
1039
538
  render() {
1040
- // update selections at level 1
1041
539
  this.state.items.forEach((menuDisplayItem, index) => {
1042
- const { items } = this.props;
540
+ const {
541
+ items
542
+ } = this.props;
1043
543
  const activeIndex = items[index].getActiveIndex(items[index].items);
1044
544
  const activeTitle = items[index].items[activeIndex].title;
1045
545
  menuDisplayItem.props.subtitle = activeTitle;
@@ -1048,224 +548,86 @@
1048
548
  }
1049
549
  }
1050
550
 
1051
- /* eslint-disable max-len */
551
+ class OSD {
552
+ constructor(props) {
553
+ this.props = props;
554
+ this.state = {
555
+ enabled: true
556
+ };
557
+ this.container = document.createElement('div');
558
+ this.container.className = 'vdt-player-osd';
559
+ this.container.innerHTML = "\n <table>\n <tbody>\n <td>\n <div class=\"osd-feedback-icon\"></div>\n </td>\n </tbody>\n </table>\n ";
560
+ this.iconEl = this.container.getElementsByClassName('osd-feedback-icon')[0];
561
+ if (this.props.target) this.props.target.appendChild(this.container);
562
+ }
563
+ feedback(feedbackType) {
564
+ let details = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
565
+ if (!this.state.enabled) {
566
+ return;
567
+ }
568
+ let className;
569
+ switch (feedbackType) {
570
+ case osd.pause:
571
+ className = 'pause';
572
+ break;
573
+ case osd.play:
574
+ className = 'play';
575
+ break;
576
+ case osd.volchange:
577
+ const currentVolumeLevel = details.volume;
578
+ let perceivedVolumeLevel;
579
+ if (currentVolumeLevel === 0) {
580
+ perceivedVolumeLevel = 'null';
581
+ } else if (currentVolumeLevel > 0.65) {
582
+ perceivedVolumeLevel = 'high';
583
+ } else if (currentVolumeLevel < 0.35) {
584
+ perceivedVolumeLevel = 'low';
585
+ } else {
586
+ perceivedVolumeLevel = 'mid';
587
+ }
588
+ className = "volchange ".concat(perceivedVolumeLevel);
589
+ break;
590
+ case osd.jumpForward:
591
+ className = 'forward-jump';
592
+ break;
593
+ case osd.jumpBackward:
594
+ className = 'backward-jump';
595
+ break;
596
+ }
597
+ if (this.osdTimeout) {
598
+ this.container.classList.remove('active');
599
+ clearTimeout(this.osdTimeout);
600
+ }
601
+ this.container.classList.add('active');
602
+ this.osdTimeout = setTimeout(() => {
603
+ this.container.classList.remove('active');
604
+ }, 200);
605
+ this.iconEl.className = "osd-feedback-icon ".concat(className);
606
+ }
607
+ disable() {
608
+ this.state.enabled = false;
609
+ }
610
+ enable() {
611
+ this.state.enabled = true;
612
+ }
613
+ }
1052
614
 
1053
615
  const constants = Object.freeze({
1054
616
  className: 'vdt-seekbar',
1055
617
  defaults: {
1056
618
  bgColor: '#fafafa',
1057
619
  fillColor: '#f00',
1058
- regionColor: '#f6e58d', // yellow
620
+ regionColor: '#f6e58d',
1059
621
  markerWidth: 16,
1060
622
  regionAccentColor: '#00a8ff',
1061
- transitionTime: 300, // ms
623
+ transitionTime: 300
1062
624
  },
1063
625
  markerTypes: {
1064
626
  in: 'in',
1065
- out: 'out',
1066
- },
627
+ out: 'out'
628
+ }
1067
629
  });
1068
-
1069
- const CSS$1 = (bgColor, fillColor) => `
1070
- .${constants.className}{
1071
- user-select: none;
1072
- background: ${colors.light};
1073
- height: 6px;
1074
- width: 100%;
1075
- position: relative;
1076
- cursor: pointer;
1077
- z-index: 5;
1078
- }
1079
-
1080
- .${classNames.onDark}.${constants.className}{
1081
- background: ${colors.gray};
1082
- }
1083
-
1084
- .fill, .load-indicator{
1085
- height: 100%;
1086
- display: inline-block;
1087
- position: absolute;
1088
- left: 0px;
1089
- top: 0px;
1090
- background: ${fillColor};
1091
- transition: width ${constants.defaults.transitionTime}ms linear;
1092
- z-index: 2;
1093
- }
1094
-
1095
- .load-indicator{
1096
- background: #e1e1e1;
1097
- z-index: 1;
1098
- opacity: .2;
1099
- transition: none;
1100
- }
1101
-
1102
- .${classNames.onDark}.${constants.className} .load-indicator{
1103
- background: white;
1104
- }
1105
-
1106
- .${constants.className}.dragging .fill{
1107
- transition: none;
1108
- }
1109
-
1110
-
1111
- .marker, .region{
1112
- position: absolute;
1113
- top: 0px;
1114
- height: 100%;
1115
- background: cyan;
1116
- width: 1px;
1117
- display: inline-block;
1118
- z-index: 4;
1119
- }
1120
-
1121
- .marker{
1122
- transition: opacity ${constants.defaults.transitionTime}ms linear;
1123
- }
1124
-
1125
- .marker{ display: none; }
1126
- .marker.active{ display: initial; }
1127
-
1128
- .marker .handle{
1129
- position: absolute;
1130
- bottom: 10px; right: 0px;
1131
- width: ${constants.defaults.markerWidth}px;
1132
- height: 28px;
1133
- background: #999;
1134
- border-radius: 3px;
1135
- cursor: col-resize;
1136
- z-index: 3;
1137
- box-sizing: border-box;
1138
- }
1139
-
1140
- .marker.regional .handle{
1141
- background: ${constants.defaults.regionAccentColor};
1142
- box-shadow: -2px 1px 1px rgba(0,0,0,.1);
1143
- }
1144
-
1145
- .marker.regional.in .handle{
1146
- box-shadow: 2px 1px 1px rgba(0,0,0,.1);
1147
- }
1148
-
1149
- .marker.in .handle{
1150
- border-bottom-left-radius: 40px;
1151
- }
1152
-
1153
- .marker.out .handle{
1154
- left: 1px;
1155
- right: initial;
1156
- border-bottom-right-radius: 20px;
1157
- }
1158
-
1159
- .handle:active{
1160
- background: ${constants.defaults.regionColor};
1161
- }
1162
-
1163
- .region{
1164
- position: absolute;
1165
- width: auto;
1166
- background: ${constants.defaults.regionColor};
1167
- opacity: .7;
1168
- }
1169
-
1170
- .region.nav{
1171
- background: transparent;
1172
- opacity: 1;
1173
- border-left: 1px solid cyan;
1174
- }
1175
-
1176
- .region.nav .bubble,
1177
- .region.nav:after{
1178
- content: " ";
1179
- height: 16px;
1180
- bottom: 10px;
1181
- width: 16px;
1182
- border-radius: 16px;
1183
- background: ${constants.defaults.regionAccentColor};
1184
- position: absolute;
1185
- margin-left: -8px;
1186
- transition: transform 200ms ease-out;
1187
- transform: translate3d(0px, 0px, 0px);
1188
- transform-origin: bottom center;
1189
- }
1190
-
1191
- .region.nav .popout{
1192
- width: 160px;
1193
- z-index: 4;
1194
- background: rgba(28, 28, 28, .7);
1195
- color: white;
1196
- font-size: 14px;
1197
- min-height: 100px;
1198
- bottom: 16px;
1199
- left: -80px;
1200
- position: absolute;
1201
- border-radius: 8px;
1202
- -webkit-backdrop-filter: blur(10px);
1203
- font-family: Arial, Helvetica, sans-serif;
1204
- overflow: hidden;
1205
- opacity: 0;
1206
- visibility: hidden;
1207
- }
1208
-
1209
- .region.nav.right-aligned .popout{
1210
- right: 0px;
1211
- left: initial;
1212
- }
1213
-
1214
- .region.nav.left-aligned .popout{
1215
- left: -32px;
1216
- }
1217
-
1218
- .region.nav .popout .title{
1219
- padding: 4px 14px;
1220
- text-align: center;
1221
- color: black;
1222
- background: white;
1223
- }
1224
-
1225
- .region.nav .popout .annotation{
1226
- padding: 8px 12px;
1227
- font-weight: light;
1228
- color: rgba(255,255,255,.9);
1229
- }
1230
-
1231
- .region.nav:after{
1232
- width: 1px;
1233
- left: 8px;
1234
- height: 10px;
1235
- bottom: 0px;
1236
- background: #00a8ff;
1237
- }
1238
-
1239
- .region.nav:hover{
1240
- background: rgba(28, 28, 28, .5);
1241
- }
1242
-
1243
- .region.nav.has-popout:hover .bubble{
1244
- background: rgba(28, 28, 28, .8);
1245
- opacity: 1;
1246
- z-index: 3;
1247
- }
1248
-
1249
- .region.nav:hover:after,
1250
- .region.nav:hover:before{
1251
- background: rgba(28, 28, 28, .8);
1252
- }
1253
-
1254
- .region.nav:hover .popout{
1255
- opacity: 1;
1256
- visibility: visible;
1257
- }
1258
-
1259
- .${constants.className}.dragging .bubble{
1260
- transform: scale3d(.75,.75,.75);
1261
- }
1262
-
1263
- .${constants.className}.hide-markers .marker{
1264
- opacity: 0;
1265
- }
1266
-
1267
- `;
1268
-
630
+ const CSS = (bgColor, fillColor) => "\n .".concat(constants.className, "{\n user-select: none;\n background: ").concat(colors.light, ";\n height: 6px;\n width: 100%;\n position: relative;\n cursor: pointer;\n z-index: 5;\n }\n\n .").concat(classNames.onDark, ".").concat(constants.className, "{\n background: ").concat(colors.gray, ";\n }\n\n .fill, .load-indicator{\n height: 100%;\n display: inline-block;\n position: absolute;\n left: 0px;\n top: 0px;\n background: ").concat(fillColor, ";\n transition: width ").concat(constants.defaults.transitionTime, "ms linear;\n z-index: 2;\n }\n\n .load-indicator{\n background: #e1e1e1;\n z-index: 1;\n opacity: .2;\n transition: none;\n }\n\n .").concat(classNames.onDark, ".").concat(constants.className, " .load-indicator{\n background: white;\n }\n\n .").concat(constants.className, ".dragging .fill{\n transition: none;\n }\n\n\n .marker, .region{\n position: absolute;\n top: 0px;\n height: 100%;\n background: cyan;\n width: 1px;\n display: inline-block;\n z-index: 4;\n }\n\n .marker{\n transition: opacity ").concat(constants.defaults.transitionTime, "ms linear;\n }\n\n .marker{ display: none; }\n .marker.active{ display: initial; }\n\n .marker .handle{\n position: absolute;\n bottom: 10px; right: 0px;\n width: ").concat(constants.defaults.markerWidth, "px;\n height: 28px;\n background: #999;\n border-radius: 3px;\n cursor: col-resize;\n z-index: 3;\n box-sizing: border-box;\n }\n\n .marker.regional .handle{\n background: ").concat(constants.defaults.regionAccentColor, ";\n box-shadow: -2px 1px 1px rgba(0,0,0,.1);\n }\n\n .marker.regional.in .handle{\n box-shadow: 2px 1px 1px rgba(0,0,0,.1);\n }\n\n .marker.in .handle{\n border-bottom-left-radius: 40px;\n }\n\n .marker.out .handle{\n left: 1px;\n right: initial;\n border-bottom-right-radius: 20px;\n }\n\n .handle:active{\n background: ").concat(constants.defaults.regionColor, ";\n }\n\n .region{\n position: absolute;\n width: auto;\n background: ").concat(constants.defaults.regionColor, ";\n opacity: .7;\n }\n\n .region.nav{\n background: transparent;\n opacity: 1;\n border-left: 1px solid cyan;\n }\n\n .region.nav .bubble,\n .region.nav:after{\n content: \" \";\n height: 16px;\n bottom: 10px;\n width: 16px;\n border-radius: 16px;\n background: ").concat(constants.defaults.regionAccentColor, ";\n position: absolute;\n margin-left: -8px;\n transition: transform 200ms ease-out;\n transform: translate3d(0px, 0px, 0px);\n transform-origin: bottom center;\n }\n\n .region.nav .popout{\n width: 160px;\n z-index: 4;\n background: rgba(28, 28, 28, .7);\n color: white;\n font-size: 14px;\n min-height: 100px;\n bottom: 16px;\n left: -80px;\n position: absolute;\n border-radius: 8px;\n -webkit-backdrop-filter: blur(10px);\n font-family: Arial, Helvetica, sans-serif;\n overflow: hidden;\n opacity: 0;\n visibility: hidden;\n }\n\n .region.nav.right-aligned .popout{\n right: 0px;\n left: initial;\n }\n\n .region.nav.left-aligned .popout{\n left: -32px;\n } \n\n .region.nav .popout .title{\n padding: 4px 14px;\n text-align: center;\n color: black;\n background: white;\n }\n\n .region.nav .popout .annotation{\n padding: 8px 12px;\n font-weight: light;\n color: rgba(255,255,255,.9);\n }\n\n .region.nav:after{\n width: 1px;\n left: 8px;\n height: 10px;\n bottom: 0px;\n background: #00a8ff;\n }\n\n .region.nav:hover{\n background: rgba(28, 28, 28, .5);\n }\n\n .region.nav.has-popout:hover .bubble{\n background: rgba(28, 28, 28, .8);\n opacity: 1;\n z-index: 3;\n }\n\n .region.nav:hover:after,\n .region.nav:hover:before{\n background: rgba(28, 28, 28, .8);\n }\n\n .region.nav:hover .popout{\n opacity: 1;\n visibility: visible;\n }\n\n .").concat(constants.className, ".dragging .bubble{\n transform: scale3d(.75,.75,.75);\n }\n\n .").concat(constants.className, ".hide-markers .marker{\n opacity: 0;\n }\n\n");
1269
631
  class Region {
1270
632
  constructor(props) {
1271
633
  const regionDefaultProps = {
@@ -1273,373 +635,273 @@
1273
635
  out: null,
1274
636
  title: '',
1275
637
  annotation: '',
1276
- el: null, // can store a
638
+ el: null
1277
639
  };
1278
-
1279
- Object.assign(this, regionDefaultProps, props); // apply props
640
+ Object.assign(this, regionDefaultProps, props);
1280
641
  }
1281
642
  }
1282
-
1283
643
  class SeekBar {
1284
644
  constructor(props) {
1285
645
  this.props = props;
1286
- reactiveSetters.apply(this); // ability to handle new props via setters
1287
-
646
+ reactiveSetters.apply(this);
1288
647
  this.container = document.createElement('div');
1289
- this.container.className = `${constants.className}-wrapper`;
1290
-
648
+ this.container.className = "".concat(constants.className, "-wrapper");
1291
649
  this.state = {
1292
- in: null, // in seconds from 0
650
+ in: null,
1293
651
  out: null,
1294
- markersEnabled: (this.props.markersEnabled === undefined) ? true : this.props.markersEnabled,
1295
- regions: [], // array with regions providing {in: 0, out: 12} and possibly more
1296
- dragging: false,
652
+ markersEnabled: this.props.markersEnabled === undefined ? true : this.props.markersEnabled,
653
+ regions: [],
654
+ dragging: false
1297
655
  };
1298
-
1299
656
  this.markers = {};
1300
-
1301
657
  if (this.props.target) {
1302
- this.shadow = this.container.attachShadow({ mode: 'open' });
658
+ this.shadow = this.container.attachShadow({
659
+ mode: 'open'
660
+ });
1303
661
  this.props.target.appendChild(this.container);
1304
- // attach styles
1305
662
  this.style = document.createElement('style');
1306
663
  this.style.type = 'text/css';
1307
-
1308
- // colors from props
1309
- const bgColor = (this.props.bgColor) ? this.props.bgColor : constants.defaults.bgColor;
1310
- const fillColor = (this.props.fillColor) ? this.props.fillColor : constants.defaults.fillColor;
1311
-
1312
- this.style.innerHTML = CSS$1(sanitize(bgColor), sanitize(fillColor));
664
+ const bgColor = this.props.bgColor ? this.props.bgColor : constants.defaults.bgColor;
665
+ const fillColor = this.props.fillColor ? this.props.fillColor : constants.defaults.fillColor;
666
+ this.style.innerHTML = CSS(sanitize(bgColor), sanitize(fillColor));
1313
667
  this.shadow.appendChild(this.style);
1314
668
  }
1315
-
1316
669
  this.bindEvents();
1317
670
  this.draw();
1318
671
  this.bindHotkeys();
1319
-
1320
- /*
1321
- run this for "in" and "out" points set in constructor
1322
- when the player metadata is ready (duration data is there)
1323
- */
1324
- const { player } = this.props;
672
+ const {
673
+ player
674
+ } = this.props;
1325
675
  if (player) {
1326
676
  player.videojs.on('loadedmetadata', () => {
1327
677
  this.handleNewProps(this.props);
1328
678
  });
1329
679
  }
1330
680
  }
1331
-
1332
- /*
1333
- Video.js seekbar works a bit differently - it
1334
- sets an interval and checks the playback position
1335
- very frequently, could do it that way -
1336
- then could use requestAnimationFrame and
1337
- get a smoother animation at more expense
1338
- */
1339
- bindEvents() { // bind to vdt player instance
681
+ bindEvents() {
1340
682
  if (this.props.player) {
1341
- const { player } = this.props;
683
+ const {
684
+ player
685
+ } = this.props;
1342
686
  player.videojs.on('timeupdate', () => {
1343
687
  const progressFraction = player.videojs.currentTime() / player.videojs.duration();
1344
- this.fill.style.width = `${100 * progressFraction}%`;
688
+ this.fill.style.width = "".concat(100 * progressFraction, "%");
1345
689
  });
1346
-
1347
690
  player.videojs.on('progress', () => {
1348
691
  this.percentLoaded = player.percentLoaded;
1349
692
  });
1350
693
  }
1351
694
  }
1352
-
1353
695
  bindHotkeys() {
1354
696
  this.bindings = [];
1355
-
1356
- // bindings related to player
1357
697
  if (this.props.player) {
1358
- const { player } = this.props;
1359
- this.bindings = [
1360
- {
1361
- key: ['i'],
1362
- method: () => {
1363
- if (this.markersEnabled) this.in = player.videojs.currentTime();
1364
- },
1365
- },
1366
- {
1367
- key: ['o'],
1368
- method: () => {
1369
- if (this.markersEnabled) this.out = player.videojs.currentTime();
1370
- },
1371
- },
1372
- {
1373
- key: ['shift+i'],
1374
- method: () => {
1375
- if (this.markersEnabled) this.navigateToIn();
1376
- },
1377
- },
1378
- {
1379
- key: ['shift+o'],
1380
- method: () => {
1381
- if (this.markersEnabled) this.navigateToOut();
1382
- },
1383
- },
1384
- ];
698
+ const {
699
+ player
700
+ } = this.props;
701
+ this.bindings = [{
702
+ key: ['i'],
703
+ method: () => {
704
+ if (this.markersEnabled) this.in = player.videojs.currentTime();
705
+ }
706
+ }, {
707
+ key: ['o'],
708
+ method: () => {
709
+ if (this.markersEnabled) this.out = player.videojs.currentTime();
710
+ }
711
+ }, {
712
+ key: ['shift+i'],
713
+ method: () => {
714
+ if (this.markersEnabled) this.navigateToIn();
715
+ }
716
+ }, {
717
+ key: ['shift+o'],
718
+ method: () => {
719
+ if (this.markersEnabled) this.navigateToOut();
720
+ }
721
+ }];
1385
722
  }
1386
-
1387
723
  if (this.props.player) {
1388
- const { player } = this.props;
724
+ const {
725
+ player
726
+ } = this.props;
1389
727
  mousetrap__default["default"].bind('i', () => {
1390
728
  if (this.markersEnabled) this.in = player.videojs.currentTime();
1391
729
  });
1392
-
1393
730
  mousetrap__default["default"].bind('o', () => {
1394
731
  if (this.markersEnabled) this.out = player.videojs.currentTime();
1395
732
  });
1396
-
1397
733
  mousetrap__default["default"].bind('shift+i', () => {
1398
734
  if (this.markersEnabled) this.navigateToIn();
1399
735
  });
1400
-
1401
736
  mousetrap__default["default"].bind('shift+o', () => {
1402
737
  if (this.markersEnabled) this.navigateToOut();
1403
738
  });
1404
739
  }
1405
-
1406
- // bind
1407
- this.bindings.forEach((binding) => {
1408
- mousetrap__default["default"].bind(binding.key, (e) => {
740
+ this.bindings.forEach(binding => {
741
+ mousetrap__default["default"].bind(binding.key, e => {
1409
742
  binding.method(e);
1410
743
  });
1411
744
  });
1412
745
  }
1413
-
1414
746
  unbindHotKeys() {
1415
- this.bindings && this.bindings.forEach((binding) => {
747
+ this.bindings && this.bindings.forEach(binding => {
1416
748
  mousetrap__default["default"].unbind(binding.key);
1417
749
  });
1418
750
  }
1419
-
1420
751
  handleSeek(event) {
1421
- // check if target is a region
1422
752
  if (this.props.player) {
1423
- const { player } = this.props;
753
+ const {
754
+ player
755
+ } = this.props;
1424
756
  if (this.state.regions) {
1425
757
  let clickedOnRegion = false;
1426
- let preventSeek = false; // prevent seek from happening
1427
- this.state.regions.forEach((region) => {
758
+ let preventSeek = false;
759
+ this.state.regions.forEach(region => {
1428
760
  if (region.el.contains(event.target)) {
1429
- // player.seekTo(region.in);
1430
-
1431
- // callback for clicking on a region, if callback returns false, no seek happens
1432
- // TODO - make a convention of returning true / false to break from seek
1433
761
  if (this.props.onRegionClick) {
1434
- preventSeek = (this.props.onRegionClick(region));
762
+ preventSeek = this.props.onRegionClick(region);
1435
763
  if (!preventSeek) {
1436
764
  player.seekTo(region.in);
1437
765
  }
1438
766
  } else {
1439
767
  player.seekTo(region.in);
1440
768
  }
1441
-
1442
769
  clickedOnRegion = true;
1443
770
  }
1444
771
  });
1445
-
1446
- /*
1447
- exit from method, TODO - implement foreach
1448
- for breaking more efficiently
1449
- */
1450
-
1451
772
  if (clickedOnRegion) return;
1452
773
  }
1453
-
1454
774
  const seekFractionX = (event.clientX - this.bar.getBoundingClientRect().left) / this.bar.offsetWidth;
1455
- // -> 0.34732
1456
-
1457
775
  const timeToSeek = seekFractionX * player.videojs.duration();
1458
- // -> something like 32.2323 (in seconds)
1459
776
  player.seekTo(timeToSeek);
1460
- } // TODO - make a callback for more generic use case (non-vdt player)
777
+ }
1461
778
  }
1462
-
1463
779
  handleSeekDrag(mouseDownEvent) {
1464
- const barOffset = this.bar.getBoundingClientRect().left; // px from page left side
1465
- const barWidth = this.bar.getBoundingClientRect().width; // seek bar width
1466
- const duration = this.props.player.videojs.duration(); // in secondsā
780
+ const barOffset = this.bar.getBoundingClientRect().left;
781
+ const barWidth = this.bar.getBoundingClientRect().width;
782
+ const duration = this.props.player.videojs.duration();
1467
783
  let left;
1468
-
1469
- const drag = (e) => {
784
+ const drag = e => {
1470
785
  left = e.clientX - barOffset;
1471
786
  const pxDragged = Math.abs(mouseDownEvent.clientX - e.clientX);
1472
-
1473
- if (left >= 0 && left < barWidth && pxDragged > 1) { // prevent out-of-bounds
787
+ if (left >= 0 && left < barWidth && pxDragged > 1) {
1474
788
  this.dragging = true;
1475
789
  const fractionFromStart = parseFloat(left) / barWidth;
1476
790
  const seconds = fractionFromStart * duration;
1477
791
  this.props.player.videojs.currentTime(seconds);
1478
792
  }
1479
-
1480
793
  e.preventDefault();
1481
794
  };
1482
-
1483
- const stopDrag = (e) => {
795
+ const stopDrag = e => {
1484
796
  this.dragging = false;
1485
-
1486
- // remove listeners
1487
797
  document.removeEventListener('mousemove', drag, false);
1488
798
  document.removeEventListener('mouseup', stopDrag, false);
1489
-
1490
799
  const fractionFromStart = parseFloat(left) / barWidth;
1491
800
  const seconds = fractionFromStart * duration;
1492
801
  this.props.player.videojs.currentTime(seconds);
1493
-
1494
802
  e.stopPropagation();
1495
803
  };
1496
-
1497
- // add listeners
1498
804
  document.addEventListener('mousemove', drag, false);
1499
805
  document.addEventListener('mouseup', stopDrag, false);
1500
806
  }
1501
-
1502
807
  draw() {
1503
808
  this.bar = document.createElement('div');
1504
809
  this.bar.className = constants.className;
1505
- this.bar.onclick = (e) => {
810
+ this.bar.onclick = e => {
1506
811
  if (!e.target.classList.contains('marker') && !e.target.classList.contains('handle')) {
1507
812
  this.handleSeek(e);
1508
813
  }
1509
814
  };
1510
-
1511
- // handle drag
1512
-
1513
- this.bar.addEventListener('mousedown', (e) => {
1514
- e.preventDefault(); // prevents selection cursor
1515
-
1516
- if (!this.dragging) { // don't attach listeners if something is already dragging
815
+ this.bar.addEventListener('mousedown', e => {
816
+ e.preventDefault();
817
+ if (!this.dragging) {
1517
818
  this.handleSeekDrag(e);
1518
819
  }
1519
820
  }, false);
1520
-
1521
821
  this.fill = document.createElement('div');
1522
822
  this.fill.className = 'fill';
1523
-
1524
823
  this.loadIndicator = document.createElement('div');
1525
824
  this.loadIndicator.className = 'load-indicator';
1526
-
1527
825
  this.region = document.createElement('div');
1528
826
  this.region.className = 'region';
1529
-
1530
827
  this.bar.appendChild(this.fill);
1531
828
  this.bar.appendChild(this.loadIndicator);
1532
829
  this.bar.appendChild(this.region);
1533
830
  this.shadow.appendChild(this.bar);
1534
-
1535
831
  this.drawMarkers();
1536
832
  }
1537
-
1538
- drawMarkers() { // TODO - add touch events
833
+ drawMarkers() {
1539
834
  if (this.markersEnabled) {
1540
835
  const markerIn = this.markers[constants.markerTypes.in] = document.createElement('div');
1541
836
  const markerOut = this.markers[constants.markerTypes.out] = document.createElement('div');
1542
- markerIn.className = `marker ${constants.markerTypes.in}`;
1543
- markerOut.className = `marker ${constants.markerTypes.out}`;
1544
-
837
+ markerIn.className = "marker ".concat(constants.markerTypes.in);
838
+ markerOut.className = "marker ".concat(constants.markerTypes.out);
1545
839
  const handleIn = document.createElement('div');
1546
840
  const handleOut = document.createElement('div');
1547
841
  handleIn.className = handleOut.className = 'handle';
1548
-
1549
- handleIn.addEventListener('mousedown', (e) => {
1550
- e.preventDefault(); // prevents selection cursor
1551
- e.stopPropagation(); // prevent triggering seek bar drag
1552
-
842
+ handleIn.addEventListener('mousedown', e => {
843
+ e.preventDefault();
844
+ e.stopPropagation();
1553
845
  this.handleMarkerDrag(markerIn, constants.markerTypes.in, e);
1554
846
  }, false);
1555
-
1556
- handleOut.addEventListener('mousedown', (e) => {
1557
- e.preventDefault(); // prevents selection cursor
1558
- e.stopPropagation(); // prevent triggering seek bar drag
1559
-
847
+ handleOut.addEventListener('mousedown', e => {
848
+ e.preventDefault();
849
+ e.stopPropagation();
1560
850
  this.handleMarkerDrag(markerOut, constants.markerTypes.out, e);
1561
851
  }, false);
1562
-
1563
852
  markerIn.appendChild(handleIn);
1564
853
  markerOut.appendChild(handleOut);
1565
-
1566
854
  this.bar.appendChild(markerIn);
1567
855
  this.bar.appendChild(markerOut);
1568
856
  }
1569
857
  }
1570
-
1571
858
  updateMarkers(marker) {
1572
- this.markers[marker.type].style.left = `${marker.value * 100}%`;
859
+ this.markers[marker.type].style.left = "".concat(marker.value * 100, "%");
1573
860
  this.markers[marker.type].classList.add('active');
1574
-
1575
- // check if marker mathes a region and add a classname to it
1576
861
  if (this.state.regions) {
1577
- // check if any region contains in / out matching value
1578
862
  const matchingRegions = [];
1579
- const isRegional = (matchingRegions.length > 0);
863
+ const isRegional = matchingRegions.length > 0;
1580
864
  if (isRegional) {
1581
865
  this.markers[marker.type].classList.add('regional');
1582
866
  } else {
1583
867
  this.markers[marker.type].classList.remove('regional');
1584
868
  }
1585
869
  }
1586
-
1587
870
  this.drawInAndOutRegion();
1588
871
  }
1589
-
1590
- drawInAndOutRegion() { // triggered after drawMarkers if in + out is set
872
+ drawInAndOutRegion() {
1591
873
  if (this.state.in !== null && this.state.out !== null) {
1592
874
  this.region.style.left = this.markers[constants.markerTypes.in].style.left;
1593
- this.region.style.right = `${100 - parseFloat(this.markers[constants.markerTypes.out].style.left.replace('%', ''))}%`;
875
+ this.region.style.right = "".concat(100 - parseFloat(this.markers[constants.markerTypes.out].style.left.replace('%', '')), "%");
1594
876
  }
1595
877
  }
1596
-
1597
- /*
1598
- TODO - handle touch events,
1599
- but find out if there is a mobile use
1600
- case before that
1601
- */
1602
- handleMarkerDrag(markerNode, type, mouseDownEvent) { // marker node, marker type (constants.markerTypes)
1603
- const barOffset = this.bar.getBoundingClientRect().left; // px from page left side
1604
- const barWidth = this.bar.getBoundingClientRect().width; // seek bar width
1605
- const duration = this.props.player.videojs.duration(); // in secondsā
878
+ handleMarkerDrag(markerNode, type, mouseDownEvent) {
879
+ const barOffset = this.bar.getBoundingClientRect().left;
880
+ const barWidth = this.bar.getBoundingClientRect().width;
881
+ const duration = this.props.player.videojs.duration();
1606
882
  let dragHappened = false;
1607
-
1608
- const drag = (e) => {
883
+ const drag = e => {
1609
884
  const pxDragged = Math.abs(mouseDownEvent.clientX - e.clientX);
1610
- const markerOffset = (type === constants.markerTypes.in) ? (constants.defaults.markerWidth / 2) : -(constants.defaults.markerWidth / 2);
885
+ const markerOffset = type === constants.markerTypes.in ? constants.defaults.markerWidth / 2 : -(constants.defaults.markerWidth / 2);
1611
886
  const left = e.clientX - barOffset + markerOffset;
1612
-
1613
- if (left >= 0 && left < barWidth && pxDragged > 1) { // prevent out-of-bounds
887
+ if (left >= 0 && left < barWidth && pxDragged > 1) {
1614
888
  dragHappened = this.dragging = true;
1615
- markerNode.style.left = `${left}px`;
889
+ markerNode.style.left = "".concat(left, "px");
1616
890
  markerNode.classList.add('active');
1617
-
1618
891
  const fractionFromStart = parseFloat(markerNode.getBoundingClientRect().left - barOffset) / barWidth;
1619
892
  const seconds = fractionFromStart * duration;
1620
893
  this.props.player.videojs.currentTime(seconds);
1621
894
  }
1622
-
1623
895
  e.preventDefault();
1624
896
  };
1625
-
1626
- const stopDrag = (e) => {
897
+ const stopDrag = e => {
1627
898
  this.dragging = false;
1628
-
1629
- // remove listeners
1630
899
  document.removeEventListener('mousemove', drag, false);
1631
900
  document.removeEventListener('mouseup', stopDrag, false);
1632
-
1633
901
  const fractionFromStart = parseFloat(markerNode.getBoundingClientRect().left - barOffset) / barWidth;
1634
902
  const seconds = fractionFromStart * duration;
1635
903
  this[type] = seconds;
1636
904
  this.props.player.videojs.currentTime(seconds);
1637
-
1638
- /*
1639
- if drag did not take place,
1640
- user just clicked the marker -
1641
- jump to the time of marker in player
1642
- */
1643
905
  if (!dragHappened) {
1644
906
  if (type === constants.markerTypes.in) {
1645
907
  this.navigateToIn();
@@ -1647,121 +909,99 @@
1647
909
  this.navigateToOut();
1648
910
  }
1649
911
  }
1650
-
1651
- this.props.onDragFinish && this.props.onDragFinish({ type, seconds });
912
+ this.props.onDragFinish && this.props.onDragFinish({
913
+ type,
914
+ seconds
915
+ });
1652
916
  e.stopPropagation();
1653
917
  };
1654
-
1655
- // add listeners
1656
918
  document.addEventListener('mousemove', drag, false);
1657
919
  document.addEventListener('mouseup', stopDrag, false);
1658
920
  }
1659
-
1660
- /*
1661
- hide dead markers (for state parity) or ones that
1662
- make no sense (like out point before in point)
1663
- */
1664
921
  cleanupMarkers() {
1665
922
  let removedMarker = false;
1666
923
  if (this.state.in === null) {
1667
924
  this.markers[constants.markerTypes.in].classList.remove('active');
1668
925
  removedMarker = true;
1669
926
  }
1670
-
1671
927
  if (this.state.out === null) {
1672
928
  this.markers[constants.markerTypes.out].classList.remove('active');
1673
929
  removedMarker = true;
1674
930
  }
1675
-
1676
- if (removedMarker) { // if a marker was removed, region no longer is valid
931
+ if (removedMarker) {
1677
932
  this.region.style.left = 'initial';
1678
933
  this.region.style.right = 'initial';
1679
934
  }
1680
935
  }
1681
-
1682
936
  clearMarkers() {
1683
937
  this.in = null;
1684
938
  this.out = null;
1685
939
  }
1686
-
1687
- // utilizes player instance from props
1688
940
  setInFromCurrent() {
1689
941
  this.in = this.props.player.videojs.currentTime();
1690
942
  }
1691
-
1692
943
  setOutFromCurrent() {
1693
944
  this.out = this.props.player.videojs.currentTime();
1694
945
  }
1695
-
1696
946
  navigateToIn() {
1697
- const { player } = this.props;
947
+ const {
948
+ player
949
+ } = this.props;
1698
950
  player.seekTo(this.in);
1699
951
  }
1700
-
1701
952
  navigateToOut() {
1702
- const { player } = this.props;
953
+ const {
954
+ player
955
+ } = this.props;
1703
956
  player.seekTo(this.out);
1704
957
  }
1705
-
1706
- // setters and getters
1707
958
  set in(val) {
1708
959
  if (!this.state.markersEnabled) return;
1709
-
1710
960
  this.state.in = val;
1711
-
1712
961
  if (val !== null && val !== undefined) {
1713
- const { player } = this.props;
962
+ const {
963
+ player
964
+ } = this.props;
1714
965
  this.updateMarkers({
1715
966
  type: constants.markerTypes.in,
1716
- value: val / player.videojs.duration(),
967
+ value: val / player.videojs.duration()
1717
968
  });
1718
-
1719
- // make sure in point is not set after out point
1720
969
  if (this.state.out !== null && val > this.state.out) {
1721
970
  this.out = null;
1722
971
  }
1723
-
1724
972
  this.props.onIn && this.props.onIn(this.state.in);
1725
973
  } else {
1726
974
  this.cleanupMarkers();
1727
975
  }
1728
976
  }
1729
-
1730
977
  get in() {
1731
978
  return this.state.in;
1732
979
  }
1733
-
1734
980
  set out(val) {
1735
981
  if (!this.state.markersEnabled) return;
1736
-
1737
982
  this.state.out = val;
1738
-
1739
983
  if (val !== null && val !== undefined) {
1740
- const { player } = this.props;
984
+ const {
985
+ player
986
+ } = this.props;
1741
987
  this.updateMarkers({
1742
988
  type: constants.markerTypes.out,
1743
- value: val / player.videojs.duration(),
989
+ value: val / player.videojs.duration()
1744
990
  });
1745
-
1746
- // make sure out point is not set before in point
1747
991
  if (this.state.in != null && val < this.state.in) {
1748
992
  this.in = null;
1749
993
  }
1750
-
1751
994
  this.props.onOut && this.props.onOut(this.state.out);
1752
995
  } else {
1753
996
  this.cleanupMarkers();
1754
997
  }
1755
998
  }
1756
-
1757
999
  get out() {
1758
1000
  return this.state.out;
1759
1001
  }
1760
-
1761
1002
  get markersEnabled() {
1762
1003
  return this.state.markersEnabled;
1763
1004
  }
1764
-
1765
1005
  set markersEnabled(bool) {
1766
1006
  if (typeof bool === 'boolean') {
1767
1007
  if (!bool) {
@@ -1773,28 +1013,23 @@
1773
1013
  }
1774
1014
  }
1775
1015
  }
1776
-
1777
- set regions(regionArray) { // different than i / o markers
1778
- if (this.state.regions.length < 1) { // first time add
1779
- regionArray && regionArray.forEach((region) => {
1016
+ set regions(regionArray) {
1017
+ if (this.state.regions.length < 1) {
1018
+ regionArray && regionArray.forEach(region => {
1780
1019
  this.addRegion(region);
1781
1020
  });
1782
- } else { // state redraw
1021
+ } else {
1783
1022
  this.state.regions.forEach((region, index) => {
1784
1023
  this.removeRegion(index);
1785
1024
  });
1786
-
1787
1025
  this.state.regions = [];
1788
- this.regions = regionArray; // run again
1026
+ this.regions = regionArray;
1789
1027
  }
1790
1028
  }
1791
-
1792
1029
  get regions() {
1793
1030
  return this.state.regions;
1794
1031
  }
1795
-
1796
1032
  set dragging(bool) {
1797
- // update only if worth updating
1798
1033
  if (bool && !this.state.dragging) {
1799
1034
  this.state.dragging = true;
1800
1035
  this.bar.classList.add('dragging');
@@ -1803,109 +1038,269 @@
1803
1038
  this.bar.classList.remove('dragging');
1804
1039
  }
1805
1040
  }
1806
-
1807
1041
  get dragging() {
1808
1042
  return this.state.dragging;
1809
1043
  }
1810
-
1811
1044
  addRegion(regionObj) {
1812
1045
  if (regionObj.constructor.name === Region.name) {
1813
1046
  this.state.regions.push(regionObj);
1814
1047
  this.drawRegion(regionObj);
1815
- } else { // try to construct region if has accepted properties
1048
+ } else {
1816
1049
  this.addRegion(new Region(regionObj));
1817
1050
  }
1818
1051
  }
1819
-
1820
- removeRegion(param) {
1821
- if (typeof param === typeof Region) ; else { // it's an index
1822
- this.state.regions[param].el.remove();
1823
- this.state.regions[param] = null;
1824
- delete this.state.regions[param];
1825
- }
1052
+ removeRegion(param) {
1053
+ if (typeof param === typeof Region) ; else {
1054
+ this.state.regions[param].el.remove();
1055
+ this.state.regions[param] = null;
1056
+ delete this.state.regions[param];
1057
+ }
1058
+ }
1059
+ drawRegion(region) {
1060
+ const duration = this.props.player.videojs.duration();
1061
+ const inPoint = region.in;
1062
+ const leftPercentage = inPoint / duration * 100;
1063
+ const rightPercentage = (duration - region.out) / duration * 100;
1064
+ const regionEl = document.createElement('div');
1065
+ regionEl.className = 'region nav ';
1066
+ regionEl.innerHTML = '<div class="bubble"></div>';
1067
+ if (region.title && region.annotation) {
1068
+ let alignmentClassName = '';
1069
+ if (leftPercentage > 70) alignmentClassName = 'right-aligned';
1070
+ if (leftPercentage < 30) alignmentClassName = 'left-aligned';
1071
+ regionEl.className += " has-popout ".concat(alignmentClassName);
1072
+ const popout = document.createElement('div');
1073
+ popout.className = 'popout';
1074
+ popout.innerHTML = "\n <div class=\"title\">\n ".concat(sanitize(region.title), "\n </div>\n <div class=\"annotation\">\n ").concat(truncate(sanitize(region.annotation), 72), "\n </div>\n ");
1075
+ regionEl.appendChild(popout);
1076
+ }
1077
+ regionEl.style.left = "".concat(leftPercentage, "%");
1078
+ regionEl.style.right = "".concat(rightPercentage, "%");
1079
+ region.el = regionEl;
1080
+ this.bar.appendChild(regionEl);
1081
+ }
1082
+ set onDark(bool) {
1083
+ if (bool) {
1084
+ this.bar.classList.add(classNames.onDark);
1085
+ } else {
1086
+ this.bar.classList.remove(classNames.onDark);
1087
+ }
1088
+ }
1089
+ get onDark() {
1090
+ return this.container.classList.contains(classNames.onDark);
1091
+ }
1092
+ set percentLoaded(val) {
1093
+ this.loadIndicator.style.width = "".concat(val, "%");
1094
+ }
1095
+ destroy() {
1096
+ this.unbindHotKeys();
1097
+ }
1098
+ toggleMarkers() {
1099
+ if (this.bar.classList.contains('hide-markers')) {
1100
+ this.showMarkers();
1101
+ } else {
1102
+ this.hideMarkers();
1103
+ }
1104
+ }
1105
+ hideMarkers() {
1106
+ this.bar.classList.add('hide-markers');
1107
+ }
1108
+ showMarkers() {
1109
+ this.bar.classList.remove('hide-markers');
1110
+ }
1111
+ }
1112
+ SeekBar.Region = Region;
1113
+
1114
+ const timeCodeNodeNames = ['hours', 'minutes', 'seconds', 'frame'];
1115
+ class TimeCodeDisplay {
1116
+ constructor(props) {
1117
+ this.props = props;
1118
+ this.state = {
1119
+ selectedNode: null
1120
+ };
1121
+ const classNames = ['vdt-timecode', 'vjs-time-control',
1122
+ !this.props.onInput ? 'duration' : ''
1123
+ ];
1124
+ this.container = document.createElement('div');
1125
+ this.container.className = classNames.join(' ');
1126
+ this.nodeNames = [].concat(timeCodeNodeNames);
1127
+ this.nodes = {};
1128
+ this.nodeNames.forEach((nodeName, index) => {
1129
+ const node = document.createElement('input');
1130
+ node.className = nodeName;
1131
+ node.tabIndex = -1;
1132
+ if (this.props.onInput) {
1133
+ node.maxLength = 2;
1134
+ node.onfocus = e => {
1135
+ this.handleInputFocus(e, nodeName);
1136
+ };
1137
+ node.onblur = e => {
1138
+ this.handleInputBlur(e, nodeName);
1139
+ };
1140
+ node.onmouseup = e => {
1141
+ e.preventDefault();
1142
+ e.currentTarget.select();
1143
+ };
1144
+ node.onkeydown = e => {
1145
+ this.handleTimeCodeInput(e, nodeName);
1146
+ };
1147
+ node.onkeyup = e => {
1148
+ this.handleTimeCodeInputOnKeyUp(e, nodeName);
1149
+ };
1150
+ } else {
1151
+ node.readOnly = true;
1152
+ }
1153
+ this.nodes[nodeName] = node;
1154
+ const colon = document.createElement('span');
1155
+ colon.innerHTML = ':';
1156
+ this.container.appendChild(node);
1157
+ index !== this.nodeNames.length - 1 && this.container.appendChild(colon);
1158
+ });
1159
+ if (this.props.target) {
1160
+ if (this.props.appendAfter) {
1161
+ this.props.target.parentNode.insertBefore(this.container, this.props.target.nextSibling);
1162
+ } else {
1163
+ this.props.target.appendChild(this.container);
1164
+ }
1165
+ this.reset();
1166
+ } else {
1167
+ console.error("Unable to instantiate a new ".concat(this.constructor.name, " without target DOM node"));
1168
+ }
1169
+ }
1170
+ update(valuesObj) {
1171
+ const startCount = this.nodeNames.length - 1;
1172
+ let i;
1173
+ for (i = startCount; i > -1; i--) {
1174
+ const key = this.nodeNames[i];
1175
+ if (typeof valuesObj[key] !== 'undefined' && valuesObj[key] !== parseInt(this.nodes[key].value, 10) && key !== this.state.selectedNode) {
1176
+ const str = valuesObj[key].toString();
1177
+ this.nodes[key].value = this.toDigitPairStr(str);
1178
+ }
1179
+ }
1180
+ }
1181
+ reset() {
1182
+ this.update({
1183
+ hours: 0,
1184
+ minutes: 0,
1185
+ seconds: 0,
1186
+ frame: 0
1187
+ });
1188
+ }
1189
+ handleInputFocus(e, nodeName) {
1190
+ this.state.selectedNode = nodeName;
1191
+ this.nodes[nodeName].select();
1192
+ this.props.onInputFocus && this.props.onInputFocus(nodeName);
1193
+ }
1194
+ handleInputBlur(e, nodeName) {
1195
+ this.state.selectedNode = null;
1196
+ this.props.onInputBlur && this.props.onInputBlur(nodeName);
1197
+ }
1198
+ sanitize(input) {
1199
+ const output = input.replace(/[^0-9]/g, '');
1200
+ return output;
1201
+ }
1202
+ toDigitPairStr(strValue) {
1203
+ return strValue.length < 2 ? "0".concat(strValue) : strValue;
1204
+ }
1205
+ handleTimeCodeInput(e, nodeName) {
1206
+ const nodeIndex = this.nodeNames.indexOf(nodeName);
1207
+ switch (e.code) {
1208
+ case 'Enter':
1209
+ this.checkAndNormalizeAllInputs();
1210
+ this.props.onInput && this.props.onInput(e, nodeName, {
1211
+ hours: this.hours,
1212
+ minutes: this.minutes,
1213
+ seconds: this.seconds,
1214
+ frame: this.frame
1215
+ });
1216
+ this[nodeName] = e.currentTarget.value;
1217
+ e.currentTarget.blur();
1218
+ break;
1219
+ case 'Tab':
1220
+ e.preventDefault();
1221
+ if (!e.shiftKey) {
1222
+ if (nodeIndex + 1 <= this.nodeNames.length - 1) {
1223
+ this.nodes[this.nodeNames[nodeIndex + 1]].focus();
1224
+ } else {
1225
+ this.nodes[this.nodeNames[0]].focus();
1226
+ }
1227
+ } else if (nodeIndex - 1 >= 0) {
1228
+ this.nodes[this.nodeNames[nodeIndex - 1]].focus();
1229
+ } else {
1230
+ this.nodes[this.nodeNames[this.nodeNames.length - 1]].focus();
1231
+ }
1232
+ break;
1233
+ }
1234
+ }
1235
+ handleTimeCodeInputOnKeyUp(e, nodeName) {
1236
+ if (this.nodes[nodeName].value.length === 2 && !isNaN(e.key)) {
1237
+ const nodeIndex = this.nodeNames.indexOf(nodeName);
1238
+ if (nodeIndex + 1 <= this.nodeNames.length - 1) {
1239
+ this.nodes[this.nodeNames[nodeIndex + 1]].focus();
1240
+ }
1241
+ }
1242
+ }
1243
+ checkAndNormalizeAllInputs() {
1244
+ this.nodeNames.forEach(nodeName => {
1245
+ if (this.nodes[nodeName].value.length < 1) {
1246
+ this.nodes[nodeName].value = '00';
1247
+ }
1248
+ this.nodes[nodeName].value = this.toDigitPairStr(this.sanitize(this.nodes[nodeName].value));
1249
+ });
1250
+ }
1251
+ set frame(val) {
1252
+ this.nodes.frame.value = this.toDigitPairStr(val);
1253
+ }
1254
+ get frame() {
1255
+ return parseInt(this.nodes.frame.value, 10);
1256
+ }
1257
+ set seconds(val) {
1258
+ this.nodes.seconds.value = this.toDigitPairStr(val);
1259
+ }
1260
+ get seconds() {
1261
+ return parseInt(this.nodes.seconds.value, 10);
1262
+ }
1263
+ set minutes(val) {
1264
+ this.nodes.minutes.value = this.toDigitPairStr(val);
1265
+ }
1266
+ get minutes() {
1267
+ return parseInt(this.nodes.minutes.value, 10);
1268
+ }
1269
+ set hours(val) {
1270
+ this.nodes.hours.value = this.toDigitPairStr(val);
1826
1271
  }
1827
-
1828
- drawRegion(region) {
1829
- const duration = this.props.player.videojs.duration(); // seconds
1830
- const inPoint = region.in;
1831
-
1832
- const leftPercentage = (inPoint / duration) * 100;
1833
- const rightPercentage = ((duration - region.out) / duration) * 100;
1834
-
1835
- const regionEl = document.createElement('div');
1836
- regionEl.className = 'region nav ';
1837
- regionEl.innerHTML = '<div class="bubble"></div>';
1838
-
1839
- if (region.title && region.annotation) {
1840
- let alignmentClassName = '';
1841
- if (leftPercentage > 70) alignmentClassName = 'right-aligned';
1842
- if (leftPercentage < 30) alignmentClassName = 'left-aligned';
1843
- regionEl.className += ` has-popout ${alignmentClassName}`;
1844
-
1845
- const popout = document.createElement('div');
1846
-
1847
- popout.className = 'popout';
1848
- popout.innerHTML = `
1849
- <div class="title">
1850
- ${sanitize(region.title)}
1851
- </div>
1852
- <div class="annotation">
1853
- ${truncate(sanitize(region.annotation), 72)}
1854
- </div>
1855
- `;
1856
-
1857
- regionEl.appendChild(popout);
1858
- }
1859
-
1860
- regionEl.style.left = `${leftPercentage}%`;
1861
- regionEl.style.right = `${rightPercentage}%`;
1862
-
1863
- region.el = regionEl;
1864
-
1865
- this.bar.appendChild(regionEl);
1272
+ get hours() {
1273
+ return parseInt(this.nodes.hours.value, 10);
1866
1274
  }
1867
-
1868
- set onDark(bool) {
1869
- if (bool) {
1870
- this.bar.classList.add(classNames.onDark);
1871
- } else {
1872
- this.bar.classList.remove(classNames.onDark);
1873
- }
1275
+ getFormatted(key) {
1276
+ return this.toDigitPairStr(this[key].toString());
1874
1277
  }
1875
-
1876
- get onDark() {
1877
- return this.container.classList.contains(classNames.onDark);
1278
+ get smpte() {
1279
+ return {
1280
+ hours: this.hours,
1281
+ minutes: this.minutes,
1282
+ seconds: this.seconds,
1283
+ frame: this.frame
1284
+ };
1878
1285
  }
1879
-
1880
- set percentLoaded(val) {
1881
- this.loadIndicator.style.width = `${val}%`;
1286
+ set smpte(o) {
1287
+ this.update(o);
1882
1288
  }
1883
-
1884
- destroy() {
1885
- this.unbindHotKeys();
1289
+ get smpteString() {
1290
+ return "".concat(this.getFormatted('hours'), ":").concat(this.getFormatted('minutes'), ":").concat(this.getFormatted('seconds'), ":").concat(this.getFormatted('frame'));
1886
1291
  }
1887
-
1888
- toggleMarkers() {
1889
- if (this.bar.classList.contains('hide-markers')) {
1890
- this.showMarkers();
1292
+ set external(bool) {
1293
+ if (bool) {
1294
+ this.container.classList.add('external');
1891
1295
  } else {
1892
- this.hideMarkers();
1296
+ this.container.classList.remove('external');
1893
1297
  }
1894
1298
  }
1895
-
1896
- hideMarkers() {
1897
- this.bar.classList.add('hide-markers');
1898
- }
1899
-
1900
- showMarkers() {
1901
- this.bar.classList.remove('hide-markers');
1299
+ get external() {
1300
+ return this.container.classList.contains('external');
1902
1301
  }
1903
1302
  }
1904
1303
 
1905
- SeekBar.Region = Region;
1906
-
1907
- // TODO - move this to @vidispine/vdt-js when finalized and use as dependency
1908
-
1909
1304
  class VideoTime {
1910
1305
  constructor(props) {
1911
1306
  if (props.frameRate === undefined) {
@@ -1915,200 +1310,141 @@
1915
1310
  this.props = props;
1916
1311
  }
1917
1312
  }
1918
-
1919
1313
  secondsToFramesFromStart(seconds) {
1920
1314
  let frame = seconds * this.props.frameRate;
1921
1315
  frame = Math.round(frame);
1922
- return frame; // we're on this frame, could be for example 23.45
1316
+ return frame;
1923
1317
  }
1924
-
1925
1318
  currentTimeToFramesFromStart(currentTime) {
1926
1319
  return this.secondsToFramesFromStart(currentTime);
1927
1320
  }
1928
-
1929
1321
  secondsToFrame(seconds) {
1930
1322
  let frame = this.secondsToFramesFromStart(seconds) % this.props.frameRate;
1931
1323
  frame = Math.round(frame);
1932
1324
  return frame;
1933
1325
  }
1934
-
1935
1326
  currentFrame(currentTime) {
1936
1327
  return Math.round(this.secondsToFrame(currentTime));
1937
1328
  }
1938
-
1939
1329
  frameToCurrentTimeFromStart(frame) {
1940
- return frame / this.props.frameRate; // in seconds from start
1330
+ return frame / this.props.frameRate;
1941
1331
  }
1942
-
1943
1332
  smpteToCurrentTimeFromStart(values) {
1944
- return (
1945
- values.hours * 3600
1946
- + values.minutes * 60
1947
- + values.seconds
1948
- + values.frame / this.props.frameRate
1949
- );
1333
+ return values.hours * 3600 + values.minutes * 60 + values.seconds + values.frame / this.props.frameRate;
1950
1334
  }
1951
-
1952
1335
  secondsFromStartToSMPTE(secondsFromStart) {
1953
1336
  let secondsFS = secondsFromStart;
1954
1337
  const hours = Math.floor(secondsFS / 3600);
1955
1338
  const minutesWithoutHours = secondsFS / 60 - 60 * hours;
1956
- // eslint-disable-next-line no-param-reassign
1957
1339
  secondsFS %= 3600;
1958
-
1959
1340
  const minutes = Math.floor(minutesWithoutHours);
1960
1341
  const seconds = Math.floor(secondsFS % 60);
1961
1342
  const frame = this.secondsToFrame(secondsFS);
1962
-
1963
1343
  return {
1964
1344
  hours,
1965
1345
  minutes,
1966
1346
  seconds,
1967
- frame,
1347
+ frame
1968
1348
  };
1969
1349
  }
1970
1350
  }
1971
1351
 
1972
- /* eslint-disable no-param-reassign */
1973
-
1352
+ function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
1353
+ function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
1974
1354
  const correctMimeTypes = {
1975
- 'audio/x-aac': 'audio/aac',
1355
+ 'audio/x-aac': 'audio/aac'
1976
1356
  };
1977
- const sanitizeMimeType = (mimeType) => correctMimeTypes[mimeType] || mimeType;
1978
-
1357
+ const sanitizeMimeType = mimeType => correctMimeTypes[mimeType] || mimeType;
1979
1358
  class Player {
1980
1359
  constructor(props) {
1981
- this.props = props; // save props
1982
- reactiveSetters.apply(this); // ability to handle new props via setters
1983
-
1984
- const classNames = [ // add conditional css classes here for the whole container
1985
- 'video-js',
1986
- 'vjs-big-play-centered',
1987
- (this.props.posterUrl) ? constants$2.classNames.poster : '',
1988
- (this.props.subtitles && this.props.subtitles.length > 0) ? constants$2.classNames.subtitles : '',
1989
- 'init', // make player height 0 while no metadata for video size available with css
1360
+ this.props = props;
1361
+ reactiveSetters.apply(this);
1362
+ const classNames = [
1363
+ 'video-js', 'vjs-big-play-centered', this.props.posterUrl ? constants$2.classNames.poster : '', this.props.subtitles && this.props.subtitles.length > 0 ? constants$2.classNames.subtitles : '', 'init'
1990
1364
  ];
1991
-
1992
1365
  this.externalTimecodes = {
1993
- // either one of these can hold an array of TimeCodeDisplay instances
1994
1366
  duration: [],
1995
- playback: [],
1367
+ playback: []
1996
1368
  };
1997
-
1998
1369
  this.timeConverter = new VideoTime({
1999
- frameRate: this.props.frameRate || constants$2.defaults.frameRate,
1370
+ frameRate: this.props.frameRate || constants$2.defaults.frameRate
2000
1371
  });
2001
-
2002
- /*
2003
- video.js requires a html video to initialize
2004
- */
2005
-
2006
1372
  this.videoEl = document.createElement('video');
2007
1373
  this.videoEl.crossOrigin = this.props.crossOrigin;
2008
1374
  this.videoEl.id = 'vdt-video';
2009
1375
  this.videoEl.className = classNames.join(' ');
2010
- this.videoEl.controls = true; // remove this line for no-controls videojs player
2011
-
2012
- // attach sources, sort mp4 on top of source list
1376
+ this.videoEl.controls = true;
2013
1377
  if (this.props.sources) {
2014
- // sort mp4 at the top
2015
1378
  if (this.props.sources.length > 1) {
2016
- this.props.sources.sort((a) => ((a.type.indexOf('mp4') > 0) ? 1 : -1));
1379
+ this.props.sources.sort(a => a.type.indexOf('mp4') > 0 ? 1 : -1);
2017
1380
  }
2018
-
2019
- // construct <source> els
2020
- this.props.sources.forEach((source) => {
1381
+ this.props.sources.forEach(source => {
2021
1382
  const aSource = document.createElement('source');
2022
1383
  aSource.src = source.src;
2023
1384
  aSource.type = sanitizeMimeType(source.type);
2024
1385
  this.videoEl.appendChild(aSource);
2025
1386
  });
2026
- }// handle else with err later
2027
-
2028
- this.videoEl.onloadedmetadata = () => { // read-only duration timecode
1387
+ }
1388
+ this.videoEl.onloadedmetadata = () => {
2029
1389
  if (this.durationTimeCode) {
2030
1390
  this.setDurationTimeCode();
2031
1391
  }
2032
-
2033
- this.videojs.el_.classList.remove('init');
1392
+ if (this.videojs.el_) this.videojs.el_.classList.remove('init');
2034
1393
  };
2035
-
2036
- // if video has not loaded by now, show the player w/ errors
2037
1394
  setTimeout(() => {
2038
- if (this.videojs.el_) {
2039
- this.videojs.el_.classList.remove('init');
2040
- }
1395
+ if (this.videojs.el_) this.videojs.el_.classList.remove('init');
2041
1396
  }, 2000);
2042
-
2043
- // attach subtitle tracks (these should come in last in the <video /> node)
2044
1397
  if (this.props.subtitles) {
2045
- this.props.subtitles.forEach((sub) => {
1398
+ this.props.subtitles.forEach(sub => {
2046
1399
  const subtitleTag = document.createElement('track');
2047
1400
  subtitleTag.kind = 'subtitles';
2048
1401
  subtitleTag.srclang = sub.lang;
2049
1402
  subtitleTag.label = sub.label;
2050
- // subtitleTag.default = (index === 0) //can use this later to set a default track
2051
-
2052
1403
  if (typeof sub.src === 'string') {
2053
- subtitleTag.src = sub.src; // external file
1404
+ subtitleTag.src = sub.src;
2054
1405
  } else {
2055
- subtitleTag.src = window.URL.createObjectURL(sub.src); // file url from blob
1406
+ subtitleTag.src = window.URL.createObjectURL(sub.src);
2056
1407
  }
2057
-
2058
1408
  this.videoEl.appendChild(subtitleTag);
2059
1409
  });
2060
1410
  }
2061
-
2062
- // attach to dom and construct videojs player
2063
- if (this.props.target) {
2064
- this.props.target && this.props.target.appendChild(this.videoEl);
2065
- }
2066
-
2067
- const showDefaultTimeDisplay = (this.props.timecode !== true);
2068
- const controlsEnabled = (this.props.controls !== false);
1411
+ if (this.props.target) this.props.target.appendChild(this.videoEl);
1412
+ const showDefaultTimeDisplay = this.props.timecode !== true;
1413
+ const controlsEnabled = this.props.controls !== false;
2069
1414
  const options = this.props.options || {};
2070
-
2071
- this.videojs = new videojs__default["default"]('vdt-video', {
1415
+ this.videojs = new videojs__default["default"]('vdt-video', _objectSpread({
2072
1416
  responsive: true,
2073
1417
  fluid: true,
2074
1418
  controls: controlsEnabled,
2075
1419
  alwaysCaptureHotkeys: false,
2076
1420
  controlBar: {
2077
1421
  RemainingTimeDisplay: false,
2078
- timeDivider: showDefaultTimeDisplay, // relates to built-in time display
1422
+ timeDivider: showDefaultTimeDisplay,
2079
1423
  durationDisplay: showDefaultTimeDisplay,
2080
- currentTimeDisplay: showDefaultTimeDisplay,
1424
+ currentTimeDisplay: showDefaultTimeDisplay
2081
1425
  },
2082
- inactivityTimeout: (this.audioOnly) ? constants$2.defaults.audioInactivityTimeout : constants$2.defaults.inactivityTimeout,
1426
+ inactivityTimeout: this.audioOnly ? constants$2.defaults.audioInactivityTimeout : constants$2.defaults.inactivityTimeout,
2083
1427
  preload: 'auto',
2084
1428
  captionsButton: false,
2085
1429
  subsCapsButton: false,
2086
1430
  subtitlesButton: false,
2087
1431
  textTrackSettings: false,
2088
1432
  html5: {
2089
- nativeTextTracks: false,
1433
+ nativeTextTracks: false
2090
1434
  },
2091
1435
  languages: {
2092
1436
  en: {
2093
1437
  'captions off': 'off',
2094
- 'subtitles off': 'off',
2095
- },
2096
- },
2097
- ...options,
2098
- });
2099
-
2100
- // set the poster
2101
- this.props.posterUrl && this.videojs.poster(this.props.posterUrl);
2102
-
2103
- // set other props that are passed
1438
+ 'subtitles off': 'off'
1439
+ }
1440
+ }
1441
+ }, options));
1442
+ if (this.props.posterUrl) this.videojs.poster(this.props.posterUrl);
2104
1443
  this.handleNewProps(this.props);
2105
-
2106
- // lifecycle
2107
1444
  this.videojs.ready(() => {
2108
1445
  this.bindHotkeys();
2109
1446
  this.bindEvents();
2110
- this.props.onReady && this.props.onReady(this.videojs);
2111
-
1447
+ if (this.props.onReady) this.props.onReady(this.videojs);
2112
1448
  this.restoreSettings();
2113
1449
  this.setupTimeCode();
2114
1450
  this.doEsothericWork();
@@ -2116,241 +1452,197 @@
2116
1452
  this.setupCustomSeekbar();
2117
1453
  });
2118
1454
  }
2119
-
2120
1455
  setupCustomSeekbar() {
2121
1456
  if (this.props.seekBar) {
2122
- // disable the built-in videojs seekbar
2123
1457
  this.videojs.controlBar.progressControl.disable();
2124
1458
  this.videojs.controlBar.progressControl.hide();
2125
-
2126
1459
  const target = this.container.getElementsByClassName('vjs-control-bar')[0];
2127
- const props = {
1460
+ const props = _objectSpread({
2128
1461
  target,
2129
1462
  player: this,
2130
- onDark: this.props.onDark,
2131
- ...this.props.seekBar,
2132
- };
2133
-
1463
+ onDark: this.props.onDark
1464
+ }, this.props.seekBar);
2134
1465
  this.seekBar = new SeekBar(props);
2135
-
2136
- // show & hide markers when controls hide
2137
1466
  this.videojs.on('userinactive', () => {
2138
1467
  if (!this.controlsBelowPlayer) {
2139
- this.seekBar && this.seekBar.hideMarkers();
1468
+ var _this$seekBar$hideMar, _this$seekBar;
1469
+ if (this.seekBar) (_this$seekBar$hideMar = (_this$seekBar = this.seekBar).hideMarkers) === null || _this$seekBar$hideMar === void 0 ? void 0 : _this$seekBar$hideMar.call(_this$seekBar);
2140
1470
  } else {
2141
- this.seekBar && this.seekBar.showMarkers();
1471
+ var _this$seekBar2, _this$seekBar2$showMa;
1472
+ (_this$seekBar2 = this.seekBar) === null || _this$seekBar2 === void 0 ? void 0 : (_this$seekBar2$showMa = _this$seekBar2.showMarkers) === null || _this$seekBar2$showMa === void 0 ? void 0 : _this$seekBar2$showMa.call(_this$seekBar2);
2142
1473
  }
2143
1474
  });
2144
-
2145
1475
  this.videojs.on('useractive', () => {
2146
- this.seekBar && this.seekBar.showMarkers();
1476
+ if (this.seekBar) this.seekBar.showMarkers();
2147
1477
  });
2148
1478
  }
2149
1479
  }
2150
-
2151
- setupTimeCode(externalTimecodeInstance = null) {
1480
+ setupTimeCode() {
1481
+ let externalTimecodeInstance = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
2152
1482
  const props = {
2153
1483
  onInput: (e, nodeName, values) => {
2154
- // TODO - check for out of bounds input
2155
1484
  this.seekTo(this.timeConverter.smpteToCurrentTimeFromStart(values));
2156
1485
  },
2157
1486
  onInputFocus: () => {
2158
- /*
2159
- lock the control control bar in place to
2160
- prevent it from unexpectedly animating away while
2161
- doing timecode input
2162
- */
2163
1487
  this.videojs.options_.inactivityTimeout = 0;
2164
1488
  this.videojs.cache_.inactivityTimeout = 0;
2165
1489
  this.videojs.reportUserActivity();
2166
-
2167
- // pause the video, expected behaviour when editing timecode position
2168
1490
  this.videojs.pause();
2169
1491
  },
2170
1492
  onInputBlur: () => {
2171
1493
  this.videojs.options().inactivityTimeout = constants$2.defaults.inactivityTimeout;
2172
1494
  this.videojs.cache_.inactivityTimeout = constants$2.defaults.inactivityTimeout;
2173
- },
1495
+ }
2174
1496
  };
2175
-
2176
- if (externalTimecodeInstance !== null) { // setting up an external playback timecode
1497
+ if (externalTimecodeInstance !== null) {
2177
1498
  externalTimecodeInstance.props = Object.assign(externalTimecodeInstance.props, props);
2178
1499
  this.externalTimecodes.playback.push(externalTimecodeInstance);
2179
- } else if (this.props.timecode === true) { // player uses timecode
2180
- // construct an in-frame timecode, if controls are enabled
1500
+ } else if (this.props.timecode === true) {
2181
1501
  if (this.props.controls !== false) {
2182
1502
  const target = document.getElementsByClassName('vjs-volume-panel')[0];
2183
- // main timecode representing playhead position
2184
- this.timeCode = new TimeCodeDisplay(Object.assign(props, { target, appendAfter: true }));
2185
-
2186
- // read-only timecode representing video duration
1503
+ this.timeCode = new TimeCodeDisplay(Object.assign(props, {
1504
+ target,
1505
+ appendAfter: true
1506
+ }));
2187
1507
  this.durationTimeCode = new TimeCodeDisplay({
2188
1508
  target: this.timeCode.container,
2189
- appendAfter: true,
1509
+ appendAfter: true
2190
1510
  });
2191
1511
  }
2192
1512
  }
2193
-
2194
1513
  if (this.timeCode || this.externalTimecodes.playback.length > 0) {
2195
- // subscribe to events
2196
1514
  this.videojs.on('timeupdate', () => {
2197
1515
  this.updateTimeCode();
2198
1516
  });
2199
-
2200
1517
  this.videojs.on('play', () => {
2201
1518
  this.updateTimeCodeFrame();
2202
1519
  });
2203
1520
  }
2204
1521
  }
2205
-
2206
- updateTimeCode() { // HH:MM:SS (the usual)
1522
+ updateTimeCode() {
2207
1523
  const currentSecond = this.videojs.currentTime();
2208
1524
  const props = this.timeConverter.secondsFromStartToSMPTE(currentSecond);
2209
-
2210
- this.timeCode && this.timeCode.update(props);
2211
-
1525
+ if (this.timeCode) this.timeCode.update(props);
2212
1526
  if (this.externalTimecodes.playback.length > 0) {
2213
- this.externalTimecodes.playback.forEach((timecode) => {
1527
+ this.externalTimecodes.playback.forEach(timecode => {
2214
1528
  timecode.update(props);
2215
1529
  });
2216
1530
  }
2217
1531
  }
2218
-
2219
- updateTimeCodeFrame() { // update the frame in this second -> 00:00:00:XX
2220
- const frame = this.timeConverter.currentFrame(this.videojs.currentTime()); // -> :XX
2221
-
2222
- this.timeCode && this.timeCode.update({ frame }); // TODO - figure out a way to drop frames
2223
-
1532
+ updateTimeCodeFrame() {
1533
+ const frame = this.timeConverter.currentFrame(this.videojs.currentTime());
1534
+ if (this.timeCode) this.timeCode.update({
1535
+ frame
1536
+ });
2224
1537
  if (this.externalTimecodes.playback.length > 0) {
2225
- this.externalTimecodes.playback.forEach((timecode) => {
2226
- timecode.update({ frame });
1538
+ this.externalTimecodes.playback.forEach(timecode => {
1539
+ timecode.update({
1540
+ frame
1541
+ });
2227
1542
  });
2228
1543
  }
2229
-
2230
- // recursive invocation if still playing
2231
1544
  if (!this.videojs.paused()) {
2232
- this.rafID = requestAnimationFrame(() => { this.updateTimeCodeFrame(); });
1545
+ this.rafID = requestAnimationFrame(() => {
1546
+ this.updateTimeCodeFrame();
1547
+ });
2233
1548
  }
2234
1549
  }
2235
-
2236
- setDurationTimeCode() { // happens once
1550
+ setDurationTimeCode() {
2237
1551
  const durationInSeconds = this.videojs.duration();
2238
1552
  const props = this.timeConverter.secondsFromStartToSMPTE(durationInSeconds);
2239
1553
  props.frame = this.timeConverter.secondsToFrame(durationInSeconds);
2240
-
2241
1554
  this.durationTimeCode.update(props);
2242
1555
  this.durationTimeCode.container.classList.add('duration');
2243
1556
  }
2244
-
2245
- bindHotkeys() { // keyboard shortcuts
2246
- this.bindings = [
2247
- {
2248
- key: ['space', 'k'],
2249
- method: (e) => {
2250
- // prevents page scroll on space, just like YT
2251
- (e.preventDefault) && e.preventDefault();
2252
- this.togglePlayback();
2253
- },
2254
- },
2255
- {
2256
- key: 'j',
2257
- method: () => {
2258
- this.videojs.hasStarted_ && this.jumpBackward();
2259
- },
2260
- },
2261
- {
2262
- key: 'l',
2263
- method: () => {
2264
- this.videojs.hasStarted_ && this.jumpForward();
2265
- },
2266
- },
2267
- {
2268
- key: ['.', 'right'],
2269
- method: () => {
2270
- this.frameForward();
2271
- },
2272
- },
2273
- {
2274
- key: [',', 'left'],
2275
- method: () => {
2276
- this.frameBackward();
2277
- },
2278
- },
2279
- {
2280
- key: 'f',
2281
- method: () => {
2282
- this.toggleFullscreen();
2283
- },
2284
- },
2285
- {
2286
- key: 'm',
2287
- method: () => {
2288
- this.toggleMute();
2289
- },
2290
- },
2291
- {
2292
- key: 'up',
2293
- method: (e) => {
2294
- e.preventDefault && e.preventDefault(); // prevent page scroll
2295
- this.volumeUp();
2296
- },
2297
- },
2298
- {
2299
- key: 'down',
2300
- method: (e) => {
2301
- e.preventDefault && e.preventDefault(); // prevent page scroll
2302
- this.volumeDown();
2303
- },
2304
- },
2305
- {
2306
- key: 'h',
2307
- method: () => {
2308
- this.help.toggle();
2309
- },
2310
- },
2311
- {
2312
- key: 'n',
2313
- method: () => {
2314
- this.menu && this.menu.toggle();
2315
- },
2316
- },
2317
- ];
2318
-
1557
+ bindHotkeys() {
1558
+ this.bindings = [{
1559
+ key: ['space', 'k'],
1560
+ method: e => {
1561
+ if (e.preventDefault) e.preventDefault();
1562
+ this.togglePlayback();
1563
+ }
1564
+ }, {
1565
+ key: 'j',
1566
+ method: () => {
1567
+ if (this.videojs.hasStarted_) this.jumpBackward();
1568
+ }
1569
+ }, {
1570
+ key: 'l',
1571
+ method: () => {
1572
+ if (this.videojs.hasStarted_) this.jumpForward();
1573
+ }
1574
+ }, {
1575
+ key: ['.', 'right'],
1576
+ method: () => {
1577
+ this.frameForward();
1578
+ }
1579
+ }, {
1580
+ key: [',', 'left'],
1581
+ method: () => {
1582
+ this.frameBackward();
1583
+ }
1584
+ }, {
1585
+ key: 'f',
1586
+ method: () => {
1587
+ this.toggleFullscreen();
1588
+ }
1589
+ }, {
1590
+ key: 'm',
1591
+ method: () => {
1592
+ this.toggleMute();
1593
+ }
1594
+ }, {
1595
+ key: 'up',
1596
+ method: e => {
1597
+ if (e.preventDefault) e.preventDefault();
1598
+ this.volumeUp();
1599
+ }
1600
+ }, {
1601
+ key: 'down',
1602
+ method: e => {
1603
+ if (e.preventDefault) e.preventDefault();
1604
+ this.volumeDown();
1605
+ }
1606
+ }, {
1607
+ key: 'h',
1608
+ method: () => {
1609
+ this.help.toggle();
1610
+ }
1611
+ }, {
1612
+ key: 'n',
1613
+ method: () => {
1614
+ if (this.menu) this.menu.toggle();
1615
+ }
1616
+ }];
2319
1617
  if (this.props.subtitles) {
2320
1618
  this.bindings.push({
2321
1619
  key: 'c',
2322
1620
  method: () => {
2323
1621
  this.toggleSubs();
2324
- },
1622
+ }
2325
1623
  });
2326
1624
  }
2327
-
2328
- // bind
2329
- this.bindings.forEach((binding) => {
2330
- mousetrap__default["default"].bind(binding.key, (e) => {
1625
+ this.bindings.forEach(binding => {
1626
+ mousetrap__default["default"].bind(binding.key, e => {
2331
1627
  binding.method(e);
2332
1628
  this.onHotkeyPressed(binding.key);
2333
1629
  });
2334
1630
  });
2335
1631
  }
2336
-
2337
1632
  unbindHotKeys() {
2338
- this.bindings && this.bindings.forEach((binding) => {
1633
+ if (this.bindings) this.bindings.forEach(binding => {
2339
1634
  mousetrap__default["default"].unbind(binding.key);
2340
1635
  });
2341
1636
  }
2342
-
2343
1637
  bindEvents() {
2344
- this.videojs.on('volumechange', () => { // persist volume changes
1638
+ this.videojs.on('volumechange', () => {
2345
1639
  this.persistSetting(constants$2.settings.volume, this.volume);
2346
1640
  });
2347
-
2348
- this.videojs.textTracks().on('change', () => { // determine subtitles state
1641
+ this.videojs.textTracks().on('change', () => {
2349
1642
  const tracks = this.videojs.textTracks().tracks_;
2350
- const selected = tracks.filter((track) => (track.mode === 'showing'));
2351
- const areAnyTxtTracksActive = selected.length > 0; // -> true if any text tracks are showing
2352
-
2353
- if (areAnyTxtTracksActive) { // update container with appropriate className
1643
+ const selected = tracks.filter(track => track.mode === 'showing');
1644
+ const areAnyTxtTracksActive = selected.length > 0;
1645
+ if (areAnyTxtTracksActive) {
2354
1646
  this.videojs.el_.classList.add(constants$2.classNames.subtitlesActive);
2355
1647
  this.persistSetting(constants$2.settings.subs, true);
2356
1648
  this.persistSetting(constants$2.settings.subLang, selected[0].language);
@@ -2360,27 +1652,31 @@
2360
1652
  }
2361
1653
  });
2362
1654
  }
2363
-
2364
1655
  volumeUp() {
2365
1656
  this.videojs.controlBar.volumePanel.volumeControl.volumeBar.stepForward();
2366
- this.osd.feedback(constants$2.osd.volchange, { volume: this.volume });
1657
+ this.osd.feedback(constants$2.osd.volchange, {
1658
+ volume: this.volume
1659
+ });
2367
1660
  }
2368
-
2369
1661
  volumeDown() {
2370
1662
  this.videojs.controlBar.volumePanel.volumeControl.volumeBar.stepBack();
2371
- this.osd.feedback(constants$2.osd.volchange, { volume: this.volume });
1663
+ this.osd.feedback(constants$2.osd.volchange, {
1664
+ volume: this.volume
1665
+ });
2372
1666
  }
2373
-
2374
1667
  toggleMute() {
2375
1668
  if (!this.videojs.muted()) {
2376
1669
  this.videojs.muted(true);
2377
- this.osd.feedback(constants$2.osd.volchange, { volume: this.volume });
1670
+ this.osd.feedback(constants$2.osd.volchange, {
1671
+ volume: this.volume
1672
+ });
2378
1673
  } else {
2379
1674
  this.videojs.muted(false);
2380
- this.osd.feedback(constants$2.osd.volchange, { volume: this.volume });
1675
+ this.osd.feedback(constants$2.osd.volchange, {
1676
+ volume: this.volume
1677
+ });
2381
1678
  }
2382
1679
  }
2383
-
2384
1680
  toggleFullscreen() {
2385
1681
  if (this.videojs.isFullscreen()) {
2386
1682
  this.videojs.exitFullscreen();
@@ -2388,68 +1684,51 @@
2388
1684
  this.videojs.requestFullscreen();
2389
1685
  }
2390
1686
  }
2391
-
2392
- seek(secs) { // relative to current playback time
1687
+ seek(secs) {
2393
1688
  let time = this.videojs.currentTime() + secs;
2394
-
2395
1689
  if (time < 0) {
2396
1690
  time = 0;
2397
1691
  }
2398
-
2399
1692
  this.videojs.currentTime(time);
2400
1693
  }
2401
-
2402
1694
  seekTo(sec) {
2403
- !this.videojs.hasStarted() && this.videojs.hasStarted(true);
1695
+ if (!this.videojs.hasStarted()) this.videojs.hasStarted(true);
2404
1696
  this.videojs.currentTime(sec);
2405
1697
  }
2406
-
2407
1698
  jumpForward() {
2408
1699
  this.seek(constants$2.defaults.jumpSeconds);
2409
1700
  this.osd.feedback(constants$2.osd.jumpForward);
2410
1701
  }
2411
-
2412
1702
  jumpBackward() {
2413
1703
  this.seek(-constants$2.defaults.jumpSeconds);
2414
1704
  this.osd.feedback(constants$2.osd.jumpBackward);
2415
1705
  }
2416
-
2417
1706
  frameForward() {
2418
1707
  const thisFrame = this.timeConverter.secondsToFramesFromStart(this.videojs.currentTime());
2419
1708
  const nextFrame = Math.round(thisFrame) + 1;
2420
-
2421
1709
  this.videojs.currentTime(this.timeConverter.frameToCurrentTimeFromStart(nextFrame));
2422
1710
  }
2423
-
2424
1711
  frameBackward() {
2425
1712
  const thisFrame = this.timeConverter.secondsToFramesFromStart(this.videojs.currentTime());
2426
1713
  const previousFrame = Math.round(thisFrame) - 1;
2427
-
2428
1714
  this.videojs.currentTime(this.timeConverter.frameToCurrentTimeFromStart(previousFrame));
2429
1715
  }
2430
-
2431
1716
  secondsToFramesFromStart(seconds) {
2432
1717
  const frameRate = this.props.frameRate || constants$2.defaults.frameRate;
2433
1718
  let frame = seconds * frameRate;
2434
1719
  frame = Math.round(frame);
2435
- return frame; // we're on this frame, could be for example 23.45
1720
+ return frame;
2436
1721
  }
2437
-
2438
- doEsothericWork() { // non critical parts, that are nice to have
2439
- if (this.props.color) { // change the seekbar color from props
1722
+ doEsothericWork() {
1723
+ if (this.props.color) {
2440
1724
  document.getElementsByClassName('vjs-play-progress')[0].style.backgroundColor = this.props.color;
2441
1725
  }
2442
-
2443
- // feedback gizmo, mainly for feedback on hotkey-presses
2444
1726
  this.osd = new OSD({
2445
- target: this.videoEl.parentElement,
1727
+ target: this.videoEl.parentElement
2446
1728
  });
2447
-
2448
- // add a cinema button if a callback is passed in props
2449
1729
  if (this.props.onCinema) {
2450
1730
  this.videoEl.parentElement.classList.add('has-cinema-button');
2451
1731
  this.cinema = false;
2452
-
2453
1732
  this.addButtonToControlBar({
2454
1733
  onClick: () => {
2455
1734
  this.props.onCinema(!this.cinema);
@@ -2457,28 +1736,23 @@
2457
1736
  },
2458
1737
  componentName: 'CinemaButton',
2459
1738
  className: 'vdt-cinema-button',
2460
- title: 'Cinema-mode',
1739
+ title: 'Cinema-mode'
2461
1740
  });
2462
1741
  }
2463
-
2464
- // add a screenshot button if a callback is passed in props
2465
1742
  if (this.props.onScreenshot) {
2466
1743
  this.videoEl.parentElement.classList.add('has-screenshot-button');
2467
-
2468
1744
  this.addButtonToControlBar({
2469
1745
  onClick: () => {
2470
1746
  this.props.onScreenshot(this.videojs.currentTime());
2471
1747
  },
2472
1748
  componentName: 'ScreenshotButton',
2473
1749
  className: 'vdt-screenshot-button',
2474
- title: 'Screenshot',
1750
+ title: 'Screenshot'
2475
1751
  });
2476
1752
  }
2477
-
2478
- // add a help overlay explaining keyboard controls
2479
1753
  this.help = new HelpOverlay({
2480
1754
  target: this.videoEl.parentElement,
2481
- onToggle: (willShow) => {
1755
+ onToggle: willShow => {
2482
1756
  if (this.menu && this.menu.opened) {
2483
1757
  this.menu.close();
2484
1758
  }
@@ -2487,41 +1761,30 @@
2487
1761
  } else {
2488
1762
  this.showControls();
2489
1763
  }
2490
- },
1764
+ }
2491
1765
  });
2492
1766
  }
2493
-
2494
- restoreSettings() { // restore settings, like volume level or language track
2495
- // get values from storage
1767
+ restoreSettings() {
2496
1768
  const volume = localStorage.getItem(constants$2.settings.volume);
2497
1769
  const subsEnabled = localStorage.getItem(constants$2.settings.subs);
2498
1770
  const subLang = localStorage.getItem(constants$2.settings.subLang);
2499
-
2500
- // values vs default behaviours in a neatly readable map
2501
1771
  this.settings = {
2502
- // avoid .99999999 and other float pecularities in js
2503
- [constants$2.settings.volume]: (volume !== null) ? Math.round(volume * 100) / 100 : 1,
2504
- [constants$2.settings.subLang]: (subLang !== null) ? subLang : 'en',
2505
- [constants$2.settings.subs]: (subsEnabled !== null) ? JSON.parse(subsEnabled) : false,
1772
+ [constants$2.settings.volume]: volume !== null ? Math.round(volume * 100) / 100 : 1,
1773
+ [constants$2.settings.subLang]: subLang !== null ? subLang : 'en',
1774
+ [constants$2.settings.subs]: subsEnabled !== null ? JSON.parse(subsEnabled) : false
2506
1775
  };
2507
-
2508
- // apply values (settings)
2509
- this.volume = this.settings[constants$2.settings.volume]; // volume
2510
-
2511
- if (this.settings[constants$2.settings.subs]) { // subtitles
1776
+ this.volume = this.settings[constants$2.settings.volume];
1777
+ if (this.settings[constants$2.settings.subs]) {
2512
1778
  this.enableSubs();
2513
1779
  }
2514
1780
  }
2515
-
2516
1781
  persistSetting(key, val) {
2517
1782
  this.settings[key] = val;
2518
1783
  localStorage.setItem(key, val);
2519
1784
  }
2520
-
2521
1785
  enableSubs() {
2522
1786
  const subs = this.videojs.textTracks();
2523
-
2524
- for (let i = 0; i < subs.length; i++) {
1787
+ for (let i = 0; i < subs.length; i += 1) {
2525
1788
  const sub = subs[i];
2526
1789
  if (sub.language === this.settings[constants$2.settings.subLang]) {
2527
1790
  sub.mode = 'showing';
@@ -2530,57 +1793,42 @@
2530
1793
  }
2531
1794
  }
2532
1795
  }
2533
-
2534
1796
  disableSubs() {
2535
1797
  const subs = this.videojs.textTracks();
2536
-
2537
- for (let i = 0; i < subs.length; i++) {
1798
+ for (let i = 0; i < subs.length; i += 1) {
2538
1799
  const sub = subs[i];
2539
1800
  sub.mode = 'hidden';
2540
1801
  }
2541
1802
  }
2542
-
2543
1803
  toggleSubs() {
2544
- if (this.videojs.textTracks().length > 0) { // check if subtitles exist
2545
- if (this.videojs.textTracks().tracks_.filter((track) => (track.mode === 'showing')).length > 0) { // a certain subtitle track is enabled
1804
+ if (this.videojs.textTracks().length > 0) {
1805
+ if (this.videojs.textTracks().tracks_.filter(track => track.mode === 'showing').length > 0) {
2546
1806
  this.disableSubs();
2547
1807
  } else {
2548
1808
  this.enableSubs();
2549
1809
  }
2550
1810
  }
2551
1811
  }
2552
-
2553
- /*
2554
- A quick example
2555
- options = {
2556
- onClick: ()=>{},
2557
- componentName: 'ScreenshotButton',
2558
- className: 'vdt-screenshot-button'
2559
- }
2560
- */
2561
1812
  addButtonToControlBar(options) {
2562
1813
  const Button = videojs__default["default"].getComponent('Button');
2563
1814
  const CustomButton = videojs__default["default"].extend(Button, {
2564
- /* eslint-disable func-names, object-shorthand */
2565
- constructor: function () {
1815
+ constructor: function constructor() {
2566
1816
  Button.apply(this, arguments);
2567
- options.className && this.el_.classList.add(options.className);
2568
- if (options.title) { this.el_.title = options.title; }
1817
+ if (options.className) this.el_.classList.add(options.className);
1818
+ if (options.title) {
1819
+ this.el_.title = options.title;
1820
+ }
2569
1821
  },
2570
1822
  handleClick: () => {
2571
- options.onClick && options.onClick();
2572
- },
1823
+ if (options.onClick) options.onClick();
1824
+ }
2573
1825
  });
2574
-
2575
1826
  videojs__default["default"].registerComponent(options.componentName, CustomButton);
2576
1827
  this.videojs.getChild('controlBar').addChild(options.componentName, {}, 7);
2577
1828
  }
2578
-
2579
- // convenient getters / setters
2580
1829
  get volume() {
2581
1830
  return this.videojs.controlBar.volumePanel.volumeControl.volumeBar.getPercent();
2582
1831
  }
2583
-
2584
1832
  set volume(floatVal) {
2585
1833
  if (floatVal === 0) {
2586
1834
  this.videojs.muted(true);
@@ -2589,58 +1837,42 @@
2589
1837
  this.videojs.volume(floatVal);
2590
1838
  }
2591
1839
  }
2592
-
2593
1840
  setupMenu() {
2594
1841
  const menuItems = this.props.menuItems || [];
2595
1842
  const sourcesMenuItems = [];
2596
- if (this.props.sources.length > 1) { // show only if more than 1 source
2597
- this.props.sources.forEach((source) => {
1843
+ if (this.props.sources.length > 1) {
1844
+ this.props.sources.forEach(source => {
2598
1845
  if (source.label) {
2599
1846
  sourcesMenuItems.push({
2600
1847
  title: source.label,
2601
1848
  onClick: () => {
2602
1849
  this.src = source;
2603
- },
1850
+ }
2604
1851
  });
2605
1852
  }
2606
1853
  });
2607
-
2608
1854
  menuItems.push({
2609
1855
  title: 'Sources',
2610
1856
  getActiveIndex: () => {
2611
- const matchingSource = this.props.sources.find((source) => (source.src === this.videojs.src()));
2612
- return sourcesMenuItems.indexOf(sourcesMenuItems.find((menuItem) => (menuItem.title === matchingSource.label)));
1857
+ const matchingSource = this.props.sources.find(source => source.src === this.videojs.src());
1858
+ return sourcesMenuItems.indexOf(sourcesMenuItems.find(menuItem => menuItem.title === matchingSource.label));
2613
1859
  },
2614
- items: sourcesMenuItems,
1860
+ items: sourcesMenuItems
2615
1861
  });
2616
1862
  }
2617
-
2618
- // subtitles
2619
1863
  if (this.props.subtitles && this.props.subtitles.length > 0) {
2620
- // off option for subs
2621
1864
  const subtitleMenuItems = [{
2622
1865
  title: 'Off',
2623
1866
  onClick: () => {
2624
1867
  this.disableSubs();
2625
- },
1868
+ }
2626
1869
  }];
2627
-
2628
- // quick toggle in the control bar
2629
- // this.addButtonToControlBar({
2630
- // onClick: () => {
2631
- // this.props.onCinema(!this.cinema);
2632
- // this.cinema = !this.cinema;
2633
- // },
2634
- // componentName: 'CinemaButton',
2635
- // className: 'vdt-cinema-button',
2636
- // });
2637
-
2638
1870
  this.props.subtitles.forEach((subtitle, index) => {
2639
1871
  subtitleMenuItems.push({
2640
1872
  title: subtitle.label,
2641
1873
  onClick: () => {
2642
1874
  const subs = this.videojs.textTracks();
2643
- for (let i = 0; i < subs.length; i++) {
1875
+ for (let i = 0; i < subs.length; i += 1) {
2644
1876
  const sub = subs[i];
2645
1877
  if (i === index) {
2646
1878
  sub.mode = 'showing';
@@ -2648,72 +1880,59 @@
2648
1880
  sub.mode = 'hidden';
2649
1881
  }
2650
1882
  }
2651
- },
1883
+ }
2652
1884
  });
2653
1885
  });
2654
-
2655
- menuItems.push(
2656
- {
2657
- title: 'Subtitles',
2658
- getActiveIndex: () => {
2659
- const subs = this.videojs.textTracks();
2660
- for (let i = 0; i < subs.length; i++) {
2661
- const sub = subs[i];
2662
- if (sub.mode === 'showing') {
2663
- return i + 1; // offset "off" option
2664
- }
1886
+ menuItems.push({
1887
+ title: 'Subtitles',
1888
+ getActiveIndex: () => {
1889
+ const subs = this.videojs.textTracks();
1890
+ for (let i = 0; i < subs.length; i += 1) {
1891
+ const sub = subs[i];
1892
+ if (sub.mode === 'showing') {
1893
+ return i + 1;
2665
1894
  }
2666
-
2667
- return 0; // if none are showing, then probably "off"
2668
- },
2669
- items: subtitleMenuItems,
1895
+ }
1896
+ return 0;
2670
1897
  },
2671
- );
1898
+ items: subtitleMenuItems
1899
+ });
2672
1900
  }
2673
-
2674
1901
  if (menuItems.length > 0) {
2675
1902
  const target = this.videojs.el_;
2676
-
2677
1903
  this.menu = new Menu({
2678
1904
  target,
2679
1905
  items: menuItems,
2680
1906
  onOpen: () => {
2681
- !this.controlsBelowPlayer && this.hideControls();
1907
+ if (!this.controlsBelowPlayer) this.hideControls();
2682
1908
  },
2683
1909
  onClose: () => {
2684
- !this.controlsBelowPlayer && this.videojs.controls(true);
2685
- },
1910
+ if (!this.controlsBelowPlayer) this.videojs.controls(true);
1911
+ }
2686
1912
  });
2687
-
2688
- // add a cogwheel button to the player
2689
1913
  this.videoEl.parentElement.classList.add('has-vdt-menu');
2690
-
2691
1914
  this.addButtonToControlBar({
2692
1915
  onClick: () => {
2693
1916
  this.menu.toggle();
2694
1917
  },
2695
1918
  componentName: 'SettingsMenuButton',
2696
1919
  className: 'vdt-settings-menu-button',
2697
- title: 'Settings',
1920
+ title: 'Settings'
2698
1921
  });
2699
1922
  }
2700
1923
  }
2701
-
2702
1924
  hideControls() {
2703
1925
  this.videojs.userActive(false);
2704
1926
  this.videojs.controls(false);
2705
1927
  this.videojs.fluid(true);
2706
-
2707
1928
  this.videoEl.parentElement.classList.remove('vjs-user-active');
2708
1929
  this.videojs.userActive(false);
2709
1930
  }
2710
-
2711
1931
  showControls() {
2712
1932
  this.videojs.fluid(!this.controlsBelowPlayer);
2713
1933
  this.videojs.controls(true);
2714
1934
  this.videojs.userActive(true);
2715
1935
  }
2716
-
2717
1936
  toggleControls() {
2718
1937
  if (this.videojs.controls()) {
2719
1938
  this.hideControls();
@@ -2721,77 +1940,73 @@
2721
1940
  this.showControls();
2722
1941
  }
2723
1942
  }
2724
-
2725
1943
  onHotkeyPressed(key) {
2726
1944
  if (key !== 'h' && this.help.state.active) {
2727
1945
  this.help.hide();
2728
1946
  }
2729
1947
  }
2730
-
2731
- // eslint-disable-next-line class-methods-use-this
2732
1948
  triggerHotkey(key) {
2733
1949
  mousetrap__default["default"].trigger(key);
2734
1950
  }
2735
-
2736
1951
  toStart() {
2737
1952
  this.videojs.currentTime(0);
2738
1953
  }
2739
-
2740
1954
  toEnd() {
2741
1955
  this.videojs.currentTime(this.videojs.duration());
2742
1956
  }
2743
-
2744
1957
  replay() {
2745
1958
  this.toStart();
2746
1959
  this.videojs.play();
2747
1960
  }
2748
-
2749
1961
  play() {
2750
1962
  this.videojs.play();
2751
1963
  }
2752
-
2753
1964
  pause() {
2754
- !this.videojs.paused() && this.videojs.pause();
1965
+ if (!this.videojs.paused()) this.videojs.pause();
2755
1966
  }
2756
-
2757
1967
  togglePlayback() {
2758
1968
  if (this.videojs.paused()) {
2759
1969
  this.play();
2760
- this.videojs.hasStarted_ && this.osd.feedback(constants$2.osd.play);
1970
+ if (this.videojs.hasStarted_) this.osd.feedback(constants$2.osd.play);
2761
1971
  } else {
2762
1972
  this.pause();
2763
1973
  this.osd.feedback(constants$2.osd.pause);
2764
1974
  }
2765
1975
  }
2766
-
2767
- destroy() { // clean up
1976
+ updateSmpte(valuesObj) {
1977
+ const startCount = this.nodeNames.length - 1;
1978
+ let i;
1979
+ for (i = startCount; i > -1; i -= 1) {
1980
+ const key = this.nodeNames[i];
1981
+ if (typeof valuesObj[key] !== 'undefined' && valuesObj[key] !== parseInt(this.nodes[key].value, 10) && key !== this.state.selectedNode) {
1982
+ const str = valuesObj[key].toString();
1983
+ this.nodes[key].value = this.toDigitPairStr(str);
1984
+ }
1985
+ }
1986
+ }
1987
+ destroy() {
2768
1988
  this.videojs.paused(true);
2769
- this.rafID && cancelAnimationFrame(this.rafID);
1989
+ if (this.rafID) cancelAnimationFrame(this.rafID);
2770
1990
  this.videojs.dispose();
2771
1991
  this.unbindHotKeys();
2772
- this.seekBar && this.seekBar.destroy();
1992
+ if (this.seekBar) this.seekBar.destroy();
2773
1993
  }
2774
-
2775
1994
  get container() {
2776
1995
  return this.videoEl.parentElement;
2777
1996
  }
2778
-
2779
1997
  set onDark(bool) {
2780
1998
  if (bool) {
2781
1999
  this.container.classList.add('on-dark');
2782
2000
  } else {
2783
2001
  this.container.classList.remove('on-dark');
2784
2002
  }
2785
-
2786
2003
  if (this.seekBar) {
2787
2004
  this.seekBar.onDark = bool;
2788
2005
  }
2789
2006
  }
2790
-
2791
2007
  get onDark() {
2792
2008
  return this.container.classList.contains('on-dark');
2793
2009
  }
2794
-
2795
2010
  set controlsBelowPlayer(bool) {
2796
2011
  if (this.videojs.controls()) {
2797
2012
  if (bool) {
@@ -2803,36 +2018,29 @@
2803
2018
  }
2804
2019
  }
2805
2020
  }
2806
-
2807
2021
  get controlsBelowPlayer() {
2808
2022
  return this.container.classList.contains('controls-below-player');
2809
2023
  }
2810
-
2811
2024
  get smpte() {
2812
2025
  const currentSecond = this.videojs.currentTime();
2813
2026
  return this.timeConverter.secondsFromStartToSMPTE(currentSecond);
2814
2027
  }
2815
-
2816
- set smpte(smpte) {
2817
- const currentSecond = this.videojs.currentTime();
2818
- return this.timeConverter.secondsFromStartToSMPTE(currentSecond);
2028
+ set smpte(o) {
2029
+ this.updateSmpte(o);
2819
2030
  }
2820
-
2821
- set src({ src, type = 'video/mp4', label = null }) {
2031
+ set src(_ref) {
2032
+ let {
2033
+ src,
2034
+ type = 'video/mp4',
2035
+ label = null
2036
+ } = _ref;
2822
2037
  const currentTime = this.videojs.currentTime();
2823
- // iterate trough sources, find existing matches
2824
- const source = this.props.sources.find((aSource) => (aSource.src === src));
2825
- // if no match try adding source
2826
-
2038
+ const source = this.props.sources.find(aSource => aSource.src === src);
2827
2039
  const onReady = () => {
2828
2040
  let initdone = false;
2829
- // wait for video metadata to load, then set time
2830
2041
  this.videojs.on('loadedmetadata', () => {
2831
2042
  this.videojs.currentTime(currentTime);
2832
2043
  });
2833
-
2834
- // iPhone/iPad need to play first, then set the time
2835
- // events: https://www.w3.org/TR/html5/embedded-content-0.html#mediaevents
2836
2044
  this.videojs.on('canplaythrough', () => {
2837
2045
  if (!initdone) {
2838
2046
  this.videojs.currentTime(currentTime);
@@ -2841,13 +2049,9 @@
2841
2049
  }
2842
2050
  });
2843
2051
  };
2844
-
2845
- if (source && typeof souce === 'Array' && source.length > 0) {
2846
- // TODO - if(source.length > 1) - try one by one until a successful load happens
2052
+ if (source && Array.isArray(source) && source.length > 0) {
2847
2053
  this.videojs.addClass('vjs-waiting');
2848
- this.videojs.src({
2849
- ...source[0],
2850
- });
2054
+ this.videojs.src(_objectSpread({}, source[0]));
2851
2055
  onReady();
2852
2056
  } else {
2853
2057
  this.videojs.addClass('vjs-waiting');
@@ -2855,26 +2059,20 @@
2855
2059
  onReady();
2856
2060
  }
2857
2061
  }
2858
-
2859
2062
  get src() {
2860
2063
  return this.videojs.src();
2861
2064
  }
2862
-
2863
2065
  addSrc(src, type, label) {
2864
2066
  const newSource = {
2865
2067
  src,
2866
2068
  type,
2867
- label,
2069
+ label
2868
2070
  };
2869
2071
  const index = this.props.sources.push(newSource) - 1;
2870
-
2871
2072
  return this.props.sources[index];
2872
2073
  }
2873
-
2874
2074
  removeSrc(url) {
2875
- // should remove all url matching sources
2876
2075
  }
2877
-
2878
2076
  set controls(bool) {
2879
2077
  if (bool) {
2880
2078
  this.showControls();
@@ -2882,357 +2080,30 @@
2882
2080
  this.hideControls();
2883
2081
  }
2884
2082
  }
2885
-
2886
2083
  get percentLoaded() {
2887
2084
  const r = this.videojs.buffered();
2888
-
2889
- const percentLoaded = (r.end(0) / this.videojs.duration()) * 100;
2085
+ const percentLoaded = r.end(0) / this.videojs.duration() * 100;
2890
2086
  return percentLoaded;
2891
2087
  }
2892
2088
  }
2893
-
2894
- /*
2895
- Some video.js prototype overrides, that
2896
- help with undesired behaviour
2897
- */
2898
-
2899
- // take over space button, prevent video.js default behaviour
2900
2089
  const button = videojs__default["default"].prototype.constructor.getComponent('Button');
2901
- // eslint-disable-next-line func-names
2902
2090
  button.prototype.handleKeyDown = function (e) {
2903
2091
  if (e.keyCode === 32) {
2904
2092
  e.preventDefault();
2905
2093
  }
2906
2094
  };
2907
2095
  videojs__default["default"].prototype.constructor.registerComponent('Button', button);
2908
-
2909
- // video.js opens menu on hover by default, this prevents that
2910
2096
  const subsButton = videojs__default["default"].prototype.constructor.getComponent('SubsCapsButton');
2911
2097
  class CustomSubsCapsButton extends subsButton {
2912
- constructor(player, options = {}) {
2098
+ constructor(player) {
2099
+ let options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
2913
2100
  super(player, options);
2914
2101
  this.menuButton_.off('mouseenter');
2915
2102
  this.menuButton_.off('mouseleave');
2916
2103
  }
2917
2104
  }
2918
-
2919
- /*
2920
- TODO - see what can be done about strange behaviour
2921
- when cc button is clicked with the menu showing
2922
- */
2923
2105
  videojs__default["default"].prototype.constructor.registerComponent('SubsCapsButton', CustomSubsCapsButton);
2924
2106
 
2925
- /* eslint-disable no-plusplus */
2926
-
2927
- const buttonNames = Object.freeze({
2928
- play: 'play-pause',
2929
- start: 'start',
2930
- fb: 'frame-backward',
2931
- ff: 'frame-forward',
2932
- end: 'end',
2933
- });
2934
-
2935
- const buttons = [ // order matters
2936
- buttonNames.start,
2937
- buttonNames.fb,
2938
- buttonNames.play,
2939
- buttonNames.ff,
2940
- buttonNames.end,
2941
- ];
2942
-
2943
- /*
2944
- Since this could possibly be used in a
2945
- context without the player and vice versa -
2946
- will try and make it as self-contained
2947
- as possible, currently utilizing
2948
- shadow DOM (style encapsulation as main reason),
2949
- https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM
2950
- BEWARE - IE < Edge is unsupported
2951
- */
2952
-
2953
- const CSS = `
2954
- .vdt-external-controls{
2955
- display: flex;
2956
- justify-content: center;
2957
- align-items: center;
2958
- }
2959
-
2960
- .control{
2961
- flex: 1;
2962
- flex-grow: 1;
2963
- height: 100%;
2964
- align-contents: center;
2965
- align-self: center;
2966
- text-align: center;
2967
- vertical-align: middle;
2968
- cursor: pointer;
2969
- user-select: none;
2970
- height: 20px;
2971
- padding-top: 10px;
2972
- padding-bottom: 10px;
2973
- }
2974
-
2975
- .control .icon{
2976
- display: inline-block;
2977
- height: 20px;
2978
- white-space: nowrap;
2979
- opacity: .9;
2980
- }
2981
-
2982
- .control:hover .icon{ opacity: 1; }
2983
-
2984
- .control .icon:before{
2985
- opacity: 1;
2986
- flex: 1;
2987
- align-self: center;
2988
- content: " ";
2989
- display: inline-block;
2990
- margin: 0 auto;
2991
-
2992
- /* triangles */
2993
- width: 0;
2994
- height: 0;
2995
- border-style: solid;
2996
- border-width: 16px 0 16px 23px;
2997
- border-color: transparent transparent transparent ${colors.gray};
2998
- }
2999
-
3000
- .vdt-external-controls.${classNames.onDark} .control .icon:before{
3001
- border-color: transparent transparent transparent ${colors.light};
3002
- }
3003
-
3004
- /* specific buttons */
3005
-
3006
- .control.${buttonNames.start},
3007
- .control.${buttonNames.end}{
3008
- height: 20px;
3009
- }
3010
-
3011
- .control.${buttonNames.play}{
3012
- height: 30px;
3013
- padding-top: 0px;
3014
- padding-bottom: 0px;
3015
- }
3016
-
3017
- .control.${buttonNames.play} .icon{
3018
- height: 30px;
3019
- }
3020
-
3021
- .control.${buttonNames.ff} .icon:before,
3022
- .control.${buttonNames.fb} .icon:before,
3023
- .control.${buttonNames.end} .icon:before,
3024
- .control.${buttonNames.start} .icon:before{
3025
- border-width: 10px 0 10px 16px;
3026
- }
3027
-
3028
- .control.${buttonNames.fb} .icon,
3029
- .control.${buttonNames.start}{
3030
- transform: rotate(180deg);
3031
- }
3032
-
3033
- .control.${buttonNames.end} .icon:after,
3034
- .control.${buttonNames.start} .icon:after{
3035
- content: " ";
3036
- width: 4px;
3037
- height: 20px;
3038
- display: inline-block;
3039
- background: ${colors.gray};
3040
- }
3041
-
3042
- .vdt-external-controls.${classNames.onDark} .control.${buttonNames.end} .icon:after,
3043
- .vdt-external-controls.${classNames.onDark} .control.${buttonNames.start} .icon:after{
3044
- background: ${colors.light};
3045
- }
3046
-
3047
- /* stateful styles */
3048
- .control.${buttonNames.play}.playing .icon:before{
3049
- height: 30px;
3050
- width: 6px;
3051
- border-width: 0px 8px 0px 8px;
3052
- border-color: transparent ${colors.gray} transparent ${colors.gray};
3053
- }
3054
-
3055
- .vdt-external-controls.${classNames.onDark} .control.${buttonNames.play}.playing .icon:before{
3056
- border-color: transparent ${colors.light} transparent ${colors.light};
3057
- }
3058
-
3059
- .vdt-external-controls.${classNames.loading} .control.${buttonNames.play}{
3060
- border-color: transparent #f00 transparent #f00;
3061
- }
3062
- `;
3063
-
3064
- class ExternalControls {
3065
- constructor(props) {
3066
- this.props = props;
3067
- reactiveSetters.apply(this);
3068
-
3069
- this.state = {
3070
- playing: false,
3071
- onDark: false,
3072
- loading: false,
3073
- };
3074
-
3075
- this.shadowRoot = document.createElement('div'); // will attach shadow here, after appending to target
3076
- this.shadowRoot.className = 'vdt-external-controls-wrapper';
3077
- this.container = document.createElement('div');
3078
- this.container.className = 'vdt-external-controls';
3079
-
3080
- if (this.props.target) {
3081
- this.props.target.appendChild(this.shadowRoot);
3082
- this.shadow = this.shadowRoot.attachShadow({ mode: 'open' });
3083
- // attach styles
3084
- this.style = document.createElement('style');
3085
- this.style.type = 'text/css';
3086
- this.style.appendChild(document.createTextNode(CSS));
3087
- this.shadow.appendChild(this.style);
3088
-
3089
- // attach container to shadow root
3090
- this.shadow.appendChild(this.container);
3091
- }
3092
-
3093
- this.handleNewProps(props);
3094
- this.bindEvents();
3095
- this.draw();
3096
- }
3097
-
3098
- draw() {
3099
- this.buttons = {}; // map of html nodes for DOM manipulations
3100
- const excludes = this.props.excludes || [];
3101
-
3102
- buttons.forEach((buttonName) => {
3103
- if (excludes.indexOf(buttonName) < 0) {
3104
- const btn = document.createElement('div');
3105
- btn.className = `control ${buttonName}`;
3106
- btn.innerHTML = '<span class="icon"></span>';
3107
-
3108
- btn.onclick = () => {
3109
- this.handlePress(buttonName);
3110
- };
3111
-
3112
- this.buttons[buttonName] = btn;
3113
- this.container.appendChild(btn);
3114
- }
3115
- });
3116
- }
3117
-
3118
- /*
3119
- bind to play / pause / stop and other events,
3120
- so that controls reflect the player state
3121
- */
3122
- bindEvents() {
3123
- if (this.props.player && this.props.player.videojs) {
3124
- const { player } = this.props;
3125
-
3126
- player.videojs.on('play', () => {
3127
- this.playing = true;
3128
- });
3129
-
3130
- player.videojs.on('pause', () => {
3131
- this.playing = false;
3132
- });
3133
-
3134
- player.videojs.on('stop', () => {
3135
- this.playing = false;
3136
- });
3137
- }
3138
- }
3139
-
3140
- handlePress(buttonName) {
3141
- if (this.props.player) { // control a vdt player instance directly
3142
- const player = this.props.player;
3143
- console.log(player);
3144
- player.osd && player.osd.disable(); // temporarily disable OSD
3145
-
3146
- switch (buttonName) {
3147
- case buttonNames.start:
3148
- player.toStart();
3149
- break;
3150
- case buttonNames.fb:
3151
- player.triggerHotkey('left');
3152
- break;
3153
- case buttonNames.play:
3154
- player.triggerHotkey('k'); // TODO - triggering hotkeys has a side effect - OSD will give feedback
3155
- break;
3156
- case buttonNames.ff:
3157
- player.triggerHotkey('right');
3158
- break;
3159
- case buttonNames.end:
3160
- player.toEnd();
3161
- break;
3162
- }
3163
-
3164
- player.osd && player.osd.enable(); // re-enable OSD
3165
- } else {
3166
- switch (buttonName) {
3167
- case buttonNames.start:
3168
- this.props.onPrev && this.props.onPrev();
3169
- break;
3170
- case buttonNames.fb:
3171
- this.props.onFB && this.props.onFB();
3172
- break;
3173
- case buttonNames.play:
3174
- if (this.playing) {
3175
- this.props.onPause && this.props.onPause();
3176
- } else {
3177
- this.props.onPlay && this.props.onPlay();
3178
- }
3179
- break;
3180
- case buttonNames.ff:
3181
- this.props.onFF && this.props.onFF();
3182
- break;
3183
- case buttonNames.end:
3184
- this.props.onNext && this.props.onNext();
3185
- break;
3186
- }
3187
- }
3188
- }
3189
-
3190
- // setters and getters for state
3191
- set playing(bool) {
3192
- if (bool) {
3193
- this.state.playing = true;
3194
- this.buttons[buttonNames.play].classList.add('playing');
3195
- } else {
3196
- this.state.playing = false;
3197
- this.buttons[buttonNames.play].classList.remove('playing');
3198
- }
3199
- }
3200
-
3201
- get playing() {
3202
- return this.state.playing;
3203
- }
3204
-
3205
- set onDark(bool) {
3206
- if (bool) {
3207
- this.state.onDark = true;
3208
- this.container.classList.add(classNames.onDark);
3209
- } else {
3210
- this.state.onDark = false;
3211
- this.container.classList.remove(classNames.onDark);
3212
- }
3213
- }
3214
-
3215
- get onDark() {
3216
- return this.container.classList.contains(classNames.onDark);
3217
- }
3218
-
3219
- set loading(bool) {
3220
- if (bool) {
3221
- this.state.loading = true;
3222
- this.container.classList.add(classNames.loading);
3223
- } else {
3224
- this.state.loading = false;
3225
- this.container.classList.remove(classNames.loading);
3226
- }
3227
- }
3228
-
3229
- get loading() {
3230
- return this.state.loading;
3231
- }
3232
- }
3233
-
3234
- ExternalControls.buttonNames = buttonNames;
3235
-
3236
2107
  exports.ExternalControls = ExternalControls;
3237
2108
  exports.Player = Player;
3238
2109
  exports.SeekBar = SeekBar;