sourav-das/stem-separator
3
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 