Changeset a85f539 in Klonkt for src/services/AudioEmbedService.js


Ignore:
Timestamp:
07/16/2026 12:20:51 PM (8 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
e276d03
Parents:
8e7f0ec
git-author:
Robin <roboburr@…> (07/12/2026 10:30:07 PM)
git-committer:
Robin <roboburr@…> (07/16/2026 12:20:51 PM)
Message:

Fix: bare .webm/.mp4/.mp3 URLs render a native player

autoembed() and [[embed:]] now detect direct media-file URLs and emit a
<video>/<audio> element (was: left as a plain link). Sanitizer allows
video/audio/source with a tight attr + http(s)-scheme allowlist so
hand-authored and federated-in players survive. detectProvider() is left
untouched so the timeline/cover callers that switch on provider slugs are
unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@…>

File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/services/AudioEmbedService.js

    r8e7f0ec ra85f539  
    8282
    8383    return null;
     84  }
     85
     86  // Direct media files (video/audio) hosted anywhere → a native <video>/<audio>
     87  // player. Kept OUT of detectProvider() on purpose: the timeline/cover callers
     88  // switch on provider slugs (youtube/spotify/…) and a bare file has none, so
     89  // overloading detectProvider would suppress e.g. a PeerTube fallback. Only
     90  // autoembed() and [[embed:…]] use this.
     91  static MEDIA_FILE_EXT = {
     92    video: ['mp4', 'webm', 'm4v', 'mov', 'ogv'],
     93    audio: ['mp3', 'ogg', 'oga', 'wav', 'm4a', 'flac', 'opus', 'aac'],
     94  };
     95
     96  static detectMediaFile(url) {
     97    if (!url || typeof url !== 'string') return null;
     98    if (!/^https?:\/\//i.test(url)) return null;
     99    let pathname;
     100    try { pathname = new URL(url).pathname.toLowerCase(); } catch { return null; }
     101    const ext = (pathname.match(/\.([a-z0-9]+)$/) || [])[1];
     102    if (!ext) return null;
     103    if (this.MEDIA_FILE_EXT.video.includes(ext)) return { kind: 'video', url };
     104    if (this.MEDIA_FILE_EXT.audio.includes(ext)) return { kind: 'audio', url };
     105    return null;
     106  }
     107
     108  static mediaFileEmbed(url) {
     109    const m = this.detectMediaFile(url);
     110    if (!m) return null;
     111    const src = this.escape(m.url);
     112    if (m.kind === 'video') {
     113      return `<figure class="folio-embed folio-embed--video"><video src="${src}" controls preload="metadata" playsinline></video></figure>`;
     114    }
     115    return `<figure class="folio-embed folio-embed--audio"><audio src="${src}" controls preload="metadata"></audio></figure>`;
    84116  }
    85117
     
    238270          return iframe || match;
    239271        }
     272        // Bare media file (…/clip.webm, …/song.mp3) → native player.
     273        const media = this.mediaFileEmbed(url);
     274        if (media) return media;
    240275        return match;
    241276      }
     
    255290      const detected = this.detectProvider(url);
    256291      if (!detected) {
     292        // Bare media file (…/clip.webm, …/song.mp3) → native player.
     293        const media = this.mediaFileEmbed(url);
     294        if (media) return media;
    257295        return `<div class="post-embed-missing"><em>Embed: niet-ondersteunde of ongeldige URL.</em></div>`;
    258296      }
Note: See TracChangeset for help on using the changeset viewer.