From b4aaa4410c6595b8965bd28ba5bffdd6725e94b1 Mon Sep 17 00:00:00 2001 From: Carl Pearson Date: Thu, 1 May 2025 05:50:53 -0600 Subject: [PATCH] Track timestamps server-side --- README.md | 2 +- handlers/set_timestamp.go | 12 ++--- static/script/save-media-progress.js | 70 +++++++++++++++++----------- 3 files changed, 49 insertions(+), 35 deletions(-) diff --git a/README.md b/README.md index 4cece93..e5e4e52 100644 --- a/README.md +++ b/README.md @@ -75,4 +75,4 @@ Build and push this container to ghcr - [x] header on playlist page - [x] Choose database directory - [x] add extension to download -- [ ] server-side watch progress +- [x] server-side watch progress diff --git a/handlers/set_timestamp.go b/handlers/set_timestamp.go index a609ba9..d5454c1 100644 --- a/handlers/set_timestamp.go +++ b/handlers/set_timestamp.go @@ -20,24 +20,20 @@ func SetTimestamp(c echo.Context) error { // Define a struct to receive the timestamp from JSON type TimestampRequest struct { - Timestamp string `json:"timestamp"` + Timestamp float64 `json:"timestamp"` } // Parse the request body var req TimestampRequest if err := c.Bind(&req); err != nil { + log.Errorln(err) return c.JSON(http.StatusBadRequest, map[string]string{"error": "Invalid request body"}) } - // Validate that timestamp was provided - if req.Timestamp == "" { - return c.JSON(http.StatusBadRequest, map[string]string{"error": "Timestamp is required"}) - } - - // Get database connection - db := database.Get() + log.Debugln("set video", id, "timestamp:", req.Timestamp) // Update the timestamp field with the value from the request + db := database.Get() result := db.Model(&originals.Original{}). Where("id = ?", id). Update("timestamp", req.Timestamp) diff --git a/static/script/save-media-progress.js b/static/script/save-media-progress.js index 16c4f30..cebe932 100644 --- a/static/script/save-media-progress.js +++ b/static/script/save-media-progress.js @@ -1,42 +1,60 @@ - // Get all video and audio elements const mediaElements = document.querySelectorAll('video, audio'); -// Generate a unique key for this page -const pageKey = `mediaProgress_${window.location.pathname}`; +// Get the video ID from the hidden input +const videoId = document.getElementById('video-id').value; -// Function to save the current time of the most recently played media -function saveMediaProgress(media) { - localStorage.setItem(pageKey, media.currentTime); +// Get the initial timestamp from the hidden input +const initialTimestamp = parseFloat(document.getElementById('video-timestamp').value || 0); + +// Track the last time we sent an update to avoid excessive requests +let lastUpdateTime = 0; +const UPDATE_INTERVAL = 5000; // 5 seconds in milliseconds + +// Function to send the current time to the server +function sendTimestampToServer(timestamp) { + console.log(timestamp) + fetch(`/video/${videoId}/set_timestamp`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ "timestamp": timestamp }) + }).catch(error => { + console.error('Error sending timestamp to server:', error); + }); } -// Function to load and set the saved time for all media elements -function loadMediaProgress() { - const savedTime = localStorage.getItem(pageKey); - if (savedTime) { - mediaElements.forEach(media => { - media.currentTime = Math.min(parseFloat(savedTime), media.duration || Infinity); - }); +// Function to handle periodic updates while playing +function handleTimeUpdate(media) { + // Only send updates every 5 seconds while playing + const now = Date.now(); + if (!media.paused && now - lastUpdateTime >= UPDATE_INTERVAL) { + sendTimestampToServer(media.currentTime); + lastUpdateTime = now; } } // Set up event listeners for each media element mediaElements.forEach(media => { - // Save progress when the media is playing - media.addEventListener('timeupdate', () => { - if (!media.paused) { - saveMediaProgress(media); + // Set initial timestamp when media is ready + media.addEventListener('loadedmetadata', () => { + // Only set if the initial timestamp is within the media duration + if (initialTimestamp > 0 && initialTimestamp < media.duration) { + media.currentTime = initialTimestamp; } }); - // Also save when the media is paused - media.addEventListener('pause', () => saveMediaProgress(media)); + // Send updates periodically while playing + media.addEventListener('timeupdate', () => handleTimeUpdate(media)); - // Load the saved progress when the media is ready - media.addEventListener('loadedmetadata', loadMediaProgress); - - // Clear progress when any media ends - media.addEventListener('ended', () => { - localStorage.removeItem(pageKey); + // Send update when media is paused + media.addEventListener('pause', () => { + sendTimestampToServer(media.currentTime); }); -}); \ No newline at end of file + + // Reset timestamp when media ends + media.addEventListener('ended', () => { + sendTimestampToServer(0); + }); +});