From eb34b0470887132e0e16938ecec2f42254320eee Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Fri, 3 Jul 2026 22:44:09 -0400 Subject: [PATCH] feat(audio): play m.file audio messages inline like m.audio Audio frequently arrives as m.file (bridges, other clients, or when the browser reported a non-audio/* mime on upload) and only got a download button. Detect audio in the m.file branch (by info.mimetype or filename extension) and render the existing MAudio inline player, falling back to the file card otherwise. Co-Authored-By: Claude Opus 4.8 --- src/app/components/RenderMessageContent.tsx | 47 ++++++++++++++++++++- 1 file changed, 46 insertions(+), 1 deletion(-) diff --git a/src/app/components/RenderMessageContent.tsx b/src/app/components/RenderMessageContent.tsx index a68ee4733..0abfd3a6e 100644 --- a/src/app/components/RenderMessageContent.tsx +++ b/src/app/components/RenderMessageContent.tsx @@ -31,7 +31,29 @@ import { ImageViewer } from './image-viewer'; import { PdfViewer } from './Pdf-viewer'; import { TextViewer } from './text-viewer'; import { testMatrixTo } from '../plugins/matrix-to'; -import { IImageContent } from '../../types/matrix/common'; +import { IAudioContent, IFileContent, IImageContent } from '../../types/matrix/common'; + +// Audio is frequently sent as m.file (bridges/other clients, or when the browser +// reported a non-audio/* mime on upload). Detect that so we can play it inline +// like m.audio instead of showing only a download button. +const AUDIO_EXT_MIME: Record = { + mp3: 'audio/mpeg', + m4a: 'audio/mp4', + aac: 'audio/aac', + oga: 'audio/ogg', + ogg: 'audio/ogg', + opus: 'audio/ogg', + wav: 'audio/wav', + flac: 'audio/flac', + weba: 'audio/webm', +}; +const resolveInlineAudioMime = (content: IFileContent): string | undefined => { + const mime = content.info?.mimetype; + if (typeof mime === 'string' && mime.startsWith('audio')) return mime; + const name = content.filename ?? content.body ?? ''; + const ext = name.split('.').pop()?.toLowerCase(); + return ext ? AUDIO_EXT_MIME[ext] : undefined; +}; type RenderMessageContentProps = { displayName: string; @@ -276,6 +298,29 @@ export function RenderMessageContent({ } if (msgType === MsgType.File) { + // If an m.file is actually audio, play it inline (like m.audio) instead of + // only offering a download. MAudio falls back to renderFile if playback fails. + const audioMime = resolveInlineAudioMime(getContent()); + if (audioMime) { + const fileContent = getContent(); + const audioContent = { + ...fileContent, + info: { ...(fileContent.info ?? {}), mimetype: audioMime }, + } as unknown as IAudioContent; + return ( + <> + ( + } /> + )} + outlined={outlineAttachment} + /> + {renderCaption()} + + ); + } return renderFile(); }