UNPKG

audio.libx.js

Version:

Comprehensive audio library with progressive streaming, recording capabilities, real-time processing, and intelligent caching for web applications

256 lines 9.2 kB
import { MediaSourceError } from './types.js'; export class MediaSourceHelper { constructor() { this._supportedMimeTypes = []; this._isSupported = false; this._hasManagedMediaSource = false; this._initialize(); } static getInstance() { if (!MediaSourceHelper._instance) { MediaSourceHelper._instance = new MediaSourceHelper(); } return MediaSourceHelper._instance; } _initialize() { this._hasManagedMediaSource = 'ManagedMediaSource' in window; const hasStandardMediaSource = 'MediaSource' in window; this._isSupported = this._hasManagedMediaSource || hasStandardMediaSource; if (this._isSupported) { this._detectSupportedMimeTypes(); } } _detectSupportedMimeTypes() { const commonMimeTypes = [ 'audio/mpeg', 'audio/mp3', 'audio/wav', 'audio/webm', 'audio/webm; codecs=opus', 'audio/webm; codecs=vorbis', 'audio/ogg', 'audio/ogg; codecs=opus', 'audio/ogg; codecs=vorbis', 'audio/mp4', 'audio/mp4; codecs=mp4a.40.2', 'audio/aac' ]; this._supportedMimeTypes = commonMimeTypes.filter(mimeType => { try { return MediaSource.isTypeSupported(mimeType); } catch { return false; } }); } createMediaSource() { if (!this._isSupported) { throw new MediaSourceError('MediaSource API is not supported on this device'); } let mediaSource; let isManaged = false; try { if (this._hasManagedMediaSource) { mediaSource = new window.ManagedMediaSource(); isManaged = true; } else { mediaSource = new MediaSource(); isManaged = false; } } catch (error) { throw new MediaSourceError('Failed to create MediaSource instance', undefined, error); } return { mediaSource, isManaged, supportedMimeTypes: [...this._supportedMimeTypes] }; } getBestMimeType(format) { const preferredMimeTypes = this._getPreferredMimeTypesForFormat(format); for (const mimeType of preferredMimeTypes) { if (this._supportedMimeTypes.includes(mimeType)) { return mimeType; } } if (this._supportedMimeTypes.includes('audio/mpeg')) { return 'audio/mpeg'; } if (this._supportedMimeTypes.length > 0) { return this._supportedMimeTypes[0]; } throw new MediaSourceError(`No supported MIME type found for format: ${format.type}`); } _getPreferredMimeTypesForFormat(format) { switch (format.type) { case 'mp3': return ['audio/mpeg', 'audio/mp3']; case 'wav': return ['audio/webm; codecs=opus', 'audio/mpeg']; case 'webm': return [ 'audio/webm; codecs=opus', 'audio/webm; codecs=vorbis', 'audio/webm' ]; case 'ogg': return [ 'audio/ogg; codecs=opus', 'audio/ogg; codecs=vorbis', 'audio/ogg' ]; default: return ['audio/mpeg']; } } detectAudioFormat(data) { const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : data; if (this._isWavFormat(bytes)) { return { type: 'wav', mimeType: 'audio/wav', streamable: false, requiresConversion: true }; } if (this._isMp3Format(bytes)) { return { type: 'mp3', mimeType: 'audio/mpeg', streamable: true }; } if (this._isWebMFormat(bytes)) { return { type: 'webm', mimeType: 'audio/webm', streamable: true, codec: 'opus' }; } if (this._isOggFormat(bytes)) { return { type: 'ogg', mimeType: 'audio/ogg', streamable: true }; } return { type: 'unknown', mimeType: 'audio/mpeg', streamable: false }; } _isWavFormat(bytes) { if (bytes.length < 12) return false; const riff = String.fromCharCode(...bytes.slice(0, 4)); const wave = String.fromCharCode(...bytes.slice(8, 12)); return riff === 'RIFF' && wave === 'WAVE'; } _isMp3Format(bytes) { if (bytes.length < 3) return false; if (bytes[0] === 0x49 && bytes[1] === 0x44 && bytes[2] === 0x33) { return true; } if (bytes.length >= 2 && bytes[0] === 0xFF && (bytes[1] & 0xE0) === 0xE0) { return true; } if (bytes.length >= 128) { const tagStart = bytes.length - 128; if (bytes[tagStart] === 0x54 && bytes[tagStart + 1] === 0x41 && bytes[tagStart + 2] === 0x47) { return true; } } return false; } _isWebMFormat(bytes) { if (bytes.length < 4) return false; return bytes[0] === 0x1A && bytes[1] === 0x45 && bytes[2] === 0xDF && bytes[3] === 0xA3; } _isOggFormat(bytes) { if (bytes.length < 4) return false; const signature = String.fromCharCode(...bytes.slice(0, 4)); return signature === 'OggS'; } async createSourceBuffer(mediaSource, mimeType, retryCount = 3) { let lastError = null; for (let attempt = 0; attempt < retryCount; attempt++) { try { const sourceBuffer = mediaSource.addSourceBuffer(mimeType); return sourceBuffer; } catch (error) { lastError = error; if (attempt < retryCount - 1) { await new Promise(resolve => setTimeout(resolve, 100 * (attempt + 1))); } } } throw new MediaSourceError(`Failed to create SourceBuffer with MIME type: ${mimeType}`, undefined, lastError); } async appendToSourceBuffer(sourceBuffer, data, timeout = 10000) { return new Promise((resolve, reject) => { const timeoutId = setTimeout(() => { reject(new MediaSourceError('SourceBuffer append timeout')); }, timeout); const onUpdateEnd = () => { clearTimeout(timeoutId); sourceBuffer.removeEventListener('updateend', onUpdateEnd); sourceBuffer.removeEventListener('error', onError); resolve(); }; const onError = (event) => { clearTimeout(timeoutId); sourceBuffer.removeEventListener('updateend', onUpdateEnd); sourceBuffer.removeEventListener('error', onError); let errorMessage = 'SourceBuffer append error'; if (event && event.error) { errorMessage += `: ${event.error.message || 'Unknown error'}`; } errorMessage += '. This may be due to unsupported audio format or codec.'; reject(new MediaSourceError(errorMessage, undefined, event)); }; if (sourceBuffer.updating) { sourceBuffer.addEventListener('updateend', () => { this._performAppend(sourceBuffer, data, onUpdateEnd, onError); }, { once: true }); } else { this._performAppend(sourceBuffer, data, onUpdateEnd, onError); } }); } _performAppend(sourceBuffer, data, onUpdateEnd, onError) { sourceBuffer.addEventListener('updateend', onUpdateEnd, { once: true }); sourceBuffer.addEventListener('error', onError, { once: true }); try { const arrayBuffer = data instanceof Uint8Array ? data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength) : data; sourceBuffer.appendBuffer(arrayBuffer); } catch (error) { sourceBuffer.removeEventListener('updateend', onUpdateEnd); sourceBuffer.removeEventListener('error', onError); throw new MediaSourceError('Failed to append buffer', undefined, error); } } getCapabilities() { return { isSupported: this._isSupported, hasManagedMediaSource: this._hasManagedMediaSource, supportedMimeTypes: [...this._supportedMimeTypes] }; } isMimeTypeSupported(mimeType) { return this._supportedMimeTypes.includes(mimeType); } } //# sourceMappingURL=MediaSourceHelper.js.map