@grfzhl/vue-hls-player 1.1.23 → 1.1.25

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
@@ -207,6 +207,14 @@ At the moment the following attribute are supported:
207
207
  ```
208
208
 
209
209
  ### Last release:
210
+ v1.1.25
211
+ - Decouple audio language switching from subtitle selection.
212
+ - Preserve user-selected subtitle language across audio changes and HLS source reloads.
213
+ - Prevent unintended subtitle resets caused by audio language updates.
214
+ v1.1.24
215
+ - Add user-initiated language-changed emit.
216
+ - Fix unwanted language re-sync on player init.
217
+ - Improve audio/subtitle switch stability.
210
218
  v1.1.23
211
219
  - Fix missing property for subtitles in vue definition
212
220
  - Clean up code
@@ -71,7 +71,7 @@
71
71
  <slot name="between-video-and-transcript"></slot>
72
72
  <slot name="before-transcripts"></slot>
73
73
  <SubtitleBlock
74
- :key="currentLang"
74
+ :key="`${currentLang}-${currentSubtitleLang}`"
75
75
  ref="transcriptRef"
76
76
  :subtitle="currentSubtitle"
77
77
  :cursor="videoCursor"
@@ -167,7 +167,7 @@ const props = defineProps({
167
167
  }
168
168
  })
169
169
 
170
- const emit = defineEmits(['pause', 'video-ended', 'video-fullscreen-change', 'pointer-update'])
170
+ const emit = defineEmits(['pause', 'video-ended', 'video-fullscreen-change', 'pointer-update', 'language-changed'])
171
171
  const video = ref(null)
172
172
  const subtitlesContainer = ref(null)
173
173
  const currentSubtitleLang = ref(null)
@@ -185,11 +185,50 @@ let currentTime = 0
185
185
  let hls = null
186
186
  let buttonElement = null
187
187
  // --- lang switcher ---
188
- const currentLang = ref(props.defaultLang || 'en')
188
+ const currentAudioLang = ref(props.defaultLang || 'en')
189
+
190
+ const sessionSubtitleLang = 'vp_subtitle_lang'
191
+ function initSubtitleLang() {
192
+ const stored = sessionStorage.getItem(sessionSubtitleLang)
193
+
194
+ if (stored && props.subtitles.some(s => s.lang === stored)) {
195
+ currentSubtitleLang.value = stored
196
+ } else {
197
+ currentSubtitleLang.value =
198
+ props.subtitles.find(s => s.lang === props.defaultLang)?.lang
199
+ || props.subtitles[0]?.lang
200
+ }
201
+
202
+ applySubtitleTrack(currentSubtitleLang.value)
203
+ }
204
+
205
+ function applySubtitleTrack(lang) {
206
+ Array.from(video.value?.textTracks || []).forEach(track => {
207
+ const tLang = (track.language || track.srclang || '').toLowerCase()
208
+ track.mode = tLang === lang.toLowerCase() ? 'showing' : 'disabled'
209
+ })
210
+ }
211
+
212
+
213
+
214
+ const isUserInitiatedLangChange = ref(false)
215
+
216
+ let initialLoad = true;
217
+ let defaultApplied = false
218
+
219
+ watch(
220
+ () => props.defaultLang,
221
+ (lang) => {
222
+ if (defaultApplied) return
223
+ currentAudioLang.value = lang
224
+ defaultApplied = true
225
+ }
226
+ )
227
+
189
228
  // --- Remember and restore last subtitle language ---
190
229
  async function selectLang(lang) {
191
230
  if (!video.value || !hls) return;
192
- if (lang === currentLang.value) return;
231
+ if (lang === currentAudioLang.value) return;
193
232
 
194
233
  const newSource = props.multiLangSources?.find(s => s.lang === lang);
195
234
  if (!newSource?.file_url) {
@@ -203,7 +242,7 @@ async function selectLang(lang) {
203
242
  const wasMuted = video.value.muted;
204
243
  const rate = video.value.playbackRate;
205
244
 
206
- currentLang.value = lang;
245
+ currentAudioLang.value = lang;
207
246
  // Switching to ${lang}...
208
247
 
209
248
  try {
@@ -225,13 +264,17 @@ async function selectLang(lang) {
225
264
  // Matching subtitles for matchLang
226
265
  Array.from(video.value?.textTracks || []).forEach(track => {
227
266
  const tLang = (track.language || track.srclang || '').toLowerCase();
228
- console.log('[LangSwitch] Track', tLang, 'current mode:', track.mode);
229
267
  track.mode = tLang === matchLang ? 'showing' : 'disabled';
230
- console.log('[LangSwitch] Track', tLang, '→ new mode:', track.mode);
231
268
  });
232
269
 
233
270
  currentSubtitleLang.value = lang;
234
- console.log('[LangSwitch] currentSubtitleLang set to', currentSubtitleLang.value);
271
+ // Emit ONLY if user initiated the change
272
+ setTimeout(() => {
273
+ if (isUserInitiatedLangChange.value) {
274
+ emit('language-changed', lang);
275
+ isUserInitiatedLangChange.value = false; // Reset flag
276
+ }
277
+ }, 100);
235
278
  });
236
279
  // Attach HLS
237
280
  await hls.loadSource(newSource.file_url);
@@ -256,7 +299,7 @@ function updateLangMenuState() {
256
299
  const langElement = li.querySelector('span[data-lang]')
257
300
  const liLang = langElement?.dataset?.lang;
258
301
  // const liLang = li.textContent.trim().toLowerCase();
259
- li.classList.toggle('active', liLang === currentLang.value.toLowerCase());
302
+ li.classList.toggle('active', liLang === currentAudioLang.value.toLowerCase());
260
303
  });
261
304
 
262
305
  // --- Subtitles ---
@@ -325,18 +368,20 @@ const mutedAttr = computed(() => {
325
368
  return (props.autoplay || props.isMuted);
326
369
  })
327
370
 
371
+
328
372
  const currentSubtitle = computed(() => {
329
- if(props.subtitles) {
330
- const current = props.subtitles.filter((subt) => {
331
- if(currentSubtitleLang.value) {
332
- return subt.lang === currentSubtitleLang.value
333
- } else {
334
- return subt.lang === "en"
335
- }
336
- })
337
- return current.length ? current[0] : null
373
+ if (!props.subtitles?.length) return null;
374
+
375
+ if (currentSubtitleLang.value) {
376
+ const match = props.subtitles.find(s => s.lang === currentSubtitleLang.value);
377
+ if (match) return match;
338
378
  }
339
- return null
379
+
380
+ // Fallback
381
+ const defaultMatch = props.subtitles.find(s => s.lang === props.defaultLang);
382
+ if (defaultMatch) return defaultMatch;
383
+
384
+ return props.subtitles[0];
340
385
  })
341
386
 
342
387
  watch(() => props.autoplay, (a) => {
@@ -559,16 +604,10 @@ function prepareVideoPlayer(link) {
559
604
 
560
605
  // Initialize subtitles
561
606
  if (props.subtitles?.length > 0) {
562
- const defaultSub = props.subtitles.find(s => s.lang === props.defaultLang);
563
- currentSubtitleLang.value = defaultSub ? defaultSub.lang : props.subtitles[0].lang;
564
- Array.from(video.value?.textTracks || []).forEach(track => {
565
- const tLang = (track.language || track.srclang || '').toLowerCase();
566
- console.log('[SubtitleInit] Track found:', tLang, '->', tLang === currentSubtitleLang.value.toLowerCase() ? 'showing' : 'disabled');
567
- track.mode = tLang === currentSubtitleLang.value.toLowerCase() ? 'showing' : 'disabled';
568
- });
607
+ initSubtitleLang();
569
608
  }
570
609
 
571
- selectLang(props.defaultLang);
610
+ selectLang(props.defaultLang);
572
611
  // HLS attached to <video>
573
612
  hls.recoverMediaError();
574
613
 
@@ -844,10 +883,11 @@ const mutationObserver = (mutationsList, observer) => {
844
883
  <span data-lang="${ src.lang }">${src.label || src.lang.toUpperCase()}</span>
845
884
  <span class="icon">${renderIcon()}</span>
846
885
  `;
