2025-02-15 16:28:47 -08:00
|
|
|
export interface NowPlayingResponse {
|
|
|
|
|
success: boolean;
|
2025-02-15 22:15:59 -08:00
|
|
|
playingItem: PlaylistItem;
|
2025-02-15 16:28:47 -08:00
|
|
|
isPaused: boolean;
|
|
|
|
|
volume: number;
|
|
|
|
|
isIdle: boolean;
|
|
|
|
|
currentFile: string;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-15 21:40:30 -08:00
|
|
|
export interface Metadata {
|
|
|
|
|
title?: string;
|
|
|
|
|
description?: string;
|
|
|
|
|
siteName?: string;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-15 16:28:47 -08:00
|
|
|
export interface PlaylistItem {
|
|
|
|
|
filename: string;
|
|
|
|
|
title: string | null;
|
|
|
|
|
id: number;
|
|
|
|
|
playing: boolean | null;
|
2025-02-15 21:40:30 -08:00
|
|
|
metadata?: Metadata;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-15 22:15:59 -08:00
|
|
|
export const getDisplayTitle = (item: PlaylistItem): string => {
|
2025-02-21 23:48:29 -08:00
|
|
|
return item.title || item.metadata?.title || item.filename;
|
2025-02-15 22:15:59 -08:00
|
|
|
}
|
|
|
|
|
|
2025-02-15 21:40:30 -08:00
|
|
|
export interface MetadataUpdateEvent {
|
|
|
|
|
event: 'metadata_update';
|
|
|
|
|
data: {
|
|
|
|
|
url: string;
|
|
|
|
|
metadata: Metadata;
|
|
|
|
|
};
|
2025-02-15 16:28:47 -08:00
|
|
|
}
|
|
|
|
|
|
2025-02-22 01:09:33 -08:00
|
|
|
export interface SearchResult {
|
|
|
|
|
type: string;
|
|
|
|
|
title: string;
|
|
|
|
|
author: string;
|
|
|
|
|
mediaUrl: string;
|
|
|
|
|
thumbnailUrl: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface SearchResponse {
|
|
|
|
|
success: boolean;
|
|
|
|
|
results: SearchResult[];
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-15 16:28:47 -08:00
|
|
|
export const API = {
|
|
|
|
|
async getPlaylist(): Promise<PlaylistItem[]> {
|
|
|
|
|
const response = await fetch('/api/playlist');
|
|
|
|
|
return response.json();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async addToPlaylist(url: string): Promise<void> {
|
|
|
|
|
await fetch('/api/playlist', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({ url }),
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async removeFromPlaylist(index: number): Promise<void> {
|
|
|
|
|
await fetch(`/api/playlist/${index}`, {
|
|
|
|
|
method: 'DELETE',
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async play(): Promise<void> {
|
|
|
|
|
await fetch('/api/play', { method: 'POST' });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async pause(): Promise<void> {
|
|
|
|
|
await fetch('/api/pause', { method: 'POST' });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async skip(): Promise<void> {
|
|
|
|
|
await fetch('/api/skip', { method: 'POST' });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async skipTo(index: number): Promise<void> {
|
|
|
|
|
await fetch(`/api/skip/${index}`, { method: 'POST' });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async previous(): Promise<void> {
|
|
|
|
|
await fetch('/api/previous', { method: 'POST' });
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async getNowPlaying(): Promise<NowPlayingResponse> {
|
|
|
|
|
const response = await fetch('/api/nowplaying');
|
|
|
|
|
return response.json();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async setVolume(volume: number): Promise<void> {
|
|
|
|
|
await fetch('/api/volume', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
},
|
|
|
|
|
body: JSON.stringify({ volume }),
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
2025-02-22 01:09:33 -08:00
|
|
|
async search(query: string): Promise<SearchResponse> {
|
|
|
|
|
const response = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
|
|
|
|
|
return response.json();
|
|
|
|
|
},
|
|
|
|
|
|
2025-02-15 16:28:47 -08:00
|
|
|
subscribeToEvents(onMessage: (event: any) => void): WebSocket {
|
2025-02-22 01:27:08 -08:00
|
|
|
const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws';
|
|
|
|
|
const ws = new WebSocket(`${protocol}://${window.location.host}/api/events`);
|
2025-02-15 16:28:47 -08:00
|
|
|
ws.onmessage = (event) => {
|
|
|
|
|
onMessage(JSON.parse(event.data));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return ws;
|
|
|
|
|
}
|
|
|
|
|
};
|