diff --git a/src/lib/components/custom/PlayerControls.svelte b/src/lib/components/custom/PlayerControls.svelte
index ef40d6b..fcaa5d9 100644
--- a/src/lib/components/custom/PlayerControls.svelte
+++ b/src/lib/components/custom/PlayerControls.svelte
@@ -3,10 +3,43 @@
-
-
view=Views.Artists}>Artists
+
viewState.setMode(View.Artists)}>Artists
Songs
Radios
Shares
diff --git a/src/lib/components/custom/QueueFrame.svelte b/src/lib/components/custom/QueueFrame.svelte
index f2cd1da..d780db4 100644
--- a/src/lib/components/custom/QueueFrame.svelte
+++ b/src/lib/components/custom/QueueFrame.svelte
@@ -5,28 +5,54 @@
import {Button} from "$lib/components/ui/button";
import {timeFormat} from "$lib/time-format";
import LoadingSpinner from "$lib/components/custom/LoadingSpinner.svelte";
+ import {queueState} from "$lib/states/play-queue.svelte";
+ import {playbackState} from "$lib/states/playback-state.svelte";
- let { queue = [], fetchQueue, saveQueue, removeSongFromQueue, playSong, currentIndex } = $props();
let loading = $state(false);
async function innerFetchQueue() {
loading = true;
- await fetchQueue()
+ await queueState.getPlayQueue()
await new Promise(resolve => setTimeout(resolve, 100));
loading = false;
}
+
+ function removeSongFromQueue(idx: number) {
+ if (idx > -1) {
+ queueState.queue.splice(idx, 1);
+ }
+ }
+
+ function playSong( songIndex: number) {
+ queueState.setSong(songIndex);
+ playbackState.pause();
+ playbackState.newSong(queueState.getSong());
+ playbackState.play();
+ }
+{#snippet songEntry(song, idx)}
+
playSong(idx)}>{song.artist} - {song.title} ({timeFormat(song.duration)})
+
removeSongFromQueue(idx)}>X
+{/snippet}
+
{#snippet playerQueue()}
- {#each queue as song, idx}
- {#if currentIndex == idx}
-
playSong(song, idx)}>{song.artist} - {song.title} ({timeFormat(song.duration)}) removeSongFromQueue(idx)}>X
+ {#each queueState.queue as song, idx}
+ {#if idx < queueState.currentIndex}
+
+ {@render songEntry(song, idx)}
+
+ {:else if idx === queueState.currentIndex}
+
+ {@render songEntry(song, idx)}
+
{:else}
-
playSong(song, idx)}>{song.artist} - {song.title} ({timeFormat(song.duration)}) removeSongFromQueue(idx)}>X
+
+ {@render songEntry(song, idx)}
+
{/if}
{/each}
-
-
queue.push(queue[0])}>Add
+
queueState.queue.push(queueState.queue[0])}>Add
{/snippet}
@@ -34,7 +60,7 @@
Current Queue
Fetch
-
Upload
+
queueState.saveQueue()}>Upload
{#if loading}
diff --git a/src/lib/components/custom/Views/AlbumView.svelte b/src/lib/components/custom/Views/AlbumsView.svelte
similarity index 78%
rename from src/lib/components/custom/Views/AlbumView.svelte
rename to src/lib/components/custom/Views/AlbumsView.svelte
index 0b295ce..68261dd 100644
--- a/src/lib/components/custom/Views/AlbumView.svelte
+++ b/src/lib/components/custom/Views/AlbumsView.svelte
@@ -1,16 +1,14 @@
diff --git a/src/lib/components/custom/Views/PlaylistView.svelte b/src/lib/components/custom/Views/PlaylistView.svelte
index e914080..ff75a51 100644
--- a/src/lib/components/custom/Views/PlaylistView.svelte
+++ b/src/lib/components/custom/Views/PlaylistView.svelte
@@ -3,9 +3,9 @@
diff --git a/src/lib/components/custom/Views/views.svelte.ts b/src/lib/components/custom/Views/views.svelte.ts
index 2c5a765..70b58cc 100644
--- a/src/lib/components/custom/Views/views.svelte.ts
+++ b/src/lib/components/custom/Views/views.svelte.ts
@@ -1,4 +1,4 @@
-export enum AlbumViews {
+export enum AlbumView {
All = "alphabeticalByName",
Random = "random",
Favourites = "starred",
@@ -8,10 +8,13 @@ export enum AlbumViews {
MostPlayed = "frequent"
}
-export enum Views {
+export enum View {
Albums = "Albums",
Artists = "Artists",
Songs = "Songs",
Radios = "Radios",
- Shares = "Shares"
+ Shares = "Shares",
+ Playlist = "Playlist",
+ Artist = "Artist",
+ Album = "Album",
}
\ No newline at end of file
diff --git a/src/lib/formatting.ts b/src/lib/formatting.ts
new file mode 100644
index 0000000..1f8ed19
--- /dev/null
+++ b/src/lib/formatting.ts
@@ -0,0 +1,4 @@
+export function toFixedNumber(num: number, digits: number, base: number): number{
+ const pow: number = Math.pow(base ?? 10, digits);
+ return Math.round(num*pow) / pow;
+}
\ No newline at end of file
diff --git a/src/lib/states/play-queue.svelte.ts b/src/lib/states/play-queue.svelte.ts
new file mode 100644
index 0000000..acaef3b
--- /dev/null
+++ b/src/lib/states/play-queue.svelte.ts
@@ -0,0 +1,134 @@
+import {
+ type GetPlayQueueResponse, type NowPlayingEntry,
+ type NowPlayingResponse,
+ OpenSubsonic,
+ type Parameter,
+ type Song
+} from "$lib/opensubsonic";
+import {playbackState} from "$lib/states/playback-state.svelte";
+
+interface QueueState {
+ queue: Array
,
+ currentIndex: number,
+ nextSong: () => Song,
+ setSong: (index: number) => void,
+ getSong: () => Song,
+ firstSong: () => Song,
+ findSong: (song: Song) => {found: boolean, index: number},
+ addSong: (song: Song) => void,
+ getPlayQueue: (addNowPlaying: boolean) => Promise,
+ saveQueue: () => Promise,
+}
+
+export const queueState: QueueState = $state({
+ queue: new Array(),
+ currentIndex: 0,
+ nextSong(): Song {
+ this.currentIndex += 1;
+ return this.queue[this.currentIndex];
+ },
+ setSong(index: number): void {
+ this.currentIndex = index;
+ },
+ getSong(): Song {
+ return this.queue[this.currentIndex];
+ },
+ firstSong(): Song {
+ this.currentIndex = 0;
+ return this.queue[this.currentIndex];
+ },
+ findSong(searchSong: Song): {found: boolean, index: number} {
+ const data = {
+ found: false,
+ index: 0,
+ }
+ for (const [index, song] of this.queue.entries()) {
+ if (song.id === searchSong.id) {
+ data.found = true;
+ data.index = index;
+ break;
+ }
+ }
+ return data;
+ },
+ addSong(newSong: Song): void {
+ this.queue.push(newSong);
+ this.currentIndex = this.queue.length - 1;
+ },
+ async getPlayQueue(addNowPlaying: boolean = false): Promise {
+ const queueData: GetPlayQueueResponse = await OpenSubsonic.get("getPlayQueue")
+ if (queueData && queueData.playQueue.entry) {
+ this.queue = queueData.playQueue.entry;
+ }
+ const nowPlayingData: NowPlayingResponse = await OpenSubsonic.get("getNowPlaying");
+
+ if (nowPlayingData && nowPlayingData.nowPlaying.entry) {
+ const userEntries: Array = [];
+ nowPlayingData.nowPlaying.entry.forEach((entry) => {
+ if (entry.username === OpenSubsonic.username) {
+ userEntries.push(entry);
+ }
+ });
+ console.log(userEntries);
+ let localClientEntry = undefined;
+ userEntries.forEach((entry) => {
+ if (entry.playerName === OpenSubsonic.clientName) {
+ localClientEntry = entry;
+ }
+ })
+
+ if (typeof localClientEntry !== "undefined") {
+ if (this.findSong(localClientEntry).found) {
+ this.setSong(this.findSong(localClientEntry).index);
+ playbackState.newSong(this.getSong());
+ }
+ else if (addNowPlaying) {
+ this.addSong(localClientEntry);
+ playbackState.newSong(this.getSong());
+ }
+ else if (this.queue.length != 0) {
+ playbackState.newSong(this.firstSong());
+ }
+ }
+ else {
+ for (const entry of userEntries) {
+ if (this.findSong(entry).found) {
+ this.setSong(this.findSong(entry).index);
+ playbackState.newSong(this.getSong());
+ }
+ else if (addNowPlaying) {
+ this.addSong(entry);
+ playbackState.newSong(this.getSong());
+ }
+ else if (this.queue.length != 0) {
+ playbackState.newSong(this.firstSong());
+ }
+ }
+ }
+
+
+ }
+ },
+ async saveQueue(): Promise {
+ const songs: Array = [];
+
+ console.log(this.queue);
+
+ this.queue.forEach((song: Song, idx: number): void => {
+ if (idx === 0) {
+ songs.push({key: "current", value: song.id})
+ songs.push({key: "id", value: song.id})
+ // Add Progress within current song
+ }
+ else {
+ songs.push({key: "id", value: song.id})
+ }
+ })
+
+ const data = await OpenSubsonic.get("savePlayQueue", songs);
+ if (data) {
+ await this.getPlayQueue();
+ }
+ }
+})
+
diff --git a/src/lib/states/playback-state.svelte.ts b/src/lib/states/playback-state.svelte.ts
new file mode 100644
index 0000000..9c87d1b
--- /dev/null
+++ b/src/lib/states/playback-state.svelte.ts
@@ -0,0 +1,98 @@
+import {PlaybackMode, PlaybackStateSvelte} from "$lib/player.svelte";
+import {OpenSubsonic, type Parameter, type Song} from "$lib/opensubsonic";
+import {queueState} from "$lib/states/play-queue.svelte";
+
+interface PlaybackState {
+ mode: PlaybackStateSvelte,
+ song: HTMLAudioElement,
+ metaData: Song,
+ duration: number,
+ volume: number,
+ paused: boolean,
+ progress: number,
+ newSong: (song: Song) => void,
+ play: () => void,
+ pause: () => void,
+}
+
+export const playbackState: PlaybackState = $state({
+ mode: new PlaybackStateSvelte(),
+ song: {},
+ metaData: {},
+ duration: 0,
+ volume: 0.05,
+ paused: true,
+ progress: 0,
+ newSong(song: Song): void {
+ const parameters: Array = [
+ { key: "id", value: song.id },
+ //{ key: "maxBitRate", value: } // TODO
+ //{ key: "format", value: } // TODO
+ //{ key: "timeOffset", value: } // TODO? Only Video related
+ //{ key: "size", value: } // TODO? Only Video related
+ { key: "estimateContentLength", value: "true" },
+ //{ key: "converted", value: } // TODO? Only Video related
+ ];
+ const url = OpenSubsonic.getApiUrl("stream", parameters);
+ this.song = new Audio(url); // Assign new URL
+ this.metaData = song;
+
+ // Reassign Event Handlers
+ this.song.onloadedmetadata = () => {
+ this.duration = this.song.duration;
+ };
+
+ this.song.onplay = () => {
+ this.song.volume = this.volume;
+ this.paused = this.song.paused;
+ const time: number = Date.now();
+ const parameters: Array = [
+ { key: "id", value: song.id },
+ { key: "time", value: time.toString()},
+ { key: "submission", value: `${false}`}
+ ];
+ OpenSubsonic.get("scrobble", parameters);
+ }
+
+ this.song.onpause = () => {
+ this.paused = this.song.paused;
+ }
+
+ this.song.ontimeupdate = () => {
+ this.progress = this.song.currentTime;
+ }
+
+ this.song.onended = () => {
+ switch (this.mode.current) {
+ case PlaybackMode.Linear: {
+ this.newSong(queueState.nextSong());
+ this.play();
+ break;
+ }
+ case PlaybackMode.LoopOne: {
+ this.play();
+ break;
+ }
+ case PlaybackMode.LoopQueue: {
+ if (queueState.currentIndex === queueState.queue.length -1) {
+ this.newSong(queueState.firstSong());
+ }
+ else {
+ this.newSong(queueState.nextSong());
+ }
+ this.play();
+ break;
+ }
+ }
+ }
+
+ this.song.load();
+ },
+ play() {
+ this.song.play().catch((): void => {});
+ },
+ pause() {
+ console.log(this.song);
+ this.song.pause();
+ }
+})
\ No newline at end of file
diff --git a/src/lib/states/view-state.svelte.ts b/src/lib/states/view-state.svelte.ts
new file mode 100644
index 0000000..3077046
--- /dev/null
+++ b/src/lib/states/view-state.svelte.ts
@@ -0,0 +1,30 @@
+import {AlbumView, View} from "$lib/components/custom/Views/views.svelte";
+
+interface ViewState {
+ current: ViewDetails,
+ previous?: ViewState,
+ parent?: ViewState,
+ setMode: (newMode: View) => void,
+ setSubMode: (newSubMode: AlbumView) => void,
+}
+
+interface ViewDetails {
+ // sort
+ mode: View,
+ subMode: AlbumView,
+}
+
+export const viewState: ViewState = $state({
+ current: {
+ mode: View.Albums,
+ subMode: AlbumView.All,
+ },
+ setSubMode(newSubMode: AlbumView): void {
+ this.previous = this.current;
+ this.current.subMode = newSubMode;
+ },
+ setMode(newMode: View): void {
+ this.previous = this.current;
+ this.current.mode = newMode;
+ }
+})
\ No newline at end of file
diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte
index b50cebc..94f8872 100644
--- a/src/routes/+layout.svelte
+++ b/src/routes/+layout.svelte
@@ -2,14 +2,34 @@
Navbar
+
+
+
+ {@render children()}
+
+
- {@render children()}
+
\ No newline at end of file
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte
index 47c8893..4579c7a 100644
--- a/src/routes/+page.svelte
+++ b/src/routes/+page.svelte
@@ -1,249 +1,30 @@
- Lydstyrke - {currentSong.data.title}({currentSong.data.artist})
+ Lydstyrke - {playbackState.metaData.title}({playbackState.metaData.artist})
-{#snippet centerModule()}
-
- {#if viewMode === Views.Albums}
-
- {:else if viewMode === Views.Artists}
- Get Fucked
- {/if}
-
-{/snippet}
-
-{#snippet leftModule()}
-
-{/snippet}
-
-{#snippet rightModule()}
-
-{/snippet}
-
-
- {@render leftModule()}
- {@render centerModule()}
- {@render rightModule()}
-
-
-
-
-
Song: {currentSong.data.title}
-
Volume: {volume}
-
{displayTime(progress)}/{displayTime(duration)}
-
{progressPercent().toFixed(2)}%
-
changeVolume(-0.05)}>-
-
changeVolume(0.05)}>+
-
currentSong.source.currentTime -= 5}>{"<<"}
- {#if isPaused}
-
Play
- {:else}
-
Pause
- {/if}
-
currentSong.source.currentTime += 5}>{">>"}
-
mode.next()}>{mode.get()}
-
+
+ {#if viewState.current.mode === View.Albums}es
+
+ {:else if viewState.current.mode === View.Artists}
+
Get Fucked
+ {/if}
+