// Video page functionality document.addEventListener('DOMContentLoaded', () => { const playerElement = document.getElementById('youtube-player'); const searchInput = document.getElementById('search-input'); const searchButton = document.getElementById('search-button'); const transcriptContainer = document.getElementById('transcript-container'); const loadingIndicator = document.getElementById('loading'); const toggleTranscriptButton = document.getElementById('toggle-transcript'); let transcriptSegments = []; let ytPlayer = null; let isProcessingUrl = false; // Check if there's a search query or timestamp in the URL const urlParams = new URLSearchParams(window.location.search); const searchQuery = urlParams.get('q'); const processingUrl = urlParams.get('processing'); const startTime = urlParams.get('t'); // Format time to display as HH:MM:SS function formatTime(seconds) { const hours = Math.floor(seconds / 3600); const mins = Math.floor((seconds % 3600) / 60); const secs = Math.floor(seconds % 60); if (hours > 0) { return `${hours}:${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`; } else { return `${mins}:${secs.toString().padStart(2, '0')}`; } } // Handle error display function handleError(error) { console.error(error); return `
Error: ${error.message}
`; } // Initialize YouTube iframe API function initYouTubePlayer() { // Get the existing iframe const iframeId = playerElement.getAttribute('id'); // Load the YouTube iframe API if it's not already loaded if (!window.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); window.onYouTubeIframeAPIReady = function() { createYouTubePlayer(iframeId); }; } else { createYouTubePlayer(iframeId); } } // Create YouTube player object function createYouTubePlayer(iframeId) { ytPlayer = new YT.Player(iframeId, { events: { 'onReady': onPlayerReady } }); } // When player is ready function onPlayerReady(event) { console.log('Player ready'); // Seeking will be handled by the dedicated timestamp handler code at the bottom } // Load transcript segments function loadTranscript() { transcriptContainer.innerHTML = '
Loading transcript...
'; // Check if video ID is valid before making API call if (!videoId || videoId === 'undefined' || videoId === 'null') { transcriptContainer.innerHTML = `
Invalid video ID. Please return to the home page and select a valid video.
`; return; } fetch(`/api/video/segments/${videoId}`) .then(response => { if (!response.ok) { throw new Error('Failed to load transcript: ' + response.status); } return response.json(); }) .then(segments => { transcriptSegments = segments; if (!segments || segments.length === 0) { transcriptContainer.innerHTML = `
No transcript available for this video. Try processing the video first from the home page.
`; } else { displayTranscript(segments); // If we have a timestamp in the URL, highlight the relevant segment if (startTime) { const timeInSeconds = parseFloat(startTime); if (!isNaN(timeInSeconds)) { // Find segment containing this time setTimeout(() => { highlightSegment(timeInSeconds); }, 500); // Short delay to ensure segments are rendered } } } }) .catch(error => { console.error('Error loading transcript:', error); transcriptContainer.innerHTML = `
Error loading transcript: ${error.message}

This may happen if:

Try processing this video from the home page first.

