feat: upgrade media player

This commit is contained in:
Youwen Wu 2024-02-21 23:59:30 -08:00
parent 6cb1c9d29c
commit f5f7371595
13 changed files with 89 additions and 68 deletions

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
app/static/songs/** filter=lfs diff=lfs merge=lfs -text

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 130 B

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1ffed6b030e6efac7b842aa1d04fe142ae67cc42676ef0863fd98c986e45167c
size 4272439

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:577320e227fedd883db36d7e1fd0399b4f7bae898a9c927de4b347c59f54917f
size 36462

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ed6986a60bd281410b2e47a21928280ef1b4ef8ea0015f046886e0c1ca38f1f8
size 3780869

View file

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:60bb7c64e60ec007b423dcd427cb7c7cb8a5e075b504e3168b5a63a0d4b6cc7e
size 79875

View file

@ -8,6 +8,7 @@
const handlePlay = () => {
musicStore.setCurrent(slug)
!$musicStore.playing && musicStore.toggle()
}
const handleQueueNext = () => {
@ -17,6 +18,8 @@
const handleQueueLast = () => {
musicStore.push(slug)
}
$: nowPlaying = slug === $musicStore.queue[$musicStore.currentIndex]
</script>
<div
@ -27,7 +30,11 @@
<p class="text-xl text-slate-400">{artist}</p>
<div class="flex justify-center">
<div class="my-auto flex gap-4">
<button class="mt-2 hover:brightness-75" on:click={handlePlay}>
<button
class="mt-2 hover:brightness-75"
on:click={!nowPlaying ? handlePlay : () => {}}
class:invisible={nowPlaying}
>
<span class="material-symbols-outlined icon fill">play_arrow</span>
</button>
<button class="mt-2 hover:brightness-75" on:click={handleQueueNext}>

View file

@ -7,10 +7,7 @@
</script>
<div class="my-auto flex gap-4 mr-4">
<button
class="mt-2 hover:brightness-75"
on:click={() => dispatch('previous')}
>
<button class="mt-2 hover:brightness-75" on:click={() => dispatch('rewind')}>
<span class="material-symbols-outlined icon">skip_previous</span>
</button>
<button class="mt-2 hover:brightness-75" on:click={() => dispatch('toggle')}>

View file

@ -10,7 +10,9 @@
musicStore.skip()
}
const handleRewind = () => {}
const rewind = () => {
musicStore.rewind()
}
const toggle = () => {
musicStore.toggle()
@ -32,6 +34,11 @@
<p class="text-lg text-slate-400">{songData.artist}</p>
</div>
</div>
<Controls on:skip={skip} on:toggle={toggle} playing={$musicStore.playing} />
<Controls
on:skip={skip}
on:toggle={toggle}
on:rewind={rewind}
playing={$musicStore.playing}
/>
</div>
{/if}

View file

@ -1,11 +1,8 @@
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
private isPlaying: boolean = false // Track whether audio is playing
constructor() {
this.initAudioContext()
@ -18,13 +15,6 @@ export class AudioManager {
}
public async playAudio(url: string): Promise<void> {
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()
@ -34,69 +24,65 @@ export class AudioManager {
try {
const response = await fetch(url)
const arrayBuffer = await response.arrayBuffer()
// Before decoding, check if the token has changed
if (this.currentToken !== playToken) {
return // Abort this operation if a new play request has been made
}
const audioBuffer = await this.audioContext.decodeAudioData(arrayBuffer)
const source = this.audioContext.createBufferSource()
source.buffer = audioBuffer
source.connect(this.audioContext.destination)
// Again check the token before starting playback
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
source.start(0)
this.currentSource = source
this.isPlaying = true // Update the playing status
// Set the playing status to false when the audio ends
source.onended = () => {
if (this.currentToken === playToken) {
// Check to avoid race conditions
this.isPlaying = false
}
}
} catch (error) {
console.error('Error playing audio:', error)
this.isPlaying = false // Ensure status is accurate in case of 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
}
this.isPlaying = false // Update the playing status
}
}
// Usage example:
// Method to check if audio is currently playing
public isAudioPlaying(): boolean {
return this.isPlaying
}
}
// Usage example
const audioManager = new AudioManager()
const audioUrl = 'https://example.com/path/to/your/audio/file.mp3'
console.log(audioManager.isAudioPlaying()) // False, initially
// To play the audio
audioManager.playAudio(audioUrl)
// To play audio
audioManager
.playAudio('https://example.com/path/to/your/audio/file.mp3')
.then(() => {
console.log(audioManager.isAudioPlaying()) // Should log true when audio starts playing
})
// To pause the audio
audioManager.pause()
// To resume the audio
audioManager.resume()
// Later, you can check if audio is still playing
setTimeout(() => {
console.log(audioManager.isAudioPlaying()) // The result depends on the audio length and timing
}, 1000)

View file

@ -5,10 +5,16 @@ export const songList: { [key: string]: SongData } = {
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',
'deja-vu': {
title: 'Deja Vu',
artist: 'Initial D',
src: '/static/songs/deja-vu/audio.m4a',
coverImg: '/static/songs/deja-vu/cover.jpg',
},
'Xenogenesis': {
title: 'Xenogenesis',
artist: 'TheFatRat',
src: '/static/songs/xenogenesis/audio.m4a',
coverImg: '/static/songs/xenogenesis/cover.jpg',
},
}

View file

@ -53,6 +53,11 @@ function createMusicStore() {
store.playing = !store.playing
return store
}),
rewind: () =>
update(store => {
store.currentIndex--
return store
}),
reset: () => set({ queue: [], currentIndex: 0, playing: false }),
}
}