diff --git a/Frontend-Learner/components/classroom/VideoPlayer.vue b/Frontend-Learner/components/classroom/VideoPlayer.vue index 4147ab09..90d5e531 100644 --- a/Frontend-Learner/components/classroom/VideoPlayer.vue +++ b/Frontend-Learner/components/classroom/VideoPlayer.vue @@ -12,7 +12,7 @@ const props = defineProps<{ const emit = defineEmits<{ (e: 'timeupdate', currentTime: number, duration: number): void; (e: 'ended'): void; - (e: 'loadedmetadata'): void; + (e: 'loadedmetadata', duration: number): void; }>(); const videoRef = ref(null); @@ -39,7 +39,115 @@ const formatTime = (time: number) => { const currentTimeDisplay = computed(() => formatTime(currentTime.value)); const durationDisplay = computed(() => formatTime(duration.value || 0)); +// YouTube Helper Logic +const isYoutube = computed(() => { + const s = props.src.toLowerCase(); + return s.includes('youtube.com') || s.includes('youtu.be'); +}); + +const youtubeEmbedUrl = computed(() => { + if (!isYoutube.value) return ''; + let videoId = ''; + + // Extract Video ID + if (props.src.includes('youtu.be')) { + videoId = props.src.split('youtu.be/')[1]?.split('?')[0]; + } else { + const urlParams = new URLSearchParams(props.src.split('?')[1]); + videoId = urlParams.get('v') || ''; + } + + // Return Embed URL with enablejsapi=1 + return `https://www.youtube.com/embed/${videoId}?enablejsapi=1&rel=0`; +}); + +// YouTube API Tracking +let ytPlayer: any = null; +let ytInterval: any = null; + +const initYoutubeAPI = () => { + if (!isYoutube.value || typeof window === 'undefined') return; + + // Load API Script if not exists + if (!(window as any).YT) { + const tag = document.createElement('script'); + tag.src = "https://www.youtube.com/iframe_api"; + const firstScriptTag = document.getElementsByTagName('script')[0]; + firstScriptTag.parentNode?.insertBefore(tag, firstScriptTag); + } + + const setupPlayer = () => { + ytPlayer = new (window as any).YT.Player('youtube-iframe', { + events: { + 'onReady': (event: any) => { + duration.value = event.target.getDuration(); + + // Resume Logic for YouTube + if (props.initialSeekTime && props.initialSeekTime > 0) { + event.target.seekTo(props.initialSeekTime, true); + } + + emit('loadedmetadata', event.target.getDuration()); + }, + 'onStateChange': (event: any) => { + if (event.data === (window as any).YT.PlayerState.PLAYING) { + startYTTracking(); + } else { + stopYTTracking(); + } + if (event.data === (window as any).YT.PlayerState.ENDED) { + emit('ended'); + } + } + } + }); + }; + + if ((window as any).YT && (window as any).YT.Player) { + setupPlayer(); + } else { + (window as any).onYouTubeIframeAPIReady = setupPlayer; + } +}; + +const startYTTracking = () => { + stopYTTracking(); + ytInterval = setInterval(() => { + if (ytPlayer && ytPlayer.getCurrentTime) { + currentTime.value = ytPlayer.getCurrentTime(); + emit('timeupdate', currentTime.value, duration.value); + } + }, 1000); // Check every second +}; + +const stopYTTracking = () => { + if (ytInterval) clearInterval(ytInterval); +}; + +onMounted(() => { + if (isYoutube.value) initYoutubeAPI(); +}); + +onUnmounted(() => { + stopYTTracking(); +}); + +// Watch for src change to re-init +watch(() => props.src, () => { + if (isYoutube.value) { + setTimeout(initYoutubeAPI, 500); + } +}); + const togglePlay = () => { + if (isYoutube.value) { + if (ytPlayer && ytPlayer.getPlayerState) { + const state = ytPlayer.getPlayerState(); + if (state === 1) ytPlayer.pauseVideo(); + else ytPlayer.playVideo(); + } + return; + } if (!videoRef.value) return; if (isPlaying.value) videoRef.value.pause(); else videoRef.value.play(); @@ -63,7 +171,7 @@ const handleLoadedMetadata = () => { videoRef.value.currentTime = seekTo; } } - emit('loadedmetadata'); + emit('loadedmetadata', videoRef.value?.duration || 0); }; const handleEnded = () => { @@ -72,7 +180,7 @@ const handleEnded = () => { }; const seek = (e: MouseEvent) => { - if (!videoRef.value) return; + if (!videoRef.value || !isFinite(videoRef.value.duration)) return; const rect = (e.currentTarget as HTMLElement).getBoundingClientRect(); const percent = (e.clientX - rect.left) / rect.width; videoRef.value.currentTime = percent * videoRef.value.duration; @@ -102,38 +210,52 @@ watch([volume, isMuted], () => {