diff --git a/tsconfig.json b/tsconfig.json index 003c897c9..fc88689d2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,6 +11,7 @@ "outDir": "./lib", "strict": true, "target": "esnext", + "module": "esnext", "skipLibCheck": true }, "include": ["src"], diff --git a/web/TrackPlayer/Player.ts b/web/TrackPlayer/Player.ts index 53b97e01d..bf0d90358 100644 --- a/web/TrackPlayer/Player.ts +++ b/web/TrackPlayer/Player.ts @@ -1,12 +1,10 @@ -// @ts-ignore -import shaka from 'shaka-player/dist/shaka-player.ui'; - import { State } from '../../src/constants/State'; import type { Track, Progress, PlaybackState } from '../../src/interfaces'; +import { SetupNotCalledError } from './SetupNotCalledError'; export class Player { - protected element: HTMLMediaElement; - protected player: shaka.Player; + protected element?: HTMLMediaElement; + protected player?: shaka.Player; protected _current?: Track = undefined; protected _playWhenReady: boolean = false; protected _state: PlaybackState = { state: State.None }; @@ -35,7 +33,12 @@ export class Player { this._playWhenReady = pwr; } - constructor() { + async setupPlayer() { + // shaka only runs in a browser + if (typeof window === 'undefined') return; + + // @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. @@ -57,16 +60,16 @@ export class Player { this.player = new shaka.Player(this.element); // Listen for relevant events events. - this.player.addEventListener('error', (error: any) => { + this.player!.addEventListener('error', (error: any) => { // Extract the shaka.util.Error object from the event. this.onError(error.detail); }); this.element.addEventListener('ended', () => this.onTrackEnded()); this.element.addEventListener('playing', () => this.onTrackPlaying()); this.element.addEventListener('pause', () => this.onTrackPaused()); - this.player.addEventListener('loading', () => this.onTrackLoading()); - this.player.addEventListener('loaded', () => this.onTrackLoaded()); - this.player.addEventListener('buffering', ({ buffering }: any) => { + this.player!.addEventListener('loading', () => this.onTrackLoading()); + this.player!.addEventListener('loaded', () => this.onTrackLoaded()); + this.player!.addEventListener('buffering', ({ buffering }: any) => { if (buffering === true) { this.onTrackBuffering(); } @@ -115,62 +118,76 @@ export class Player { * player control */ public async load(track: Track) { - await this.player.load(track.url); + 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(); } 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, @@ -179,6 +196,7 @@ export class Player { } public getBufferedPosition() { + if (!this.element) throw new SetupNotCalledError(); return this.element.buffered.end; } } diff --git a/web/TrackPlayer/SetupNotCalledError.ts b/web/TrackPlayer/SetupNotCalledError.ts new file mode 100644 index 000000000..8f1e55530 --- /dev/null +++ b/web/TrackPlayer/SetupNotCalledError.ts @@ -0,0 +1,5 @@ +export class SetupNotCalledError extends Error { + constructor() { + super('You must call `setupPlayer` prior to interacting with the player.'); + } +} diff --git a/web/TrackPlayerModule.ts b/web/TrackPlayerModule.ts index 190c581c2..65022f64e 100644 --- a/web/TrackPlayerModule.ts +++ b/web/TrackPlayerModule.ts @@ -3,6 +3,7 @@ import { DeviceEventEmitter } from 'react-native'; import { Event, PlaybackState, State } from '../src'; import type { Track, UpdateOptions } from '../src'; import { PlaylistPlayer, RepeatMode } from './TrackPlayer'; +import { SetupNotCalledError } from './TrackPlayer/SetupNotCalledError'; export class TrackPlayerModule extends PlaylistPlayer { protected emitter = DeviceEventEmitter; @@ -61,9 +62,6 @@ export class TrackPlayerModule extends PlaylistPlayer { this.emitter.emit(Event.PlaybackState, newState); } - // TODO: this probably does nothing - public setupPlayer(options: any) {} - public async updateOptions(options: UpdateOptions) { // clear and reset interval this.clearUpdateEventInterval(); @@ -91,7 +89,7 @@ export class TrackPlayerModule extends PlaylistPlayer { } protected async onTrackEnded() { - const position = this.element.currentTime; + const position = this.element!.currentTime; await super.onTrackEnded(); this.emitter.emit(Event.PlaybackTrackChanged, { @@ -105,7 +103,7 @@ export class TrackPlayerModule extends PlaylistPlayer { await super.onPlaylistEnded(); this.emitter.emit(Event.PlaybackQueueEnded, { track: this.currentIndex, - position: this.element.currentTime, + position: this.element!.currentTime, }); } @@ -132,6 +130,7 @@ export class TrackPlayerModule extends PlaylistPlayer { } public async load(track: Track) { + if (!this.element) throw new SetupNotCalledError(); const lastTrack = this.current; const lastPosition = this.element.currentTime; await super.load(track); @@ -154,6 +153,8 @@ export class TrackPlayerModule extends PlaylistPlayer { } public getActiveTrackIndex(): number | undefined { + // per the existing spec, this should throw if setup hasn't been called + if (!this.element || !this.player) throw new SetupNotCalledError(); return this.currentIndex; }