Skip to content

Commit

Permalink
feat(web): prevent exceptions during SSR due to inability to import s…
Browse files Browse the repository at this point in the history
…haka player
  • Loading branch information
jspizziri committed Apr 27, 2023
1 parent 4295134 commit 5f5ea22
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 16 deletions.
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"outDir": "./lib",
"strict": true,
"target": "esnext",
"module": "esnext",
"skipLibCheck": true
},
"include": ["src"],
Expand Down
40 changes: 29 additions & 11 deletions web/TrackPlayer/Player.ts
Original file line number Diff line number Diff line change
@@ -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 };
Expand Down Expand Up @@ -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.
Expand All @@ -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();
}
Expand Down Expand Up @@ -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,
Expand All @@ -179,6 +196,7 @@ export class Player {
}

public getBufferedPosition() {
if (!this.element) throw new SetupNotCalledError();
return this.element.buffered.end;
}
}
5 changes: 5 additions & 0 deletions web/TrackPlayer/SetupNotCalledError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class SetupNotCalledError extends Error {
constructor() {
super('You must call `setupPlayer` prior to interacting with the player.');
}
}
11 changes: 6 additions & 5 deletions web/TrackPlayerModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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, {
Expand All @@ -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,
});
}

Expand All @@ -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);
Expand All @@ -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;
}

Expand Down

0 comments on commit 5f5ea22

Please sign in to comment.