/** * Copyright (c) Double Symmetry GmbH * Commercial use requires a license. See https://rntp.dev/pricing */ import { headersOf, uriOf, type AudioEngine, type EngineCallbacks, type EngineProvider, type ResolvedMediaItem, } from './AudioEngine'; import { HtmlAudioEngine } from './HtmlAudioEngine'; import { ShakaEngine } from './ShakaEngine'; export type StreamKind = 'hls' | 'dash' | null; const HLS_MIME = /application\/(x-mpegurl|vnd\.apple\.mpegurl)/i; const DASH_MIME = /application\/dash\+xml/i; export function sniffStreamKind( item: Pick ): StreamKind { if (item.mimeType) { if (HLS_MIME.test(item.mimeType)) return 'hls'; if (DASH_MIME.test(item.mimeType)) return 'dash'; return null; // explicit mimeType wins over extension sniffing } const path = uriOf(item.url).split(/[?#]/, 1)[0]!.toLowerCase(); if (path.endsWith('.m3u8')) return 'hls'; if (path.endsWith('.mpd')) return 'dash'; return null; } function canPlayHlsNatively(): boolean { const el = document.createElement('audio'); return el.canPlayType('application/vnd.apple.mpegurl') !== ''; } /** * One engine instance per kind, lazily constructed. HLS prefers the native * element where supported (Safari), matching AVFoundation behavior on iOS. * * Each cached engine captures the `callbacks` from its first `engineFor` call * and reuses them; subsequent calls ignore the passed `callbacks`. This is safe * because a single {@link WebTrackPlayer} owns one provider and always passes * the same stable callbacks object — do not share one provider across players. */ export class DefaultEngineProvider implements EngineProvider { private html: HtmlAudioEngine | null = null; private shaka: ShakaEngine | null = null; engineFor(item: ResolvedMediaItem, callbacks: EngineCallbacks): AudioEngine { const kind = sniffStreamKind(item); const needsShaka = kind === 'dash' || (kind === 'hls' && !canPlayHlsNatively()) || headersOf(item.url) != null; if (needsShaka) { if (!this.shaka) this.shaka = new ShakaEngine(callbacks); return this.shaka; } if (!this.html) this.html = new HtmlAudioEngine(callbacks); return this.html; } destroy(): void { this.html?.destroy(); this.shaka?.destroy(); this.html = null; this.shaka = null; } }