react-native-track-player
Version:
A fully fledged audio module created for music apps
202 lines (174 loc) • 5.9 kB
text/typescript
import { State } from '../../src/constants/State';
import type { Track, Progress, PlaybackState } from '../../src/interfaces';
import { SetupNotCalledError } from './SetupNotCalledError';
export class Player {
protected hasInitialized: boolean = false;
protected element?: HTMLMediaElement;
protected player?: shaka.Player;
protected _current?: Track = undefined;
protected _playWhenReady: boolean = false;
protected _state: PlaybackState = { state: State.None };
// current getter/setter
public get current(): Track | undefined {
return this._current;
}
public set current(cur: Track | undefined) {
this._current = cur;
}
// state getter/setter
public get state(): PlaybackState {
return this._state;
}
public set state(newState: PlaybackState) {
this._state = newState;
}
// playWhenReady getter/setter
public get playWhenReady(): boolean {
return this._playWhenReady;
}
public set playWhenReady(pwr: boolean) {
this._playWhenReady = pwr;
}
async setupPlayer() {
// shaka only runs in a browser
if (typeof window === 'undefined') return;
if (this.hasInitialized === true) {
// TODO: double check the structure of this error message
throw { code: 'player_already_initialized', message: 'The player is not initialized. Call setupPlayer first.' };
}
// @ts-ignore
const shaka = await import('shaka-player/dist/shaka-player.ui');
// Install built-in polyfills to patch browser incompatibilities.
shaka.polyfill.installAll();
// Check to see if the browser supports the basic APIs Shaka needs.
if (!shaka.Player.isBrowserSupported()) {
// This browser does not have the minimum set of APIs we need.
this.state = {
state: State.Error,
error: {
code: 'not_supported',
message: 'Browser not supported.',
},
};
throw new Error('Browser not supported.');
}
// build dom element and attach shaka-player
this.element = document.createElement('audio');
this.element.setAttribute('id', 'react-native-track-player');
this.player = new shaka.Player();
this.player?.attach(this.element);
// Listen for relevant events events.
this.player!.addEventListener('error', (error: any) => {
// Extract the shaka.util.Error object from the event.
this.onError(error.detail);
});
this.element.addEventListener('ended', this.onStateUpdate.bind(this, State.Ended));
this.element.addEventListener('playing', this.onStateUpdate.bind(this, State.Playing));
this.element.addEventListener('pause', this.onStateUpdate.bind(this, State.Paused));
this.player!.addEventListener('loading', this.onStateUpdate.bind(this, State.Loading));
this.player!.addEventListener('loaded', this.onStateUpdate.bind(this, State.Ready));
this.player!.addEventListener('buffering', ({ buffering }: any) => {
if (buffering === true) {
this.onStateUpdate(State.Buffering);
}
});
// Attach player to the window to make it easy to access in the JS console.
// @ts-ignore
window.rntp = this.player;
this.hasInitialized = true;
}
/**
* event handlers
*/
protected onStateUpdate(state: Exclude<State, State.Error>) {
this.state = { state };
}
protected onError(error: any) {
// unload the current track to allow for clean playback on other
this.player?.unload();
this.state = {
state: State.Error,
error: {
code: error.code.toString(),
message: error.message,
},
};
// Log the error.
console.debug('Error code', error.code, 'object', error);
}
/**
* player control
*/
public async load(track: Track) {
if (!this.player) throw new SetupNotCalledError();
await this.player.load(track.url as string);
this.current = track;
}
public async retry() {
if (!this.player) throw new SetupNotCalledError();
this.player.retryStreaming();
}
public async stop() {
if (!this.player) throw new SetupNotCalledError();
this.current = undefined;
await this.player.unload()
}
public play() {
if (!this.element) throw new SetupNotCalledError();
this.playWhenReady = true;
return this.element.play()
.catch(err => {
console.error(err);
})
;
}
public pause() {
if (!this.element) throw new SetupNotCalledError();
this.playWhenReady = false;
return this.element.pause();
}
public setRate(rate: number) {
if (!this.element) throw new SetupNotCalledError();
return this.element.playbackRate = rate;
}
public getRate() {
if (!this.element) throw new SetupNotCalledError();
return this.element.playbackRate;
}
public seekBy(offset: number) {
if (!this.element) throw new SetupNotCalledError();
this.element.currentTime += offset;
}
public seekTo(seconds: number) {
if (!this.element) throw new SetupNotCalledError();
this.element.currentTime = seconds;
}
public setVolume(volume: number) {
if (!this.element) throw new SetupNotCalledError();
this.element.volume = volume;
}
public getVolume() {
if (!this.element) throw new SetupNotCalledError();
return this.element.volume;
}
public getDuration() {
if (!this.element) throw new SetupNotCalledError();
return this.element.duration
}
public getPosition() {
if (!this.element) throw new SetupNotCalledError();
return this.element.currentTime
}
public getProgress(): Progress {
if (!this.element) throw new SetupNotCalledError();
return {
position: this.element.currentTime,
duration: this.element.duration || 0,
buffered: 0, // TODO: this.element.buffered.end,
}
}
public getBufferedPosition() {
if (!this.element) throw new SetupNotCalledError();
return this.element.buffered.end;
}
}