(function () { "use strict"; const summary = document.getElementById("local-video-summary"); const body = document.querySelector("#local-video-list [data-data-list-body]"); const refreshButton = document.getElementById("local-videos-refresh-btn"); const playerPanel = document.getElementById("local-video-player-panel"); const playerTitle = document.getElementById("local-video-player-title"); const player = document.getElementById("local-video-player"); const closePlayerButton = document.getElementById("local-video-player-close"); let playingPath = null; const thumbnailObserver = new IntersectionObserver(function (entries, observer) { entries.forEach(function (entry) { if (!entry.isIntersecting) { return; } const image = entry.target; image.src = `/api/local-video/thumbnail?path=${encodeURIComponent(image.dataset.path)}`; observer.unobserve(image); }); }, { rootMargin: "200px" }); function fetchJson(url, options) { return fetch(url, options).then(function (response) { return response.json().then(function (data) { if (!response.ok) { throw new Error(data.message || data.error || data.status || `HTTP ${response.status}`); } return data; }); }); } function textCell(text) { const cell = document.createElement("td"); cell.textContent = text; return cell; } function closePlayer() { player.pause(); player.removeAttribute("src"); player.load(); playerPanel.hidden = true; playingPath = null; } function playVideo(path) { playingPath = path; playerTitle.textContent = path; player.src = `/api/local-video/file?path=${encodeURIComponent(path)}`; playerPanel.hidden = false; player.load(); player.play().catch(function () { // Browser autoplay policy may require the user to press play. }); playerPanel.scrollIntoView({ behavior: "smooth", block: "start" }); } function renderFiles(result) { thumbnailObserver.disconnect(); body.replaceChildren(); const files = result.files || []; let uploadedCount = 0; files.forEach(function (file) { const row = document.createElement("tr"); row.dataset.path = file.path; row.dataset.size = file.size; row.dataset.modified = file.modified_at; row.dataset.uploaded = file.uploaded ? "1" : "0"; const preview = document.createElement("td"); const image = document.createElement("img"); image.className = "local-video-thumbnail"; image.alt = `Preview of ${file.path}`; image.loading = "lazy"; image.dataset.path = file.path; image.addEventListener("error", function () { image.hidden = true; }); preview.appendChild(image); row.appendChild(preview); thumbnailObserver.observe(image); row.appendChild(textCell(file.path)); row.appendChild(textCell(YarisUI.formatBytes(file.size))); row.appendChild(textCell(new Date(file.modified_at * 1000).toLocaleString())); const upload = document.createElement("td"); const badge = document.createElement("span"); badge.className = file.uploaded ? "badge bg-success" : "badge bg-warning text-dark"; badge.textContent = file.uploaded ? "Uploaded" : "Pending"; upload.appendChild(badge); row.appendChild(upload); if (file.uploaded) { uploadedCount += 1; } const actions = document.createElement("td"); actions.className = "data-list-actions"; const playButton = document.createElement("button"); playButton.type = "button"; playButton.className = "btn btn-sm btn-outline-primary"; playButton.dataset.playPath = file.path; playButton.innerHTML = 'Play'; actions.appendChild(playButton); const deleteButton = document.createElement("button"); deleteButton.type = "button"; deleteButton.className = "btn btn-sm btn-outline-danger"; deleteButton.dataset.deleteLocalPath = file.path; deleteButton.dataset.confirm = `Delete the local Pi copy of "${file.path}"? The remote archive will not be changed.`; deleteButton.innerHTML = 'Delete local'; actions.appendChild(deleteButton); row.appendChild(actions); body.appendChild(row); }); summary.textContent = `${files.length} video(s), ${YarisUI.formatBytes(result.total_size || 0)} total, ${uploadedCount} uploaded`; if (result.retention_days > 0) { summary.textContent += `; uploaded copies older than ${result.retention_days} day(s) are auto-deleted`; } else { summary.textContent += "; automatic retention cleanup is off"; } if (result.errors && result.errors.length) { summary.textContent += ` (${result.errors.length} file error(s))`; } } function refreshFiles() { refreshButton.disabled = true; summary.textContent = "Loading..."; return fetchJson("/api/local-videos") .then(renderFiles) .catch(function (error) { body.replaceChildren(); summary.textContent = `Unable to list local videos: ${error.message}`; }) .finally(function () { refreshButton.disabled = false; }); } function deleteVideo(path) { return fetchJson("/api/local-video", { method: "DELETE", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ path }), }).then(function () { if (playingPath === path) { closePlayer(); } return refreshFiles(); }).catch(function (error) { summary.textContent = `Local delete failed: ${error.message}`; }); } body.addEventListener("click", function (event) { const playButton = event.target.closest("[data-play-path]"); if (playButton) { playVideo(playButton.dataset.playPath); return; } const deleteButton = event.target.closest("[data-delete-local-path]"); if (deleteButton) { deleteButton.disabled = true; deleteVideo(deleteButton.dataset.deleteLocalPath).finally(function () { deleteButton.disabled = false; }); } }); refreshButton.addEventListener("click", refreshFiles); closePlayerButton.addEventListener("click", closePlayer); refreshFiles(); }());