Save a position only from a player that has played since its last save
A tab left paused further into an episode saved its older place as it reloaded, over where the listener had got to since, and the episode dropped out of Currently Listening. A jump back is now saved at once. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,11 @@ The long form, with what was wrong before and how it was found, is in
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- A player left open in another tab or on another device no longer saves its older place over
|
||||
where you have got to since, which could drop an episode out of Currently Listening.
|
||||
|
||||
## [0.6.0] - 2026-09-15
|
||||
|
||||
### Added
|
||||
|
||||
@@ -6,6 +6,28 @@ reasoning lives. New write-ups go at the top.
|
||||
|
||||
See [README.md](../README.md) for what the thing is.
|
||||
|
||||
## 2026-09-15 — A player nobody was listening to, saving over one that was
|
||||
|
||||
After 0.6.0 the Kristen Bell episode dropped out of Currently Listening again, with eleven
|
||||
minutes left in the player. Its saved position was 41:15 of a file 43:48 long, 94% and finished
|
||||
by any measure, while the listener was at about 32:48. The first guess was a wrong length from
|
||||
the feed, and it was wrong: the feed does say 41:23, but no length makes 41:15 unfinished.
|
||||
|
||||
The log had the answer. Saves every ten seconds while it played, then two that were not
|
||||
playback: one beside the only request for the file since the restart, and the last in the same
|
||||
millisecond as a page load. A player left paused further on, in another tab or on another
|
||||
device, saved its own place as its page reloaded. Every save goes through `savePos`, and it
|
||||
saved whatever the player held whether or not anyone had played it since.
|
||||
|
||||
`savePos` now saves only once the player has played since its last save, so an idle one never
|
||||
writes, and only playing counts: the seek to where you left off happens paused, and counting it
|
||||
would save back whatever the list said, however old. A jump back is also saved at once, where
|
||||
the ten-second check only ever looked forward. The fixture audio does not decode, so the browser
|
||||
test stands in for a loaded player and counts what `savePos` sends.
|
||||
|
||||
The feed's length, 2 minutes 25 seconds short of the file, still moves this episode's 90% line
|
||||
earlier than the player's. Not fixed here; the measured length only fills in a missing one.
|
||||
|
||||
## 2026-09-15 — Currently Listening, empty for anyone who opens what they play
|
||||
|
||||
Issue #14: an episode 32 minutes into 41 was missing from Currently Listening, which said nothing
|
||||
|
||||
@@ -200,6 +200,26 @@ test('Currently Listening, its own place below Popular, resumes an episode you s
|
||||
await expect(row.locator('.eq')).toBeHidden();
|
||||
});
|
||||
|
||||
test('a player nobody has played since it last saved does not save again', async ({ page }) => {
|
||||
// A tab left paused at 41:15 saved that as it reloaded, over the 32:48 another had reached,
|
||||
// and the episode dropped out of Currently Listening. The fixture audio does not decode, so
|
||||
// this stands in for a loaded file paused at 2 seconds and counts what savePos sends.
|
||||
const sent = await page.evaluate(() => {
|
||||
Object.defineProperty(audio, 'readyState', { get: () => 4 });
|
||||
Object.defineProperty(audio, 'currentTime', { get: () => 2, set() {} });
|
||||
let n = 0;
|
||||
navigator.sendBeacon = () => (n++, true);
|
||||
player.guid = 'ui-2'; player.feed = 'test-show'; player.entry = null; player.moved = false;
|
||||
savePos(); // what a reload, a pause or the close button calls
|
||||
const idle = n;
|
||||
player.moved = true; // what playing sets
|
||||
savePos();
|
||||
savePos(); // and once saved, it is idle again
|
||||
return [idle, n];
|
||||
});
|
||||
expect(sent).toEqual([0, 1]);
|
||||
});
|
||||
|
||||
test('the filter tabs change what is listed', async ({ page }) => {
|
||||
await page.locator('.feed', { hasText: 'Test Show' }).click();
|
||||
await expect(page.locator('.ep').first()).toBeVisible({ timeout: 20_000 });
|
||||
|
||||
@@ -1507,7 +1507,7 @@ function play(e,enc=e.enclosures.find(isPlayable)){
|
||||
// The same file carries on where it was; another of the item's files starts from its top.
|
||||
const resuming = player.guid===e.guid && player.enc===enc.id;
|
||||
if(!resuming){
|
||||
player.guid=e.guid; player.feed=e.feed_id; player.entry=e; player.enc=enc.id;
|
||||
player.guid=e.guid; player.feed=e.feed_id; player.entry=e; player.enc=enc.id; player.moved=false;
|
||||
document.body.classList.toggle('has-video', kindOf(enc)==='video');
|
||||
audio.src=`/media/${enc.id}`;
|
||||
audio.currentTime=0;
|
||||
@@ -1539,14 +1539,21 @@ audio.addEventListener('timeupdate',()=>{
|
||||
$('#pcur').textContent=clock(audio.currentTime);
|
||||
$('#pdur').textContent=clock(d);
|
||||
if(d) $('#seek').value=String(Math.round(audio.currentTime/d*1000));
|
||||
// Persist roughly every 10s so a reload resumes where you were.
|
||||
if(player.guid && audio.currentTime-player.saveAt>10){ savePos(); }
|
||||
// Only playing counts as moving: the seek to where you left off happens paused, and saving
|
||||
// that would write back whatever the list said, however old.
|
||||
if(!audio.paused) player.moved=true;
|
||||
// Persist roughly every 10s so a reload resumes where you were. Either way: a jump back used
|
||||
// to wait for the next pause to be saved.
|
||||
if(player.guid && Math.abs(audio.currentTime-player.saveAt)>10){ savePos(); }
|
||||
if(d && audio.currentTime/d >= 0.9) markPlayed();
|
||||
});
|
||||
function savePos(){
|
||||
// Before the file has loaded, currentTime is 0 rather than where you are: saving it then --
|
||||
// a failed load, or a pause before the seek to where you left off -- wiped the position.
|
||||
if(!player.guid||!audio.readyState) return;
|
||||
// Nor from a player nobody has played since it last saved: one left paused in another tab
|
||||
// saved its older place as that tab reloaded, over where you had got to since.
|
||||
if(!player.guid||!audio.readyState||!player.moved) return;
|
||||
player.moved=false;
|
||||
player.saveAt=audio.currentTime;
|
||||
if(player.entry) player.entry.position=Math.floor(audio.currentTime);
|
||||
// The measured length stands in for one the feed left out: without it Currently Listening
|
||||
|
||||
Reference in New Issue
Block a user