Mark episodes read when finished, not when started

play() set read=1 the instant playback began. The default view is the
Unread tab, so pressing play removed the episode from the list being
looked at, which reads as the episode going missing. All four affected
rows had position=0: started, never listened to.

Read is now set on 'ended' or past 90% of the duration.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RPyeapneuXrCdojsaiXGbe
This commit is contained in:
2026-09-10 01:55:46 +00:00
parent 72280b9ccd
commit 44623098ae
2 changed files with 46 additions and 6 deletions

View File

@@ -495,7 +495,20 @@ async function epAction(a,e,el){
/* ---------------- player ---------------- */
const audio=$('#audio');
const player={guid:null,feed:null,entry:null,saveAt:0};
const player={guid:null,feed:null,entry:null,saveAt:0,marked:false};
// Marked read when an episode has actually been listened to -- at the end, or past 90%.
// NOT on play: doing that made the episode vanish from the default Unread list the instant
// you pressed play, which looks exactly like it went missing.
function markPlayed(){
if(!player.guid||player.marked) return;
player.marked=true;
const e=player.entry;
if(!e||e.read) return;
e.read=true;
api(`/api/entries/${encodeURIComponent(e.feed_id)}/${encodeURIComponent(e.guid)}/flags`,
{method:'POST',body:JSON.stringify({read:true})}).then(()=>loadFeeds(true)).catch(()=>{});
}
function play(e){
const enc=e.enclosures.find(x=>x.path);
@@ -512,10 +525,7 @@ function play(e){
$('#pfeed').textContent=f?(f.title||f.id):'';
$('#player').classList.add('on');
mediaSession(e,f);
// Playing it is the clearest signal it has been listened to; retention deletes
// read episodes before unread ones.
if(!e.read){ e.read=true; api(`/api/entries/${encodeURIComponent(e.feed_id)}/${encodeURIComponent(e.guid)}/flags`,
{method:'POST',body:JSON.stringify({read:true})}).then(()=>loadFeeds(true)); }
player.marked=false;
}
audio.play().catch(err=>toast('Playback failed: '+err.message,true));
renderEntries();
@@ -537,6 +547,7 @@ audio.addEventListener('timeupdate',()=>{
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(); }
if(d && audio.currentTime/d >= 0.9) markPlayed();
});
function savePos(){
if(!player.guid) return;
@@ -547,7 +558,7 @@ function savePos(){
new Blob([JSON.stringify({secs:Math.floor(audio.currentTime)})],{type:'application/json'}));
}
audio.addEventListener('pause',savePos);
audio.addEventListener('ended',()=>{savePos();$('#pplay').textContent='▶'});
audio.addEventListener('ended',()=>{savePos();markPlayed();$('#pplay').textContent='▶'});
audio.addEventListener('play',()=>$('#pplay').textContent='❚❚');
audio.addEventListener('pause',()=>$('#pplay').textContent='▶');
window.addEventListener('beforeunload',savePos);