From a3801f66980df85b2369f44e324c6c4d87f67941 Mon Sep 17 00:00:00 2001 From: James Magahern Date: Sat, 15 Feb 2025 22:15:59 -0800 Subject: [PATCH] More rich metadata with NowPlaying --- frontend/src/api/player.tsx | 6 +++- frontend/src/components/App.tsx | 14 +++++--- frontend/src/components/NowPlaying.tsx | 15 ++++----- frontend/src/components/SongRow.tsx | 4 +-- frontend/src/index.css | 10 ++++++ src/MediaPlayer.ts | 45 +++++++++++++++++++++----- src/server.ts | 4 +-- 7 files changed, 72 insertions(+), 26 deletions(-) diff --git a/frontend/src/api/player.tsx b/frontend/src/api/player.tsx index eb25691..2badbe1 100644 --- a/frontend/src/api/player.tsx +++ b/frontend/src/api/player.tsx @@ -1,6 +1,6 @@ export interface NowPlayingResponse { success: boolean; - nowPlaying: string; + playingItem: PlaylistItem; isPaused: boolean; volume: number; isIdle: boolean; @@ -21,6 +21,10 @@ export interface PlaylistItem { metadata?: Metadata; } +export const getDisplayTitle = (item: PlaylistItem): string => { + return item.metadata?.title || item.title || item.filename; +} + export interface MetadataUpdateEvent { event: 'metadata_update'; data: { diff --git a/frontend/src/components/App.tsx b/frontend/src/components/App.tsx index 8b00964..a4ceda8 100644 --- a/frontend/src/components/App.tsx +++ b/frontend/src/components/App.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useCallback } from 'react'; import SongTable from './SongTable'; import NowPlaying from './NowPlaying'; import AddSongPanel from './AddSongPanel'; -import { API, PlaylistItem } from '../api/player'; +import { API, getDisplayTitle, PlaylistItem } from '../api/player'; const App: React.FC = () => { const [isPlaying, setIsPlaying] = useState(false); @@ -20,8 +20,8 @@ const App: React.FC = () => { const fetchNowPlaying = useCallback(async () => { const nowPlaying = await API.getNowPlaying(); - setNowPlayingSong(nowPlaying.nowPlaying); - setNowPlayingFileName(nowPlaying.currentFile); + setNowPlayingSong(getDisplayTitle(nowPlaying.playingItem)); + setNowPlayingFileName(nowPlaying.playingItem.filename); setIsPlaying(!nowPlaying.isPaused); if (!volumeSettingIsLocked) { @@ -29,11 +29,15 @@ const App: React.FC = () => { } }, [volumeSettingIsLocked]); - const handleAddURL = (url: string) => { + const handleAddURL = async (url: string) => { const urlToAdd = url.trim(); if (urlToAdd) { - API.addToPlaylist(urlToAdd); + await API.addToPlaylist(urlToAdd); fetchPlaylist(); + + if (!isPlaying) { + await API.play(); + } } }; diff --git a/frontend/src/components/NowPlaying.tsx b/frontend/src/components/NowPlaying.tsx index 5973cd6..65cc477 100644 --- a/frontend/src/components/NowPlaying.tsx +++ b/frontend/src/components/NowPlaying.tsx @@ -23,15 +23,14 @@ interface NowPlayingProps extends HTMLAttributes { const NowPlaying: React.FC = (props) => { return ( -
+
-
-
-
{props.songName}
-
{props.fileName}
+
+
+
{props.songName}
+
{props.fileName}
-
- +
= (props) => { onMouseDown={() => props.onVolumeWillChange(props.volume)} onMouseUp={() => props.onVolumeDidChange(props.volume)} onChange={(e) => props.onVolumeSettingChange(Number(e.target.value))} - className="w-24 h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-3 [&::-webkit-slider-thumb]:h-3 [&::-webkit-slider-thumb]:bg-white [&::-webkit-slider-thumb]:rounded-full hover:[&::-webkit-slider-thumb]:bg-violet-300" + className="fancy-slider w-48 md:w-24 h-2" />
diff --git a/frontend/src/components/SongRow.tsx b/frontend/src/components/SongRow.tsx index 5669ad0..6d80ba7 100644 --- a/frontend/src/components/SongRow.tsx +++ b/frontend/src/components/SongRow.tsx @@ -1,7 +1,7 @@ import classNames from 'classnames'; import React, { useState, useRef, useEffect } from 'react'; import { FaPlay, FaVolumeUp, FaVolumeOff } from 'react-icons/fa'; -import { PlaylistItem } from '../api/player'; +import { getDisplayTitle, PlaylistItem } from '../api/player'; export enum PlayState { NotPlaying, @@ -36,7 +36,7 @@ const SongRow: React.FC = ({ song, playState, onDelete, onPlay }) }; }, [showDeleteConfirm]); - const displayTitle = song.metadata?.title || song.title || song.filename; + const displayTitle = getDisplayTitle(song); return (
{ + public async getPlaylist(): Promise { return this.writeCommand("get_property", ["playlist"]) .then((response) => { // Enhance playlist items with metadata - const playlist = response.data; - return playlist.map((item: any) => ({ + const playlist = response.data as PlaylistItem[]; + return playlist.map((item: PlaylistItem) => ({ ...item, metadata: this.metadata.get(item.filename) || {} })); }); } - public async getNowPlaying(): Promise { - return this.writeCommand("get_property", ["media-title"]) - .then((response) => { - return response.data; - }); + public async getNowPlaying(): Promise { + const playlist = await this.getPlaylist(); + const currentlyPlayingSong = playlist.find((item: PlaylistItem) => item.current); + const fetchMediaTitle = async (): Promise => { + return (await this.writeCommand("get_property", ["media-title"])).data; + }; + + if (currentlyPlayingSong !== undefined) { + // Use media title if we don't have a title + if (currentlyPlayingSong.title === undefined && currentlyPlayingSong.metadata?.title === undefined) { + return { + ...currentlyPlayingSong, + title: await fetchMediaTitle() + }; + } + + return currentlyPlayingSong; + } + + const mediaTitle = await fetchMediaTitle(); + return { + id: 0, + filename: mediaTitle, + title: mediaTitle + }; } public async getCurrentFile(): Promise { diff --git a/src/server.ts b/src/server.ts index b1aed09..33295a8 100644 --- a/src/server.ts +++ b/src/server.ts @@ -63,7 +63,7 @@ apiRouter.post("/previous", withErrorHandling(async (req, res) => { })); apiRouter.get("/nowplaying", withErrorHandling(async (req, res) => { - const nowPlaying = await mediaPlayer.getNowPlaying(); + const playingItem = await mediaPlayer.getNowPlaying(); const currentFile = await mediaPlayer.getCurrentFile(); const pauseState = await mediaPlayer.getPauseState(); const volume = await mediaPlayer.getVolume(); @@ -71,7 +71,7 @@ apiRouter.get("/nowplaying", withErrorHandling(async (req, res) => { res.send(JSON.stringify({ success: true, - nowPlaying: nowPlaying, + playingItem: playingItem, isPaused: pauseState, volume: volume, isIdle: idle,