aboutsummaryrefslogtreecommitdiffstats
path: root/tools/media-upload-test.js
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-30 10:10:10 -0700
committerAdam Soltys <[email protected]>2026-09-30 10:10:10 -0700
commit505cd84813a80e0a69b2cc95aa93c68cf2856cb2 (patch)
tree3453513920b23476e01efe508e73ba8651f438da /tools/media-upload-test.js
parent93c07bec934e8e8d847349b7792f4cc9089adbb7 (diff)
downloadcoinosv3-505cd84813a80e0a69b2cc95aa93c68cf2856cb2.tar.gz
coinosv3-505cd84813a80e0a69b2cc95aa93c68cf2856cb2.zip
Videos are shrunk on the phone before upload; nostr.build is the first media host.
No public Blossom host transcodes (nostr.build's and primal's BUD-05 /media return the same bitrate), so a 15-20 Mbps phone clip stalled on playback. src/video-compress.js re-encodes on the device with WebCodecs via Mediabunny (a lazily loaded chunk, left out of the SW precache and the standalone file): H.264, short edge 720, ~2.5 Mbps, moov first, audio passed through, rotation baked in. Anything short of a clear win uploads the original. A toast counts the shrinking down. Media hosts: blossom.nostr.build first, nostr.download second (it was unreachable for hours); a HEAD probe skips a host that doesn't answer within 5 s instead of waiting out the whole upload timeout. video-compress-test: 1080p 20 Mbps 5 s (landscape and rotated portrait) → 1.6 MB H.264 720p 2.49 Mbps with audio, moov first, in 2-3 s. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp
Diffstat (limited to 'tools/media-upload-test.js')
-rw-r--r--tools/media-upload-test.js18
1 files changed, 18 insertions, 0 deletions
diff --git a/tools/media-upload-test.js b/tools/media-upload-test.js
index f9cc56a..e19cac7 100644
--- a/tools/media-upload-test.js
+++ b/tools/media-upload-test.js
@@ -10,6 +10,7 @@ let signed = null;
const sign = async (event) => (signed = { ...event, id: '1'.repeat(64), pubkey: '2'.repeat(64), sig: '3'.repeat(128) });
const calls = [];
const fetcher = async (url, init) => {
+ if (init?.method === 'HEAD') return new Response('', { status: 200 }); // the reachability probe
calls.push({ url, init });
if (url.startsWith('https://first')) return new Response(JSON.stringify({ message: 'temporarily unavailable' }), { status: 503 });
return new Response(JSON.stringify({ url: 'https://cdn.example/clip.mov' }), { status: 201, headers: { 'content-type': 'application/json' } });
@@ -23,6 +24,23 @@ check('hashes the file and binds the hash into Blossom auth', calls[1].init.head
&& signed.tags.some((tag) => tag[0] === 'x' && tag[1] === calls[1].init.headers['x-sha-256']));
check('sends signed Blossom authorization', calls[1].init.headers.authorization.startsWith('Nostr '));
+// a host that never answers is skipped after the probe, not waited on
+{
+ const puts = [];
+ const t0 = Date.now();
+ const u = await uploadPublicMedia(file, sign, {
+ servers: ['https://dead.example', 'https://alive.example'], timeoutMs: 60_000,
+ fetcher: async (url, init) => {
+ if (url.startsWith('https://dead')) return new Promise((_, reject) => init.signal.addEventListener('abort', () => reject(new Error('timeout'))));
+ if (init.method === 'HEAD') return new Response('', { status: 404 });
+ puts.push(url);
+ return new Response(JSON.stringify({ url: 'https://alive.example/x.mov' }), { status: 201 });
+ },
+ });
+ check('an unreachable host is skipped within seconds', u === 'https://alive.example/x.mov' && Date.now() - t0 < 7000 && puts.length === 1);
+}
+check('nostr.build is the first host', (await import('../src/media-upload.js')).PUBLIC_MEDIA_SERVERS[0] === 'https://blossom.nostr.build');
+
const bareHashUrl = await uploadPublicMedia(file, sign, {
servers: ['https://media.example'], timeoutMs: 1000,
fetcher: async () => new Response(JSON.stringify({ url: '/' + 'a'.repeat(64) }), { status: 201 }),