847
- if (src.lang === currentLang.value) li.classList.add('active');
886
+ if (src.lang === currentAudioLang.value) li.classList.add('active');
848
887
  li.addEventListener('click', () => {
849
888
  audioCol.querySelectorAll('li').forEach(el => el.classList.remove('active'));
850
889
  li.classList.add('active');
890
+ isUserInitiatedLangChange.value = true;
851
891
  selectLang(src.lang);
852
892
  menu.style.display = 'none';
853
893
  });
@@ -862,14 +902,10 @@ const mutationObserver = (mutationsList, observer) => {
862
902
 
863
903
  if (sub.lang === currentSubtitleLang.value) li.classList.add('active');
864
904
  li.addEventListener('click', () => {
865
- Array.from(video.value?.textTracks || []).forEach(track => {
866
- const tLang = (track.language || track.srclang || '').toLowerCase();
867
- track.mode = tLang === sub.lang.toLowerCase() ? 'showing' : 'disabled';
868
- });
869
- subCol.querySelectorAll('li').forEach(el => el.classList.remove('active'));
870
- li.classList.add('active');
871
- menu.style.display = 'none';
872
- currentSubtitleLang.value = sub.lang;
905
+ currentSubtitleLang.value = sub.lang
906
+ sessionStorage.setItem(sessionSubtitleLang, sub.lang)
907
+ applySubtitleTrack(sub.lang)
908
+ menu.style.display = 'none'
873
909
  });
874
910
  subCol.appendChild(li);
875
911
  });
@@ -17,6 +17,7 @@
17
17
  @pause="pause"
18
18
  @video-ended="onVideoEnd"
19
19
  @video-fullscreen-change="onFullscreenChange"
20
+ @language-changed="onLanguageChanged"
20
21
  v-model="videoElement"
21
22
  ref="childRef"
22
23
  >
@@ -32,7 +33,7 @@
32
33
  import BasePlayer from './BasePlayer.vue'
33
34
  import { ref, toRef } from 'vue'
34
35
 
35
- const emit = defineEmits(['pause', 'video-ended', 'video-fullscreen-change'])
36
+ const emit = defineEmits(['pause', 'video-ended', 'video-fullscreen-change', 'language-changed'])
36
37
 
37
38
  const videoElement = ref(null);
38
39
  const childRef = ref(null)
@@ -114,6 +115,9 @@ function onFullscreenChange(data) {
114
115
  emit('video-fullscreen-change', data);
115
116
  }
116
117
 
118
+ function onLanguageChanged(data) {
119
+ emit('language-changed', data);
120
+ }
117
121
  function startFullscreen() {
118
122
  childRef.value.startFullscreen();
119
123
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@grfzhl/vue-hls-player",
3
3
  "private": false,
4
- "version": "1.1.23",
4
+ "version": "1.1.25",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist"