PHP Classes

File: docs/proposals/MessageMediaPlayer.md

Recommend this page to a friend!
  Packages of Matthew Asham   Binkterm PHP   docs/proposals/MessageMediaPlayer.md   Download  
File: docs/proposals/MessageMediaPlayer.md
Role: Auxiliary data
Content type: text/markdown
Description: Auxiliary data
Class: Binkterm PHP
Bulletin board system based on the Web
Author: By
Last change:
Date: 5 days ago
Size: 12,540 bytes
 

Contents

Class file image Download

Message Media Player

> Draft Notice: This proposal is a draft generated by AI and may not have been reviewed for accuracy. Details are subject to change.

Overview

When a message (echomail or netmail) contains a URL to a known media platform or a direct link to a media file, BinkTermPHP detects it and renders an inline player rather than displaying a bare hyperlink. Platform links use each provider's native embed mechanism; raw media file links are played through a media player element.

The feature is also active in the file area: when a URL-type file's link points to a supported media platform, an embed is shown inline in the file detail modal.

Implementation Phases

| Phase | Description | Status | |---|---|---| | 1 | User Setting Evolution | ? Complete (branch: mediaview) | | 2 | Server-Side Provider Infrastructure | ? Complete (branch: mediaview) | | 3 | Front-End Integration | ? Complete (branch: mediaview) | | 4 | oEmbed Providers | ? Complete (branch: mediaview) | | 5 | Admin Settings | ? Complete (branch: mediaview) | | 6 | BinkTermPHP Custom Player | ? Complete (branch: mediaview) |

Phase 1 ? User Setting Evolution ?

Evolve the existing image_load_mode user setting into a broader Inline media rendering setting that gates both markdown inline images (existing behavior) and all new media embeds (new behavior).

Setting values:

| Value | Label | Behavior | |---|---|---| | auto (new default) | Automatically render rich media | Markdown images and media embeds inject automatically | | click | Click to expand | Images show a click-to-load placeholder; media embeds show a "? Load player" button |

Changes: - Rename DB column user_settings.image_load_mode ? media_render_mode via migration; change column default from click to auto; backfill all existing rows to auto (existing users cannot be distinguished from those who explicitly chose click, and the new default is the desired experience for everyone) - Rename i18n keys ui.settings.image_load_mode.* ? ui.settings.media_render_mode.* and update strings across all locales (en, es, fr, de, it) - Update the app.js MutationObserver guard and all other code references to the old key - Update settings UI label and help text in the web settings panel and term-side telnet/src/SettingsHandler.php

Files:

