Skip to content

Save playback position - #926

Merged
Dananji merged 5 commits into
mainfrom
save-playback-position
Apr 9, 2026
Merged

Save playback position#926
Dananji merged 5 commits into
mainfrom
save-playback-position

Conversation

@Dananji

@Dananji Dananji commented Apr 1, 2026

Copy link
Copy Markdown
Collaborator

Related issue: #919

Changes in this PR:

  • adds a resumeCache prop to MediaPlayer with a default of { enable: false, ttlDays: 30, maxItems: 200 } to configure the feature and LRU cache capacity in localStorage
  • adds usePlaybackPositions custom hook implementing a bounded LRU cache in localStorage to persist per-canvas playback positions. This is in its own file src/services/save-playback-positions.js to keep the LRU implementation with the hook implementation.
  • saves the current playback position on timeupdate events, skipping the first and last 5 seconds to avoid unhelpful resumes
  • clears the saved position when the user reaches the end of the media
  • shows a resume modal on initial page load if a saved position exists for the starting Canvas; modal is suppressed on canvas switches and when startCanvasTime is set in the props
  • when the resumeCache.enable is set to false any existing saved playback positions in localStorage is cleared otherwise the browser keeps this unused information indefinitely with the feature disabled

Implementation notes for the resume modal:

  • its creation and display is invoked inside a loadedmetadata event, which is registered via player.one() method to listen for this event exactly once. This ensures the modal is shown only on the first time the player is loaded and suppressed for subsequent Canvas switches.
  • resume modal is suppressed when startCanvasTime is set, as the explicit start time takes precedence
  • a saved position is cleared immediately when the user takes action to resume playback/start from the beginning from the resume modal
  • resume playback modal is not invoked in playlist context

The resume modal looks as follows for audio and video players;

Screenshot 2026-04-01 at 3 09 22 PM Screenshot 2026-04-01 at 3 09 10 PM

Possible future work:

  • show player controls along with the modal for easy navigation between canvases (just the previous/next buttons)
  • re-organize the code structure to separate the custom hook files into a different directory for a cleaner code

@Dananji
Dananji force-pushed the save-playback-position branch from a714de7 to 6747e79 Compare April 1, 2026 22:17

@cjcolvar cjcolvar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is really neat and I'm excited to have this feature in Avalon!

This looks great for single canvas manifests but it seems like it isn't resuming playback when the save position is on a canvas other than the first. For example, if I listen to https://media.dlib.indiana.edu/media_objects/tm70nc75d/manifest.json, which has two audio files, and stop playback at 20 seconds into the 2nd section and reload ramp with the manifest then ramp starts at 0 on the first canvas. I'd expect ramp to know that the saved playback position for the manifest is on the 2nd canvas at 20sec and offer to resume me there. This would be especially useful in a many sectioned item like https://media.dlib.indiana.edu/media_objects/nk322d54j.

Seeing that the local storage holds a canvas id -> playback position map, I'm wondering if this would need to be manifest id -> canvas id + playback position or if it is helpful to have two maps manifest id -> canvas id and canvas id -> playback position. It is theoretically possible for a canvas to exist in more than one manifest and it seems like it could be helpful to resume playback regardless of the manifest. But that is probably too much of an edge case to make the extra complexity worth it.

Does this make sense and seem feasible?

@Dananji

Dananji commented Apr 7, 2026

Copy link
Copy Markdown
Collaborator Author

Yes, that sounds great. It would provide a more user-friendly meaningful user experience for this feature.

I think it should be possible to change the storage to provide this solution. And it would probably reduce the number of cached entries in the localStorage as it would be storing only one Canvas info per Manifest, instead of how I had it imagined where it needed storing information for any Canvas that has a mid-playback stop point.

@Dananji
Dananji force-pushed the save-playback-position branch from e7f4419 to 33d35fe Compare April 7, 2026 17:04
@Dananji
Dananji marked this pull request as draft April 7, 2026 17:04
Comment thread src/services/save-playback-positions.test.js Fixed
…tion or class'

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
@Dananji

Dananji commented Apr 8, 2026

Copy link
Copy Markdown
Collaborator Author

Changes made from review:

  • adds usePlaybackPositions custom hook implementing a bounded LRU cache in localStorage to persist per-canvas playback positions. This is in its own file src/services/save-playback-positions.js to keep the LRU implementation with the hook implementation.
  • usePlaybackPositions custom hook implementing a bounded LRU cache in localStorage to persist per-Manifest playback positions, keyed by Manifest URL, storing the most recently active Canvas URL and playback time
  • shows a resume modal on initial page load if a saved position exists for the starting Canvas; modal is suppressed on canvas switches and when startCanvasTime is set in the props
  • shows a resume modal on initial page load if a saved position exists for the current Manifest; modal is suppressed on Canvas switches and when startCanvasTime is set in the props, or in playlist context

  • when the saved position is on a different Canvas than the one loaded on page load - i.e. the first Canvas of the Manifest, switches to the saved Canvas first and shows the modal after the Canvas switch settles via a pendingResumeRef

  • when the saved position is on a different Canvas than the one loaded on page load - i.e. the Canvas referenced by startCanvasId prop, then modal does not show up and the player loads with the Canvas specified in the startCanvasId prop

  • extracts the resume modal creation and display into VideoJSResumeModal.js as a standalone showResumeModal function

Implementation notes for the resume modal:

  • a saved position is cleared immediately when the user takes action to resume playback/start from the beginning from the resume modal
  • a saved position is cleared immediately when the user takes action from the resume modal (resume or dismiss)
  • when the saved Canvas differs from the starting Canvas, the dismiss button label reads No, start from beginning of this section to make it clear the action affects only the current Canvas

Notes:

  • resume modal is not shown when startCanvasId targets a Canvas other than the one the position was saved on
  • when the saved Canvas is no longer present in the Manifest, the stale entry is cleared and no modal is shown

For a multi-Canvas Manifest when the saved position is not on the first Canvas:
Screenshot 2026-04-08 at 4 03 58 PM
Screenshot 2026-04-08 at 4 03 47 PM

For a single-Canvas Manifest:
Screenshot 2026-04-08 at 4 04 06 PM

@Dananji
Dananji marked this pull request as ready for review April 8, 2026 23:05

@cjcolvar cjcolvar left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This looks great! I'm excited to see this make it to production!

Not within scope of this PR, but I had a couple thoughts that I wonder might be future improvements if user testing agrees:

@Dananji
Dananji merged commit 598d58a into main Apr 9, 2026
4 checks passed
@Dananji
Dananji deleted the save-playback-position branch April 9, 2026 15:44
@Dananji Dananji mentioned this pull request Apr 9, 2026
4 tasks
@Dananji

Dananji commented Apr 9, 2026

Copy link
Copy Markdown
Collaborator Author

New ticket to address the suggestions: #932

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