linguaielts-api / fronted /src /composables /useShadowingSession.js
AnhviNguyen
shadowing
02f8344
Raw
History Blame Contribute Delete
3.28 kB
/**
* Shared shadowing session — segment sync, continuous playback.
*/
import { ref, computed, watch } from 'vue'
import { useYoutubePlayer } from '@/composables/useYoutubePlayer.js'
import { loadProgress, saveProgress } from '@/utils/shadowingProgress.js'
export function useShadowingSession(videoDataRef) {
const yt = useYoutubePlayer()
const currentIndex = ref(0)
const playbackRate = ref(1)
const showVideo = ref(true)
const showTranslation = ref(true)
const flaggedIds = ref([])
const segments = computed(() => videoDataRef.value?.segments ?? [])
const currentSegment = computed(() => segments.value[currentIndex.value] ?? null)
function loadSaved(videoId) {
const p = loadProgress(videoId)
currentIndex.value = Math.min(p.currentSegment, Math.max(0, segments.value.length - 1))
flaggedIds.value = [...(p.flaggedSegments || [])]
}
function persist(videoId) {
saveProgress(videoId, {
currentSegment: currentIndex.value,
flaggedSegments: [...flaggedIds.value],
})
}
function findIndexAtTime(time) {
const segs = segments.value
if (!segs.length) return 0
let idx = 0
for (let i = 0; i < segs.length; i++) {
if (time >= (segs[i].start ?? 0) - 0.02) idx = i
}
return idx
}
function syncIndexFromTime(time) {
const idx = findIndexAtTime(time)
if (idx !== currentIndex.value) {
currentIndex.value = idx
}
return idx
}
/**
* @param {number} index
* @param {{ autoPlay?: boolean | null }} opts — null = giữ trạng thái play/pause hiện tại
*/
function playSegment(index = currentIndex.value, { autoPlay = true } = {}) {
const seg = segments.value[index]
if (!seg) return
currentIndex.value = index
yt.seekTo(seg.start)
if (autoPlay === true) yt.play()
else if (autoPlay === false) yt.pause()
}
function nextSegment() {
if (currentIndex.value < segments.value.length - 1) {
playSegment(currentIndex.value + 1, { autoPlay: playing.value })
}
}
function prevSegment() {
if (currentIndex.value > 0) {
playSegment(currentIndex.value - 1, { autoPlay: playing.value })
}
}
function replaySegment() {
playSegment(currentIndex.value, { autoPlay: playing.value })
}
function toggleFlag(id) {
const ids = new Set(flaggedIds.value)
if (ids.has(id)) ids.delete(id)
else ids.add(id)
flaggedIds.value = [...ids]
const vid = videoDataRef.value?.video_id
if (vid) persist(vid)
}
/** Chỉ cập nhật highlight theo thời gian — không pause, không seek. */
function onTimeUpdate(time) {
syncIndexFromTime(time)
}
watch(playbackRate, (r) => yt.setRate(r))
watch(currentIndex, (i) => {
const vid = videoDataRef.value?.video_id
if (vid) saveProgress(vid, { currentSegment: i })
})
const playing = yt.playing
return {
playing,
playerReady: yt.ready,
mountPlayer: yt.mount,
destroyPlayer: yt.destroy,
currentIndex,
playbackRate,
showVideo,
showTranslation,
flaggedIds,
segments,
currentSegment,
loadSaved,
persist,
playSegment,
nextSegment,
prevSegment,
replaySegment,
toggleFlag,
onTimeUpdate,
pause: yt.pause,
play: yt.play,
}
}