| Path | Change | |---|---| | database/migrations/vYYYYMMDDHHMMSS_rename_image_load_mode.sql | Rename column, change default | | config/i18n/*/common.php | Rename keys, update strings (all locales) | | public_html/js/app.js | Update setting key references | | src/MessageHandler.php | Update setting key references | | telnet/src/SettingsHandler.php | Update setting key and label | | routes/api-routes.php | Update setting key in user settings endpoints | | templates/ | Update settings UI label and help text |

Phase 2 ? Server-Side Provider Infrastructure ?

Build the backend class hierarchy and API endpoint that Phase 3 uses for deterministic platforms and that Phase 4 extends for oEmbed providers.

Class structure:

src/Media/
  EmbedProviderInterface.php
  MediaLinkResolver.php
  EmbedProviders/
    YouTubeProvider.php
    OdyseeProvider.php
    RumbleProvider.php
    BitChuteProvider.php
    BrighteonProvider.php
    GenericRawMediaProvider.php

EmbedProviderInterface:

interface EmbedProviderInterface
{
    public function matches(string $url): bool;
    public function getEmbedHtml(string $url): string;
}

MediaLinkResolver iterates registered providers in order and returns the first match, classifying URLs as platform, raw_media, or unknown.

API endpoint:

GET /api/media/embed?url={encoded_url}

Returns:

{
  "type": "platform|raw_media|unknown",
  "provider": "youtube",
  "embed_html": "<iframe ...></iframe>"
}

Files:

| Path | Change | |---|---| | src/Media/EmbedProviderInterface.php | New | | src/Media/MediaLinkResolver.php | New | | src/Media/EmbedProviders/*.php | New (deterministic providers) | | routes/api-routes.php | Add GET /api/media/embed endpoint |

Phase 3 ? Front-End Integration ?

public_html/js/media-player.js exports window.BinkMediaPlayer, which scans anchor tags already produced by linkifyUrls() and injects embeds inline. Platform URL matching is done client-side for the deterministic providers; the backend API from Phase 2 is available but not required for this phase.

Platform registry (client-side, deterministic):

| Platform | URL Pattern | Embed URL | |---|---|---| | YouTube | youtube.com/watch?v=ID, youtu.be/ID | https://www.youtube.com/embed/{ID} | | Odysee | odysee.com/@{channel}/{slug} | https://odysee.com/$/embed/@{channel}/{slug} | | Rumble | rumble.com/v{hash}- | https://rumble.com/embed/v{hash}/ | | BitChute | bitchute.com/video/{ID} | https://www.bitchute.com/embed/{ID}/ | | Brighteon | brighteon.com/{ID} | https://www.brighteon.com/embed/{ID} |

Raw media extensions:

| Type | Extensions | Element | |---|---|---| | Video | mp4, webm, ogv, mov | <video controls> | | Audio | mp3, flac, ogg, opus, wav, m4a, aac | <audio controls> | | Image | png, webp, gif, jpg, jpeg, svg | <img referrerpolicy="no-referrer" loading="lazy"> |

Video and audio render as styled native elements themed via CSS custom properties. See Phase 6 for the Plyr-based custom player.

Images render as <img> elements with referrerpolicy="no-referrer" and loading="lazy". SVG files are intentionally loaded via <img src> rather than <object> or inline SVG ? when served this way the browser does not execute SVG scripts and the image cannot access the parent DOM, providing the required sandboxing. All images are subject to the same media_render_mode setting as other embeds.

Exported API:

window.BinkMediaPlayer = {
    scan(container),    // scan all <a> tags inside a DOM node or selector
    cleanup(container)  // remove injected embeds before re-render
};

scan() checks window.userSettings.media_render_mode: in auto mode it injects embeds immediately; in click mode it injects a "? Load player" button instead.

Integration points:

| Location | Container | Hook | |---|---|---| | Echomail | #messageTextContainer | End of renderCurrentMessageBody() in echomail.js | | Netmail | #messageBodyContainer | End of renderCurrentMessageBody() in netmail.js | | File detail modal | #detailUrl | After URL anchor is set in showFileDetails() in files.twig |

Files:

| Path | Change | |---|---| | public_html/js/media-player.js | New | | public_html/js/echomail.js | Add BinkMediaPlayer.scan() call | | public_html/js/netmail.js | Add BinkMediaPlayer.scan() call | | templates/files.twig | Add scan call in showFileDetails() | | templates/shells/web/base.twig | Include media-player.js | | templates/base.twig | Include media-player.js | | public_html/css/style.css | Add .bink-media-embed styles | | public_html/css/amber.css | Theme overrides | | public_html/css/dark.css | Theme overrides | | public_html/css/greenterm.css | Theme overrides | | public_html/css/cyberpunk.css | Theme overrides | | public_html/sw.js | Add to staticAssets, bump CACHE_NAME |

Phase 4 ? oEmbed Providers ?

Extend media-player.js with client-side oEmbed resolution. The browser fetches each provider's oEmbed endpoint directly; resolved responses are cached in a Map keyed by URL so repeated views of the same message make zero additional network requests. For providers whose oEmbed endpoints do not support CORS, the client silently retries through the server-side /api/media/embed proxy endpoint from Phase 2.

Additional providers:

| Platform | URL Pattern | Embed Method | CORS | |---|---|---|---| | SoundCloud | soundcloud.com/ | soundcloud.com/oembed | Yes | | Twitter/X | twitter.com/, x.com/ | publish.twitter.com/oembed | Yes | | TikTok | tiktok.com/@{user}/video/{id} | tiktok.com/oembed | Yes | | Minds | minds.com/newsfeed/{id} | Minds oEmbed API | Uncertain ? falls back to server proxy | | Bastyon | bastyon.com/ | Direct iframe (no oEmbed) | N/A | | ReverbNation | reverbnation.com/ | ReverbNation oEmbed | Uncertain ? falls back to server proxy |

Resolution flow:

async function resolveOembed(url, oembedEndpoint) {
    if (oembedCache.has(url)) return oembedCache.get(url);
    const encoded = encodeURIComponent(url);
    let html;
    try {
        const res = await fetch(`${oembedEndpoint}?url=${encoded}&format=json`);
        html = (await res.json()).html;
    } catch {
        // CORS blocked ? fall back to server proxy
        const res = await fetch(`/api/media/embed?url=${encoded}`);
        html = (await res.json()).embed_html;
    }
    oembedCache.set(url, html);
    return html;
}

Files:

| Path | Change | |---|---| | public_html/js/media-player.js | Add oEmbed provider registry and resolveOembed() with in-memory cache | | routes/api-routes.php | /api/media/embed retained as CORS fallback proxy only |

Phase 5 ? Admin Settings ?

A new Appearance ? Media Player section in the BBS admin panel.

Controls: - Enable/disable inline media embedding globally - Per-platform provider toggles - API key fields for providers that require them (global keys stored under config/bbs.json ? media_player.api_keys; per-user keys only if a specific provider demands them) - Maximum embed width (default: 100% of message column) - Allow/disallow raw media autoplay

Files:

| Path | Change | |---|---| | config/bbs.json / config/bbs.json.example | New media_player config block | | templates/admin/bbs_settings.twig | Media Player admin section | | routes/admin-routes.php | Save/load media player settings |

Phase 6 ? BinkTermPHP Custom Player ?

Replace the Phase 3 native HTML5 elements with a branded player based on a fork of Plyr (MIT). Plyr was chosen for its minimal UI chrome, small footprint (~26 KB gzipped), and CSS-custom-property theming model. Its built-in YouTube and Vimeo iframe support could eventually replace bare iframes for those platforms and give users consistent controls. For HLS/DASH stream support, hls.js can be wired in as a Plyr tech plugin if needed.

Planned future features: playlist support, download toggle, in-player chapter markers from message kludge lines, BBS credit integration for gated media.

Files:

| Path | Change | |---|---| | public_html/js/binkplayer/ | New (Plyr fork) | | public_html/css/binkplayer/ | New | | public_html/js/media-player.js | Wire raw media links to BinkPlayer instead of native elements |

Parity Considerations

  • This feature is visual-only and applies to the web interface. The terminal (telnet/SSH) interface is unaffected; URLs display as plain text there.
  • The feature applies to both echomail and netmail message bodies equally.
  • File area integration (Phase 3) scans only the `#detailUrl` anchor for URL-type files. Long descriptions are plain text and are not linkified.

Privacy and Security Considerations

  • Phase 3 platform iframes are sandboxed: `sandbox="allow-scripts allow-same-origin allow-presentation"`.
  • Raw media URLs are passed directly to the browser; the server does not proxy the stream. The external server will see the user's IP when media loads.
  • Phase 4 oEmbed fetches happen client-side. Since the embed itself loads third-party content that exposes the user's IP regardless, the oEmbed API call being client-side does not meaningfully change the privacy posture. Providers that block CORS are fetched via the server proxy.
  • CSP `frame-src` must be updated to include each enabled platform's embed domain.

Resolved Questions

  1. Default behavior ? On by default (`auto`); users can switch to click-to-load via the Inline media rendering setting.
  2. API keys ? Global, stored in `config/bbs.json` under `media_player.api_keys`. A per-provider key field is shown in the admin panel for each oEmbed provider that requires one. Per-user keys are not supported unless a future provider specifically demands them.
  3. Raw media proxying ? No proxy. Raw media URLs are passed directly to the browser's player element.