@mindedge/vuetify-player 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -50
- package/package.json +1 -1
- package/src/components/Media/CaptionsMenu.vue +260 -94
- package/src/components/Media/Html5Player.vue +595 -349
- package/src/components/Media/PlaylistMenu.vue +40 -42
- package/src/components/Media/SettingsMenu.vue +98 -0
- package/src/components/Media/YoutubePlayer.vue +5 -1
- package/src/components/VuetifyPlayer.vue +214 -28
- package/src/i18n/en-US.js +10 -0
- package/src/i18n/es-ES.js +16 -3
- package/src/i18n/i18n.js +6 -1
- package/src/i18n/sv-SE.js +12 -0
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<v-container>
|
|
3
3
|
<v-row>
|
|
4
|
-
<v-col
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
<v-col
|
|
5
|
+
ref="playerContainer"
|
|
6
|
+
:cols="!state.expandedCaptions ? 12 : 6"
|
|
7
|
+
class="pb-0 mb-0"
|
|
8
|
+
>
|
|
9
|
+
<div
|
|
10
|
+
v-if="resolvedType === 'video' && buffering"
|
|
11
|
+
class="player-overlay"
|
|
12
|
+
>
|
|
13
|
+
<div class="player-overlay--icon">
|
|
14
|
+
<v-progress-circular
|
|
15
|
+
:size="50"
|
|
16
|
+
indeterminate
|
|
17
|
+
></v-progress-circular>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
<div
|
|
21
|
+
v-if="resolvedType === 'video' && state.replay"
|
|
22
|
+
class="player-overlay"
|
|
23
|
+
>
|
|
24
|
+
<div class="player-overlay--icon">
|
|
25
|
+
<v-icon class="player-overlay--replay-icon">
|
|
26
|
+
mdi-replay
|
|
27
|
+
</v-icon>
|
|
28
|
+
</div>
|
|
10
29
|
</div>
|
|
11
30
|
<video
|
|
12
31
|
ref="player"
|
|
13
32
|
tabindex="0"
|
|
14
|
-
:class="'player-' +
|
|
33
|
+
:class="'player-' + resolvedType"
|
|
15
34
|
:height="attributes.height"
|
|
16
35
|
:width="attributes.width"
|
|
17
36
|
:autoplay="attributes.autoplay"
|
|
@@ -27,7 +46,7 @@
|
|
|
27
46
|
:playsinline="attributes.playsinline"
|
|
28
47
|
:poster="src.poster || attributes.poster"
|
|
29
48
|
:preload="attributes.preload"
|
|
30
|
-
@click="
|
|
49
|
+
@click="playToggle"
|
|
31
50
|
@seeking="onSeeking"
|
|
32
51
|
@timeupdate="onTimeupdate"
|
|
33
52
|
@progress="onMediaProgress"
|
|
@@ -44,6 +63,8 @@
|
|
|
44
63
|
@emptied="$emit('emptied', $event)"
|
|
45
64
|
@stalled="$emit('stalled', $event)"
|
|
46
65
|
@abort="$emit('abort', $event)"
|
|
66
|
+
@focusin="$emit('focusin', $event)"
|
|
67
|
+
@focusout="$emit('focusout', $event)"
|
|
47
68
|
>
|
|
48
69
|
<source
|
|
49
70
|
v-for="(source, index) of current.sources"
|
|
@@ -65,12 +86,13 @@
|
|
|
65
86
|
</video>
|
|
66
87
|
|
|
67
88
|
<div
|
|
89
|
+
ref="controlsContainer"
|
|
68
90
|
class="controls-container"
|
|
69
91
|
v-if="attributes.controls"
|
|
70
92
|
@mouseover="onControlsHover"
|
|
71
93
|
>
|
|
72
94
|
<v-slide-y-reverse-transition>
|
|
73
|
-
<div v-if="player &&
|
|
95
|
+
<div v-if="player && state.controls" class="controls">
|
|
74
96
|
<v-slider
|
|
75
97
|
dark
|
|
76
98
|
v-model="currentPercent"
|
|
@@ -89,25 +111,23 @@
|
|
|
89
111
|
>
|
|
90
112
|
<template #prepend>
|
|
91
113
|
<!-- Play button -->
|
|
92
|
-
<v-tooltip top>
|
|
93
|
-
<template
|
|
94
|
-
v-slot:activator="{ on, attrs }"
|
|
95
|
-
>
|
|
114
|
+
<v-tooltip v-if="!state.replay" top>
|
|
115
|
+
<template #activator="{ on, attrs }">
|
|
96
116
|
<v-btn
|
|
97
117
|
small
|
|
98
118
|
text
|
|
99
119
|
v-bind="attrs"
|
|
100
120
|
v-on="on"
|
|
101
|
-
@click="
|
|
121
|
+
@click="playToggle"
|
|
102
122
|
>
|
|
103
123
|
<v-icon>{{
|
|
104
|
-
|
|
124
|
+
state.paused
|
|
105
125
|
? 'mdi-play'
|
|
106
126
|
: 'mdi-pause'
|
|
107
127
|
}}</v-icon>
|
|
108
128
|
<span class="d-sr-only">
|
|
109
129
|
{{
|
|
110
|
-
|
|
130
|
+
state.paused
|
|
111
131
|
? t(
|
|
112
132
|
language,
|
|
113
133
|
'player.play'
|
|
@@ -121,23 +141,50 @@
|
|
|
121
141
|
</v-btn>
|
|
122
142
|
</template>
|
|
123
143
|
<span>{{
|
|
124
|
-
|
|
144
|
+
state.paused
|
|
125
145
|
? t(language, 'player.play')
|
|
126
146
|
: t(language, 'player.pause')
|
|
127
147
|
}}</span>
|
|
128
148
|
</v-tooltip>
|
|
129
149
|
|
|
150
|
+
<!-- Replay button -->
|
|
151
|
+
<v-tooltip v-if="state.replay" top>
|
|
152
|
+
<template #activator="{ on, attrs }">
|
|
153
|
+
<v-btn
|
|
154
|
+
small
|
|
155
|
+
text
|
|
156
|
+
v-bind="attrs"
|
|
157
|
+
v-on="on"
|
|
158
|
+
@click="onClickReplay"
|
|
159
|
+
>
|
|
160
|
+
<v-icon>mdi-replay</v-icon>
|
|
161
|
+
<span class="d-sr-only">
|
|
162
|
+
{{
|
|
163
|
+
t(
|
|
164
|
+
language,
|
|
165
|
+
'player.replay'
|
|
166
|
+
)
|
|
167
|
+
}}
|
|
168
|
+
</span>
|
|
169
|
+
</v-btn>
|
|
170
|
+
</template>
|
|
171
|
+
<span>{{
|
|
172
|
+
t(language, 'player.replay')
|
|
173
|
+
}}</span>
|
|
174
|
+
</v-tooltip>
|
|
175
|
+
|
|
130
176
|
<!-- Rewind Button-->
|
|
131
|
-
<v-tooltip
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
177
|
+
<v-tooltip
|
|
178
|
+
v-if="attributes.rewind && !activeAd"
|
|
179
|
+
top
|
|
180
|
+
>
|
|
181
|
+
<template #activator="{ on, attrs }">
|
|
135
182
|
<v-btn
|
|
136
183
|
small
|
|
137
184
|
text
|
|
138
185
|
v-bind="attrs"
|
|
139
186
|
v-on="on"
|
|
140
|
-
@click="
|
|
187
|
+
@click="rewind"
|
|
141
188
|
>
|
|
142
189
|
<v-icon>mdi-rewind-10</v-icon>
|
|
143
190
|
<span class="sr-only">{{
|
|
@@ -161,22 +208,21 @@
|
|
|
161
208
|
current.tracks &&
|
|
162
209
|
current.tracks.length > 0
|
|
163
210
|
"
|
|
211
|
+
:attach="$refs.controlsContainer"
|
|
164
212
|
open-on-hover
|
|
165
|
-
top
|
|
166
213
|
offset-y
|
|
214
|
+
top
|
|
167
215
|
>
|
|
168
|
-
<template
|
|
169
|
-
v-slot:activator="{ on, attrs }"
|
|
170
|
-
>
|
|
216
|
+
<template #activator="{ on, attrs }">
|
|
171
217
|
<v-btn
|
|
172
218
|
small
|
|
173
219
|
text
|
|
174
220
|
v-bind="attrs"
|
|
175
221
|
v-on="on"
|
|
176
|
-
@click="
|
|
222
|
+
@click="CCToggle"
|
|
177
223
|
>
|
|
178
224
|
<v-icon>{{
|
|
179
|
-
|
|
225
|
+
state.cc
|
|
180
226
|
? 'mdi-closed-caption'
|
|
181
227
|
: 'mdi-closed-caption-outline'
|
|
182
228
|
}}</v-icon>
|
|
@@ -209,45 +255,47 @@
|
|
|
209
255
|
</v-menu>
|
|
210
256
|
|
|
211
257
|
<!-- Volume -->
|
|
212
|
-
<v-menu
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
258
|
+
<v-menu
|
|
259
|
+
:attach="$refs.controlsContainer"
|
|
260
|
+
open-on-hover
|
|
261
|
+
offset-y
|
|
262
|
+
top
|
|
263
|
+
>
|
|
264
|
+
<template #activator="{ on, attrs }">
|
|
216
265
|
<v-btn
|
|
217
266
|
small
|
|
218
267
|
text
|
|
219
268
|
v-bind="attrs"
|
|
220
269
|
v-on="on"
|
|
221
|
-
@click="
|
|
270
|
+
@click="muteToggle"
|
|
222
271
|
>
|
|
223
272
|
<v-icon
|
|
224
273
|
v-if="
|
|
225
|
-
!
|
|
226
|
-
|
|
274
|
+
!state.muted &&
|
|
275
|
+
state.volume > 0.75
|
|
227
276
|
"
|
|
228
277
|
>mdi-volume-high</v-icon
|
|
229
278
|
>
|
|
230
279
|
<v-icon
|
|
231
280
|
v-if="
|
|
232
|
-
!
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
options.volume <= 0.75
|
|
281
|
+
!state.muted &&
|
|
282
|
+
state.volume >= 0.25 &&
|
|
283
|
+
state.volume <= 0.75
|
|
236
284
|
"
|
|
237
285
|
>mdi-volume-medium</v-icon
|
|
238
286
|
>
|
|
239
287
|
<v-icon
|
|
240
288
|
v-if="
|
|
241
|
-
!
|
|
242
|
-
|
|
243
|
-
|
|
289
|
+
!state.muted &&
|
|
290
|
+
state.volume > 0 &&
|
|
291
|
+
state.volume < 0.25
|
|
244
292
|
"
|
|
245
293
|
>mdi-volume-low</v-icon
|
|
246
294
|
>
|
|
247
295
|
<v-icon
|
|
248
296
|
v-if="
|
|
249
|
-
|
|
250
|
-
|
|
297
|
+
state.muted ||
|
|
298
|
+
state.volume === 0
|
|
251
299
|
"
|
|
252
300
|
>mdi-volume-off</v-icon
|
|
253
301
|
>
|
|
@@ -268,31 +316,29 @@
|
|
|
268
316
|
)
|
|
269
317
|
}}</span>
|
|
270
318
|
<v-slider
|
|
271
|
-
v-model="
|
|
319
|
+
v-model="state.volume"
|
|
272
320
|
inverse-label
|
|
273
321
|
:min="0"
|
|
274
322
|
:max="1"
|
|
275
323
|
:step="0.1"
|
|
276
324
|
vertical
|
|
277
|
-
@change="
|
|
325
|
+
@change="volumeChange"
|
|
278
326
|
></v-slider>
|
|
279
327
|
</v-sheet>
|
|
280
328
|
</v-menu>
|
|
281
329
|
|
|
282
330
|
<!-- Fullscreen -->
|
|
283
|
-
<v-tooltip v-if="
|
|
284
|
-
<template
|
|
285
|
-
v-slot:activator="{ on, attrs }"
|
|
286
|
-
>
|
|
331
|
+
<v-tooltip v-if="allowFullscreen" top>
|
|
332
|
+
<template #activator="{ on, attrs }">
|
|
287
333
|
<v-btn
|
|
288
334
|
small
|
|
289
335
|
text
|
|
290
336
|
v-bind="attrs"
|
|
291
337
|
v-on="on"
|
|
292
|
-
@click="
|
|
338
|
+
@click="fullscreenToggle"
|
|
293
339
|
>
|
|
294
340
|
<v-icon>{{
|
|
295
|
-
!
|
|
341
|
+
!state.fullscreen
|
|
296
342
|
? 'mdi-fullscreen'
|
|
297
343
|
: 'mdi-fullscreen-exit'
|
|
298
344
|
}}</v-icon>
|
|
@@ -319,9 +365,7 @@
|
|
|
319
365
|
"
|
|
320
366
|
top
|
|
321
367
|
>
|
|
322
|
-
<template
|
|
323
|
-
v-slot:activator="{ on, attrs }"
|
|
324
|
-
>
|
|
368
|
+
<template #activator="{ on, attrs }">
|
|
325
369
|
<v-btn
|
|
326
370
|
small
|
|
327
371
|
text
|
|
@@ -349,13 +393,8 @@
|
|
|
349
393
|
</v-tooltip>
|
|
350
394
|
|
|
351
395
|
<!-- Remote playback -->
|
|
352
|
-
<v-tooltip
|
|
353
|
-
|
|
354
|
-
top
|
|
355
|
-
>
|
|
356
|
-
<template
|
|
357
|
-
v-slot:activator="{ on, attrs }"
|
|
358
|
-
>
|
|
396
|
+
<v-tooltip v-if="allowRemotePlayback" top>
|
|
397
|
+
<template #activator="{ on, attrs }">
|
|
359
398
|
<v-btn
|
|
360
399
|
small
|
|
361
400
|
text
|
|
@@ -381,10 +420,8 @@
|
|
|
381
420
|
</v-tooltip>
|
|
382
421
|
|
|
383
422
|
<!-- Download -->
|
|
384
|
-
<v-tooltip v-if="
|
|
385
|
-
<template
|
|
386
|
-
v-slot:activator="{ on, attrs }"
|
|
387
|
-
>
|
|
423
|
+
<v-tooltip v-if="allowDownload" top>
|
|
424
|
+
<template #activator="{ on, attrs }">
|
|
388
425
|
<v-btn
|
|
389
426
|
small
|
|
390
427
|
text
|
|
@@ -407,117 +444,18 @@
|
|
|
407
444
|
</v-tooltip>
|
|
408
445
|
|
|
409
446
|
<!-- Settings -->
|
|
410
|
-
<
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
:
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
v-bind="attrs"
|
|
423
|
-
v-on="on"
|
|
424
|
-
>
|
|
425
|
-
<v-icon>mdi-cog</v-icon>
|
|
426
|
-
<span class="d-sr-only">{{
|
|
427
|
-
t(
|
|
428
|
-
language,
|
|
429
|
-
'player.toggle_settings'
|
|
430
|
-
)
|
|
431
|
-
}}</span>
|
|
432
|
-
</v-btn>
|
|
433
|
-
</template>
|
|
434
|
-
|
|
435
|
-
<v-list>
|
|
436
|
-
<v-list-item>
|
|
437
|
-
<v-list-item-title>
|
|
438
|
-
<v-icon
|
|
439
|
-
>mdi-play-speed</v-icon
|
|
440
|
-
>
|
|
441
|
-
{{
|
|
442
|
-
t(
|
|
443
|
-
language,
|
|
444
|
-
'player.playback_speed'
|
|
445
|
-
)
|
|
446
|
-
}}
|
|
447
|
-
</v-list-item-title>
|
|
448
|
-
</v-list-item>
|
|
449
|
-
<v-list-item>
|
|
450
|
-
<v-list-item-title
|
|
451
|
-
class="text-center"
|
|
452
|
-
>
|
|
453
|
-
<v-btn
|
|
454
|
-
small
|
|
455
|
-
:disabled="
|
|
456
|
-
options.playbackRateIndex ===
|
|
457
|
-
0
|
|
458
|
-
"
|
|
459
|
-
@click="
|
|
460
|
-
onPlaybackSpeed(
|
|
461
|
-
options.playbackRateIndex -
|
|
462
|
-
1
|
|
463
|
-
)
|
|
464
|
-
"
|
|
465
|
-
>
|
|
466
|
-
<v-icon>
|
|
467
|
-
mdi-clock-minus-outline
|
|
468
|
-
</v-icon>
|
|
469
|
-
<span
|
|
470
|
-
class="d-sr-only"
|
|
471
|
-
>{{
|
|
472
|
-
t(
|
|
473
|
-
language,
|
|
474
|
-
'player.playback_decrease'
|
|
475
|
-
)
|
|
476
|
-
}}</span
|
|
477
|
-
>
|
|
478
|
-
</v-btn>
|
|
479
|
-
<span class="pl-2 pr-2"
|
|
480
|
-
>{{
|
|
481
|
-
attributes
|
|
482
|
-
.playbackrates[
|
|
483
|
-
options
|
|
484
|
-
.playbackRateIndex
|
|
485
|
-
]
|
|
486
|
-
}}x</span
|
|
487
|
-
>
|
|
488
|
-
<v-btn
|
|
489
|
-
small
|
|
490
|
-
:disabled="
|
|
491
|
-
options.playbackRateIndex >=
|
|
492
|
-
attributes
|
|
493
|
-
.playbackrates
|
|
494
|
-
.length -
|
|
495
|
-
1
|
|
496
|
-
"
|
|
497
|
-
@click="
|
|
498
|
-
onPlaybackSpeed(
|
|
499
|
-
options.playbackRateIndex +
|
|
500
|
-
1
|
|
501
|
-
)
|
|
502
|
-
"
|
|
503
|
-
>
|
|
504
|
-
<v-icon>
|
|
505
|
-
mdi-clock-plus-outline
|
|
506
|
-
</v-icon>
|
|
507
|
-
<span
|
|
508
|
-
class="d-sr-only"
|
|
509
|
-
>{{
|
|
510
|
-
t(
|
|
511
|
-
language,
|
|
512
|
-
'player.playback_increase'
|
|
513
|
-
)
|
|
514
|
-
}}</span
|
|
515
|
-
>
|
|
516
|
-
</v-btn>
|
|
517
|
-
</v-list-item-title>
|
|
518
|
-
</v-list-item>
|
|
519
|
-
</v-list>
|
|
520
|
-
</v-menu>
|
|
447
|
+
<SettingsMenu
|
|
448
|
+
:attach="$refs.controlsContainer"
|
|
449
|
+
:state="state"
|
|
450
|
+
:attributes="attributes"
|
|
451
|
+
:language="language"
|
|
452
|
+
:captions-visible.sync="
|
|
453
|
+
captionsVisibleState
|
|
454
|
+
"
|
|
455
|
+
@change:playback-rate-index="
|
|
456
|
+
onPlaybackSpeedChange
|
|
457
|
+
"
|
|
458
|
+
></SettingsMenu>
|
|
521
459
|
</template>
|
|
522
460
|
</v-slider>
|
|
523
461
|
</div>
|
|
@@ -533,14 +471,32 @@
|
|
|
533
471
|
captions.cues &&
|
|
534
472
|
Object.keys(captions.cues).length
|
|
535
473
|
"
|
|
536
|
-
:cols="!
|
|
474
|
+
:cols="!state.expandedCaptions ? 12 : 6"
|
|
475
|
+
class="pt-0 mt-0"
|
|
537
476
|
>
|
|
538
477
|
<CaptionsMenu
|
|
539
478
|
v-model="captions"
|
|
540
479
|
:language="language"
|
|
480
|
+
:expanded.sync="captionsExpandedState"
|
|
481
|
+
:hide-expand="captionsHideExpand"
|
|
482
|
+
:paragraph-view="captionsParagraphView"
|
|
483
|
+
:hide-paragraph-view="captionsHideParagraphView"
|
|
484
|
+
:autoscroll="captionsAutoscroll"
|
|
485
|
+
:visible.sync="captionsVisibleState"
|
|
486
|
+
:hide-autoscroll="captionsHideAutoscroll"
|
|
487
|
+
:hide-close="captionsHideClose"
|
|
488
|
+
@update:paragraph-view="
|
|
489
|
+
$emit('update:captions-paragraph-view', $event)
|
|
490
|
+
"
|
|
491
|
+
@update:autoscroll="
|
|
492
|
+
$emit('update:captions-autoscroll', $event)
|
|
493
|
+
"
|
|
494
|
+
@update:close="$emit('update:captions-visible', $event)"
|
|
541
495
|
@click:cue="onCueClick"
|
|
542
496
|
@click:expand="onClickExpandCaptions"
|
|
543
|
-
@click:paragraph="onClickParagraph"
|
|
497
|
+
@click:paragraph-view="onClickParagraph"
|
|
498
|
+
@click:autoscroll="onClickAutoscroll"
|
|
499
|
+
@click:close="onClickCaptionsClose"
|
|
544
500
|
></CaptionsMenu>
|
|
545
501
|
</v-col>
|
|
546
502
|
</v-row>
|
|
@@ -549,12 +505,14 @@
|
|
|
549
505
|
|
|
550
506
|
<script>
|
|
551
507
|
import filters from '../filters'
|
|
508
|
+
import SettingsMenu from './SettingsMenu.vue'
|
|
552
509
|
import CaptionsMenu from './CaptionsMenu.vue'
|
|
553
510
|
import { t } from '../../i18n/i18n'
|
|
554
511
|
|
|
555
512
|
export default {
|
|
556
513
|
name: 'Html5Player',
|
|
557
514
|
components: {
|
|
515
|
+
SettingsMenu,
|
|
558
516
|
CaptionsMenu,
|
|
559
517
|
},
|
|
560
518
|
props: {
|
|
@@ -562,7 +520,7 @@ export default {
|
|
|
562
520
|
type: {
|
|
563
521
|
type: String,
|
|
564
522
|
required: false,
|
|
565
|
-
default: '
|
|
523
|
+
default: 'auto',
|
|
566
524
|
},
|
|
567
525
|
attributes: {
|
|
568
526
|
type: Object,
|
|
@@ -572,33 +530,187 @@ export default {
|
|
|
572
530
|
type: Object,
|
|
573
531
|
required: true,
|
|
574
532
|
},
|
|
533
|
+
captionsExpanded: {
|
|
534
|
+
type: Boolean,
|
|
535
|
+
required: false,
|
|
536
|
+
default: undefined,
|
|
537
|
+
},
|
|
538
|
+
captionsHideExpand: { type: Boolean, required: false, default: true },
|
|
539
|
+
captionsParagraphView: {
|
|
540
|
+
type: Boolean,
|
|
541
|
+
required: false,
|
|
542
|
+
default: undefined,
|
|
543
|
+
},
|
|
544
|
+
captionsHideParagraphView: {
|
|
545
|
+
type: Boolean,
|
|
546
|
+
required: false,
|
|
547
|
+
default: false,
|
|
548
|
+
},
|
|
549
|
+
captionsAutoscroll: {
|
|
550
|
+
type: Boolean,
|
|
551
|
+
required: false,
|
|
552
|
+
default: undefined,
|
|
553
|
+
},
|
|
554
|
+
captionsVisible: {
|
|
555
|
+
type: Boolean,
|
|
556
|
+
required: false,
|
|
557
|
+
default: undefined,
|
|
558
|
+
},
|
|
559
|
+
captionsHideAutoscroll: {
|
|
560
|
+
type: Boolean,
|
|
561
|
+
required: false,
|
|
562
|
+
default: false,
|
|
563
|
+
},
|
|
564
|
+
captionsHideClose: {
|
|
565
|
+
type: Boolean,
|
|
566
|
+
required: false,
|
|
567
|
+
default: false,
|
|
568
|
+
},
|
|
569
|
+
},
|
|
570
|
+
emits: [
|
|
571
|
+
'error',
|
|
572
|
+
'canplaythrough',
|
|
573
|
+
'emptied',
|
|
574
|
+
'stalled',
|
|
575
|
+
'abort',
|
|
576
|
+
'canplay',
|
|
577
|
+
'waiting',
|
|
578
|
+
'play',
|
|
579
|
+
'pause',
|
|
580
|
+
'load',
|
|
581
|
+
'mouseover',
|
|
582
|
+
'mouseout',
|
|
583
|
+
'ended',
|
|
584
|
+
'trackchange',
|
|
585
|
+
'ratechange',
|
|
586
|
+
'timeupdate',
|
|
587
|
+
'seeking',
|
|
588
|
+
'progress',
|
|
589
|
+
'volumechange',
|
|
590
|
+
'cuechange',
|
|
591
|
+
'loadeddata',
|
|
592
|
+
'loadedmetadata',
|
|
593
|
+
'click:fullscreen',
|
|
594
|
+
'click:pictureinpicture',
|
|
595
|
+
'click:remoteplayback',
|
|
596
|
+
'click:captions-expand',
|
|
597
|
+
'click:captions-paragraph-view',
|
|
598
|
+
'click:captions-autoscroll',
|
|
599
|
+
'click:captions-close',
|
|
600
|
+
'click:captions-cue',
|
|
601
|
+
'update:captions-expanded',
|
|
602
|
+
'update:captions-paragraph-view',
|
|
603
|
+
'update:captions-autoscroll',
|
|
604
|
+
'update:captions-visible',
|
|
605
|
+
],
|
|
606
|
+
watch: {
|
|
607
|
+
'state.controls': function () {
|
|
608
|
+
this.setCuePosition()
|
|
609
|
+
},
|
|
575
610
|
},
|
|
576
|
-
watch: {},
|
|
577
611
|
computed: {
|
|
578
612
|
current() {
|
|
579
613
|
// We're playing an ad currently
|
|
580
614
|
if (this.activeAd) {
|
|
581
615
|
return this.activeAd
|
|
582
|
-
|
|
583
|
-
// We hit an ad spot~ play_at_percent
|
|
584
|
-
} else if (
|
|
585
|
-
!this.activeAd &&
|
|
586
|
-
typeof this.ads[this.currentPercent] !== 'undefined' &&
|
|
587
|
-
this.ads[this.currentPercent].sources &&
|
|
588
|
-
this.ads[this.currentPercent].sources.length &&
|
|
589
|
-
!this.ads[this.currentPercent].complete
|
|
590
|
-
) {
|
|
591
|
-
this.setActiveAd(this.currentPercent)
|
|
592
|
-
return this.ads[this.currentPercent]
|
|
593
616
|
} else {
|
|
594
617
|
// Only change sources if we're not watching an ad or pre/postroll
|
|
595
618
|
return this.src
|
|
596
619
|
}
|
|
597
620
|
},
|
|
598
621
|
playerClass() {
|
|
599
|
-
let classList = 'player-' + this.
|
|
622
|
+
let classList = 'player-' + this.resolvedType
|
|
600
623
|
return classList
|
|
601
624
|
},
|
|
625
|
+
resolvedType() {
|
|
626
|
+
// Default to video if the type can't be resolved
|
|
627
|
+
let type = 'video'
|
|
628
|
+
|
|
629
|
+
// Make sure current is set and valid and has sources
|
|
630
|
+
if (
|
|
631
|
+
this.current &&
|
|
632
|
+
this.current.sources &&
|
|
633
|
+
this.current.sources.length > 0
|
|
634
|
+
) {
|
|
635
|
+
const source = this.current.sources[0]
|
|
636
|
+
|
|
637
|
+
// Determine off the type / mime field first, then check the extensions
|
|
638
|
+
if (source.type && source.type.match(/^video\//i)) {
|
|
639
|
+
type = 'video'
|
|
640
|
+
} else if (source.type && source.type.match(/^audio\//i)) {
|
|
641
|
+
type = 'audio'
|
|
642
|
+
} else if (
|
|
643
|
+
source.src &&
|
|
644
|
+
source.src.match(/(?:mp4|webm|ogg)$/)
|
|
645
|
+
) {
|
|
646
|
+
type = 'video'
|
|
647
|
+
} else if (source.src && source.src.match(/(?:mp3|wav)$/)) {
|
|
648
|
+
type = 'audio'
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
return type
|
|
653
|
+
},
|
|
654
|
+
captionsVisibleState: {
|
|
655
|
+
get() {
|
|
656
|
+
if (typeof this.captionsVisible !== 'undefined') {
|
|
657
|
+
return this.captionsVisible
|
|
658
|
+
} else {
|
|
659
|
+
return this.state.captionsVisible
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
set(v) {
|
|
663
|
+
this.$emit('update:captions-visible', v)
|
|
664
|
+
this.state.captionsVisible = v
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
captionsExpandedState: {
|
|
668
|
+
get() {
|
|
669
|
+
if (typeof this.captionsExpanded !== 'undefined') {
|
|
670
|
+
return this.captionsExpanded
|
|
671
|
+
} else {
|
|
672
|
+
return this.state.expandedCaptions
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
set(v) {
|
|
676
|
+
this.$emit('update:captions-expanded', v)
|
|
677
|
+
this.state.expandedCaptions = v
|
|
678
|
+
},
|
|
679
|
+
},
|
|
680
|
+
allowFullscreen() {
|
|
681
|
+
// Determine fullscreen settings
|
|
682
|
+
// If we explicitly disabled fullscreen in the attributes
|
|
683
|
+
// Or the browser doesn't support fullscreen
|
|
684
|
+
// Or we passed the HTML nofullscreen attribute
|
|
685
|
+
if (
|
|
686
|
+
this.attributes.playsinline ||
|
|
687
|
+
!document.fullscreenEnabled ||
|
|
688
|
+
this.state.controlslist.indexOf('nofullscreen') !== -1
|
|
689
|
+
) {
|
|
690
|
+
return false
|
|
691
|
+
} else {
|
|
692
|
+
return true
|
|
693
|
+
}
|
|
694
|
+
},
|
|
695
|
+
allowRemotePlayback() {
|
|
696
|
+
// Determine remote playback settings
|
|
697
|
+
if (
|
|
698
|
+
this.attributes.disableremoteplayback ||
|
|
699
|
+
this.state.controlslist.indexOf('noremoteplayback') !== -1
|
|
700
|
+
) {
|
|
701
|
+
return false
|
|
702
|
+
} else {
|
|
703
|
+
return true
|
|
704
|
+
}
|
|
705
|
+
},
|
|
706
|
+
allowDownload() {
|
|
707
|
+
// Determine download settings
|
|
708
|
+
if (this.state.controlslist.indexOf('nodownload') !== -1) {
|
|
709
|
+
return false
|
|
710
|
+
} else {
|
|
711
|
+
return true
|
|
712
|
+
}
|
|
713
|
+
},
|
|
602
714
|
},
|
|
603
715
|
data() {
|
|
604
716
|
return {
|
|
@@ -609,8 +721,8 @@ export default {
|
|
|
609
721
|
currentPercent: 0,
|
|
610
722
|
player: {},
|
|
611
723
|
captions: { nonce: 0 },
|
|
612
|
-
|
|
613
|
-
|
|
724
|
+
state: {
|
|
725
|
+
replay: false,
|
|
614
726
|
cc: true,
|
|
615
727
|
ccLang: this.language,
|
|
616
728
|
controls: true,
|
|
@@ -621,8 +733,7 @@ export default {
|
|
|
621
733
|
playbackRateIndex: 0,
|
|
622
734
|
fullscreen: false,
|
|
623
735
|
expandedCaptions: false,
|
|
624
|
-
|
|
625
|
-
remoteplayback: false,
|
|
736
|
+
captionsVisible: true,
|
|
626
737
|
controlslist: [],
|
|
627
738
|
},
|
|
628
739
|
watchPlayer: 0,
|
|
@@ -630,9 +741,65 @@ export default {
|
|
|
630
741
|
buffering: false,
|
|
631
742
|
}
|
|
632
743
|
},
|
|
744
|
+
beforeMount() {
|
|
745
|
+
// Parse the html controlslist attribute string
|
|
746
|
+
if (
|
|
747
|
+
this.attributes.controlslist &&
|
|
748
|
+
typeof this.attributes.controlslist === 'string' &&
|
|
749
|
+
this.attributes.controlslist !== ''
|
|
750
|
+
) {
|
|
751
|
+
this.state.controlslist = this.attributes.controlslist.split(' ')
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
if (
|
|
755
|
+
typeof this.attributes.playbackrates === 'undefined' ||
|
|
756
|
+
this.attributes.playbackrates.length === 0
|
|
757
|
+
) {
|
|
758
|
+
throw new Error(
|
|
759
|
+
'attributes.playbackrates must be defined and an array of numbers!'
|
|
760
|
+
)
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// Adjust the playback speed to 1 by default
|
|
764
|
+
if (this.attributes.playbackrates.indexOf(1) !== -1) {
|
|
765
|
+
this.state.playbackRateIndex =
|
|
766
|
+
this.attributes.playbackrates.indexOf(1)
|
|
767
|
+
} else {
|
|
768
|
+
// 1 aka normal playback not enabled (What monster would do this?!)
|
|
769
|
+
// Set the playback rate to "middle of the road" for whatever is available
|
|
770
|
+
this.state.playbackRateIndex = Math.floor(
|
|
771
|
+
this.attributes.playbackrates.length / 2
|
|
772
|
+
)
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Initialize the ads aka pre/post/midroll
|
|
776
|
+
if (this.src.ads && this.src.ads.length) {
|
|
777
|
+
for (const ad of this.src.ads) {
|
|
778
|
+
// Map to a percent so we can avoid dupe timings and have easier lookups
|
|
779
|
+
this.ads[ad.play_at_percent] = ad
|
|
780
|
+
this.ads[ad.play_at_percent].complete = false
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
},
|
|
784
|
+
mounted() {
|
|
785
|
+
if (
|
|
786
|
+
!this.activeAd &&
|
|
787
|
+
typeof this.ads[this.currentPercent] !== 'undefined' &&
|
|
788
|
+
this.ads[this.currentPercent].sources &&
|
|
789
|
+
this.ads[this.currentPercent].sources.length &&
|
|
790
|
+
!this.ads[this.currentPercent].complete
|
|
791
|
+
) {
|
|
792
|
+
this.activeAd = this.ads[this.currentPercent]
|
|
793
|
+
}
|
|
794
|
+
},
|
|
633
795
|
methods: {
|
|
634
|
-
setActiveAd(currentPercent) {
|
|
796
|
+
setActiveAd(currentPercent, e = null) {
|
|
635
797
|
this.activeAd = this.ads[currentPercent]
|
|
798
|
+
|
|
799
|
+
// Reload the player to refresh all the sources / tracks
|
|
800
|
+
this.load(e)
|
|
801
|
+
// Start playing the main video
|
|
802
|
+
this.play(e)
|
|
636
803
|
},
|
|
637
804
|
percentToTimeSeconds(percent) {
|
|
638
805
|
const scaleFactor = this.player.duration / this.scrub.max
|
|
@@ -648,34 +815,45 @@ export default {
|
|
|
648
815
|
},
|
|
649
816
|
onCueClick(time) {
|
|
650
817
|
this.setTime(time)
|
|
818
|
+
this.$emit('click:captions-cue', time)
|
|
651
819
|
},
|
|
652
820
|
onClickExpandCaptions(expanded) {
|
|
653
|
-
this.options.expandedCaptions = expanded
|
|
654
821
|
this.$emit('click:captions-expand', expanded)
|
|
655
822
|
},
|
|
656
823
|
onClickParagraph(isParagraph) {
|
|
657
|
-
this.$emit('click:captions-paragraph', isParagraph)
|
|
824
|
+
this.$emit('click:captions-paragraph-view', isParagraph)
|
|
825
|
+
},
|
|
826
|
+
onClickAutoscroll(autoscroll) {
|
|
827
|
+
this.$emit('click:captions-autoscroll', autoscroll)
|
|
828
|
+
},
|
|
829
|
+
onClickCaptionsClose() {
|
|
830
|
+
this.state.captionsVisible = false
|
|
831
|
+
this.$emit('click:captions-close')
|
|
658
832
|
},
|
|
659
833
|
onDownload() {
|
|
660
834
|
window.open(this.src.sources[0].src, '_blank')
|
|
661
835
|
},
|
|
662
|
-
|
|
663
|
-
// Rewind in seconds
|
|
664
|
-
const seconds = 10
|
|
665
|
-
|
|
836
|
+
rewind(seconds = 10) {
|
|
666
837
|
if (this.player.currentTime <= seconds) {
|
|
667
838
|
this.setTime(0)
|
|
668
839
|
} else {
|
|
669
840
|
this.setTime(this.player.currentTime - seconds)
|
|
670
841
|
}
|
|
671
842
|
},
|
|
672
|
-
|
|
673
|
-
this.
|
|
843
|
+
fastForward(seconds = 10) {
|
|
844
|
+
if (this.player.currentTime + seconds >= this.player.duration) {
|
|
845
|
+
this.setTime(this.player.duration)
|
|
846
|
+
} else {
|
|
847
|
+
this.setTime(this.player.currentTime + seconds)
|
|
848
|
+
}
|
|
849
|
+
},
|
|
850
|
+
fullscreenToggle() {
|
|
851
|
+
this.state.fullscreen = !document.fullscreenElement
|
|
674
852
|
// Return the whole element to be fullscreened so the controls come with it
|
|
675
|
-
this.$emit('click:fullscreen', this.$
|
|
853
|
+
this.$emit('click:fullscreen', this.$refs.playerContainer)
|
|
676
854
|
},
|
|
677
855
|
onPictureInPicture() {
|
|
678
|
-
//this.
|
|
856
|
+
//this.state.pip = !document.fullscreenElement;
|
|
679
857
|
// Return the player aka HTMLVideoElement
|
|
680
858
|
this.$emit('click:pictureinpicture', this.$refs.player)
|
|
681
859
|
},
|
|
@@ -683,51 +861,65 @@ export default {
|
|
|
683
861
|
this.$emit('click:remoteplayback', this.$refs.player)
|
|
684
862
|
},
|
|
685
863
|
onVideoHover(e) {
|
|
686
|
-
this.
|
|
687
|
-
clearTimeout(this.
|
|
864
|
+
this.state.controls = true
|
|
865
|
+
clearTimeout(this.state.controlsDebounce)
|
|
688
866
|
this.$emit('mouseover', e)
|
|
689
867
|
},
|
|
690
868
|
onVideoLeave(e) {
|
|
691
869
|
const self = this
|
|
692
870
|
// Clear any existing timeouts before we create one
|
|
693
|
-
clearTimeout(this.
|
|
694
|
-
this.
|
|
695
|
-
self.
|
|
871
|
+
clearTimeout(this.state.controlsDebounce)
|
|
872
|
+
this.state.controlsDebounce = setTimeout(() => {
|
|
873
|
+
self.state.controls = false
|
|
696
874
|
}, 50)
|
|
697
875
|
this.$emit('mouseout', e)
|
|
698
876
|
},
|
|
699
877
|
onEnded(e) {
|
|
700
|
-
if
|
|
878
|
+
// Active ad ended but only continue playing if the video didn't just end on a postroll
|
|
879
|
+
if (this.activeAd && this.activeAd.play_at_percent !== 100) {
|
|
701
880
|
this.ads[this.activeAd.play_at_percent].complete = true
|
|
702
881
|
// Go back to the play_at_percent for the main video
|
|
703
882
|
this.currentPercent = this.activeAd.play_at_percent
|
|
883
|
+
|
|
704
884
|
this.activeAd = null
|
|
705
885
|
|
|
706
886
|
// Reload the player to refresh all the sources / tracks
|
|
707
887
|
this.load(e)
|
|
888
|
+
|
|
708
889
|
// Start playing the main video
|
|
709
890
|
this.play(e)
|
|
891
|
+
} else if (
|
|
892
|
+
!this.activeAd &&
|
|
893
|
+
typeof this.ads[this.currentPercent] !== 'undefined' &&
|
|
894
|
+
this.ads[this.currentPercent].sources &&
|
|
895
|
+
this.ads[this.currentPercent].sources.length &&
|
|
896
|
+
!this.ads[this.currentPercent].complete
|
|
897
|
+
) {
|
|
898
|
+
// Video ended but there's an ad (probably 100% ad)
|
|
899
|
+
this.setActiveAd(this.currentPercent, e)
|
|
710
900
|
} else if (
|
|
711
901
|
this.activeAd !== null &&
|
|
712
902
|
this.activeAd.play_at_percent === 100
|
|
713
903
|
) {
|
|
904
|
+
this.state.replay = true
|
|
714
905
|
// Ended but this ad was a postroll
|
|
715
906
|
this.$emit('ended', e)
|
|
716
907
|
} else {
|
|
908
|
+
this.state.replay = true
|
|
717
909
|
// Ended without an ad
|
|
718
910
|
this.$emit('ended', e)
|
|
719
911
|
}
|
|
720
912
|
},
|
|
721
913
|
onControlsHover() {
|
|
722
|
-
clearTimeout(this.
|
|
723
|
-
this.
|
|
914
|
+
clearTimeout(this.state.controlsDebounce)
|
|
915
|
+
this.state.controls = true
|
|
724
916
|
},
|
|
725
917
|
onControlsLeave() {
|
|
726
918
|
const self = this
|
|
727
919
|
// Clear any existing timeouts before we create one
|
|
728
|
-
clearTimeout(this.
|
|
729
|
-
this.
|
|
730
|
-
self.
|
|
920
|
+
clearTimeout(this.state.controlsDebounce)
|
|
921
|
+
this.state.controlsDebounce = setTimeout(() => {
|
|
922
|
+
self.state.controls = false
|
|
731
923
|
}, 50)
|
|
732
924
|
},
|
|
733
925
|
/**
|
|
@@ -738,25 +930,25 @@ export default {
|
|
|
738
930
|
onSelectTrack(lang = null) {
|
|
739
931
|
if (this.player.textTracks && this.player.textTracks.length > 0) {
|
|
740
932
|
for (let i = 0; i < this.player.textTracks.length; i++) {
|
|
741
|
-
|
|
933
|
+
// Disable all tracks by default
|
|
934
|
+
// We only want to enable the correct active track otherwise track switches / replays will overlay tracks
|
|
935
|
+
this.player.textTracks[i].mode = 'disabled'
|
|
742
936
|
|
|
743
|
-
if (
|
|
744
|
-
this.
|
|
937
|
+
if (this.player.textTracks[i].language === lang) {
|
|
938
|
+
this.state.ccLang = lang
|
|
745
939
|
this.player.textTracks[i].mode = 'showing'
|
|
746
940
|
|
|
747
|
-
this.setCues(
|
|
941
|
+
this.setCues(this.player.textTracks[i])
|
|
748
942
|
|
|
749
943
|
// Emit the current track
|
|
750
|
-
this.$emit('trackchange',
|
|
751
|
-
} else {
|
|
752
|
-
this.player.textTracks[i].mode = 'disabled'
|
|
944
|
+
this.$emit('trackchange', this.player.textTracks[i])
|
|
753
945
|
}
|
|
754
946
|
}
|
|
755
947
|
}
|
|
756
948
|
},
|
|
757
|
-
|
|
949
|
+
onPlaybackSpeedChange(index) {
|
|
758
950
|
this.player.playbackRate = this.attributes.playbackrates[index]
|
|
759
|
-
this.
|
|
951
|
+
this.state.playbackRateIndex = index
|
|
760
952
|
this.$emit('ratechange', this.player.playbackRate)
|
|
761
953
|
},
|
|
762
954
|
onTimeupdate(e) {
|
|
@@ -764,6 +956,17 @@ export default {
|
|
|
764
956
|
(this.player.currentTime / this.player.duration) * 100
|
|
765
957
|
)
|
|
766
958
|
|
|
959
|
+
// Check if there's an ad that needs to be played
|
|
960
|
+
if (
|
|
961
|
+
!this.activeAd &&
|
|
962
|
+
typeof this.ads[this.currentPercent] !== 'undefined' &&
|
|
963
|
+
this.ads[this.currentPercent].sources &&
|
|
964
|
+
this.ads[this.currentPercent].sources.length &&
|
|
965
|
+
!this.ads[this.currentPercent].complete
|
|
966
|
+
) {
|
|
967
|
+
this.setActiveAd(this.currentPercent, e)
|
|
968
|
+
}
|
|
969
|
+
|
|
767
970
|
this.$emit('timeupdate', {
|
|
768
971
|
event: e,
|
|
769
972
|
current_percent: this.currentPercent,
|
|
@@ -775,38 +978,47 @@ export default {
|
|
|
775
978
|
onMediaProgress(e) {
|
|
776
979
|
this.$emit('progress', e)
|
|
777
980
|
},
|
|
778
|
-
|
|
779
|
-
this.
|
|
981
|
+
CCToggle() {
|
|
982
|
+
this.state.cc = !this.state.cc
|
|
780
983
|
|
|
781
|
-
if (this.
|
|
782
|
-
this.onSelectTrack(this.
|
|
984
|
+
if (this.state.cc) {
|
|
985
|
+
this.onSelectTrack(this.state.ccLang)
|
|
783
986
|
} else {
|
|
784
987
|
this.onSelectTrack()
|
|
785
988
|
}
|
|
786
989
|
},
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
this.
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
}, 5000)
|
|
990
|
+
onClickReplay(e) {
|
|
991
|
+
// Re-initialize the ads aka pre/post/midroll
|
|
992
|
+
if (this.src.ads && this.src.ads.length) {
|
|
993
|
+
for (const ad of this.src.ads) {
|
|
994
|
+
// Map to a percent so we can avoid dupe timings and have easier lookups
|
|
995
|
+
this.ads[ad.play_at_percent] = ad
|
|
996
|
+
this.ads[ad.play_at_percent].complete = false
|
|
997
|
+
}
|
|
796
998
|
|
|
797
|
-
|
|
798
|
-
this.
|
|
799
|
-
|
|
800
|
-
|
|
999
|
+
// There's a pre-roll / start ad. Reassign as the active
|
|
1000
|
+
if (typeof this.ads[0] !== 'undefined') {
|
|
1001
|
+
this.activeAd = this.ads[0]
|
|
1002
|
+
} else {
|
|
1003
|
+
// Clear the active ad otherwise
|
|
1004
|
+
this.activeAd = null
|
|
1005
|
+
}
|
|
801
1006
|
}
|
|
1007
|
+
|
|
1008
|
+
// Reload the player to refresh all the sources / tracks
|
|
1009
|
+
this.load(e)
|
|
1010
|
+
// Start playing the main video
|
|
1011
|
+
this.play(e)
|
|
1012
|
+
// Restart from the beginning
|
|
1013
|
+
this.setTime(0)
|
|
802
1014
|
},
|
|
803
|
-
|
|
1015
|
+
muteToggle() {
|
|
804
1016
|
if (this.player.muted) {
|
|
805
|
-
this.
|
|
1017
|
+
this.state.muted = false
|
|
806
1018
|
this.player.muted = false
|
|
807
|
-
this.$emit('volumechange', this.
|
|
1019
|
+
this.$emit('volumechange', this.state.volume)
|
|
808
1020
|
} else {
|
|
809
|
-
this.
|
|
1021
|
+
this.state.muted = true
|
|
810
1022
|
this.player.muted = true
|
|
811
1023
|
this.$emit('volumechange', 0)
|
|
812
1024
|
}
|
|
@@ -839,6 +1051,15 @@ export default {
|
|
|
839
1051
|
if (typeof track.activeCues[0].rawText === 'undefined') {
|
|
840
1052
|
track.activeCues[0].rawText = track.activeCues[0].text
|
|
841
1053
|
}
|
|
1054
|
+
// Retain the original cue display values
|
|
1055
|
+
// This way we can swap between a modified display when the controls are visible
|
|
1056
|
+
if (typeof track.activeCues[0].defaults === 'undefined') {
|
|
1057
|
+
track.activeCues[0].defaults = {
|
|
1058
|
+
line: track.activeCues[0].line,
|
|
1059
|
+
size: track.activeCues[0].size,
|
|
1060
|
+
snapToLines: track.activeCues[0].snapToLines,
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
842
1063
|
|
|
843
1064
|
// Now remove `<c.transcript>` tags
|
|
844
1065
|
const transcriptTagRegex = /<c.transcript>.*?<\/c>/gi
|
|
@@ -848,6 +1069,8 @@ export default {
|
|
|
848
1069
|
transcriptTagRegex,
|
|
849
1070
|
''
|
|
850
1071
|
)
|
|
1072
|
+
|
|
1073
|
+
this.setCuePosition()
|
|
851
1074
|
}
|
|
852
1075
|
|
|
853
1076
|
this.setCues(track)
|
|
@@ -877,19 +1100,31 @@ export default {
|
|
|
877
1100
|
//this.player.media = this.$refs.player;
|
|
878
1101
|
this.$emit('loadedmetadata', e)
|
|
879
1102
|
this.player = this.$refs.player
|
|
880
|
-
this.player.volume = this.
|
|
881
|
-
this.$emit('volumechange', this.
|
|
1103
|
+
this.player.volume = this.state.volume
|
|
1104
|
+
this.$emit('volumechange', this.state.volume)
|
|
882
1105
|
},
|
|
883
|
-
|
|
884
|
-
|
|
1106
|
+
volumeChange(value) {
|
|
1107
|
+
// Value needs to be a decimal value between 0 and 1
|
|
1108
|
+
if (value > 1) {
|
|
1109
|
+
value = 1
|
|
1110
|
+
} else if (value < 0) {
|
|
1111
|
+
value = 0
|
|
1112
|
+
}
|
|
1113
|
+
this.state.volume = value
|
|
885
1114
|
this.player.volume = value
|
|
886
1115
|
this.$emit('volumechange', value)
|
|
887
1116
|
},
|
|
1117
|
+
volumeAdjust(value) {
|
|
1118
|
+
const newVolume = this.state.volume + value
|
|
1119
|
+
this.volumeChange(newVolume)
|
|
1120
|
+
},
|
|
888
1121
|
onDurationChange() {
|
|
889
1122
|
// console.log('onDurationChange');
|
|
890
1123
|
// console.log(e);
|
|
891
1124
|
},
|
|
892
1125
|
setTime(time) {
|
|
1126
|
+
// Scrubbing / manually setting the time should remove the replay button
|
|
1127
|
+
this.state.replay = false
|
|
893
1128
|
this.player.currentTime = time
|
|
894
1129
|
},
|
|
895
1130
|
setCues(track) {
|
|
@@ -901,92 +1136,115 @@ export default {
|
|
|
901
1136
|
// Required so the v-model will actually update.
|
|
902
1137
|
this.captions.nonce = Math.random()
|
|
903
1138
|
},
|
|
1139
|
+
setCuePosition() {
|
|
1140
|
+
if (
|
|
1141
|
+
this.player &&
|
|
1142
|
+
this.player.textTracks &&
|
|
1143
|
+
this.player.textTracks.length > 0
|
|
1144
|
+
) {
|
|
1145
|
+
for (let i = 0; i < this.player.textTracks.length; i++) {
|
|
1146
|
+
// Only alter the currently showing text track
|
|
1147
|
+
if (this.player.textTracks[i].mode === 'showing') {
|
|
1148
|
+
// If the controls are showing then bump the alignment to the start
|
|
1149
|
+
if (
|
|
1150
|
+
this.state.controls &&
|
|
1151
|
+
this.player.textTracks[i].activeCues &&
|
|
1152
|
+
this.player.textTracks[i].activeCues.length > 0
|
|
1153
|
+
) {
|
|
1154
|
+
// Count the number of line breaks in the cue to figure out our offset from the bottom
|
|
1155
|
+
// VTTCue doesn't have a "margin from bottom" by default
|
|
1156
|
+
const numLines = (
|
|
1157
|
+
this.player.textTracks[
|
|
1158
|
+
i
|
|
1159
|
+
].activeCues[0].text.match(/\n/g) || []
|
|
1160
|
+
).length
|
|
1161
|
+
|
|
1162
|
+
// Limit the cues to 90% of the screen width
|
|
1163
|
+
// If this is left default / set to 100 then the above line
|
|
1164
|
+
// Also set snapToLines to true otherwise if there's a line % in the vtt file the display will be relative and make the lines not aligned properly
|
|
1165
|
+
this.player.textTracks[i].activeCues[0].line =
|
|
1166
|
+
-3 - numLines
|
|
1167
|
+
this.player.textTracks[i].activeCues[0].size = 99
|
|
1168
|
+
this.player.textTracks[
|
|
1169
|
+
i
|
|
1170
|
+
].activeCues[0].snapToLines = true
|
|
1171
|
+
} else if (
|
|
1172
|
+
this.player.textTracks[i].activeCues &&
|
|
1173
|
+
this.player.textTracks[i].activeCues.length > 0 &&
|
|
1174
|
+
typeof this.player.textTracks[i].activeCues[0]
|
|
1175
|
+
.defaults !== 'undefined'
|
|
1176
|
+
) {
|
|
1177
|
+
this.player.textTracks[i].activeCues[0].line =
|
|
1178
|
+
this.player.textTracks[
|
|
1179
|
+
i
|
|
1180
|
+
].activeCues[0].defaults.line
|
|
1181
|
+
this.player.textTracks[i].activeCues[0].size =
|
|
1182
|
+
this.player.textTracks[
|
|
1183
|
+
i
|
|
1184
|
+
].activeCues[0].defaults.size
|
|
1185
|
+
this.player.textTracks[
|
|
1186
|
+
i
|
|
1187
|
+
].activeCues[0].snapToLines =
|
|
1188
|
+
this.player.textTracks[
|
|
1189
|
+
i
|
|
1190
|
+
].activeCues[0].defaults.snapToLines
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
},
|
|
904
1196
|
load(e = null) {
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
1197
|
+
if (this.player.load) {
|
|
1198
|
+
// Reload the player to refresh all the sources / tracks
|
|
1199
|
+
this.player.load()
|
|
1200
|
+
this.$emit('load', e)
|
|
1201
|
+
} else {
|
|
1202
|
+
console.error('Cannot load player')
|
|
1203
|
+
}
|
|
908
1204
|
},
|
|
909
1205
|
pause(e = null) {
|
|
910
|
-
this.player.pause
|
|
911
|
-
|
|
912
|
-
|
|
1206
|
+
if (this.player.pause) {
|
|
1207
|
+
this.player.pause()
|
|
1208
|
+
this.state.paused = true
|
|
1209
|
+
this.$emit('pause', e)
|
|
1210
|
+
} else {
|
|
1211
|
+
console.log('Cannot pause player')
|
|
1212
|
+
}
|
|
913
1213
|
},
|
|
914
1214
|
play(e = null) {
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
1215
|
+
if (this.player.play) {
|
|
1216
|
+
// Start playing the main video
|
|
1217
|
+
this.player.play()
|
|
1218
|
+
this.state.paused = false
|
|
1219
|
+
this.state.replay = false
|
|
1220
|
+
this.$emit('play', e)
|
|
1221
|
+
} else {
|
|
1222
|
+
console.log('Cannot play player')
|
|
1223
|
+
}
|
|
919
1224
|
},
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
this.options.controlslist = this.attributes.controlslist.split(' ')
|
|
929
|
-
}
|
|
1225
|
+
playToggle(e) {
|
|
1226
|
+
// If the replay button is active then we actually need to call the onClickReplay method instead
|
|
1227
|
+
// Otherwise we'd just end up replaying any postroll ad
|
|
1228
|
+
if (this.state.replay) {
|
|
1229
|
+
this.onClickReplay(e)
|
|
1230
|
+
} else {
|
|
1231
|
+
const self = this
|
|
1232
|
+
this.state.controls = true
|
|
930
1233
|
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
'attributes.playbackrates must be defined and an array of numbers!'
|
|
937
|
-
)
|
|
938
|
-
}
|
|
1234
|
+
// Clear any existing timeouts and close the controls in 5 seconds
|
|
1235
|
+
clearTimeout(this.state.controlsDebounce)
|
|
1236
|
+
this.state.controlsDebounce = setTimeout(() => {
|
|
1237
|
+
self.state.controls = false
|
|
1238
|
+
}, 5000)
|
|
939
1239
|
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
// 1 aka normal playback not enabled (What monster would do this?!)
|
|
946
|
-
// Set the playback rate to "middle of the road" for whatever is available
|
|
947
|
-
this.options.playbackRateIndex = Math.floor(
|
|
948
|
-
this.attributes.playbackrates.length / 2
|
|
949
|
-
)
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
// Initialize the ads aka pre/post/midroll
|
|
953
|
-
if (this.src.ads && this.src.ads.length) {
|
|
954
|
-
for (const ad of this.src.ads) {
|
|
955
|
-
// Map to a percent so we can avoid dupe timings and have easier lookups
|
|
956
|
-
this.ads[ad.play_at_percent] = ad
|
|
957
|
-
this.ads[ad.play_at_percent].complete = false
|
|
1240
|
+
if (this.player.paused) {
|
|
1241
|
+
this.play(e)
|
|
1242
|
+
} else {
|
|
1243
|
+
this.pause(e)
|
|
1244
|
+
}
|
|
958
1245
|
}
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
// Determine fullscreen settings
|
|
962
|
-
if (
|
|
963
|
-
this.attributes.playsinline ||
|
|
964
|
-
!document.fullscreenEnabled ||
|
|
965
|
-
this.options.controlslist.indexOf('nofullscreen') !== -1
|
|
966
|
-
) {
|
|
967
|
-
this.fullscreenEnabled = false
|
|
968
|
-
} else {
|
|
969
|
-
this.fullscreenEnabled = true
|
|
970
|
-
}
|
|
971
|
-
|
|
972
|
-
// Determine remote playback settings
|
|
973
|
-
if (
|
|
974
|
-
this.attributes.disableremoteplayback ||
|
|
975
|
-
this.options.controlslist.indexOf('noremoteplayback') !== -1
|
|
976
|
-
) {
|
|
977
|
-
this.options.remoteplayback = false
|
|
978
|
-
} else {
|
|
979
|
-
this.options.remoteplayback = true
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
// Determine download settings
|
|
983
|
-
if (this.options.controlslist.indexOf('nodownload') !== -1) {
|
|
984
|
-
this.options.download = false
|
|
985
|
-
} else {
|
|
986
|
-
this.options.download = true
|
|
987
|
-
}
|
|
1246
|
+
},
|
|
988
1247
|
},
|
|
989
|
-
mounted() {},
|
|
990
1248
|
}
|
|
991
1249
|
</script>
|
|
992
1250
|
|
|
@@ -996,29 +1254,11 @@ export default {
|
|
|
996
1254
|
position: relative;
|
|
997
1255
|
top: -50px;
|
|
998
1256
|
margin-bottom: -40px;
|
|
999
|
-
overflow: hidden;
|
|
1000
1257
|
}
|
|
1001
1258
|
.controls {
|
|
1002
1259
|
height: 40px;
|
|
1003
1260
|
background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.7));
|
|
1004
1261
|
}
|
|
1005
|
-
.volume-slider {
|
|
1006
|
-
position: relative;
|
|
1007
|
-
right: -50px;
|
|
1008
|
-
top: -180px;
|
|
1009
|
-
height: 180px;
|
|
1010
|
-
width: 50px;
|
|
1011
|
-
margin-left: -50px;
|
|
1012
|
-
padding-bottom: 10px;
|
|
1013
|
-
}
|
|
1014
|
-
.slider-active-area {
|
|
1015
|
-
width: 50px;
|
|
1016
|
-
height: 200px;
|
|
1017
|
-
margin-right: -50px;
|
|
1018
|
-
margin-bottom: -200px;
|
|
1019
|
-
position: relative;
|
|
1020
|
-
top: -160px; /* height of this - controls height */
|
|
1021
|
-
}
|
|
1022
1262
|
.player-audio {
|
|
1023
1263
|
height: 40px;
|
|
1024
1264
|
}
|
|
@@ -1036,12 +1276,18 @@ export default {
|
|
|
1036
1276
|
color: #fff;
|
|
1037
1277
|
left: 25%;
|
|
1038
1278
|
width: 50%;
|
|
1039
|
-
top:
|
|
1279
|
+
top: 35%;
|
|
1040
1280
|
height: 0;
|
|
1041
1281
|
text-align: center;
|
|
1042
1282
|
}
|
|
1043
|
-
.player-overlay
|
|
1283
|
+
.player-overlay--replay-icon {
|
|
1284
|
+
color: #fff;
|
|
1285
|
+
font-size: 5rem;
|
|
1286
|
+
}
|
|
1287
|
+
.player-overlay > .player-overlay--icon {
|
|
1288
|
+
display: inline-block;
|
|
1044
1289
|
background: rgba(0, 0, 0, 0.25);
|
|
1045
1290
|
border-radius: 100%;
|
|
1291
|
+
padding: 1rem;
|
|
1046
1292
|
}
|
|
1047
1293
|
</style>
|