fix music player

This commit is contained in:
Youwen Wu 2024-02-22 17:16:25 -08:00
parent f5f7371595
commit 1ed960d8e8
8 changed files with 123 additions and 139 deletions

View file

@ -7,8 +7,7 @@
let { title, artist, coverImg } = song let { title, artist, coverImg } = song
const handlePlay = () => { const handlePlay = () => {
musicStore.setCurrent(slug) musicStore.play(slug)
!$musicStore.playing && musicStore.toggle()
} }
const handleQueueNext = () => { const handleQueueNext = () => {
@ -33,7 +32,6 @@
<button <button
class="mt-2 hover:brightness-75" class="mt-2 hover:brightness-75"
on:click={!nowPlaying ? handlePlay : () => {}} on:click={!nowPlaying ? handlePlay : () => {}}
class:invisible={nowPlaying}
> >
<span class="material-symbols-outlined icon fill">play_arrow</span> <span class="material-symbols-outlined icon fill">play_arrow</span>
</button> </button>

View file

@ -3,10 +3,10 @@
import Speedometer from './Speedometer.svelte' import Speedometer from './Speedometer.svelte'
import SpeedLimit from './SpeedLimit.svelte' import SpeedLimit from './SpeedLimit.svelte'
import MediaDisplay from './MediaPlayer/MediaDisplay.svelte' import MediaDisplay from './MediaPlayer/MediaDisplay.svelte'
import Player from './MediaPlayer/Player.svelte' // import Player from './MediaPlayer/Player.svelte'
</script> </script>
<Player /> <!-- <Player /> -->
<div class="mt-2"> <div class="mt-2">
<div class="px-5"> <div class="px-5">

View file

@ -1,9 +1,16 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte' import { createEventDispatcher } from 'svelte'
import { musicStore } from '../../stores/musicStore'
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
export let playing = false export let playing = false
let startTime = Date.now()
$: if (playing) {
startTime = Date.now()
}
</script> </script>
<div class="my-auto flex gap-4 mr-4"> <div class="my-auto flex gap-4 mr-4">

View file

@ -2,6 +2,9 @@
import Controls from './Controls.svelte' import Controls from './Controls.svelte'
import { musicStore } from '../../stores/musicStore' import { musicStore } from '../../stores/musicStore'
import { songList } from './songList' import { songList } from './songList'
import { fly } from 'svelte/transition'
import { quintInOut } from 'svelte/easing'
import { onMount } from 'svelte'
$: currentSong = $musicStore.queue[$musicStore.currentIndex] $: currentSong = $musicStore.queue[$musicStore.currentIndex]
$: songData = songList[currentSong] $: songData = songList[currentSong]
@ -17,10 +20,19 @@
const toggle = () => { const toggle = () => {
musicStore.toggle() musicStore.toggle()
} }
onMount(() => {
document.addEventListener('ended', () => {
musicStore.skip()
})
})
</script> </script>
{#if songData} {#if songData}
<div class="rounded-t-lg bg-neutral-800 px-4 py-2 h-24 flex justify-between"> <div
class="rounded-t-lg bg-neutral-800 px-4 py-2 h-24 flex justify-between"
transition:fly={{ y: 100, duration: 300, easing: quintInOut }}
>
<div class="flex gap-6"> <div class="flex gap-6">
<div class="aspect-square"> <div class="aspect-square">
<img <img

View file

@ -1,26 +0,0 @@
<script lang="ts">
import { AudioManager } from './audioManager'
import { musicStore } from '../../stores/musicStore'
import { songList } from './songList'
const audioManager = new AudioManager()
$: currentSong = songList[$musicStore.queue[$musicStore.currentIndex]]
let src: string = ''
$: {
if (currentSong) src = currentSong.src
console.log(currentSong)
}
$: {
if (src !== '' && $musicStore.playing) {
audioManager.playAudio(src)
console.log(src)
} else if (!$musicStore.playing) {
console.log('stopping')
audioManager.stopAudio()
}
console.log($musicStore.queue)
console.log($musicStore.currentIndex)
}
</script>

View file

@ -1,88 +0,0 @@
export class AudioManager {
private audioContext: AudioContext | null = null
private currentSource: AudioBufferSourceNode | null = null
private currentToken: number = 0 // Unique token for each play request
private isPlaying: boolean = false // Track whether audio is playing
constructor() {
this.initAudioContext()
}
private async initAudioContext() {
if (!this.audioContext) {
this.audioContext = new AudioContext()
}
}
public async playAudio(url: string): Promise<void> {
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()
// 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
}
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
}
}
}
public stopAudio() {
if (this.currentSource) {
this.currentSource.stop()
this.currentSource = null
this.isPlaying = false // Update the playing status
}
}
// Method to check if audio is currently playing
public isAudioPlaying(): boolean {
return this.isPlaying
}
}
// Usage example
const audioManager = new AudioManager()
console.log(audioManager.isAudioPlaying()) // False, initially
// 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
})
// 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