`; }); } // Display transcript segments function displayTranscript(segments) { const html = segments.map((segment, index) => { const formattedTime = formatTime(segment.start); return `
${formattedTime} ${segment.text}
`; }).join(''); transcriptContainer.innerHTML = html; // Add click handlers to segments document.querySelectorAll('.transcript-segment').forEach(segment => { segment.addEventListener('click', () => { const startTime = parseFloat(segment.dataset.start); seekToTime(startTime); }); }); } // Seek to specific time in the video function seekToTime(seconds) { console.log('Seeking to time:', seconds); if (ytPlayer && typeof ytPlayer.seekTo === 'function') { try { // Ensure seconds is a number seconds = parseFloat(seconds); if (isNaN(seconds)) { console.error('Invalid seconds value:', seconds); return; } // Seek to time ytPlayer.seekTo(seconds, true); // Try to play the video (may be blocked by browser autoplay policies) try { ytPlayer.playVideo(); } catch (e) { console.warn('Could not autoplay video:', e); } // Highlight the current segment setTimeout(() => { highlightSegment(seconds); }, 300); // Short delay to ensure seek completes first } catch (error) { console.error('Error seeking to time:', error); } } else { console.error('YouTube player is not ready yet or seekTo method is not available'); // Queue the seek operation for when the player becomes available console.log('Queueing seek operation for later...'); setTimeout(() => { if (ytPlayer && typeof ytPlayer.seekTo === 'function') { console.log('Player now ready, executing queued seek'); seekToTime(seconds); } }, 1000); // Try again in 1 second } } // Highlight segment containing the current time function highlightSegment(time) { // Remove highlight from all segments document.querySelectorAll('.transcript-segment').forEach(segment => { segment.classList.remove('highlight'); }); // Wait until segments are available in the DOM if (document.querySelectorAll('.transcript-segment').length === 0) { console.log('No transcript segments found, waiting...'); // Retry after a short delay to allow transcript to load setTimeout(() => highlightSegment(time), 500); return; } // Find the segment containing current time // Need to find by approximate match since floating point exact matches may not work const segments = document.querySelectorAll('.transcript-segment'); let currentSegment = null; for (const segment of segments) { const start = parseFloat(segment.dataset.start); const end = parseFloat(segment.dataset.end); if (time >= start && time <= end) { currentSegment = segment; break; } } // If exact time match not found, find the closest segment by time if (!currentSegment && segments.length > 0) { // First try exact match const exactMatch = document.querySelector(`.transcript-segment[data-start="${time}"]`); if (exactMatch) { currentSegment = exactMatch; } else { // Find closest segment let closestSegment = segments[0]; let closestDistance = Math.abs(parseFloat(closestSegment.dataset.start) - time); segments.forEach(segment => { const distance = Math.abs(parseFloat(segment.dataset.start) - time); if (distance < closestDistance) { closestDistance = distance; closestSegment = segment; } }); currentSegment = closestSegment; } } if (currentSegment) { currentSegment.classList.add('highlight'); // Ensure the segment is visible in the transcript container currentSegment.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } // Search functionality searchButton.addEventListener('click', performSearch); searchInput.addEventListener('keypress', e => { if (e.key === 'Enter') performSearch(); }); function performSearch() { const query = searchInput.value.trim(); if (!query) { transcriptContainer.innerHTML = '
Please enter a search query
'; return; } // Validate video ID before searching if (!videoId || videoId === 'undefined' || videoId === 'null') { transcriptContainer.innerHTML = `
Invalid video ID. Please return to the home page and select a valid video.
`; return; } // Show loading indicator loadingIndicator.classList.remove('hidden'); // Send search request fetch(`/api/video/search?query=${encodeURIComponent(query)}&video_id=${videoId}`) .then(response => { if (!response.ok) { throw new Error('Search failed'); } return response.json(); }) .then(results => { // Hide loading indicator loadingIndicator.classList.add('hidden'); if (results.length === 0) { // Show "no results" message in transcript container transcriptContainer.innerHTML = ` `; // Add click handler to reset search link document.getElementById('reset-search').addEventListener('click', (e) => { e.preventDefault(); resetTranscriptFilter(); displayTranscript(transcriptSegments); }); return; } // Display search results as filtered transcript filterTranscript(results); // Add a header with search info and reset option const searchInfoHeader = document.createElement('div'); searchInfoHeader.className = 'mb-4 flex justify-between items-center'; searchInfoHeader.innerHTML = `
${results.length} results for "${query}"
Show all transcript `; // Insert the header before transcript segments transcriptContainer.insertBefore(searchInfoHeader, transcriptContainer.firstChild); // Add click handler to reset search link document.getElementById('reset-search').addEventListener('click', (e) => { e.preventDefault(); resetTranscriptFilter(); displayTranscript(transcriptSegments); }); }) .catch(error => { // Hide loading indicator loadingIndicator.classList.add('hidden'); // Show error transcriptContainer.innerHTML = handleError(error); }); } // Filter transcript to show only matching segments function filterTranscript(results) { // Create a highlighted version of the transcript with only matching segments const html = results.map(result => { const segment = result.segment; const formattedTime = formatTime(segment.start); const score = (result.score * 100).toFixed(0); const index = transcriptSegments.findIndex(s => s.segment_id === segment.segment_id); return `
${formattedTime}
${score}% match
${segment.text}
`; }).join(''); // Replace transcript with filtered results transcriptContainer.innerHTML = html; // Add click handlers to segments document.querySelectorAll('.transcript-segment').forEach(segment => { segment.addEventListener('click', () => { const startTime = parseFloat(segment.dataset.start); seekToTime(startTime); }); }); } // Transcript is always visible - toggle functionality removed // Reset transcript filter to show all segments function resetTranscriptFilter() { searchInput.value = ''; } // Show processing indicator if URL was just processed function showProcessingIndicator() { if (processingUrl === 'true') { isProcessingUrl = true; transcriptContainer.innerHTML = `
Processing video from URL... This may take a few moments
`; // Check for segments every second const processingInterval = setInterval(() => { // Validate video ID before making API call if (!videoId || videoId === 'undefined' || videoId === 'null') { clearInterval(processingInterval); transcriptContainer.innerHTML = `
Invalid video ID. Please return to the home page and select a valid video.
`; return; } fetch(`/api/video/segments/${videoId}`) .then(response => { if (!response.ok) { return null; } return response.json(); }) .then(segments => { if (segments && segments.length > 0) { clearInterval(processingInterval); isProcessingUrl = false; loadTranscript(); } }) .catch(error => { console.error('Error checking segments:', error); }); }, 2000); // Set timeout to stop checking after 2 minutes setTimeout(() => { clearInterval(processingInterval); if (isProcessingUrl) { transcriptContainer.innerHTML = `
Processing is taking longer than expected. Refresh the page to check progress.
`; isProcessingUrl = false; } }, 120000); return true; } return false; } // Initialize initYouTubePlayer(); // Show processing indicator or load transcript if (!showProcessingIndicator()) { loadTranscript(); } // If there's a search query in the URL, apply it after transcript loads if (searchQuery) { const checkTranscriptInterval = setInterval(() => { if (transcriptSegments.length > 0) { clearInterval(checkTranscriptInterval); // Set the search input value and trigger search searchInput.value = searchQuery; performSearch(); } }, 500); // Set timeout to stop checking after 10 seconds setTimeout(() => clearInterval(checkTranscriptInterval), 10000); } // If there's a timestamp in the URL, ensure it will be seeked to after transcript loads if (startTime) { // Handle timestamp regardless of search query let timeInSeconds = parseFloat(startTime); // If parsing fails, try to parse as HH:MM:SS format if (isNaN(timeInSeconds) && typeof startTime === 'string') { // Try to parse HH:MM:SS or MM:SS format const timeParts = startTime.split(':').map(part => parseInt(part, 10)); if (timeParts.length === 3) { // HH:MM:SS format timeInSeconds = timeParts[0] * 3600 + timeParts[1] * 60 + timeParts[2]; } else if (timeParts.length === 2) { // MM:SS format timeInSeconds = timeParts[0] * 60 + timeParts[1]; } } if (!isNaN(timeInSeconds)) { console.log('Will seek to timestamp:', timeInSeconds, 'seconds'); // Try immediately if player is ready if (ytPlayer && typeof ytPlayer.seekTo === 'function') { console.log('YouTube player is ready, seeking now'); seekToTime(timeInSeconds); // Also try to play the video as a fallback if autoplay doesn't work try { ytPlayer.playVideo(); // Unmute the video after a short delay setTimeout(() => { try { ytPlayer.unMute(); // Set volume to a reasonable level ytPlayer.setVolume(80); } catch (e) { console.warn('Could not unmute video:', e); } }, 1000); } catch (e) { console.warn('Could not autoplay video:', e); } } // Also set up a backup interval to ensure we seek once everything is ready const checkReadyInterval = setInterval(() => { if (ytPlayer && typeof ytPlayer.seekTo === 'function') { if (transcriptSegments.length > 0) { clearInterval(checkReadyInterval); console.log('Everything loaded, seeking to timestamp:', timeInSeconds); seekToTime(timeInSeconds); // Also try to play the video try { ytPlayer.playVideo(); // Unmute the video after a short delay setTimeout(() => { try { ytPlayer.unMute(); ytPlayer.setVolume(80); } catch (e) { console.warn('Could not unmute video after delay:', e); } }, 1000); } catch (e) { console.warn('Could not autoplay video after delay:', e); } } else { console.log('Waiting for transcript segments to load...'); } } else { console.log('Waiting for YouTube player to be ready...'); } }, 500); // Set timeout to stop checking after 10 seconds setTimeout(() => { clearInterval(checkReadyInterval); // Final attempt if (ytPlayer && typeof ytPlayer.seekTo === 'function') { console.log('Final attempt to seek to:', timeInSeconds); seekToTime(timeInSeconds); // One final attempt to play try { ytPlayer.playVideo(); // Unmute the video after a short delay setTimeout(() => { try { ytPlayer.unMute(); ytPlayer.setVolume(80); } catch (e) { console.warn('Could not unmute video on final attempt:', e); } }, 1000); } catch (e) { console.warn('Could not autoplay video on final attempt:', e); } } }, 10000); } else { console.warn('Could not parse timestamp from URL:', startTime); } } });