@mindedge/vuetify-player 0.1.3 → 0.3.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.
@@ -0,0 +1,1047 @@
1
+ <template>
2
+ <v-container>
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)
85
+ "
86
+ inverse-label
87
+ @mousedown="onPause"
88
+ @change="onScrubTime"
89
+ >
90
+ <template #prepend>
91
+ <!-- Play button -->
92
+ <v-tooltip 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')
127
+ }}</span>
128
+ </v-tooltip>
129
+
130
+ <!-- Rewind Button-->
131
+ <v-tooltip v-if="attributes.rewind" top>
132
+ <template
133
+ v-slot:activator="{ on, attrs }"
134
+ >
135
+ <v-btn
136
+ small
137
+ text
138
+ v-bind="attrs"
139
+ v-on="on"
140
+ @click="onRewind"
141
+ >
142
+ <v-icon>mdi-rewind-10</v-icon>
143
+ <span class="sr-only">{{
144
+ t(
145
+ language,
146
+ 'player.rewind_10'
147
+ )
148
+ }}</span>
149
+ </v-btn>
150
+ </template>
151
+ <span>{{
152
+ t(language, 'player.rewind_10')
153
+ }}</span>
154
+ </v-tooltip>
155
+ </template>
156
+
157
+ <template #append>
158
+ <!-- Closed Captions -->
159
+ <v-menu
160
+ v-if="
161
+ current.tracks &&
162
+ current.tracks.length > 0
163
+ "
164
+ open-on-hover
165
+ top
166
+ offset-y
167
+ >
168
+ <template
169
+ v-slot:activator="{ on, attrs }"
170
+ >
171
+ <v-btn
172
+ small
173
+ text
174
+ v-bind="attrs"
175
+ v-on="on"
176
+ @click="onCCToggle"
177
+ >
178
+ <v-icon>{{
179
+ options.cc
180
+ ? 'mdi-closed-caption'
181
+ : 'mdi-closed-caption-outline'
182
+ }}</v-icon>
183
+ <span class="d-sr-only">{{
184
+ t(
185
+ language,
186
+ 'player.toggle_cc'
187
+ )
188
+ }}</span>
189
+ </v-btn>
190
+ </template>
191
+
192
+ <v-list>
193
+ <v-list-item-group>
194
+ <v-list-item
195
+ v-for="track in current.tracks"
196
+ :key="track.srclang"
197
+ @click="
198
+ onSelectTrack(
199
+ track.srclang
200
+ )
201
+ "
202
+ >
203
+ <v-list-item-title>{{
204
+ track.srclang
205
+ }}</v-list-item-title>
206
+ </v-list-item>
207
+ </v-list-item-group>
208
+ </v-list>
209
+ </v-menu>
210
+
211
+ <!-- Volume -->
212
+ <v-menu open-on-hover top offset-y>
213
+ <template
214
+ v-slot:activator="{ on, attrs }"
215
+ >
216
+ <v-btn
217
+ small
218
+ text
219
+ v-bind="attrs"
220
+ v-on="on"
221
+ @click="onMuteToggle"
222
+ >
223
+ <v-icon
224
+ v-if="
225
+ !options.muted &&
226
+ options.volume > 0.75
227
+ "
228
+ >mdi-volume-high</v-icon
229
+ >
230
+ <v-icon
231
+ v-if="
232
+ !options.muted &&
233
+ options.volume >=
234
+ 0.25 &&
235
+ options.volume <= 0.75
236
+ "
237
+ >mdi-volume-medium</v-icon
238
+ >
239
+ <v-icon
240
+ v-if="
241
+ !options.muted &&
242
+ options.volume > 0 &&
243
+ options.volume < 0.25
244
+ "
245
+ >mdi-volume-low</v-icon
246
+ >
247
+ <v-icon
248
+ v-if="
249
+ options.muted ||
250
+ options.volume === 0
251
+ "
252
+ >mdi-volume-off</v-icon
253
+ >
254
+ <span class="d-sr-only">{{
255
+ t(
256
+ language,
257
+ 'player.volume_slider'
258
+ )
259
+ }}</span>
260
+ </v-btn>
261
+ </template>
262
+
263
+ <v-sheet class="pa-5">
264
+ <span class="d-sr-only">{{
265
+ t(
266
+ language,
267
+ 'player.volume_slider'
268
+ )
269
+ }}</span>
270
+ <v-slider
271
+ v-model="options.volume"
272
+ inverse-label
273
+ :min="0"
274
+ :max="1"
275
+ :step="0.1"
276
+ vertical
277
+ @change="onVolumeChange"
278
+ ></v-slider>
279
+ </v-sheet>
280
+ </v-menu>
281
+
282
+ <!-- Fullscreen -->
283
+ <v-tooltip v-if="fullscreenEnabled" top>
284
+ <template
285
+ v-slot:activator="{ on, attrs }"
286
+ >
287
+ <v-btn
288
+ small
289
+ text
290
+ v-bind="attrs"
291
+ v-on="on"
292
+ @click="onFullscreen"
293
+ >
294
+ <v-icon>{{
295
+ !options.fullscreen
296
+ ? 'mdi-fullscreen'
297
+ : 'mdi-fullscreen-exit'
298
+ }}</v-icon>
299
+ <span class="d-sr-only">{{
300
+ t(
301
+ language,
302
+ 'player.toggle_fullscreen'
303
+ )
304
+ }}</span>
305
+ </v-btn></template
306
+ >
307
+ <span>{{
308
+ t(
309
+ language,
310
+ 'player.toggle_fullscreen'
311
+ )
312
+ }}</span>
313
+ </v-tooltip>
314
+
315
+ <!-- Picture in picture -->
316
+ <v-tooltip
317
+ v-if="
318
+ !attributes.disablepictureinpicture
319
+ "
320
+ top
321
+ >
322
+ <template
323
+ v-slot:activator="{ on, attrs }"
324
+ >
325
+ <v-btn
326
+ small
327
+ text
328
+ v-bind="attrs"
329
+ v-on="on"
330
+ @click="onPictureInPicture"
331
+ >
332
+ <v-icon
333
+ >mdi-picture-in-picture-bottom-right</v-icon
334
+ >
335
+ <span class="d-sr-only">{{
336
+ t(
337
+ language,
338
+ 'player.toggle_picture_in_picture'
339
+ )
340
+ }}</span>
341
+ </v-btn></template
342
+ >
343
+ <span>{{
344
+ t(
345
+ language,
346
+ 'player.toggle_picture_in_picture'
347
+ )
348
+ }}</span>
349
+ </v-tooltip>
350
+
351
+ <!-- Remote playback -->
352
+ <v-tooltip
353
+ v-if="options.remoteplayback"
354
+ top
355
+ >
356
+ <template
357
+ v-slot:activator="{ on, attrs }"
358
+ >
359
+ <v-btn
360
+ small
361
+ text
362
+ v-bind="attrs"
363
+ v-on="on"
364
+ @click="onRemoteplayback"
365
+ >
366
+ <v-icon>mdi-cast</v-icon>
367
+ <span class="d-sr-only">{{
368
+ t(
369
+ language,
370
+ 'player.toggle_remote_playback'
371
+ )
372
+ }}</span>
373
+ </v-btn></template
374
+ >
375
+ <span>{{
376
+ t(
377
+ language,
378
+ 'player.toggle_remote_playback'
379
+ )
380
+ }}</span>
381
+ </v-tooltip>
382
+
383
+ <!-- Download -->
384
+ <v-tooltip v-if="options.download" top>
385
+ <template
386
+ v-slot:activator="{ on, attrs }"
387
+ >
388
+ <v-btn
389
+ small
390
+ text
391
+ v-bind="attrs"
392
+ v-on="on"
393
+ @click="onDownload"
394
+ >
395
+ <v-icon>mdi-download</v-icon>
396
+ <span class="d-sr-only">{{
397
+ t(
398
+ language,
399
+ 'player.download'
400
+ )
401
+ }}</span>
402
+ </v-btn></template
403
+ >
404
+ <span>{{
405
+ t(language, 'player.download')
406
+ }}</span>
407
+ </v-tooltip>
408
+
409
+ <!-- Settings -->
410
+ <v-menu
411
+ top
412
+ offset-y
413
+ :close-on-content-click="false"
414
+ nudge-left="100"
415
+ >
416
+ <template
417
+ v-slot:activator="{ on, attrs }"
418
+ >
419
+ <v-btn
420
+ small
421
+ text
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>
521
+ </template>
522
+ </v-slider>
523
+ </div>
524
+ </v-slide-y-reverse-transition>
525
+ </div>
526
+ </v-col>
527
+
528
+ <v-col
529
+ v-if="
530
+ attributes.captionsmenu &&
531
+ current.tracks &&
532
+ captions &&
533
+ captions.cues &&
534
+ Object.keys(captions.cues).length
535
+ "
536
+ :cols="!options.expandedCaptions ? 12 : 6"
537
+ >
538
+ <CaptionsMenu
539
+ v-model="captions"
540
+ :language="language"
541
+ @click:cue="onCueClick"
542
+ @click:expand="onClickExpandCaptions"
543
+ @click:paragraph="onClickParagraph"
544
+ ></CaptionsMenu>
545
+ </v-col>
546
+ </v-row>
547
+ </v-container>
548
+ </template>
549
+
550
+ <script>
551
+ import filters from '../filters'
552
+ import CaptionsMenu from './CaptionsMenu.vue'
553
+ import { t } from '../../i18n/i18n'
554
+
555
+ export default {
556
+ name: 'Html5Player',
557
+ components: {
558
+ CaptionsMenu,
559
+ },
560
+ props: {
561
+ language: { type: String, required: false, default: 'en-US' },
562
+ type: {
563
+ type: String,
564
+ required: false,
565
+ default: 'video',
566
+ },
567
+ attributes: {
568
+ type: Object,
569
+ required: true,
570
+ },
571
+ src: {
572
+ type: Object,
573
+ required: true,
574
+ },
575
+ },
576
+ watch: {},
577
+ computed: {
578
+ current() {
579
+ // We're playing an ad currently
580
+ if (this.activeAd) {
581
+ 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
+ } else {
594
+ // Only change sources if we're not watching an ad or pre/postroll
595
+ return this.src
596
+ }
597
+ },
598
+ playerClass() {
599
+ let classList = 'player-' + this.type
600
+ return classList
601
+ },
602
+ },
603
+ data() {
604
+ return {
605
+ t,
606
+ filters,
607
+ ads: {},
608
+ activeAd: null,
609
+ currentPercent: 0,
610
+ player: {},
611
+ captions: { nonce: 0 },
612
+ fullscreenEnabled: false,
613
+ options: {
614
+ cc: true,
615
+ ccLang: this.language,
616
+ controls: true,
617
+ controlsDebounce: null,
618
+ volume: 0.5, // default 50%
619
+ muted: false,
620
+ paused: true,
621
+ playbackRateIndex: 0,
622
+ fullscreen: false,
623
+ expandedCaptions: false,
624
+ download: false,
625
+ remoteplayback: false,
626
+ controlslist: [],
627
+ },
628
+ watchPlayer: 0,
629
+ scrub: { max: 100 },
630
+ buffering: false,
631
+ }
632
+ },
633
+ methods: {
634
+ setActiveAd(currentPercent) {
635
+ this.activeAd = this.ads[currentPercent]
636
+ },
637
+ percentToTimeSeconds(percent) {
638
+ const scaleFactor = this.player.duration / this.scrub.max
639
+ return Math.floor(percent * scaleFactor)
640
+ },
641
+ onCanplay(e) {
642
+ this.buffering = false
643
+ this.$emit('canplay', e)
644
+ },
645
+ onWaiting(e) {
646
+ this.buffering = true
647
+ this.$emit('waiting', e)
648
+ },
649
+ onCueClick(time) {
650
+ this.setTime(time)
651
+ },
652
+ onClickExpandCaptions(expanded) {
653
+ this.options.expandedCaptions = expanded
654
+ this.$emit('click:captions-expand', expanded)
655
+ },
656
+ onClickParagraph(isParagraph) {
657
+ this.$emit('click:captions-paragraph', isParagraph)
658
+ },
659
+ onDownload() {
660
+ window.open(this.src.sources[0].src, '_blank')
661
+ },
662
+ onRewind() {
663
+ // Rewind in seconds
664
+ const seconds = 10
665
+
666
+ if (this.player.currentTime <= seconds) {
667
+ this.setTime(0)
668
+ } else {
669
+ this.setTime(this.player.currentTime - seconds)
670
+ }
671
+ },
672
+ onFullscreen() {
673
+ this.options.fullscreen = !document.fullscreenElement
674
+ // Return the whole element to be fullscreened so the controls come with it
675
+ this.$emit('click:fullscreen', this.$el)
676
+ },
677
+ onPictureInPicture() {
678
+ //this.options.pip = !document.fullscreenElement;
679
+ // Return the player aka HTMLVideoElement
680
+ this.$emit('click:pictureinpicture', this.$refs.player)
681
+ },
682
+ onRemoteplayback() {
683
+ this.$emit('click:remoteplayback', this.$refs.player)
684
+ },
685
+ onVideoHover(e) {
686
+ this.options.controls = true
687
+ clearTimeout(this.options.controlsDebounce)
688
+ this.$emit('mouseover', e)
689
+ },
690
+ onVideoLeave(e) {
691
+ const self = this
692
+ // Clear any existing timeouts before we create one
693
+ clearTimeout(this.options.controlsDebounce)
694
+ this.options.controlsDebounce = setTimeout(() => {
695
+ self.options.controls = false
696
+ }, 50)
697
+ this.$emit('mouseout', e)
698
+ },
699
+ onEnded(e) {
700
+ if (this.activeAd) {
701
+ this.ads[this.activeAd.play_at_percent].complete = true
702
+ // Go back to the play_at_percent for the main video
703
+ this.currentPercent = this.activeAd.play_at_percent
704
+ this.activeAd = null
705
+
706
+ // Reload the player to refresh all the sources / tracks
707
+ this.load(e)
708
+ // Start playing the main video
709
+ this.play(e)
710
+ } else if (
711
+ this.activeAd !== null &&
712
+ this.activeAd.play_at_percent === 100
713
+ ) {
714
+ // Ended but this ad was a postroll
715
+ this.$emit('ended', e)
716
+ } else {
717
+ // Ended without an ad
718
+ this.$emit('ended', e)
719
+ }
720
+ },
721
+ onControlsHover() {
722
+ clearTimeout(this.options.controlsDebounce)
723
+ this.options.controls = true
724
+ },
725
+ onControlsLeave() {
726
+ const self = this
727
+ // Clear any existing timeouts before we create one
728
+ clearTimeout(this.options.controlsDebounce)
729
+ this.options.controlsDebounce = setTimeout(() => {
730
+ self.options.controls = false
731
+ }, 50)
732
+ },
733
+ /**
734
+ * Select a specific track by lang
735
+ *
736
+ * @param String|null lang The lang to load. Eg en-US, sv-SE, etc. Pass nothing / null to turn off captions
737
+ */
738
+ onSelectTrack(lang = null) {
739
+ if (this.player.textTracks && this.player.textTracks.length > 0) {
740
+ for (let i = 0; i < this.player.textTracks.length; i++) {
741
+ const tt = this.player.textTracks[i]
742
+
743
+ if (tt.language === lang) {
744
+ this.options.ccLang = lang
745
+ this.player.textTracks[i].mode = 'showing'
746
+
747
+ this.setCues(tt)
748
+
749
+ // Emit the current track
750
+ this.$emit('trackchange', tt)
751
+ } else {
752
+ this.player.textTracks[i].mode = 'disabled'
753
+ }
754
+ }
755
+ }
756
+ },
757
+ onPlaybackSpeed(index) {
758
+ this.player.playbackRate = this.attributes.playbackrates[index]
759
+ this.options.playbackRateIndex = index
760
+ this.$emit('ratechange', this.player.playbackRate)
761
+ },
762
+ onTimeupdate(e) {
763
+ this.currentPercent = Math.floor(
764
+ (this.player.currentTime / this.player.duration) * 100
765
+ )
766
+
767
+ this.$emit('timeupdate', {
768
+ event: e,
769
+ current_percent: this.currentPercent,
770
+ })
771
+ },
772
+ onSeeking(e) {
773
+ this.$emit('seeking', e)
774
+ },
775
+ onMediaProgress(e) {
776
+ this.$emit('progress', e)
777
+ },
778
+ onCCToggle() {
779
+ this.options.cc = !this.options.cc
780
+
781
+ if (this.options.cc) {
782
+ this.onSelectTrack(this.options.ccLang)
783
+ } else {
784
+ this.onSelectTrack()
785
+ }
786
+ },
787
+ onPlayToggle(e) {
788
+ const self = this
789
+ this.options.controls = true
790
+
791
+ // Clear any existing timeouts and close the controls in 5 seconds
792
+ clearTimeout(this.options.controlsDebounce)
793
+ this.options.controlsDebounce = setTimeout(() => {
794
+ self.options.controls = false
795
+ }, 5000)
796
+
797
+ if (this.player.paused) {
798
+ this.play(e)
799
+ } else {
800
+ this.pause(e)
801
+ }
802
+ },
803
+ onMuteToggle() {
804
+ if (this.player.muted) {
805
+ this.options.muted = false
806
+ this.player.muted = false
807
+ this.$emit('volumechange', this.options.volume)
808
+ } else {
809
+ this.options.muted = true
810
+ this.player.muted = true
811
+ this.$emit('volumechange', 0)
812
+ }
813
+ },
814
+ onPlay(e) {
815
+ this.play(e)
816
+ },
817
+ onPause(e) {
818
+ this.pause(e)
819
+ },
820
+ onScrubTime(value) {
821
+ // Value is a number from 0 to scrub max (eg 100). Translate that into a time
822
+ // We don't want the scrub.max to equal time.duration since we don't want
823
+ // thousands of "targets" for long videos
824
+ const scaleFactor = this.player.duration / this.scrub.max
825
+ this.setTime(value * scaleFactor)
826
+ this.player.pause()
827
+ },
828
+ onCuechange(e) {
829
+ if (e && e.srcElement && e.srcElement.track) {
830
+ const track = e.srcElement.track
831
+
832
+ // Remove transcript classes from cues manually
833
+ // This is because Firefox doesn't support ::cue(<selector>) so we can't remove them via css
834
+ if (
835
+ typeof track.activeCues !== 'undefined' &&
836
+ track.activeCues.length > 0
837
+ ) {
838
+ // Store the raw version so we can retain it for the CaptionsMenu
839
+ if (typeof track.activeCues[0].rawText === 'undefined') {
840
+ track.activeCues[0].rawText = track.activeCues[0].text
841
+ }
842
+
843
+ // Now remove `<c.transcript>` tags
844
+ const transcriptTagRegex = /<c.transcript>.*?<\/c>/gi
845
+
846
+ track.activeCues[0].text =
847
+ track.activeCues[0].text.replaceAll(
848
+ transcriptTagRegex,
849
+ ''
850
+ )
851
+ }
852
+
853
+ this.setCues(track)
854
+ }
855
+
856
+ this.$emit('cuechange', e)
857
+ },
858
+ /**
859
+ * The this.player.textTracks are now loaded
860
+ */
861
+ onLoadeddata(e) {
862
+ // Set the default captions since apparently the default attribute means nothing
863
+ if (this.current.tracks && this.current.tracks.length > 0) {
864
+ for (const track of this.current.tracks) {
865
+ if (track.default) {
866
+ this.onSelectTrack(track.srclang)
867
+ }
868
+ }
869
+ }
870
+
871
+ this.$emit('loadeddata', e)
872
+ },
873
+ onLoadedmetadata(e) {
874
+ // Set the player object since metadata (and therefore duration) is now loaded
875
+ // this.$refs.player is a type of HTMLMediaElement
876
+ // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement
877
+ //this.player.media = this.$refs.player;
878
+ this.$emit('loadedmetadata', e)
879
+ this.player = this.$refs.player
880
+ this.player.volume = this.options.volume
881
+ this.$emit('volumechange', this.options.volume)
882
+ },
883
+ onVolumeChange(value) {
884
+ this.options.volume = value
885
+ this.player.volume = value
886
+ this.$emit('volumechange', value)
887
+ },
888
+ onDurationChange() {
889
+ // console.log('onDurationChange');
890
+ // console.log(e);
891
+ },
892
+ setTime(time) {
893
+ this.player.currentTime = time
894
+ },
895
+ setCues(track) {
896
+ // Create reactive fields
897
+ this.$set(this.captions, 'language', track.language)
898
+ this.$set(this.captions, 'cues', track.cues)
899
+ this.$set(this.captions, 'activeCues', track.activeCues)
900
+
901
+ // Required so the v-model will actually update.
902
+ this.captions.nonce = Math.random()
903
+ },
904
+ load(e = null) {
905
+ // Reload the player to refresh all the sources / tracks
906
+ this.player.load()
907
+ this.$emit('load', e)
908
+ },
909
+ pause(e = null) {
910
+ this.player.pause()
911
+ this.options.paused = true
912
+ this.$emit('pause', e)
913
+ },
914
+ play(e = null) {
915
+ // Start playing the main video
916
+ this.player.play()
917
+ this.options.paused = false
918
+ this.$emit('play', e)
919
+ },
920
+ },
921
+ beforeMount() {
922
+ // Parse the controlslist string
923
+ if (
924
+ this.attributes.controlslist &&
925
+ typeof this.attributes.controlslist === 'string' &&
926
+ this.attributes.controlslist !== ''
927
+ ) {
928
+ this.options.controlslist = this.attributes.controlslist.split(' ')
929
+ }
930
+
931
+ if (
932
+ typeof this.attributes.playbackrates === 'undefined' ||
933
+ this.attributes.playbackrates.length === 0
934
+ ) {
935
+ throw new Error(
936
+ 'attributes.playbackrates must be defined and an array of numbers!'
937
+ )
938
+ }
939
+
940
+ // Adjust the playback speed to 1 by default
941
+ if (this.attributes.playbackrates.indexOf(1) !== -1) {
942
+ this.options.playbackRateIndex =
943
+ this.attributes.playbackrates.indexOf(1)
944
+ } else {
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
958
+ }
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
+ }
988
+ },
989
+ mounted() {},
990
+ }
991
+ </script>
992
+
993
+ <style scoped>
994
+ .controls-container {
995
+ height: 40px;
996
+ position: relative;
997
+ top: -50px;
998
+ margin-bottom: -40px;
999
+ overflow: hidden;
1000
+ }
1001
+ .controls {
1002
+ height: 40px;
1003
+ background: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 0.7));
1004
+ }
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
+ .player-audio {
1023
+ height: 40px;
1024
+ }
1025
+ .player-video {
1026
+ max-height: 100%;
1027
+ background: #000;
1028
+ }
1029
+ /* Hide transcript classes from player */
1030
+ .player-video::cue(c.transcript) {
1031
+ visibility: hidden;
1032
+ font-size: 0;
1033
+ }
1034
+ .player-overlay {
1035
+ position: relative;
1036
+ color: #fff;
1037
+ left: 25%;
1038
+ width: 50%;
1039
+ top: 100px;
1040
+ height: 0;
1041
+ text-align: center;
1042
+ }
1043
+ .player-overlay > div {
1044
+ background: rgba(0, 0, 0, 0.25);
1045
+ border-radius: 100%;
1046
+ }
1047
+ </style>