@mindedge/vuetify-player 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -36
- package/package.json +1 -1
- package/src/components/Media/CaptionsMenu.vue +10 -15
- package/src/components/Media/Html5Player.vue +89 -11
- package/src/components/VuetifyPlayer.vue +95 -4
- package/src/i18n/en-US.js +1 -0
- package/src/i18n/es-ES.js +1 -0
- package/src/i18n/sv-SE.js +1 -0
package/README.md
CHANGED
|
@@ -192,39 +192,43 @@ See [Full media `src` structure for where the ads array is placed](#full-media-s
|
|
|
192
192
|
|
|
193
193
|
## Supported `<VuetifyPlayer>` Attributes
|
|
194
194
|
|
|
195
|
-
| Attribute Name | Datatype
|
|
196
|
-
| ------------------------------ |
|
|
197
|
-
| `language` | `String`
|
|
198
|
-
| `src` | `Object`
|
|
199
|
-
| `playlist` | `Array `
|
|
200
|
-
| `type` | `String`
|
|
201
|
-
| `autoplay` | `Boolean`
|
|
202
|
-
| `autopictureinpicture` | `Boolean`
|
|
203
|
-
| `controls` | `Boolean`
|
|
204
|
-
| `controlslist` | `String`
|
|
205
|
-
| `crossorigin` | `String`
|
|
206
|
-
| `disablepictureinpicture` | `Boolean`
|
|
207
|
-
| `disableremoteplayback` | `Boolean`
|
|
208
|
-
| `height` | `String`
|
|
209
|
-
| `width` | `String`
|
|
210
|
-
| `rewind` | `Boolean`
|
|
211
|
-
| `loop` | `Boolean`
|
|
212
|
-
| `muted` | `Boolean`
|
|
213
|
-
| `playsinline` | `Boolean`
|
|
214
|
-
| `poster` | `String`
|
|
215
|
-
| `preload` | `String`
|
|
216
|
-
| `captionsmenu` | `Boolean`
|
|
217
|
-
| `playlistmenu` | `Boolean`
|
|
218
|
-
| `playlistautoadvance` | `Boolean`
|
|
219
|
-
| `playbackrates` | `Array`
|
|
220
|
-
| `
|
|
221
|
-
| `
|
|
222
|
-
| `captions-
|
|
223
|
-
| `captions-hide-
|
|
224
|
-
| `captions-
|
|
225
|
-
| `captions-hide-
|
|
226
|
-
| `captions-
|
|
227
|
-
| `captions-
|
|
195
|
+
| Attribute Name | Datatype | Allowed Values | Default | Description |
|
|
196
|
+
| ------------------------------ | ------------------ | -------------------------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
197
|
+
| `language` | `String` | en-US \| See BCP 47 Spec | "en-US" | Defines which locale the video player controls will load in |
|
|
198
|
+
| `src` | `Object` | See [src attribute](#the-src-attribute) | {} | A single media source |
|
|
199
|
+
| `playlist` | `Array ` | See [playlist attribute](#the-playlist-attribute) | [] | A collection of media source(s) |
|
|
200
|
+
| `type` | `String` | auto \| video \| audio \| image | "auto" | In audio mode the player has a max-height of 40px |
|
|
201
|
+
| `autoplay` | `Boolean` | true \| false | false | Autoplay on load. It's in the spec but DON'T USE THIS |
|
|
202
|
+
| `autopictureinpicture` | `Boolean` | true \| false | false | Start with picture in picture mode |
|
|
203
|
+
| `controls` | `Boolean` | true \| false | true | Show video controls. When false only play/pause allowed but clicking on the video itself |
|
|
204
|
+
| `controlslist` | `String` | nodownload nofullscreen noremoteplayback | "nodownload noremoteplayback" | Space separated string per <video>. Allowed 'nodownload nofullscreen noremoteplayback' |
|
|
205
|
+
| `crossorigin` | `String` | anonymous \| use-credentials | "anonymous" | Indicates whether to use CORS to fetch the related video |
|
|
206
|
+
| `disablepictureinpicture` | `Boolean` | true \| false | true | Shows the picture in picture button |
|
|
207
|
+
| `disableremoteplayback` | `Boolean` | true \| false | true | Shows the remote playback button but functionality does not exist when clicked |
|
|
208
|
+
| `height` | `String` | `css pixel value` | "auto" | The players height |
|
|
209
|
+
| `width` | `String` | `css pixel value` | "100%" | The players width |
|
|
210
|
+
| `rewind` | `Boolean` | true \| false | true | Enabled the rewind 10s button |
|
|
211
|
+
| `loop` | `Boolean` | true \| false | false | Loop the video on completion |
|
|
212
|
+
| `muted` | `Boolean` | true \| false | false | Start the video muted |
|
|
213
|
+
| `playsinline` | `Boolean` | true \| false | false | Force inline & disable fullscreen |
|
|
214
|
+
| `poster` | `String` | `image url` | "" | Overridden with the playlist.poster if one is set there |
|
|
215
|
+
| `preload` | `String` | "none" \| "metadata" \| "auto" \| `_empty string_` | "" | Empty string = `auto` Provide a hint to the browser about what the author thinks will lead to the best user experience with regards to what content is loaded before the video is played. |
|
|
216
|
+
| `captionsmenu` | `Boolean` | true \| false | true | Allow the captions below the video |
|
|
217
|
+
| `playlistmenu` | `Boolean` | true \| false | true | Show the playlist menu if there's multiple videos |
|
|
218
|
+
| `playlistautoadvance` | `Boolean` | true \| false | true | Play the next source group |
|
|
219
|
+
| `playbackrates` | `Array` | [`array of numbers`] | [0.5, 1, 1.5, 2] | Default playback speeds. Anything below 0.25 and above 4 will make cause audio distortion |
|
|
220
|
+
| `volume` | `Number` | 0-1 | 0.5 | Supports `.sync`. The initial volume of the player. |
|
|
221
|
+
| `cc` | `Boolean` | true \| false | false | Supports `.sync`. The initial state of the player embedded closed captions. |
|
|
222
|
+
| `captions-expanded` | `Boolean` | true \| false | undefined | Supports `.sync`. The initial state of the captions transcript being expanded. |
|
|
223
|
+
| `captions-hide-expand` | `Boolean` | true \| false | true | Show / allow the captions transcript expand button for users |
|
|
224
|
+
| `captions-paragraph-view` | `Boolean` | true \| false | undefined | Supports `.sync`. The initial state of the captions transcript paragraph view |
|
|
225
|
+
| `captions-hide-paragraph-view` | `Boolean` | true \| false | false | Allow the captions transcript paragraph view for users |
|
|
226
|
+
| `captions-autoscroll` | `Boolean` | true \| false | false | Supports `.sync`. The initial state for the captions transcript autoscroll state |
|
|
227
|
+
| `captions-hide-autoscroll` | `Boolean` | true \| false | undefined | Allow users to enable / disable captions transcript autoscrolling |
|
|
228
|
+
| `captions-hide-close` | `Boolean` | true \| false | false | Allow users to show / hide the captions transcript box |
|
|
229
|
+
| `captions-visible` | `Boolean` | true \| false | false | Supports `.sync`. The initial state for the captions transcript visibility |
|
|
230
|
+
| `elevation` | `Number`\|`String` | 0-24 | 2 | Designates an elevation applied to the component between 0 and 24 |
|
|
231
|
+
| `flat` | `Boolean` | true \| false | false | Removes the elevation |
|
|
228
232
|
|
|
229
233
|
## Supported `<VuetifyPlayer>` Events
|
|
230
234
|
|
|
@@ -256,6 +260,8 @@ See [Full media `src` structure for where the ads array is placed](#full-media-s
|
|
|
256
260
|
| `click:captions-paragraph-view` | `true` \| `false` | When the view as paragraph button is clicked. true when viewing as a paragraph, false when viewing as timed captions |
|
|
257
261
|
| `click:captions-autoscroll` | `true` \| `false` | When the autoscroll captions button is clicked. true on autoscrolling otherwise false |
|
|
258
262
|
| `click:captions-close` | `true` \| `false` | When the c;pse captions button is clicked. true on closed, false on visible |
|
|
263
|
+
| `update:volume` | `Number` | When the volume is updated. This is the same as `volumechange` |
|
|
264
|
+
| `update:cc` | `true` \| `false` | When the players closed captions state is updated |
|
|
259
265
|
| `update:captions-expanded` | `true` \| `false` | When the captions expand state is updated |
|
|
260
266
|
| `update:captions-paragraph-view` | `true` \| `false` | When the captions paragraph-view state is updated |
|
|
261
267
|
| `update:captions-autoscroll` | `true` \| `false` | When the captions autoscroll state is updated |
|
|
@@ -263,9 +269,10 @@ See [Full media `src` structure for where the ads array is placed](#full-media-s
|
|
|
263
269
|
|
|
264
270
|
## Supported `<VuetifyPlayer>` Slots
|
|
265
271
|
|
|
266
|
-
| Slot name | Attributes | Description
|
|
267
|
-
| ----------- | ---------- |
|
|
268
|
-
| `no-source` | `none` | Displayed over the media skeleton loader when no media source is configured
|
|
272
|
+
| Slot name | Attributes | Description |
|
|
273
|
+
| ----------- | ---------- | ------------------------------------------------------------------------------------ |
|
|
274
|
+
| `no-source` | `none` | Displayed over the media skeleton loader when no media source is configured |
|
|
275
|
+
| `loading` | `none` | Displayed over the media skeleton loader when the playlist has changed via its props |
|
|
269
276
|
|
|
270
277
|
## Captions
|
|
271
278
|
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<v-card v-if="visibleState">
|
|
2
|
+
<v-card v-if="visibleState" :elevation="elevation">
|
|
3
3
|
<v-card-actions class="d-flex flex-wrap flex-row-reverse mb-0 pb-0">
|
|
4
4
|
<div class="d-flex ml-auto">
|
|
5
5
|
<v-tooltip v-if="!hideAutoscroll" top>
|
|
@@ -147,15 +147,6 @@
|
|
|
147
147
|
@click="onCueClick(cue.startTime)"
|
|
148
148
|
>
|
|
149
149
|
<template v-if="!expandedState">
|
|
150
|
-
<v-list-item-icon v-if="!paragraphViewState">
|
|
151
|
-
<v-icon
|
|
152
|
-
>{{
|
|
153
|
-
index === captionIndex
|
|
154
|
-
? 'mdi-arrow-right-drop-circle-outline'
|
|
155
|
-
: 'mdi-checkbox-blank-circle-outline'
|
|
156
|
-
}}
|
|
157
|
-
</v-icon>
|
|
158
|
-
</v-list-item-icon>
|
|
159
150
|
<v-list-item-content>
|
|
160
151
|
<v-list-item-title
|
|
161
152
|
v-html="cue.rawText || cue.text"
|
|
@@ -231,6 +222,7 @@ export default {
|
|
|
231
222
|
hideAutoscroll: { type: Boolean, required: false, default: false },
|
|
232
223
|
visible: { type: Boolean, required: false, default: undefined },
|
|
233
224
|
hideClose: { type: Boolean, required: false, default: false },
|
|
225
|
+
elevation: { type: [Number, String], required: false, default: 2 },
|
|
234
226
|
},
|
|
235
227
|
emits: [
|
|
236
228
|
'click:cue',
|
|
@@ -298,20 +290,23 @@ export default {
|
|
|
298
290
|
}
|
|
299
291
|
|
|
300
292
|
// Create a new paragraph every 3 sentences
|
|
301
|
-
if (
|
|
302
|
-
|
|
293
|
+
if (
|
|
294
|
+
puncuationCount > 3 &&
|
|
295
|
+
typeof cues[i + 1] !== 'undefined'
|
|
296
|
+
) {
|
|
297
|
+
// Find the first puncuation and include it in the slice so the NEXT paragraph doesn't start mid sentence
|
|
303
298
|
const breakIndex = cues[i].text.search(/[.?!]/) + 1
|
|
304
299
|
|
|
305
300
|
// Append the first part to the previous paragraph so it ends on a period
|
|
306
301
|
paragraphs[paragraphs.length - 1].text +=
|
|
307
|
-
' ' + cues[i].text.slice(0, breakIndex)
|
|
302
|
+
' ' + cues[i].text.slice(0, breakIndex).trim()
|
|
308
303
|
|
|
309
304
|
// Use `new VTTCue` to break the reference. Otherwise the below appends will duplicate text
|
|
310
305
|
// Also grab from the breakIndex afterwards to get the potential next sentence
|
|
311
306
|
paragraphs.push(
|
|
312
307
|
new VTTCue(
|
|
313
|
-
cues[i].startTime,
|
|
314
|
-
cues[i].endTime,
|
|
308
|
+
cues[i + 1].startTime,
|
|
309
|
+
cues[i + 1].endTime,
|
|
315
310
|
cues[i].text.slice(breakIndex).trim()
|
|
316
311
|
)
|
|
317
312
|
)
|
|
@@ -184,7 +184,7 @@
|
|
|
184
184
|
text
|
|
185
185
|
v-bind="attrs"
|
|
186
186
|
v-on="on"
|
|
187
|
-
@click="rewind"
|
|
187
|
+
@click="rewind(10)"
|
|
188
188
|
>
|
|
189
189
|
<v-icon>mdi-rewind-10</v-icon>
|
|
190
190
|
<span class="sr-only">{{
|
|
@@ -219,10 +219,10 @@
|
|
|
219
219
|
text
|
|
220
220
|
v-bind="attrs"
|
|
221
221
|
v-on="on"
|
|
222
|
-
@click="
|
|
222
|
+
@click="onClickCCToggle"
|
|
223
223
|
>
|
|
224
224
|
<v-icon>{{
|
|
225
|
-
|
|
225
|
+
ccState
|
|
226
226
|
? 'mdi-closed-caption'
|
|
227
227
|
: 'mdi-closed-caption-outline'
|
|
228
228
|
}}</v-icon>
|
|
@@ -485,6 +485,7 @@
|
|
|
485
485
|
:visible.sync="captionsVisibleState"
|
|
486
486
|
:hide-autoscroll="captionsHideAutoscroll"
|
|
487
487
|
:hide-close="captionsHideClose"
|
|
488
|
+
:elevation="elevation"
|
|
488
489
|
@update:paragraph-view="
|
|
489
490
|
$emit('update:captions-paragraph-view', $event)
|
|
490
491
|
"
|
|
@@ -530,6 +531,16 @@ export default {
|
|
|
530
531
|
type: Object,
|
|
531
532
|
required: true,
|
|
532
533
|
},
|
|
534
|
+
volume: {
|
|
535
|
+
type: Number,
|
|
536
|
+
required: false,
|
|
537
|
+
default: undefined,
|
|
538
|
+
},
|
|
539
|
+
cc: {
|
|
540
|
+
type: Boolean,
|
|
541
|
+
required: false,
|
|
542
|
+
default: undefined,
|
|
543
|
+
},
|
|
533
544
|
captionsExpanded: {
|
|
534
545
|
type: Boolean,
|
|
535
546
|
required: false,
|
|
@@ -566,6 +577,7 @@ export default {
|
|
|
566
577
|
required: false,
|
|
567
578
|
default: false,
|
|
568
579
|
},
|
|
580
|
+
elevation: { type: [Number, String], required: false, default: 2 },
|
|
569
581
|
},
|
|
570
582
|
emits: [
|
|
571
583
|
'error',
|
|
@@ -598,6 +610,7 @@ export default {
|
|
|
598
610
|
'click:captions-autoscroll',
|
|
599
611
|
'click:captions-close',
|
|
600
612
|
'click:captions-cue',
|
|
613
|
+
'update:cc',
|
|
601
614
|
'update:captions-expanded',
|
|
602
615
|
'update:captions-paragraph-view',
|
|
603
616
|
'update:captions-autoscroll',
|
|
@@ -651,6 +664,19 @@ export default {
|
|
|
651
664
|
|
|
652
665
|
return type
|
|
653
666
|
},
|
|
667
|
+
ccState: {
|
|
668
|
+
get() {
|
|
669
|
+
if (typeof this.cc !== 'undefined') {
|
|
670
|
+
return this.cc
|
|
671
|
+
} else {
|
|
672
|
+
return this.state.cc
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
set(v) {
|
|
676
|
+
this.$emit('update:cc', v)
|
|
677
|
+
this.state.cc = v
|
|
678
|
+
},
|
|
679
|
+
},
|
|
654
680
|
captionsVisibleState: {
|
|
655
681
|
get() {
|
|
656
682
|
if (typeof this.captionsVisible !== 'undefined') {
|
|
@@ -729,6 +755,7 @@ export default {
|
|
|
729
755
|
controlsDebounce: null,
|
|
730
756
|
volume: 0.5, // default 50%
|
|
731
757
|
muted: false,
|
|
758
|
+
unmuteVolume: 0, // The stored volume to return to the initial volume when unmuting
|
|
732
759
|
paused: true,
|
|
733
760
|
playbackRateIndex: 0,
|
|
734
761
|
fullscreen: false,
|
|
@@ -780,6 +807,11 @@ export default {
|
|
|
780
807
|
this.ads[ad.play_at_percent].complete = false
|
|
781
808
|
}
|
|
782
809
|
}
|
|
810
|
+
|
|
811
|
+
// Set the initial volume
|
|
812
|
+
if (typeof this.volume !== 'undefined') {
|
|
813
|
+
this.state.volume = this.volume
|
|
814
|
+
}
|
|
783
815
|
},
|
|
784
816
|
mounted() {
|
|
785
817
|
if (
|
|
@@ -927,7 +959,7 @@ export default {
|
|
|
927
959
|
*
|
|
928
960
|
* @param String|null lang The lang to load. Eg en-US, sv-SE, etc. Pass nothing / null to turn off captions
|
|
929
961
|
*/
|
|
930
|
-
onSelectTrack(lang = null) {
|
|
962
|
+
onSelectTrack(lang = null, mode = 'showing') {
|
|
931
963
|
if (this.player.textTracks && this.player.textTracks.length > 0) {
|
|
932
964
|
for (let i = 0; i < this.player.textTracks.length; i++) {
|
|
933
965
|
// Disable all tracks by default
|
|
@@ -936,7 +968,7 @@ export default {
|
|
|
936
968
|
|
|
937
969
|
if (this.player.textTracks[i].language === lang) {
|
|
938
970
|
this.state.ccLang = lang
|
|
939
|
-
this.player.textTracks[i].mode =
|
|
971
|
+
this.player.textTracks[i].mode = mode
|
|
940
972
|
|
|
941
973
|
this.setCues(this.player.textTracks[i])
|
|
942
974
|
|
|
@@ -978,13 +1010,16 @@ export default {
|
|
|
978
1010
|
onMediaProgress(e) {
|
|
979
1011
|
this.$emit('progress', e)
|
|
980
1012
|
},
|
|
981
|
-
|
|
982
|
-
|
|
1013
|
+
onClickCCToggle() {
|
|
1014
|
+
// We can't read the opposite of !this.ccState because it's a computed off of props
|
|
1015
|
+
// So this.ccState = !this.ccState takes 1 tick to reflect the actual changes between the getter and setter
|
|
1016
|
+
const state = !this.ccState
|
|
1017
|
+
this.ccState = state
|
|
983
1018
|
|
|
984
|
-
if (
|
|
1019
|
+
if (state) {
|
|
985
1020
|
this.onSelectTrack(this.state.ccLang)
|
|
986
1021
|
} else {
|
|
987
|
-
this.onSelectTrack()
|
|
1022
|
+
this.onSelectTrack(this.state.ccLang, 'hidden')
|
|
988
1023
|
}
|
|
989
1024
|
},
|
|
990
1025
|
onClickReplay(e) {
|
|
@@ -1014,10 +1049,19 @@ export default {
|
|
|
1014
1049
|
},
|
|
1015
1050
|
muteToggle() {
|
|
1016
1051
|
if (this.player.muted) {
|
|
1052
|
+
// Restore the inital volume
|
|
1053
|
+
this.state.volume = this.state.unmuteVolume
|
|
1054
|
+
this.player.volume = this.state.unmuteVolume
|
|
1055
|
+
this.state.unmuteVolume = 0
|
|
1017
1056
|
this.state.muted = false
|
|
1018
1057
|
this.player.muted = false
|
|
1019
1058
|
this.$emit('volumechange', this.state.volume)
|
|
1020
1059
|
} else {
|
|
1060
|
+
// Store the initial volume
|
|
1061
|
+
this.state.unmuteVolume = this.state.volume
|
|
1062
|
+
this.state.volume = 0
|
|
1063
|
+
this.player.volume = 0
|
|
1064
|
+
|
|
1021
1065
|
this.state.muted = true
|
|
1022
1066
|
this.player.muted = true
|
|
1023
1067
|
this.$emit('volumechange', 0)
|
|
@@ -1082,15 +1126,29 @@ export default {
|
|
|
1082
1126
|
* The this.player.textTracks are now loaded
|
|
1083
1127
|
*/
|
|
1084
1128
|
onLoadeddata(e) {
|
|
1129
|
+
let defaultTrackLang = null
|
|
1085
1130
|
// Set the default captions since apparently the default attribute means nothing
|
|
1086
1131
|
if (this.current.tracks && this.current.tracks.length > 0) {
|
|
1087
1132
|
for (const track of this.current.tracks) {
|
|
1088
1133
|
if (track.default) {
|
|
1134
|
+
defaultTrackLang = track.srclang
|
|
1089
1135
|
this.onSelectTrack(track.srclang)
|
|
1090
1136
|
}
|
|
1091
1137
|
}
|
|
1092
1138
|
}
|
|
1093
1139
|
|
|
1140
|
+
// Toggle the closed captions if the state is disabled
|
|
1141
|
+
if (!this.ccState) {
|
|
1142
|
+
this.onSelectTrack(defaultTrackLang, 'hidden')
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// We're starting muted so set the appropriate return volume / muted values
|
|
1146
|
+
if (this.state.volume === 0) {
|
|
1147
|
+
this.state.unmuteVolume = 0.5
|
|
1148
|
+
this.state.muted = true
|
|
1149
|
+
this.player.muted = true
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1094
1152
|
this.$emit('loadeddata', e)
|
|
1095
1153
|
},
|
|
1096
1154
|
onLoadedmetadata(e) {
|
|
@@ -1110,6 +1168,13 @@ export default {
|
|
|
1110
1168
|
} else if (value < 0) {
|
|
1111
1169
|
value = 0
|
|
1112
1170
|
}
|
|
1171
|
+
|
|
1172
|
+
// Unmuted if we're adjusting the volume up
|
|
1173
|
+
if (value > 0 && (this.player.muted || this.state.muted)) {
|
|
1174
|
+
this.state.muted = false
|
|
1175
|
+
this.player.muted = false
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1113
1178
|
this.state.volume = value
|
|
1114
1179
|
this.player.volume = value
|
|
1115
1180
|
this.$emit('volumechange', value)
|
|
@@ -1128,10 +1193,23 @@ export default {
|
|
|
1128
1193
|
this.player.currentTime = time
|
|
1129
1194
|
},
|
|
1130
1195
|
setCues(track) {
|
|
1196
|
+
// Filter out any cues / active cues that start after the video has already ended
|
|
1197
|
+
// This way we don't show captions that we can't skip to
|
|
1198
|
+
const cues = Object.keys(track.cues || {})
|
|
1199
|
+
.map((key) => track.cues[key])
|
|
1200
|
+
.filter((c) => {
|
|
1201
|
+
return c.startTime < this.player.duration
|
|
1202
|
+
})
|
|
1203
|
+
const activeCues = Object.keys(track.activeCues || {})
|
|
1204
|
+
.map((key) => track.activeCues[key])
|
|
1205
|
+
.filter((c) => {
|
|
1206
|
+
return c.startTime < this.player.duration
|
|
1207
|
+
})
|
|
1208
|
+
|
|
1131
1209
|
// Create reactive fields
|
|
1132
1210
|
this.$set(this.captions, 'language', track.language)
|
|
1133
|
-
this.$set(this.captions, 'cues',
|
|
1134
|
-
this.$set(this.captions, 'activeCues',
|
|
1211
|
+
this.$set(this.captions, 'cues', cues)
|
|
1212
|
+
this.$set(this.captions, 'activeCues', activeCues)
|
|
1135
1213
|
|
|
1136
1214
|
// Required so the v-model will actually update.
|
|
1137
1215
|
this.captions.nonce = Math.random()
|
|
@@ -2,7 +2,29 @@
|
|
|
2
2
|
<div>
|
|
3
3
|
<v-row tabindex="0">
|
|
4
4
|
<v-col cols="12">
|
|
5
|
-
<div v-if="
|
|
5
|
+
<div v-if="loading">
|
|
6
|
+
<div class="player-skeleton--no-source">
|
|
7
|
+
<slot name="loading">
|
|
8
|
+
<div>
|
|
9
|
+
<v-progress-circular indeterminate />
|
|
10
|
+
<p>
|
|
11
|
+
{{ t(language, 'player.loading') }}
|
|
12
|
+
</p>
|
|
13
|
+
</div>
|
|
14
|
+
</slot>
|
|
15
|
+
</div>
|
|
16
|
+
<v-skeleton-loader
|
|
17
|
+
type="image, image, list-item-avatar"
|
|
18
|
+
class="player-skeleton"
|
|
19
|
+
></v-skeleton-loader>
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
<div
|
|
23
|
+
v-if="
|
|
24
|
+
!loading &&
|
|
25
|
+
parseSourceType(current.src.sources) === null
|
|
26
|
+
"
|
|
27
|
+
>
|
|
6
28
|
<div class="player-skeleton--no-source">
|
|
7
29
|
<slot name="no-source">
|
|
8
30
|
<div>
|
|
@@ -18,24 +40,35 @@
|
|
|
18
40
|
class="player-skeleton"
|
|
19
41
|
></v-skeleton-loader>
|
|
20
42
|
</div>
|
|
43
|
+
|
|
21
44
|
<YoutubePlayer
|
|
22
45
|
ref="youtubePlayer"
|
|
23
|
-
v-if="
|
|
46
|
+
v-if="
|
|
47
|
+
!loading &&
|
|
48
|
+
parseSourceType(current.src.sources) === 'youtube'
|
|
49
|
+
"
|
|
24
50
|
:language="language"
|
|
25
51
|
:type="current.type"
|
|
26
52
|
:attributes="current.attributes"
|
|
27
53
|
:src="current.src"
|
|
54
|
+
:elevation="flat ? 0 : elevation"
|
|
28
55
|
@focusin="onFocusin"
|
|
29
56
|
@focusout="onFocusout"
|
|
30
57
|
@click:fullscreen="onFullscreen"
|
|
31
58
|
></YoutubePlayer>
|
|
59
|
+
|
|
32
60
|
<Html5Player
|
|
33
61
|
ref="html5Player"
|
|
34
|
-
v-if="
|
|
62
|
+
v-if="
|
|
63
|
+
!loading &&
|
|
64
|
+
parseSourceType(current.src.sources) === 'html5'
|
|
65
|
+
"
|
|
35
66
|
:language="language"
|
|
36
67
|
:type="current.type"
|
|
37
68
|
:attributes="current.attributes"
|
|
38
69
|
:src="current.src"
|
|
70
|
+
:volume.sync="volumeState"
|
|
71
|
+
:cc.sync="ccState"
|
|
39
72
|
:captions-expanded.sync="captionsExpandedState"
|
|
40
73
|
:captions-hide-expand="captionsHideExpand"
|
|
41
74
|
:captions-paragraph-view="captionsParagraphView"
|
|
@@ -43,6 +76,7 @@
|
|
|
43
76
|
:captions-autoscroll="captionsAutoscroll"
|
|
44
77
|
:captions-hide-autoscroll="captionsHideAutoscroll"
|
|
45
78
|
:captions-visible.sync="captionsVisibleState"
|
|
79
|
+
:elevation="flat ? 0 : elevation"
|
|
46
80
|
@load="$emit('load', $event)"
|
|
47
81
|
@ended="onEnded"
|
|
48
82
|
@loadeddata="onLoadeddata"
|
|
@@ -52,6 +86,7 @@
|
|
|
52
86
|
@seeking="$emit('seeking', $event)"
|
|
53
87
|
@timeupdate="$emit('timeupdate', $event)"
|
|
54
88
|
@progress="$emit('progress', $event)"
|
|
89
|
+
@volumechange="onVolumeChange"
|
|
55
90
|
@canplay="$emit('canplay', $event)"
|
|
56
91
|
@waiting="$emit('waiting', $event)"
|
|
57
92
|
@canplaythrough="$emit('canplaythrough', $event)"
|
|
@@ -155,9 +190,11 @@ export default {
|
|
|
155
190
|
rewind: { type: Boolean, required: false, default: false }, // Enabled the rewind 10s button
|
|
156
191
|
loop: { type: Boolean, required: false, default: false }, // Loop the video on completion
|
|
157
192
|
muted: { type: Boolean, required: false, default: false }, // Start the video muted
|
|
193
|
+
volume: { type: Number, required: false, default: undefined }, // The initial volume level. Undefined will use the local value of 0.5
|
|
158
194
|
playsinline: { Boolean: String, required: false, default: false }, // Force inline & disable fullscreen
|
|
159
195
|
poster: { type: String, required: false, default: '' }, // Overridden with the playlist.poster if one is set there
|
|
160
196
|
preload: { type: String, required: false, default: '' },
|
|
197
|
+
cc: { type: Boolean, required: false, default: undefined }, // The initial state of the closed captions (if available). Undefined will use the local value of false
|
|
161
198
|
captionsmenu: { type: Boolean, required: false, default: true }, // Show the captions below the video
|
|
162
199
|
captionsExpanded: {
|
|
163
200
|
type: Boolean,
|
|
@@ -204,6 +241,8 @@ export default {
|
|
|
204
241
|
return [0.5, 1, 1.5, 2]
|
|
205
242
|
},
|
|
206
243
|
}, // Default playback speeds
|
|
244
|
+
elevation: { type: [Number, String], required: false, default: 2 },
|
|
245
|
+
flat: { type: Boolean, required: false, default: false },
|
|
207
246
|
},
|
|
208
247
|
emits: [
|
|
209
248
|
'load',
|
|
@@ -214,6 +253,7 @@ export default {
|
|
|
214
253
|
'seeking',
|
|
215
254
|
'timeupdate',
|
|
216
255
|
'progress',
|
|
256
|
+
'volumechange',
|
|
217
257
|
'canplay',
|
|
218
258
|
'waiting',
|
|
219
259
|
'canplaythrough',
|
|
@@ -231,12 +271,24 @@ export default {
|
|
|
231
271
|
'click:captions-paragraph-view',
|
|
232
272
|
'click:captions-autoscroll',
|
|
233
273
|
'click:captions-close',
|
|
274
|
+
'update:volume',
|
|
275
|
+
'update:cc',
|
|
234
276
|
'update:captions-expanded',
|
|
235
277
|
'update:captions-paragraph-view',
|
|
236
278
|
'update:captions-autoscroll',
|
|
237
279
|
'update:captions-visible',
|
|
238
280
|
],
|
|
239
|
-
watch: {
|
|
281
|
+
watch: {
|
|
282
|
+
playlist: {
|
|
283
|
+
handler(newValue, oldValue) {
|
|
284
|
+
// Make sure there was actual changes to prevent unnecessary reloads
|
|
285
|
+
if (JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
|
|
286
|
+
// Force-reload the media on playlist changes since we changed the source / tracks
|
|
287
|
+
this.reloadMedia()
|
|
288
|
+
}
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
},
|
|
240
292
|
computed: {
|
|
241
293
|
player() {
|
|
242
294
|
if (this.parseSourceType(this.current.src.sources) === 'youtube') {
|
|
@@ -299,6 +351,32 @@ export default {
|
|
|
299
351
|
return 8
|
|
300
352
|
}
|
|
301
353
|
},
|
|
354
|
+
ccState: {
|
|
355
|
+
get() {
|
|
356
|
+
if (typeof this.cc !== 'undefined') {
|
|
357
|
+
return this.cc
|
|
358
|
+
} else {
|
|
359
|
+
return this.captions.cc
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
set(v) {
|
|
363
|
+
this.$emit('update:cc', v)
|
|
364
|
+
this.captions.cc = v
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
volumeState: {
|
|
368
|
+
get() {
|
|
369
|
+
if (typeof this.volume !== 'undefined') {
|
|
370
|
+
return this.volume
|
|
371
|
+
} else {
|
|
372
|
+
return this.localVolume
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
set(v) {
|
|
376
|
+
this.player.volumeChange(v)
|
|
377
|
+
this.localVolume = v
|
|
378
|
+
},
|
|
379
|
+
},
|
|
302
380
|
captionsVisibleState: {
|
|
303
381
|
get() {
|
|
304
382
|
if (typeof this.captionsVisible !== 'undefined') {
|
|
@@ -332,16 +410,25 @@ export default {
|
|
|
332
410
|
data() {
|
|
333
411
|
return {
|
|
334
412
|
t,
|
|
413
|
+
loading: false,
|
|
335
414
|
sourceIndex: 0,
|
|
336
415
|
captions: {
|
|
416
|
+
cc: false,
|
|
337
417
|
visible: true,
|
|
338
418
|
expanded: false,
|
|
339
419
|
},
|
|
420
|
+
localVolume: 0.5, // The initial volume level
|
|
340
421
|
mediaFocus: false,
|
|
341
422
|
keyListener: null,
|
|
342
423
|
}
|
|
343
424
|
},
|
|
344
425
|
methods: {
|
|
426
|
+
reloadMedia() {
|
|
427
|
+
this.loading = true
|
|
428
|
+
setTimeout(() => {
|
|
429
|
+
this.loading = false
|
|
430
|
+
}, 500)
|
|
431
|
+
},
|
|
345
432
|
onEnded(e) {
|
|
346
433
|
if (
|
|
347
434
|
this.playlistautoadvance &&
|
|
@@ -355,6 +442,10 @@ export default {
|
|
|
355
442
|
// Loaded a new video
|
|
356
443
|
this.$emit('loadeddata', e)
|
|
357
444
|
},
|
|
445
|
+
onVolumeChange(e) {
|
|
446
|
+
this.$emit('update:volume', e)
|
|
447
|
+
this.$emit('volumechange', e)
|
|
448
|
+
},
|
|
358
449
|
onRemoteplayback(el) {
|
|
359
450
|
// Make sure the browser supports remote playback
|
|
360
451
|
if (!el.remote || !el.remote.watchAvailability) {
|
package/src/i18n/en-US.js
CHANGED
package/src/i18n/es-ES.js
CHANGED
|
@@ -10,6 +10,7 @@ export default {
|
|
|
10
10
|
next: 'Reproducir el siguiente elemento en la lista de reproducción',
|
|
11
11
|
},
|
|
12
12
|
player: {
|
|
13
|
+
loading: 'Cargando...',
|
|
13
14
|
not_configured: 'Medios no configurados',
|
|
14
15
|
playback_speed: 'Velocidad de reproducción',
|
|
15
16
|
playback_decrease: 'Disminuir la velocidad de reproducción',
|
package/src/i18n/sv-SE.js
CHANGED