CoolFace
Apppublic

sourav-das/stem-separator

sourceHugging Facemitupdated 6mo agoView on Hugging Face
3likes
player.js252 linesDownload Raw Back to dist
1var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {2    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }3    return new (P || (P = Promise))(function (resolve, reject) {4        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }5        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }6        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }7        step((generator = generator.apply(thisArg, _arguments || [])).next());8    });9};10import EventEmitter from './event-emitter.js';11import { signal } from './reactive/store.js';12class Player extends EventEmitter {13    // Expose reactive state as writable signals14    // These are writable to allow WaveSurfer to compose them into centralized state15    get isPlayingSignal() {16        return this._isPlaying;17    }18    get currentTimeSignal() {19        return this._currentTime;20    }21    get durationSignal() {22        return this._duration;23    }24    get volumeSignal() {25        return this._volume;26    }27    get mutedSignal() {28        return this._muted;29    }30    get playbackRateSignal() {31        return this._playbackRate;32    }33    get seekingSignal() {34        return this._seeking;35    }36    constructor(options) {37        super();38        this.isExternalMedia = false;39        this.reactiveMediaEventCleanups = [];40        if (options.media) {41            this.media = options.media;42            this.isExternalMedia = true;43        }44        else {45            this.media = document.createElement('audio');46        }47        // Initialize reactive state48        this._isPlaying = signal(false);49        this._currentTime = signal(0);50        this._duration = signal(0);51        this._volume = signal(this.media.volume);52        this._muted = signal(this.media.muted);53        this._playbackRate = signal(this.media.playbackRate || 1);54        this._seeking = signal(false);55        // Setup reactive media event handlers56        this.setupReactiveMediaEvents();57        // Controls58        if (options.mediaControls) {59            this.media.controls = true;60        }61        // Autoplay62        if (options.autoplay) {63            this.media.autoplay = true;64        }65        // Speed66        if (options.playbackRate != null) {67            this.onMediaEvent('canplay', () => {68                if (options.playbackRate != null) {69                    this.media.playbackRate = options.playbackRate;70                }71            }, { once: true });72        }73    }74    /**75     * Setup reactive media event handlers that update signals76     * This bridges the imperative HTMLMediaElement API to reactive state77     */78    setupReactiveMediaEvents() {79        // Playing state80        this.reactiveMediaEventCleanups.push(this.onMediaEvent('play', () => {81            this._isPlaying.set(true);82        }));83        this.reactiveMediaEventCleanups.push(this.onMediaEvent('pause', () => {84            this._isPlaying.set(false);85        }));86        this.reactiveMediaEventCleanups.push(this.onMediaEvent('ended', () => {87            this._isPlaying.set(false);88        }));89        // Time tracking90        this.reactiveMediaEventCleanups.push(this.onMediaEvent('timeupdate', () => {91            this._currentTime.set(this.media.currentTime);92        }));93        this.reactiveMediaEventCleanups.push(this.onMediaEvent('durationchange', () => {94            this._duration.set(this.media.duration || 0);95        }));96        this.reactiveMediaEventCleanups.push(this.onMediaEvent('loadedmetadata', () => {97            this._duration.set(this.media.duration || 0);98        }));99        // Seeking state100        this.reactiveMediaEventCleanups.push(this.onMediaEvent('seeking', () => {101            this._seeking.set(true);102        }));103        this.reactiveMediaEventCleanups.push(this.onMediaEvent('seeked', () => {104            this._seeking.set(false);105        }));106        // Volume and muted107        this.reactiveMediaEventCleanups.push(this.onMediaEvent('volumechange', () => {108            this._volume.set(this.media.volume);109            this._muted.set(this.media.muted);110        }));111        // Playback rate112        this.reactiveMediaEventCleanups.push(this.onMediaEvent('ratechange', () => {113            this._playbackRate.set(this.media.playbackRate);114        }));115    }116    onMediaEvent(event, callback, options) {117        this.media.addEventListener(event, callback, options);118        return () => this.media.removeEventListener(event, callback, options);119    }120    getSrc() {121        return this.media.currentSrc || this.media.src || '';122    }123    revokeSrc() {124        const src = this.getSrc();125        if (src.startsWith('blob:')) {126            URL.revokeObjectURL(src);127        }128    }129    canPlayType(type) {130        return this.media.canPlayType(type) !== '';131    }132    setSrc(url, blob) {133        const prevSrc = this.getSrc();134        if (url && prevSrc === url)135            return; // no need to change the source136        this.revokeSrc();137        const newSrc = blob instanceof Blob && (this.canPlayType(blob.type) || !url) ? URL.createObjectURL(blob) : url;138        // Reset the media element, otherwise it keeps the previous source139        if (prevSrc) {140            this.media.removeAttribute('src');141        }142        if (newSrc || url) {143            try {144                this.media.src = newSrc;145            }146            catch (_a) {147                this.media.src = url;148            }149        }150    }151    destroy() {152        // Cleanup reactive media event listeners153        this.reactiveMediaEventCleanups.forEach((cleanup) => cleanup());154        this.reactiveMediaEventCleanups = [];155        if (this.isExternalMedia)156            return;157        this.media.pause();158        this.revokeSrc();159        this.media.removeAttribute('src');160        // Load resets the media element to its initial state161        this.media.load();162        // Remove from DOM after cleanup163        this.media.remove();164    }165    setMediaElement(element) {166        // Cleanup reactive event listeners from old media element167        this.reactiveMediaEventCleanups.forEach((cleanup) => cleanup());168        this.reactiveMediaEventCleanups = [];169        // Set new media element170        this.media = element;171        // Reinitialize reactive event listeners on new media element172        this.setupReactiveMediaEvents();173    }174    /** Start playing the audio */175    play() {176        return __awaiter(this, void 0, void 0, function* () {177            try {178                return yield this.media.play();179            }180            catch (err) {181                if (err instanceof DOMException && err.name === 'AbortError') {182                    return;183                }184                throw err;185            }186        });187    }188    /** Pause the audio */189    pause() {190        this.media.pause();191    }192    /** Check if the audio is playing */193    isPlaying() {194        return !this.media.paused && !this.media.ended;195    }196    /** Jump to a specific time in the audio (in seconds) */197    setTime(time) {198        this.media.currentTime = Math.max(0, Math.min(time, this.getDuration()));199    }200    /** Get the duration of the audio in seconds */201    getDuration() {202        return this.media.duration;203    }204    /** Get the current audio position in seconds */205    getCurrentTime() {206        return this.media.currentTime;207    }208    /** Get the audio volume */209    getVolume() {210        return this.media.volume;211    }212    /** Set the audio volume */213    setVolume(volume) {214        this.media.volume = volume;215    }216    /** Get the audio muted state */217    getMuted() {218        return this.media.muted;219    }220    /** Mute or unmute the audio */221    setMuted(muted) {222        this.media.muted = muted;223    }224    /** Get the playback speed */225    getPlaybackRate() {226        return this.media.playbackRate;227    }228    /** Check if the audio is seeking */229    isSeeking() {230        return this.media.seeking;231    }232    /** Set the playback speed, pass an optional false to NOT preserve the pitch */233    setPlaybackRate(rate, preservePitch) {234        // preservePitch is true by default in most browsers235        if (preservePitch != null) {236            this.media.preservesPitch = preservePitch;237        }238        this.media.playbackRate = rate;239    }240    /** Get the HTML media element */241    getMediaElement() {242        return this.media;243    }244    /** Set a sink id to change the audio output device */245    setSinkId(sinkId) {246        // See https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/setSinkId247        const media = this.media;248        return media.setSinkId(sinkId);249    }250}251export default Player;252