This commit is contained in:
Fredrik Burmester
2024-10-08 15:39:44 +02:00
parent a5b4f6cc78
commit ec0843d737
33 changed files with 895 additions and 527 deletions

View File

@@ -0,0 +1,63 @@
// utils/getDefaultPlaySettings.ts
import { BITRATES } from "@/components/BitrateSelector";
import {
BaseItemDto,
MediaSourceInfo,
} from "@jellyfin/sdk/lib/generated-client";
import { Settings } from "../atoms/settings";
interface PlaySettings {
item: BaseItemDto;
bitrate: (typeof BITRATES)[0];
mediaSource: MediaSourceInfo | undefined;
audioIndex?: number | null;
subtitleIndex?: number | null;
}
export function getDefaultPlaySettings(
item: BaseItemDto,
settings: Settings
): PlaySettings {
if (item.Type === "Program") {
return {
item,
bitrate: BITRATES[0],
mediaSource: undefined,
audioIndex: undefined,
subtitleIndex: undefined,
};
}
// 1. Get first media source
const mediaSource = item.MediaSources?.[0];
if (!mediaSource) throw new Error("No media source found");
// 2. Get default or preferred audio
const defaultAudioIndex = mediaSource?.DefaultAudioStreamIndex;
const preferedAudioIndex = mediaSource?.MediaStreams?.find(
(x) => x.Language === settings?.defaultAudioLanguage
)?.Index;
const firstAudioIndex = mediaSource?.MediaStreams?.find(
(x) => x.Type === "Audio"
)?.Index;
// 3. Get default or preferred subtitle
const preferedSubtitleIndex = mediaSource?.MediaStreams?.find(
(x) => x.Language === settings?.defaultSubtitleLanguage?.value
)?.Index;
const defaultSubtitleIndex = mediaSource?.MediaStreams?.find(
(stream) => stream.Type === "Subtitle" && stream.IsDefault
)?.Index;
// 4. Get default bitrate
const bitrate = BITRATES[0];
return {
item,
bitrate,
mediaSource,
audioIndex: preferedAudioIndex ?? defaultAudioIndex ?? firstAudioIndex,
subtitleIndex: preferedSubtitleIndex ?? defaultSubtitleIndex ?? -1,
};
}

View File

