From 6cb1c9d29c74656839a4f9e1c32588cc4fbd09a7 Mon Sep 17 00:00:00 2001 From: Youwen Wu Date: Wed, 21 Feb 2024 23:36:24 -0800 Subject: [PATCH] feat: add basic media player --- client/src/App.svelte | 2 +- client/src/globals.d.ts | 1 - client/src/lib/Apps/AppBar.svelte | 6 +- client/src/lib/Apps/Camera/Camera.svelte | 15 +-- .../lib/Apps/MusicBrowser/MusicBrowser.svelte | 12 +++ client/src/lib/Apps/MusicBrowser/Song.svelte | 50 +++++++++ .../lib/Apps/MusicPlayer/MusicBrowser.svelte | 22 ---- client/src/lib/Apps/MusicPlayer/Song.svelte | 33 ------ client/src/lib/Apps/MusicPlayer/songList.ts | 30 ------ client/src/lib/Apps/appList.ts | 2 +- client/src/lib/Dashboard/Dashboard.svelte | 5 +- .../Dashboard/MediaDisplay/Controls.svelte | 28 ----- .../MediaDisplay/MediaDisplay.svelte | 20 ---- .../lib/Dashboard/MediaPlayer/Controls.svelte | 33 ++++++ .../Dashboard/MediaPlayer/MediaDisplay.svelte | 37 +++++++ .../lib/Dashboard/MediaPlayer/Player.svelte | 26 +++++ .../lib/Dashboard/MediaPlayer/audioManager.ts | 102 ++++++++++++++++++ .../src/lib/Dashboard/MediaPlayer/songList.ts | 14 +++ client/src/lib/stores/musicStore.ts | 60 +++++++++++ 19 files changed, 344 insertions(+), 154 deletions(-) create mode 100644 client/src/lib/Apps/MusicBrowser/MusicBrowser.svelte create mode 100644 client/src/lib/Apps/MusicBrowser/Song.svelte delete mode 100644 client/src/lib/Apps/MusicPlayer/MusicBrowser.svelte delete mode 100644 client/src/lib/Apps/MusicPlayer/Song.svelte delete mode 100644 client/src/lib/Apps/MusicPlayer/songList.ts delete mode 100644 client/src/lib/Dashboard/MediaDisplay/Controls.svelte delete mode 100644 client/src/lib/Dashboard/MediaDisplay/MediaDisplay.svelte create mode 100644 client/src/lib/Dashboard/MediaPlayer/Controls.svelte create mode 100644 client/src/lib/Dashboard/MediaPlayer/MediaDisplay.svelte create mode 100644 client/src/lib/Dashboard/MediaPlayer/Player.svelte create mode 100644 client/src/lib/Dashboard/MediaPlayer/audioManager.ts create mode 100644 client/src/lib/Dashboard/MediaPlayer/songList.ts create mode 100644 client/src/lib/stores/musicStore.ts diff --git a/client/src/App.svelte b/client/src/App.svelte index a419633..654fe33 100644 --- a/client/src/App.svelte +++ b/client/src/App.svelte @@ -15,7 +15,7 @@ -
+
diff --git a/client/src/globals.d.ts b/client/src/globals.d.ts index 93ce3e9..cac0a05 100644 --- a/client/src/globals.d.ts +++ b/client/src/globals.d.ts @@ -9,7 +9,6 @@ interface SongData { artist: string src: string coverImg: string - slug: string } interface AppData { diff --git a/client/src/lib/Apps/AppBar.svelte b/client/src/lib/Apps/AppBar.svelte index e0d0b30..daf3ebf 100644 --- a/client/src/lib/Apps/AppBar.svelte +++ b/client/src/lib/Apps/AppBar.svelte @@ -5,7 +5,7 @@
{#each Object.entries(appList) as [appName, appData]} + + +
+
+
+ + diff --git a/client/src/lib/Apps/MusicPlayer/MusicBrowser.svelte b/client/src/lib/Apps/MusicPlayer/MusicBrowser.svelte deleted file mode 100644 index b9da24c..0000000 --- a/client/src/lib/Apps/MusicPlayer/MusicBrowser.svelte +++ /dev/null @@ -1,22 +0,0 @@ - - -
- {#each songList as song} - - {/each} -
- - diff --git a/client/src/lib/Apps/MusicPlayer/Song.svelte b/client/src/lib/Apps/MusicPlayer/Song.svelte deleted file mode 100644 index 7a1e357..0000000 --- a/client/src/lib/Apps/MusicPlayer/Song.svelte +++ /dev/null @@ -1,33 +0,0 @@ - - -
- album cover -

{title}

-

{artist}

-
-
- - - -
-
-
- - diff --git a/client/src/lib/Apps/MusicPlayer/songList.ts b/client/src/lib/Apps/MusicPlayer/songList.ts deleted file mode 100644 index 8e231dd..0000000 --- a/client/src/lib/Apps/MusicPlayer/songList.ts +++ /dev/null @@ -1,30 +0,0 @@ -export const songList = [ - { - title: 'Danger Zone', - artist: 'Kenny Loggins', - src: '/static/songs/danger-zone/audio.mp3', - coverImg: '/static/songs/danger-zone/cover.png', - slug: 'danger-zone', - }, - { - title: 'Danger Zone', - artist: 'Kenny Loggins', - src: '/static/songs/danger-zone/audio.mp3', - coverImg: '/static/songs/danger-zone/cover.png', - slug: 'danger-zone', - }, - { - title: 'Danger Zone', - artist: 'Kenny Loggins', - src: '/static/songs/danger-zone/audio.mp3', - coverImg: '/static/songs/danger-zone/cover.png', - slug: 'danger-zone', - }, - { - title: 'Danger Zone', - artist: 'Kenny Loggins', - src: '/static/songs/danger-zone/audio.mp3', - coverImg: '/static/songs/danger-zone/cover.png', - slug: 'danger-zone', - }, -] diff --git a/client/src/lib/Apps/appList.ts b/client/src/lib/Apps/appList.ts index 2de0990..5c70a30 100644 --- a/client/src/lib/Apps/appList.ts +++ b/client/src/lib/Apps/appList.ts @@ -1,5 +1,5 @@ import Camera from './Camera/Camera.svelte' -import MusicBrowser from './MusicPlayer/MusicBrowser.svelte' +import MusicBrowser from './MusicBrowser/MusicBrowser.svelte' export const appList = { 'camera': { diff --git a/client/src/lib/Dashboard/Dashboard.svelte b/client/src/lib/Dashboard/Dashboard.svelte index 1c48c0f..dc825d1 100644 --- a/client/src/lib/Dashboard/Dashboard.svelte +++ b/client/src/lib/Dashboard/Dashboard.svelte @@ -2,9 +2,12 @@ import TopBar from './TopBar/TopBar.svelte' import Speedometer from './Speedometer.svelte' import SpeedLimit from './SpeedLimit.svelte' - import MediaDisplay from './MediaDisplay/MediaDisplay.svelte' + import MediaDisplay from './MediaPlayer/MediaDisplay.svelte' + import Player from './MediaPlayer/Player.svelte' + +
diff --git a/client/src/lib/Dashboard/MediaDisplay/Controls.svelte b/client/src/lib/Dashboard/MediaDisplay/Controls.svelte deleted file mode 100644 index 1cebede..0000000 --- a/client/src/lib/Dashboard/MediaDisplay/Controls.svelte +++ /dev/null @@ -1,28 +0,0 @@ - - -
- - {#if playing} - - {:else} - - {/if} - -
- - diff --git a/client/src/lib/Dashboard/MediaDisplay/MediaDisplay.svelte b/client/src/lib/Dashboard/MediaDisplay/MediaDisplay.svelte deleted file mode 100644 index bee6783..0000000 --- a/client/src/lib/Dashboard/MediaDisplay/MediaDisplay.svelte +++ /dev/null @@ -1,20 +0,0 @@ - - -
-
-
- album cover -
-
-

Danger Zone

-

Kenny Loggins

-
-
- -
diff --git a/client/src/lib/Dashboard/MediaPlayer/Controls.svelte b/client/src/lib/Dashboard/MediaPlayer/Controls.svelte new file mode 100644 index 0000000..5dfeb28 --- /dev/null +++ b/client/src/lib/Dashboard/MediaPlayer/Controls.svelte @@ -0,0 +1,33 @@ + + +
+ + + +
+ + diff --git a/client/src/lib/Dashboard/MediaPlayer/MediaDisplay.svelte b/client/src/lib/Dashboard/MediaPlayer/MediaDisplay.svelte new file mode 100644 index 0000000..60847d0 --- /dev/null +++ b/client/src/lib/Dashboard/MediaPlayer/MediaDisplay.svelte @@ -0,0 +1,37 @@ + + +{#if songData} +
+
+
+ album cover +
+
+

{songData.title}

+

{songData.artist}

+
+
+ +
+{/if} diff --git a/client/src/lib/Dashboard/MediaPlayer/Player.svelte b/client/src/lib/Dashboard/MediaPlayer/Player.svelte new file mode 100644 index 0000000..4e244d6 --- /dev/null +++ b/client/src/lib/Dashboard/MediaPlayer/Player.svelte @@ -0,0 +1,26 @@ + diff --git a/client/src/lib/Dashboard/MediaPlayer/audioManager.ts b/client/src/lib/Dashboard/MediaPlayer/audioManager.ts new file mode 100644 index 0000000..63bc198 --- /dev/null +++ b/client/src/lib/Dashboard/MediaPlayer/audioManager.ts @@ -0,0 +1,102 @@ +export class AudioManager { + private audioContext: AudioContext | null = null + private currentSource: AudioBufferSourceNode | null = null + private currentBuffer: AudioBuffer | null = null // Stores the current audio buffer + private startTime: number = 0 // When the current playback started + private pauseTime: number = 0 // Track where we paused + private currentToken: number = 0 // Unique token for each play request + private isPaused: boolean = false + + constructor() { + this.initAudioContext() + } + + private async initAudioContext() { + if (!this.audioContext) { + this.audioContext = new AudioContext() + } + } + + public async playAudio(url: string): Promise { + if (this.isPaused && this.currentBuffer) { + // If paused, resume instead of reloading + this.resume() + return + } + + this.pauseTime = 0 // Reset pause time for a new track + const playToken = ++this.currentToken // Update the token for this request + await this.initAudioContext() + + if (this.audioContext) { + this.stopAudio() // Stop any currently playing audio + + try { + const response = await fetch(url) + const arrayBuffer = await response.arrayBuffer() + if (this.currentToken !== playToken) { + return // Abort if a newer request has been made + } + const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer) + this.currentBuffer = audioBuffer // Save the buffer for potential pausing/resuming + + this.startPlayback(audioBuffer, 0) // Start playback from the beginning + } catch (error) { + console.error('Error playing audio:', error) + } + } + } + + private startPlayback(buffer: AudioBuffer, offset: number) { + const source = this.audioContext!.createBufferSource() + source.buffer = buffer + source.connect(this.audioContext!.destination) + source.start(0, offset) + this.startTime = this.audioContext!.currentTime - offset + this.currentSource = source + this.isPaused = false + + source.onended = () => { + if (!this.isPaused) { + this.currentBuffer = null // Clear the buffer if playback finishes normally + } + } + } + + public pause() { + if (!this.isPaused && this.currentSource && this.audioContext) { + this.pauseTime = this.audioContext.currentTime - this.startTime + this.currentSource.stop() + this.isPaused = true + } + } + + public resume() { + if (this.isPaused && this.currentBuffer) { + this.startPlayback(this.currentBuffer, this.pauseTime) + } + } + + public stopAudio() { + if (this.currentSource) { + this.currentSource.stop() + this.currentSource = null + this.currentBuffer = null // Clear the current buffer + this.isPaused = false + this.pauseTime = 0 + } + } +} + +// Usage example: +const audioManager = new AudioManager() +const audioUrl = 'https://example.com/path/to/your/audio/file.mp3' + +// To play the audio +audioManager.playAudio(audioUrl) + +// To pause the audio +audioManager.pause() + +// To resume the audio +audioManager.resume() diff --git a/client/src/lib/Dashboard/MediaPlayer/songList.ts b/client/src/lib/Dashboard/MediaPlayer/songList.ts new file mode 100644 index 0000000..d0a0dd6 --- /dev/null +++ b/client/src/lib/Dashboard/MediaPlayer/songList.ts @@ -0,0 +1,14 @@ +export const songList: { [key: string]: SongData } = { + 'danger-zone': { + title: 'Danger Zone', + artist: 'Kenny Loggins', + src: '/static/songs/danger-zone/audio.mp3', + coverImg: '/static/songs/danger-zone/cover.png', + }, + 'monko-zone': { + title: 'Danger Zone', + artist: 'Kenny Loggins', + src: '/static/songs/danger-zone/audio.mp3', + coverImg: '/static/songs/danger-zone/cover.png', + }, +} diff --git a/client/src/lib/stores/musicStore.ts b/client/src/lib/stores/musicStore.ts new file mode 100644 index 0000000..b608c65 --- /dev/null +++ b/client/src/lib/stores/musicStore.ts @@ -0,0 +1,60 @@ +import { writable } from 'svelte/store' + +interface MusicQueue { + queue: string[] + currentIndex: number + playing: boolean +} + +function createMusicStore() { + const { subscribe, set, update } = writable({ + queue: [], + currentIndex: 0, + playing: false, + }) + + return { + subscribe, + push: (songSlug: string) => + update(store => { + store.queue.push(songSlug) + return store + }), + skip: () => + update(store => { + let next = store.queue[store.currentIndex + 1] + + if (next !== undefined) { + store.currentIndex++ + } + return store + }), + setCurrent: (songSlug: string) => + update(store => { + store.currentIndex = store.queue.length + store.queue.push(songSlug) + return store + }), + queueNext: (songSlug: string) => + update(store => { + store.queue.splice(store.currentIndex + 1, 0, songSlug) + return store + }), + pause: update(store => { + store.playing = false + return store + }), + play: update(store => { + store.playing = true + return store + }), + toggle: () => + update(store => { + store.playing = !store.playing + return store + }), + reset: () => set({ queue: [], currentIndex: 0, playing: false }), + } +} + +export const musicStore = createMusicStore()