@ -0,0 +1,51 @@
export class AudioPlayer {
private audio: HTMLAudioElement
private currentUrl: string | null = null
private isPlaying: boolean = false
public length: number = 0
constructor() {
this.audio = new Audio()
this.audio.addEventListener('ended', () => {
this.isPlaying = false
const ended = new CustomEvent('ended', {
detail: this.currentUrl,
bubbles: true,
})
document.dispatchEvent(ended)
})
this.audio.onloadedmetadata = () => {
this.length = this.audio.duration
}
}
// Getter for playing state
get playing(): boolean {
return this.isPlaying
}
// Method to play audio from a URL
play(url: string): void {
this.audio.src = url
this.currentUrl = url
this.audio.play().catch(e => console.error('Error playing audio:', e))
this.isPlaying = true
}
// Method to pause audio playback
pause(): void {
if (this.isPlaying) {
this.audio.pause()
this.isPlaying = false
}
}
// Method to unpause (resume) audio playback
unpause(): void {
if (!this.isPlaying && this.currentUrl) {
this.audio.play().catch(e => console.error('Error playing audio:', e))
this.isPlaying = true
}
}
}

View file

@ -1,8 +1,11 @@
import { writable } from 'svelte/store' import { writable } from 'svelte/store'
import { songList } from '../Dashboard/MediaPlayer/songList'
import { AudioPlayer } from './audioManager'
interface MusicQueue { interface MusicQueue {
queue: string[] queue: string[]
currentIndex: number currentIndex: number
player: AudioPlayer
playing: boolean playing: boolean
} }
@ -10,11 +13,27 @@ function createMusicStore() {
const { subscribe, set, update } = writable<MusicQueue>({ const { subscribe, set, update } = writable<MusicQueue>({
queue: [], queue: [],
currentIndex: 0, currentIndex: 0,
player: new AudioPlayer(),
playing: false, playing: false,
}) })
return { return {
subscribe, subscribe,
play: (songSlug: string) =>
update(store => {
if (store.queue.length >= 1) {
store.queue.splice(store.currentIndex + 1, 0, songSlug)
store.currentIndex++
store.player.play(songList[songSlug].src)
} else {
store.queue[0] = songSlug
store.currentIndex = 0
store.player.play(songList[songSlug].src)
}
store.playing = true
return store
}),
push: (songSlug: string) => push: (songSlug: string) =>
update(store => { update(store => {
store.queue.push(songSlug) store.queue.push(songSlug)
@ -22,43 +41,54 @@ function createMusicStore() {
}), }),
skip: () => skip: () =>
update(store => { update(store => {
let next = store.queue[store.currentIndex + 1] if (store.currentIndex < store.queue.length - 1) {
let next = store.queue[store.currentIndex + 1]
if (next !== undefined) {
store.currentIndex++ store.currentIndex++
store.playing = true
store.player.play(songList[next].src)
} }
return store return store
}), }),
setCurrent: (songSlug: string) =>
update(store => {
store.currentIndex = store.queue.length
store.queue.push(songSlug)
return store
}),
queueNext: (songSlug: string) => queueNext: (songSlug: string) =>
update(store => { update(store => {
store.queue.splice(store.currentIndex + 1, 0, songSlug) store.queue.splice(store.currentIndex + 1, 0, songSlug)
return store return store
}), }),
pause: update(store => {
store.playing = false
return store
}),
play: update(store => {
store.playing = true
return store
}),
toggle: () => toggle: () =>
update(store => { update(store => {
store.playing = !store.playing if (store.player.playing) {
store.playing = false
store.player.pause()
} else {
store.playing = true
store.player.unpause()
}
return store return store
}), }),
pause: update(store => {
store.player.pause()
return store
}),
unpause: update(store => {
store.player.unpause()
return store
}),
rewind: () => rewind: () =>
update(store => { update(store => {
store.currentIndex-- store.currentIndex--
store.player.play(songList[store.queue[store.currentIndex]].src)
store.playing = true
return store return store
}), }),
reset: () => set({ queue: [], currentIndex: 0, playing: false }), reset: () =>
set({
queue: [],
currentIndex: 0,
player: new AudioPlayer(),
playing: false,
}),
} }
} }