@@ -34,8 +34,8 @@ export const getStreamUrl = async ({
height?: number;
mediaSourceId?: string | null;
}): Promise<{
url: string | null | undefined;
sessionId: string | null | undefined;
url: string | null;
sessionId: string | null;
} | null> => {
if (!api || !userId || !item?.Id) {
return null;
@@ -66,7 +66,7 @@ export const getStreamUrl = async ({
}
);
const transcodeUrl = res0.data.MediaSources?.[0].TranscodingUrl;
sessionId = res0.data.PlaySessionId;
sessionId = res0.data.PlaySessionId || null;
if (transcodeUrl) {
return { url: `${api.basePath}${transcodeUrl}`, sessionId };
@@ -75,29 +75,6 @@ export const getStreamUrl = async ({
const itemId = item.Id;
// const res2 = await api.axiosInstance.post(
// `${api.basePath}/Items/${itemId}/PlaybackInfo`,
// {
// DeviceProfile: deviceProfile,
// UserId: userId,
// MaxStreamingBitrate: maxStreamingBitrate,
// StartTimeTicks: startTimeTicks,
// EnableTranscoding: maxStreamingBitrate ? true : undefined,
// AutoOpenLiveStream: true,
// MediaSourceId: mediaSourceId,
// AllowVideoStreamCopy: maxStreamingBitrate ? false : true,
// AudioStreamIndex: audioStreamIndex,
// SubtitleStreamIndex: subtitleStreamIndex,
// DeInterlace: true,
// BreakOnNonKeyFrames: false,
// CopyTimestamps: false,
// EnableMpegtsM2TsMode: false,
// },
// {
// headers: getAuthHeaders(api),
// }
// );
const res2 = await getMediaInfoApi(api).getPlaybackInfo(
{
userId,
@@ -120,55 +97,64 @@ export const getStreamUrl = async ({
breakOnNonKeyFrames: false,
copyTimestamps: false,
enableMpegtsM2TsMode: false,
},
}
);
sessionId = res2.data.PlaySessionId;
sessionId = res2.data.PlaySessionId || null;
mediaSource = res2.data.MediaSources?.find(
(source: MediaSourceInfo) => source.Id === mediaSourceId
);
if (mediaSource?.SupportsDirectPlay || forceDirectPlay === true) {
if (item.MediaType === "Video") {
url = `${api.basePath}/Videos/${itemId}/stream.mp4?playSessionId=${sessionData?.PlaySessionId}&mediaSourceId=${mediaSource?.Id}&static=true&subtitleStreamIndex=${subtitleStreamIndex}&audioStreamIndex=${audioStreamIndex}&deviceId=${api.deviceInfo.id}&api_key=${api.accessToken}`;
} else if (item.MediaType === "Audio") {
const searchParams = new URLSearchParams({
UserId: userId,
DeviceId: api.deviceInfo.id,
MaxStreamingBitrate: "140000000",
Container:
"opus,webm|opus,mp3,aac,m4a|aac,m4b|aac,flac,webma,webm|webma,wav,ogg",
TranscodingContainer: "mp4",
TranscodingProtocol: "hls",
AudioCodec: "aac",
api_key: api.accessToken,
PlaySessionId: sessionData?.PlaySessionId || "",
StartTimeTicks: "0",
EnableRedirection: "true",
EnableRemoteMedia: "false",
});
url = `${
api.basePath
}/Audio/${itemId}/universal?${searchParams.toString()}`;
console.log("getStreamUrl ~ ", item.MediaType);
if (item.MediaType === "Video") {
if (mediaSource?.TranscodingUrl) {
return {
url: `${api.basePath}${mediaSource.TranscodingUrl}`,
sessionId: sessionId,
};
}
if (mediaSource?.SupportsDirectPlay || forceDirectPlay === true) {
return {
url: `${api.basePath}/Videos/${itemId}/stream.mp4?playSessionId=${sessionData?.PlaySessionId}&mediaSourceId=${mediaSource?.Id}&static=true&subtitleStreamIndex=${subtitleStreamIndex}&audioStreamIndex=${audioStreamIndex}&deviceId=${api.deviceInfo.id}&api_key=${api.accessToken}`,
sessionId: sessionId,
};
}
} else if (mediaSource?.TranscodingUrl) {
url = `${api.basePath}${mediaSource.TranscodingUrl}`;
}
if (!url) {
console.log("getStreamUrl: no url found", {
api: api.basePath,
userId,
item: item.Id,
mediaSourceId,
if (item.MediaType === "Audio") {
console.log("getStreamUrl ~ Audio");
if (mediaSource?.TranscodingUrl) {
return { url: `${api.basePath}${mediaSource.TranscodingUrl}`, sessionId };
}
const searchParams = new URLSearchParams({
UserId: userId,
DeviceId: api.deviceInfo.id,
MaxStreamingBitrate: "140000000",
Container:
"opus,webm|opus,mp3,aac,m4a|aac,m4b|aac,flac,webma,webm|webma,wav,ogg",
TranscodingContainer: "mp4",
TranscodingProtocol: "hls",
AudioCodec: "aac",
api_key: api.accessToken,
PlaySessionId: sessionData?.PlaySessionId || "",
StartTimeTicks: "0",
EnableRedirection: "true",
EnableRemoteMedia: "false",
});
return null;
return {
url: `${
api.basePath
}/Audio/${itemId}/universal?${searchParams.toString()}`,
sessionId,
};
}
return {
url,
sessionId,
};
throw new Error("Unsupported media type");
};

View File

@@ -1,6 +1,7 @@
import { itemRouter } from "@/components/common/TouchableItemRouter";
import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
import axios from "axios";
import { writeToLog } from "./log";
interface IJobInput {
deviceId?: string | null;
@@ -108,6 +109,7 @@ export async function cancelAllJobs({ authHeader, url, deviceId }: IJobInput) {
});
});
} catch (error) {
writeToLog("ERROR", "Failed to cancel all jobs", error);
console.error(error);
return false;
}