@mindedge/vuetify-player 0.3.1 → 0.4.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.
- package/README.md +90 -50
- package/package.json +1 -1
- package/src/components/Media/CaptionsMenu.vue +269 -99
- package/src/components/Media/Html5Player.vue +454 -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 +218 -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
|
@@ -1,71 +1,153 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<v-card>
|
|
3
|
-
<v-card-actions class="
|
|
4
|
-
<
|
|
5
|
-
<
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
2
|
+
<v-card v-if="visibleState" :elevation="elevation">
|
|
3
|
+
<v-card-actions class="d-flex flex-wrap flex-row-reverse mb-0 pb-0">
|
|
4
|
+
<div class="d-flex ml-auto">
|
|
5
|
+
<v-tooltip v-if="!hideAutoscroll" top>
|
|
6
|
+
<template #activator="{ on, attrs }">
|
|
7
|
+
<div v-bind="attrs" v-on="on">
|
|
8
|
+
<v-switch
|
|
9
|
+
:input-value="autoscrollState"
|
|
10
|
+
color="primary"
|
|
11
|
+
text
|
|
12
|
+
class="d-flex align-self-center"
|
|
13
|
+
@click="onClickToggleAutoscroll"
|
|
14
|
+
>
|
|
15
|
+
<template #label>
|
|
16
|
+
<div v-if="autoscrollState">
|
|
17
|
+
<v-icon> mdi-lock-open-variant </v-icon>
|
|
18
|
+
<span class="sr-only">
|
|
19
|
+
{{
|
|
20
|
+
t(
|
|
21
|
+
language,
|
|
22
|
+
'captions.autoscroll_enabled'
|
|
23
|
+
)
|
|
24
|
+
}}
|
|
25
|
+
</span>
|
|
26
|
+
</div>
|
|
27
|
+
<div v-else>
|
|
28
|
+
<v-icon>mdi-arrow-vertical-lock</v-icon>
|
|
29
|
+
<span class="sr-only">
|
|
30
|
+
{{
|
|
31
|
+
t(
|
|
32
|
+
language,
|
|
33
|
+
'captions.autoscroll_disabled'
|
|
34
|
+
)
|
|
35
|
+
}}
|
|
36
|
+
</span>
|
|
37
|
+
</div>
|
|
38
|
+
</template>
|
|
39
|
+
</v-switch>
|
|
40
|
+
</div>
|
|
41
|
+
</template>
|
|
42
|
+
<span>{{
|
|
43
|
+
autoscrollState
|
|
44
|
+
? t(language, 'captions.disable_autoscroll')
|
|
45
|
+
: t(language, 'captions.enable_autoscroll')
|
|
46
|
+
}}</span>
|
|
47
|
+
</v-tooltip>
|
|
48
|
+
|
|
49
|
+
<v-tooltip v-if="!hideParagraphView" top>
|
|
50
|
+
<template #activator="{ on, attrs }">
|
|
51
|
+
<v-btn
|
|
52
|
+
color="primary"
|
|
53
|
+
text
|
|
54
|
+
class="d-flex align-self-center"
|
|
55
|
+
v-bind="attrs"
|
|
56
|
+
v-on="on"
|
|
57
|
+
@click="onClickToggleParagraphView"
|
|
58
|
+
>
|
|
59
|
+
<v-icon>{{
|
|
60
|
+
paragraphViewState
|
|
61
|
+
? 'mdi-closed-caption-outline'
|
|
62
|
+
: 'mdi-text-box-outline'
|
|
63
|
+
}}</v-icon>
|
|
64
|
+
<span class="sr-only">{{
|
|
65
|
+
paragraphViewState
|
|
66
|
+
? t(language, 'captions.view_as_captions')
|
|
67
|
+
: t(language, 'captions.view_as_paragraph')
|
|
68
|
+
}}</span>
|
|
69
|
+
</v-btn></template
|
|
12
70
|
>
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
71
|
+
<span>{{
|
|
72
|
+
paragraphViewState
|
|
73
|
+
? t(language, 'captions.view_as_captions')
|
|
74
|
+
: t(language, 'captions.view_as_paragraph')
|
|
75
|
+
}}</span>
|
|
76
|
+
</v-tooltip>
|
|
77
|
+
|
|
78
|
+
<v-tooltip v-if="!hideExpand" top>
|
|
79
|
+
<template #activator="{ on, attrs }">
|
|
80
|
+
<v-btn
|
|
81
|
+
color="primary"
|
|
82
|
+
text
|
|
83
|
+
class="d-flex align-self-center"
|
|
84
|
+
v-bind="attrs"
|
|
85
|
+
v-on="on"
|
|
86
|
+
@click="onClickToggleExpand"
|
|
87
|
+
>
|
|
88
|
+
<v-icon>{{
|
|
89
|
+
expandedState
|
|
90
|
+
? 'mdi-arrow-collapse'
|
|
91
|
+
: 'mdi-arrow-expand'
|
|
92
|
+
}}</v-icon>
|
|
93
|
+
<span class="sr-only">{{
|
|
94
|
+
expandedState
|
|
95
|
+
? t(language, 'captions.collapse')
|
|
96
|
+
: t(language, 'captions.expand')
|
|
97
|
+
}}</span>
|
|
98
|
+
</v-btn></template
|
|
99
|
+
>
|
|
100
|
+
<span>{{
|
|
101
|
+
expandedState
|
|
102
|
+
? t(language, 'captions.collapse')
|
|
103
|
+
: t(language, 'captions.expand')
|
|
104
|
+
}}</span>
|
|
105
|
+
</v-tooltip>
|
|
106
|
+
|
|
107
|
+
<v-tooltip v-if="!hideClose" top>
|
|
108
|
+
<template #activator="{ on, attrs }">
|
|
109
|
+
<v-btn
|
|
110
|
+
color="primary"
|
|
111
|
+
text
|
|
112
|
+
class="d-flex align-self-center"
|
|
113
|
+
v-bind="attrs"
|
|
114
|
+
v-on="on"
|
|
115
|
+
@click="onClickClose"
|
|
116
|
+
>
|
|
117
|
+
<v-icon>mdi-close</v-icon>
|
|
118
|
+
</v-btn></template
|
|
39
119
|
>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
120
|
+
<span>{{ t(language, 'captions.close') }}</span>
|
|
121
|
+
</v-tooltip>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<div class="d-flex flex-grow-1">
|
|
125
|
+
<v-text-field
|
|
126
|
+
id="captions-search"
|
|
127
|
+
v-model="search"
|
|
128
|
+
:label="t(language, 'captions.search')"
|
|
129
|
+
append-icon="mdi-magnify"
|
|
130
|
+
class="ml-2 mr-12"
|
|
131
|
+
clearable
|
|
49
132
|
>
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
? t(language, 'captions.collapse')
|
|
53
|
-
: t(language, 'captions.expand')
|
|
54
|
-
}}</span>
|
|
55
|
-
</v-tooltip>
|
|
133
|
+
</v-text-field>
|
|
134
|
+
</div>
|
|
56
135
|
</v-card-actions>
|
|
57
|
-
<v-card-text>
|
|
136
|
+
<v-card-text class="mt-0 pt-0">
|
|
137
|
+
<span v-if="search && !filteredCues.length" class="caption">
|
|
138
|
+
{{ t(language, 'captions.none_found', [search]) }}
|
|
139
|
+
</span>
|
|
58
140
|
<v-list ref="captionList" :class="captionsList">
|
|
59
141
|
<v-list-item-group v-model="captionIndex">
|
|
60
142
|
<v-list-item
|
|
61
143
|
ref="captionItems"
|
|
62
|
-
v-for="(cue, index) in
|
|
144
|
+
v-for="(cue, index) in filteredCues"
|
|
63
145
|
:key="index"
|
|
64
|
-
:two-line="
|
|
146
|
+
:two-line="expandedState"
|
|
65
147
|
@click="onCueClick(cue.startTime)"
|
|
66
148
|
>
|
|
67
|
-
<template v-if="!
|
|
68
|
-
<v-list-item-icon v-if="!
|
|
149
|
+
<template v-if="!expandedState">
|
|
150
|
+
<v-list-item-icon v-if="!paragraphViewState">
|
|
69
151
|
<v-icon
|
|
70
152
|
>{{
|
|
71
153
|
index === captionIndex
|
|
@@ -80,7 +162,7 @@
|
|
|
80
162
|
class="caption-text"
|
|
81
163
|
></v-list-item-title>
|
|
82
164
|
</v-list-item-content>
|
|
83
|
-
<v-list-item-action v-if="!
|
|
165
|
+
<v-list-item-action v-if="!paragraphViewState">
|
|
84
166
|
<span aria-hidden="true">
|
|
85
167
|
{{
|
|
86
168
|
filters.playerShortDuration(
|
|
@@ -94,13 +176,15 @@
|
|
|
94
176
|
</span>
|
|
95
177
|
</v-list-item-action>
|
|
96
178
|
</template>
|
|
97
|
-
<template v-if="
|
|
179
|
+
<template v-if="expandedState">
|
|
98
180
|
<v-list-item-content>
|
|
99
181
|
<v-list-item-title
|
|
100
182
|
v-html="cue.rawText || cue.text"
|
|
101
183
|
class="caption-text"
|
|
102
184
|
></v-list-item-title>
|
|
103
|
-
<v-list-item-subtitle
|
|
185
|
+
<v-list-item-subtitle
|
|
186
|
+
v-if="!paragraphViewState"
|
|
187
|
+
>
|
|
104
188
|
<v-icon
|
|
105
189
|
>{{
|
|
106
190
|
index === captionIndex
|
|
@@ -139,10 +223,43 @@ export default {
|
|
|
139
223
|
props: {
|
|
140
224
|
value: { type: [Object, Array], required: true },
|
|
141
225
|
language: { type: String, required: false, default: 'en-US' },
|
|
226
|
+
expanded: { type: Boolean, required: false, default: undefined },
|
|
227
|
+
hideExpand: { type: Boolean, required: false, default: true },
|
|
228
|
+
paragraphView: { type: Boolean, required: false, default: undefined },
|
|
229
|
+
hideParagraphView: { type: Boolean, required: false, default: false },
|
|
230
|
+
autoscroll: { type: Boolean, required: false, default: undefined },
|
|
231
|
+
hideAutoscroll: { type: Boolean, required: false, default: false },
|
|
232
|
+
visible: { type: Boolean, required: false, default: undefined },
|
|
233
|
+
hideClose: { type: Boolean, required: false, default: false },
|
|
234
|
+
elevation: { type: [Number, String], required: false, default: 2 },
|
|
142
235
|
},
|
|
236
|
+
emits: [
|
|
237
|
+
'click:cue',
|
|
238
|
+
'click:expand',
|
|
239
|
+
'click:paragraph',
|
|
240
|
+
'click:autoscroll',
|
|
241
|
+
'click:close',
|
|
242
|
+
'update:expanded',
|
|
243
|
+
'update:paragraph-view',
|
|
244
|
+
'update:autoscroll',
|
|
245
|
+
'update:visible',
|
|
246
|
+
],
|
|
143
247
|
computed: {
|
|
248
|
+
filteredCues() {
|
|
249
|
+
// Cues are an object with keys of 0,1,2,3...
|
|
250
|
+
const cues = Object.values(this.cues)
|
|
251
|
+
if (this.search !== '') {
|
|
252
|
+
return cues.filter((c) =>
|
|
253
|
+
c.text
|
|
254
|
+
.toLowerCase()
|
|
255
|
+
.includes((this.search || '').toLowerCase())
|
|
256
|
+
)
|
|
257
|
+
} else {
|
|
258
|
+
return cues
|
|
259
|
+
}
|
|
260
|
+
},
|
|
144
261
|
captionsList() {
|
|
145
|
-
return !this.
|
|
262
|
+
return !this.expandedState
|
|
146
263
|
? 'captions-list captions-list--state-collapsed'
|
|
147
264
|
: 'captions-list captions-list--state-expanded'
|
|
148
265
|
},
|
|
@@ -150,12 +267,12 @@ export default {
|
|
|
150
267
|
// Normal cues view
|
|
151
268
|
if (
|
|
152
269
|
typeof this.captions.cues !== 'undefined' &&
|
|
153
|
-
!this.
|
|
270
|
+
!this.paragraphViewState
|
|
154
271
|
) {
|
|
155
272
|
return this.captions.cues
|
|
156
273
|
} else if (
|
|
157
274
|
typeof this.captions.cues !== 'undefined' &&
|
|
158
|
-
this.
|
|
275
|
+
this.paragraphViewState
|
|
159
276
|
) {
|
|
160
277
|
// Paragraph view
|
|
161
278
|
let cues = this.captions.cues
|
|
@@ -182,20 +299,23 @@ export default {
|
|
|
182
299
|
}
|
|
183
300
|
|
|
184
301
|
// Create a new paragraph every 3 sentences
|
|
185
|
-
if (
|
|
186
|
-
|
|
302
|
+
if (
|
|
303
|
+
puncuationCount > 3 &&
|
|
304
|
+
typeof cues[i + 1] !== 'undefined'
|
|
305
|
+
) {
|
|
306
|
+
// Find the first puncuation and include it in the slice so the NEXT paragraph doesn't start mid sentence
|
|
187
307
|
const breakIndex = cues[i].text.search(/[.?!]/) + 1
|
|
188
308
|
|
|
189
309
|
// Append the first part to the previous paragraph so it ends on a period
|
|
190
310
|
paragraphs[paragraphs.length - 1].text +=
|
|
191
|
-
' ' + cues[i].text.slice(0, breakIndex)
|
|
311
|
+
' ' + cues[i].text.slice(0, breakIndex).trim()
|
|
192
312
|
|
|
193
313
|
// Use `new VTTCue` to break the reference. Otherwise the below appends will duplicate text
|
|
194
314
|
// Also grab from the breakIndex afterwards to get the potential next sentence
|
|
195
315
|
paragraphs.push(
|
|
196
316
|
new VTTCue(
|
|
197
|
-
cues[i].startTime,
|
|
198
|
-
cues[i].endTime,
|
|
317
|
+
cues[i + 1].startTime,
|
|
318
|
+
cues[i + 1].endTime,
|
|
199
319
|
cues[i].text.slice(breakIndex).trim()
|
|
200
320
|
)
|
|
201
321
|
)
|
|
@@ -215,15 +335,70 @@ export default {
|
|
|
215
335
|
return []
|
|
216
336
|
}
|
|
217
337
|
},
|
|
338
|
+
expandedState: {
|
|
339
|
+
get() {
|
|
340
|
+
if (typeof this.expanded !== 'undefined') {
|
|
341
|
+
return this.expanded
|
|
342
|
+
} else {
|
|
343
|
+
return this.localExpanded
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
set(v) {
|
|
347
|
+
this.localExpanded = v
|
|
348
|
+
this.$emit('update:expanded', v)
|
|
349
|
+
},
|
|
350
|
+
},
|
|
351
|
+
paragraphViewState: {
|
|
352
|
+
get() {
|
|
353
|
+
if (typeof this.paragraphView !== 'undefined') {
|
|
354
|
+
return this.paragraphView
|
|
355
|
+
} else {
|
|
356
|
+
return this.localParagraphView
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
set(v) {
|
|
360
|
+
this.$emit('update:paragraph-view', v)
|
|
361
|
+
this.localParagraphView = v
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
autoscrollState: {
|
|
365
|
+
get() {
|
|
366
|
+
if (typeof this.autoscroll !== 'undefined') {
|
|
367
|
+
return this.autoscroll
|
|
368
|
+
} else {
|
|
369
|
+
return this.localAutoscroll
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
set(v) {
|
|
373
|
+
this.$emit('update:autoscroll', v)
|
|
374
|
+
this.localAutoscroll = v
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
visibleState: {
|
|
378
|
+
get() {
|
|
379
|
+
if (typeof this.visible !== 'undefined') {
|
|
380
|
+
return this.visible
|
|
381
|
+
} else {
|
|
382
|
+
return this.localVisible
|
|
383
|
+
}
|
|
384
|
+
},
|
|
385
|
+
set(v) {
|
|
386
|
+
this.$emit('update:visible', v)
|
|
387
|
+
this.localVisible = v
|
|
388
|
+
},
|
|
389
|
+
},
|
|
218
390
|
},
|
|
219
391
|
data() {
|
|
220
392
|
return {
|
|
221
393
|
t,
|
|
222
394
|
filters,
|
|
395
|
+
search: '',
|
|
223
396
|
captions: {},
|
|
224
397
|
captionIndex: 0,
|
|
225
|
-
|
|
226
|
-
|
|
398
|
+
localExpanded: false,
|
|
399
|
+
localParagraphView: false,
|
|
400
|
+
localAutoscroll: true,
|
|
401
|
+
localVisible: true,
|
|
227
402
|
}
|
|
228
403
|
},
|
|
229
404
|
watch: {
|
|
@@ -235,6 +410,11 @@ export default {
|
|
|
235
410
|
},
|
|
236
411
|
},
|
|
237
412
|
},
|
|
413
|
+
|
|
414
|
+
mounted() {
|
|
415
|
+
this.captions = this.value
|
|
416
|
+
this.captionIndex = this.currentCue(this.captions)
|
|
417
|
+
},
|
|
238
418
|
methods: {
|
|
239
419
|
currentCue(captions) {
|
|
240
420
|
let currentIndex = 0
|
|
@@ -242,10 +422,13 @@ export default {
|
|
|
242
422
|
if (
|
|
243
423
|
typeof captions.cues !== 'undefined' &&
|
|
244
424
|
typeof captions.activeCues !== 'undefined' &&
|
|
245
|
-
captions.activeCues.length
|
|
425
|
+
captions.activeCues.length &&
|
|
426
|
+
this.filteredCues.length
|
|
246
427
|
) {
|
|
247
|
-
|
|
248
|
-
|
|
428
|
+
currentIndex = -1
|
|
429
|
+
// Loop over the filtered cues and see if we can find the index
|
|
430
|
+
for (let i = 0; i < this.filteredCues.length; i++) {
|
|
431
|
+
const cue = this.filteredCues[i]
|
|
249
432
|
if (captions.activeCues[0].startTime === cue.startTime) {
|
|
250
433
|
currentIndex = i
|
|
251
434
|
}
|
|
@@ -258,6 +441,7 @@ export default {
|
|
|
258
441
|
// If the captions ref and index is available and the list ref is available
|
|
259
442
|
// Auto-scroll the list to the current caption
|
|
260
443
|
if (
|
|
444
|
+
this.autoscrollState &&
|
|
261
445
|
this.$refs.captionItems &&
|
|
262
446
|
this.$refs.captionItems[currentIndex] &&
|
|
263
447
|
this.$refs.captionItems[currentIndex].$el &&
|
|
@@ -274,17 +458,21 @@ export default {
|
|
|
274
458
|
this.$emit('click:cue', time)
|
|
275
459
|
},
|
|
276
460
|
onClickToggleExpand() {
|
|
277
|
-
this.
|
|
278
|
-
this.$emit('click:expand', this.
|
|
461
|
+
this.expandedState = !this.expandedState
|
|
462
|
+
this.$emit('click:expand', this.expandedState)
|
|
279
463
|
},
|
|
280
464
|
onClickToggleParagraphView() {
|
|
281
|
-
this.
|
|
282
|
-
this.$emit('click:paragraph', this.
|
|
465
|
+
this.paragraphViewState = !this.paragraphViewState
|
|
466
|
+
this.$emit('click:paragraph-view', this.paragraphViewState)
|
|
467
|
+
},
|
|
468
|
+
onClickToggleAutoscroll() {
|
|
469
|
+
this.autoscrollState = !this.autoscrollState
|
|
470
|
+
this.$emit('click:autoscroll', !this.autoscroll)
|
|
471
|
+
},
|
|
472
|
+
onClickClose() {
|
|
473
|
+
this.visibleState = false
|
|
474
|
+
this.$emit('click:close')
|
|
283
475
|
},
|
|
284
|
-
},
|
|
285
|
-
mounted() {
|
|
286
|
-
this.captions = this.value
|
|
287
|
-
this.captionIndex = this.currentCue(this.captions)
|
|
288
476
|
},
|
|
289
477
|
}
|
|
290
478
|
</script>
|
|
@@ -294,28 +482,10 @@ export default {
|
|
|
294
482
|
overflow-y: scroll;
|
|
295
483
|
}
|
|
296
484
|
.captions-list--state-collapsed {
|
|
297
|
-
max-height:
|
|
298
|
-
/* Fade the top/bottom 20% effect. The "red" mask is so the scrollbar doesn't get this effect*/
|
|
299
|
-
mask: linear-gradient(90deg, rgba(255, 0, 0, 0) 98%, rgba(255, 0, 0, 1) 98%),
|
|
300
|
-
linear-gradient(
|
|
301
|
-
0deg,
|
|
302
|
-
rgba(0, 0, 0, 0) 0%,
|
|
303
|
-
rgba(0, 0, 0, 1) 20%,
|
|
304
|
-
rgba(0, 0, 0, 1) 80%,
|
|
305
|
-
rgba(0, 0, 0, 0) 100%
|
|
306
|
-
);
|
|
485
|
+
max-height: 15em;
|
|
307
486
|
}
|
|
308
487
|
.captions-list--state-expanded {
|
|
309
488
|
aspect-ratio: 16 / 9;
|
|
310
|
-
/* Fade the top/bottom 20% effect. The "red" mask is so the scrollbar doesn't get this effect*/
|
|
311
|
-
mask: linear-gradient(90deg, rgba(255, 0, 0, 0) 98%, rgba(255, 0, 0, 1) 98%),
|
|
312
|
-
linear-gradient(
|
|
313
|
-
0deg,
|
|
314
|
-
rgba(0, 0, 0, 0) 0%,
|
|
315
|
-
rgba(0, 0, 0, 1) 5%,
|
|
316
|
-
rgba(0, 0, 0, 1) 95%,
|
|
317
|
-
rgba(0, 0, 0, 0) 100%
|
|
318
|
-
);
|
|
319
489
|
}
|
|
320
490
|
.caption-text {
|
|
321
491
|
overflow: visible;
|