From 24cf7a39f70910e871ab3bacd6c74e414009ef2d Mon Sep 17 00:00:00 2001 From: 5shekel Date: Sun, 18 Jan 2026 02:42:45 +0200 Subject: [PATCH] feat: add deep link support for audio position (t= param) and set default transcript rows to 2 --- app.js | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- config.js | 2 +- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index d5db3c2..c74eff6 100644 --- a/app.js +++ b/app.js @@ -78,6 +78,41 @@ function formatTime(seconds) { return `${mins}:${String(secs).padStart(2, "0")}`; } +// Parse time from string: supports seconds (120), m:ss (2:30), h:mm:ss (1:30:45) +function parseTimeString(timeStr) { + if (!timeStr) return null; + const parts = timeStr.split(":").map(Number); + if (parts.some(isNaN)) return null; + if (parts.length === 1) return parts[0]; // seconds only + if (parts.length === 2) return parts[0] * 60 + parts[1]; // m:ss + if (parts.length === 3) return parts[0] * 3600 + parts[1] * 60 + parts[2]; // h:mm:ss + return null; +} + +// Get time parameter from URL (supports ?t=120 or #t=120) +function getTimeFromURL() { + const params = new URLSearchParams(window.location.search); + const queryTime = params.get("t"); + if (queryTime) return parseTimeString(queryTime); + + const hash = window.location.hash.slice(1); // remove # + const hashParams = new URLSearchParams(hash); + const hashTime = hashParams.get("t"); + if (hashTime) return parseTimeString(hashTime); + + return null; +} + +// Update URL with current time (uses hash to avoid page reload) +function updateURLTime(seconds) { + if (!Number.isFinite(seconds) || seconds < 0) return; + const time = Math.floor(seconds); + const newHash = `t=${time}`; + if (window.location.hash !== `#${newHash}`) { + history.replaceState(null, "", `#${newHash}`); + } +} + function hashString(input) { let hash = 0; for (let i = 0; i < input.length; i += 1) { @@ -786,12 +821,31 @@ async function init() { playPause.textContent = "Play"; }); audio.addEventListener("loadedmetadata", () => { - // Start at configured offset, but don't exceed audio duration + // Check URL for deep link time first + const urlTime = getTimeFromURL(); + if (urlTime !== null && Number.isFinite(audio.duration) && audio.duration > 0) { + audio.currentTime = Math.min(urlTime, audio.duration - 0.1); + return; + } + // Fall back to configured offset, but don't exceed audio duration if (Number.isFinite(audio.duration) && audio.duration > 0 && START_OFFSET_SECONDS > 0) { audio.currentTime = Math.min(START_OFFSET_SECONDS, audio.duration - 1); } }); + // Update URL when user seeks (manual scrub or click) + audio.addEventListener("seeked", () => { + updateURLTime(audio.currentTime); + }); + + // Handle hash changes (when user modifies URL directly) + window.addEventListener("hashchange", () => { + const urlTime = getTimeFromURL(); + if (urlTime !== null && Number.isFinite(audio.duration)) { + audio.currentTime = Math.min(urlTime, audio.duration - 0.1); + } + }); + if (configToggle && configPanel) { configToggle.addEventListener("click", () => { configPanel.classList.toggle("open"); diff --git a/config.js b/config.js index 9e25dda..8bb018a 100644 --- a/config.js +++ b/config.js @@ -57,7 +57,7 @@ const CONFIG = { // =================== transcript: { - rows: 6, // Number of transcript lines shown (2-12) + rows: 2, // Number of transcript lines shown (2-12) opacity: 0.25, // Transcript panel opacity (0.2-1.0) size: "medium", // Text size: "small", "medium", "large" },