@mindedge/vuetify-player 0.3.1 → 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 +452 -304
- 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 +9 -0
- package/src/i18n/es-ES.js +12 -0
- package/src/i18n/i18n.js +6 -1
- package/src/i18n/sv-SE.js +11 -0
|
@@ -2,33 +2,40 @@
|
|
|
2
2
|
<v-card>
|
|
3
3
|
<v-card-title>{{ t(language, 'playlist.up_next') }}</v-card-title>
|
|
4
4
|
<v-card-text>
|
|
5
|
-
<v-list>
|
|
5
|
+
<v-list class="playlist-list">
|
|
6
6
|
<v-list-item-group v-model="sourceIndex">
|
|
7
|
-
<v-
|
|
7
|
+
<v-tooltip
|
|
8
|
+
bottom
|
|
8
9
|
v-for="(source, index) of playlist"
|
|
9
10
|
:key="index + 'playlistSources'"
|
|
10
|
-
@click="onPlaylistSelect(index)"
|
|
11
11
|
>
|
|
12
|
-
<
|
|
13
|
-
<v-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
<template #activator="{ on, attrs }">
|
|
13
|
+
<v-list-item
|
|
14
|
+
class="pl-1"
|
|
15
|
+
v-bind="attrs"
|
|
16
|
+
v-on="on"
|
|
17
|
+
@click="onPlaylistSelect(index)"
|
|
16
18
|
>
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
19
|
+
<v-list-item-icon class="ma-2">
|
|
20
|
+
<v-avatar
|
|
21
|
+
v-if="getPoster(source.poster, poster)"
|
|
22
|
+
tile
|
|
23
|
+
>
|
|
24
|
+
<img
|
|
25
|
+
:src="
|
|
26
|
+
getPoster(source.poster, poster)
|
|
27
|
+
"
|
|
28
|
+
/>
|
|
29
|
+
</v-avatar>
|
|
30
|
+
<v-skeleton-loader
|
|
31
|
+
v-if="!getPoster(source.poster, poster)"
|
|
32
|
+
class="ma-3"
|
|
33
|
+
type="avatar"
|
|
34
|
+
tile
|
|
35
|
+
></v-skeleton-loader>
|
|
36
|
+
</v-list-item-icon>
|
|
37
|
+
<v-list-item-content>
|
|
29
38
|
<div
|
|
30
|
-
v-bind="attrs"
|
|
31
|
-
v-on="on"
|
|
32
39
|
class="text-lg-subtitle-1 text-truncate"
|
|
33
40
|
>
|
|
34
41
|
{{
|
|
@@ -36,16 +43,16 @@
|
|
|
36
43
|
t(language, 'playlist.default_name')
|
|
37
44
|
}}
|
|
38
45
|
</div>
|
|
39
|
-
</
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
</
|
|
48
|
-
</v-
|
|
46
|
+
</v-list-item-content>
|
|
47
|
+
</v-list-item>
|
|
48
|
+
</template>
|
|
49
|
+
<span>
|
|
50
|
+
{{
|
|
51
|
+
source.name ||
|
|
52
|
+
t(language, 'playlist.default_name')
|
|
53
|
+
}}
|
|
54
|
+
</span>
|
|
55
|
+
</v-tooltip>
|
|
49
56
|
</v-list-item-group>
|
|
50
57
|
</v-list>
|
|
51
58
|
</v-card-text>
|
|
@@ -122,17 +129,8 @@ export default {
|
|
|
122
129
|
</script>
|
|
123
130
|
|
|
124
131
|
<style scoped>
|
|
125
|
-
.
|
|
126
|
-
max-height:
|
|
132
|
+
.playlist-list {
|
|
133
|
+
max-height: 200px;
|
|
127
134
|
overflow-y: scroll;
|
|
128
|
-
/* Fade the top/bottom 20% effect. The "red" mask is so the scrollbar doesn't get this effect*/
|
|
129
|
-
mask: linear-gradient(90deg, rgba(255, 0, 0, 0) 98%, rgba(255, 0, 0, 1) 98%),
|
|
130
|
-
linear-gradient(
|
|
131
|
-
0deg,
|
|
132
|
-
rgba(0, 0, 0, 0) 0%,
|
|
133
|
-
rgba(0, 0, 0, 1) 20%,
|
|
134
|
-
rgba(0, 0, 0, 1) 80%,
|
|
135
|
-
rgba(0, 0, 0, 0) 100%
|
|
136
|
-
);
|
|
137
135
|
}
|
|
138
136
|
</style>
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<!-- Settings -->
|
|
3
|
+
<v-menu :attach="attach" top left offset-y :close-on-content-click="false">
|
|
4
|
+
<template #activator="{ on, attrs }">
|
|
5
|
+
<v-btn small text v-bind="attrs" v-on="on">
|
|
6
|
+
<v-icon>mdi-cog</v-icon>
|
|
7
|
+
<span class="d-sr-only">{{
|
|
8
|
+
t(language, 'player.toggle_settings')
|
|
9
|
+
}}</span>
|
|
10
|
+
</v-btn>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<v-list>
|
|
14
|
+
<v-list-item v-if="attributes.captionsmenu">
|
|
15
|
+
<v-list-item-title>
|
|
16
|
+
<v-switch
|
|
17
|
+
:input-value="captionsVisible"
|
|
18
|
+
:label="t(language, 'captions.show_transcript')"
|
|
19
|
+
@change="$emit('update:captions-visible', $event)"
|
|
20
|
+
></v-switch>
|
|
21
|
+
</v-list-item-title>
|
|
22
|
+
</v-list-item>
|
|
23
|
+
<v-list-item>
|
|
24
|
+
<v-list-item-title>
|
|
25
|
+
<v-icon>mdi-play-speed</v-icon>
|
|
26
|
+
{{ t(language, 'player.playback_speed') }}
|
|
27
|
+
</v-list-item-title>
|
|
28
|
+
</v-list-item>
|
|
29
|
+
<v-list-item>
|
|
30
|
+
<v-list-item-title class="text-center">
|
|
31
|
+
<v-btn
|
|
32
|
+
small
|
|
33
|
+
:disabled="state.playbackRateIndex === 0"
|
|
34
|
+
@click="onPlaybackSpeed(state.playbackRateIndex - 1)"
|
|
35
|
+
>
|
|
36
|
+
<v-icon> mdi-clock-minus-outline </v-icon>
|
|
37
|
+
<span class="d-sr-only">{{
|
|
38
|
+
t(language, 'player.playback_decrease')
|
|
39
|
+
}}</span>
|
|
40
|
+
</v-btn>
|
|
41
|
+
<span class="pl-2 pr-2"
|
|
42
|
+
>{{
|
|
43
|
+
attributes.playbackrates[state.playbackRateIndex]
|
|
44
|
+
}}x</span
|
|
45
|
+
>
|
|
46
|
+
<v-btn
|
|
47
|
+
small
|
|
48
|
+
:disabled="
|
|
49
|
+
state.playbackRateIndex >=
|
|
50
|
+
attributes.playbackrates.length - 1
|
|
51
|
+
"
|
|
52
|
+
@click="onPlaybackSpeed(state.playbackRateIndex + 1)"
|
|
53
|
+
>
|
|
54
|
+
<v-icon> mdi-clock-plus-outline </v-icon>
|
|
55
|
+
<span class="d-sr-only">{{
|
|
56
|
+
t(language, 'player.playback_increase')
|
|
57
|
+
}}</span>
|
|
58
|
+
</v-btn>
|
|
59
|
+
</v-list-item-title>
|
|
60
|
+
</v-list-item>
|
|
61
|
+
</v-list>
|
|
62
|
+
</v-menu>
|
|
63
|
+
</template>
|
|
64
|
+
|
|
65
|
+
<script>
|
|
66
|
+
import { t } from '../../i18n/i18n'
|
|
67
|
+
|
|
68
|
+
export default {
|
|
69
|
+
name: 'SettingsMenu',
|
|
70
|
+
components: {},
|
|
71
|
+
props: {
|
|
72
|
+
language: { type: String, required: false, default: 'en-US' },
|
|
73
|
+
attach: { type: null, required: false, default: false },
|
|
74
|
+
state: {
|
|
75
|
+
type: Object,
|
|
76
|
+
required: true,
|
|
77
|
+
},
|
|
78
|
+
attributes: {
|
|
79
|
+
type: Object,
|
|
80
|
+
required: true,
|
|
81
|
+
},
|
|
82
|
+
captionsVisible: { type: Boolean, required: false, default: undefined },
|
|
83
|
+
},
|
|
84
|
+
emits: ['update:captions-visible', 'change:playback-rate-index'],
|
|
85
|
+
watch: {},
|
|
86
|
+
computed: {},
|
|
87
|
+
data() {
|
|
88
|
+
return { t }
|
|
89
|
+
},
|
|
90
|
+
beforeMount() {},
|
|
91
|
+
mounted() {},
|
|
92
|
+
methods: {
|
|
93
|
+
onPlaybackSpeed(index) {
|
|
94
|
+
this.$emit('change:playback-rate-index', index)
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
</script>
|
|
@@ -34,9 +34,13 @@ export default {
|
|
|
34
34
|
watch: {},
|
|
35
35
|
computed: {
|
|
36
36
|
playerClass() {
|
|
37
|
-
let classList = 'player-' + this.
|
|
37
|
+
let classList = 'player-' + this.resolvedType
|
|
38
38
|
return classList
|
|
39
39
|
},
|
|
40
|
+
resolvedType() {
|
|
41
|
+
// Youtube is always a video
|
|
42
|
+
return 'video'
|
|
43
|
+
},
|
|
40
44
|
},
|
|
41
45
|
data() {
|
|
42
46
|
return {
|
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div>
|
|
3
|
-
<v-row>
|
|
4
|
-
<v-col
|
|
3
|
+
<v-row tabindex="0">
|
|
4
|
+
<v-col cols="12">
|
|
5
|
+
<div v-if="parseSourceType(current.src.sources) === null">
|
|
6
|
+
<div class="player-skeleton--no-source">
|
|
7
|
+
<slot name="no-source">
|
|
8
|
+
<div>
|
|
9
|
+
<v-icon x-large>mdi-cloud-question</v-icon>
|
|
10
|
+
<p>
|
|
11
|
+
{{ t(language, 'player.not_configured') }}
|
|
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>
|
|
5
21
|
<YoutubePlayer
|
|
6
22
|
ref="youtubePlayer"
|
|
7
23
|
v-if="parseSourceType(current.src.sources) === 'youtube'"
|
|
@@ -9,6 +25,8 @@
|
|
|
9
25
|
:type="current.type"
|
|
10
26
|
:attributes="current.attributes"
|
|
11
27
|
:src="current.src"
|
|
28
|
+
@focusin="onFocusin"
|
|
29
|
+
@focusout="onFocusout"
|
|
12
30
|
@click:fullscreen="onFullscreen"
|
|
13
31
|
></YoutubePlayer>
|
|
14
32
|
<Html5Player
|
|
@@ -18,6 +36,13 @@
|
|
|
18
36
|
:type="current.type"
|
|
19
37
|
:attributes="current.attributes"
|
|
20
38
|
:src="current.src"
|
|
39
|
+
:captions-expanded.sync="captionsExpandedState"
|
|
40
|
+
:captions-hide-expand="captionsHideExpand"
|
|
41
|
+
:captions-paragraph-view="captionsParagraphView"
|
|
42
|
+
:captions-hide-paragraph-view="captionsHideParagraphView"
|
|
43
|
+
:captions-autoscroll="captionsAutoscroll"
|
|
44
|
+
:captions-hide-autoscroll="captionsHideAutoscroll"
|
|
45
|
+
:captions-visible.sync="captionsVisibleState"
|
|
21
46
|
@load="$emit('load', $event)"
|
|
22
47
|
@ended="onEnded"
|
|
23
48
|
@loadeddata="onLoadeddata"
|
|
@@ -37,19 +62,29 @@
|
|
|
37
62
|
@abort="$emit('abort', $event)"
|
|
38
63
|
@mouseover="$emit('mouseover', $event)"
|
|
39
64
|
@mouseout="$emit('mouseout', $event)"
|
|
65
|
+
@update:captions-expanded="
|
|
66
|
+
$emit('update:captions-expanded', $event)
|
|
67
|
+
"
|
|
68
|
+
@update:captions-paragraph-view="
|
|
69
|
+
$emit('update:captions-paragraph-view', $event)
|
|
70
|
+
"
|
|
71
|
+
@update:captions-autoscroll="
|
|
72
|
+
$emit('update:captions-autoscroll', $event)
|
|
73
|
+
"
|
|
40
74
|
@click:fullscreen="onFullscreen"
|
|
41
75
|
@click:pictureinpicture="onPictureInPicture"
|
|
42
76
|
@click:remoteplayback="onRemoteplayback"
|
|
43
77
|
@click:captions-expand="onClickCaptionsExpand"
|
|
44
|
-
@click:captions-paragraph="onClickCaptionsParagraph"
|
|
78
|
+
@click:captions-paragraph-view="onClickCaptionsParagraph"
|
|
79
|
+
@click:captions-autoscroll="onClickCaptionsAutoscroll"
|
|
80
|
+
@click:captions-close="onClickCaptionsClose"
|
|
81
|
+
@focusin="onFocusin"
|
|
82
|
+
@focusout="onFocusout"
|
|
45
83
|
></Html5Player>
|
|
46
84
|
</v-col>
|
|
47
85
|
|
|
48
86
|
<!-- Playlist stuff -->
|
|
49
|
-
<v-col
|
|
50
|
-
v-if="playlistmenu && playlist.length > 1"
|
|
51
|
-
:cols="playlistCols"
|
|
52
|
-
>
|
|
87
|
+
<v-col v-if="showPlaylist" cols="12" class="mt-0 pt-0">
|
|
53
88
|
<PlaylistMenu
|
|
54
89
|
v-model="sourceIndex"
|
|
55
90
|
:language="language"
|
|
@@ -63,6 +98,7 @@
|
|
|
63
98
|
</template>
|
|
64
99
|
|
|
65
100
|
<script>
|
|
101
|
+
import { t } from '../i18n/i18n'
|
|
66
102
|
import YoutubePlayer from './Media/YoutubePlayer.vue'
|
|
67
103
|
import Html5Player from './Media/Html5Player.vue'
|
|
68
104
|
import PlaylistMenu from './Media/PlaylistMenu.vue'
|
|
@@ -90,7 +126,7 @@ export default {
|
|
|
90
126
|
return []
|
|
91
127
|
},
|
|
92
128
|
},
|
|
93
|
-
type: { type: String, required: false, default: '
|
|
129
|
+
type: { type: String, required: false, default: 'auto' }, // Allowed auto|video|audio. In audio mode the player has a max-height of 40px
|
|
94
130
|
autoplay: { type: Boolean, required: false, default: false }, // Autoplay on load. It's in the spec but DON'T USE THIS
|
|
95
131
|
autopictureinpicture: {
|
|
96
132
|
type: Boolean,
|
|
@@ -123,6 +159,42 @@ export default {
|
|
|
123
159
|
poster: { type: String, required: false, default: '' }, // Overridden with the playlist.poster if one is set there
|
|
124
160
|
preload: { type: String, required: false, default: '' },
|
|
125
161
|
captionsmenu: { type: Boolean, required: false, default: true }, // Show the captions below the video
|
|
162
|
+
captionsExpanded: {
|
|
163
|
+
type: Boolean,
|
|
164
|
+
required: false,
|
|
165
|
+
default: undefined,
|
|
166
|
+
},
|
|
167
|
+
captionsHideExpand: { type: Boolean, required: false, default: true },
|
|
168
|
+
captionsParagraphView: {
|
|
169
|
+
type: Boolean,
|
|
170
|
+
required: false,
|
|
171
|
+
default: undefined,
|
|
172
|
+
},
|
|
173
|
+
captionsHideParagraphView: {
|
|
174
|
+
type: Boolean,
|
|
175
|
+
required: false,
|
|
176
|
+
default: false,
|
|
177
|
+
},
|
|
178
|
+
captionsAutoscroll: {
|
|
179
|
+
type: Boolean,
|
|
180
|
+
required: false,
|
|
181
|
+
default: undefined,
|
|
182
|
+
},
|
|
183
|
+
captionsHideAutoscroll: {
|
|
184
|
+
type: Boolean,
|
|
185
|
+
required: false,
|
|
186
|
+
default: false,
|
|
187
|
+
},
|
|
188
|
+
captionsHideClose: {
|
|
189
|
+
type: Boolean,
|
|
190
|
+
required: false,
|
|
191
|
+
default: false,
|
|
192
|
+
},
|
|
193
|
+
captionsVisible: {
|
|
194
|
+
type: Boolean,
|
|
195
|
+
required: false,
|
|
196
|
+
default: true,
|
|
197
|
+
},
|
|
126
198
|
playlistmenu: { type: Boolean, required: false, default: true }, // Show the playlist menu if there's multiple videos
|
|
127
199
|
playlistautoadvance: { type: Boolean, required: false, default: true }, // Play the next source group
|
|
128
200
|
playbackrates: {
|
|
@@ -133,6 +205,37 @@ export default {
|
|
|
133
205
|
},
|
|
134
206
|
}, // Default playback speeds
|
|
135
207
|
},
|
|
208
|
+
emits: [
|
|
209
|
+
'load',
|
|
210
|
+
'loadeddata',
|
|
211
|
+
'loadedmetadata',
|
|
212
|
+
'play',
|
|
213
|
+
'pause',
|
|
214
|
+
'seeking',
|
|
215
|
+
'timeupdate',
|
|
216
|
+
'progress',
|
|
217
|
+
'canplay',
|
|
218
|
+
'waiting',
|
|
219
|
+
'canplaythrough',
|
|
220
|
+
'error',
|
|
221
|
+
'emptied',
|
|
222
|
+
'ratechange',
|
|
223
|
+
'stalled',
|
|
224
|
+
'abort',
|
|
225
|
+
'mouseover',
|
|
226
|
+
'mouseout',
|
|
227
|
+
'ended',
|
|
228
|
+
'click:pictureinpicture',
|
|
229
|
+
'click:fullscreen',
|
|
230
|
+
'click:captions-expand',
|
|
231
|
+
'click:captions-paragraph-view',
|
|
232
|
+
'click:captions-autoscroll',
|
|
233
|
+
'click:captions-close',
|
|
234
|
+
'update:captions-expanded',
|
|
235
|
+
'update:captions-paragraph-view',
|
|
236
|
+
'update:captions-autoscroll',
|
|
237
|
+
'update:captions-visible',
|
|
238
|
+
],
|
|
136
239
|
watch: {},
|
|
137
240
|
computed: {
|
|
138
241
|
player() {
|
|
@@ -180,17 +283,9 @@ export default {
|
|
|
180
283
|
},
|
|
181
284
|
playlistCols() {
|
|
182
285
|
// Captions collapsed, playlist will appear on the right
|
|
183
|
-
if (
|
|
184
|
-
!this.captionsExpanded &&
|
|
185
|
-
this.playlistmenu &&
|
|
186
|
-
this.playlist.length > 1
|
|
187
|
-
) {
|
|
286
|
+
if (!this.captionsExpandedState && this.showPlaylist) {
|
|
188
287
|
return 4
|
|
189
|
-
} else if (
|
|
190
|
-
this.captionsExpanded &&
|
|
191
|
-
this.playlistmenu &&
|
|
192
|
-
this.playlist.length > 1
|
|
193
|
-
) {
|
|
288
|
+
} else if (this.captionsExpandedState && this.showPlaylist) {
|
|
194
289
|
// Captions expanded, playlist will appear as a new row on the bottom of everything
|
|
195
290
|
return 12
|
|
196
291
|
} else {
|
|
@@ -198,21 +293,52 @@ export default {
|
|
|
198
293
|
}
|
|
199
294
|
},
|
|
200
295
|
playerCols() {
|
|
201
|
-
if (
|
|
202
|
-
this.captionsExpanded ||
|
|
203
|
-
!this.playlistmenu ||
|
|
204
|
-
this.playlist.length <= 1
|
|
205
|
-
) {
|
|
296
|
+
if (this.captionsExpandedState || !this.showPlaylist) {
|
|
206
297
|
return 12
|
|
207
298
|
} else {
|
|
208
299
|
return 8
|
|
209
300
|
}
|
|
210
301
|
},
|
|
302
|
+
captionsVisibleState: {
|
|
303
|
+
get() {
|
|
304
|
+
if (typeof this.captionsVisible !== 'undefined') {
|
|
305
|
+
return this.captionsVisible
|
|
306
|
+
} else {
|
|
307
|
+
return this.captions.visible
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
set(v) {
|
|
311
|
+
this.$emit('update:captions-visible', v)
|
|
312
|
+
this.captions.visible = v
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
captionsExpandedState: {
|
|
316
|
+
get() {
|
|
317
|
+
if (typeof this.captionsExpanded !== 'undefined') {
|
|
318
|
+
return this.captionsExpanded
|
|
319
|
+
} else {
|
|
320
|
+
return this.captions.expanded
|
|
321
|
+
}
|
|
322
|
+
},
|
|
323
|
+
set(v) {
|
|
324
|
+
this.$emit('update:captions-expanded', v)
|
|
325
|
+
this.captions.expanded = v
|
|
326
|
+
},
|
|
327
|
+
},
|
|
328
|
+
showPlaylist() {
|
|
329
|
+
return this.playlistmenu && this.playlist.length > 1
|
|
330
|
+
},
|
|
211
331
|
},
|
|
212
332
|
data() {
|
|
213
333
|
return {
|
|
334
|
+
t,
|
|
214
335
|
sourceIndex: 0,
|
|
215
|
-
|
|
336
|
+
captions: {
|
|
337
|
+
visible: true,
|
|
338
|
+
expanded: false,
|
|
339
|
+
},
|
|
340
|
+
mediaFocus: false,
|
|
341
|
+
keyListener: null,
|
|
216
342
|
}
|
|
217
343
|
},
|
|
218
344
|
methods: {
|
|
@@ -273,16 +399,25 @@ export default {
|
|
|
273
399
|
}
|
|
274
400
|
},
|
|
275
401
|
onClickCaptionsExpand(expanded) {
|
|
276
|
-
this.captionsExpanded = expanded
|
|
277
402
|
this.$emit('click:captions-expand', expanded)
|
|
278
403
|
},
|
|
279
404
|
onClickCaptionsParagraph(isParagraph) {
|
|
280
|
-
this.$emit('click:captions-paragraph', isParagraph)
|
|
405
|
+
this.$emit('click:captions-paragraph-view', isParagraph)
|
|
406
|
+
},
|
|
407
|
+
onClickCaptionsAutoscroll(autoscroll) {
|
|
408
|
+
this.$emit('click:captions-autoscroll', autoscroll)
|
|
409
|
+
},
|
|
410
|
+
onClickCaptionsClose() {
|
|
411
|
+
this.$emit('click:captions-close')
|
|
281
412
|
},
|
|
282
413
|
onPlaylistSelect(index) {
|
|
283
414
|
this.sourceIndex = parseInt(index)
|
|
284
|
-
|
|
285
|
-
|
|
415
|
+
|
|
416
|
+
// If we give a bad media type then the player won't resolve
|
|
417
|
+
if (this.player && typeof this.player.load !== 'undefined') {
|
|
418
|
+
this.player.load()
|
|
419
|
+
this.player.play()
|
|
420
|
+
}
|
|
286
421
|
},
|
|
287
422
|
parseSourceType(sources) {
|
|
288
423
|
const ytRegex =
|
|
@@ -304,6 +439,44 @@ export default {
|
|
|
304
439
|
return 'html5'
|
|
305
440
|
}
|
|
306
441
|
},
|
|
442
|
+
onFocusin() {
|
|
443
|
+
this.mediaFocus = true
|
|
444
|
+
if (this.player && this.player.$el) {
|
|
445
|
+
this.player.$el.addEventListener('keydown', this.onKeydown)
|
|
446
|
+
}
|
|
447
|
+
},
|
|
448
|
+
onFocusout() {
|
|
449
|
+
this.mediaFocus = false
|
|
450
|
+
if (this.player && this.player.$el) {
|
|
451
|
+
this.player.$el.removeEventListener('keydown', this.onKeydown)
|
|
452
|
+
}
|
|
453
|
+
},
|
|
454
|
+
onKeydown(e) {
|
|
455
|
+
// Only process keyboard events if the media is focused
|
|
456
|
+
// This is just in case the media lost focus but the event listener wasn't removed for some reason
|
|
457
|
+
if (this.mediaFocus) {
|
|
458
|
+
e.preventDefault()
|
|
459
|
+
const map = {
|
|
460
|
+
Space: { cb: this.player.playToggle, params: [e] },
|
|
461
|
+
KeyK: { cb: this.player.playToggle, params: [e] },
|
|
462
|
+
KeyM: { cb: this.player.muteToggle, params: [e] },
|
|
463
|
+
ArrowLeft: { cb: this.player.rewind, params: [5] },
|
|
464
|
+
ArrowRight: { cb: this.player.fastForward, params: [5] },
|
|
465
|
+
KeyJ: { cb: this.player.rewind, params: [10] },
|
|
466
|
+
KeyL: { cb: this.player.fastForward, params: [10] },
|
|
467
|
+
ArrowUp: { cb: this.player.volumeAdjust, params: [0.1] },
|
|
468
|
+
ArrowDown: { cb: this.player.volumeAdjust, params: [-0.1] },
|
|
469
|
+
KeyF: { cb: this.player.fullscreenToggle, params: [e] },
|
|
470
|
+
KeyC: { cb: this.player.CCToggle, params: [e] },
|
|
471
|
+
}
|
|
472
|
+
if (
|
|
473
|
+
typeof map[e.code] !== 'undefined' &&
|
|
474
|
+
typeof map[e.code].cb !== 'undefined'
|
|
475
|
+
) {
|
|
476
|
+
map[e.code].cb(...map[e.code].params)
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
},
|
|
307
480
|
},
|
|
308
481
|
beforeCreate() {},
|
|
309
482
|
beforeMount() {},
|
|
@@ -311,3 +484,16 @@ export default {
|
|
|
311
484
|
beforeDestroy() {},
|
|
312
485
|
}
|
|
313
486
|
</script>
|
|
487
|
+
|
|
488
|
+
<style scoped>
|
|
489
|
+
.player-skeleton {
|
|
490
|
+
aspect-ratio: 16 / 9;
|
|
491
|
+
overflow: hidden;
|
|
492
|
+
}
|
|
493
|
+
.player-skeleton--no-source {
|
|
494
|
+
height: 0px;
|
|
495
|
+
position: relative;
|
|
496
|
+
top: 100px;
|
|
497
|
+
text-align: center;
|
|
498
|
+
}
|
|
499
|
+
</style>
|
package/src/i18n/en-US.js
CHANGED
|
@@ -10,6 +10,7 @@ export default {
|
|
|
10
10
|
next: 'Play next item in playlist',
|
|
11
11
|
},
|
|
12
12
|
player: {
|
|
13
|
+
not_configured: 'Media not configured',
|
|
13
14
|
playback_speed: 'Playback Speed',
|
|
14
15
|
playback_decrease: 'Decrease playback speed',
|
|
15
16
|
playback_increase: 'Increase playback speed',
|
|
@@ -27,9 +28,17 @@ export default {
|
|
|
27
28
|
no_support: "Sorry, your browser doesn't support embedded videos.",
|
|
28
29
|
},
|
|
29
30
|
captions: {
|
|
31
|
+
show_transcript: 'Show Transcript',
|
|
32
|
+
close: 'Close',
|
|
33
|
+
search: 'Search',
|
|
34
|
+
none_found: "No captions found for '{0}'",
|
|
30
35
|
expand: 'Expand',
|
|
31
36
|
collapse: 'Collapse',
|
|
32
37
|
view_as_paragraph: 'View as paragraph',
|
|
33
38
|
view_as_captions: 'View as captions',
|
|
39
|
+
enable_autoscroll: 'Enable auto-scroll for transcript as media plays',
|
|
40
|
+
disable_autoscroll: 'Disable auto-scroll for transcript as media plays',
|
|
41
|
+
autoscroll_enabled: 'Autoscroll transcript enabled',
|
|
42
|
+
autoscroll_disabled: 'Autoscroll transcript disabled',
|
|
34
43
|
},
|
|
35
44
|
}
|
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
|
+
not_configured: 'Medios no configurados',
|
|
13
14
|
playback_speed: 'Velocidad de reproducción',
|
|
14
15
|
playback_decrease: 'Disminuir la velocidad de reproducción',
|
|
15
16
|
playback_increase: 'Aumentar la velocidad de reproducción',
|
|
@@ -27,9 +28,20 @@ export default {
|
|
|
27
28
|
no_support: 'Lo sentimos, su navegador no admite vídeos incrustados. ',
|
|
28
29
|
},
|
|
29
30
|
captions: {
|
|
31
|
+
show_transcript: 'Mostrar transcripción',
|
|
32
|
+
close: 'Cerca',
|
|
33
|
+
search: 'Buscar',
|
|
34
|
+
none_found: "No se encontraron subtítulos para '{0}'",
|
|
30
35
|
expand: 'Expandir',
|
|
31
36
|
collapse: 'Colapsar',
|
|
32
37
|
view_as_paragraph: 'Ver como párrafo',
|
|
33
38
|
view_as_captions: 'Ver como subtítulos',
|
|
39
|
+
enable_autoscroll:
|
|
40
|
+
'Habilite el desplazamiento automático para la transcripción mientras se reproducen los medios',
|
|
41
|
+
disable_autoscroll:
|
|
42
|
+
'Deshabilite el desplazamiento automático para la transcripción mientras se reproducen los medios',
|
|
43
|
+
autoscroll_enabled:
|
|
44
|
+
'Transcripción con desplazamiento automático habilitada',
|
|
45
|
+
autoscroll_disabled: 'Transcripción automática deshabilitado',
|
|
34
46
|
},
|
|
35
47
|
}
|
package/src/i18n/i18n.js
CHANGED
|
@@ -18,7 +18,7 @@ function get(obj, path) {
|
|
|
18
18
|
return get(obj[parts[0]], parts.slice(1).join('.'))
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const t = (lang, path) => {
|
|
21
|
+
const t = (lang, path, replacements = []) => {
|
|
22
22
|
if (typeof i18n[lang] === 'undefined') {
|
|
23
23
|
console.warn(
|
|
24
24
|
'[VuetifyPlayer] No support for locale ' +
|
|
@@ -35,6 +35,11 @@ const t = (lang, path) => {
|
|
|
35
35
|
console.warn(
|
|
36
36
|
'[VuetifyPlayer] localization path ' + path + ' does not exist'
|
|
37
37
|
)
|
|
38
|
+
} else {
|
|
39
|
+
// Apply replacements
|
|
40
|
+
for (const key in replacements) {
|
|
41
|
+
localized = localized.replaceAll('{' + key + '}', replacements[key])
|
|
42
|
+
}
|
|
38
43
|
}
|
|
39
44
|
|
|
40
45
|
return localized
|
package/src/i18n/sv-SE.js
CHANGED
|
@@ -10,6 +10,7 @@ export default {
|
|
|
10
10
|
next: 'Spela nästa',
|
|
11
11
|
},
|
|
12
12
|
player: {
|
|
13
|
+
not_configured: 'Media inte konfigurerad',
|
|
13
14
|
playback_speed: 'Uppspelningshastighet',
|
|
14
15
|
playback_decrease: 'Minska uppspelningshastigheten',
|
|
15
16
|
playback_increase: 'Öka uppspelningshastigheten',
|
|
@@ -27,9 +28,19 @@ export default {
|
|
|
27
28
|
no_support: 'Tyvärr, din webbläsare stöder inte inbäddade videor.',
|
|
28
29
|
},
|
|
29
30
|
captions: {
|
|
31
|
+
show_transcript: 'Visa avskrift',
|
|
32
|
+
close: 'Stäng',
|
|
33
|
+
search: 'Sök',
|
|
34
|
+
none_found: "Inga bildtexter hittades för '{0}'",
|
|
30
35
|
expand: 'Bygga ut',
|
|
31
36
|
collapse: 'Kollaps',
|
|
32
37
|
view_as_paragraph: 'Visa som stycke',
|
|
33
38
|
view_as_captions: 'Visa som bildtexter',
|
|
39
|
+
enable_autoscroll:
|
|
40
|
+
'Aktivera automatisk rullning för transkription när media spelas upp',
|
|
41
|
+
disable_autoscroll:
|
|
42
|
+
'Inaktivera automatisk rullning för transkribering när media spelas upp',
|
|
43
|
+
autoscroll_enabled: 'Autoscroll-transkription aktiverad',
|
|
44
|
+
autoscroll_disabled: 'Autoscroll-transkription inaktiverad',
|
|
34
45
|
},
|
|
35
46
|
}
|