("pause");
+}
+
+export function seek(seconds: number) {
+ return invoke("seek_to", {
+ position: {
+ secs: Math.floor(seconds),
+ nanos: 0
+ }
+ });
+}
diff --git a/src/app/store/tauri.ts b/src/app/store/tauri.ts
index 8bc0b72..274dcb4 100644
--- a/src/app/store/tauri.ts
+++ b/src/app/store/tauri.ts
@@ -27,10 +27,11 @@ const initialState = {
speed: 1,
paused: true,
position: null as Duration | null,
- currentlyPlaying: null as string | null,
+ currentlyPlayingPath: null as string | null,
duration: null as Duration | null
}
+// TODO: Rename to `playbackStateSlice`
export const tauriSlice = createSlice({
name: "tauri",
initialState,
@@ -44,7 +45,7 @@ export const tauriSlice = createSlice({
state.volume = action.payload.volume;
state.speed = action.payload.speed;
state.paused = action.payload.paused;
- state.currentlyPlaying = action.payload["currently_playing_file_path"];
+ state.currentlyPlayingPath = action.payload["currently_playing_file_path"];
state.duration = action.payload["currently_playing_duration"];
});
// TODO: Add error handling
@@ -53,4 +54,4 @@ export const tauriSlice = createSlice({
export const {setPosition} = tauriSlice.actions;
-export default tauriSlice.reducer;
\ No newline at end of file
+export default tauriSlice.reducer;
diff --git a/src/app/views/home.component.ts b/src/app/views/home.component.ts
index 2e5bded..2d4177e 100644
--- a/src/app/views/home.component.ts
+++ b/src/app/views/home.component.ts
@@ -1,130 +1,86 @@
-import { Component, computed } from '@angular/core';
-import { invoke } from "@tauri-apps/api/core";
-import { open } from '@tauri-apps/plugin-dialog';
-import { injectMutation, injectQuery } from '@tanstack/angular-query-experimental';
-import { JsonPipe } from '@angular/common';
-import { injectSelector } from '@reduxjs/angular-redux';
-import { RootState } from '../store';
-import { SongMetadata } from '../types/song-metadata';
+import {Component, computed, inject} from '@angular/core';
+import {JsonPipe} from '@angular/common';
+import {injectSelector} from '@reduxjs/angular-redux';
+import {RootState} from '../store';
+import {pause, play, resume, seek} from "../services/music";
+import {pickSong} from "../services/fs";
+import {Metadata} from "../injectables/metadata";
+import {injectMutation} from "@tanstack/angular-query-experimental";
@Component({
// TODO: Rename from "home" to "now-playing"
- selector: 'app-home',
- imports: [JsonPipe],
template: `
- @if (!openFileMutation.data()) {
-
- }
- @if (openFileMutation.isError()) {
- Error: {{ openFileMutation.error | json }}
- }
- @if (mp3Metadata.isError()) {
- Error: {{ mp3Metadata.error | json }}
- }
- @if (openFileMutation.isPending()) {
- Loading...
- }
- @if (mp3Metadata.isLoading()) {
- Loading song metadata...
- }
-
- @if (openFileMutation.data() && openFileMutation.isSuccess() && mp3Metadata.isSuccess()) {
-
- @if (mp3CoverImage()) {
-
+ @if (!openFileMutation.data()) {
+
+ }
+ @if (openFileMutation.isError()) {
+ Error: {{ openFileMutation.error | json }}
+ }
+ @if (stateMetadata.mp3Metadata.isError()) {
+ Error: {{ stateMetadata.mp3Metadata.error | json }}
+ }
+ @if (openFileMutation.isPending()) {
+ Loading...
+ }
+ @if (stateMetadata.mp3Metadata.isLoading()) {
+ Loading song metadata...
}
- {{ mp3Metadata.data()?.tags?.TrackTitle }}
- {{ mp3Metadata.data()?.tags?.AlbumArtist }}
- {{ mp3Metadata.data()?.tags?.Album }}
-
-
- @if (stateMetadata().paused) {
-
- } @else {
-
- }
-
- @if (stateMetadata().currentlyPlaying === null) {
-
- } @else {
-
- }
-
-
- }
+ @if (openFileMutation.data() && stateMetadata.mp3Metadata.isSuccess() && openFileMutation.isSuccess()) {
+
+ @if (stateMetadata.mp3CoverImage()) {
+
+ }
+ {{ stateMetadata.mp3Metadata.data()?.tags?.TrackTitle }}
+ {{ stateMetadata.mp3Metadata.data()?.tags?.AlbumArtist }}
+ {{ stateMetadata.mp3Metadata.data()?.tags?.Album }}
+
+
+ @if (playingMetadata().paused) {
+
+ } @else {
+
+ }
+
+
+
+ }
`,
+ selector: 'app-home',
+ imports: [JsonPipe],
})
export class Home {
- stateMetadata = injectSelector((state: RootState) => state.tauri);
+ playingMetadata = injectSelector((state: RootState) => state.tauri);
+ stateMetadata = inject(Metadata);
openFileMutation = injectMutation(() => ({
mutationKey: ['openFile'],
mutationFn: async () => {
- const result = await open({
- multiple: false,
- directory: false,
- filters: [{ name: 'MP3', extensions: ['mp3'] }, { name: 'FLAC', extensions: ['flac'] }]
- });
- return result;
+ const path = await pickSong();
+ if (!path) {
+ return;
+ }
+ await play(path);
}
- }));
+ }))
currentTime = computed(() => {
- return this.stateMetadata().position?.secs;
+ return this.playingMetadata().position?.secs;
})
totalTime = computed(() => {
- return this.stateMetadata().duration?.secs;
+ return this.playingMetadata().duration?.secs;
})
- mp3Metadata = injectQuery(() => ({
- queryKey: ['mp3Metadata', this.openFileMutation.data()],
- queryFn: async () => {
- const path = this.openFileMutation.data();
- if (!path) return null;
- // TODO: Move invoke to a service
- return await invoke("read_metadata", { path });
- }
- }));
-
- mp3CoverImage = computed(() => {
- const metadata = this.mp3Metadata.data();
- if (!metadata) return null;
- // uint8array to base64
- const cover = metadata.visuals[Object.keys(metadata.visuals)[0] as keyof typeof metadata.visuals | never]?.data;
- if (!cover) return null;
- return cover;
- });
-
- play() {
- invoke("play", {
- path: this.openFileMutation.data()
- });
- }
-
- stop() {
- invoke("stop");
- }
-
- resume() {
- invoke("resume");
- }
-
- pause() {
- invoke("pause");
- }
+ resume = resume;
+ pause = pause;
seekFromProgressBar(event: MouseEvent) {
const progressBar = event.target as HTMLProgressElement;
const value = event.offsetX / progressBar.offsetWidth * parseInt(progressBar.getAttribute('max') || '0');
- invoke("seek_to", {
- position: {
- secs: Math.floor(value),
- nanos: 0
- }
- });
+ seek(value);
}
}