@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
|
|
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 ===
|
|
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
|
-
|
|
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
|
-
|
|
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 ===
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ===
|
|
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
|
-
|
|
866
|
-
|
|
867
|
-
|
|
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
|
}
|