@grfzhl/vue-hls-player 1.0.6 → 1.0.8
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 +101 -27
- package/dist/VideoPlayer/BasePlayer.vue +112 -49
- package/dist/VideoPlayer/VDefaultVideoPlayer.vue +20 -1
- package/dist/VideoPlayer/index.vue +21 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,24 +39,26 @@ npm i vue-hls-player
|
|
|
39
39
|
</script>
|
|
40
40
|
|
|
41
41
|
<template>
|
|
42
|
-
<
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
42
|
+
<div id="video-container">
|
|
43
|
+
<VideoPlayer
|
|
44
|
+
type="default"
|
|
45
|
+
@pause="processPause"
|
|
46
|
+
previewImageLink="poster.webp"
|
|
47
|
+
link="videoLink.m3u8"
|
|
48
|
+
:progress="30"
|
|
49
|
+
:isMuted="false"
|
|
50
|
+
:isControls="true"
|
|
51
|
+
:subtitles="subtitles"
|
|
52
|
+
class="customClassName"
|
|
53
|
+
/>
|
|
54
|
+
|
|
55
|
+
<VideoPlayer
|
|
56
|
+
type="preview"
|
|
57
|
+
previewImageLink="poster.webp"
|
|
58
|
+
link="videoLink.m3u8"
|
|
59
|
+
class="customClassName"
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
60
62
|
</template>
|
|
61
63
|
```
|
|
62
64
|
For **nuxt 3**, try to wrap this component in ClientOnly, images for previewImageLink need to store in public folder
|
|
@@ -97,6 +99,49 @@ function processPause(progress: number) {
|
|
|
97
99
|
console.log(progress)
|
|
98
100
|
}
|
|
99
101
|
```
|
|
102
|
+
|
|
103
|
+
**@video-ended**:
|
|
104
|
+
1. Event, called if the video has ended
|
|
105
|
+
@video-ended="videoEnded"
|
|
106
|
+
```
|
|
107
|
+
const videoEnded = (data) => {
|
|
108
|
+
console.log("video ended at time: ", data.currentTime)
|
|
109
|
+
console.log("video element ", data.video)
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**@video-fullscreen-change**:
|
|
114
|
+
1. Event, event dispatcher for detecting fullscreen change
|
|
115
|
+
@video-fullscreen-change="fullscreenChange"
|
|
116
|
+
```
|
|
117
|
+
const fullScreenAction = (fullScreenElement) => {
|
|
118
|
+
if(fullScreenElement === null) {
|
|
119
|
+
console.log("fullscreen is off")
|
|
120
|
+
} else {
|
|
121
|
+
console.log("fullscreen is on")
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
**@video-fullscreen-action**:
|
|
127
|
+
1. Event, handling the fullscreen action of the player
|
|
128
|
+
@video-fullscreen-action="fullScreenAction"
|
|
129
|
+
```
|
|
130
|
+
const fullScreenAction = (data) => {
|
|
131
|
+
if(isFullscreen.value) {
|
|
132
|
+
document.exitFullscreen();
|
|
133
|
+
} else {
|
|
134
|
+
document.getElementById("video-container").requestFullscreen()
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
**showTranscriptBlock**:
|
|
139
|
+
1. value: true or false, type: Boolean
|
|
140
|
+
|
|
141
|
+
pass true, if you want to show the transcript block.
|
|
142
|
+
To make transcripts work, your need to provide `.txt` files
|
|
143
|
+
in the same path and base-filename like the passed `subtitles` prop.
|
|
144
|
+
|
|
100
145
|
**previewImageLink**:
|
|
101
146
|
1. value: 'poster.webp', type: String
|
|
102
147
|
|
|
@@ -118,19 +163,48 @@ it can show and hide the video control panel
|
|
|
118
163
|
1. value: array of object, for subtitles to append: object has link, lang
|
|
119
164
|
|
|
120
165
|
subtitles to add as tracks to the video
|
|
166
|
+
example:
|
|
167
|
+
```
|
|
168
|
+
[{
|
|
169
|
+
link: `https://url-to-your/subtitles.vtt`,
|
|
170
|
+
label: 'English',
|
|
171
|
+
lang: 'en'
|
|
172
|
+
}]
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**isMuted**:
|
|
176
|
+
1 value: true or false, type: Boolean
|
|
177
|
+
|
|
178
|
+
it makes the video muted
|
|
179
|
+
|
|
180
|
+
**autoplay**:
|
|
181
|
+
1. value: true or false, type Boolean
|
|
182
|
+
|
|
183
|
+
it will set the native <video> autoplay property
|
|
121
184
|
|
|
122
185
|
### Last release:
|
|
186
|
+
v1.0.8
|
|
187
|
+
- Add slots to inject own elements nearby video element
|
|
188
|
+
- Add prop for autoplay video
|
|
189
|
+
|
|
190
|
+
v1.0.7
|
|
191
|
+
- Add function to handle own logic for fullscreen
|
|
192
|
+
|
|
193
|
+
v1.0.6
|
|
194
|
+
- Small fixes
|
|
195
|
+
- Remove debug log
|
|
196
|
+
|
|
123
197
|
v1.0.5
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
198
|
+
- Load transcriptions additionally to subtitles
|
|
199
|
+
- Add styled transcription block for better readability
|
|
200
|
+
- Improve interaction and dynamic params
|
|
127
201
|
|
|
128
202
|
v1.0.4
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
203
|
+
- Make subtitles dynamic
|
|
204
|
+
- Add new switch to disable the subtitle block
|
|
205
|
+
- Fix some minor issues
|
|
132
206
|
|
|
133
207
|
v1.0.3
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
208
|
+
- Removed controls in favour of themable overlay by `player.style`.
|
|
209
|
+
- Updated hls library
|
|
210
|
+
- Added styled caption overlays. Added separate container to show all captions.
|
|
@@ -1,38 +1,45 @@
|
|
|
1
1
|
<template>
|
|
2
2
|
<div class="video-container">
|
|
3
|
-
<media-
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
:
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
3
|
+
<div class="media-container" id="hls-player-media-container">
|
|
4
|
+
<slot name="before-media"></slot>
|
|
5
|
+
<media-theme-sutro class="video-player-theme-container">
|
|
6
|
+
<video
|
|
7
|
+
class="hls-player"
|
|
8
|
+
slot="media"
|
|
9
|
+
@pause="pause"
|
|
10
|
+
@keyup="changeSpeed"
|
|
11
|
+
@ended="onVideoEnd"
|
|
12
|
+
@seek="seekVideo"
|
|
13
|
+
ref="video"
|
|
14
|
+
:poster="previewImageLink"
|
|
15
|
+
:controls="false"
|
|
16
|
+
:title="title"
|
|
17
|
+
controlslist="nodownload"
|
|
18
|
+
playsinline
|
|
19
|
+
crossorigin
|
|
20
|
+
:muted="mutedAttr"
|
|
21
|
+
:autoplay="autoplay && isMuted"
|
|
22
|
+
>
|
|
23
|
+
<source
|
|
24
|
+
:src="link"
|
|
25
|
+
type="application/x-mpegURL"
|
|
26
|
+
/>
|
|
27
|
+
<track
|
|
28
|
+
v-if="subtitles.length"
|
|
29
|
+
v-for="(subtitle, i) in subtitles"
|
|
30
|
+
:src="subtitle.link"
|
|
31
|
+
kind="subtitles"
|
|
32
|
+
:srclang="subtitle.lang"
|
|
33
|
+
:label="subtitle.label" :default="i === 0" />
|
|
34
|
+
</video>
|
|
35
|
+
</media-theme-sutro>
|
|
36
|
+
<div class="custom-subtitles" v-show="(isFullscreen) || (!showTranscriptBlock)">
|
|
37
|
+
<div class="subtitle-text" ref="subtitlesContainer" style="display: none;"></div>
|
|
38
|
+
</div>
|
|
39
|
+
<slot name="after-media"></slot>
|
|
34
40
|
</div>
|
|
35
41
|
</div>
|
|
42
|
+
<slot name="before-transcripts"></slot>
|
|
36
43
|
<SubtitleBlock
|
|
37
44
|
:subtitle="currentSubtitle"
|
|
38
45
|
:cursor="videoCursor"
|
|
@@ -40,10 +47,12 @@
|
|
|
40
47
|
@seek="seekVideo"
|
|
41
48
|
@toggleTranscript="toggleTranscript">
|
|
42
49
|
</SubtitleBlock>
|
|
50
|
+
<slot name="after-transcripts"></slot>
|
|
51
|
+
<slot name="default"></slot>
|
|
43
52
|
</template>
|
|
44
53
|
|
|
45
54
|
<script setup>
|
|
46
|
-
import { onMounted, onUpdated, ref, onUnmounted, computed } from 'vue'
|
|
55
|
+
import { onMounted, onUpdated, ref, onUnmounted, computed, watch } from 'vue'
|
|
47
56
|
import Hls from 'hls.js'
|
|
48
57
|
import 'player.style/sutro';
|
|
49
58
|
import SubtitleBlock from './SubtitleBlock.vue';
|
|
@@ -69,6 +78,10 @@ const props = defineProps({
|
|
|
69
78
|
type: Boolean,
|
|
70
79
|
default: false
|
|
71
80
|
},
|
|
81
|
+
autoplay: {
|
|
82
|
+
type: Boolean,
|
|
83
|
+
default: false
|
|
84
|
+
},
|
|
72
85
|
/**
|
|
73
86
|
* array of object, for
|
|
74
87
|
* subtitles to append:
|
|
@@ -92,23 +105,57 @@ const props = defineProps({
|
|
|
92
105
|
}
|
|
93
106
|
})
|
|
94
107
|
|
|
95
|
-
const emit = defineEmits(['pause', 'video-ended', 'video-fullscreen-change'])
|
|
108
|
+
const emit = defineEmits(['pause', 'video-ended', 'video-fullscreen-change', 'video-fullscreen-action'])
|
|
96
109
|
const video = ref(null)
|
|
97
110
|
const subtitlesContainer = ref(null)
|
|
98
111
|
const currentSubtitleLang = ref(null)
|
|
99
112
|
const videoCursor = ref(0)
|
|
100
113
|
const isFullscreen = ref(false);
|
|
101
114
|
|
|
115
|
+
const videoElement = defineModel()
|
|
116
|
+
|
|
102
117
|
onMounted(() => {
|
|
103
118
|
prepareVideoPlayer()
|
|
104
119
|
if (video.value) {
|
|
105
|
-
|
|
120
|
+
|
|
121
|
+
// pass video element as reference to model
|
|
122
|
+
if (!videoElement.value) {
|
|
123
|
+
videoElement.value = video.value;
|
|
124
|
+
}
|
|
125
|
+
|
|
106
126
|
video.value.addEventListener('timeupdate', updateCurrentTime);
|
|
107
127
|
document.addEventListener('fullscreenchange', onFullscreenChange);
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* overwrite player.style video fullscreen button
|
|
131
|
+
* to inject own fullscreen logic
|
|
132
|
+
*/
|
|
133
|
+
const observer = new MutationObserver((mutationsList, observer) => {
|
|
134
|
+
const mediaTheme = document.querySelector('.video-player-theme-container');
|
|
135
|
+
|
|
136
|
+
if (mediaTheme && mediaTheme.shadowRoot) {
|
|
137
|
+
const fullscreenButton = mediaTheme.shadowRoot.querySelector('media-fullscreen-button');
|
|
138
|
+
if (fullscreenButton) {
|
|
139
|
+
fullscreenButton.handleClick = (event) => {
|
|
140
|
+
event.preventDefault();
|
|
141
|
+
event.stopPropagation();
|
|
142
|
+
event.stopImmediatePropagation();
|
|
143
|
+
emit('video-fullscreen-action', event)
|
|
144
|
+
}
|
|
145
|
+
observer.disconnect();
|
|
146
|
+
} else {
|
|
147
|
+
console.error('Button not found in Shadow DOM!');
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
console.error('Shadow Root not found!');
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
108
154
|
}
|
|
109
155
|
})
|
|
110
156
|
|
|
111
157
|
onUpdated(() => {
|
|
158
|
+
|
|
112
159
|
})
|
|
113
160
|
|
|
114
161
|
onUnmounted(() => {
|
|
@@ -118,6 +165,11 @@ onUnmounted(() => {
|
|
|
118
165
|
}
|
|
119
166
|
});
|
|
120
167
|
|
|
168
|
+
const mutedAttr = computed(() => {
|
|
169
|
+
// autoplay is only possible when muted
|
|
170
|
+
return (props.autoplay || props.isMuted);
|
|
171
|
+
})
|
|
172
|
+
|
|
121
173
|
const currentSubtitle = computed(() => {
|
|
122
174
|
if(props.subtitles) {
|
|
123
175
|
const current = props.subtitles.filter((subt) => {
|
|
@@ -128,12 +180,17 @@ const currentSubtitle = computed(() => {
|
|
|
128
180
|
return null
|
|
129
181
|
})
|
|
130
182
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
183
|
+
watch([props, videoElement], (a) => {
|
|
184
|
+
if(a[0].autoplay && a[1]) {
|
|
185
|
+
// autoplay is only possible when muted
|
|
186
|
+
a[1].muted = true
|
|
187
|
+
setTimeout(() => {
|
|
188
|
+
a[1].play();
|
|
189
|
+
}, 200)
|
|
190
|
+
}
|
|
191
|
+
})
|
|
134
192
|
|
|
135
193
|
function onFullscreenChange() {
|
|
136
|
-
checkFullscreen();
|
|
137
194
|
emit('video-fullscreen-change', document.fullscreenElement)
|
|
138
195
|
};
|
|
139
196
|
|
|
@@ -168,25 +225,31 @@ function prepareVideoPlayer() {
|
|
|
168
225
|
track.addEventListener("cuechange", () => {
|
|
169
226
|
const activeCues = track.activeCues;
|
|
170
227
|
currentSubtitleLang.value = track.language
|
|
171
|
-
if
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
228
|
+
if(subtitlesContainer.value) {
|
|
229
|
+
if (activeCues && activeCues.length > 0) {
|
|
230
|
+
subtitlesContainer.value.textContent = activeCues[0].text
|
|
231
|
+
subtitlesContainer.value.style.display = "block";
|
|
232
|
+
} else {
|
|
233
|
+
subtitlesContainer.value.style.display = "none";
|
|
234
|
+
}
|
|
176
235
|
}
|
|
177
236
|
});
|
|
178
237
|
if (track.mode !== previousModes[index]) {
|
|
179
238
|
if (track.mode === "showing") {
|
|
180
239
|
const activeCues = track.activeCues;
|
|
181
240
|
currentSubtitleLang.value = track.language
|
|
182
|
-
if
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
241
|
+
if(subtitlesContainer.value) {
|
|
242
|
+
if (activeCues && activeCues.length > 0) {
|
|
243
|
+
subtitlesContainer.value.style.display = "block";
|
|
244
|
+
subtitlesContainer.value.textContent = activeCues[0].text
|
|
245
|
+
} else {
|
|
246
|
+
subtitlesContainer.value.style.display = "none";
|
|
247
|
+
}
|
|
187
248
|
}
|
|
188
249
|
} else {
|
|
189
|
-
subtitlesContainer.value
|
|
250
|
+
if(subtitlesContainer.value) {
|
|
251
|
+
subtitlesContainer.value.style.display = "none";
|
|
252
|
+
}
|
|
190
253
|
}
|
|
191
254
|
previousModes[index] = track.mode;
|
|
192
255
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
:link="link"
|
|
5
5
|
:progress="progress"
|
|
6
6
|
:isMuted="isMuted"
|
|
7
|
+
:autoplay="autoplay"
|
|
7
8
|
:isControls="isControls"
|
|
8
9
|
:onVideoEnd="onVideoEnd"
|
|
9
10
|
:isFullscreen="isFullscreen"
|
|
@@ -11,14 +12,24 @@
|
|
|
11
12
|
@pause="pause"
|
|
12
13
|
@video-ended="onVideoEnd"
|
|
13
14
|
@video-fullscreen-change="onFullscreenChange"
|
|
14
|
-
|
|
15
|
+
@video-fullscreen-action="oVideoFullscreenAction"
|
|
16
|
+
v-model="videoElement"
|
|
17
|
+
>
|
|
18
|
+
<template v-slot:before-media><slot name="before-media"></slot></template>
|
|
19
|
+
<template v-slot:after-media><slot name="after-media"></slot></template>
|
|
20
|
+
<template v-slot:before-transcripts><slot name="before-transcripts"></slot></template>
|
|
21
|
+
<template v-slot:after-transcripts><slot name="after-transcripts"></slot></template>
|
|
22
|
+
</BasePlayer>
|
|
15
23
|
</template>
|
|
16
24
|
|
|
17
25
|
<script setup>
|
|
18
26
|
import BasePlayer from './BasePlayer.vue'
|
|
27
|
+
import { ref } from 'vue'
|
|
19
28
|
|
|
20
29
|
const emit = defineEmits(['pause', 'video-ended', 'video-fullscreen-change'])
|
|
21
30
|
|
|
31
|
+
const videoElement = ref(null);
|
|
32
|
+
|
|
22
33
|
defineProps({
|
|
23
34
|
previewImageLink: {
|
|
24
35
|
type: String,
|
|
@@ -36,6 +47,10 @@ defineProps({
|
|
|
36
47
|
type: Boolean,
|
|
37
48
|
default: false
|
|
38
49
|
},
|
|
50
|
+
autoplay: {
|
|
51
|
+
type: Boolean,
|
|
52
|
+
default: false
|
|
53
|
+
},
|
|
39
54
|
isControls: {
|
|
40
55
|
type: Boolean,
|
|
41
56
|
default: true
|
|
@@ -62,4 +77,8 @@ function onVideoEnd(data) {
|
|
|
62
77
|
function onFullscreenChange(data) {
|
|
63
78
|
emit('video-fullscreen-change', data);
|
|
64
79
|
}
|
|
80
|
+
|
|
81
|
+
function oVideoFullscreenAction(data) {
|
|
82
|
+
emit('video-fullscreen-action', data)
|
|
83
|
+
}
|
|
65
84
|
</script>
|
|
@@ -4,14 +4,22 @@
|
|
|
4
4
|
@pause="pause"
|
|
5
5
|
@video-fullscreen-change="onVideoFullScreenChange"
|
|
6
6
|
@video-ended="onVideoEnd"
|
|
7
|
+
@video-fullscreen-action="oVideoFullscreenAction"
|
|
7
8
|
:previewImageLink="previewImageLink"
|
|
8
9
|
:showTranscriptBlock="showTranscriptBlock"
|
|
9
10
|
:isFullscreen="isFullscreen"
|
|
10
11
|
:link="link"
|
|
11
12
|
:progress="progress"
|
|
12
13
|
:isMuted="isMuted"
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
:autoplay="autoplay"
|
|
15
|
+
v-model="videoElement"
|
|
16
|
+
>
|
|
17
|
+
<template v-slot:before-media><slot name="before-media"></slot></template>
|
|
18
|
+
<template v-slot:after-media><slot name="after-media"></slot></template>
|
|
19
|
+
<template v-slot:before-transcripts><slot name="before-transcripts"></slot></template>
|
|
20
|
+
<template v-slot:after-transcripts><slot name="after-transcripts"></slot></template>
|
|
21
|
+
</VDefaultVideoPlayer>
|
|
22
|
+
|
|
15
23
|
<VPreviewVideoPlayer
|
|
16
24
|
v-else-if="type === 'preview'"
|
|
17
25
|
:previewImageLink="previewImageLink"
|
|
@@ -22,8 +30,11 @@
|
|
|
22
30
|
<script setup>
|
|
23
31
|
import VDefaultVideoPlayer from './VDefaultVideoPlayer.vue'
|
|
24
32
|
import VPreviewVideoPlayer from './VPreviewVideoPlayer.vue'
|
|
33
|
+
import { ref } from 'vue'
|
|
25
34
|
|
|
26
|
-
const emit = defineEmits(['pause', 'video-ended', 'video-fullscreen-change'])
|
|
35
|
+
const emit = defineEmits(['pause', 'video-ended', 'video-fullscreen-change', 'video-fullscreen-action'])
|
|
36
|
+
|
|
37
|
+
const videoElement = ref(null);
|
|
27
38
|
|
|
28
39
|
defineProps({
|
|
29
40
|
previewImageLink: {
|
|
@@ -46,6 +57,10 @@ defineProps({
|
|
|
46
57
|
type: Boolean,
|
|
47
58
|
default: false
|
|
48
59
|
},
|
|
60
|
+
autoplay: {
|
|
61
|
+
type: Boolean,
|
|
62
|
+
default: false
|
|
63
|
+
},
|
|
49
64
|
isControls: {
|
|
50
65
|
type: Boolean,
|
|
51
66
|
default: true
|
|
@@ -69,4 +84,7 @@ function onVideoFullScreenChange(data) {
|
|
|
69
84
|
function onVideoEnd(data) {
|
|
70
85
|
emit('video-ended', data);
|
|
71
86
|
}
|
|
87
|
+
function oVideoFullscreenAction(data) {
|
|
88
|
+
emit('video-fullscreen-action', data)
|
|
89
|
+
}
|
|
72
90
|
</script>
|