fix music player
This commit is contained in:
parent
f5f7371595
commit
1ed960d8e8
8 changed files with 123 additions and 139 deletions
|
@ -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>
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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>
|
|
|
@ -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)
|
|
51
client/src/lib/stores/audioManager.ts
Normal file
51
client/src/lib/stores/audioManager.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue