@mindedge/vuetify-player 0.4.9 → 0.5.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 CHANGED
@@ -26,6 +26,7 @@ An accessible, localized, full featured media player with Vuetifyjs
26
26
  | webm | wav | jpg |
27
27
  | ogg | ogg | gif (+ animated) |
28
28
  | YouTube URL | | |
29
+ | Vimeo URL | | |
29
30
 
30
31
  ## Quick Start
31
32
 
@@ -244,9 +245,11 @@ See [Full media `src` structure for where the ads array is placed](#full-media-s
244
245
  | `loadeddata` | `Event` | When the frame at the current playback position of the media has finished loading; often the first frame |
245
246
  | `loadedmetadata` | `Event` | When the metadata has been loaded |
246
247
  | `play` | `Event` | The media has received a request to start playing |
248
+ | `playing` | `Event` | The media playback is first started, and whenever it is restarted |
247
249
  | `pause` | `Event` | Playback has been suspended |
248
250
  | `progress` | `Event` | The progress event is fired periodically as the browser loads a resource. |
249
- | `seeking` | `Event` | Playback has moved to a new location |
251
+ | `seeking` | `Event` | Playback is currently beeing seeked |
252
+ | `seeked` | `Event` | Playback has moved to a new location |
250
253
  | `timeupdate` | `Object` | The current time was changed. Object contains { event: Event, current_percent: Number } |
251
254
  | `ratechange` | `Number` | The playback speed multiplier |
252
255
  | `stalled` | `Event` | The browser tried to download but has not received data yet |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindedge/vuetify-player",
3
- "version": "0.4.9",
3
+ "version": "0.5.0",
4
4
  "private": false,
5
5
  "description": "Accessible, localized, full featured media player with Vuetifyjs",
6
6
  "author": "Jacob Rogaishio",
@@ -20,13 +20,32 @@
20
20
  <div
21
21
  v-if="resolvedType === 'video' && state.replay"
22
22
  class="player-overlay"
23
+ role="button"
24
+ @click="play"
23
25
  >
24
26
  <div class="player-overlay--icon">
25
- <v-icon class="player-overlay--replay-icon">
27
+ <v-icon class="player-overlay--play-icon">
26
28
  mdi-replay
27
29
  </v-icon>
28
30
  </div>
29
31
  </div>
32
+ <div
33
+ v-if="
34
+ resolvedType === 'video' &&
35
+ state.paused &&
36
+ !state.replay &&
37
+ player.currentTime === 0
38
+ "
39
+ class="player-overlay"
40
+ role="button"
41
+ @click="play"
42
+ >
43
+ <div class="player-overlay--icon">
44
+ <v-icon class="player-overlay--play-icon">
45
+ mdi-play
46
+ </v-icon>
47
+ </div>
48
+ </div>
30
49
  <video
31
50
  ref="player"
32
51
  tabindex="0"
@@ -48,6 +67,7 @@
48
67
  :preload="attributes.preload"
49
68
  @click="playToggle"
50
69
  @seeking="onSeeking"
70
+ @seeked="onSeeked"
51
71
  @timeupdate="onTimeupdate"
52
72
  @progress="onMediaProgress"
53
73
  @loadedmetadata="onLoadedmetadata"
@@ -630,6 +650,7 @@ export default {
630
650
  'ratechange',
631
651
  'timeupdate',
632
652
  'seeking',
653
+ 'seeked',
633
654
  'progress',
634
655
  'volumechange',
635
656
  'cuechange',
@@ -1053,6 +1074,9 @@ export default {
1053
1074
  onSeeking(e) {
1054
1075
  this.$emit('seeking', e)
1055
1076
  },
1077
+ onSeeked(e) {
1078
+ this.$emit('seeked', e)
1079
+ },
1056
1080
  onMediaProgress(e) {
1057
1081
  this.$emit('progress', e)
1058
1082
  },
@@ -1437,7 +1461,7 @@ export default {
1437
1461
  height: 0;
1438
1462
  text-align: center;
1439
1463
  }
1440
- .player-overlay--replay-icon {
1464
+ .player-overlay--play-icon {
1441
1465
  color: #fff;
1442
1466
  font-size: 5rem;
1443
1467
  }
@@ -0,0 +1,219 @@
1
+ <template>
2
+ <v-container>
3
+ <v-responsive :aspect-ratio="aspectRatio">
4
+ <v-skeleton-loader
5
+ v-if="!player.ready"
6
+ class="mx-auto player-skeleton"
7
+ type="image"
8
+ ></v-skeleton-loader>
9
+
10
+ <div :id="player.id" :class="playerClass"></div>
11
+ </v-responsive>
12
+ </v-container>
13
+ </template>
14
+
15
+ <script>
16
+ export default {
17
+ name: 'VimeoPlayer',
18
+ props: {
19
+ language: { type: String, required: false, default: 'en-US' },
20
+ type: {
21
+ type: String,
22
+ required: false,
23
+ default: 'video',
24
+ },
25
+ attributes: {
26
+ type: Object,
27
+ required: true,
28
+ },
29
+ src: {
30
+ type: Object,
31
+ required: true,
32
+ },
33
+ },
34
+ emits: [
35
+ 'loadeddata',
36
+ 'play',
37
+ 'pause',
38
+ 'progress',
39
+ 'volumechange',
40
+ 'ended',
41
+ 'seeking',
42
+ 'seeked',
43
+ 'timeupdate',
44
+ ],
45
+ watch: {},
46
+ computed: {
47
+ playerClass() {
48
+ let classList = 'player-' + this.resolvedType
49
+ return classList
50
+ },
51
+ resolvedType() {
52
+ // vimeo is always a video
53
+ return 'video'
54
+ },
55
+ aspectRatio() {
56
+ if (this.player.height === 0 || this.player.width === 0) {
57
+ return 16 / 9
58
+ } else {
59
+ return this.player.width / this.player.height
60
+ }
61
+ },
62
+ },
63
+ data() {
64
+ return {
65
+ player: {
66
+ id:
67
+ 'vimeo-player-' +
68
+ Math.floor(Math.random() * 10000000) +
69
+ 1000000,
70
+ vimeo: {},
71
+ tag: {},
72
+ scriptTag: {},
73
+ loaded: false,
74
+ done: false,
75
+ ready: false,
76
+ height: 0,
77
+ width: 0,
78
+ },
79
+ }
80
+ },
81
+ mounted() {
82
+ this.loadAPI()
83
+ },
84
+ beforeDestroy() {
85
+ if (this.player.loaded) {
86
+ this.player.vimeo.off('seeked')
87
+ this.player.vimeo.off('timeupdate')
88
+ }
89
+ },
90
+ methods: {
91
+ parseVideoSource(src) {
92
+ const result = {
93
+ videoId: null,
94
+ listId: null,
95
+ }
96
+
97
+ if (!src.sources || !src.sources.length || !src.sources[0].src) {
98
+ return result
99
+ } else {
100
+ let url = src.sources[0].src
101
+ const regexId =
102
+ /(http|https)?:\/\/(www\.|player\.)?vimeo\.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|video\/|)(\d+)(?:|\/\?)/
103
+ const idMatch = url.match(regexId)
104
+
105
+ if (idMatch[4]) {
106
+ result.videoId = idMatch[4]
107
+ }
108
+
109
+ const regexPlaylist = /[?&]list=([^#&?]+)/
110
+ const playlistMatch = url.match(regexPlaylist)
111
+
112
+ if (playlistMatch && playlistMatch[1]) {
113
+ result.listId = playlistMatch[1]
114
+ }
115
+
116
+ return result
117
+ }
118
+ },
119
+ onreadystatechange() {
120
+ if (
121
+ !this.player.loaded &&
122
+ (this.player.tag.readyState === 'complete' ||
123
+ this.player.tag.readyState === 'loaded')
124
+ ) {
125
+ this.player.loaded = true
126
+ setTimeout(this.tagReady, 500)
127
+ }
128
+ },
129
+ onload() {
130
+ if (!this.player.loaded) {
131
+ this.player.loaded = true
132
+ setTimeout(this.tagReady, 500)
133
+ }
134
+ },
135
+ async tagReady() {
136
+ const source = this.parseVideoSource(this.src)
137
+ this.player.vimeo = new window.Vimeo.Player(this.player.id, {
138
+ id: source.videoId,
139
+ responsive: true,
140
+ })
141
+ this.player.vimeo.on('seeked', this.onSeek)
142
+ this.player.vimeo.on('timeupdate', this.onTimeupdate)
143
+
144
+ this.player.vimeo.on('ended', this.onEnded)
145
+ this.player.vimeo.on('loaded', this.onLoaded)
146
+ this.player.vimeo.on('play', this.onPlay)
147
+ this.player.vimeo.on('pause', this.onPause)
148
+ this.player.vimeo.on('progress', this.onProgress)
149
+ this.player.vimeo.on('volumechange', this.onVolumechange)
150
+ this.player.vimeo.on('error', this.onError)
151
+
152
+ this.player.ready = true
153
+ },
154
+ loadAPI() {
155
+ if (this.player.loaded) {
156
+ this.tagReady()
157
+ } else {
158
+ this.player.tag = document.createElement('script')
159
+
160
+ this.player.tag.src = 'https://player.vimeo.com/api/player.js'
161
+ this.player.scriptTag =
162
+ document.getElementsByTagName('script')[0]
163
+
164
+ // Make sure script tag was successfully created
165
+ if (this.player.scriptTag) {
166
+ this.player.scriptTag.parentNode.insertBefore(
167
+ this.player.tag,
168
+ this.player.scriptTag
169
+ )
170
+
171
+ this.player.done = false
172
+ this.player.tag.onload = this.onload
173
+ this.player.tag.onreadystatechange = this.onreadystatechange
174
+ }
175
+ }
176
+ },
177
+ onTimeupdate(e) {
178
+ this.$emit('timeupdate', {
179
+ event: e,
180
+ current_percent: e.percent,
181
+ })
182
+ },
183
+ onLoaded(e) {
184
+ this.$emit('loadeddata', e)
185
+ },
186
+ onPlay(e) {
187
+ this.$emit('play', e)
188
+ },
189
+ onPause(e) {
190
+ this.$emit('pause', e)
191
+ },
192
+ onProgress(e) {
193
+ this.$emit('progress', e)
194
+ },
195
+ onVolumechange(e) {
196
+ this.$emit('volumechange', e)
197
+ },
198
+ onError(e) {
199
+ this.$emit('error', e)
200
+ },
201
+ onEnded(e) {
202
+ this.$emit('ended', e)
203
+ },
204
+ onSeek(e) {
205
+ this.$emit('seeking', e)
206
+ this.$emit('seeked', e)
207
+ },
208
+ },
209
+ }
210
+ </script>
211
+
212
+ <style scoped>
213
+ .player-skeleton {
214
+ position: relative;
215
+ margin-bottom: -400px;
216
+ height: 400px;
217
+ aspect-ratio: 16/9;
218
+ }
219
+ </style>
@@ -1,12 +1,11 @@
1
1
  <template>
2
2
  <v-container>
3
- <v-responsive :aspect-ratio="16 / 9">
3
+ <v-responsive>
4
4
  <v-skeleton-loader
5
5
  v-if="!player.ready"
6
6
  class="mx-auto player-skeleton"
7
7
  type="image"
8
8
  ></v-skeleton-loader>
9
-
10
9
  <div :id="player.id" :class="playerClass"></div>
11
10
  </v-responsive>
12
11
  </v-container>
@@ -31,6 +30,18 @@ export default {
31
30
  required: true,
32
31
  },
33
32
  },
33
+ emits: [
34
+ 'loadeddata',
35
+ 'play',
36
+ 'pause',
37
+ 'progress',
38
+ 'playing',
39
+ 'ended',
40
+ 'seeking',
41
+ 'seeked',
42
+ 'timeupdate',
43
+ 'error',
44
+ ],
34
45
  watch: {},
35
46
  computed: {
36
47
  playerClass() {
@@ -55,9 +66,19 @@ export default {
55
66
  loaded: false,
56
67
  done: false,
57
68
  ready: false,
69
+ currentTime: 0,
70
+ currentPercent: 0,
58
71
  },
59
72
  }
60
73
  },
74
+ mounted() {
75
+ this.init()
76
+ },
77
+ beforeDestroy() {
78
+ if (this.player.ready) {
79
+ window.removeEventListener('message', this.onPostmessage)
80
+ }
81
+ },
61
82
  methods: {
62
83
  parseVideoSource(src) {
63
84
  const result = {
@@ -71,6 +92,7 @@ export default {
71
92
  let url = src.sources[0].src
72
93
  const regexId =
73
94
  /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/
95
+
74
96
  const idMatch = url.match(regexId)
75
97
 
76
98
  if (idMatch && idMatch[2].length === 11) {
@@ -87,7 +109,8 @@ export default {
87
109
  return result
88
110
  }
89
111
  },
90
- onPlayerReady() {
112
+ onPlayerReady(e) {
113
+ this.$emit('loadeddata', e)
91
114
  // Uncomment for auto-play
92
115
  // e.target.playVideo();
93
116
  this.player.ready = true
@@ -95,10 +118,31 @@ export default {
95
118
  const source = this.parseVideoSource(this.src)
96
119
 
97
120
  if (source.listId) {
98
- this.ytPlayer.loadPlaylist(source.listId)
121
+ this.player.yt.loadPlaylist(source.listId)
99
122
  }
123
+ window.addEventListener('message', this.onPostmessage)
100
124
  },
101
125
  onPlayerStateChange(e) {
126
+ if (e && e.data) {
127
+ switch (e.data) {
128
+ case window.YT.PlayerState.UNSTARTED:
129
+ this.$emit('play', e)
130
+ break
131
+ case window.YT.PlayerState.PAUSED:
132
+ this.$emit('pause', e)
133
+ break
134
+ case window.YT.PlayerState.BUFFERING:
135
+ this.$emit('progress', e)
136
+ break
137
+ case window.YT.PlayerState.PLAYING:
138
+ this.$emit('playing', e)
139
+ break
140
+ case window.YT.PlayerState.ENDED:
141
+ this.$emit('ended', e)
142
+ break
143
+ }
144
+ }
145
+
102
146
  if (e.data == window.YT.PlayerState.PLAYING && !this.player.done) {
103
147
  setTimeout(() => {
104
148
  this.player.yt.stopVideo()
@@ -106,6 +150,9 @@ export default {
106
150
  this.player.done = true
107
151
  }
108
152
  },
153
+ onError(e) {
154
+ this.$emit('error', e)
155
+ },
109
156
  tagReady() {
110
157
  const source = this.parseVideoSource(this.src)
111
158
 
@@ -118,6 +165,7 @@ export default {
118
165
  events: {
119
166
  onReady: this.onPlayerReady,
120
167
  onStateChange: this.onPlayerStateChange,
168
+ onError: this.onError,
121
169
  },
122
170
  })
123
171
  },
@@ -160,9 +208,54 @@ export default {
160
208
  }
161
209
  }
162
210
  },
163
- },
164
- mounted() {
165
- this.init()
211
+ onPostmessage(event) {
212
+ // Check that the event was sent from the YouTube IFrame.
213
+ if (event.source === this.player.yt.getIframe().contentWindow) {
214
+ const data = JSON.parse(event.data)
215
+
216
+ // The "infoDelivery" event is used by YT to transmit any
217
+ // kind of information change in the player,
218
+ // such as the current time or a playback quality change.
219
+ if (
220
+ data.event === 'infoDelivery' &&
221
+ data.info &&
222
+ data.info.currentTime
223
+ ) {
224
+ // currentTime is emitted very frequently (milliseconds),
225
+ // but we only care about whole second changes.
226
+ const time = Math.floor(data.info.currentTime)
227
+
228
+ if (time !== this.player.currentTime) {
229
+ // Since this postmessage sends multiple times per second there's no reason the
230
+ // current time should be more than a second or two off. If it is then you scrubbed the video
231
+ if (Math.abs(time - this.player.currentTime) > 2) {
232
+ this.$emit('seeking', data)
233
+ this.$emit('seeked', data)
234
+ }
235
+
236
+ this.player.currentTime = time
237
+
238
+ const totalDuration = Math.floor(
239
+ data.info.progressState &&
240
+ data.info.progressState.duration
241
+ ? data.info.progressState.duration
242
+ : this.player.yt.getDuration()
243
+ )
244
+
245
+ this.player.currentPercent =
246
+ totalDuration > 0
247
+ ? (this.player.currentTime / totalDuration) *
248
+ 100
249
+ : 0
250
+
251
+ this.$emit('timeupdate', {
252
+ event: data,
253
+ current_percent: this.player.currentPercent,
254
+ })
255
+ }
256
+ }
257
+ }
258
+ },
166
259
  },
167
260
  }
168
261
  </script>
@@ -172,5 +265,9 @@ export default {
172
265
  position: relative;
173
266
  margin-bottom: -400px;
174
267
  height: 400px;
268
+ aspect-ratio: 16/9;
269
+ }
270
+ .player-video {
271
+ min-height: 300px;
175
272
  }
176
273
  </style>
@@ -52,11 +52,70 @@
52
52
  :attributes="current.attributes"
53
53
  :src="current.src"
54
54
  :elevation="flat ? 0 : elevation"
55
+ @load="$emit('load', $event)"
56
+ @ended="onEnded"
57
+ @loadeddata="onLoadeddata"
58
+ @loadedmetadata="$emit('loadedmetadata', $event)"
59
+ @play="$emit('play', $event)"
60
+ @playing="$emit('playing', $event)"
61
+ @pause="$emit('pause', $event)"
62
+ @seeking="$emit('seeking', $event)"
63
+ @seeked="$emit('seeked', $event)"
64
+ @timeupdate="$emit('timeupdate', $event)"
65
+ @progress="$emit('progress', $event)"
66
+ @volumechange="onVolumeChange"
67
+ @canplay="$emit('canplay', $event)"
68
+ @waiting="$emit('waiting', $event)"
69
+ @canplaythrough="$emit('canplaythrough', $event)"
70
+ @error="$emit('error', $event)"
71
+ @emptied="$emit('emptied', $event)"
72
+ @ratechange="$emit('ratechange', $event)"
73
+ @stalled="$emit('stalled', $event)"
74
+ @abort="$emit('abort', $event)"
75
+ @mouseover="$emit('mouseover', $event)"
76
+ @mouseout="$emit('mouseout', $event)"
55
77
  @focusin="onFocusin"
56
78
  @focusout="onFocusout"
57
79
  @click:fullscreen="onFullscreen"
58
80
  ></YoutubePlayer>
59
81
 
82
+ <VimeoPlayer
83
+ ref="vimeoPlayer"
84
+ v-if="
85
+ !loading &&
86
+ parseSourceType(current.src.sources) === 'vimeo'
87
+ "
88
+ :language="language"
89
+ :type="current.type"
90
+ :attributes="current.attributes"
91
+ :src="current.src"
92
+ :elevation="flat ? 0 : elevation"
93
+ @ended="onEnded"
94
+ @loadeddata="onLoadeddata"
95
+ @loadedmetadata="$emit('loadedmetadata', $event)"
96
+ @play="$emit('play', $event)"
97
+ @playing="$emit('playing', $event)"
98
+ @pause="$emit('pause', $event)"
99
+ @seeking="$emit('seeking', $event)"
100
+ @seeked="$emit('seeked', $event)"
101
+ @timeupdate="$emit('timeupdate', $event)"
102
+ @progress="$emit('progress', $event)"
103
+ @volumechange="onVolumeChange"
104
+ @canplay="$emit('canplay', $event)"
105
+ @waiting="$emit('waiting', $event)"
106
+ @canplaythrough="$emit('canplaythrough', $event)"
107
+ @error="$emit('error', $event)"
108
+ @emptied="$emit('emptied', $event)"
109
+ @ratechange="$emit('ratechange', $event)"
110
+ @stalled="$emit('stalled', $event)"
111
+ @abort="$emit('abort', $event)"
112
+ @mouseover="$emit('mouseover', $event)"
113
+ @mouseout="$emit('mouseout', $event)"
114
+ @focusin="onFocusin"
115
+ @focusout="onFocusout"
116
+ @click:fullscreen="onFullscreen"
117
+ ></VimeoPlayer>
118
+
60
119
  <Html5Player
61
120
  ref="html5Player"
62
121
  v-if="
@@ -82,8 +141,10 @@
82
141
  @loadeddata="onLoadeddata"
83
142
  @loadedmetadata="$emit('loadedmetadata', $event)"
84
143
  @play="$emit('play', $event)"
144
+ @playing="$emit('playing', $event)"
85
145
  @pause="$emit('pause', $event)"
86
146
  @seeking="$emit('seeking', $event)"
147
+ @seeked="$emit('seeked', $event)"
87
148
  @timeupdate="$emit('timeupdate', $event)"
88
149
  @progress="$emit('progress', $event)"
89
150
  @volumechange="onVolumeChange"
@@ -135,6 +196,7 @@
135
196
  <script>
136
197
  import { t } from '../i18n/i18n'
137
198
  import YoutubePlayer from './Media/YoutubePlayer.vue'
199
+ import VimeoPlayer from './Media/VimeoPlayer.vue'
138
200
  import Html5Player from './Media/Html5Player.vue'
139
201
  import PlaylistMenu from './Media/PlaylistMenu.vue'
140
202
 
@@ -142,6 +204,7 @@ export default {
142
204
  name: 'VuetifyPlayer',
143
205
  components: {
144
206
  YoutubePlayer,
207
+ VimeoPlayer,
145
208
  Html5Player,
146
209
  PlaylistMenu,
147
210
  },
@@ -249,8 +312,10 @@ export default {
249
312
  'loadeddata',
250
313
  'loadedmetadata',
251
314
  'play',
315
+ 'playing',
252
316
  'pause',
253
317
  'seeking',
318
+ 'seeked',
254
319
  'timeupdate',
255
320
  'progress',
256
321
  'volumechange',
@@ -293,6 +358,10 @@ export default {
293
358
  player() {
294
359
  if (this.parseSourceType(this.current.src.sources) === 'youtube') {
295
360
  return this.$refs.youtubePlayer
361
+ } else if (
362
+ this.parseSourceType(this.current.src.sources) === 'vimeo'
363
+ ) {
364
+ return this.$refs.vimeoPlayer
296
365
  } else if (
297
366
  this.parseSourceType(this.current.src.sources) === 'html5'
298
367
  ) {
@@ -514,6 +583,9 @@ export default {
514
583
  const ytRegex =
515
584
  /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/
516
585
 
586
+ const vimeoRegex =
587
+ /(http|https)?:\/\/(www\.|player\.)?vimeo\.com\/(?:channels\/(?:\w+\/)?|groups\/([^/]*)\/videos\/|video\/|)(\d+)(?:|\/\?)/
588
+
517
589
  if (!sources || !sources.length || !sources[0].src) {
518
590
  return null
519
591
  }
@@ -526,6 +598,8 @@ export default {
526
598
  return null
527
599
  } else if (src.match(ytRegex) || type === 'video/youtube') {
528
600
  return 'youtube'
601
+ } else if (src.match(vimeoRegex) || type === 'video/vimeo') {
602
+ return 'vimeo'
529
603
  } else {
530
604
  return 'html5'
531
605
  }