@mindedge/vuetify-player 0.1.2 → 0.2.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/babel.config.js +3 -0
- package/jest.config.js +3 -0
- package/jsconfig.json +12 -0
- package/package.json +2 -4
- package/src/components/Media/CaptionsMenu.vue +128 -0
- package/src/components/Media/Html5Player.vue +926 -0
- package/src/components/Media/PlaylistMenu.vue +138 -0
- package/src/components/Media/YoutubePlayer.vue +172 -0
- package/src/components/VuetifyPlayer.vue +270 -0
- package/src/components/filters.js +31 -0
- package/src/i18n/en-US.js +28 -0
- package/src/i18n/i18n.js +43 -0
- package/src/i18n/index.js +7 -0
- package/src/i18n/sv-SE.js +28 -0
- package/vue.config.js +34 -0
- package/dist/VuetifyPlayer.common.js +0 -24149
- package/dist/VuetifyPlayer.common.js.map +0 -1
- package/dist/VuetifyPlayer.umd.js +0 -24169
- package/dist/VuetifyPlayer.umd.js.map +0 -1
- package/dist/VuetifyPlayer.umd.min.js +0 -2
- package/dist/VuetifyPlayer.umd.min.js.map +0 -1
- package/dist/demo.html +0 -1
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-card>
|
|
3
|
+
<v-card-title>{{ t(language, 'playlist.up_next') }}</v-card-title>
|
|
4
|
+
<v-card-text>
|
|
5
|
+
<v-list>
|
|
6
|
+
<v-list-item-group v-model="sourceIndex">
|
|
7
|
+
<v-list-item
|
|
8
|
+
v-for="(source, index) of playlist"
|
|
9
|
+
:key="index + 'playlistSources'"
|
|
10
|
+
@click="onPlaylistSelect(index)"
|
|
11
|
+
>
|
|
12
|
+
<v-list-item-icon>
|
|
13
|
+
<v-avatar
|
|
14
|
+
v-if="getPoster(source.poster, poster)"
|
|
15
|
+
tile
|
|
16
|
+
>
|
|
17
|
+
<img :src="getPoster(source.poster, poster)" />
|
|
18
|
+
</v-avatar>
|
|
19
|
+
<v-skeleton-loader
|
|
20
|
+
v-if="!getPoster(source.poster, poster)"
|
|
21
|
+
class="ma-3"
|
|
22
|
+
type="avatar"
|
|
23
|
+
tile
|
|
24
|
+
></v-skeleton-loader>
|
|
25
|
+
</v-list-item-icon>
|
|
26
|
+
<v-list-item-content>
|
|
27
|
+
<v-tooltip bottom>
|
|
28
|
+
<template v-slot:activator="{ on, attrs }">
|
|
29
|
+
<div
|
|
30
|
+
v-bind="attrs"
|
|
31
|
+
v-on="on"
|
|
32
|
+
class="text-lg-subtitle-1 text-truncate"
|
|
33
|
+
>
|
|
34
|
+
{{
|
|
35
|
+
source.name ||
|
|
36
|
+
t(language, 'playlist.default_name')
|
|
37
|
+
}}
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
40
|
+
<span>
|
|
41
|
+
{{
|
|
42
|
+
source.name ||
|
|
43
|
+
t(language, 'playlist.default_name')
|
|
44
|
+
}}
|
|
45
|
+
</span>
|
|
46
|
+
</v-tooltip>
|
|
47
|
+
</v-list-item-content>
|
|
48
|
+
</v-list-item>
|
|
49
|
+
</v-list-item-group>
|
|
50
|
+
</v-list>
|
|
51
|
+
</v-card-text>
|
|
52
|
+
<v-card-actions>
|
|
53
|
+
<v-col cols="6">
|
|
54
|
+
<v-btn
|
|
55
|
+
block
|
|
56
|
+
:disabled="playlist.length < 1 || sourceIndex === 0"
|
|
57
|
+
@click="onPlaylistSelect(sourceIndex - 1)"
|
|
58
|
+
>
|
|
59
|
+
<v-icon>mdi-skip-previous</v-icon>
|
|
60
|
+
<span class="d-sr-only">{{
|
|
61
|
+
t(language, 'playlist.previous')
|
|
62
|
+
}}</span>
|
|
63
|
+
</v-btn>
|
|
64
|
+
</v-col>
|
|
65
|
+
<v-col cols="6">
|
|
66
|
+
<v-btn
|
|
67
|
+
block
|
|
68
|
+
:disabled="
|
|
69
|
+
playlist.length < 1 ||
|
|
70
|
+
sourceIndex === playlist.length - 1
|
|
71
|
+
"
|
|
72
|
+
@click="onPlaylistSelect(sourceIndex + 1)"
|
|
73
|
+
>
|
|
74
|
+
<v-icon>mdi-skip-next</v-icon>
|
|
75
|
+
<span class="d-sr-only">{{
|
|
76
|
+
t(language, 'playlist.next')
|
|
77
|
+
}}</span>
|
|
78
|
+
</v-btn>
|
|
79
|
+
</v-col>
|
|
80
|
+
</v-card-actions>
|
|
81
|
+
</v-card>
|
|
82
|
+
</template>
|
|
83
|
+
|
|
84
|
+
<script>
|
|
85
|
+
import { t } from '../../i18n/i18n'
|
|
86
|
+
|
|
87
|
+
export default {
|
|
88
|
+
props: {
|
|
89
|
+
value: { type: Number, required: true },
|
|
90
|
+
playlist: { type: Array, required: true },
|
|
91
|
+
poster: { type: String, required: false, default: '' },
|
|
92
|
+
language: { type: String, required: false, default: 'en-US' },
|
|
93
|
+
},
|
|
94
|
+
data() {
|
|
95
|
+
return {
|
|
96
|
+
t,
|
|
97
|
+
sourceIndex: this.value,
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
watch: {
|
|
101
|
+
value(newIndex) {
|
|
102
|
+
this.sourceIndex = newIndex
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
methods: {
|
|
106
|
+
onPlaylistSelect(index) {
|
|
107
|
+
this.sourceIndex = index
|
|
108
|
+
this.$emit('input', parseInt(this.sourceIndex))
|
|
109
|
+
this.$emit('click:select', index)
|
|
110
|
+
},
|
|
111
|
+
getPoster(...posters) {
|
|
112
|
+
for (const poster of posters) {
|
|
113
|
+
if (poster) {
|
|
114
|
+
return poster
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return null
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
mounted() {},
|
|
121
|
+
}
|
|
122
|
+
</script>
|
|
123
|
+
|
|
124
|
+
<style scoped>
|
|
125
|
+
.captions-list {
|
|
126
|
+
max-height: 10em;
|
|
127
|
+
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
|
+
}
|
|
138
|
+
</style>
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<v-container>
|
|
3
|
+
<v-responsive :aspect-ratio="16 / 9">
|
|
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: 'YoutubePlayer',
|
|
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
|
+
watch: {},
|
|
35
|
+
computed: {
|
|
36
|
+
playerClass() {
|
|
37
|
+
let classList = 'player-' + this.type
|
|
38
|
+
return classList
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
data() {
|
|
42
|
+
return {
|
|
43
|
+
player: {
|
|
44
|
+
id:
|
|
45
|
+
'yt-player-' +
|
|
46
|
+
Math.floor(Math.random() * 10000000) +
|
|
47
|
+
1000000,
|
|
48
|
+
yt: {},
|
|
49
|
+
tag: {},
|
|
50
|
+
scriptTag: {},
|
|
51
|
+
loaded: false,
|
|
52
|
+
done: false,
|
|
53
|
+
ready: false,
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
methods: {
|
|
58
|
+
parseVideoSource(src) {
|
|
59
|
+
const result = {
|
|
60
|
+
videoId: null,
|
|
61
|
+
listId: null,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if (!src.sources || !src.sources.length || !src.sources[0].src) {
|
|
65
|
+
return result
|
|
66
|
+
} else {
|
|
67
|
+
let url = src.sources[0].src
|
|
68
|
+
const regexId =
|
|
69
|
+
/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/
|
|
70
|
+
const idMatch = url.match(regexId)
|
|
71
|
+
|
|
72
|
+
if (idMatch && idMatch[2].length === 11) {
|
|
73
|
+
result.videoId = idMatch[2]
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const regexPlaylist = /[?&]list=([^#&?]+)/
|
|
77
|
+
const playlistMatch = url.match(regexPlaylist)
|
|
78
|
+
|
|
79
|
+
if (playlistMatch && playlistMatch[1]) {
|
|
80
|
+
result.listId = playlistMatch[1]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return result
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
onPlayerReady() {
|
|
87
|
+
// Uncomment for auto-play
|
|
88
|
+
// e.target.playVideo();
|
|
89
|
+
this.player.ready = true
|
|
90
|
+
|
|
91
|
+
const source = this.parseVideoSource(this.src)
|
|
92
|
+
|
|
93
|
+
if (source.listId) {
|
|
94
|
+
this.ytPlayer.loadPlaylist(source.listId)
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
onPlayerStateChange(e) {
|
|
98
|
+
if (e.data == window.YT.PlayerState.PLAYING && !this.player.done) {
|
|
99
|
+
setTimeout(() => {
|
|
100
|
+
this.player.yt.stopVideo()
|
|
101
|
+
}, 6000)
|
|
102
|
+
this.player.done = true
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
tagReady() {
|
|
106
|
+
const source = this.parseVideoSource(this.src)
|
|
107
|
+
|
|
108
|
+
this.player.yt = new window.YT.Player(this.player.id, {
|
|
109
|
+
width: '100%',
|
|
110
|
+
videoId: source.videoId,
|
|
111
|
+
playerVars: {
|
|
112
|
+
playsinline: 1,
|
|
113
|
+
},
|
|
114
|
+
events: {
|
|
115
|
+
onReady: this.onPlayerReady,
|
|
116
|
+
onStateChange: this.onPlayerStateChange,
|
|
117
|
+
},
|
|
118
|
+
})
|
|
119
|
+
},
|
|
120
|
+
onreadystatechange() {
|
|
121
|
+
if (
|
|
122
|
+
!this.player.loaded &&
|
|
123
|
+
(this.player.tag.readyState === 'complete' ||
|
|
124
|
+
this.player.tag.readyState === 'loaded')
|
|
125
|
+
) {
|
|
126
|
+
this.player.loaded = true
|
|
127
|
+
setTimeout(this.tagReady, 500)
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
onload() {
|
|
131
|
+
if (!this.player.loaded) {
|
|
132
|
+
this.player.loaded = true
|
|
133
|
+
setTimeout(this.tagReady, 500)
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
init() {
|
|
137
|
+
if (this.player.loaded) {
|
|
138
|
+
this.tagReady()
|
|
139
|
+
} else {
|
|
140
|
+
this.player.tag = document.createElement('script')
|
|
141
|
+
|
|
142
|
+
this.player.tag.src = 'https://www.youtube.com/iframe_api'
|
|
143
|
+
this.player.scriptTag =
|
|
144
|
+
document.getElementsByTagName('script')[0]
|
|
145
|
+
|
|
146
|
+
// Make sure script tag was successfully created
|
|
147
|
+
if (this.player.scriptTag) {
|
|
148
|
+
this.player.scriptTag.parentNode.insertBefore(
|
|
149
|
+
this.player.tag,
|
|
150
|
+
this.player.scriptTag
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
this.player.done = false
|
|
154
|
+
this.player.tag.onload = this.onload
|
|
155
|
+
this.player.tag.onreadystatechange = this.onreadystatechange
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
mounted() {
|
|
161
|
+
this.init()
|
|
162
|
+
},
|
|
163
|
+
}
|
|
164
|
+
</script>
|
|
165
|
+
|
|
166
|
+
<style scoped>
|
|
167
|
+
.player-skeleton {
|
|
168
|
+
position: relative;
|
|
169
|
+
margin-bottom: -400px;
|
|
170
|
+
height: 400px;
|
|
171
|
+
}
|
|
172
|
+
</style>
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<v-row>
|
|
4
|
+
<v-col :cols="!playlistmenu || playlist.length <= 1 ? 12 : 8">
|
|
5
|
+
<YoutubePlayer
|
|
6
|
+
ref="youtubePlayer"
|
|
7
|
+
v-if="parseSourceType(current.src.sources) === 'youtube'"
|
|
8
|
+
:language="language"
|
|
9
|
+
:type="current.type"
|
|
10
|
+
:attributes="current.attributes"
|
|
11
|
+
:src="current.src"
|
|
12
|
+
@click:fullscreen="onFullscreen"
|
|
13
|
+
></YoutubePlayer>
|
|
14
|
+
<Html5Player
|
|
15
|
+
ref="html5Player"
|
|
16
|
+
v-if="parseSourceType(current.src.sources) === 'html5'"
|
|
17
|
+
:language="language"
|
|
18
|
+
:type="current.type"
|
|
19
|
+
:attributes="current.attributes"
|
|
20
|
+
:src="current.src"
|
|
21
|
+
@load="$emit('load', $event)"
|
|
22
|
+
@ended="onEnded"
|
|
23
|
+
@loadeddata="onLoadeddata"
|
|
24
|
+
@loadedmetadata="$emit('loadedmetadata', $event)"
|
|
25
|
+
@play="$emit('play', $event)"
|
|
26
|
+
@pause="$emit('pause', $event)"
|
|
27
|
+
@seeking="$emit('seeking', $event)"
|
|
28
|
+
@timeupdate="$emit('timeupdate', $event)"
|
|
29
|
+
@progress="$emit('progress', $event)"
|
|
30
|
+
@canplay="$emit('canplay', $event)"
|
|
31
|
+
@waiting="$emit('waiting', $event)"
|
|
32
|
+
@canplaythrough="$emit('canplaythrough', $event)"
|
|
33
|
+
@error="$emit('error', $event)"
|
|
34
|
+
@emptied="$emit('emptied', $event)"
|
|
35
|
+
@ratechange="$emit('ratechange', $event)"
|
|
36
|
+
@stalled="$emit('stalled', $event)"
|
|
37
|
+
@abort="$emit('abort', $event)"
|
|
38
|
+
@mouseover="$emit('mouseover', $event)"
|
|
39
|
+
@mouseout="$emit('mouseout', $event)"
|
|
40
|
+
@click:fullscreen="onFullscreen"
|
|
41
|
+
@click:pictureinpicture="onPictureInPicture"
|
|
42
|
+
@click:remoteplayback="onRemoteplayback"
|
|
43
|
+
></Html5Player>
|
|
44
|
+
</v-col>
|
|
45
|
+
|
|
46
|
+
<!-- Playlist stuff -->
|
|
47
|
+
<v-col v-if="playlistmenu && playlist.length > 1" cols="4">
|
|
48
|
+
<PlaylistMenu
|
|
49
|
+
v-model="sourceIndex"
|
|
50
|
+
:language="language"
|
|
51
|
+
:playlist="playlist"
|
|
52
|
+
:poster="poster"
|
|
53
|
+
@click:select="onPlaylistSelect"
|
|
54
|
+
></PlaylistMenu>
|
|
55
|
+
</v-col>
|
|
56
|
+
</v-row>
|
|
57
|
+
</div>
|
|
58
|
+
</template>
|
|
59
|
+
|
|
60
|
+
<script>
|
|
61
|
+
import YoutubePlayer from './Media/YoutubePlayer.vue'
|
|
62
|
+
import Html5Player from './Media/Html5Player.vue'
|
|
63
|
+
import PlaylistMenu from './Media/PlaylistMenu.vue'
|
|
64
|
+
|
|
65
|
+
export default {
|
|
66
|
+
name: 'VuetifyPlayer',
|
|
67
|
+
components: {
|
|
68
|
+
YoutubePlayer,
|
|
69
|
+
Html5Player,
|
|
70
|
+
PlaylistMenu,
|
|
71
|
+
},
|
|
72
|
+
props: {
|
|
73
|
+
language: { type: String, required: false, default: 'en-US' },
|
|
74
|
+
src: {
|
|
75
|
+
type: Object,
|
|
76
|
+
required: false,
|
|
77
|
+
default: () => {
|
|
78
|
+
return {}
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
playlist: {
|
|
82
|
+
type: Array,
|
|
83
|
+
required: false,
|
|
84
|
+
default: () => {
|
|
85
|
+
return []
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
type: { type: String, required: false, default: 'video' }, // Allowed video|audio. In audio mode the player has a max-height of 40px
|
|
89
|
+
autoplay: { type: Boolean, required: false, default: false }, // Autoplay on load. It's in the spec but DON'T USE THIS
|
|
90
|
+
autopictureinpicture: {
|
|
91
|
+
type: Boolean,
|
|
92
|
+
required: false,
|
|
93
|
+
default: false,
|
|
94
|
+
}, // Start with picture in picture mode
|
|
95
|
+
controls: { type: Boolean, required: false, default: true }, // Show video controls. When false only play/pause allowed but clicking on the video itself
|
|
96
|
+
controlslist: {
|
|
97
|
+
type: String,
|
|
98
|
+
required: false,
|
|
99
|
+
default: 'nodownload noremoteplayback',
|
|
100
|
+
}, // Space separated string per <video>. Allowed 'nodownload nofullscreen noremoteplayback'
|
|
101
|
+
crossorigin: { type: String, required: false, default: 'anonymous' },
|
|
102
|
+
disablepictureinpicture: {
|
|
103
|
+
type: Boolean,
|
|
104
|
+
required: false,
|
|
105
|
+
default: true,
|
|
106
|
+
}, // Shows the picture in picture button
|
|
107
|
+
disableremoteplayback: {
|
|
108
|
+
type: Boolean,
|
|
109
|
+
required: false,
|
|
110
|
+
default: true,
|
|
111
|
+
}, // Shows the remote playback button but functionality does not exist when clicked
|
|
112
|
+
height: { type: String, required: false, default: 'auto' },
|
|
113
|
+
width: { type: String, required: false, default: '100%' },
|
|
114
|
+
rewind: { type: Boolean, required: false, default: false }, // Enabled the rewind 10s button
|
|
115
|
+
loop: { type: Boolean, required: false, default: false }, // Loop the video on completion
|
|
116
|
+
muted: { type: Boolean, required: false, default: false }, // Start the video muted
|
|
117
|
+
playsinline: { Boolean: String, required: false, default: false }, // Force inline & disable fullscreen
|
|
118
|
+
poster: { type: String, required: false, default: '' }, // Overridden with the playlist.poster if one is set there
|
|
119
|
+
preload: { type: String, required: false, default: '' },
|
|
120
|
+
captionsmenu: { type: Boolean, required: false, default: true }, // Show the captions below the video
|
|
121
|
+
playlistmenu: { type: Boolean, required: false, default: true }, // Show the playlist menu if there's multiple videos
|
|
122
|
+
playlistautoadvance: { type: Boolean, required: false, default: true }, // Play the next source group
|
|
123
|
+
playbackrates: {
|
|
124
|
+
type: Array,
|
|
125
|
+
required: false,
|
|
126
|
+
default: () => {
|
|
127
|
+
return [0.5, 1, 1.5, 2]
|
|
128
|
+
},
|
|
129
|
+
}, // Default playback speeds
|
|
130
|
+
},
|
|
131
|
+
watch: {},
|
|
132
|
+
computed: {
|
|
133
|
+
player() {
|
|
134
|
+
if (this.parseSourceType(this.current.src.sources) === 'youtube') {
|
|
135
|
+
return this.$refs.youtubePlayer
|
|
136
|
+
} else if (
|
|
137
|
+
this.parseSourceType(this.current.src.sources) === 'html5'
|
|
138
|
+
) {
|
|
139
|
+
return this.$refs.html5Player
|
|
140
|
+
} else {
|
|
141
|
+
return null
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
current() {
|
|
145
|
+
const c = {
|
|
146
|
+
type: this.type,
|
|
147
|
+
attributes: this.attributes,
|
|
148
|
+
src: {},
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (this.src && this.src.sources && this.src.sources.length) {
|
|
152
|
+
c.src = this.src
|
|
153
|
+
return c
|
|
154
|
+
} else if (
|
|
155
|
+
this.playlist &&
|
|
156
|
+
this.playlist.length &&
|
|
157
|
+
typeof this.playlist[this.sourceIndex] !== 'undefined'
|
|
158
|
+
) {
|
|
159
|
+
c.src = this.playlist[this.sourceIndex]
|
|
160
|
+
return c
|
|
161
|
+
} else {
|
|
162
|
+
return c
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
attributes() {
|
|
166
|
+
const attrs = {}
|
|
167
|
+
|
|
168
|
+
// Loop over all available props and get the value / default value
|
|
169
|
+
for (let i = 0; i < this.$options._propKeys.length; i++) {
|
|
170
|
+
let key = this.$options._propKeys[i]
|
|
171
|
+
attrs[key] = this[key]
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return attrs
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
data() {
|
|
178
|
+
return {
|
|
179
|
+
sourceIndex: 0,
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
methods: {
|
|
183
|
+
onEnded(e) {
|
|
184
|
+
if (
|
|
185
|
+
this.playlistautoadvance &&
|
|
186
|
+
this.sourceIndex < this.playlist.length - 1
|
|
187
|
+
) {
|
|
188
|
+
this.onPlaylistSelect(this.sourceIndex + 1)
|
|
189
|
+
this.$emit('ended', e)
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
onLoadeddata(e) {
|
|
193
|
+
// Loaded a new video
|
|
194
|
+
this.$emit('loadeddata', e)
|
|
195
|
+
},
|
|
196
|
+
onRemoteplayback(el) {
|
|
197
|
+
// Make sure the browser supports remote playback
|
|
198
|
+
if (!el.remote || !el.remote.watchAvailability) {
|
|
199
|
+
return false
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
el.remote
|
|
203
|
+
.prompt()
|
|
204
|
+
.then(() => {
|
|
205
|
+
alert('Remote playback not implemented on this player')
|
|
206
|
+
})
|
|
207
|
+
.catch((e) => {
|
|
208
|
+
//if(e.name == 'NotSupportedError') {}
|
|
209
|
+
alert(e.message)
|
|
210
|
+
})
|
|
211
|
+
},
|
|
212
|
+
onPictureInPicture(el) {
|
|
213
|
+
// Make sure the browser supports HTMLVideoElement.requestPictureInPicture()
|
|
214
|
+
if (!el.requestPictureInPicture) {
|
|
215
|
+
return false
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!document.pictureInPictureElement) {
|
|
219
|
+
el.requestPictureInPicture()
|
|
220
|
+
this.$emit('click:pictureinpicture', true)
|
|
221
|
+
} else {
|
|
222
|
+
document.exitPictureInPicture()
|
|
223
|
+
this.$emit('click:pictureinpicture', false)
|
|
224
|
+
}
|
|
225
|
+
},
|
|
226
|
+
onFullscreen(el) {
|
|
227
|
+
// Make sure the browser supports document.fullscreenEnabled
|
|
228
|
+
if (!document.fullscreenEnabled) {
|
|
229
|
+
return false
|
|
230
|
+
}
|
|
231
|
+
if (!document.fullscreenElement) {
|
|
232
|
+
el.requestFullscreen()
|
|
233
|
+
this.$emit('click:fullscreen', true)
|
|
234
|
+
} else {
|
|
235
|
+
document.exitFullscreen()
|
|
236
|
+
this.$emit('click:fullscreen', false)
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
onPlaylistSelect(index) {
|
|
240
|
+
this.sourceIndex = parseInt(index)
|
|
241
|
+
this.player.load()
|
|
242
|
+
this.player.play()
|
|
243
|
+
},
|
|
244
|
+
parseSourceType(sources) {
|
|
245
|
+
const ytRegex =
|
|
246
|
+
/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/
|
|
247
|
+
|
|
248
|
+
if (!sources || !sources.length || !sources[0].src) {
|
|
249
|
+
return null
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Parse the first src since any additional should be fallbacks of the same type
|
|
253
|
+
const src = sources[0].src
|
|
254
|
+
const type = sources[0].type
|
|
255
|
+
|
|
256
|
+
if (typeof type !== 'string') {
|
|
257
|
+
return null
|
|
258
|
+
} else if (src.match(ytRegex) || type === 'video/youtube') {
|
|
259
|
+
return 'youtube'
|
|
260
|
+
} else {
|
|
261
|
+
return 'html5'
|
|
262
|
+
}
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
beforeCreate() {},
|
|
266
|
+
beforeMount() {},
|
|
267
|
+
mounted() {},
|
|
268
|
+
beforeDestroy() {},
|
|
269
|
+
}
|
|
270
|
+
</script>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a number of seconds to a short duration format. Eg hh:mm:ss
|
|
3
|
+
*
|
|
4
|
+
* @param Integer seconds
|
|
5
|
+
*
|
|
6
|
+
* @param String The duration in "hh:mm:ss" format
|
|
7
|
+
*/
|
|
8
|
+
const playerShortDuration = function (secondsParam) {
|
|
9
|
+
let ret = '--:--:--'
|
|
10
|
+
|
|
11
|
+
if (!isNaN(secondsParam) && secondsParam !== null) {
|
|
12
|
+
ret = ''
|
|
13
|
+
let seconds = parseInt(secondsParam, 10)
|
|
14
|
+
|
|
15
|
+
const hour = Math.floor(seconds / 60 / 60)
|
|
16
|
+
seconds = seconds - hour * 60 * 60 // Subtract it from seconds
|
|
17
|
+
|
|
18
|
+
const minute = Math.floor(seconds / 60)
|
|
19
|
+
seconds = Math.floor(seconds - minute * 60) // Subtract it from seconds
|
|
20
|
+
|
|
21
|
+
if (hour > 0) {
|
|
22
|
+
ret += String(hour).padStart(2, '0') + ':'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
ret += String(minute).padStart(2, '0') + ':'
|
|
26
|
+
ret += String(seconds).padStart(2, '0')
|
|
27
|
+
}
|
|
28
|
+
return ret
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default { playerShortDuration }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
locales: {
|
|
3
|
+
'en-US': 'English',
|
|
4
|
+
'sv-SE': 'Swedish',
|
|
5
|
+
},
|
|
6
|
+
playlist: {
|
|
7
|
+
up_next: 'Up Next',
|
|
8
|
+
default_name: 'Media',
|
|
9
|
+
previous: 'Play previous item in playlist',
|
|
10
|
+
next: 'Play next item in playlist',
|
|
11
|
+
},
|
|
12
|
+
player: {
|
|
13
|
+
playback_speed: 'Playback Speed',
|
|
14
|
+
playback_decrease: 'Decrease playback speed',
|
|
15
|
+
playback_increase: 'Increase playback speed',
|
|
16
|
+
toggle_settings: 'Toggle Settings',
|
|
17
|
+
download: 'Download',
|
|
18
|
+
toggle_remote_playback: 'Toggle Remote Playback',
|
|
19
|
+
toggle_picture_in_picture: 'Toggle Picture in Picture',
|
|
20
|
+
toggle_fullscreen: 'Toggle Fullscreen',
|
|
21
|
+
toggle_cc: 'Toggle closed captions',
|
|
22
|
+
volume_slider: 'Volume Slider',
|
|
23
|
+
rewind_10: 'Rewind 10 seconds',
|
|
24
|
+
play: 'Click to play',
|
|
25
|
+
pause: 'Click to pause',
|
|
26
|
+
no_support: "Sorry, your browser doesn't support embedded videos.",
|
|
27
|
+
},
|
|
28
|
+
}
|
package/src/i18n/i18n.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import i18n from './index.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Traverses the properties of an object to return the localized value
|
|
5
|
+
*
|
|
6
|
+
* @param object obj The object to search
|
|
7
|
+
* @param string path The path in the object. Eg `a.b.c`
|
|
8
|
+
* @returns
|
|
9
|
+
*/
|
|
10
|
+
function get(obj, path) {
|
|
11
|
+
const parts = path.split('.')
|
|
12
|
+
if (parts.length == 1) {
|
|
13
|
+
return obj[parts[0]]
|
|
14
|
+
}
|
|
15
|
+
if (typeof obj[parts[0]] === 'undefined') {
|
|
16
|
+
return null
|
|
17
|
+
}
|
|
18
|
+
return get(obj[parts[0]], parts.slice(1).join('.'))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const t = (lang, path) => {
|
|
22
|
+
if (typeof i18n[lang] === 'undefined') {
|
|
23
|
+
console.warn(
|
|
24
|
+
'[VuetifyPlayer] No support for locale ' +
|
|
25
|
+
lang +
|
|
26
|
+
'. Falling back to en-US'
|
|
27
|
+
)
|
|
28
|
+
lang = 'en-US'
|
|
29
|
+
}
|
|
30
|
+
let localized = get(i18n[lang], path)
|
|
31
|
+
|
|
32
|
+
// Could not localize this path. Return the path instead of null / undefined
|
|
33
|
+
if (!localized) {
|
|
34
|
+
localized = path
|
|
35
|
+
console.warn(
|
|
36
|
+
'[VuetifyPlayer] localization path ' + path + ' does not exist'
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return localized
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { t }
|