Refresh presigned media URL every 7 minutes to prevent TTL expiry

Long videos (>15 min) would die mid-playback when the 15-minute presigned
URL expired. Now a timer fetches a fresh URL from the watch JSON endpoint
every 7 minutes, swaps the src, and seeks back to the saved position.
Timer resets on every product transition.
This commit is contained in:
russell@unturf.com 2026-02-09 13:27:22 -05:00
parent 204bca813d
commit 6eb4b3ca95

View file

@ -18,6 +18,9 @@
var djCrossfadeActive = false;
var djCrossfadeData = null;
var preloadingId = null;
var currentProductId = null;
var urlRefreshTimer = null;
var URL_REFRESH_MS = 7 * 60 * 1000; // 7 minutes
// --- Ring state (persisted in localStorage) ---
var ringProductIds = [];
@ -161,6 +164,7 @@
var initialProductEl = document.querySelector('[data-watch-product-id]');
if (initialProductEl) {
var initialId = initialProductEl.getAttribute('data-watch-product-id');
currentProductId = initialId;
markWatched(initialId);
syncRingPosition(initialId);
if (initialProductEl.getAttribute('data-watch-is-mod') === '1') {
@ -514,8 +518,10 @@
ringProductIds = data.ring;
}
if (data.is_mod) isMod = true;
currentProductId = data.product_id;
markWatched(data.product_id);
syncRingPosition(data.product_id);
scheduleUrlRefresh();
document.title = data.title;
@ -1056,6 +1062,48 @@
standbyMedia.load();
}
// --- Presigned URL refresh: swap src every 7 min to prevent TTL expiry ---
function scheduleUrlRefresh() {
if (urlRefreshTimer) clearTimeout(urlRefreshTimer);
urlRefreshTimer = setTimeout(refreshMediaUrl, URL_REFRESH_MS);
}
function refreshMediaUrl() {
urlRefreshTimer = null;
if (!activeMedia || !currentProductId) return;
// Skip refresh during crossfade — the new media already has a fresh URL
if (djCrossfadeActive) { scheduleUrlRefresh(); return; }
fetchWatchData(currentProductId).then(function(data) {
if (!data || !data.media_url) { scheduleUrlRefresh(); return; }
// Only refresh if still on the same product
if (data.product_id !== currentProductId) { scheduleUrlRefresh(); return; }
var wasPlaying = !activeMedia.paused;
var savedTime = activeMedia.currentTime;
var savedVolume = activeMedia.volume;
var savedMuted = activeMedia.muted;
activeMedia.src = data.media_url;
activeMedia.addEventListener('loadeddata', function onLoaded() {
activeMedia.removeEventListener('loadeddata', onLoaded);
activeMedia.currentTime = savedTime;
activeMedia.volume = savedVolume;
activeMedia.muted = savedMuted;
if (wasPlaying) activeMedia.play().catch(function() {});
scheduleUrlRefresh();
});
// Fallback: if loadeddata never fires, reschedule anyway
setTimeout(function() {
if (!urlRefreshTimer) scheduleUrlRefresh();
}, 10000);
}).catch(function() {
scheduleUrlRefresh();
});
}
// --- Named event handlers (must be named so removeEventListener works) ---
function onMediaEnded() {
// Fallback: only start countdown if DJ crossfade didn't handle it
@ -1113,6 +1161,7 @@
// Initial setup
if (activeMedia) {
setupMediaEvents();
scheduleUrlRefresh();
} else {
// Static content (PDF, image, etc.) — start 42-second timer
// Reset on scroll — scrolling means the user is still reading