Fix position saving for videos and have history update when opened - #4245
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughHistoryPage now loads watch history on activation via loadHistory(), iterating IndexedDB to prune expired entries and populate videosStore; auto-delete prefs are initialized at module setup. VideoPlayer resets initialSeekComplete on load, applies startTime to the media element after Shaka load, and prevents progress/history DB writes during component teardown. ChangesWatch History and Playback Flow
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/components/HistoryPage.vue (1)
122-146: ⚡ Quick winAdd error handler for IndexedDB cursor request.
If the cursor request fails,
cursorPromisewill never resolve and the history page will appear stuck without feedback. Consider adding anonerrorhandler.Proposed fix
const cursorRequest = store.index("watchedAt").openCursor(null, "prev"); const cursorPromise = new Promise(resolve => { cursorRequest.onsuccess = e => { const cursor = e.target.result; if (cursor) { const video = cursor.value; if (!shouldRemoveVideo(video)) { videosStore.push({ url: "/watch?v=" + video.videoId, title: video.title, uploaderName: video.uploaderName, uploaderUrl: video.uploaderUrl ?? "", duration: video.duration ?? 0, thumbnail: video.thumbnail, watchedAt: video.watchedAt, watched: true, currentTime: video.currentTime, }); } else { store.delete(video.videoId); } if (videosStore.length < 1000) cursor.continue(); else resolve(); } else resolve(); }; + cursorRequest.onerror = () => { + console.error("Failed to read watch history"); + resolve(); + }; });🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/HistoryPage.vue` around lines 122 - 146, The cursorPromise created for the IndexedDB cursor (cursorRequest) lacks an error handler so it can hang if the request fails; add cursorRequest.onerror to handle errors by logging the error (use console.error or existing logger), resolving or rejecting the cursorPromise so awaiting code doesn't block, and ensure any cleanup like not leaving unresolved promises or partially populated videosStore; update the block that defines cursorPromise/cursorRequest to attach an onerror callback that calls resolve() or reject(err) and logs context (e.g., reference cursorRequest, cursorPromise, shouldRemoveVideo, store, videosStore).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/components/HistoryPage.vue`:
- Around line 122-146: The cursorPromise created for the IndexedDB cursor
(cursorRequest) lacks an error handler so it can hang if the request fails; add
cursorRequest.onerror to handle errors by logging the error (use console.error
or existing logger), resolving or rejecting the cursorPromise so awaiting code
doesn't block, and ensure any cleanup like not leaving unresolved promises or
partially populated videosStore; update the block that defines
cursorPromise/cursorRequest to attach an onerror callback that calls resolve()
or reject(err) and logs context (e.g., reference cursorRequest, cursorPromise,
shouldRemoveVideo, store, videosStore).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 06bad0ea-8d5c-42b9-8194-0de017af8e3c
📒 Files selected for processing (2)
src/components/HistoryPage.vuesrc/components/VideoPlayer.vue
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/components/HistoryPage.vue (1)
112-152:⚠️ Potential issue | 🟠 Major | ⚡ Quick winGuard
loadHistory()against overlapping refreshes.This rebuilds shared
videosStore/videosstate incrementally. If the page is reactivated before the previous IndexedDB cursor finishes, both runs will append into the same arrays and can leave duplicated or partially ordered history entries. Build into a local array and only commit the latest invocation.Suggested direction
+let loadHistoryRun = 0; + async function loadHistory() { - videosStore.length = 0; - currentVideoCount = 0; - videos.value = []; + const run = ++loadHistoryRun; + const nextVideos = []; - return (async () => { + await (async () => { if (window.db && getPreferenceBoolean("watchHistory", false)) { // ... cursorRequest.onsuccess = e => { const cursor = e.target.result; if (cursor) { const video = cursor.value; if (!shouldRemoveVideo(video)) { - videosStore.push({ + nextVideos.push({ // ... }); } else { store.delete(video.videoId); } - if (videosStore.length < 1000) cursor.continue(); + if (nextVideos.length < 1000) cursor.continue(); else resolve(); } else resolve(); }; } - })().then(() => { - loadMoreVideos(); - }); + })(); + + if (run !== loadHistoryRun) return; + videosStore.length = 0; + videosStore.push(...nextVideos); + currentVideoCount = 0; + videos.value = []; + loadMoreVideos(); }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/HistoryPage.vue` around lines 112 - 152, loadHistory is vulnerable to overlapping runs mutating shared videosStore/videos; change it to collect results in a local array (e.g., localVideos) and use a run token (incrementing id or AbortController) stored in module scope (e.g., loadHistoryRunId) so only the latest invocation commits to videosStore, videos and currentVideoCount after the cursor promise completes; ensure when committing you replace or merge deterministically (not push) and ignore results if the run token has changed mid-run or signal is aborted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/VideoPlayer.vue`:
- Around line 476-480: The code currently sets el.currentTime immediately after
playerInstance.load resolves, which can allow a timeupdate handler to flip
initialSeekComplete true before the resume seek actually lands; modify the logic
so initialSeekComplete remains false until the resume seek finishes by
performing the seek (setting el.currentTime) and then waiting for the element's
'seeked' event (only when startTime > 0) before setting initialSeekComplete =
true; update both the load resolution block that uses playerInstance.load and
the similar branch around line 577 to attach a one-time 'seeked' listener (or
await a seeked promise) and only then flip initialSeekComplete, ensuring the
time/history branches still respect that gate.
---
Outside diff comments:
In `@src/components/HistoryPage.vue`:
- Around line 112-152: loadHistory is vulnerable to overlapping runs mutating
shared videosStore/videos; change it to collect results in a local array (e.g.,
localVideos) and use a run token (incrementing id or AbortController) stored in
module scope (e.g., loadHistoryRunId) so only the latest invocation commits to
videosStore, videos and currentVideoCount after the cursor promise completes;
ensure when committing you replace or merge deterministically (not push) and
ignore results if the run token has changed mid-run or signal is aborted.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b0c7e630-1ec5-40c1-96a4-8b297a9ad00b
📒 Files selected for processing (2)
src/components/HistoryPage.vuesrc/components/VideoPlayer.vue
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/components/HistoryPage.vue (1)
122-146: 💤 Low valueMissing
onerrorhandler may leave promise hanging.If the IndexedDB cursor request fails,
cursorPromisenever resolves or rejects, causingloadHistory()to hang indefinitely. Consider adding anonerrorhandler.Suggested fix
const cursorPromise = new Promise(resolve => { + cursorRequest.onerror = () => resolve(); cursorRequest.onsuccess = e => {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/HistoryPage.vue` around lines 122 - 146, The cursorPromise lacks an onerror handler so a failed IndexedDB cursorRequest can leave loadHistory() hanging; add cursorRequest.onerror = e => { /* reject or resolve to unblock */ } to ensure the promise always settles (use reject(e.target.error) or resolve() consistent with how loadHistory() expects), and update any consumer of cursorPromise if needed to handle a rejection; locate the cursorPromise and cursorRequest in HistoryPage.vue and add the onerror callback to cleanly handle errors and perform any necessary cleanup (e.g., close store or stop iterating).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@src/components/HistoryPage.vue`:
- Around line 122-146: The cursorPromise lacks an onerror handler so a failed
IndexedDB cursorRequest can leave loadHistory() hanging; add
cursorRequest.onerror = e => { /* reject or resolve to unblock */ } to ensure
the promise always settles (use reject(e.target.error) or resolve() consistent
with how loadHistory() expects), and update any consumer of cursorPromise if
needed to handle a rejection; locate the cursorPromise and cursorRequest in
HistoryPage.vue and add the onerror callback to cleanly handle errors and
perform any necessary cleanup (e.g., close store or stop iterating).
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: ffb18319-2270-466a-901c-628af9a6b749
📒 Files selected for processing (2)
src/components/HistoryPage.vuesrc/components/VideoPlayer.vue
Clicking a video from history (or any cached watch page) restarted it from 0 instead of resuming, and history bars showed stale positions. Four shared-path bugs, all engine-agnostic: - The resume position was computed but never applied: Shaka's load(uri, startTime) doesn't perform the initial seek for lazily-fetched segment indexes, so playback began at 0. Apply the resume explicitly with a runtime seek once load() resolves. - initialSeekComplete (gates progress saving until the resume seek lands) was never reset per-load. On a reactivated player it stayed true from the previous play, so a timeupdate at currentTime=0 during rebuild churn overwrote the saved position before the resume read ran. Reset it at the start of loadVideo. - Leaving a watch page (destroy) empties the media element -> currentTime snaps to 0 and a stray timeupdate fires while initialSeekComplete is still true, clobbering the saved position. Gate the save on destroying as well. - HistoryPage: re-read watch_history in onActivated so progress bars reflect the current saved position instead of a stale first-mount snapshot. Kept off onMounted to avoid double-loading (both fire on first keep-alive mount). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The code was written with Claude and reviewed by me.
Description by Claude:
Clicking a video from history (or any cached watch page) restarted it from 0 instead of resuming, and history bars showed stale positions. Four shared-path bugs, all engine-agnostic:
The resume position was computed but never applied: Shaka's load(uri, startTime) doesn't perform the initial seek for lazily-fetched segment indexes, so playback began at 0. Apply the resume explicitly with a runtime seek once load() resolves.
initialSeekComplete (gates progress saving until the resume seek lands) was never reset per-load. On a reactivated player it stayed true from the previous play, so a timeupdate at currentTime=0 during rebuild churn overwrote the saved position before the resume read ran. Reset it at the start of loadVideo.
Leaving a watch page (destroy) empties the media element -> currentTime snaps to 0 and a stray timeupdate fires while initialSeekComplete is still true, clobbering the saved position. Gate the save on destroying as well.
HistoryPage: re-read watch_history in onActivated so progress bars reflect the current saved position instead of a stale first-mount snapshot. Kept off onMounted to avoid double-loading (both fire on first keep-alive mount).
Summary by CodeRabbit
Bug Fixes
Improvements