mirror of
https://github.com/antebudimir/feishin.git
synced 2025-12-31 18:13:31 +00:00
Add playlist list
This commit is contained in:
parent
00a21269dd
commit
ec79d91d30
21 changed files with 911 additions and 47 deletions
|
|
@ -199,6 +199,10 @@ const getArtistList = async (args: ArtistListArgs) => {
|
|||
return (apiController('getArtistList') as ControllerEndpoint['getArtistList'])?.(args);
|
||||
};
|
||||
|
||||
const getPlaylistList = async (args: PlaylistListArgs) => {
|
||||
return (apiController('getPlaylistList') as ControllerEndpoint['getPlaylistList'])?.(args);
|
||||
};
|
||||
|
||||
export const controller = {
|
||||
getAlbumArtistList,
|
||||
getAlbumDetail,
|
||||
|
|
@ -206,5 +210,6 @@ export const controller = {
|
|||
getArtistList,
|
||||
getGenreList,
|
||||
getMusicFolderList,
|
||||
getPlaylistList,
|
||||
getSongList,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import type {
|
|||
JFGenreListResponse,
|
||||
JFMusicFolderList,
|
||||
JFMusicFolderListResponse,
|
||||
JFPlaylist,
|
||||
JFPlaylistDetail,
|
||||
JFPlaylistDetailResponse,
|
||||
JFPlaylistList,
|
||||
|
|
@ -32,7 +33,7 @@ import type {
|
|||
JFSongListResponse,
|
||||
} from '/@/renderer/api/jellyfin.types';
|
||||
import { JFCollectionType } from '/@/renderer/api/jellyfin.types';
|
||||
import type {
|
||||
import {
|
||||
Album,
|
||||
AlbumArtist,
|
||||
AlbumArtistDetailArgs,
|
||||
|
|
@ -48,13 +49,13 @@ import type {
|
|||
FavoriteResponse,
|
||||
GenreListArgs,
|
||||
MusicFolderListArgs,
|
||||
Playlist,
|
||||
PlaylistDetailArgs,
|
||||
PlaylistListArgs,
|
||||
playlistListSortMap,
|
||||
PlaylistSongListArgs,
|
||||
Song,
|
||||
SongListArgs,
|
||||
} from '/@/renderer/api/types';
|
||||
import {
|
||||
songListSortMap,
|
||||
albumListSortMap,
|
||||
artistListSortMap,
|
||||
|
|
@ -396,18 +397,20 @@ const getPlaylistSongList = async (args: PlaylistSongListArgs): Promise<JFSongLi
|
|||
};
|
||||
|
||||
const getPlaylistList = async (args: PlaylistListArgs): Promise<JFPlaylistList> => {
|
||||
const { server, signal } = args;
|
||||
const { query, server, signal } = args;
|
||||
|
||||
const searchParams = {
|
||||
fields: 'ChildCount, Genres, DateCreated, ParentId, Overview',
|
||||
includeItemTypes: 'Playlist',
|
||||
limit: query.limit,
|
||||
recursive: true,
|
||||
sortBy: 'SortName',
|
||||
sortOrder: 'Ascending',
|
||||
sortBy: playlistListSortMap.jellyfin[query.sortBy],
|
||||
sortOrder: sortOrderMap.jellyfin[query.sortOrder],
|
||||
startIndex: query.startIndex,
|
||||
};
|
||||
|
||||
const data = await api
|
||||
.get(`/users/${server?.userId}/items`, {
|
||||
.get(`users/${server?.userId}/items`, {
|
||||
headers: { 'X-MediaBrowser-Token': server?.credential },
|
||||
prefixUrl: server?.url,
|
||||
searchParams: parseSearchParams(searchParams),
|
||||
|
|
@ -415,12 +418,12 @@ const getPlaylistList = async (args: PlaylistListArgs): Promise<JFPlaylistList>
|
|||
})
|
||||
.json<JFPlaylistListResponse>();
|
||||
|
||||
const playlistData = data.Items.filter((item) => item.MediaType === 'Audio');
|
||||
const playlistItems = data.Items.filter((item) => item.MediaType === 'Audio');
|
||||
|
||||
return {
|
||||
Items: playlistData,
|
||||
StartIndex: 0,
|
||||
TotalRecordCount: playlistData.length,
|
||||
items: playlistItems,
|
||||
startIndex: 0,
|
||||
totalRecordCount: playlistItems.length,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
@ -690,6 +693,20 @@ const normalizeAlbumArtist = (
|
|||
};
|
||||
};
|
||||
|
||||
const normalizePlaylist = (item: JFPlaylist): Playlist => {
|
||||
return {
|
||||
duration: item.RunTimeTicks / 10000000,
|
||||
id: item.Id,
|
||||
name: item.Name,
|
||||
public: null,
|
||||
rules: null,
|
||||
size: null,
|
||||
songCount: item?.ChildCount || null,
|
||||
userId: null,
|
||||
username: null,
|
||||
};
|
||||
};
|
||||
|
||||
// const normalizeArtist = (item: any) => {
|
||||
// return {
|
||||
// album: (item.album || []).map((entry: any) => normalizeAlbum(entry)),
|
||||
|
|
@ -710,24 +727,6 @@ const normalizeAlbumArtist = (
|
|||
// };
|
||||
// };
|
||||
|
||||
// const normalizePlaylist = (item: any) => {
|
||||
// return {
|
||||
// changed: item.DateLastMediaAdded,
|
||||
// comment: item.Overview,
|
||||
// created: item.DateCreated,
|
||||
// duration: item.RunTimeTicks / 10000000,
|
||||
// genre: item.GenreItems && item.GenreItems.map((entry: any) => normalizeItem(entry)),
|
||||
// id: item.Id,
|
||||
// image: getCoverArtUrl(item, 350),
|
||||
// owner: undefined,
|
||||
// public: undefined,
|
||||
// song: [],
|
||||
// songCount: item.ChildCount,
|
||||
// title: item.Name,
|
||||
// uniqueId: nanoid(),
|
||||
// };
|
||||
// };
|
||||
|
||||
// const normalizeGenre = (item: any) => {
|
||||
// return {
|
||||
// albumCount: undefined,
|
||||
|
|
@ -780,5 +779,6 @@ export const jellyfinApi = {
|
|||
export const jfNormalize = {
|
||||
album: normalizeAlbum,
|
||||
albumArtist: normalizeAlbumArtist,
|
||||
playlist: normalizePlaylist,
|
||||
song: normalizeSong,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -63,7 +63,19 @@ export interface JFPlaylistListResponse extends JFBasePaginatedResponse {
|
|||
Items: JFPlaylist[];
|
||||
}
|
||||
|
||||
export type JFPlaylistList = JFPlaylistListResponse;
|
||||
export type JFPlaylistList = {
|
||||
items: JFPlaylist[];
|
||||
startIndex: number;
|
||||
totalRecordCount: number;
|
||||
};
|
||||
|
||||
export enum JFPlaylistListSort {
|
||||
ALBUM_ARTIST = 'AlbumArtist,SortName',
|
||||
DURATION = 'Runtime',
|
||||
NAME = 'SortName',
|
||||
RECENTLY_ADDED = 'DateCreated,SortName',
|
||||
SONG_COUNT = 'ChildCount',
|
||||
}
|
||||
|
||||
export type JFPlaylistDetailResponse = JFPlaylist;
|
||||
|
||||
|
|
@ -485,6 +497,7 @@ type JFBaseParams = {
|
|||
imageTypeLimit?: number;
|
||||
parentId?: string;
|
||||
recursive?: boolean;
|
||||
searchTerm?: string;
|
||||
userId?: string;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import type {
|
|||
NDSongList,
|
||||
NDSongListResponse,
|
||||
NDAlbumArtist,
|
||||
NDPlaylist,
|
||||
} from '/@/renderer/api/navidrome.types';
|
||||
import { NDPlaylistListSort, NDSongListSort, NDSortOrder } from '/@/renderer/api/navidrome.types';
|
||||
import type {
|
||||
|
|
@ -51,6 +52,7 @@ import type {
|
|||
CreatePlaylistResponse,
|
||||
PlaylistSongListArgs,
|
||||
AlbumArtist,
|
||||
Playlist,
|
||||
} from '/@/renderer/api/types';
|
||||
import {
|
||||
playlistListSortMap,
|
||||
|
|
@ -329,12 +331,13 @@ const getPlaylistList = async (args: PlaylistListArgs): Promise<NDPlaylistList>
|
|||
_order: query.sortOrder ? sortOrderMap.navidrome[query.sortOrder] : NDSortOrder.ASC,
|
||||
_sort: query.sortBy ? playlistListSortMap.navidrome[query.sortBy] : NDPlaylistListSort.NAME,
|
||||
_start: query.startIndex,
|
||||
...query.ndParams,
|
||||
};
|
||||
|
||||
const res = await api.get('api/playlist', {
|
||||
headers: { 'x-nd-authorization': `Bearer ${server?.ndCredential}` },
|
||||
prefixUrl: server?.url,
|
||||
searchParams,
|
||||
searchParams: parseSearchParams(searchParams),
|
||||
signal,
|
||||
});
|
||||
|
||||
|
|
@ -521,6 +524,20 @@ const normalizeAlbumArtist = (item: NDAlbumArtist): AlbumArtist => {
|
|||
};
|
||||
};
|
||||
|
||||
const normalizePlaylist = (item: NDPlaylist): Playlist => {
|
||||
return {
|
||||
duration: item.duration,
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
public: item.public,
|
||||
rules: item?.rules || null,
|
||||
size: item.size,
|
||||
songCount: item.songCount,
|
||||
userId: item.ownerId,
|
||||
username: item.ownerName,
|
||||
};
|
||||
};
|
||||
|
||||
export const navidromeApi = {
|
||||
authenticate,
|
||||
createPlaylist,
|
||||
|
|
@ -540,5 +557,6 @@ export const navidromeApi = {
|
|||
export const ndNormalize = {
|
||||
album: normalizeAlbum,
|
||||
albumArtist: normalizeAlbumArtist,
|
||||
playlist: normalizePlaylist,
|
||||
song: normalizeSong,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -287,7 +287,7 @@ export type NDPlaylist = {
|
|||
ownerName: string;
|
||||
path: string;
|
||||
public: boolean;
|
||||
rules: null;
|
||||
rules: Record<string, any> | null;
|
||||
size: number;
|
||||
songCount: number;
|
||||
sync: boolean;
|
||||
|
|
@ -309,7 +309,7 @@ export type NDPlaylistListResponse = NDPlaylist[];
|
|||
export enum NDPlaylistListSort {
|
||||
DURATION = 'duration',
|
||||
NAME = 'name',
|
||||
OWNER = 'owner',
|
||||
OWNER = 'ownerName',
|
||||
PUBLIC = 'public',
|
||||
SONG_COUNT = 'songCount',
|
||||
UPDATED_AT = 'updatedAt',
|
||||
|
|
|
|||
|
|
@ -4,10 +4,17 @@ import type {
|
|||
JFAlbumArtist,
|
||||
JFGenreList,
|
||||
JFMusicFolderList,
|
||||
JFPlaylist,
|
||||
JFSong,
|
||||
} from '/@/renderer/api/jellyfin.types';
|
||||
import { ndNormalize } from '/@/renderer/api/navidrome.api';
|
||||
import type { NDAlbum, NDAlbumArtist, NDGenreList, NDSong } from '/@/renderer/api/navidrome.types';
|
||||
import type {
|
||||
NDAlbum,
|
||||
NDAlbumArtist,
|
||||
NDGenreList,
|
||||
NDPlaylist,
|
||||
NDSong,
|
||||
} from '/@/renderer/api/navidrome.types';
|
||||
import { SSGenreList, SSMusicFolderList } from '/@/renderer/api/subsonic.types';
|
||||
import type {
|
||||
Album,
|
||||
|
|
@ -16,6 +23,7 @@ import type {
|
|||
RawAlbumListResponse,
|
||||
RawGenreListResponse,
|
||||
RawMusicFolderListResponse,
|
||||
RawPlaylistListResponse,
|
||||
RawSongListResponse,
|
||||
} from '/@/renderer/api/types';
|
||||
import { ServerListItem } from '/@/renderer/types';
|
||||
|
|
@ -163,11 +171,32 @@ const albumArtistList = (
|
|||
};
|
||||
};
|
||||
|
||||
const playlistList = (data: RawPlaylistListResponse | undefined, server: ServerListItem | null) => {
|
||||
let playlists;
|
||||
switch (server?.type) {
|
||||
case 'jellyfin':
|
||||
playlists = data?.items.map((item) => jfNormalize.playlist(item as JFPlaylist));
|
||||
break;
|
||||
case 'navidrome':
|
||||
playlists = data?.items.map((item) => ndNormalize.playlist(item as NDPlaylist));
|
||||
break;
|
||||
case 'subsonic':
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
items: playlists,
|
||||
startIndex: data?.startIndex,
|
||||
totalRecordCount: data?.totalRecordCount,
|
||||
};
|
||||
};
|
||||
|
||||
export const normalize = {
|
||||
albumArtistList,
|
||||
albumDetail,
|
||||
albumList,
|
||||
genreList,
|
||||
musicFolderList,
|
||||
playlistList,
|
||||
songList,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import type {
|
|||
AlbumDetailQuery,
|
||||
AlbumArtistListQuery,
|
||||
ArtistListQuery,
|
||||
PlaylistListQuery,
|
||||
} from './types';
|
||||
|
||||
export const queryKeys = {
|
||||
|
|
@ -34,6 +35,11 @@ export const queryKeys = {
|
|||
musicFolders: {
|
||||
list: (serverId: string) => [serverId, 'musicFolders', 'list'] as const,
|
||||
},
|
||||
playlists: {
|
||||
list: (serverId: string, query?: PlaylistListQuery) =>
|
||||
[serverId, 'playlists', 'list', query] as const,
|
||||
root: (serverId: string) => [serverId, 'playlists'] as const,
|
||||
},
|
||||
server: {
|
||||
root: (serverId: string) => [serverId] as const,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import {
|
|||
JFPlaylistList,
|
||||
JFPlaylistDetail,
|
||||
JFMusicFolderList,
|
||||
JFPlaylistListSort,
|
||||
} from '/@/renderer/api/jellyfin.types';
|
||||
import {
|
||||
NDSortOrder,
|
||||
|
|
@ -243,14 +244,15 @@ export type MusicFolder = {
|
|||
};
|
||||
|
||||
export type Playlist = {
|
||||
duration?: number;
|
||||
duration: number | null;
|
||||
id: string;
|
||||
name: string;
|
||||
public?: boolean;
|
||||
size?: number;
|
||||
songCount?: number;
|
||||
userId: string;
|
||||
username: string;
|
||||
public: boolean | null;
|
||||
rules?: Record<string, any> | null;
|
||||
size: number | null;
|
||||
songCount: number | null;
|
||||
userId: string | null;
|
||||
username: string | null;
|
||||
};
|
||||
|
||||
export type GenresResponse = Genre[];
|
||||
|
|
@ -756,11 +758,21 @@ export type RawPlaylistListResponse = NDPlaylistList | JFPlaylistList | undefine
|
|||
|
||||
export type PlaylistListResponse = BasePaginatedResponse<Playlist[]>;
|
||||
|
||||
export type PlaylistListSort = NDPlaylistListSort;
|
||||
export enum PlaylistListSort {
|
||||
DURATION = 'duration',
|
||||
NAME = 'name',
|
||||
OWNER = 'owner',
|
||||
PUBLIC = 'public',
|
||||
SONG_COUNT = 'songCount',
|
||||
UPDATED_AT = 'updatedAt',
|
||||
}
|
||||
|
||||
export type PlaylistListQuery = {
|
||||
limit?: number;
|
||||
musicFolderId?: string;
|
||||
ndParams?: {
|
||||
owner_id?: string;
|
||||
};
|
||||
searchTerm?: string;
|
||||
sortBy: PlaylistListSort;
|
||||
sortOrder: SortOrder;
|
||||
startIndex: number;
|
||||
|
|
@ -769,18 +781,18 @@ export type PlaylistListQuery = {
|
|||
export type PlaylistListArgs = { query: PlaylistListQuery } & BaseEndpointArgs;
|
||||
|
||||
type PlaylistListSortMap = {
|
||||
jellyfin: Record<PlaylistListSort, undefined>;
|
||||
jellyfin: Record<PlaylistListSort, JFPlaylistListSort | undefined>;
|
||||
navidrome: Record<PlaylistListSort, NDPlaylistListSort | undefined>;
|
||||
subsonic: Record<PlaylistListSort, undefined>;
|
||||
};
|
||||
|
||||
export const playlistListSortMap: PlaylistListSortMap = {
|
||||
jellyfin: {
|
||||
duration: undefined,
|
||||
name: undefined,
|
||||
duration: JFPlaylistListSort.DURATION,
|
||||
name: JFPlaylistListSort.NAME,
|
||||
owner: undefined,
|
||||
public: undefined,
|
||||
songCount: undefined,
|
||||
songCount: JFPlaylistListSort.SONG_COUNT,
|
||||
updatedAt: undefined,
|
||||
},
|
||||
navidrome: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue