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] Loading skeletons for search results instead of a bare "Searching…".
|
||||||
- [x] Keyboard-shortcut help overlay (press `?`), listing space / arrows / f / m.
|
- [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).
|
- [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).
|
- [ ] Toast queue (stack multiple toasts instead of replacing).
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ function toAssetUrl(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------- State ----------
|
// ---------- 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 view = { type: 'search' }; // 'search' | 'history' | 'playlist' | 'settings'
|
||||||
let searchResults = [];
|
let searchResults = [];
|
||||||
let queue = []; // list of video objects for autoplay
|
let queue = []; // list of video objects for autoplay
|
||||||
@@ -245,6 +245,19 @@ const Player = {
|
|||||||
els.npChannel.textContent = current.meta.channel || '';
|
els.npChannel.textContent = current.meta.channel || '';
|
||||||
updateNowPlayingActions();
|
updateNowPlayingActions();
|
||||||
markPlayingCard();
|
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
|
// 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('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('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('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('loadedmetadata', () => { if (masterIs(el)) updateProgress(); });
|
||||||
el.addEventListener('ended', () => { if (masterIs(el)) playNext(); });
|
el.addEventListener('ended', () => { if (masterIs(el)) playNext(); });
|
||||||
el.addEventListener('error', () => {
|
el.addEventListener('error', () => {
|
||||||
@@ -1137,6 +1171,7 @@ async function boot() {
|
|||||||
data = {
|
data = {
|
||||||
playlists: loaded.playlists || [],
|
playlists: loaded.playlists || [],
|
||||||
history: loaded.history || [],
|
history: loaded.history || [],
|
||||||
|
resumePositions: loaded.resumePositions || {},
|
||||||
settings: { quality: 'auto', volume: 1, audioOnly: false, autoPreload: true, ...(loaded.settings || {}) },
|
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