import {z} from 'zod' export type GetApiItemsItemIdPlaybackInfoArgs = {itemId: string, userId: string}; export type GetApiItemsItemIdPlaybackInfoOptions = {baseUrl?: string | undefined, CustomAuthentication?: string | undefined, fetch?: typeof fetch | undefined}; export const mediaProtocol = z.enum(["File", "Http", "Rtmp", "Rtsp", "Udp", "Rtp", "Ftp"]); export const mediaSourceType = z.enum(["Default", "Grouping", "Placeholder"]); export const videoType = z.enum(["VideoFile", "Iso", "Dvd", "BluRay"]); export const isoType = z.enum(["Dvd", "BluRay"]); export const video3DFormat = z.enum(["HalfSideBySide", "FullSideBySide", "FullTopAndBottom", "HalfTopAndBottom", "MVC"]); export const mediaStreamType = z.enum(["Audio", "Video", "Subtitle", "EmbeddedImage"]); export const subtitleDeliveryMethod = z.enum(["Encode", "Embed", "External", "Hls"]); export const mediaStream = z.object({Codec: z.string().nullable().optional(), CodecTag: z.string().nullable().optional(), Language: z.string().nullable().optional(), ColorRange: z.string().nullable().optional(), ColorSpace: z.string().nullable().optional(), ColorTransfer: z.string().nullable().optional(), ColorPrimaries: z.string().nullable().optional(), Comment: z.string().nullable().optional(), TimeBase: z.string().nullable().optional(), CodecTimeBase: z.string().nullable().optional(), Title: z.string().nullable().optional(), VideoRange: z.string().nullable().optional(), localizedUndefined: z.string().nullable().optional(), localizedDefault: z.string().nullable().optional(), localizedForced: z.string().nullable().optional(), DisplayTitle: z.string().nullable().optional(), NalLengthSize: z.string().nullable().optional(), IsInterlaced: z.boolean().optional(), IsAVC: z.boolean().nullable().optional(), ChannelLayout: z.string().nullable().optional(), BitRate: z.number().int().nullable().optional(), BitDepth: z.number().int().nullable().optional(), RefFrames: z.number().int().nullable().optional(), PacketLength: z.number().int().nullable().optional(), Channels: z.number().int().nullable().optional(), SampleRate: z.number().int().nullable().optional(), IsDefault: z.boolean().optional(), IsForced: z.boolean().optional(), Height: z.number().int().nullable().optional(), Width: z.number().int().nullable().optional(), AverageFrameRate: z.number().nullable().optional(), RealFrameRate: z.number().nullable().optional(), Profile: z.string().nullable().optional(), Type: mediaStreamType.optional(), AspectRatio: z.string().nullable().optional(), Index: z.number().int().optional(), Score: z.number().int().nullable().optional(), IsExternal: z.boolean().optional(), DeliveryMethod: subtitleDeliveryMethod.optional(), DeliveryUrl: z.string().nullable().optional(), IsExternalUrl: z.boolean().nullable().optional(), IsTextSubtitleStream: z.boolean().optional(), SupportsExternalStream: z.boolean().optional(), Path: z.string().nullable().optional(), PixelFormat: z.string().nullable().optional(), Level: z.number().nullable().optional(), IsAnamorphic: z.boolean().nullable().optional()}); export const mediaAttachment = z.object({Codec: z.string().nullable().optional(), CodecTag: z.string().nullable().optional(), Comment: z.string().nullable().optional(), Index: z.number().int().optional(), FileName: z.string().nullable().optional(), MimeType: z.string().nullable().optional(), DeliveryUrl: z.string().nullable().optional()}); export const transportStreamTimestamp = z.enum(["None", "Zero", "Valid"]); export const mediaSourceInfo = z.object({Protocol: mediaProtocol.optional(), Id: z.string().nullable().optional(), Path: z.string().nullable().optional(), EncoderPath: z.string().nullable().optional(), EncoderProtocol: mediaProtocol.optional(), Type: mediaSourceType.optional(), Container: z.string().nullable().optional(), Size: z.number().int().nullable().optional(), Name: z.string().nullable().optional(), IsRemote: z.boolean().optional(), ETag: z.string().nullable().optional(), RunTimeTicks: z.number().int().nullable().optional(), ReadAtNativeFramerate: z.boolean().optional(), IgnoreDts: z.boolean().optional(), IgnoreIndex: z.boolean().optional(), GenPtsInput: z.boolean().optional(), SupportsTranscoding: z.boolean().optional(), SupportsDirectStream: z.boolean().optional(), SupportsDirectPlay: z.boolean().optional(), IsInfiniteStream: z.boolean().optional(), RequiresOpening: z.boolean().optional(), OpenToken: z.string().nullable().optional(), RequiresClosing: z.boolean().optional(), LiveStreamId: z.string().nullable().optional(), BufferMs: z.number().int().nullable().optional(), RequiresLooping: z.boolean().optional(), SupportsProbing: z.boolean().optional(), VideoType: videoType.optional(), IsoType: isoType.optional(), Video3DFormat: video3DFormat.optional(), MediaStreams: z.array(mediaStream).nullable().optional(), MediaAttachments: z.array(mediaAttachment).nullable().optional(), Formats: z.array(z.string()).nullable().optional(), Bitrate: z.number().int().nullable().optional(), Timestamp: transportStreamTimestamp.optional(), RequiredHttpHeaders: z.record(z.string(), z.string()).nullable().optional(), TranscodingUrl: z.string().nullable().optional(), TranscodingSubProtocol: z.string().nullable().optional(), TranscodingContainer: z.string().nullable().optional(), AnalyzeDurationMs: z.number().int().nullable().optional(), DefaultAudioStreamIndex: z.number().int().nullable().optional(), DefaultSubtitleStreamIndex: z.number().int().nullable().optional()}); export const playbackErrorCode = z.enum(["NotAllowed", "NoCompatibleStream", "RateLimitExceeded"]); export const playbackInfoResponse = z.object({MediaSources: z.array(mediaSourceInfo).nullable().optional(), PlaySessionId: z.string().nullable().optional(), ErrorCode: playbackErrorCode.optional()}); export const validateResponse = async ( schema: { readonly '~standard': { readonly validate: (value: unknown) => ({ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }) | Promise<{ readonly value: Output; readonly issues?: undefined } | { readonly issues: ReadonlyArray<{ readonly message: string; readonly path?: ReadonlyArray | undefined }> }> } }, value: unknown ): Promise => { const result = await schema['~standard'].validate(value) if (result.issues !== undefined) { const detail = result.issues .map(({ message, path }) => path?.length ? `${path.map((segment) => String(typeof segment === 'object' ? segment.key : segment)).join('.')}: ${message}` : message ) .join('; ') throw new Error(`Response does not match the schema: ${detail}`, { cause: { issues: result.issues } }) } return result.value }; /** * Gets live playback media info for an item. * * GET /Items/{itemId}/PlaybackInfo */ export const getApiItemsItemIdPlaybackInfo = async (args: GetApiItemsItemIdPlaybackInfoArgs, options?: GetApiItemsItemIdPlaybackInfoOptions) => { const {baseUrl = "http://localhost", fetch: fetchFn = fetch, CustomAuthentication} = options ?? {} const query = new URLSearchParams() const headers = new Headers() headers.set('accept', "application/json") if (args.userId != null) query.append("userId", String(args.userId)) if (CustomAuthentication !== undefined) headers.set("X-Emby-Authorization", CustomAuthentication) const url = `${baseUrl.replace(/\/+$/, '')}/Items/${encodeURIComponent(String(args.itemId))}/PlaybackInfo${query.size > 0 ? `?${query}` : ''}` const res = await fetchFn(url, {method: "GET", headers}) if (!res.ok) throw new Error(`Request failed with status ${res.status}`, {cause: {status: res.status, body: await res.text()}}) return validateResponse(playbackInfoResponse, await res.json()) };