window.addEventListener('DOMContentLoaded', () => { let cancelAutoAdvance = false; // Global flag for auto-advance cancellation let syncSocket = null; // WebSocket for video sync let isSyncing = false; // Flag to prevent infinite loops during sync const authPage = document.getElementById('auth-page'); const loginBtn = document.getElementById('login-btn'); const registerBtn = document.getElementById('register-btn'); const authError = document.getElementById('auth-error'); const moviesListEl = document.getElementById('movies-list'); const showsListEl = document.getElementById('shows-list'); const overlay = document.getElementById('overlay'); const movieDetailsEl = document.getElementById('movie-details'); const closeOverlayBtn = document.getElementById('close-overlay'); const moviesTabBtn = document.getElementById('movies-tab'); const showsTabBtn = document.getElementById('shows-tab'); const scanBtn = document.getElementById('scan-btn'); // Listen for token expiration events from preload. window.addEventListener("tokenExpired", () => { console.warn("Access token expired. Redirecting to login screen."); authPage.style.display = 'flex'; }); // Modified authentication check: hide the login overlay if a token is loaded. function checkAuth() { if (window.api.getToken()) { authPage.style.display = 'none'; } else { authPage.style.display = 'flex'; } } loginBtn.addEventListener('click', () => { const username = document.getElementById('username').value.trim(); const password = document.getElementById('password').value; window.api.login(username, password).then(data => { if(data.access_token) { authPage.style.display = 'none'; loadMovies(); } else { authError.textContent = data.error || 'Login failed'; } }).catch(err => { authError.textContent = 'Login error'; console.error(err); }); }); registerBtn.addEventListener('click', () => { const username = document.getElementById('username').value.trim(); const password = document.getElementById('password').value; window.api.register(username, password).then(data => { if(data.success) { authError.textContent = 'Registration successful. Please log in.'; } else { authError.textContent = data.error || 'Registration failed'; } }).catch(err => { authError.textContent = 'Registration error'; console.error(err); }); }); // Helper to boost video volume using the Web Audio API function boostVideoVolume(video) { try { const AudioContext = window.AudioContext || window.webkitAudioContext; const audioContext = new AudioContext(); const source = audioContext.createMediaElementSource(video); const gainNode = audioContext.createGain(); gainNode.gain.value = 5; // 200% volume boost source.connect(gainNode); gainNode.connect(audioContext.destination); } catch (err) { console.error('Error boosting video volume:', err); } } // New: transcodeAudioStream uses ffmpeg.wasm to re-encode the audio async function transcodeAudioStream(videoUrl) { // Ensure ffmpeg is loaded only once if (!window.ffmpegInstance) { const { createFFmpeg, fetchFile } = FFmpeg; window.ffmpegInstance = createFFmpeg({ log: true }); await window.ffmpegInstance.load(); } const ffmpeg = window.ffmpegInstance; try { // Fetch the original video file as an ArrayBuffer const response = await fetch(videoUrl); const data = await response.arrayBuffer(); ffmpeg.FS('writeFile', 'input.mp4', new Uint8Array(data)); // Run ffmpeg command to copy the video stream and transcode the audio to AAC await ffmpeg.run('-i', 'input.mp4', '-c:v', 'copy', '-c:a', 'aac', 'output.mp4'); const outputData = ffmpeg.FS('readFile', 'output.mp4'); // Clean up the virtual FS ffmpeg.FS('unlink', 'input.mp4'); ffmpeg.FS('unlink', 'output.mp4'); const blob = new Blob([outputData.buffer], { type: 'video/mp4' }); const transcodedUrl = URL.createObjectURL(blob); return transcodedUrl; } catch (error) { console.error("Transcoding error:", error); throw error; } } // New: try direct play, detect “no audio” or error, then remux on demand async function safePlayVideo(videoElement, videoUrl, subtitlesUrl = null) { // 1) wire up subtitles (same as you had) if (subtitlesUrl) { let track = videoElement.querySelector('track'); if (!track) { track = document.createElement('track'); track.kind = 'subtitles'; track.label = 'English'; track.srclang = 'en'; videoElement.appendChild(track); } track.src = subtitlesUrl; track.default = true; } // 2) helper that tries to play + checks for audioTracks.length const tryPlay = url => new Promise((resolve, reject) => { videoElement.src = url; videoElement.load(); const onErr = () => cleanup() || reject(); const onPlay = () => { setTimeout(() => { const hasAudio = videoElement.audioTracks ? videoElement.audioTracks.length > 0 : true; // assume “ok” if browser doesn’t support audioTracks cleanup(); hasAudio ? resolve() : reject(); }, 200); }; function cleanup() { videoElement.removeEventListener('error', onErr); videoElement.removeEventListener('play', onPlay); return true; } videoElement.addEventListener('error', onErr); videoElement.addEventListener('play', onPlay); videoElement.play().catch(onErr); }); try { // first attempt: raw MKV await tryPlay(videoUrl); } catch (_) { console.warn('Direct play failed or no audio → remuxing…'); // insert the indicator right above the video const indicator = document.createElement('div'); indicator.classList.add('remuxing-indicator'); indicator.textContent = 'Remuxing...'; videoElement.parentNode.insertBefore(indicator, videoElement); // do the heavy FFmpeg step const mp4url = await transcodeAudioStream(videoUrl); await tryPlay(mp4url); // remove the banner indicator.remove(); } } // Add error handling for video playback function playVideo(videoElement, videoUrl, subtitlesUrl = null) { try { const mediaType = videoElement.dataset.mediaType; const mediaId = videoElement.dataset.mediaId; window.api.getProgress(mediaType, mediaId) .then(data => { // Determine progress; default to 0 if none found. const progress = (data.last_position && data.last_position > 0) ? data.last_position : 0; // Listen for metadata load to seek to the progress point. videoElement.addEventListener('loadedmetadata', function setProgress() { if (progress > 0) { videoElement.currentTime = progress; } videoElement.removeEventListener('loadedmetadata', setProgress); }); safePlayVideo(videoElement, videoUrl, subtitlesUrl) .then(() => { // Define helper functions to start and stop the progress timer. function startProgressTimer() { if (!videoElement.progressTimer) { videoElement.progressTimer = setInterval(() => { window.api.saveProgress(mediaType, mediaId, videoElement.currentTime) .catch(err => console.error("Error saving progress:", err)); }, 60000); // 5 minutes in ms } } function stopProgressTimer() { if (videoElement.progressTimer) { clearInterval(videoElement.progressTimer); videoElement.progressTimer = null; } } videoElement.addEventListener('play', startProgressTimer); videoElement.addEventListener('pause', stopProgressTimer); videoElement.addEventListener('ended', stopProgressTimer); if (!videoElement.paused) { startProgressTimer(); } }) .catch(err => console.error('Playback failed even after remux:', err)); }) .catch(err => { console.warn('Failed to fetch progress:', err); // Continue with playback even if fetching progress failed. safePlayVideo(videoElement, videoUrl, subtitlesUrl) .then(() => { function startProgressTimer() { if (!videoElement.progressTimer) { videoElement.progressTimer = setInterval(() => { window.api.saveProgress(mediaType, mediaId, videoElement.currentTime) .catch(err => console.error("Error saving progress:", err)); }, 300000); } } function stopProgressTimer() { if (videoElement.progressTimer) { clearInterval(videoElement.progressTimer); videoElement.progressTimer = null; } } videoElement.addEventListener('play', startProgressTimer); videoElement.addEventListener('pause', stopProgressTimer); videoElement.addEventListener('ended', stopProgressTimer); if (!videoElement.paused) { startProgressTimer(); } }) .catch(err => console.error('Playback failed even after remux:', err)); }); } catch (err) { console.error('Unexpected playback error:', err); alert('An unexpected error occurred. Please try again.'); } } // Helper to switch active tab and content function setActiveTab(tab) { if (tab === 'movies') { moviesTabBtn.classList.add('active'); showsTabBtn.classList.remove('active'); moviesListEl.style.display = ''; // use CSS default (i.e. .section-list) showsListEl.style.display = 'none'; } else { moviesTabBtn.classList.remove('active'); showsTabBtn.classList.add('active'); moviesListEl.style.display = 'none'; showsListEl.style.display = ''; // use CSS default (i.e. .section-list) } } moviesTabBtn.addEventListener('click', () => { setActiveTab('movies'); }); showsTabBtn.addEventListener('click', () => { setActiveTab('shows'); if (!showsListEl.hasChildNodes()) loadShows(); }); // Load and display movie list function loadMovies() { // New: Load and display in-progress movies loadInProgressMovies(); window.api.getMovies().then(movies => { // --- Newest Movies Section --- const sectionNewest = document.createElement('section'); const headerNewest = document.createElement('h2'); headerNewest.textContent = 'Newest Movies'; sectionNewest.appendChild(headerNewest); const gridNewest = document.createElement('div'); gridNewest.classList.add('content-row'); // sort by created_at descending (newest first) const moviesByNewest = [...movies].sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); moviesByNewest.forEach(movie => { const item = document.createElement('div'); item.classList.add('movie-item'); item.innerHTML = ` ${movie.title} ${movie.title} `; item.addEventListener('click', () => { window.api.getMovieDetails(movie.id).then(movieDetails => { movieDetailsEl.innerHTML = `

${movieDetails.title}

${movieDetails.plot}

Year: ${movieDetails.year}

Released: ${movieDetails.released}

`; overlay.style.display = 'flex'; const movieVideo = document.getElementById('movie-video'); boostVideoVolume(movieVideo); const streamUrl = window.api.getStream(movieDetails.id); const subtitlesUrl = window.api.getSubtitles(movieDetails.id); playVideo(movieVideo, streamUrl, subtitlesUrl); }); }); gridNewest.appendChild(item); }); // wrap row + arrows const scrollWrap = document.createElement('div'); scrollWrap.classList.add('scroll-container'); // left arrow const leftBtn = document.createElement('button'); leftBtn.classList.add('scroll-arrow','left'); leftBtn.innerHTML = '◀'; leftBtn.addEventListener('click', ()=> { gridNewest.scrollBy({ left: -300, behavior: 'smooth' }); }); // right arrow const rightBtn = document.createElement('button'); rightBtn.classList.add('scroll-arrow','right'); rightBtn.innerHTML = '▶'; rightBtn.addEventListener('click', ()=> { gridNewest.scrollBy({ left: 300, behavior: 'smooth' }); }); scrollWrap.append(leftBtn, gridNewest, rightBtn); sectionNewest.appendChild(scrollWrap); moviesListEl.appendChild(sectionNewest); // --- Alphabetical Movies Section --- const sectionAlpha = document.createElement('section'); const headerAlpha = document.createElement('h2'); headerAlpha.textContent = 'All Movies (A–Z)'; sectionAlpha.appendChild(headerAlpha); const gridAlpha = document.createElement('div'); gridAlpha.classList.add('content-list'); // sort by title ascending const moviesAlpha = [...movies].sort((a, b) => a.title.localeCompare(b.title)); moviesAlpha.forEach(movie => { const item = document.createElement('div'); item.classList.add('movie-item'); item.innerHTML = ` ${movie.title} ${movie.title} `; item.addEventListener('click', () => { window.api.getMovieDetails(movie.id).then(movieDetails => { movieDetailsEl.innerHTML = `

${movieDetails.title}

${movieDetails.plot}

Year: ${movieDetails.year}

Released: ${movieDetails.released}

`; overlay.style.display = 'flex'; const movieVideo = document.getElementById('movie-video'); boostVideoVolume(movieVideo); const streamUrl = window.api.getStream(movieDetails.id); const subtitlesUrl = window.api.getSubtitles(movieDetails.id); playVideo(movieVideo, streamUrl, subtitlesUrl); }); }); gridAlpha.appendChild(item); }); sectionAlpha.appendChild(gridAlpha); moviesListEl.appendChild(sectionAlpha); }); } // New: Function to load currently in-progress movies function loadInProgressMovies() { window.api.getInProgress().then(data => { const movieProgress = data.movies || []; if (movieProgress.length > 0) { const sectionInProgress = document.createElement('section'); const headerInProgress = document.createElement('h2'); headerInProgress.textContent = 'Continue Watching'; sectionInProgress.appendChild(headerInProgress); const gridInProgress = document.createElement('div'); gridInProgress.classList.add('content-row'); movieProgress.forEach(item => { // item.media_id holds the movie id. window.api.getMovieDetails(item.media_id).then(movieDetails => { const movieItem = document.createElement('div'); movieItem.classList.add('movie-item'); movieItem.innerHTML = ` ${movieDetails.title} ${movieDetails.title} `; movieItem.addEventListener('click', () => { movieDetailsEl.innerHTML = `

${movieDetails.title}

${movieDetails.plot}

Year: ${movieDetails.year}

Released: ${movieDetails.released}

`; overlay.style.display = 'flex'; const movieVideo = document.getElementById('movie-video'); boostVideoVolume(movieVideo); const streamUrl = window.api.getStream(movieDetails.id); const subtitlesUrl = window.api.getSubtitles(movieDetails.id); playVideo(movieVideo, streamUrl, subtitlesUrl); }); gridInProgress.appendChild(movieItem); }).catch(err => console.error("Failed to load movie details for progress item:", err)); }); sectionInProgress.appendChild(gridInProgress); moviesListEl.insertBefore(sectionInProgress, moviesListEl.firstChild); } }).catch(err => console.error('Error loading in progress movies:', err)); } // Load and display TV shows list async function loadShows() { // New: Load and display in-progress episodes for TV shows loadInProgressShows(); const shows = await window.api.getShows(); showsListEl.innerHTML = ''; // --- Newest TV Shows Section (by latest episode added) --- const sectionNewest = document.createElement('section'); const headerNewest = document.createElement('h2'); headerNewest.textContent = 'Newest TV Shows (by latest episode)'; sectionNewest.appendChild(headerNewest); const gridNewest = document.createElement('div'); gridNewest.classList.add('content-row'); // Fetch seasons & episodes for each show to find the most recent episode date const showsWithLatestEp = await Promise.all(shows.map(async show => { let seasons = await window.api.getShowSeasons(show.id); if (!Array.isArray(seasons)) seasons = Object.values(seasons); const episodeLists = await Promise.all( seasons.map(season => window.api.getSeasonEpisodes(show.id, season)) ); const allEpisodes = episodeLists.flat(); const latestDate = allEpisodes.reduce((max, ep) => { const dt = new Date(ep.created_at); return dt > max ? dt : max; }, new Date(0)); return { show, latestDate }; })); // Sort shows by their latest episode date (newest first) const showsByNewest = showsWithLatestEp .sort((a, b) => b.latestDate - a.latestDate) .map(item => item.show); showsByNewest.forEach(show => { const item = document.createElement('div'); item.classList.add('movie-item'); item.innerHTML = ` ${show.name} ${show.name} `; item.addEventListener('click', () => { window.api.getShowDetails(show.id).then(showDetails => { const showHTML = `

${showDetails.name}

${showDetails.name}

Rating: ${showDetails.rating}

Genres: ${showDetails.genres}

${showDetails.summary}

Seasons:

`; window.api.getShowSeasons(show.id).then(seasons => { if (!Array.isArray(seasons)) seasons = Object.values(seasons); const seasonsHTML = seasons .map(s => ``) .join(''); movieDetailsEl.innerHTML = showHTML + seasonsHTML; overlay.style.display = 'flex'; overlay.querySelectorAll('.season-btn').forEach(btn => btn.addEventListener('click', seasonBtnHandler) ); }); }); }); gridNewest.appendChild(item); }); sectionNewest.appendChild(gridNewest); showsListEl.appendChild(sectionNewest); // --- Alphabetical TV Shows Section --- const sectionAlpha = document.createElement('section'); const headerAlpha = document.createElement('h2'); headerAlpha.textContent = 'All TV Shows (A–Z)'; sectionAlpha.appendChild(headerAlpha); const gridAlpha = document.createElement('div'); gridAlpha.classList.add('content-list'); const showsAlpha = [...shows].sort((a, b) => a.name.localeCompare(b.name)); showsAlpha.forEach(show => { const item = document.createElement('div'); item.classList.add('movie-item'); item.innerHTML = ` ${show.name} ${show.name} `; item.addEventListener('click', () => { window.api.getShowDetails(show.id).then(showDetails => { const showHTML = `

${showDetails.name}

${showDetails.name}

Rating: ${showDetails.rating}

Genres: ${showDetails.genres}

${showDetails.summary}

Seasons:

`; window.api.getShowSeasons(show.id).then(seasons => { if (!Array.isArray(seasons)) seasons = Object.values(seasons); const seasonsHTML = seasons .map(s => ``) .join(''); movieDetailsEl.innerHTML = showHTML + seasonsHTML; overlay.style.display = 'flex'; overlay.querySelectorAll('.season-btn').forEach(btn => btn.addEventListener('click', seasonBtnHandler) ); }); }); }); gridAlpha.appendChild(item); }); sectionAlpha.appendChild(gridAlpha); showsListEl.appendChild(sectionAlpha); } // Updated function to stream the episode without calling getEpisodeDetails function loadInProgressShows() { window.api.getInProgress().then(data => { const episodesProgress = data.episodes || []; if (episodesProgress.length > 0) { const sectionInProgress = document.createElement('section'); const headerInProgress = document.createElement('h2'); headerInProgress.textContent = 'Continue Watching'; sectionInProgress.appendChild(headerInProgress); const gridInProgress = document.createElement('div'); gridInProgress.classList.add('content-row'); episodesProgress.forEach(item => { window.api.getShowByEpisode(item.media_id) .then(showDetails => { const episodeItem = document.createElement('div'); episodeItem.classList.add('movie-item'); episodeItem.innerHTML = ` ${showDetails.name} ${showDetails.name} `; episodeItem.addEventListener('click', () => { movieDetailsEl.innerHTML = `

Episode ${item.media_id}

`; overlay.style.display = 'flex'; const videoElement = document.getElementById('episode-video'); const episodeStreamUrl = window.api.getEpisodeStream(item.media_id); playVideo(videoElement, episodeStreamUrl); }); gridInProgress.appendChild(episodeItem); }) .catch(err => { console.error("Failed to fetch show by episode details:", err); }); }); sectionInProgress.appendChild(gridInProgress); showsListEl.insertBefore(sectionInProgress, showsListEl.firstChild); } }).catch(err => console.error('Error loading in progress shows:', err)); } scanBtn.addEventListener('click', () => { scanBtn.disabled = true; // Disable the button to prevent multiple clicks scanBtn.textContent = 'Scanning...'; window.api.scanForNewFiles().then(response => { if (response.success) { alert('Scan completed successfully!'); loadMovies(); loadShows(); } else { alert('Scan failed: ' + (response.error || 'Unknown error')); } }).catch(err => { console.error('Error during scan:', err); alert('An error occurred while scanning for new files.'); }).finally(() => { scanBtn.disabled = false; scanBtn.textContent = 'Scan for New Files'; }); }); // Initial auth check; hide login overlay if token exists and load movies. checkAuth(); if (window.api.getToken()) { loadMovies(); } closeOverlayBtn.addEventListener('click', () => { cancelAutoAdvance = true; const video = overlay.querySelector('video'); if (video) { video.pause(); video.currentTime = 0; // Stop the progress timer if it's running if (video.progressTimer) { clearInterval(video.progressTimer); video.progressTimer = null; } video.removeAttribute('src'); video.load(); } overlay.style.display = 'none'; }); // Add handler for season buttons function seasonBtnHandler(event) { const btn = event.currentTarget; const showId = btn.dataset.showId; const season = btn.dataset.season; // Clear previous details and show season header movieDetailsEl.innerHTML = `

Season ${season} Episodes

`; window.api.getSeasonEpisodes(showId, season) .then(episodes => { if (episodes && episodes.length) { const episodesContainer = document.createElement('div'); episodesContainer.classList.add('episodes-list'); episodes.forEach(ep => { const epBtn = document.createElement('button'); epBtn.classList.add('episode-btn'); epBtn.textContent = ep.title || `Episode ${ep.id}`; epBtn.dataset.episodeId = ep.id; epBtn.addEventListener('click', () => { // Display selected episode video in the overlay movieDetailsEl.innerHTML = `

${ep.title}

`; const videoElement = document.getElementById('episode-video'); const episodeStreamUrl = window.api.getEpisodeStream(ep.id); playVideo(videoElement, episodeStreamUrl); }); episodesContainer.appendChild(epBtn); }); movieDetailsEl.appendChild(episodesContainer); } else { movieDetailsEl.innerHTML += '

No episodes found for this season.

'; } }) .catch(err => { console.error('Error fetching episodes:', err); movieDetailsEl.innerHTML += '

Error loading episodes.

'; }); } function connectToSyncSession(sessionId, mediaId, mediaType, videoElement = null) { if (syncSocket) { syncSocket.close(); } // Include media_id and media_type as query parameters in the WebSocket URL syncSocket = new WebSocket(`ws://bbrunson.com:8495/ws/sync/${sessionId}?media_id=${mediaId}&media_type=${mediaType}`); syncSocket.onopen = () => { console.log("Connected to sync session:", sessionId); }; syncSocket.onmessage = async (event) => { if (isSyncing) return; // Prevent loops caused by local updates const message = JSON.parse(event.data); // Ensure the media_id and media_type match the current video if (message.media_id !== mediaId || message.media_type !== mediaType) { console.warn("Received sync message for a different media_id or media_type. Ignoring."); return; } const data = JSON.parse(message.data); // If no video element is provided, fetch video details and start playback if (!videoElement) { try { let videoDetails; if (mediaType === "movie") { videoDetails = await window.api.getMovieDetails(mediaId); // Fetch movie details } else if (mediaType === "episode") { videoDetails = await window.api.getEpisodeDetails(mediaId); // Fetch episode details } else { throw new Error("Unknown media type"); } movieDetailsEl.innerHTML = `

${videoDetails.title}

${videoDetails.plot}

Year: ${videoDetails.year}

Released: ${videoDetails.released}

`; overlay.style.display = 'flex'; videoElement = document.getElementById('movie-video'); boostVideoVolume(videoElement); const streamUrl = mediaType === "movie" ? window.api.getStream(videoDetails.id) : window.api.getEpisodeStream(videoDetails.id); playVideo(videoElement, streamUrl); } catch (error) { console.error("Error fetching video details:", error); alert("Failed to load video for the sync session."); return; } } const currentTime = videoElement.currentTime; const timeDifference = Math.abs(currentTime - data.currentTime); if (data.type === "play") { if (timeDifference > 5) { videoElement.currentTime = data.currentTime; } videoElement.play(); } else if (data.type === "pause") { if (timeDifference > 5) { videoElement.currentTime = data.currentTime; } videoElement.pause(); } else if (data.type === "seek") { if (timeDifference > 5) { videoElement.currentTime = data.currentTime; } } }; syncSocket.onclose = () => { console.log("Disconnected from sync session:", sessionId); }; syncSocket.onerror = (error) => { console.error("WebSocket error:", error); }; // Sync video events with the server if (videoElement) { videoElement.addEventListener("play", () => { if (syncSocket.readyState === WebSocket.OPEN) { isSyncing = true; syncSocket.send(JSON.stringify({ type: "play", currentTime: videoElement.currentTime })); isSyncing = false; } }); videoElement.addEventListener("pause", () => { if (syncSocket.readyState === WebSocket.OPEN) { isSyncing = true; syncSocket.send(JSON.stringify({ type: "pause", currentTime: videoElement.currentTime })); isSyncing = false; } }); videoElement.addEventListener("seeked", () => { if (syncSocket.readyState === WebSocket.OPEN) { isSyncing = true; syncSocket.send(JSON.stringify({ type: "seek", currentTime: videoElement.currentTime })); isSyncing = false; } }); } } document.getElementById("join-sync-btn").addEventListener("click", async () => { const sessionId = document.getElementById("sync-session-id").value.trim(); if (!sessionId) { alert("Please enter a valid session ID."); return; } // Fetch session details from the server let mediaId, mediaType; try { const sessionDetails = await window.api.getSessionDetails(sessionId); mediaId = sessionDetails.media_id; mediaType = sessionDetails.media_type; } catch (error) { console.error("Error fetching session details:", error); alert("Failed to retrieve session details. Please try again."); return; } if (!mediaId || !mediaType) { alert("Media ID or type is missing. Unable to join the sync session."); return; } const videoElement = document.querySelector("video"); connectToSyncSession(sessionId, mediaId, mediaType, videoElement); }); document.getElementById("overlay-join-sync-btn").addEventListener("click", () => { const sessionId = document.getElementById("overlay-sync-session-id").value.trim(); if (!sessionId) { alert("Please enter a valid session ID."); return; } // Automatically retrieve the mediaId from the currently playing video in the overlay const videoElement = document.querySelector("#overlay video"); if (!videoElement || !videoElement.dataset.mediaId) { alert("No video is currently playing or media ID is missing."); return; } const mediaId = videoElement.dataset.mediaId; // Retrieve mediaId from the video element connectToSyncSession(sessionId, mediaId, "movie", videoElement); }); });