mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-07 04:20:12 +02:00
add transcode extension to player songUrl
This commit is contained in:
@@ -734,7 +734,9 @@ export const controller: GeneralController = {
|
||||
const server = getServerById(args.apiClientProps.serverId);
|
||||
|
||||
if (!server) {
|
||||
return '';
|
||||
throw new Error(
|
||||
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getStreamUrl`,
|
||||
);
|
||||
}
|
||||
|
||||
return apiController(
|
||||
@@ -784,20 +786,6 @@ export const controller: GeneralController = {
|
||||
server.type,
|
||||
)?.(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }));
|
||||
},
|
||||
getTranscodeDecision(args) {
|
||||
const server = getServerById(args.apiClientProps.serverId);
|
||||
|
||||
if (!server) {
|
||||
throw new Error(
|
||||
`${i18n.t('error.apiRouteError', { postProcess: 'sentenceCase' })}: getTranscodeDecision`,
|
||||
);
|
||||
}
|
||||
|
||||
return apiController(
|
||||
'getTranscodeDecision',
|
||||
server.type,
|
||||
)?.(addContext({ ...args, apiClientProps: { ...args.apiClientProps, server } }));
|
||||
},
|
||||
getUserInfo(args) {
|
||||
const server = getServerById(args.apiClientProps.serverId);
|
||||
|
||||
|
||||
@@ -1283,35 +1283,40 @@ export const JellyfinController: InternalControllerEndpoint = {
|
||||
apiClientProps,
|
||||
query: { ...query, limit: 1, startIndex: 0 },
|
||||
}).then((result) => result!.totalRecordCount!),
|
||||
getStreamUrl: ({ apiClientProps: { server }, query }) => {
|
||||
const { bitrate, format, id, offset = 0, transcode, transcodeParams } = query;
|
||||
getStreamUrl: async ({ apiClientProps: { server }, query }) => {
|
||||
const { bitrate, format, id, transcode } = query;
|
||||
const deviceId = '';
|
||||
|
||||
if (transcodeParams != null || transcode) {
|
||||
let url = `${server?.url}/Items/${id}/Download?apiKey=${server?.credential}&playSessionId=${deviceId}`;
|
||||
|
||||
if (transcode) {
|
||||
// Some format appears to be required. Fall back to trusty MP3 if not specified
|
||||
// Otherwise, ffmpeg appears to crash
|
||||
const realFormat = format || 'mp3';
|
||||
let url =
|
||||
|
||||
url =
|
||||
`${server?.url}/audio` +
|
||||
`/${id}/universal` +
|
||||
`?userId=${server?.userId}` +
|
||||
`&deviceId=${deviceId}` +
|
||||
'&audioCodec=aac' +
|
||||
`&apiKey=${server?.credential}` +
|
||||
`&playSessionId=${deviceId}` +
|
||||
'&container=opus,mp3,aac,m4a,m4b,flac,wav,ogg';
|
||||
|
||||
url += `&transcodingProtocol=http&transcodingContainer=${realFormat}`;
|
||||
url = url.replace('audioCodec=aac', `audioCodec=${realFormat}`);
|
||||
url = url.replace(
|
||||
'&container=opus,mp3,aac,m4a,m4b,flac,wav,ogg',
|
||||
`&container=${realFormat}`,
|
||||
);
|
||||
|
||||
if (bitrate !== undefined) {
|
||||
url += `&maxStreamingBitrate=${bitrate * 1000}`;
|
||||
}
|
||||
if (offset > 0) {
|
||||
url += `&startTimeTicks=${Math.floor(offset * 10_000_000)}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
return `${server?.url}/Items/${id}/Download?apiKey=${server?.credential}&playSessionId=${deviceId}`;
|
||||
return url;
|
||||
},
|
||||
getTagList: async (args) => {
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
@@ -941,7 +941,6 @@ export const NavidromeController: InternalControllerEndpoint = {
|
||||
totalRecordCount: res.totalRecordCount,
|
||||
};
|
||||
},
|
||||
getTranscodeDecision: SubsonicController.getTranscodeDecision,
|
||||
getUserInfo: SubsonicController.getUserInfo,
|
||||
getUserList: async (args) => {
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
@@ -9,6 +9,7 @@ import { z } from 'zod';
|
||||
|
||||
import { contract, ssApiClient } from '/@/renderer/api/subsonic/subsonic-api';
|
||||
import { randomString } from '/@/renderer/utils';
|
||||
import { logFn } from '/@/renderer/utils/logger';
|
||||
import { getServerUrl } from '/@/renderer/utils/normalize-server-url';
|
||||
import { ssNormalize } from '/@/shared/api/subsonic/subsonic-normalize';
|
||||
import {
|
||||
@@ -87,6 +88,151 @@ const ALBUM_LIST_SORT_MAPPING: Record<AlbumListSort, AlbumListSortType | undefin
|
||||
const MAX_SUBSONIC_ITEMS = 500;
|
||||
const SUBSONIC_FAST_BATCH_SIZE = MAX_SUBSONIC_ITEMS * 10;
|
||||
|
||||
const TRANSCODE_DIRECT_PLAY_PROFILES = [
|
||||
{
|
||||
audioCodecs: ['mp3'],
|
||||
containers: ['mp3'],
|
||||
maxAudioChannels: 2,
|
||||
protocols: ['http'],
|
||||
},
|
||||
{
|
||||
audioCodecs: ['aac'],
|
||||
containers: ['m4a', 'mp4'],
|
||||
maxAudioChannels: 2,
|
||||
protocols: ['http'],
|
||||
},
|
||||
{
|
||||
audioCodecs: ['vorbis'],
|
||||
containers: ['ogg'],
|
||||
maxAudioChannels: 2,
|
||||
protocols: ['http'],
|
||||
},
|
||||
{
|
||||
audioCodecs: ['opus'],
|
||||
containers: ['ogg', 'webm'],
|
||||
maxAudioChannels: 2,
|
||||
protocols: ['http'],
|
||||
},
|
||||
{
|
||||
audioCodecs: ['pcm'],
|
||||
containers: ['wav'],
|
||||
maxAudioChannels: 2,
|
||||
protocols: ['http'],
|
||||
},
|
||||
{
|
||||
audioCodecs: ['flac'],
|
||||
containers: ['flac'],
|
||||
maxAudioChannels: 2,
|
||||
protocols: ['http'],
|
||||
},
|
||||
];
|
||||
|
||||
// const TRANSCODE_UNSUPPORTED_DIRECT_PLAY_PROFILES = [
|
||||
// {
|
||||
// containers: ["m4a", "mp4"],
|
||||
// audioCodecs: ["alac"],
|
||||
// protocols: ["http"],
|
||||
// maxAudioChannels: 2
|
||||
// },
|
||||
// {
|
||||
// containers: ["m4a", "mp4"],
|
||||
// audioCodecs: ["ac3", "eac3"],
|
||||
// protocols: ["http"],
|
||||
// maxAudioChannels: 6
|
||||
// },
|
||||
// {
|
||||
// containers: ["ogg"],
|
||||
// audioCodecs: ["flac", "speex"],
|
||||
// protocols: ["http"],
|
||||
// maxAudioChannels: 2
|
||||
// },
|
||||
// {
|
||||
// containers: ["wav"],
|
||||
// audioCodecs: ["adpcm", "gsm", "aac", "mp3"],
|
||||
// protocols: ["http"],
|
||||
// maxAudioChannels: 2
|
||||
// },
|
||||
// {
|
||||
// containers: ["mkv"],
|
||||
// audioCodecs: ["aac", "mp3", "flac", "opus", "vorbis", "ac3", "eac3", "dts"],
|
||||
// protocols: ["http"],
|
||||
// maxAudioChannels: 8
|
||||
// },
|
||||
// {
|
||||
// containers: ["avi"],
|
||||
// audioCodecs: ["mp3", "ac3", "pcm", "aac"],
|
||||
// protocols: ["http"],
|
||||
// maxAudioChannels: 6
|
||||
// },
|
||||
// {
|
||||
// containers: ["asf", "wma"],
|
||||
// audioCodecs: ["wma", "pcm", "mp3"],
|
||||
// protocols: ["http"],
|
||||
// maxAudioChannels: 2
|
||||
// },
|
||||
// {
|
||||
// containers: ["caf"],
|
||||
// audioCodecs: ["pcm", "aac", "alac", "mp3"],
|
||||
// protocols: ["http"],
|
||||
// maxAudioChannels: 8
|
||||
// },
|
||||
// {
|
||||
// containers: ["3gp"],
|
||||
// audioCodecs: ["aac", "amr"],
|
||||
// protocols: ["http"],
|
||||
// maxAudioChannels: 2
|
||||
// },
|
||||
// {
|
||||
// containers: ["amr"],
|
||||
// audioCodecs: ["amr"],
|
||||
// protocols: ["http"],
|
||||
// maxAudioChannels: 1
|
||||
// },
|
||||
// {
|
||||
// containers: ["ape"],
|
||||
// audioCodecs: ["ape"],
|
||||
// protocols: ["http"],
|
||||
// maxAudioChannels: 2
|
||||
// },
|
||||
// {
|
||||
// containers: ["wv"],
|
||||
// audioCodecs: ["wavpack"],
|
||||
// protocols: ["http"],
|
||||
// maxAudioChannels: 2
|
||||
// },
|
||||
// {
|
||||
// containers: ["ac3"],
|
||||
// audioCodecs: ["ac3"],
|
||||
// protocols: ["http"],
|
||||
// maxAudioChannels: 6
|
||||
// },
|
||||
// {
|
||||
// containers: ["eac3"],
|
||||
// audioCodecs: ["eac3"],
|
||||
// protocols: ["http"],
|
||||
// maxAudioChannels: 8
|
||||
// },
|
||||
// {
|
||||
// containers: ["dts"],
|
||||
// audioCodecs: ["dts"],
|
||||
// protocols: ["http"],
|
||||
// maxAudioChannels: 8
|
||||
// }
|
||||
// ];
|
||||
|
||||
function appendTranscodeParams(url: string, format?: string, bitrate?: number) {
|
||||
let streamUrl = url;
|
||||
|
||||
if (format) {
|
||||
streamUrl += `&format=${format}`;
|
||||
}
|
||||
if (bitrate !== undefined) {
|
||||
streamUrl += `&maxBitRate=${bitrate}`;
|
||||
}
|
||||
|
||||
return streamUrl;
|
||||
}
|
||||
|
||||
function sortAndPaginate<T>(
|
||||
items: T[],
|
||||
options: {
|
||||
@@ -1805,32 +1951,83 @@ export const SubsonicController: InternalControllerEndpoint = {
|
||||
|
||||
return totalRecordCount;
|
||||
},
|
||||
getStreamUrl: ({ apiClientProps: { server }, query }) => {
|
||||
const {
|
||||
bitrate,
|
||||
format,
|
||||
id,
|
||||
mediaType = 'song',
|
||||
offset = 0,
|
||||
transcode,
|
||||
transcodeParams,
|
||||
} = query;
|
||||
getStreamUrl: async ({ apiClientProps, context, query }) => {
|
||||
const { server } = apiClientProps;
|
||||
const { bitrate, format, id, mediaType = 'song', transcode } = query;
|
||||
const { serverFeatures, transcode: transcodeSettings } = context || {};
|
||||
|
||||
if (transcodeParams != null) {
|
||||
const q = `mediaId=${encodeURIComponent(id)}&mediaType=${mediaType}&offset=${offset}&transcodeParams=${transcodeParams}&v=1.13.0&c=Feishin`;
|
||||
return `${server?.url}/rest/getTranscodeStream.view?${q}&${server?.credential}`;
|
||||
}
|
||||
const streamUrl = `${server?.url}/rest/stream.view?id=${id}&v=1.13.0&c=Feishin&${server?.credential}`;
|
||||
|
||||
let url = `${server?.url}/rest/stream.view?id=${id}&v=1.13.0&c=Feishin&${server?.credential}`;
|
||||
// If transcoding is explicitly enabled, just return the direct transcoded stream URL
|
||||
if (transcode) {
|
||||
if (format) {
|
||||
url += `&format=${format}`;
|
||||
}
|
||||
if (bitrate !== undefined) {
|
||||
url += `&maxBitRate=${bitrate}`;
|
||||
}
|
||||
return appendTranscodeParams(streamUrl, format, bitrate);
|
||||
}
|
||||
return url;
|
||||
|
||||
// If the server supports transcoding decision, always use it to determine if we need to transcode
|
||||
if (serverFeatures?.[ServerFeature.OS_TRANSCODE_DECISION]) {
|
||||
const maxTranscodingAudioBitrate = transcodeSettings?.bitrate || 0;
|
||||
|
||||
const transcodingProfiles = (transcodeSettings?.format || []).map((format) => {
|
||||
return {
|
||||
audioCodec: format,
|
||||
container: format,
|
||||
maxAudioChannels: 2,
|
||||
protocol: 'http',
|
||||
};
|
||||
});
|
||||
|
||||
const transcodeDecision = await ssApiClient(apiClientProps).getTranscodeDecision({
|
||||
body: {
|
||||
codecProfiles: [],
|
||||
directPlayProfiles: TRANSCODE_DIRECT_PLAY_PROFILES,
|
||||
maxAudioBitrate: 512000,
|
||||
maxTranscodingAudioBitrate,
|
||||
name: 'Feishin',
|
||||
platform: 'Web',
|
||||
transcodingProfiles,
|
||||
},
|
||||
query: {
|
||||
mediaId: id,
|
||||
mediaType,
|
||||
},
|
||||
});
|
||||
|
||||
if (transcodeDecision.status !== 200) {
|
||||
throw new Error('Failed to get transcode decision');
|
||||
}
|
||||
|
||||
const td = transcodeDecision.body.transcodeDecision;
|
||||
const requiresTranscoding = !td?.canDirectPlay;
|
||||
|
||||
// If the server does not require transcoding, just return the direct stream URL
|
||||
if (!requiresTranscoding) {
|
||||
return streamUrl;
|
||||
}
|
||||
|
||||
logFn.info(`Song ${id} requires transcoding: ${[td.transcodeReason].join(', ')}`);
|
||||
|
||||
// If the server does not return transcode params, manually create the transcode params
|
||||
if (!td.transcodeParams) {
|
||||
return appendTranscodeParams(streamUrl, format, bitrate);
|
||||
}
|
||||
|
||||
const transcodeStreamUrl = await ssApiClient(apiClientProps).getTranscodeStream({
|
||||
query: {
|
||||
mediaId: id,
|
||||
mediaType,
|
||||
offset: 0,
|
||||
transcodeParams: td.transcodeParams,
|
||||
},
|
||||
});
|
||||
|
||||
if (transcodeStreamUrl.status !== 200) {
|
||||
throw new Error('Failed to get transcode stream');
|
||||
}
|
||||
|
||||
return transcodeStreamUrl.body;
|
||||
}
|
||||
|
||||
return streamUrl;
|
||||
},
|
||||
getStructuredLyrics: async (args) => {
|
||||
const { apiClientProps, query } = args;
|
||||
@@ -1927,32 +2124,6 @@ export const SubsonicController: InternalControllerEndpoint = {
|
||||
totalRecordCount: res.totalRecordCount,
|
||||
};
|
||||
},
|
||||
getTranscodeDecision: async (args) => {
|
||||
const { apiClientProps, body, query } = args;
|
||||
|
||||
const defaultBody = {
|
||||
name: 'Feishin',
|
||||
platform: 'Web',
|
||||
};
|
||||
|
||||
const res = await ssApiClient(apiClientProps).getTranscodeDecision({
|
||||
body: body ?? defaultBody,
|
||||
query: {
|
||||
mediaId: query.id,
|
||||
mediaType: query.type === 'song' ? 'song' : 'podcast',
|
||||
},
|
||||
});
|
||||
|
||||
if (res.status !== 200) {
|
||||
throw new Error('Failed to get transcode decision');
|
||||
}
|
||||
|
||||
const td = res.body.transcodeDecision;
|
||||
return {
|
||||
decision: td.canDirectPlay ? 'direct' : 'transcode',
|
||||
transcodeParams: td.transcodeParams,
|
||||
};
|
||||
},
|
||||
getUserInfo: async (args) => {
|
||||
const { apiClientProps, query } = args;
|
||||
|
||||
|
||||
@@ -124,10 +124,10 @@ export const MpvPlayerEngine = (props: MpvPlayerEngineProps) => {
|
||||
if (!radioState.currentStreamUrl) {
|
||||
const playerData = usePlayerStore.getState().getPlayerData();
|
||||
const currentSongUrl = playerData.currentSong
|
||||
? getSongUrl(playerData.currentSong, transcode)
|
||||
? await getSongUrl(playerData.currentSong, transcode)
|
||||
: undefined;
|
||||
const nextSongUrl = playerData.nextSong
|
||||
? getSongUrl(playerData.nextSong, transcode)
|
||||
? await getSongUrl(playerData.nextSong, transcode)
|
||||
: undefined;
|
||||
|
||||
if (currentSongUrl && nextSongUrl && !hasPopulatedQueueRef.current && mpvPlayer) {
|
||||
@@ -274,14 +274,14 @@ export const MpvPlayerEngine = (props: MpvPlayerEngineProps) => {
|
||||
onMediaPrev: () => {
|
||||
replaceMpvQueue(transcode);
|
||||
},
|
||||
onNextSongInsertion: (song) => {
|
||||
onNextSongInsertion: async (song) => {
|
||||
const radioState = useRadioStore.getState();
|
||||
|
||||
if (radioState.currentStreamUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextSongUrl = song ? getSongUrl(song, transcode) : undefined;
|
||||
const nextSongUrl = song ? await getSongUrl(song, transcode) : undefined;
|
||||
mpvPlayer?.setQueueNext(nextSongUrl);
|
||||
},
|
||||
onPlayerPlay: () => {
|
||||
@@ -339,19 +339,19 @@ export const MpvPlayerEngine = (props: MpvPlayerEngineProps) => {
|
||||
|
||||
MpvPlayerEngine.displayName = 'MpvPlayerEngine';
|
||||
|
||||
function handleMpvAutoNext(transcode: {
|
||||
async function handleMpvAutoNext(transcode: {
|
||||
bitrate?: number | undefined;
|
||||
enabled: boolean;
|
||||
format?: string | undefined;
|
||||
}) {
|
||||
const playerData = usePlayerStore.getState().getPlayerData();
|
||||
const nextSongUrl = playerData.nextSong
|
||||
? getSongUrl(playerData.nextSong, transcode)
|
||||
? await getSongUrl(playerData.nextSong, transcode)
|
||||
: undefined;
|
||||
mpvPlayer?.autoNext(nextSongUrl);
|
||||
}
|
||||
|
||||
function replaceMpvQueue(transcode: {
|
||||
async function replaceMpvQueue(transcode: {
|
||||
bitrate?: number | undefined;
|
||||
enabled: boolean;
|
||||
format?: string | undefined;
|
||||
@@ -365,10 +365,10 @@ function replaceMpvQueue(transcode: {
|
||||
|
||||
const playerData = usePlayerStore.getState().getPlayerData();
|
||||
const currentSongUrl = playerData.currentSong
|
||||
? getSongUrl(playerData.currentSong, transcode)
|
||||
? await getSongUrl(playerData.currentSong, transcode)
|
||||
: undefined;
|
||||
const nextSongUrl = playerData.nextSong
|
||||
? getSongUrl(playerData.nextSong, transcode)
|
||||
? await getSongUrl(playerData.nextSong, transcode)
|
||||
: undefined;
|
||||
mpvPlayer?.setQueue(currentSongUrl, nextSongUrl, false);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useMemo, useRef } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { api } from '/@/renderer/api';
|
||||
import { TranscodingConfig } from '/@/renderer/store';
|
||||
@@ -10,46 +11,58 @@ export function useSongUrl(
|
||||
transcode: TranscodingConfig,
|
||||
): string | undefined {
|
||||
const prior = useRef(['', '']);
|
||||
const shouldReusePrior = Boolean(
|
||||
song?._serverId && current && prior.current[0] === song._uniqueId && prior.current[1],
|
||||
);
|
||||
|
||||
return useMemo(() => {
|
||||
if (song?._serverId) {
|
||||
// If we are the current track, we do not want a transcoding
|
||||
// reconfiguration to force a restart.
|
||||
if (current && prior.current[0] === song._uniqueId) {
|
||||
return prior.current[1];
|
||||
}
|
||||
|
||||
const url = api.controller.getStreamUrl({
|
||||
apiClientProps: { serverId: song._serverId },
|
||||
const { data: queryStreamUrl } = useQuery({
|
||||
enabled: Boolean(song?._serverId) && !shouldReusePrior,
|
||||
queryFn: () =>
|
||||
api.controller.getStreamUrl({
|
||||
apiClientProps: { serverId: song!._serverId },
|
||||
query: {
|
||||
bitrate: transcode.bitrate,
|
||||
format: transcode.format,
|
||||
id: song.id,
|
||||
id: song!.id,
|
||||
transcode: transcode.enabled,
|
||||
},
|
||||
});
|
||||
}),
|
||||
queryKey: [
|
||||
song?._serverId,
|
||||
'stream-url',
|
||||
song?.id,
|
||||
shouldReusePrior ? 'reuse-prior' : transcode.bitrate,
|
||||
shouldReusePrior ? 'reuse-prior' : transcode.format,
|
||||
shouldReusePrior ? 'reuse-prior' : transcode.enabled,
|
||||
] as const,
|
||||
staleTime: 60 * 1000,
|
||||
});
|
||||
|
||||
// transcoding enabled; save the updated result
|
||||
prior.current = [song._uniqueId, url];
|
||||
return url;
|
||||
useEffect(() => {
|
||||
if (!song?._serverId) {
|
||||
prior.current = ['', ''];
|
||||
return;
|
||||
}
|
||||
|
||||
// no track; clear result
|
||||
prior.current = ['', ''];
|
||||
return undefined;
|
||||
}, [
|
||||
song?._serverId,
|
||||
song?._uniqueId,
|
||||
song?.id,
|
||||
current,
|
||||
transcode.bitrate,
|
||||
transcode.format,
|
||||
transcode.enabled,
|
||||
]);
|
||||
if (!queryStreamUrl) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save resolved URL to avoid restarting current track on transcode setting changes.
|
||||
prior.current = [song._uniqueId, queryStreamUrl];
|
||||
}, [song?._serverId, song?._uniqueId, queryStreamUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!song?._serverId) {
|
||||
prior.current = ['', ''];
|
||||
}
|
||||
}, [song?._serverId]);
|
||||
|
||||
return shouldReusePrior ? prior.current[1] : queryStreamUrl;
|
||||
}
|
||||
|
||||
export const getSongUrl = (song: QueueSong, transcode: TranscodingConfig) => {
|
||||
return api.controller.getStreamUrl({
|
||||
export const getSongUrl = async (song: QueueSong, transcode: TranscodingConfig) => {
|
||||
const url = await api.controller.getStreamUrl({
|
||||
apiClientProps: { serverId: song._serverId },
|
||||
query: {
|
||||
bitrate: transcode.bitrate,
|
||||
@@ -58,4 +71,6 @@ export const getSongUrl = (song: QueueSong, transcode: TranscodingConfig) => {
|
||||
transcode: transcode.enabled,
|
||||
},
|
||||
});
|
||||
|
||||
return url;
|
||||
};
|
||||
|
||||
@@ -1424,11 +1424,10 @@ export type ControllerEndpoint = {
|
||||
getSongDetail: (args: SongDetailArgs) => Promise<SongDetailResponse>;
|
||||
getSongList: (args: SongListArgs) => Promise<SongListResponse>;
|
||||
getSongListCount: (args: SongListCountArgs) => Promise<number>;
|
||||
getStreamUrl: (args: StreamArgs) => string;
|
||||
getStreamUrl: (args: StreamArgs) => Promise<string>;
|
||||
getStructuredLyrics?: (args: StructuredLyricsArgs) => Promise<StructuredLyric[]>;
|
||||
getTagList?: (args: TagListArgs) => Promise<TagListResponse>;
|
||||
getTopSongs: (args: TopSongListArgs) => Promise<TopSongListResponse>;
|
||||
getTranscodeDecision: (args: TranscodeDecisionArgs) => Promise<TranscodeDecisionResponse>;
|
||||
getUserInfo: (args: UserInfoArgs) => Promise<UserInfoResponse>;
|
||||
getUserList?: (args: UserListArgs) => Promise<UserListResponse>;
|
||||
movePlaylistItem?: (args: MoveItemArgs) => Promise<void>;
|
||||
@@ -1577,9 +1576,6 @@ export type InternalControllerEndpoint = {
|
||||
) => Promise<StructuredLyric[]>;
|
||||
getTagList?: (args: ReplaceApiClientProps<TagListArgs>) => Promise<TagListResponse>;
|
||||
getTopSongs: (args: ReplaceApiClientProps<TopSongListArgs>) => Promise<TopSongListResponse>;
|
||||
getTranscodeDecision: (
|
||||
args: ReplaceApiClientProps<TranscodeDecisionArgs>,
|
||||
) => Promise<TranscodeDecisionResponse>;
|
||||
getUserInfo: (args: ReplaceApiClientProps<UserInfoArgs>) => Promise<UserInfoResponse>;
|
||||
getUserList?: (args: ReplaceApiClientProps<UserListArgs>) => Promise<UserListResponse>;
|
||||
movePlaylistItem?: (args: ReplaceApiClientProps<MoveItemArgs>) => Promise<void>;
|
||||
|
||||
Reference in New Issue
Block a user