@mindedge/vuetify-player 0.2.0 → 0.3.1

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.
@@ -1,470 +1,580 @@
1
1
  <template>
2
2
  <v-container>
3
- <div v-if="buffering" class="player-overlay">
4
- <v-progress-circular :size="50" indeterminate></v-progress-circular>
5
- </div>
6
- <video
7
- ref="player"
8
- tabindex="0"
9
- :class="'player-' + type"
10
- :height="attributes.height"
11
- :width="attributes.width"
12
- :autoplay="attributes.autoplay"
13
- :autopictureinpicture="attributes.autopictureinpicture"
14
- :controlslist="attributes.controlslist"
15
- :crossorigin="attributes.crossorigin"
16
- :disablepictureinpicture="attributes.disablepictureinpicture"
17
- :disableremoteplayback="attributes.disableremoteplayback"
18
- :loop="attributes.loop"
19
- :muted="attributes.muted"
20
- :playsinline="attributes.playsinline"
21
- :poster="src.poster || attributes.poster"
22
- :preload="attributes.preload"
23
- @click="onPlayToggle"
24
- @seeking="onSeeking"
25
- @timeupdate="onTimeupdate"
26
- @progress="onMediaProgress"
27
- @loadedmetadata="onLoadedmetadata"
28
- @loadeddata="onLoadeddata"
29
- @focus="onVideoHover"
30
- @mouseover="onVideoHover"
31
- @mouseout="onVideoLeave"
32
- @ended="onEnded"
33
- @error="$emit('error', $event)"
34
- @canplay="onCanplay"
35
- @waiting="onWaiting"
36
- @canplaythrough="$emit('canplaythrough', $event)"
37
- @emptied="$emit('emptied', $event)"
38
- @stalled="$emit('stalled', $event)"
39
- @abort="$emit('abort', $event)"
40
- >
41
- <source
42
- v-for="(source, index) of current.sources"
43
- :key="index + '_mediasources'"
44
- :src="source.src"
45
- :type="source.type"
46
- :label="source.label"
47
- />
48
- <track
49
- v-for="(track, index) of current.tracks"
50
- :key="index + '_mediatracks'"
51
- :default="track.default"
52
- :src="track.src"
53
- :kind="track.kind"
54
- :srclang="track.srclang"
55
- @cuechange="onCuechange"
56
- />
57
- {{ t(language, 'player.no_support') }}
58
- </video>
59
-
60
- <div
61
- class="controls-container"
62
- v-if="attributes.controls"
63
- @mouseover="onControlsHover"
64
- >
65
- <v-slide-y-reverse-transition>
66
- <div v-if="player && options.controls" class="controls">
67
- <v-slider
68
- dark
69
- v-model="currentPercent"
70
- :min="0"
71
- :max="scrub.max"
72
- :label="
73
- filters.playerShortDuration(
74
- percentToTimeSeconds(currentPercent)
75
- ) +
76
- ' / ' +
77
- filters.playerShortDuration(player.duration)
78
- "
79
- inverse-label
80
- @mousedown="onPause"
81
- @change="onScrubTime"
82
- >
83
- <template #prepend>
84
- <!-- Play button -->
85
- <v-tooltip top>
86
- <template v-slot:activator="{ on, attrs }">
87
- <v-btn
88
- small
89
- text
90
- v-bind="attrs"
91
- v-on="on"
92
- @click="onPlayToggle"
93
- >
94
- <v-icon>{{
95
- options.paused
96
- ? 'mdi-play'
97
- : 'mdi-pause'
98
- }}</v-icon>
99
- <span class="d-sr-only">
100
- {{
101
- options.paused
102
- ? t(language, 'player.play')
103
- : t(
104
- language,
105
- 'player.pause'
106
- )
107
- }}
108
- </span>
109
- </v-btn>
110
- </template>
111
- <span>{{
112
- options.paused
113
- ? t(language, 'player.play')
114
- : t(language, 'player.pause')
115
- }}</span>
116
- </v-tooltip>
117
-
118
- <!-- Rewind Button-->
119
- <v-tooltip v-if="attributes.rewind" top>
120
- <template v-slot:activator="{ on, attrs }">
121
- <v-btn
122
- small
123
- text
124
- v-bind="attrs"
125
- v-on="on"
126
- @click="onRewind"
127
- >
128
- <v-icon>mdi-rewind-10</v-icon>
129
- <span class="sr-only">{{
130
- t(language, 'player.rewind_10')
131
- }}</span>
132
- </v-btn>
133
- </template>
134
- <span>{{
135
- t(language, 'player.rewind_10')
136
- }}</span>
137
- </v-tooltip>
138
- </template>
139
-
140
- <template #append>
141
- <!-- Closed Captions -->
142
- <v-menu
143
- v-if="
144
- current.tracks && current.tracks.length > 0
3
+ <v-row>
4
+ <v-col :cols="!options.expandedCaptions ? 12 : 6">
5
+ <div v-if="buffering" class="player-overlay">
6
+ <v-progress-circular
7
+ :size="50"
8
+ indeterminate
9
+ ></v-progress-circular>
10
+ </div>
11
+ <video
12
+ ref="player"
13
+ tabindex="0"
14
+ :class="'player-' + type"
15
+ :height="attributes.height"
16
+ :width="attributes.width"
17
+ :autoplay="attributes.autoplay"
18
+ :autopictureinpicture="attributes.autopictureinpicture"
19
+ :controlslist="attributes.controlslist"
20
+ :crossorigin="attributes.crossorigin"
21
+ :disablepictureinpicture="
22
+ attributes.disablepictureinpicture
23
+ "
24
+ :disableremoteplayback="attributes.disableremoteplayback"
25
+ :loop="attributes.loop"
26
+ :muted="attributes.muted"
27
+ :playsinline="attributes.playsinline"
28
+ :poster="src.poster || attributes.poster"
29
+ :preload="attributes.preload"
30
+ @click="onPlayToggle"
31
+ @seeking="onSeeking"
32
+ @timeupdate="onTimeupdate"
33
+ @progress="onMediaProgress"
34
+ @loadedmetadata="onLoadedmetadata"
35
+ @loadeddata="onLoadeddata"
36
+ @focus="onVideoHover"
37
+ @mouseover="onVideoHover"
38
+ @mouseout="onVideoLeave"
39
+ @ended="onEnded"
40
+ @error="$emit('error', $event)"
41
+ @canplay="onCanplay"
42
+ @waiting="onWaiting"
43
+ @canplaythrough="$emit('canplaythrough', $event)"
44
+ @emptied="$emit('emptied', $event)"
45
+ @stalled="$emit('stalled', $event)"
46
+ @abort="$emit('abort', $event)"
47
+ >
48
+ <source
49
+ v-for="(source, index) of current.sources"
50
+ :key="index + '_mediasources'"
51
+ :src="source.src"
52
+ :type="source.type"
53
+ :label="source.label"
54
+ />
55
+ <track
56
+ v-for="(track, index) of current.tracks"
57
+ :key="index + '_mediatracks'"
58
+ :default="track.default"
59
+ :src="track.src"
60
+ :kind="track.kind"
61
+ :srclang="track.srclang"
62
+ @cuechange="onCuechange"
63
+ />
64
+ {{ t(language, 'player.no_support') }}
65
+ </video>
66
+
67
+ <div
68
+ class="controls-container"
69
+ v-if="attributes.controls"
70
+ @mouseover="onControlsHover"
71
+ >
72
+ <v-slide-y-reverse-transition>
73
+ <div v-if="player && options.controls" class="controls">
74
+ <v-slider
75
+ dark
76
+ v-model="currentPercent"
77
+ :min="0"
78
+ :max="scrub.max"
79
+ :label="
80
+ filters.playerShortDuration(
81
+ percentToTimeSeconds(currentPercent)
82
+ ) +
83
+ ' / ' +
84
+ filters.playerShortDuration(player.duration)
145
85
  "
