v4: an audio file's waveform is made once by the server, and the player seeks on one accessible scrubber — every player downloaded the whole file a second time and decoded all of it in the browser to draw 150 bars, so a long FLAC cost tens of megabytes of memory per page, and the waveform could only be clicked: no drag, no keyboard, no slider for a screen reader. GET /api/dumps/:id/waveform now answers an audio dump's peaks, which lib/ffmpeg.ts decodes with ffmpeg on first request, mono at 8 kHz and streamed, keeps beside the thumbnails as the response itself, and serves with an ETag (a ten-minute MP3 takes about a second, then a few hundred bytes). The peaks are taken by shared/waveform.ts's peaksOf, scaled to the loudest so a quiet recording fills the track, and the dump form's preview of a file not yet uploaded decodes it with the same function, at 8 kHz in an OfflineAudioContext. Scrubber replaces Waveform and SeekBar: a role="slider" that seeks where a pointer lets go, follows a drag (the clock shows where it is held), takes arrows, Home and End, and draws the bars as one path with the played part clipped by a nested svg, so it moves smoothly and a timeupdate redraws two attributes instead of 150 rects; audio without a waveform, and video, get a plain bar. MediaPlayer reads whether it plays from the element's events alone, so a click no longer reports the change twice, its handles for PlayerProvider are stable callbacks instead of refs to refs, the drag no longer reattaches every listener, and the <source> children that src made dead are gone, with the mime prop. The file, thumbnail and waveform routes resolve their dump, token and caching through lib/dump-media.ts, and what is generated from an upload is named and removed in lib/ffmpeg.ts, the custom thumbnail by dump-service's customThumbnailPath, instead of paths spelled out in each. The test setup's play() and pause() fire their events as a browser does. 277 backend tests and 684 frontend tests pass, and deno check, tsc, eslint, doc:lint and i18n:compile --strict are clean; checked in the app with a real FLAC on its dump page and in the global player at 1280 and 390px, seeking by keyboard.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -669,7 +669,7 @@ msgid "Done"
|
||||
msgstr "Done"
|
||||
|
||||
#. placeholder {0}: dump.fileName
|
||||
#: src/components/dump/FilePreview.tsx:215
|
||||
#: src/components/dump/FilePreview.tsx:226
|
||||
msgid "Download {0}"
|
||||
msgstr "Download {0}"
|
||||
|
||||
@@ -911,7 +911,7 @@ msgstr "From people you follow"
|
||||
msgid "From playlists"
|
||||
msgstr "From playlists"
|
||||
|
||||
#: src/components/player/MediaPlayer.tsx:373
|
||||
#: src/components/player/MediaPlayer.tsx:326
|
||||
msgid "Fullscreen"
|
||||
msgstr "Fullscreen"
|
||||
|
||||
@@ -1127,7 +1127,7 @@ msgstr "Moderator"
|
||||
msgid "digest.monthly"
|
||||
msgstr "Monthly recap"
|
||||
|
||||
#: src/components/player/MediaPlayer.tsx:346
|
||||
#: src/components/player/MediaPlayer.tsx:299
|
||||
msgid "Mute"
|
||||
msgstr "Mute"
|
||||
|
||||
@@ -1335,10 +1335,10 @@ msgstr "Passwords do not match"
|
||||
msgid "Paste a link…"
|
||||
msgstr "Paste a link…"
|
||||
|
||||
#: src/components/dump/FilePreview.tsx:65
|
||||
#: src/components/dump/FilePreview.tsx:177
|
||||
#: src/components/dump/FilePreview.tsx:70
|
||||
#: src/components/dump/FilePreview.tsx:184
|
||||
#: src/components/dump/RichContentCard.tsx:103
|
||||
#: src/components/player/MediaPlayer.tsx:330
|
||||
#: src/components/player/MediaPlayer.tsx:276
|
||||
msgid "Pause"
|
||||
msgstr "Pause"
|
||||
|
||||
@@ -1346,11 +1346,11 @@ msgstr "Pause"
|
||||
msgid "Picked up where you left off."
|
||||
msgstr "Picked up where you left off."
|
||||
|
||||
#: src/components/dump/FilePreview.tsx:65
|
||||
#: src/components/dump/FilePreview.tsx:177
|
||||
#: src/components/dump/FilePreview.tsx:70
|
||||
#: src/components/dump/FilePreview.tsx:184
|
||||
#: src/components/dump/RichContentCard.tsx:103
|
||||
#: src/components/dump/RichContentCard.tsx:150
|
||||
#: src/components/player/MediaPlayer.tsx:330
|
||||
#: src/components/player/MediaPlayer.tsx:276
|
||||
msgid "Play"
|
||||
msgstr "Play"
|
||||
|
||||
@@ -1571,7 +1571,7 @@ msgstr "Searching…"
|
||||
msgid "email.digest.communityPage"
|
||||
msgstr "See it all on the community page"
|
||||
|
||||
#: src/components/player/SeekBar.tsx:50
|
||||
#: src/components/player/Scrubber.tsx:101
|
||||
msgid "Seek"
|
||||
msgstr "Seek"
|
||||
|
||||
@@ -1809,7 +1809,7 @@ msgstr "Unfollow {targetUsername}"
|
||||
msgid "Unfollow playlist"
|
||||
msgstr "Unfollow playlist"
|
||||
|
||||
#: src/components/player/MediaPlayer.tsx:346
|
||||
#: src/components/player/MediaPlayer.tsx:299
|
||||
msgid "Unmute"
|
||||
msgstr "Unmute"
|
||||
|
||||
@@ -1902,7 +1902,7 @@ msgstr "View all →"
|
||||
msgid "View dump →"
|
||||
msgstr "View dump →"
|
||||
|
||||
#: src/components/player/MediaPlayer.tsx:363
|
||||
#: src/components/player/MediaPlayer.tsx:316
|
||||
msgid "Volume"
|
||||
msgstr "Volume"
|
||||
|
||||
|
||||
@@ -669,7 +669,7 @@ msgid "Done"
|
||||
msgstr "Terminé"
|
||||
|
||||
#. placeholder {0}: dump.fileName
|
||||
#: src/components/dump/FilePreview.tsx:215
|
||||
#: src/components/dump/FilePreview.tsx:226
|
||||
msgid "Download {0}"
|
||||
msgstr "Télécharger {0}"
|
||||
|
||||
@@ -911,7 +911,7 @@ msgstr "De ceux que vous suivez"
|
||||
msgid "From playlists"
|
||||
msgstr "De collections"
|
||||
|
||||
#: src/components/player/MediaPlayer.tsx:373
|
||||
#: src/components/player/MediaPlayer.tsx:326
|
||||
msgid "Fullscreen"
|
||||
msgstr "Plein écran"
|
||||
|
||||
@@ -1127,7 +1127,7 @@ msgstr "Modérateur"
|
||||
msgid "digest.monthly"
|
||||
msgstr "Récap mensuel"
|
||||
|
||||
#: src/components/player/MediaPlayer.tsx:346
|
||||
#: src/components/player/MediaPlayer.tsx:299
|
||||
msgid "Mute"
|
||||
msgstr "Couper le son"
|
||||
|
||||
@@ -1335,10 +1335,10 @@ msgstr "Les mots de passe ne correspondent pas"
|
||||
msgid "Paste a link…"
|
||||
msgstr "Collez un lien…"
|
||||
|
||||
#: src/components/dump/FilePreview.tsx:65
|
||||
#: src/components/dump/FilePreview.tsx:177
|
||||
#: src/components/dump/FilePreview.tsx:70
|
||||
#: src/components/dump/FilePreview.tsx:184
|
||||
#: src/components/dump/RichContentCard.tsx:103
|
||||
#: src/components/player/MediaPlayer.tsx:330
|
||||
#: src/components/player/MediaPlayer.tsx:276
|
||||
msgid "Pause"
|
||||
msgstr "Pause"
|
||||
|
||||
@@ -1346,11 +1346,11 @@ msgstr "Pause"
|
||||
msgid "Picked up where you left off."
|
||||
msgstr "Reprise de votre brouillon."
|
||||
|
||||
#: src/components/dump/FilePreview.tsx:65
|
||||
#: src/components/dump/FilePreview.tsx:177
|
||||
#: src/components/dump/FilePreview.tsx:70
|
||||
#: src/components/dump/FilePreview.tsx:184
|
||||
#: src/components/dump/RichContentCard.tsx:103
|
||||
#: src/components/dump/RichContentCard.tsx:150
|
||||
#: src/components/player/MediaPlayer.tsx:330
|
||||
#: src/components/player/MediaPlayer.tsx:276
|
||||
msgid "Play"
|
||||
msgstr "Lecture"
|
||||
|
||||
@@ -1571,7 +1571,7 @@ msgstr "Recherche…"
|
||||
msgid "email.digest.communityPage"
|
||||
msgstr "Tout voir sur la page de la communauté"
|
||||
|
||||
#: src/components/player/SeekBar.tsx:50
|
||||
#: src/components/player/Scrubber.tsx:101
|
||||
msgid "Seek"
|
||||
msgstr "Position"
|
||||
|
||||
@@ -1809,7 +1809,7 @@ msgstr "Ne plus suivre {targetUsername}"
|
||||
msgid "Unfollow playlist"
|
||||
msgstr "Ne plus suivre la collection"
|
||||
|
||||
#: src/components/player/MediaPlayer.tsx:346
|
||||
#: src/components/player/MediaPlayer.tsx:299
|
||||
msgid "Unmute"
|
||||
msgstr "Rétablir le son"
|
||||
|
||||
@@ -1902,7 +1902,7 @@ msgstr "Tout voir →"
|
||||
msgid "View dump →"
|
||||
msgstr "Voir la reco →"
|
||||
|
||||
#: src/components/player/MediaPlayer.tsx:363
|
||||
#: src/components/player/MediaPlayer.tsx:316
|
||||
msgid "Volume"
|
||||
msgstr "Volume"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user