iOS: episodes kept on the phone play with no connection (#425)
A WKWebView runs no service worker, so the page's Keep button (#412) was not shown in the app and everything played from the server. Now the page asks the app to keep the file (keep, unkeep, clearKept), and the app answers with what it holds (kept, keepFailed) whether or not anything plays. The app fetches with a background URLSession, so a download goes on with the app closed, into Application Support (not backed up; the files can be fetched again), and Playback plays from the file when there is one. The DeadAir copy is kept under its own path, as the browser keeps it. Places and finishes reached offline wait in an outbox, newest per address, and go when the network is back. Checked in the simulator against a local server: kept /media/42 and /media/42/deadair, then with the server refusing /media, a fresh launch played each from the phone and asked the server for nothing. The page itself still needs a connection to open (#435). Deploy the app (build 6) before the page: a new page in an old app shows a Keep button nothing answers. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- In the iOS app, Keep on this device keeps the episode on the phone, where the app's own player plays it with no connection; until now the app had no Keep button (#425).
|
||||
- An episode kept on this device says so in its row of the list, On this device, not only in its files (#413).
|
||||
- Keep an episode on this device to listen offline: the button beside Play on a downloaded episode keeps the file, without its dead air if DeadAir shortens it for you, and it then plays from the device whether you are online or not, with your place sent once the connection is back. Press it again to take it off; signing out takes everything off (#412).
|
||||
- iPX opens and reads without a connection: the lists it has fetched (All Subscriptions, Unread and the feeds it preloads or you opened) are kept on the device with the pictures in their unread items, up to 200 MB of them, and items read or flagged and places in an episode while offline are sent when the connection is back. Signing out clears it all (#389).
|
||||
|
||||
@@ -141,6 +141,8 @@ function installNativePlayback(){
|
||||
(window as any).ipxNative = {
|
||||
version: 1,
|
||||
on(m: any){
|
||||
// Not playback's, so not gated on it: the app says what it keeps whether or not it plays.
|
||||
if(m.t==='kept'||m.t==='keepFailed'){ hostKept(m); return; }
|
||||
if(!N.on) return;
|
||||
switch(m.t){
|
||||
case 'time':
|
||||
|
||||
@@ -58,15 +58,26 @@ function syncKeepButtons(){
|
||||
for(const e of S.entries)
|
||||
if(e.enclosures.some(isKept)!==!!$(`#eps .ep[data-key="${CSS.escape(entryKey(e))}"] .kept`)) swapRow(e);
|
||||
}
|
||||
/// The button for a file on the server that plays; none without a worker to play it offline.
|
||||
/// The button for a file on the server that plays; none without a worker, or an app, to play it
|
||||
/// offline.
|
||||
function keepBtn(x: Enclosure){
|
||||
if(!worker) return '';
|
||||
if(!worker&&!ipxHost) return '';
|
||||
const k=isKept(x), label=k?'Kept on this device to play offline. Remove it':'Keep on this device to play offline';
|
||||
return `<button class="btn ico" data-a="offline" data-enc="${x.id}" data-url="${keepUrl(x)}" aria-pressed="${k}" aria-busy="${keeping.has(keepUrl(x))}" title="${label}" aria-label="${label}">${k?ICON.kept:ICON.keep}</button>`;
|
||||
}
|
||||
async function toggleKept(x: Enclosure, name: string){
|
||||
const url=keepUrl(x);
|
||||
if(keeping.has(url)) return;
|
||||
// In the iOS app the phone keeps the file and its player plays it (#425): a WebView runs no
|
||||
// service worker, and the app's player could not read the page's cache if it did. The app
|
||||
// answers with what it holds, hostKept below.
|
||||
if(ipxHost){
|
||||
if(kept.has(url)){ ipxHost({t:'unkeep',url}); toast('Removed from this device: '+name); return; }
|
||||
keeping.add(url); syncKeepButtons();
|
||||
toast('Keeping on this device: '+name);
|
||||
ipxHost({t:'keep',url});
|
||||
return;
|
||||
}
|
||||
const c=await caches.open(KEPT);
|
||||
if(kept.has(url)){
|
||||
await c.delete(url); kept.delete(url); syncKeepButtons();
|
||||
@@ -91,8 +102,26 @@ async function toggleKept(x: Enclosure, name: string){
|
||||
}finally{ keeping.delete(url); syncKeepButtons(); }
|
||||
}
|
||||
|
||||
/// What the app holds and is fetching (#425), told when the page starts and after each change,
|
||||
/// or that one could not be kept. Only a file asked for here says it was kept: the list at start
|
||||
/// is no news.
|
||||
function hostKept(m: {t: string, urls?: string[], keeping?: string[], url?: string, message?: string}){
|
||||
if(m.t==='keepFailed'){
|
||||
keeping.delete(m.url||'');
|
||||
toast(m.message||'Unable to keep it on this device. Try again.',true);
|
||||
syncKeepButtons();
|
||||
return;
|
||||
}
|
||||
const asked=new Set(keeping);
|
||||
kept.clear(); for(const u of m.urls||[]) kept.add(u);
|
||||
keeping.clear(); for(const u of m.keeping||[]) keeping.add(u);
|
||||
for(const u of asked) if(kept.has(u)) toast('Kept on this device, to play offline');
|
||||
syncKeepButtons();
|
||||
}
|
||||
|
||||
/// Everything kept for offline, gone: at sign-out, and when another account signs in here.
|
||||
async function clearOffline(){
|
||||
ipxHost?.({t:'clearKept'});
|
||||
try{ for(const k of await caches.keys()) if(k.startsWith('ipx-')) await caches.delete(k); }catch{}
|
||||
kept.clear();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user