Remember and restore last playback position per video
Saves playback position to store on pause and periodically every 10s during playback. Restores position (>1s) when the same video is loaded again, showing a 'Resumed from 0:42' toast. Files: - frontend/app.js: resumePositions in data, save in pause/timeupdate, restore in Player.afterLoad, boot merge includes resumePositions
This commit is contained in:
@@ -19,7 +19,7 @@ bottom as they come up.
|
||||
- [x] Loading skeletons for search results instead of a bare "Searching…".
|
||||
- [x] Keyboard-shortcut help overlay (press `?`), listing space / arrows / f / m.
|
||||
- [x] Download/preload progress indicator on cards (percent or a bar, not just a pulse).
|
||||
- [ ] Remember and restore the last playback position per video.
|
||||
- [x] Remember and restore the last playback position per video.
|
||||
- [ ] Toast queue (stack multiple toasts instead of replacing).
|
||||
|
||||
## Features
|
||||
|
||||
@@ -45,7 +45,7 @@ function toAssetUrl(path) {
|
||||
}
|
||||
|
||||
// ---------- State ----------
|
||||
let data = { playlists: [], history: [], settings: { quality: 'auto', volume: 1, audioOnly: false, autoPreload: true } };
|
||||
let data = { playlists: [], history: [], settings: { quality: 'auto', volume: 1, audioOnly: false, autoPreload: true }, resumePositions: {} };
|
||||
let view = { type: 'search' }; // 'search' | 'history' | 'playlist' | 'settings'
|
||||
let searchResults = [];
|
||||
let queue = []; // list of video objects for autoplay
|
||||
@@ -245,6 +245,19 @@ const Player = {
|
||||
els.npChannel.textContent = current.meta.channel || '';
|
||||
updateNowPlayingActions();
|
||||
markPlayingCard();
|
||||
// Restore saved playback position
|
||||
const id = current.meta.id;
|
||||
if (data.resumePositions[id] && data.resumePositions[id] > 1) {
|
||||
const saved = data.resumePositions[id];
|
||||
const restore = () => {
|
||||
Player.seek(saved);
|
||||
toast('Resumed from ' + fmtTime(saved));
|
||||
Player.master.removeEventListener('canplay', restore);
|
||||
Player.master.removeEventListener('loadedmetadata', restore);
|
||||
};
|
||||
Player.master.addEventListener('canplay', restore, { once: true });
|
||||
Player.master.addEventListener('loadedmetadata', restore, { once: true });
|
||||
}
|
||||
},
|
||||
|
||||
// Ordered list of qualities to try if playback errors out. Progressive
|
||||
@@ -459,7 +472,28 @@ function wirePlayerEvents() {
|
||||
el.addEventListener('waiting', () => { if (masterIs(el)) { showSpinner(true); if (Player.secondary) Player.secondary.pause(); } });
|
||||
el.addEventListener('playing', () => { if (masterIs(el)) { showSpinner(false); if (Player.secondary && !el.paused) { Player.secondary.currentTime = el.currentTime; Player.secondary.play().catch(() => {}); } } });
|
||||
el.addEventListener('canplay', () => { if (masterIs(el)) showSpinner(false); });
|
||||
el.addEventListener('timeupdate', () => { if (masterIs(el)) updateProgress(); });
|
||||
el.addEventListener('timeupdate', () => {
|
||||
if (masterIs(el)) {
|
||||
updateProgress();
|
||||
// Persist playback position every 10s
|
||||
if (current && current.meta) {
|
||||
const t = Player.master.currentTime;
|
||||
if (t > 5 && Math.floor(t) % 10 === 0) {
|
||||
data.resumePositions[current.meta.id] = t;
|
||||
persist();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
el.addEventListener('pause', () => {
|
||||
if (masterIs(el) && current && current.meta) {
|
||||
const t = Player.master.currentTime;
|
||||
if (t > 1) {
|
||||
data.resumePositions[current.meta.id] = t;
|
||||
persist();
|
||||
}
|
||||
}
|
||||
});
|
||||
el.addEventListener('loadedmetadata', () => { if (masterIs(el)) updateProgress(); });
|
||||
el.addEventListener('ended', () => { if (masterIs(el)) playNext(); });
|
||||
el.addEventListener('error', () => {
|
||||
@@ -1137,6 +1171,7 @@ async function boot() {
|
||||
data = {
|
||||
playlists: loaded.playlists || [],
|
||||
history: loaded.history || [],
|
||||
resumePositions: loaded.resumePositions || {},
|
||||
settings: { quality: 'auto', volume: 1, audioOnly: false, autoPreload: true, ...(loaded.settings || {}) },
|
||||
};
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user