@mindedge/vuetify-player 0.4.1 → 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 CHANGED
@@ -217,6 +217,8 @@ See [Full media `src` structure for where the ads array is placed](#full-media-s
217
217
  | `playlistmenu` | `Boolean` | true \| false | true | Show the playlist menu if there's multiple videos |
218
218
  | `playlistautoadvance` | `Boolean` | true \| false | true | Play the next source group |
219
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. |
220
222
  | `captions-expanded` | `Boolean` | true \| false | undefined | Supports `.sync`. The initial state of the captions transcript being expanded. |
221
223
  | `captions-hide-expand` | `Boolean` | true \| false | true | Show / allow the captions transcript expand button for users |
222
224
  | `captions-paragraph-view` | `Boolean` | true \| false | undefined | Supports `.sync`. The initial state of the captions transcript paragraph view |
@@ -258,6 +260,8 @@ See [Full media `src` structure for where the ads array is placed](#full-media-s
258
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 |
259
261
  | `click:captions-autoscroll` | `true` \| `false` | When the autoscroll captions button is clicked. true on autoscrolling otherwise false |
260
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 |
261
265
  | `update:captions-expanded` | `true` \| `false` | When the captions expand state is updated |
262
266
  | `update:captions-paragraph-view` | `true` \| `false` | When the captions paragraph-view state is updated |
263
267
  | `update:captions-autoscroll` | `true` \| `false` | When the captions autoscroll state is updated |
@@ -265,9 +269,10 @@ See [Full media `src` structure for where the ads array is placed](#full-media-s
265
269
 
266
270
  ## Supported `<VuetifyPlayer>` Slots
267
271
 
268
- | Slot name | Attributes | Description |
269
- | ----------- | ---------- | --------------------------------------------------------------------------- |
270
- | `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 |
271
276
 
272
277
  ## Captions
273
278
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindedge/vuetify-player",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "private": false,
5
5
  "description": "Accessible, localized, full featured media player with Vuetifyjs",
6
6
  "author": "Jacob Rogaishio",
@@ -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"
@@ -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="CCToggle"
222
+ @click="onClickCCToggle"
223
223
  >
224
224
  <v-icon>{{
225
- state.cc
225
+ ccState
226
226
  ? 'mdi-closed-caption'
227
227
  : 'mdi-closed-caption-outline'
228
228
  }}</v-icon>
@@ -531,6 +531,16 @@ export default {
531
531
  type: Object,
532
532
  required: true,
533
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
+ },
534
544
  captionsExpanded: {
535
545
  type: Boolean,
536
546
  required: false,
@@ -600,6 +610,7 @@ export default {
600
610
  'click:captions-autoscroll',
601
611
  'click:captions-close',
602
612
  'click:captions-cue',
613
+ 'update:cc',
603
614
  'update:captions-expanded',
604
615
  'update:captions-paragraph-view',
605
616
  'update:captions-autoscroll',
@@ -653,6 +664,19 @@ export default {
653
664
 
654
665
  return type
655
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
+ },
656
680
  captionsVisibleState: {
657
681
  get() {
658
682
  if (typeof this.captionsVisible !== 'undefined') {
@@ -731,6 +755,7 @@ export default {
731
755
  controlsDebounce: null,
732
756
  volume: 0.5, // default 50%
733
757
  muted: false,
758
+ unmuteVolume: 0, // The stored volume to return to the initial volume when unmuting
734
759
  paused: true,
735
760
  playbackRateIndex: 0,
736
761
  fullscreen: false,
@@ -782,6 +807,11 @@ export default {
782
807
  this.ads[ad.play_at_percent].complete = false
783
808
  }
784
809
  }
810
+
811
+ // Set the initial volume
812
+ if (typeof this.volume !== 'undefined') {
813
+ this.state.volume = this.volume
814
+ }
785
815
  },
786
816
  mounted() {
787
817
  if (
@@ -929,7 +959,7 @@ export default {
929
959
  *
930
960
  * @param String|null lang The lang to load. Eg en-US, sv-SE, etc. Pass nothing / null to turn off captions
931
961
  */
932
- onSelectTrack(lang = null) {
962
+ onSelectTrack(lang = null, mode = 'showing') {
933
963
  if (this.player.textTracks && this.player.textTracks.length > 0) {
934
964
  for (let i = 0; i < this.player.textTracks.length; i++) {
935
965
  // Disable all tracks by default
@@ -938,7 +968,7 @@ export default {
938
968
 
939
969
  if (this.player.textTracks[i].language === lang) {
940
970
  this.state.ccLang = lang
941
- this.player.textTracks[i].mode = 'showing'
971
+ this.player.textTracks[i].mode = mode
942
972
 
943
973
  this.setCues(this.player.textTracks[i])
944
974
 
@@ -980,13 +1010,16 @@ export default {
980
1010
  onMediaProgress(e) {
981
1011
  this.$emit('progress', e)
982
1012
  },
983
- CCToggle() {
984
- this.state.cc = !this.state.cc
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
985
1018
 
986
- if (this.state.cc) {
1019
+ if (state) {
987
1020
  this.onSelectTrack(this.state.ccLang)
988
1021
  } else {
989
- this.onSelectTrack()
1022
+ this.onSelectTrack(this.state.ccLang, 'hidden')
990
1023
  }
991
1024
  },
992
1025
  onClickReplay(e) {
@@ -1016,10 +1049,19 @@ export default {
1016
1049
  },
1017
1050
  muteToggle() {
1018
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
1019
1056
  this.state.muted = false
1020
1057
  this.player.muted = false
1021
1058
  this.$emit('volumechange', this.state.volume)
1022
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
+
1023
1065
  this.state.muted = true
1024
1066
  this.player.muted = true
1025
1067
  this.$emit('volumechange', 0)
@@ -1084,15 +1126,29 @@ export default {
1084
1126
  * The this.player.textTracks are now loaded
1085
1127
  */
1086
1128
  onLoadeddata(e) {
1129
+ let defaultTrackLang = null
1087
1130
  // Set the default captions since apparently the default attribute means nothing
1088
1131
  if (this.current.tracks && this.current.tracks.length > 0) {
1089
1132
  for (const track of this.current.tracks) {
1090
1133
  if (track.default) {
1134
+ defaultTrackLang = track.srclang
1091
1135
  this.onSelectTrack(track.srclang)
1092
1136
  }
1093
1137
  }
1094
1138
  }
1095
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
+
1096
1152
  this.$emit('loadeddata', e)
1097
1153
  },
1098
1154
  onLoadedmetadata(e) {
@@ -1112,6 +1168,13 @@ export default {
1112
1168
  } else if (value < 0) {
1113
1169
  value = 0
1114
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
+
1115
1178
  this.state.volume = value
1116
1179
  this.player.volume = value
1117
1180
  this.$emit('volumechange', value)
@@ -1130,10 +1193,23 @@ export default {
1130
1193
  this.player.currentTime = time
1131
1194
  },
1132
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
+
1133
1209
  // Create reactive fields
1134
1210
  this.$set(this.captions, 'language', track.language)
1135
- this.$set(this.captions, 'cues', track.cues)
1136
- this.$set(this.captions, 'activeCues', track.activeCues)
1211
+ this.$set(this.captions, 'cues', cues)
1212
+ this.$set(this.captions, 'activeCues', activeCues)
1137
1213
 
1138
1214
  // Required so the v-model will actually update.
1139
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="parseSourceType(current.src.sources) === null">
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,9 +40,13 @@
18
40
  class="player-skeleton"
19
41
  ></v-skeleton-loader>
20
42
  </div>
43
+
21
44
  <YoutubePlayer
22
45
  ref="youtubePlayer"
23
- v-if="parseSourceType(current.src.sources) === 'youtube'"
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"
@@ -30,13 +56,19 @@
30
56
  @focusout="onFocusout"
31
57
  @click:fullscreen="onFullscreen"
32
58
  ></YoutubePlayer>
59
+
33
60
  <Html5Player
34
61
  ref="html5Player"
35
- v-if="parseSourceType(current.src.sources) === 'html5'"
62
+ v-if="
63
+ !loading &&
64
+ parseSourceType(current.src.sources) === 'html5'
65
+ "
36
66
  :language="language"
37
67
  :type="current.type"
38
68
  :attributes="current.attributes"
39
69
  :src="current.src"
70
+ :volume.sync="volumeState"
71
+ :cc.sync="ccState"
40
72
  :captions-expanded.sync="captionsExpandedState"
41
73
  :captions-hide-expand="captionsHideExpand"
42
74
  :captions-paragraph-view="captionsParagraphView"
@@ -54,6 +86,7 @@
54
86
  @seeking="$emit('seeking', $event)"
55
87
  @timeupdate="$emit('timeupdate', $event)"
56
88
  @progress="$emit('progress', $event)"
89
+ @volumechange="onVolumeChange"
57
90
  @canplay="$emit('canplay', $event)"
58
91
  @waiting="$emit('waiting', $event)"
59
92
  @canplaythrough="$emit('canplaythrough', $event)"
@@ -157,9 +190,11 @@ export default {
157
190
  rewind: { type: Boolean, required: false, default: false }, // Enabled the rewind 10s button
158
191
  loop: { type: Boolean, required: false, default: false }, // Loop the video on completion
159
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
160
194
  playsinline: { Boolean: String, required: false, default: false }, // Force inline & disable fullscreen
161
195
  poster: { type: String, required: false, default: '' }, // Overridden with the playlist.poster if one is set there
162
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
163
198
  captionsmenu: { type: Boolean, required: false, default: true }, // Show the captions below the video
164
199
  captionsExpanded: {
165
200
  type: Boolean,
@@ -218,6 +253,7 @@ export default {
218
253
  'seeking',
219
254
  'timeupdate',
220
255
  'progress',
256
+ 'volumechange',
221
257
  'canplay',
222
258
  'waiting',
223
259
  'canplaythrough',
@@ -235,12 +271,24 @@ export default {
235
271
  'click:captions-paragraph-view',
236
272
  'click:captions-autoscroll',
237
273
  'click:captions-close',
274
+ 'update:volume',
275
+ 'update:cc',
238
276
  'update:captions-expanded',
239
277
  'update:captions-paragraph-view',
240
278
  'update:captions-autoscroll',
241
279
  'update:captions-visible',
242
280
  ],
243
- 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
+ },
244
292
  computed: {
245
293
  player() {
246
294
  if (this.parseSourceType(this.current.src.sources) === 'youtube') {
@@ -303,6 +351,32 @@ export default {
303
351
  return 8
304
352
  }
305
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
+ },
306
380
  captionsVisibleState: {
307
381
  get() {
308
382
  if (typeof this.captionsVisible !== 'undefined') {
@@ -336,16 +410,25 @@ export default {
336
410
  data() {
337
411
  return {
338
412
  t,
413
+ loading: false,
339
414
  sourceIndex: 0,
340
415
  captions: {
416
+ cc: false,
341
417
  visible: true,
342
418
  expanded: false,
343
419
  },
420
+ localVolume: 0.5, // The initial volume level
344
421
  mediaFocus: false,
345
422
  keyListener: null,
346
423
  }
347
424
  },
348
425
  methods: {
426
+ reloadMedia() {
427
+ this.loading = true
428
+ setTimeout(() => {
429
+ this.loading = false
430
+ }, 500)
431
+ },
349
432
  onEnded(e) {
350
433
  if (
351
434
  this.playlistautoadvance &&
@@ -359,6 +442,10 @@ export default {
359
442
  // Loaded a new video
360
443
  this.$emit('loadeddata', e)
361
444
  },
445
+ onVolumeChange(e) {
446
+ this.$emit('update:volume', e)
447
+ this.$emit('volumechange', e)
448
+ },
362
449
  onRemoteplayback(el) {
363
450
  // Make sure the browser supports remote playback
364
451
  if (!el.remote || !el.remote.watchAvailability) {
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
+ loading: 'Loading...',
13
14
  not_configured: 'Media not configured',
14
15
  playback_speed: 'Playback Speed',
15
16
  playback_decrease: 'Decrease playback speed',
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
@@ -10,6 +10,7 @@ export default {
10
10
  next: 'Spela nästa',
11
11
  },
12
12
  player: {
13
+ loading: 'Laddar...',
13
14
  not_configured: 'Media inte konfigurerad',
14
15
  playback_speed: 'Uppspelningshastighet',
15
16
  playback_decrease: 'Minska uppspelningshastigheten',