feat: add jukebox player engine for server-side playback (#2109)

* feat: add jukebox player engine for server-side playback

---------

Co-authored-by: jeffvli <jeffvictorli@gmail.com>
This commit is contained in:
mannansainidev
2026-07-10 09:59:22 +05:30
committed by GitHub
parent dd136f91e6
commit 0c89fea1ea
15 changed files with 743 additions and 31 deletions
+51
View File
@@ -805,6 +805,53 @@ const reportPlaybackParameters = z.object({
const reportPlayback = z.null();
const jukeboxControlParameters = z.object({
action: z.enum([
'start',
'stop',
'skip',
'set',
'get',
'setGain',
'add',
'clear',
'remove',
'shuffle',
'status',
]),
gain: z.number().optional(),
id: z.union([z.string(), z.array(z.string())]).optional(),
index: z.number().optional(),
offset: z.number().optional(),
});
const jukeboxPlaylistEntry = z.object({
album: z.string().optional(),
artist: z.string().optional(),
coverArt: z.string().optional(),
duration: z.number().optional(),
id: z.string(),
isDir: z.boolean(),
parent: z.string().optional(),
title: z.string(),
});
const jukeboxStatus = z.object({
currentIndex: z.number().optional(),
gain: z.number(),
playing: z.boolean(),
position: z.number().optional(),
});
const jukeboxPlaylist = jukeboxStatus.extend({
entry: z.array(jukeboxPlaylistEntry).optional(),
});
const jukeboxControl = z.object({
jukeboxPlaylist: jukeboxPlaylist.optional(),
jukeboxStatus: jukeboxStatus.optional(),
});
export const ssType = {
_body: {
getTranscodeDecision: transcodeDecisionRequestBody,
@@ -834,6 +881,7 @@ export const ssType = {
getStarred: getStarredParameters,
getTranscodeDecision: transcodeDecisionParameters,
getTranscodeStream: getTranscodeStreamParameters,
jukeboxControl: jukeboxControlParameters,
randomSongList: randomSongListParameters,
removeFavorite: removeFavoriteParameters,
reportPlayback: reportPlaybackParameters,
@@ -882,6 +930,9 @@ export const ssType = {
getStarred,
getTranscodeDecision,
internetRadioStation,
jukeboxControl,
jukeboxPlaylist,
jukeboxStatus,
musicFolderList,
ping,
playlist,
+52
View File
@@ -1540,6 +1540,7 @@ export type ControllerEndpoint = {
getTopSongs: (args: TopSongListArgs) => Promise<TopSongListResponse>;
getUserInfo: (args: UserInfoArgs) => Promise<UserInfoResponse>;
getUserList?: (args: UserListArgs) => Promise<UserListResponse>;
jukeboxControl?: (args: JukeboxControlArgs) => Promise<JukeboxControlResponse>;
movePlaylistItem?: (args: MoveItemArgs) => Promise<void>;
removeFromPlaylist: (args: RemoveFromPlaylistArgs) => Promise<RemoveFromPlaylistResponse>;
replacePlaylist: (args: ReplacePlaylistArgs) => Promise<ReplacePlaylistResponse>;
@@ -1705,6 +1706,9 @@ export type InternalControllerEndpoint = {
getTopSongs: (args: ReplaceApiClientProps<TopSongListArgs>) => Promise<TopSongListResponse>;
getUserInfo: (args: ReplaceApiClientProps<UserInfoArgs>) => Promise<UserInfoResponse>;
getUserList?: (args: ReplaceApiClientProps<UserListArgs>) => Promise<UserListResponse>;
jukeboxControl?: (
args: ReplaceApiClientProps<JukeboxControlArgs>,
) => Promise<JukeboxControlResponse>;
movePlaylistItem?: (args: ReplaceApiClientProps<MoveItemArgs>) => Promise<void>;
removeFromPlaylist: (
args: ReplaceApiClientProps<RemoveFromPlaylistArgs>,
@@ -1737,6 +1741,54 @@ export type InternalControllerEndpoint = {
) => Promise<UploadPlaylistImageResponse>;
};
export type JukeboxControlAction =
| 'add'
| 'clear'
| 'get'
| 'remove'
| 'set'
| 'setGain'
| 'shuffle'
| 'skip'
| 'start'
| 'status'
| 'stop';
export type JukeboxControlArgs = BaseEndpointArgs & { query: JukeboxControlQuery };
export type JukeboxControlQuery = {
action: JukeboxControlAction;
gain?: number;
id?: string | string[];
index?: number;
offset?: number;
};
export type JukeboxControlResponse = null | {
jukeboxPlaylist?: {
currentIndex?: number;
entry?: Array<{
album?: string;
artist?: string;
coverArt?: string;
duration?: number;
id: string;
isDir: boolean;
parent?: string;
title: string;
}>;
gain: number;
playing: boolean;
position?: number;
};
jukeboxStatus?: {
currentIndex?: number;
gain: number;
playing: boolean;
position?: number;
};
};
export type LyricGetQuery = {
remoteSongId: string;
remoteSource: LyricSource;
+1
View File
@@ -5,6 +5,7 @@ export enum ServerFeature {
ARTIST_IMAGE_UPLOAD = 'artistImageUpload',
BFR = 'bfr',
INTERNET_RADIO_IMAGE_UPLOAD = 'internetRadioImageUpload',
JUKEBOX = 'jukebox',
LYRICS_MULTIPLE_STRUCTURED = 'lyricsMultipleStructured',
LYRICS_SINGLE_STRUCTURED = 'lyricsSingleStructured',
MUSIC_FOLDER_MULTISELECT = 'musicFolderMultiselect',
+1
View File
@@ -158,6 +158,7 @@ export enum PlayerStyle {
}
export enum PlayerType {
JUKEBOX = 'jukebox',
LOCAL = 'local',
WEB = 'web',
}