146
- open-on-hover
147
- top
148
- offset-y
86
+ inverse-label
87
+ @mousedown="onPause"
88
+ @change="onScrubTime"
149
89
  >
150
- <template v-slot:activator="{ on, attrs }">
151
- <v-btn
152
- small
153
- text
154
- v-bind="attrs"
155
- v-on="on"
156
- @click="onCCToggle"
157
- >
158
- <v-icon>{{
159
- options.cc
160
- ? 'mdi-closed-caption'
161
- : 'mdi-closed-caption-outline'
162
- }}</v-icon>
163
- <span class="d-sr-only">{{
164
- t(language, 'player.toggle_cc')
90
+ <template #prepend>
91
+ <!-- Play button -->
92
+ <v-tooltip v-if="!showReplay" top>
93
+ <template
94
+ v-slot:activator="{ on, attrs }"
95
+ >
96
+ <v-btn
97
+ small
98
+ text
99
+ v-bind="attrs"
100
+ v-on="on"
101
+ @click="onPlayToggle"
102
+ >
103
+ <v-icon>{{
104
+ options.paused
105
+ ? 'mdi-play'
106
+ : 'mdi-pause'
107
+ }}</v-icon>
108
+ <span class="d-sr-only">
109
+ {{
110
+ options.paused
111
+ ? t(
112
+ language,
113
+ 'player.play'
114
+ )
115
+ : t(
116
+ language,
117
+ 'player.pause'
118
+ )
119
+ }}
120
+ </span>
121
+ </v-btn>
122
+ </template>
123
+ <span>{{
124
+ options.paused
125
+ ? t(language, 'player.play')
126
+ : t(language, 'player.pause')
165
127
  }}</span>
166
- </v-btn>
167
- </template>
128
+ </v-tooltip>
168
129
 
169
- <v-list>
170
- <v-list-item-group>
171
- <v-list-item
172
- v-for="track in current.tracks"
173
- :key="track.srclang"
174
- @click="
175
- onSelectTrack(track.srclang)
176
- "
130
+ <!-- Replay button -->
131
+ <v-tooltip v-if="showReplay" top>
132
+ <template
133
+ v-slot:activator="{ on, attrs }"
177
134
  >
178
- <v-list-item-title>{{
179
- track.srclang
180
- }}</v-list-item-title>
181
- </v-list-item>
182
- </v-list-item-group>
183
- </v-list>
184
- </v-menu>
185
-
186
- <!-- Volume -->
187
- <v-menu open-on-hover top offset-y>
188
- <template v-slot:activator="{ on, attrs }">
189
- <v-btn
190
- small
191
- text
192
- v-bind="attrs"
193
- v-on="on"
194
- @click="onMuteToggle"
135
+ <v-btn
136
+ small
137
+ text
138
+ v-bind="attrs"
139
+ v-on="on"
140
+ @click="onClickReplay"
141
+ >
142
+ <v-icon>mdi-replay</v-icon>
143
+ <span class="d-sr-only">
144
+ {{
145
+ t(
146
+ language,
147
+ 'player.replay'
148
+ )
149
+ }}
150
+ </span>
151
+ </v-btn>
152
+ </template>
153
+ <span>{{
154
+ t(language, 'player.replay')
155
+ }}</span>
156
+ </v-tooltip>
157
+
158
+ <!-- Rewind Button-->
159
+ <v-tooltip
160
+ v-if="attributes.rewind && !activeAd"
161
+ top
195
162
  >
196
- <v-icon
197
- v-if="
198
- !options.muted &&
199
- options.volume > 0.75
200
- "
201
- >mdi-volume-high</v-icon
202
- >
203
- <v-icon
204
- v-if="
205
- !options.muted &&
206
- options.volume >= 0.25 &&
207
- options.volume <= 0.75
208
- "
209
- >mdi-volume-medium</v-icon
210
- >
211
- <v-icon
212
- v-if="
213
- !options.muted &&
214
- options.volume > 0 &&
215
- options.volume < 0.25
216
- "
217
- >mdi-volume-low</v-icon
163
+ <template
164
+ v-slot:activator="{ on, attrs }"
218
165
  >
219
- <v-icon
220
- v-if="
221
- options.muted ||
222
- options.volume === 0
223
- "
224
- >mdi-volume-off</v-icon
225
- >
226
- <span class="d-sr-only">{{
227
- t(language, 'player.volume_slider')
166
+ <v-btn
167
+ small
168
+ text
169
+ v-bind="attrs"
170
+ v-on="on"
171
+ @click="onRewind"
172
+ >
173
+ <v-icon>mdi-rewind-10</v-icon>
174
+ <span class="sr-only">{{
175
+ t(
176
+ language,
177
+ 'player.rewind_10'
178
+ )
179
+ }}</span>
180
+ </v-btn>
181
+ </template>
182
+ <span>{{
183
+ t(language, 'player.rewind_10')
228
184
  }}</span>
229
- </v-btn>
185
+ </v-tooltip>
230
186
  </template>
231
187
 
232
- <v-sheet class="pa-5">
233
- <span class="d-sr-only">{{
234
- t(language, 'player.volume_slider')
235
- }}</span>
236
- <v-slider
237
- v-model="options.volume"
238
- inverse-label
239
- :min="0"
240
- :max="1"
241
- :step="0.1"
242
- vertical
243
- @change="onVolumeChange"
244
- ></v-slider>
245
- </v-sheet>
246
- </v-menu>
247
-
248
- <!-- Fullscreen -->
249
- <v-tooltip v-if="fullscreenEnabled" top>
250
- <template v-slot:activator="{ on, attrs }">
251
- <v-btn
252
- small
253
- text
254
- v-bind="attrs"
255
- v-on="on"
256
- @click="onFullscreen"
188
+ <template #append>
189
+ <!-- Closed Captions -->
190
+ <v-menu
191
+ v-if="
192
+ current.tracks &&
193
+ current.tracks.length > 0
194
+ "
195
+ open-on-hover
196
+ top
197
+ offset-y
257
198
  >
258
- <v-icon>{{
259
- !options.fullscreen
260
- ? 'mdi-fullscreen'
261
- : 'mdi-fullscreen-exit'
262
- }}</v-icon>
263
- <span class="d-sr-only">{{
199
+ <template
200
+ v-slot:activator="{ on, attrs }"
201
+ >
202
+ <v-btn
203
+ small
204
+ text
205
+ v-bind="attrs"
206
+ v-on="on"
207
+ @click="onCCToggle"
208
+ >
209
+ <v-icon>{{
210
+ options.cc
211
+ ? 'mdi-closed-caption'
212
+ : 'mdi-closed-caption-outline'
213
+ }}</v-icon>
214
+ <span class="d-sr-only">{{
215
+ t(
216
+ language,
217
+ 'player.toggle_cc'
218
+ )
219
+ }}</span>
220
+ </v-btn>
221
+ </template>
222
+
223
+ <v-list>
224
+ <v-list-item-group>
225
+ <v-list-item
226
+ v-for="track in current.tracks"
227
+ :key="track.srclang"
228
+ @click="
229
+ onSelectTrack(
230
+ track.srclang
231
+ )
232
+ "
233
+ >
234
+ <v-list-item-title>{{
235
+ track.srclang
236
+ }}</v-list-item-title>
237
+ </v-list-item>
238
+ </v-list-item-group>
239
+ </v-list>
240
+ </v-menu>
241
+
242
+ <!-- Volume -->
243
+ <v-menu open-on-hover top offset-y>
244
+ <template
245
+ v-slot:activator="{ on, attrs }"
246
+ >
247
+ <v-btn
248
+ small
249
+ text
250
+ v-bind="attrs"
251
+ v-on="on"
252
+ @click="onMuteToggle"
253
+ >
254
+ <v-icon
255
+ v-if="
256
+ !options.muted &&
257
+ options.volume > 0.75
258
+ "
259
+ >mdi-volume-high</v-icon
260
+ >
261
+ <v-icon
262
+ v-if="
263
+ !options.muted &&
264
+ options.volume >=
265
+ 0.25 &&
266
+ options.volume <= 0.75
267
+ "
268
+ >mdi-volume-medium</v-icon
269
+ >
270
+ <v-icon
271
+ v-if="
272
+ !options.muted &&
273
+ options.volume > 0 &&
274
+ options.volume < 0.25
275
+ "
276
+ >mdi-volume-low</v-icon
277
+ >
278
+ <v-icon
279
+ v-if="
280
+ options.muted ||
281
+ options.volume === 0
282
+ "
283
+ >mdi-volume-off</v-icon
284
+ >
285
+ <span class="d-sr-only">{{
286
+ t(
287
+ language,
288
+ 'player.volume_slider'
289
+ )
290
+ }}</span>
291
+ </v-btn>
292
+ </template>
293
+
294
+ <v-sheet class="pa-5">
295
+ <span class="d-sr-only">{{
296
+ t(
297
+ language,
298
+ 'player.volume_slider'
299
+ )
300
+ }}</span>
301
+ <v-slider
302
+ v-model="options.volume"
303
+ inverse-label
304
+ :min="0"
305
+ :max="1"
306
+ :step="0.1"
307
+ vertical
308
+ @change="onVolumeChange"
309
+ ></v-slider>
310
+ </v-sheet>
311
+ </v-menu>
312
+
313
+ <!-- Fullscreen -->
314
+ <v-tooltip v-if="fullscreenEnabled" top>
315
+ <template
316
+ v-slot:activator="{ on, attrs }"
317
+ >
318
+ <v-btn
319
+ small
320
+ text
321
+ v-bind="attrs"
322
+ v-on="on"
323
+ @click="onFullscreen"
324
+ >
325
+ <v-icon>{{
326
+ !options.fullscreen
327
+ ? 'mdi-fullscreen'
328
+ : 'mdi-fullscreen-exit'
329
+ }}</v-icon>
330
+ <span class="d-sr-only">{{
331
+ t(
332
+ language,
333
+ 'player.toggle_fullscreen'
334
+ )
335
+ }}</span>
336
+ </v-btn></template
337
+ >
338
+ <span>{{
264
339
  t(
265
340
  language,
266
341
  'player.toggle_fullscreen'
267
342
  )
268
343
  }}</span>
269
- </v-btn></template
270
- >
271
- <span>{{
272
- t(language, 'player.toggle_fullscreen')
273
- }}</span>
274
- </v-tooltip>
275
-
276
- <!-- Picture in picture -->
277
- <v-tooltip
278
- v-if="!attributes.disablepictureinpicture"
279
- top
280
- >
281
- <template v-slot:activator="{ on, attrs }">
282
- <v-btn
283
- small
284
- text
285
- v-bind="attrs"
286
- v-on="on"
287
- @click="onPictureInPicture"
344
+ </v-tooltip>
345
+
346
+ <!-- Picture in picture -->
347
+ <v-tooltip
348
+ v-if="
349
+ !attributes.disablepictureinpicture
350
+ "
351
+ top
288
352
  >
289
- <v-icon
290
- >mdi-picture-in-picture-bottom-right</v-icon
353
+ <template
354
+ v-slot:activator="{ on, attrs }"
291
355
  >
292
- <span class="d-sr-only">{{
356
+ <v-btn
357
+ small
358
+ text
359
+ v-bind="attrs"
360
+ v-on="on"
361
+ @click="onPictureInPicture"
362
+ >
363
+ <v-icon
364
+ >mdi-picture-in-picture-bottom-right</v-icon
365
+ >
366
+ <span class="d-sr-only">{{
367
+ t(
368
+ language,
369
+ 'player.toggle_picture_in_picture'
370
+ )
371
+ }}</span>
372
+ </v-btn></template
373
+ >
374
+ <span>{{
293
375
  t(
294
376
  language,
295
377
  'player.toggle_picture_in_picture'
296
378
  )
297
379
  }}</span>
298
- </v-btn></template
299
- >
300
- <span>{{
301
- t(
302
- language,
303
- 'player.toggle_picture_in_picture'
304
- )
305
- }}</span>
306
- </v-tooltip>
307
-
308
- <!-- Remote playback -->
309
- <v-tooltip v-if="options.remoteplayback" top>
310
- <template v-slot:activator="{ on, attrs }">
311
- <v-btn
312
- small
313
- text
314
- v-bind="attrs"
315
- v-on="on"
316
- @click="onRemoteplayback"
380
+ </v-tooltip>
381
+
382
+ <!-- Remote playback -->
383
+ <v-tooltip
384
+ v-if="options.remoteplayback"
385
+ top
317
386
  >
318
- <v-icon>mdi-cast</v-icon>
319
- <span class="d-sr-only">{{
387
+ <template
388
+ v-slot:activator="{ on, attrs }"
389
+ >
390
+ <v-btn
391
+ small
392
+ text
393
+ v-bind="attrs"
394
+ v-on="on"
395
+ @click="onRemoteplayback"
396
+ >
397
+ <v-icon>mdi-cast</v-icon>
398
+ <span class="d-sr-only">{{
399
+ t(
400
+ language,
401
+ 'player.toggle_remote_playback'
402
+ )
403
+ }}</span>
404
+ </v-btn></template
405
+ >
406
+ <span>{{
320
407
  t(
321
408
  language,
322
409
  'player.toggle_remote_playback'
323
410
  )
324
411
  }}</span>
325
- </v-btn></template
326
- >
327
- <span>{{
328
- t(language, 'player.toggle_remote_playback')
329
- }}</span>
330
- </v-tooltip>
331
-
332
- <!-- Download -->
333
- <v-tooltip v-if="options.download" top>
334
- <template v-slot:activator="{ on, attrs }">
335
- <v-btn
336
- small
337
- text
338
- v-bind="attrs"
339
- v-on="on"
340
- @click="onDownload"
341
- >
342
- <v-icon>mdi-download</v-icon>
343
- <span class="d-sr-only">{{
344
- t(language, 'player.download')
345
- }}</span>
346
- </v-btn></template
347
- >
348
- <span>{{
349
- t(language, 'player.download')
350
- }}</span>
351
- </v-tooltip>
352
-
353
- <!-- Settings -->
354
- <v-menu
355
- top
356
- offset-y
357
- :close-on-content-click="false"
358
- nudge-left="100"
359
- >
360
- <template v-slot:activator="{ on, attrs }">
361
- <v-btn small text v-bind="attrs" v-on="on">
362
- <v-icon>mdi-cog</v-icon>
363
- <span class="d-sr-only">{{
364
- t(
365
- language,
366
- 'player.toggle_settings'
367
- )
368
- }}</span>
369
- </v-btn>
370
- </template>
412
+ </v-tooltip>
371
413
 
372
- <v-list>
373
- <v-list-item>
374
- <v-list-item-title>
375
- <v-icon>mdi-play-speed</v-icon>
376
- {{
377
- t(
378
- language,
379
- 'player.playback_speed'
380
- )
381
- }}
382
- </v-list-item-title>
383
- </v-list-item>
384
- <v-list-item>
385
- <v-list-item-title class="text-center">
414
+ <!-- Download -->
415
+ <v-tooltip v-if="options.download" top>
416
+ <template
417
+ v-slot:activator="{ on, attrs }"
418
+ >
386
419
  <v-btn
387
420
  small
388
- :disabled="
389
- options.playbackRateIndex ===
390
- 0
391
- "
392
- @click="
393
- onPlaybackSpeed(
394
- options.playbackRateIndex -
395
- 1
396
- )
397
- "
421
+ text
422
+ v-bind="attrs"
423
+ v-on="on"
424
+ @click="onDownload"
398
425
  >
399
- <v-icon>
400
- mdi-clock-minus-outline
401
- </v-icon>
426
+ <v-icon>mdi-download</v-icon>
402
427
  <span class="d-sr-only">{{
403
428
  t(
404
429
  language,
405
- 'player.playback_decrease'
430
+ 'player.download'
406
431
  )
407
432
  }}</span>
408
- </v-btn>
409
- <span class="pl-2 pr-2"
410
- >{{
411
- attributes.playbackrates[
412
- options
413
- .playbackRateIndex
414
- ]
415
- }}x</span
416
- >
433
+ </v-btn></template
434
+ >
435
+ <span>{{
436
+ t(language, 'player.download')
437
+ }}</span>
438
+ </v-tooltip>
439
+
440
+ <!-- Settings -->
441
+ <v-menu
442
+ top
443
+ offset-y
444
+ :close-on-content-click="false"
445
+ nudge-left="100"
446
+ >
447
+ <template
448
+ v-slot:activator="{ on, attrs }"
449
+ >
417
450
  <v-btn
418
451
  small
419
- :disabled="
420
- options.playbackRateIndex >=
421
- attributes.playbackrates
422
- .length -
423
- 1
424
- "
425
- @click="
426
- onPlaybackSpeed(
427
- options.playbackRateIndex +
428
- 1
429
- )
430
- "
452
+ text
453
+ v-bind="attrs"
454
+ v-on="on"
431
455
  >
432
- <v-icon>
433
- mdi-clock-plus-outline
434
- </v-icon>
456
+ <v-icon>mdi-cog</v-icon>
435
457
  <span class="d-sr-only">{{
436
458
  t(
437
459
  language,
438
- 'player.playback_increase'
460
+ 'player.toggle_settings'
439
461
  )
440
462
  }}</span>
441
463
  </v-btn>
442
- </v-list-item-title>
443
- </v-list-item>
444
- </v-list>
445
- </v-menu>
446
- </template>
447
- </v-slider>
464
+ </template>
465
+
466
+ <v-list>
467
+ <v-list-item>
468
+ <v-list-item-title>
469
+ <v-icon
470
+ >mdi-play-speed</v-icon
471
+ >
472
+ {{
473
+ t(
474
+ language,
475
+ 'player.playback_speed'
476
+ )
477
+ }}
478
+ </v-list-item-title>
479
+ </v-list-item>
480
+ <v-list-item>
481
+ <v-list-item-title
482
+ class="text-center"
483
+ >
484
+ <v-btn
485
+ small
486
+ :disabled="
487
+ options.playbackRateIndex ===
488
+ 0
489
+ "
490
+ @click="
491
+ onPlaybackSpeed(
492
+ options.playbackRateIndex -
493
+ 1
494
+ )
495
+ "
496
+ >
497
+ <v-icon>
498
+ mdi-clock-minus-outline
499
+ </v-icon>
500
+ <span
501
+ class="d-sr-only"
502
+ >{{
503
+ t(
504
+ language,
505
+ 'player.playback_decrease'
506
+ )
507
+ }}</span
508
+ >
509
+ </v-btn>
510
+ <span class="pl-2 pr-2"
511
+ >{{
512
+ attributes
513
+ .playbackrates[
514
+ options
515
+ .playbackRateIndex
516
+ ]
517
+ }}x</span
518
+ >
519
+ <v-btn
520
+ small
521
+ :disabled="
522
+ options.playbackRateIndex >=
523
+ attributes
524
+ .playbackrates
525
+ .length -
526
+ 1
527
+ "
528
+ @click="
529
+ onPlaybackSpeed(
530
+ options.playbackRateIndex +
531
+ 1
532
+ )
533
+ "
534
+ >
535
+ <v-icon>
536
+ mdi-clock-plus-outline
537
+ </v-icon>
538
+ <span
539
+ class="d-sr-only"
540
+ >{{
541
+ t(
542
+ language,
543
+ 'player.playback_increase'
544
+ )
545
+ }}</span
546
+ >
547
+ </v-btn>
548
+ </v-list-item-title>
549
+ </v-list-item>
550
+ </v-list>
551
+ </v-menu>
552
+ </template>
553
+ </v-slider>
554
+ </div>
555
+ </v-slide-y-reverse-transition>
448
556
  </div>
449
- </v-slide-y-reverse-transition>
450
- </div>
451
-
452
- <v-col
453
- v-if="
454
- attributes.captionsmenu &&
455
- current.tracks &&
456
- captions &&
457
- captions.cues &&
458
- Object.keys(captions.cues).length
459
- "
460
- cols="12"
461
- >
462
- <CaptionsMenu
463
- v-model="captions"
464
- :language="language"
465
- @click:cue="onCueClick"
466
- ></CaptionsMenu>
467
- </v-col>
557
+ </v-col>
558
+
559
+ <v-col
560
+ v-if="
561
+ attributes.captionsmenu &&
562
+ current.tracks &&
563
+ captions &&
564
+ captions.cues &&
565
+ Object.keys(captions.cues).length
566
+ "
567
+ :cols="!options.expandedCaptions ? 12 : 6"
568
+ >
569
+ <CaptionsMenu
570
+ v-model="captions"
571
+ :language="language"
572
+ @click:cue="onCueClick"
573
+ @click:expand="onClickExpandCaptions"
574
+ @click:paragraph="onClickParagraph"
575
+ ></CaptionsMenu>
576
+ </v-col>
577
+ </v-row>
468
578
  </v-container>
469
579
  </template>
470
580
 
@@ -500,17 +610,6 @@ export default {
500
610
  // We're playing an ad currently
501
611
  if (this.activeAd) {
502
612
  return this.activeAd
503
-
504
- // We hit an ad spot~ play_at_percent
505
- } else if (
506
- !this.activeAd &&
507
- typeof this.ads[this.currentPercent] !== 'undefined' &&
508
- this.ads[this.currentPercent].sources &&
509
- this.ads[this.currentPercent].sources.length &&
510
- !this.ads[this.currentPercent].complete
511
- ) {
512
- this.setActiveAd(this.currentPercent)
513
- return this.ads[this.currentPercent]
514
613
  } else {
515
614
  // Only change sources if we're not watching an ad or pre/postroll
516
615
  return this.src
@@ -541,6 +640,7 @@ export default {
541
640
  paused: true,
542
641
  playbackRateIndex: 0,
543
642
  fullscreen: false,
643
+ expandedCaptions: false,
544
644
  download: false,
545
645
  remoteplayback: false,
546
646
  controlslist: [],
@@ -548,11 +648,96 @@ export default {
548
648
  watchPlayer: 0,
549
649
  scrub: { max: 100 },
550
650
  buffering: false,
651
+ showReplay: false,
652
+ }
653
+ },
654
+ beforeMount() {
655
+ // Parse the controlslist string
656
+ if (
657
+ this.attributes.controlslist &&
658
+ typeof this.attributes.controlslist === 'string' &&
659
+ this.attributes.controlslist !== ''
660
+ ) {
661
+ this.options.controlslist = this.attributes.controlslist.split(' ')
662
+ }
663
+
664
+ if (
665
+ typeof this.attributes.playbackrates === 'undefined' ||
666
+ this.attributes.playbackrates.length === 0
667
+ ) {
668
+ throw new Error(
669
+ 'attributes.playbackrates must be defined and an array of numbers!'
670
+ )
671
+ }
672
+
673
+ // Adjust the playback speed to 1 by default
674
+ if (this.attributes.playbackrates.indexOf(1) !== -1) {
675
+ this.options.playbackRateIndex =
676
+ this.attributes.playbackrates.indexOf(1)
677
+ } else {
678
+ // 1 aka normal playback not enabled (What monster would do this?!)
679
+ // Set the playback rate to "middle of the road" for whatever is available
680
+ this.options.playbackRateIndex = Math.floor(
681
+ this.attributes.playbackrates.length / 2
682
+ )
683
+ }
684
+
685
+ // Initialize the ads aka pre/post/midroll
686
+ if (this.src.ads && this.src.ads.length) {
687
+ for (const ad of this.src.ads) {
688
+ // Map to a percent so we can avoid dupe timings and have easier lookups
689
+ this.ads[ad.play_at_percent] = ad
690
+ this.ads[ad.play_at_percent].complete = false
691
+ }
692
+ }
693
+
694
+ // Determine fullscreen settings
695
+ if (
696
+ this.attributes.playsinline ||
697
+ !document.fullscreenEnabled ||
698
+ this.options.controlslist.indexOf('nofullscreen') !== -1
699
+ ) {
700
+ this.fullscreenEnabled = false
701
+ } else {
702
+ this.fullscreenEnabled = true
703
+ }
704
+
705
+ // Determine remote playback settings
706
+ if (
707
+ this.attributes.disableremoteplayback ||
708
+ this.options.controlslist.indexOf('noremoteplayback') !== -1
709
+ ) {
710
+ this.options.remoteplayback = false
711
+ } else {
712
+ this.options.remoteplayback = true
713
+ }
714
+
715
+ // Determine download settings
716
+ if (this.options.controlslist.indexOf('nodownload') !== -1) {
717
+ this.options.download = false
718
+ } else {
719
+ this.options.download = true
720
+ }
721
+ },
722
+ mounted() {
723
+ if (
724
+ !this.activeAd &&
725
+ typeof this.ads[this.currentPercent] !== 'undefined' &&
726
+ this.ads[this.currentPercent].sources &&
727
+ this.ads[this.currentPercent].sources.length &&
728
+ !this.ads[this.currentPercent].complete
729
+ ) {
730
+ this.activeAd = this.ads[this.currentPercent]
551
731
  }
552
732
  },
553
733
  methods: {
554
- setActiveAd(currentPercent) {
734
+ setActiveAd(currentPercent, e = null) {
555
735
  this.activeAd = this.ads[currentPercent]
736
+
737
+ // Reload the player to refresh all the sources / tracks
738
+ this.load(e)
739
+ // Start playing the main video
740
+ this.play(e)
556
741
  },
557
742
  percentToTimeSeconds(percent) {
558
743
  const scaleFactor = this.player.duration / this.scrub.max
@@ -569,6 +754,13 @@ export default {
569
754
  onCueClick(time) {
570
755
  this.setTime(time)
571
756
  },
757
+ onClickExpandCaptions(expanded) {
758
+ this.options.expandedCaptions = expanded
759
+ this.$emit('click:captions-expand', expanded)
760
+ },
761
+ onClickParagraph(isParagraph) {
762
+ this.$emit('click:captions-paragraph', isParagraph)
763
+ },
572
764
  onDownload() {
573
765
  window.open(this.src.sources[0].src, '_blank')
574
766
  },
@@ -610,23 +802,37 @@ export default {
610
802
  this.$emit('mouseout', e)
611
803
  },
612
804
  onEnded(e) {
613
- if (this.activeAd) {
805
+ // Active ad ended but only continue playing if the video didn't just end on a postroll
806
+ if (this.activeAd && this.activeAd.play_at_percent !== 100) {
614
807
  this.ads[this.activeAd.play_at_percent].complete = true
615
808
  // Go back to the play_at_percent for the main video
616
809
  this.currentPercent = this.activeAd.play_at_percent
810
+
617
811
  this.activeAd = null
618
812
 
619
813
  // Reload the player to refresh all the sources / tracks
620
814
  this.load(e)
815
+
621
816
  // Start playing the main video
622
817
  this.play(e)
818
+ } else if (
819
+ !this.activeAd &&
820
+ typeof this.ads[this.currentPercent] !== 'undefined' &&
821
+ this.ads[this.currentPercent].sources &&
822
+ this.ads[this.currentPercent].sources.length &&
823
+ !this.ads[this.currentPercent].complete
824
+ ) {
825
+ // Video ended but there's an ad (probably 100% ad)
826
+ this.setActiveAd(this.currentPercent, e)
623
827
  } else if (
624
828
  this.activeAd !== null &&
625
829
  this.activeAd.play_at_percent === 100
626
830
  ) {
831
+ this.showReplay = true
627
832
  // Ended but this ad was a postroll
628
833
  this.$emit('ended', e)
629
834
  } else {
835
+ this.showReplay = true
630
836
  // Ended without an ad
631
837
  this.$emit('ended', e)
632
838
  }
@@ -677,6 +883,17 @@ export default {
677
883
  (this.player.currentTime / this.player.duration) * 100
678
884
  )
679
885
 
886
+ // Check if there's an ad that needs to be played
887
+ if (
888
+ !this.activeAd &&
889
+ typeof this.ads[this.currentPercent] !== 'undefined' &&
890
+ this.ads[this.currentPercent].sources &&
891
+ this.ads[this.currentPercent].sources.length &&
892
+ !this.ads[this.currentPercent].complete
893
+ ) {
894
+ this.setActiveAd(this.currentPercent, e)
895
+ }
896
+
680
897
  this.$emit('timeupdate', {
681
898
  event: e,
682
899
  current_percent: this.currentPercent,
@@ -684,11 +901,9 @@ export default {
684
901
  },
685
902
  onSeeking(e) {
686
903
  this.$emit('seeking', e)
687
- // console.log('onSeeking', e)
688
904
  },
689
905
  onMediaProgress(e) {
690
906
  this.$emit('progress', e)
691
- //console.log('onMediaProgress', e)
692
907
  },
693
908
  onCCToggle() {
694
909
  this.options.cc = !this.options.cc
@@ -715,6 +930,31 @@ export default {
715
930
  this.pause(e)
716
931
  }
717
932
  },
933
+ onClickReplay(e) {
934
+ // Re-initialize the ads aka pre/post/midroll
935
+ if (this.src.ads && this.src.ads.length) {
936
+ for (const ad of this.src.ads) {
937
+ // Map to a percent so we can avoid dupe timings and have easier lookups
938
+ this.ads[ad.play_at_percent] = ad
939
+ this.ads[ad.play_at_percent].complete = false
940
+ }
941
+
942
+ // There's a pre-roll / start ad. Reassign as the active
943
+ if (typeof this.ads[0] !== 'undefined') {
944
+ this.activeAd = this.ads[0]
945
+ } else {
946
+ // Clear the active ad otherwise
947
+ this.activeAd = null
948
+ }
949
+ }
950
+
951
+ // Reload the player to refresh all the sources / tracks
952
+ this.load(e)
953
+ // Start playing the main video
954
+ this.play(e)
955
+ // Restart from the beginning
956
+ this.setTime(0)
957
+ },
718
958
  onMuteToggle() {
719
959
  if (this.player.muted) {
720
960
  this.options.muted = false
@@ -743,6 +983,28 @@ export default {
743
983
  onCuechange(e) {
744
984
  if (e && e.srcElement && e.srcElement.track) {
745
985
  const track = e.srcElement.track
986
+
987
+ // Remove transcript classes from cues manually
988
+ // This is because Firefox doesn't support ::cue(<selector>) so we can't remove them via css
989
+ if (
990
+ typeof track.activeCues !== 'undefined' &&
991
+ track.activeCues.length > 0
992
+ ) {
993
+ // Store the raw version so we can retain it for the CaptionsMenu
994
+ if (typeof track.activeCues[0].rawText === 'undefined') {
995
+ track.activeCues[0].rawText = track.activeCues[0].text
996
+ }
997
+
998
+ // Now remove `<c.transcript>` tags
999
+ const transcriptTagRegex = /<c.transcript>.*?<\/c>/gi
1000
+
1001
+ track.activeCues[0].text =
1002
+ track.activeCues[0].text.replaceAll(
1003
+ transcriptTagRegex,
1004
+ ''
1005
+ )
1006
+ }
1007
+
746
1008
  this.setCues(track)
747
1009
  }
748
1010
 
@@ -795,82 +1057,34 @@ export default {
795
1057
  this.captions.nonce = Math.random()
796
1058
  },
797
1059
  load(e = null) {
798
- // Reload the player to refresh all the sources / tracks
799
- this.player.load()
800
- this.$emit('load', e)
1060
+ if (this.player.load) {
1061
+ // Reload the player to refresh all the sources / tracks
1062
+ this.player.load()
1063
+ this.$emit('load', e)
1064
+ } else {
1065
+ console.log('Cannot load player')
1066
+ }
801
1067
  },
802
1068
  pause(e = null) {
803
- this.player.pause()
804
- this.options.paused = true
805
- this.$emit('pause', e)
1069
+ if (this.player.pause) {
1070
+ this.player.pause()
1071
+ this.options.paused = true
1072
+ this.$emit('pause', e)
1073
+ } else {
1074
+ console.log('Cannot pause player')
1075
+ }
806
1076
  },
807
1077
  play(e = null) {
808
- // Start playing the main video
809
- this.player.play()
810
- this.options.paused = false
811
- this.$emit('play', e)
812
- },
813
- },
814
- beforeMount() {
815
- // Parse the controlslist string
816
- if (
817
- this.attributes.controlslist &&
818
- typeof this.attributes.controlslist === 'string' &&
819
- this.attributes.controlslist !== ''
820
- ) {
821
- this.options.controlslist = this.attributes.controlslist.split(' ')
822
- }
823
-
824
- // Adjust the playback speed to 1 by default
825
- if (this.attributes.playbackrates.indexOf(1) !== -1) {
826
- this.options.playbackRateIndex =
827
- this.attributes.playbackrates.indexOf(1)
828
- } else {
829
- // 1 aka normal playback not enabled (What monster would do this?!)
830
- // Set the playback rate to "middle of the road" for whatever is available
831
- this.options.playbackRateIndex = Math.floor(
832
- this.attributes.playbackrates.length / 2
833
- )
834
- }
835
-
836
- // Initialize the ads aka pre/post/midroll
837
- if (this.src.ads && this.src.ads.length) {
838
- for (const ad of this.src.ads) {
839
- // Map to a percent so we can avoid dupe timings and have easier lookups
840
- this.ads[ad.play_at_percent] = ad
841
- this.ads[ad.play_at_percent].complete = false
1078
+ if (this.player.play) {
1079
+ // Start playing the main video
1080
+ this.player.play()
1081
+ this.options.paused = false
1082
+ this.$emit('play', e)
1083
+ } else {
1084
+ console.log('Cannot play player')
842
1085
  }
843
- }
844
-
845
- // Determine fullscreen settings
846
- if (
847
- this.attributes.playsinline ||
848
- !document.fullscreenEnabled ||
849
- this.options.controlslist.indexOf('nofullscreen') !== -1
850
- ) {
851
- this.fullscreenEnabled = false
852
- } else {
853
- this.fullscreenEnabled = true
854
- }
855
-
856
- // Determine remote playback settings
857
- if (
858
- this.attributes.disableremoteplayback ||
859
- this.options.controlslist.indexOf('noremoteplayback') !== -1
860
- ) {
861
- this.options.remoteplayback = false
862
- } else {
863
- this.options.remoteplayback = true
864
- }
865
-
866
- // Determine download settings
867
- if (this.options.controlslist.indexOf('nodownload') !== -1) {
868
- this.options.download = false
869
- } else {
870
- this.options.download = true
871
- }
1086
+ },
872
1087
  },
873
- mounted() {},
874
1088
  }
875
1089
  </script>
876
1090
 
@@ -910,6 +1124,11 @@ export default {
910
1124
  max-height: 100%;
911
1125
  background: #000;
912
1126
  }
1127
+ /* Hide transcript classes from player */
1128
+ .player-video::cue(c.transcript) {
1129
+ visibility: hidden;
1130
+ font-size: 0;
1131
+ }
913
1132
  .player-overlay {
914
1133
  position: relative;
915
1134
  color: #fff;