@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 CHANGED
@@ -39,24 +39,26 @@ npm i vue-hls-player
39
39
  </script>
40
40
 
41
41
  <template>
42
- <VideoPlayer
43
- type="default"
44
- @pause="processPause"
45
- previewImageLink="poster.webp"
46
- link="videoLink.m3u8"
47
- :progress="30"
48
- :isMuted="false"
49
- :isControls="true"
50
- :subtitles="subtitles"
51
- class="customClassName"
52
- />
53
-
54
- <VideoPlayer
55
- type="preview"
56
- previewImageLink="poster.webp"
57
- link="videoLink.m3u8"
58
- class="customClassName"
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
- 1. Load transcriptions additionally to subtitles
125
- 2. Add styled transcription block for better readability
126
- 3. Improve interaction and dynamic params
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
- 1. Make subtitles dynamic
130
- 2. Add new switch to disable the subtitle block
131
- 3. Fix some minor issues
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
- 1. Removed controls in favour of themable overlay by `player.style`.
135
- 2. Updated hls library
136
- 3. Added styled caption overlays. Added separate container to show all captions.
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-theme-sutro class="video-player-theme-container">
4
- <video
5
- class="hls-player"
6
- slot="media"
7
- @pause="pause"
8
- @keyup="changeSpeed"
9
- @ended="onVideoEnd"
10
- @seek="seekVideo"
11
- ref="video"
12
- :poster="previewImageLink"
13
- :controls="false"
14
- :title="title"
15
- controlslist="nodownload"
16
- playsinline
17
- crossorigin
18
- >
19
- <source
20
- :src="link"
21
- type="application/x-mpegURL"
22
- />
23
- <track
24
- v-if="subtitles.length"
25
- v-for="(subtitle, i) in subtitles"
26
- :src="subtitle.link"
27
- kind="subtitles"
28
- :srclang="subtitle.lang"
29
- :label="subtitle.label" :default="i === 0" />
30
- </video>
31
- </media-theme-sutro>
32
- <div class="custom-subtitles" v-show="!showTranscriptBlock">
33
- <div class="subtitle-text" ref="subtitlesContainer" style="display: none;"></div>
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
- checkFullscreen();
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
- function checkFullscreen() {
132
- isFullscreen.value = !!document.fullscreenElement;
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 (activeCues && activeCues.length > 0) {
172
- subtitlesContainer.value.textContent = activeCues[0].text
173
- subtitlesContainer.value.style.display = "block";
174
- } else {
175
- subtitlesContainer.value.style.display = "none";
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 (activeCues && activeCues.length > 0) {
183
- subtitlesContainer.value.style.display = "block";
184
- subtitlesContainer.value.textContent = activeCues[0].text
185
- } else {
186
- subtitlesContainer.value.style.display = "none";
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.style.display = "none";
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>
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@grfzhl/vue-hls-player",
3
3
  "private": false,
4
- "version": "1.0.6",
4
+ "version": "1.0.8",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist"