Skip to content

Fix position saving for videos and have history update when opened - #4245

Merged
FireMasterK merged 1 commit into
TeamPiped:masterfrom
t6fb3m59:upstream-pr
Jun 1, 2026
Merged

Fix position saving for videos and have history update when opened#4245
FireMasterK merged 1 commit into
TeamPiped:masterfrom
t6fb3m59:upstream-pr

Conversation

@t6fb3m59

@t6fb3m59 t6fb3m59 commented May 30, 2026

Copy link
Copy Markdown

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

    • Prevented playback progress updates during component teardown, improving reliability of saved position.
    • Ensured videos resume precisely from the intended start time after loading.
  • Improvements

    • Faster, more reliable watch history loading and lifecycle handling when viewing history.
    • Automatically filters and removes expired history entries.
    • Auto-delete preferences initialized earlier and scroll handling refined for a more responsive UI.

@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 271b0246-a241-4620-9281-e6261d39301d

📥 Commits

Reviewing files that changed from the base of the PR and between 99d879a and 174ca1a.

📒 Files selected for processing (2)
  • src/components/HistoryPage.vue
  • src/components/VideoPlayer.vue
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/components/VideoPlayer.vue
  • src/components/HistoryPage.vue

📝 Walkthrough

Walkthrough

HistoryPage 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.

Changes

Watch History and Playback Flow

Layer / File(s) Summary
History loading lifecycle
src/components/HistoryPage.vue
Adds loadHistory() to reset videosStore, currentVideoCount, and videos, iterate watch_history via the watchedAt index in descending order, delete expired entries with shouldRemoveVideo, and append remaining items. Moves initial load to onActivated (sets document.title). Auto-delete preferences are initialized eagerly at setup.
Video player initialization and progress tracking
src/components/VideoPlayer.vue
loadVideo resets initialSeekComplete to false. After playerInstance.load(...) resolves, the code sets the underlying video element currentTime to startTime when > 0 and waits for seeked, then marks initialSeekComplete. updateProgressDatabase now returns early when destroying.value is true to avoid writes during teardown.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A rabbit hums as histories wake,
Activation stirs old crumbs to take,
The player waits, then seeks anew,
Safe writes pause when leaving view,
Hops of code make pathways true.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: fixing video position saving and ensuring history updates when the page is reopened.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/components/HistoryPage.vue (1)

122-146: ⚡ Quick win

Add error handler for IndexedDB cursor request.

If the cursor request fails, cursorPromise will never resolve and the history page will appear stuck without feedback. Consider adding an onerror handler.

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

📥 Commits

Reviewing files that changed from the base of the PR and between da7ab35 and 3e671f3.

📒 Files selected for processing (2)
  • src/components/HistoryPage.vue
  • src/components/VideoPlayer.vue

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 win

Guard loadHistory() against overlapping refreshes.

This rebuilds shared videosStore/videos state 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

📥 Commits

Reviewing files that changed from the base of the PR and between 3e671f3 and d0baf9b.

📒 Files selected for processing (2)
  • src/components/HistoryPage.vue
  • src/components/VideoPlayer.vue

Comment thread src/components/VideoPlayer.vue

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/components/HistoryPage.vue (1)

122-146: 💤 Low value

Missing onerror handler may leave promise hanging.

If the IndexedDB cursor request fails, cursorPromise never resolves or rejects, causing loadHistory() to hang indefinitely. Consider adding an onerror handler.

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

📥 Commits

Reviewing files that changed from the base of the PR and between d0baf9b and 99d879a.

📒 Files selected for processing (2)
  • src/components/HistoryPage.vue
  • src/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>
@FireMasterK
FireMasterK merged commit 5321c71 into TeamPiped:master Jun 1, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants