mirror of
https://github.com/janishutz/MusicPlayer.git
synced 2026-09-10 14:25:24 +02:00
refactor!: use plugin-based system for sources
This commit is contained in:
Vendored
+6
-1
@@ -13,10 +13,15 @@ export interface Song {
|
||||
'source': 'local' | 'applemusic';
|
||||
|
||||
/**
|
||||
* The identifier for the song so it can be found again (such as Apple Music ID or filename)
|
||||
* The identifier for the song so it can be found again (such as Apple Music ID or URL for playback)
|
||||
*/
|
||||
'identifier': string;
|
||||
|
||||
/**
|
||||
* Any additional identifiers (such as filename, etc) to associate
|
||||
*/
|
||||
'additional-identifier'?: string;
|
||||
|
||||
/**
|
||||
* Additional info, such as dance type to be displayed on remote screens
|
||||
*/
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
export const useMusicKit = ( storefront: string = 'ch' ) => {
|
||||
const init = async () => {
|
||||
const res = await fetch( import.meta.env.VITE_BACKEND_URL + '/', {
|
||||
'credentials': 'include'
|
||||
} );
|
||||
|
||||
if ( res.status === 200 ) {
|
||||
const token = await res.text();
|
||||
|
||||
await window.MusicKit.configure( {
|
||||
'developerToken': token,
|
||||
'app': {
|
||||
'name': 'MusicPlayer',
|
||||
'build': '4',
|
||||
'icon': 'https://music.janishutz.com/logo.jpg'
|
||||
},
|
||||
'storefront': storefront.toUpperCase()
|
||||
} );
|
||||
}
|
||||
|
||||
isInit = true;
|
||||
};
|
||||
|
||||
let isInit = false;
|
||||
|
||||
if ( !window.MusicKit ) {
|
||||
document.addEventListener( 'musickitloaded', () => {
|
||||
init();
|
||||
} );
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
@@ -1,9 +0,0 @@
|
||||
export const play = () => {};
|
||||
|
||||
export const pause = () => {};
|
||||
|
||||
export const skip10 = () => {};
|
||||
|
||||
export const back10 = () => {};
|
||||
|
||||
export const playSong = ( id: string ) => {};
|
||||
@@ -1,3 +0,0 @@
|
||||
export const login = async () => {
|
||||
|
||||
};
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
import type {
|
||||
Song
|
||||
} from '@/ts/dtype/playlist';
|
||||
|
||||
// TODO: Add search interface elements to plugin's args (such as a search interface)
|
||||
export type PlayerSourcePluginInitializer = () => Promise<PlayerSourcePlugin>;
|
||||
|
||||
export interface PlayerSourcePlugin {
|
||||
/**
|
||||
* The name of the source that is displayed in the UI
|
||||
*/
|
||||
'name': string;
|
||||
|
||||
/**
|
||||
* The identifier used in the playlist files
|
||||
*/
|
||||
'id': string;
|
||||
|
||||
/**
|
||||
* Implement any login flow here. Optional
|
||||
*/
|
||||
'login'?: () => Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Play a song by its ID
|
||||
*/
|
||||
'playSong': ( id: string ) => Promise<void>;
|
||||
|
||||
/**
|
||||
* Continue playing the song
|
||||
*/
|
||||
'play': () => void;
|
||||
|
||||
/**
|
||||
* Pause the currently playing song
|
||||
*/
|
||||
'pause': () => void;
|
||||
|
||||
/**
|
||||
* Stop / unload the current song. This indicates that either the playlist was cleared
|
||||
* or a next song is about to be loaded (possibly from another plugin)
|
||||
*/
|
||||
'stop': () => void;
|
||||
|
||||
/**
|
||||
* Seek to a specific point in the song.
|
||||
* @param pos is always given as a percentage (pos \in [0, 1])
|
||||
*/
|
||||
'seekTo': ( pos: number ) => void;
|
||||
|
||||
/**
|
||||
* Return the playback position as a percentage of the duration. (return value \in [0, 1])
|
||||
*/
|
||||
'getPlaybackPos': () => number;
|
||||
|
||||
/**
|
||||
* Return the song duration in seconds.
|
||||
*/
|
||||
'getDuration': () => number;
|
||||
|
||||
/**
|
||||
* Called when user adds another song to the playlist via this source.
|
||||
* You may use the provided interface elements (search bar and popups) to e.g. ask if user wants to use a playlist, album, etc
|
||||
*/
|
||||
'addSongsFromThisSource': () => Promise<Song[]>;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import type {
|
||||
PlayerSourcePlugin,
|
||||
PlayerSourcePluginInitializer
|
||||
} from '../interface';
|
||||
|
||||
export const useLocalPlayer: PlayerSourcePluginInitializer = async (): Promise<PlayerSourcePlugin> => {
|
||||
const player = document.createElement( 'audio' );
|
||||
|
||||
const playSong = async ( id: string ) => {
|
||||
player.src = id;
|
||||
player.play();
|
||||
};
|
||||
|
||||
return {
|
||||
'id': 'local',
|
||||
'name': 'Local Disk',
|
||||
'play': player.play,
|
||||
'getPlaybackPos': () => player.currentTime / player.duration,
|
||||
'getDuration': () => player.duration,
|
||||
playSong,
|
||||
'seekTo': pos => player.currentTime = pos,
|
||||
'pause': player.pause,
|
||||
'stop': () => player.src = '',
|
||||
// TODO: Implement
|
||||
'addSongsFromThisSource': async () => []
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import 'musickitjs-v3-types';
|
||||
import type {
|
||||
PlayerSourcePlugin,
|
||||
PlayerSourcePluginInitializer
|
||||
} from '../interface';
|
||||
import {
|
||||
musicKitPlayback
|
||||
} from './playback';
|
||||
|
||||
export const useMusicKit: PlayerSourcePluginInitializer = ( storefront: string = 'ch' ): Promise<PlayerSourcePlugin> => {
|
||||
return new Promise( ( resolve, reject ) => {
|
||||
const init = async ( storefront: string ): Promise<PlayerSourcePlugin> => {
|
||||
const res = await fetch( import.meta.env.VITE_BACKEND_URL + '/', {
|
||||
'credentials': 'include'
|
||||
} );
|
||||
|
||||
if ( res.status === 200 ) {
|
||||
const token = await res.text();
|
||||
const instance = await window.MusicKit.configure( {
|
||||
'developerToken': token,
|
||||
'app': {
|
||||
'name': 'MusicPlayer',
|
||||
'build': '4',
|
||||
'icon': 'https://music.janishutz.com/logo.jpg'
|
||||
},
|
||||
'storefrontId': storefront.toUpperCase()
|
||||
} );
|
||||
const controls = musicKitPlayback( instance );
|
||||
|
||||
const login = async (): Promise<boolean> => {
|
||||
try {
|
||||
await instance.authorize();
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
'play': controls.play,
|
||||
'pause': controls.pause,
|
||||
'stop': controls.stop,
|
||||
'playSong': controls.playSong,
|
||||
'getDuration': controls.getDuration,
|
||||
'getPlaybackPos': controls.getPlaybackPos,
|
||||
'id': 'applemusic',
|
||||
'name': 'Apple Music',
|
||||
'seekTo': controls.seekTo,
|
||||
'login': login,
|
||||
// TODO: Implement
|
||||
'addSongsFromThisSource': async () => []
|
||||
};
|
||||
} else {
|
||||
throw new Error( 'ERR_AUTH' );
|
||||
}
|
||||
};
|
||||
|
||||
if ( !window.MusicKit ) {
|
||||
document.addEventListener( 'musickitloaded', () => {
|
||||
init( storefront )
|
||||
.then( resolve )
|
||||
.catch( reject );
|
||||
} );
|
||||
} else {
|
||||
init( storefront )
|
||||
.then( resolve )
|
||||
.catch( reject );
|
||||
}
|
||||
} );
|
||||
};
|
||||
@@ -0,0 +1,46 @@
|
||||
import type {
|
||||
MusicKitInstance
|
||||
} from 'musickitjs-v3-types/MusicKitInstance';
|
||||
|
||||
export const musicKitPlayback = ( musickitInstance: MusicKitInstance ) => {
|
||||
const play = () => {
|
||||
musickitInstance.play();
|
||||
};
|
||||
|
||||
const pause = () => {
|
||||
musickitInstance.pause();
|
||||
};
|
||||
|
||||
const seekTo = ( pos: number ) => {
|
||||
musickitInstance.seekToTime( pos * musickitInstance.currentPlaybackDuration );
|
||||
};
|
||||
|
||||
const playSong = async ( id: string ) => {
|
||||
await musickitInstance.setQueue( {
|
||||
'song': id
|
||||
} );
|
||||
musickitInstance.play();
|
||||
};
|
||||
|
||||
const stop = () => {
|
||||
musickitInstance.stop();
|
||||
};
|
||||
|
||||
const getPlaybackPos = (): number => {
|
||||
return musickitInstance.currentPlaybackProgress;
|
||||
};
|
||||
|
||||
const getDuration = (): number => {
|
||||
return musickitInstance.currentPlaybackDuration;
|
||||
};
|
||||
|
||||
return {
|
||||
play,
|
||||
pause,
|
||||
seekTo,
|
||||
playSong,
|
||||
stop,
|
||||
getPlaybackPos,
|
||||
getDuration
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
// TODO: If local files contained, prompt user to select files, then associate based on file names
|
||||
Reference in New Issue
Block a user