lydstyrke/src/routes/+page.svelte

261 lines
8.1 KiB
Svelte
Raw Normal View History

<svelte:options runes={true} />
<script lang="ts">
import {OpenSubsonic} from "$lib/opensubsonic";
import {onMount} from "svelte";
import QueueFrame from "$lib/components/custom/QueueFrame.svelte";
2024-04-21 21:39:56 +00:00
//import PlayerFrame from "$lib/components/custom/PlayerFrame.svelte";
import {Button} from "$lib/components/ui/button";
import {browser} from "$app/environment";
//let audioSource: HTMLAudioElement = $state(new Audio());
//let paused: boolean = $derived(audioSource.paused);
//let volume: number = $derived(audioSource.volume);
2024-04-22 18:06:25 +00:00
enum PlaybackMode {
Linear,
LoopOne,
LoopQueue,
}
class PlaybackState {
values = {
[PlaybackMode.Linear]: "1",
[PlaybackMode.LoopOne]: "0",
[PlaybackMode.LoopQueue]: "00",
}
current: PlaybackMode = $state(PlaybackMode.Linear);
next = function() {
console.log(this.current)
if (this.current == PlaybackMode.LoopQueue) {
this.current = -1;
}
return this.values[++this.current];
}
2024-04-22 18:06:25 +00:00
prev = function() {
if (this.current == PlaybackMode.Linear) {
this.current = this.values.length;
}
return this.values[--this.current];
}
get = function() {
return this.values[this.current];
}
}
let source: HTMLAudioElement = $state();
2024-04-22 18:06:25 +00:00
let mode = $state(new PlaybackState());
let title = $state("");
2024-04-22 18:07:03 +00:00
let artist = $state("");
let queueIndex = $state(0);
let currentSong = $state({
2024-04-22 18:05:27 +00:00
data: {
title,
2024-04-22 18:07:03 +00:00
artist
2024-04-22 18:05:27 +00:00
},
2024-04-22 18:07:41 +00:00
source,
queueIndex
});
2024-04-21 21:39:56 +00:00
let isPaused = $state(true);
let volume = $state(0.2);
let progress = $state(0);
let duration = $state(0);
let queue: Array<unknown> = $state([]);
async function fetchQueue() {
const data = await OpenSubsonic.get("getPlayQueue");
if (data) {
queue = [];
queue = queue.concat(data.playQueue.entry);
}
}
async function fetchNowPlaying() {
const data = await OpenSubsonic.get("getNowPlaying");
let foundInNowPlaying = false;
if (data && data.nowPlaying.entry) {
data.nowPlaying.entry.forEach((entry) => {
if (entry.username == OpenSubsonic.username) {
title = entry.title;
newSong(entry);
foundInNowPlaying = true;
}
})
}
if (!foundInNowPlaying && queue.length != 0) {
newSong(queue[0])
currentSong.queueIndex = 0;
}
}
async function saveQueue() {
let songs = [];
queue.forEach((song, idx) => {
if (idx === 0) {
songs.push({parameter: "current", value: song.id})
songs.push({parameter: "id", value: song.id})
// Add Progress within current song
}
else {
songs.push({parameter: "id", value: song.id})
}
})
const data = await OpenSubsonic.get("savePlayQueue", songs);
if (data) {
await fetchQueue();
}
}
function removeSongFromQueue(idx: number) {
if (idx > -1) {
queue.splice(idx, 1);
}
}
function playSong(song: unknown, songIndex: number) {
//const chosenSong = queue[songIndex];
pause();
newSong(song);
currentSong.queueIndex = songIndex;
play();
}
2024-04-22 18:07:03 +00:00
function newSong(song: unknown) {
let parameters = [
{ parameter: "id", value: song.id },
//{ parameter: "maxBitRate", value: } // TODO
//{ parameter: "format", value: } // TODO
//{ parameter: "timeOffset", value: } // TODO? Only Video related
//{ parameter: "size", value: } // TODO? Only Video related
{ parameter: "estimateContentLength", value: "true" },
//{ parameter: "converted", value: } // TODO? Only Video related
];
let url = OpenSubsonic.getApiUrl("stream", parameters);
2024-04-22 18:07:41 +00:00
currentSong.source = new Audio(url); // Assign new URL
2024-04-22 18:07:03 +00:00
currentSong.data = song;
2024-04-21 21:39:56 +00:00
// Reassign Event Handlers
2024-04-22 18:07:41 +00:00
currentSong.source.onloadedmetadata = () => {
duration = currentSong.source.duration;
2024-04-21 21:39:56 +00:00
};
2024-04-22 18:08:08 +00:00
currentSong.source.onplay = () => {
currentSong.source.volume = volume;
isPaused = currentSong.source.paused;
let time: number = Date.now();
let parameters = [
{ parameter: "id", value: song.id },
{ parameter: "time", value: time},
{ parameter: "submission", value: false}
];
console.warn("Scrobble is disabled");
OpenSubsonic.get("scrobble", parameters);
2024-04-21 21:39:56 +00:00
}
2024-04-22 18:07:41 +00:00
currentSong.source.onpause = () => {
isPaused = currentSong.source.paused;
2024-04-21 21:39:56 +00:00
}
2024-04-22 18:07:41 +00:00
currentSong.source.ontimeupdate = () => {
progress = currentSong.source.currentTime;
2024-04-21 21:39:56 +00:00
}
2024-04-22 18:06:25 +00:00
currentSong.source.onended = () => {
if (mode.current == PlaybackMode.LoopOne) {
currentSong.source.play();
}
}
2024-04-22 18:07:41 +00:00
currentSong.source.load();
2024-04-21 21:39:56 +00:00
}
function play() {
2024-04-22 18:07:41 +00:00
currentSong.source.play().catch(() => {});
}
2024-04-21 21:39:56 +00:00
function pause() {
2024-04-22 18:07:41 +00:00
currentSong.source.pause();
}
2024-04-22 18:04:12 +00:00
function changeVolume(change: number) {
if (volume + change > 1) {
volume = 1;
}
else if (volume + change < 0) {
volume = 0;
}
else {
volume = toFixedNumber(volume + change, 2, 10) ;
}
if (currentSong.source) {
currentSong.source.volume = volume;
2024-04-21 21:39:56 +00:00
}
2024-04-22 18:07:41 +00:00
}
2024-04-21 21:39:56 +00:00
function progressPercent() {
return progress / duration * 100 || 0;
}
function displayTime(rawSeconds: number) {
const intSeconds = rawSeconds.toFixed(0);
const seconds = intSeconds % 60;
2024-04-22 18:02:47 +00:00
const minutes = Math.floor((intSeconds / 60)) % 60;
const hours = Math.floor((intSeconds / 3600));
2024-04-21 21:39:56 +00:00
if (hours == 0) {
return `${minutes.toString()}:${seconds.toString().padStart(2, 0)}`
}
else {
return `${hours}:${minutes.toString().padStart(2, 0)}:${seconds.toString().padStart(2, 0)}`
}
}
onMount(() => {
fetchQueue().then(() => {
fetchNowPlaying();
});
2024-04-22 18:07:41 +00:00
currentSong.source = new Audio();
})
</script>
2024-04-22 18:05:27 +00:00
<svelte:head>
<title>Lytter - {currentSong.data.title}({currentSong.data.artist})</title>
<meta name="robots" content="noindex nofollow" />
</svelte:head>
<div class="border border-2 flex-1 grid grid-cols-5">
<div class="border border-2 col-span-1">
<h1>Left Sidebar</h1>
</div>
<div class="border border-2 col-span-3">
<h1>Center</h1>
2024-04-21 21:39:56 +00:00
<button onclick={() => volume += 0.1}>Louder</button>
<button onclick={() => volume -= 0.1}>Quieter</button>
</div>
<QueueFrame {queue} {fetchQueue} {saveQueue} {removeSongFromQueue} {playSong} currentIndex={currentSong.queueIndex} />
</div>
<div class="border border-2 min-h-24 h-24">
2024-04-21 21:39:56 +00:00
<div class="flex flex-row gap-2">
2024-04-22 18:05:27 +00:00
<p class="border p-2">Song: {currentSong.data.title}</p>
2024-04-21 21:39:56 +00:00
<p class="border p-2">Volume: {volume}</p>
<p class="border p-2">{displayTime(progress)}/{displayTime(duration)}</p>
<p class="border p-2">{progressPercent().toFixed(2)}%</p>
2024-04-22 18:04:12 +00:00
<Button class="border p-2" onclick={() => changeVolume(-0.05)}>-</Button>
<Button class="border p-2" onclick={() => changeVolume(0.05)}>+</Button>
2024-04-22 18:07:52 +00:00
<Button class="border p-2" onclick={() => currentSong.source.currentTime -= 5}>{"<<"}</Button>
2024-04-21 21:39:56 +00:00
{#if isPaused}
<Button onclick={play}>Play</Button>
{:else}
<Button onclick={pause}>Pause</Button>
{/if}
2024-04-22 18:07:52 +00:00
<Button class="border p-2" onclick={() => currentSong.source.currentTime += 5}>{">>"}</Button>
2024-04-22 18:06:25 +00:00
<Button class="border p-2" onclick={() => mode.next()}>{mode.get()}</Button>
2024-04-21 21:39:56 +00:00
</div>
</div>