Download a verified file from another device over WebRTC
This commit is contained in:
@@ -1786,10 +1786,11 @@ const Player = {
|
||||
btn.className = 'retry-btn';
|
||||
btn.textContent = '↻ Retry';
|
||||
btn.addEventListener('click', () => {
|
||||
btn.remove();
|
||||
els.playerPane.querySelectorAll('.retry-btn').forEach((b) => b.remove());
|
||||
Player.loadVideo(videoObj, { preferStream, resume, reveal, nocache });
|
||||
});
|
||||
els.playerPane.appendChild(btn);
|
||||
offerPeerDownload(videoObj);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -2465,6 +2466,65 @@ async function renderAvailability(id) {
|
||||
el.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// Fetch a verified copy from another device (docs/p2p-architecture.md flow 6).
|
||||
// Download-then-play: on success the file is saved offline like any save.
|
||||
async function getFromPeers(videoObj, c, peers, onProgress) {
|
||||
const id = videoObj.id;
|
||||
downloading.add(id);
|
||||
markCardCacheState(id, 'downloading');
|
||||
updateDownloadBadge();
|
||||
try {
|
||||
const res = await window.P2PTransfer.download({ videoId: id, cid: c.cid, size: c.size, peers, onProgress });
|
||||
if (res.ok) {
|
||||
cachedIds.add(id);
|
||||
cacheMutations++;
|
||||
recordDeviceFile(id, { sha256: res.sha256, expectedSha: c.cid, size: res.size });
|
||||
toast(`Saved "${videoObj.title || id}" from another device ✓`);
|
||||
}
|
||||
return res;
|
||||
} finally {
|
||||
downloading.delete(id);
|
||||
markCardCacheState(id, cachedIds.has(id) ? 'cached' : 'none');
|
||||
updateDownloadBadge();
|
||||
renderSidebar();
|
||||
}
|
||||
}
|
||||
|
||||
// Offered when a video won't load: only if some device holding it is online now.
|
||||
async function offerPeerDownload(videoObj) {
|
||||
if (!WEB || !window.P2PTransfer || !window.P2PCore || data.settings.p2pReceive === false) return;
|
||||
const id = videoObj && videoObj.id;
|
||||
if (!id || videoObj.custom || cachedIds.has(id)) return;
|
||||
let payload = null;
|
||||
try {
|
||||
const r = await fetch(`/api/p2p/holders?v=${encodeURIComponent(id)}`);
|
||||
payload = r.ok ? await r.json() : null;
|
||||
} catch { return; }
|
||||
const c = payload && Array.isArray(payload.cids) ? payload.cids[0] : null;
|
||||
const peers = c ? window.P2PCore.pickPeers(payload, c.cid) : [];
|
||||
if (!peers.length || !els.playerPane.querySelector('.retry-btn')) return; // nobody online / user moved on
|
||||
const old = els.playerPane.querySelector('.peer-btn');
|
||||
if (old) old.remove();
|
||||
const btn = document.createElement('button');
|
||||
btn.className = 'retry-btn peer-btn';
|
||||
btn.textContent = `📡 Get it from a device (${peers.length} online)`;
|
||||
btn.addEventListener('click', async () => {
|
||||
btn.disabled = true;
|
||||
const res = await getFromPeers(videoObj, c, peers, (got, total) => {
|
||||
btn.textContent = `📡 ${Math.min(99, Math.round((got / total) * 100))}%…`;
|
||||
});
|
||||
if (res.ok) {
|
||||
els.playerPane.querySelectorAll('.retry-btn').forEach((b) => b.remove());
|
||||
Player.loadVideo(videoObj);
|
||||
} else {
|
||||
btn.disabled = false;
|
||||
btn.textContent = '📡 Try again';
|
||||
toast('⚠ ' + res.error);
|
||||
}
|
||||
});
|
||||
els.playerPane.appendChild(btn);
|
||||
}
|
||||
|
||||
// Reflect cache state on the now-playing Save button.
|
||||
function updateNowPlayingActions() {
|
||||
if (!current || !current.meta) return;
|
||||
@@ -9981,6 +10041,7 @@ async function boot() {
|
||||
// Peer-to-peer: report what this device holds (on by default; settings.p2pShare).
|
||||
if (WEB && window.P2PClient) {
|
||||
window.P2PClient.start({ getSettings: () => data.settings, getProfile: () => (data.profile && data.profile.name) || '' });
|
||||
if (window.P2PTransfer) window.P2PTransfer.start({ canShare: () => data.settings.p2pShare !== false });
|
||||
}
|
||||
data.playlists.forEach(preloadPlaylist);
|
||||
preloadPinnedPlaylists();
|
||||
|
||||
Reference in New Issue
Block a user