Files
Lazuli/src/lib/mediaPlayer.svelte
Nicholas Tamassia b2790a7151 first commit
2023-10-09 17:49:46 -04:00

188 lines
7.4 KiB
Svelte

<script>
export let currentlyPlaying
export let playlistItems
import { buildAudioEndpoint } from '$lib/audio-manager.js'
import { onMount } from 'svelte'
import { createEventDispatcher } from 'svelte'
import { generateURL } from '$lib/Jellyfin-api.js'
import { fade } from 'svelte/transition'
import ListItem from '$lib/listItem.svelte'
import MediaControl from '$lib/mediaControl.svelte'
const dispatch = createEventDispatcher()
$: currentlyPlayingImageId = 'Primary' in currentlyPlaying.ImageTags ? currentlyPlaying.Id : currentlyPlaying.AlbumId
$: currentlyPlayingImage = generateURL({
type: 'Image',
pathParams: { id: currentlyPlayingImageId },
})
$: audioEndpoint = buildAudioEndpoint(currentlyPlaying.Id, 'default')
let audio
let audioVolume = 0.1
let progressBar
let playingState = 'paused'
onMount(() => {
audio = document.getElementById('audio')
audio.volume = audioVolume
progressBar = document.getElementById('progress-bar')
if ('mediaSession' in navigator) {
navigator.mediaSession.setActionHandler('play', () => playSong())
navigator.mediaSession.setActionHandler('pause', () => pauseSong())
navigator.mediaSession.setActionHandler('stop', () => closeMediaPlayer())
navigator.mediaSession.setActionHandler('nexttrack', () => playNext())
navigator.mediaSession.setActionHandler('previoustrack', () => playPrevious())
}
})
$: updateAudioSrc(audioEndpoint)
const updateAudioSrc = (newAudioEndpoint) => {
if (!audio) {
return onMount(() => {
audio.src = newAudioEndpoint
playSong()
})
}
audio.src = newAudioEndpoint
playSong()
}
$: updateMediaSession(currentlyPlaying)
const updateMediaSession = (media) => {
if ('mediaSession' in navigator) {
navigator.mediaSession.metadata = new MediaMetadata({
title: media.Name,
artist: media.Artists.join(' / '),
album: media.Album,
artwork: [
{
src: currentlyPlayingImage + '&width=96&height=96',
sizes: '96x96',
type: 'image/png',
},
{
src: currentlyPlayingImage + '&width=128&height=128',
sizes: '128x128',
type: 'image/png',
},
{
src: currentlyPlayingImage + '&width=192&height=192',
sizes: '192x192',
type: 'image/png',
},
{
src: currentlyPlayingImage + '&width=256&height=256',
sizes: '256x256',
type: 'image/png',
},
{
src: currentlyPlayingImage + '&width=384&height=384',
sizes: '384x384',
type: 'image/png',
},
{
src: currentlyPlayingImage + '&width=512&height=512',
sizes: '512x512',
type: 'image/png',
},
],
})
}
}
const playSong = () => {
audio.play()
playingState = 'playing'
}
const pauseSong = () => {
audio.pause()
playingState = 'paused'
}
const playNext = () => {
let nextSong = playlistItems[playlistItems.indexOf(currentlyPlaying) + 1]
if (nextSong) {
dispatch('startPlayback', {
item: nextSong,
})
}
}
const playPrevious = () => {
let previousSong = playlistItems[playlistItems.indexOf(currentlyPlaying) - 1]
if (previousSong) {
dispatch('startPlayback', {
item: previousSong,
})
}
}
const updateProgressBar = (event) => {
if (event.target.currentTime) {
let currentPercentage = event.target.currentTime / event.target.duration
if (document.activeElement !== progressBar) {
progressBar.value = currentPercentage
}
}
}
const updateAudioTime = () => {
let audioTimeStamp = audio.duration * progressBar.value
audio.currentTime = audioTimeStamp
}
const closeMediaPlayer = () => {
dispatch('closeMediaPlayer')
}
</script>
<div id="layout" class="grid h-full grid-cols-1 grid-rows-[3fr_2fr] lg:grid-cols-[2fr_1fr] lg:grid-rows-1">
<div class="relative h-full overflow-hidden">
<div class="absolute z-0 flex h-full w-full items-center justify-items-center bg-neutral-900">
<div class="absolute z-10 h-full w-full backdrop-blur-3xl"></div>
{#key currentlyPlaying}
<img in:fade src={currentlyPlayingImage} alt="" class="absolute h-full w-full object-cover brightness-[70%]" />
{/key}
</div>
<div class="absolute grid h-full w-full grid-rows-[auto_8rem_3rem_6rem] justify-items-center p-8">
{#key currentlyPlaying}
<img in:fade src={currentlyPlayingImage} alt="" class="h-full min-h-[8rem] overflow-hidden rounded-xl object-contain p-2" />
{/key}
<div in:fade class="flex flex-col items-center justify-center gap-1 px-8 text-center font-notoSans">
{#key currentlyPlaying}
<span class="text-xl text-neutral-500">{currentlyPlaying.Album}</span>
<span class="text-3xl text-neutral-300">{currentlyPlaying.Name}</span>
<span class="text-xl text-neutral-500">{currentlyPlaying.Artists.join(' / ')}</span>
{/key}
</div>
<input id="progress-bar" on:mouseup={updateAudioTime} type="range" value="0" min="0" max="1" step="any" class="w-[90%] cursor-pointer rounded-lg bg-gray-400" />
<div class="flex h-full w-11/12 justify-around overflow-hidden">
<MediaControl type={'previoustrack'} on:mediaControlEvent={() => playPrevious()} />
<MediaControl type={playingState} on:mediaControlEvent={(event) => (event.detail.eventType === 'playing' ? pauseSong() : playSong())} />
<MediaControl type={'stop'} on:mediaControlEvent={() => closeMediaPlayer()} />
<MediaControl type={'nexttrack'} on:mediaControlEvent={() => playNext()} />
</div>
</div>
</div>
<div class="no-scrollbar flex w-full flex-col items-center divide-y-[1px] divide-[#353535] overflow-y-scroll bg-neutral-900 p-4">
{#each playlistItems as item}
{#if item == currentlyPlaying}
<div class="flex w-full bg-neutral-500">
<ListItem {item} />
</div>
{:else}
<div class="flex w-full hover:bg-neutral-500">
<ListItem {item} on:startPlayback />
</div>
{/if}
{/each}
</div>
<audio id="audio" on:ended={playNext} on:timeupdate={updateProgressBar} crossorigin="anonymous" class="hidden" />
</div>