aboutsummaryrefslogtreecommitdiffstats
path: root/src/features
diff options
context:
space:
mode:
Diffstat (limited to 'src/features')
-rw-r--r--src/features/messages.js25
1 files changed, 25 insertions, 0 deletions
diff --git a/src/features/messages.js b/src/features/messages.js
index 3bd9ac2..801ad28 100644
--- a/src/features/messages.js
+++ b/src/features/messages.js
@@ -5545,6 +5545,7 @@ export function messagesFeature(ctx) {
// moov-first file — with a short cap, since it gates the row. Remembered
// on disk so a clip seen once is never probed again.
const VIDEO_RE = /\.(mp4|webm|mov|m4v)(\?[^\s]*)?$/i;
+ const AUDIO_RE = /\.(mp3|m4a|aac|ogg|oga|opus|wav|flac)(\?[^\s]*)?$/i;
const VIDEO_PROBE_MS = 3000, VIDEO_DIMS_KEEP = 400, VIDEO_DIMS_KEY = 'coinos-video-dims';
const videoDims = new Map(); // url -> { width, height }
const videoUnknown = new Set(); // probed this session and no answer: paint 16:9, don't ask again
@@ -6887,6 +6888,7 @@ export function messagesFeature(ctx) {
if (VIDEO_RE.test(url)) {
return videoNode(url, { stable: !!feedPaint });
}
+ if (AUDIO_RE.test(url)) return audioNode(url, label);
if (isImage || /\.(png|jpe?g|gif|webp|avif)(\?[^\s]*)?$/i.test(url)) {
const size = feedPaint ? feedMedia(url) : null;
if (feedPaint && !size) return h('a', { href: url, target: '_blank', rel: 'noopener noreferrer' }, label || url);
@@ -6918,6 +6920,29 @@ export function messagesFeature(ctx) {
return h('a', { href: url, target: '_blank', rel: 'noopener noreferrer' }, shown);
}
+ // An audio file (a podcast episode on Blossom, a voice note) as a player,
+ // the way Amethyst shows one. Nothing is fetched until play; once playing
+ // the box is the listener's and no repaint touches it.
+ function audioNode(url, label) {
+ let name = label;
+ if (!name) {
+ try {
+ const u = new URL(url);
+ const file = decodeURIComponent(u.pathname.split('/').pop() || '');
+ // a Blossom name is the file's hash: the host says more than that does
+ name = /^[0-9a-f]{64}\./i.test(file) ? u.hostname : file || u.hostname;
+ } catch { name = url; }
+ }
+ return h('div', { class: 'note-audio', onClick: (e) => e.stopPropagation() },
+ h('div', { class: 'note-audio-head' },
+ h('span', { class: 'note-audio-ico', 'aria-hidden': 'true' }, '\u{1F3A7}'),
+ h('a', { href: url, target: '_blank', rel: 'noopener noreferrer', class: 'small muted note-audio-name' }, name)),
+ h('audio', {
+ controls: true, preload: 'none', src: url,
+ onPlay: (e) => { const box = e.target.closest('.note-audio'); if (box) box._skipMorph = true; },
+ }));
+ }
+
// ---- encrypted attachments (NIP-92 imeta + NIP-17 file fields) ----
// A picture in a Concord channel never reaches the media server in the
// clear: the sender encrypts it under a one-off key, uploads the blob to