diff --git a/.gitignore b/.gitignore index 2813f770..a5e835a0 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,7 @@ npm-debug.* .ruby-lsp .cursor/ .claude/ +CLAUDE.md # Environment and Configuration expo-env.d.ts @@ -66,3 +67,5 @@ streamyfin-4fec1-firebase-adminsdk.json # Version and Backup Files /version-backup-* modules/background-downloader/android/build/* +/modules/sf-player/android/build +/modules/music-controls/android/build diff --git a/README.md b/README.md index 90349db4..39535770 100644 --- a/README.md +++ b/README.md @@ -104,6 +104,7 @@ You can contribute translations directly on our [Crowdin project page](https://c 1. Use node `>20` 2. Install dependencies `bun i && bun run submodule-reload` 3. Make sure you have xcode and/or android studio installed. (follow the guides for expo: https://docs.expo.dev/workflow/android-studio-emulator/) + - If iOS builds fail with `missing Metal Toolchain` (KSPlayer shaders), run `npm run ios:install-metal-toolchain` once 4. Install BiomeJS extension in VSCode/Your IDE (https://biomejs.dev/) 4. run `npm run prebuild` 5. Create an expo dev build by running `npm run ios` or `npm run android`. This will open a simulator on your computer and run the app diff --git a/app.config.js b/app.config.js index 2e37927b..527e0cab 100644 --- a/app.config.js +++ b/app.config.js @@ -6,6 +6,9 @@ module.exports = ({ config }) => { "react-native-google-cast", { useDefaultExpandedMediaControls: true }, ]); + + // KSPlayer for iOS (GPU acceleration + native PiP) + config.plugins.push("./plugins/withKSPlayer.js"); } // Only override googleServicesFile if env var is set diff --git a/app.json b/app.json index 5d4e9f4a..d81bcae1 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "Streamyfin", "slug": "streamyfin", - "version": "0.48.0", + "version": "0.50.1", "orientation": "default", "icon": "./assets/images/icon.png", "scheme": "streamyfin", @@ -34,7 +34,7 @@ }, "android": { "jsEngine": "hermes", - "versionCode": 85, + "versionCode": 88, "adaptiveIcon": { "foregroundImage": "./assets/images/icon-android-plain.png", "monochromeImage": "./assets/images/icon-android-themed.png", @@ -53,29 +53,16 @@ "@react-native-tvos/config-tv", "expo-router", "expo-font", - [ - "react-native-video", - { - "enableNotificationControls": true, - "enableBackgroundAudio": true, - "androidExtensions": { - "useExoplayerRtsp": false, - "useExoplayerSmoothStreaming": false, - "useExoplayerHls": true, - "useExoplayerDash": false - } - } - ], + "./plugins/withExcludeMedia3Dash.js", [ "expo-build-properties", { "ios": { - "deploymentTarget": "15.6", - "useFrameworks": "static" + "deploymentTarget": "15.6" }, "android": { "buildArchs": ["arm64-v8a", "x86_64"], - "compileSdkVersion": 35, + "compileSdkVersion": 36, "targetSdkVersion": 35, "buildToolsVersion": "35.0.0", "kotlinVersion": "2.0.21", diff --git a/app/(auth)/(tabs)/(favorites)/see-all.tsx b/app/(auth)/(tabs)/(favorites)/see-all.tsx new file mode 100644 index 00000000..e3b0198a --- /dev/null +++ b/app/(auth)/(tabs)/(favorites)/see-all.tsx @@ -0,0 +1,212 @@ +import type { Api } from "@jellyfin/sdk"; +import type { + BaseItemDto, + BaseItemKind, +} from "@jellyfin/sdk/lib/generated-client"; +import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; +import { FlashList } from "@shopify/flash-list"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import { Stack, useLocalSearchParams } from "expo-router"; +import { t } from "i18next"; +import { useAtom } from "jotai"; +import { useCallback, useMemo } from "react"; +import { useWindowDimensions, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { Text } from "@/components/common/Text"; +import { TouchableItemRouter } from "@/components/common/TouchableItemRouter"; +import { ItemCardText } from "@/components/ItemCardText"; +import { Loader } from "@/components/Loader"; +import { ItemPoster } from "@/components/posters/ItemPoster"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; + +type FavoriteTypes = + | "Series" + | "Movie" + | "Episode" + | "Video" + | "BoxSet" + | "Playlist"; + +const favoriteTypes: readonly FavoriteTypes[] = [ + "Series", + "Movie", + "Episode", + "Video", + "BoxSet", + "Playlist", +] as const; + +function isFavoriteType(value: unknown): value is FavoriteTypes { + return ( + typeof value === "string" && + (favoriteTypes as readonly string[]).includes(value) + ); +} + +export default function FavoritesSeeAllScreen() { + const insets = useSafeAreaInsets(); + const { width: screenWidth } = useWindowDimensions(); + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + + const searchParams = useLocalSearchParams<{ + type?: string; + title?: string; + }>(); + const typeParam = searchParams.type; + const titleParam = searchParams.title; + + const itemType = useMemo(() => { + if (!isFavoriteType(typeParam)) return null; + return typeParam as BaseItemKind; + }, [typeParam]); + + const headerTitle = useMemo(() => { + if (typeof titleParam === "string" && titleParam.trim().length > 0) + return titleParam; + return ""; + }, [titleParam]); + + const pageSize = 50; + + const fetchItems = useCallback( + async ({ pageParam }: { pageParam: number }): Promise => { + if (!api || !user?.Id || !itemType) return []; + + const response = await getItemsApi(api as Api).getItems({ + userId: user.Id, + sortBy: ["SeriesSortName", "SortName"], + sortOrder: ["Ascending"], + filters: ["IsFavorite"], + recursive: true, + fields: ["PrimaryImageAspectRatio"], + collapseBoxSetItems: false, + excludeLocationTypes: ["Virtual"], + enableTotalRecordCount: true, + startIndex: pageParam, + limit: pageSize, + includeItemTypes: [itemType], + }); + + return response.data.Items || []; + }, + [api, itemType, user?.Id], + ); + + const { data, isFetching, fetchNextPage, hasNextPage, isLoading } = + useInfiniteQuery({ + queryKey: ["favorites", "see-all", itemType], + queryFn: ({ pageParam = 0 }) => fetchItems({ pageParam }), + getNextPageParam: (lastPage, pages) => { + if (!lastPage || lastPage.length < pageSize) return undefined; + return pages.reduce((acc, page) => acc + page.length, 0); + }, + initialPageParam: 0, + enabled: !!api && !!user?.Id && !!itemType, + }); + + const flatData = useMemo(() => data?.pages.flat() ?? [], [data]); + + const nrOfCols = useMemo(() => { + if (screenWidth < 350) return 2; + if (screenWidth < 600) return 3; + if (screenWidth < 900) return 5; + return 6; + }, [screenWidth]); + + const renderItem = useCallback( + ({ item, index }: { item: BaseItemDto; index: number }) => ( + + + + + + + ), + [nrOfCols], + ); + + const keyExtractor = useCallback((item: BaseItemDto) => item.Id || "", []); + + const handleEndReached = useCallback(() => { + if (hasNextPage) { + fetchNextPage(); + } + }, [fetchNextPage, hasNextPage]); + + return ( + <> + + {!itemType ? ( + + + {t("favorites.noData", { defaultValue: "No items found." })} + + + ) : isLoading ? ( + + + + ) : ( + ( + + )} + ListEmptyComponent={ + + + {t("home.no_items", { defaultValue: "No items" })} + + + } + ListFooterComponent={ + isFetching ? ( + + + + ) : null + } + /> + )} + + ); +} diff --git a/app/(auth)/(tabs)/(home)/_layout.tsx b/app/(auth)/(tabs)/(home)/_layout.tsx index bfbebed5..40a64413 100644 --- a/app/(auth)/(tabs)/(home)/_layout.tsx +++ b/app/(auth)/(tabs)/(home)/_layout.tsx @@ -238,6 +238,42 @@ export default function IndexLayout() { ), }} /> + ( + _router.back()} + className='pl-0.5' + style={{ marginRight: Platform.OS === "android" ? 16 : 0 }} + > + + + ), + }} + /> + ( + _router.back()} + className='pl-0.5' + style={{ marginRight: Platform.OS === "android" ? 16 : 0 }} + > + + + ), + }} + /> + + + + + ); +} diff --git a/app/(auth)/(tabs)/(home)/settings/plugins/marlin-search/page.tsx b/app/(auth)/(tabs)/(home)/settings/plugins/marlin-search/page.tsx index 4eb36aef..4857a928 100644 --- a/app/(auth)/(tabs)/(home)/settings/plugins/marlin-search/page.tsx +++ b/app/(auth)/(tabs)/(home)/settings/plugins/marlin-search/page.tsx @@ -60,7 +60,7 @@ export default function page() { ), }); } - }, [navigation, value]); + }, [navigation, value, pluginSettings?.marlinServerUrl?.locked, t]); if (!settings) return null; @@ -75,7 +75,10 @@ export default function page() { { updateSettings({ searchEngine: value ? "Marlin" : "Jellyfin", diff --git a/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx b/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx new file mode 100644 index 00000000..3cd4b11f --- /dev/null +++ b/app/(auth)/(tabs)/(home)/settings/plugins/streamystats/page.tsx @@ -0,0 +1,245 @@ +import { useQueryClient } from "@tanstack/react-query"; +import { useNavigation } from "expo-router"; +import { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + Linking, + ScrollView, + Switch, + TextInput, + TouchableOpacity, + View, +} from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { toast } from "sonner-native"; +import { Text } from "@/components/common/Text"; +import { ListGroup } from "@/components/list/ListGroup"; +import { ListItem } from "@/components/list/ListItem"; +import { useSettings } from "@/utils/atoms/settings"; + +export default function page() { + const { t } = useTranslation(); + const navigation = useNavigation(); + const insets = useSafeAreaInsets(); + + const { + settings, + updateSettings, + pluginSettings, + refreshStreamyfinPluginSettings, + } = useSettings(); + const queryClient = useQueryClient(); + + // Local state for all editable fields + const [url, setUrl] = useState(settings?.streamyStatsServerUrl || ""); + const [useForSearch, setUseForSearch] = useState( + settings?.searchEngine === "Streamystats", + ); + const [movieRecs, setMovieRecs] = useState( + settings?.streamyStatsMovieRecommendations ?? false, + ); + const [seriesRecs, setSeriesRecs] = useState( + settings?.streamyStatsSeriesRecommendations ?? false, + ); + const [promotedWatchlists, setPromotedWatchlists] = useState( + settings?.streamyStatsPromotedWatchlists ?? false, + ); + + const isUrlLocked = pluginSettings?.streamyStatsServerUrl?.locked === true; + const isStreamystatsEnabled = !!url; + + const onSave = useCallback(() => { + const cleanUrl = url.endsWith("/") ? url.slice(0, -1) : url; + updateSettings({ + streamyStatsServerUrl: cleanUrl, + searchEngine: useForSearch ? "Streamystats" : "Jellyfin", + streamyStatsMovieRecommendations: movieRecs, + streamyStatsSeriesRecommendations: seriesRecs, + streamyStatsPromotedWatchlists: promotedWatchlists, + }); + queryClient.invalidateQueries({ queryKey: ["search"] }); + queryClient.invalidateQueries({ queryKey: ["streamystats"] }); + toast.success(t("home.settings.plugins.streamystats.toasts.saved")); + }, [ + url, + useForSearch, + movieRecs, + seriesRecs, + promotedWatchlists, + updateSettings, + queryClient, + t, + ]); + + // Set up header save button + useEffect(() => { + navigation.setOptions({ + headerRight: () => ( + + + {t("home.settings.plugins.streamystats.save")} + + + ), + }); + }, [navigation, onSave, t]); + + const handleClearStreamystats = useCallback(() => { + setUrl(""); + setUseForSearch(false); + setMovieRecs(false); + setSeriesRecs(false); + setPromotedWatchlists(false); + updateSettings({ + streamyStatsServerUrl: "", + searchEngine: "Jellyfin", + streamyStatsMovieRecommendations: false, + streamyStatsSeriesRecommendations: false, + streamyStatsPromotedWatchlists: false, + }); + queryClient.invalidateQueries({ queryKey: ["streamystats"] }); + queryClient.invalidateQueries({ queryKey: ["search"] }); + toast.success(t("home.settings.plugins.streamystats.toasts.disabled")); + }, [updateSettings, queryClient, t]); + + const handleOpenLink = () => { + Linking.openURL("https://github.com/fredrikburmester/streamystats"); + }; + + const handleRefreshFromServer = useCallback(async () => { + const newPluginSettings = await refreshStreamyfinPluginSettings(true); + // Update local state with new values + const newUrl = newPluginSettings?.streamyStatsServerUrl?.value || ""; + setUrl(newUrl); + if (newUrl) { + setUseForSearch(true); + } + toast.success(t("home.settings.plugins.streamystats.toasts.refreshed")); + }, [refreshStreamyfinPluginSettings, t]); + + if (!settings) return null; + + return ( + + + + + + + + + + {t("home.settings.plugins.streamystats.streamystats_search_hint")}{" "} + + {t( + "home.settings.plugins.streamystats.read_more_about_streamystats", + )} + + + + + + + + + + + + + + + + + + + {t("home.settings.plugins.streamystats.home_sections_hint")} + + + + + {t("home.settings.plugins.streamystats.refresh_from_server")} + + + + {/* Disable button - only show if URL is not locked and Streamystats is enabled */} + {!isUrlLocked && isStreamystatsEnabled && ( + + + {t("home.settings.plugins.streamystats.disable_streamystats")} + + + )} + + + ); +} diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/collections/[collectionId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/collections/[collectionId].tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search,favorites)/collections/[collectionId].tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/collections/[collectionId].tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/items/page.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/items/page.tsx similarity index 90% rename from app/(auth)/(tabs)/(home,libraries,search,favorites)/items/page.tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/items/page.tsx index b3d905fa..7f26a289 100644 --- a/app/(auth)/(tabs)/(home,libraries,search,favorites)/items/page.tsx +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/items/page.tsx @@ -21,14 +21,16 @@ const Page: React.FC = () => { const { offline } = useLocalSearchParams() as { offline?: string }; const isOffline = offline === "true"; - const { data: item, isError } = useItemQuery(id, false, undefined, [ + // Exclude MediaSources/MediaStreams from initial fetch for faster loading + // (especially important for plugins like Gelato) + const { data: item, isError } = useItemQuery(id, isOffline, undefined, [ ItemFields.MediaSources, ItemFields.MediaSourceCount, ItemFields.MediaStreams, ]); - // preload media sources - const { data: itemWithSources } = useItemQuery(id, false, undefined, []); + // Lazily preload item with full media sources in background + const { data: itemWithSources } = useItemQuery(id, isOffline, undefined, []); const opacity = useSharedValue(1); const animatedStyle = useAnimatedStyle(() => { diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/company/[companyId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/company/[companyId].tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/company/[companyId].tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/company/[companyId].tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/genre/[genreId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/genre/[genreId].tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/genre/[genreId].tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/genre/[genreId].tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/page.tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/page.tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/page.tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/person/[personId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/person/[personId].tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search,favorites)/jellyseerr/person/[personId].tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/jellyseerr/person/[personId].tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/_layout.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/_layout.tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/_layout.tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/_layout.tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/channels.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/channels.tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/channels.tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/channels.tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/guide.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/guide.tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/guide.tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/guide.tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/programs.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/programs.tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/programs.tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/programs.tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/recordings.tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/recordings.tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search,favorites)/livetv/recordings.tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/livetv/recordings.tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/music/album/[albumId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/music/album/[albumId].tsx new file mode 100644 index 00000000..6ae88c0c --- /dev/null +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/music/album/[albumId].tsx @@ -0,0 +1,192 @@ +import { Ionicons } from "@expo/vector-icons"; +import { getItemsApi, getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api"; +import { FlashList } from "@shopify/flash-list"; +import { useQuery } from "@tanstack/react-query"; +import { Image } from "expo-image"; +import { useLocalSearchParams, useNavigation } from "expo-router"; +import { useAtom } from "jotai"; +import { useCallback, useEffect, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { Dimensions, TouchableOpacity, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { Text } from "@/components/common/Text"; +import { Loader } from "@/components/Loader"; +import { MusicTrackItem } from "@/components/music/MusicTrackItem"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; +import { useMusicPlayer } from "@/providers/MusicPlayerProvider"; +import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl"; +import { runtimeTicksToMinutes } from "@/utils/time"; + +const { width: SCREEN_WIDTH } = Dimensions.get("window"); +const ARTWORK_SIZE = SCREEN_WIDTH * 0.5; + +export default function AlbumDetailScreen() { + const { albumId } = useLocalSearchParams<{ albumId: string }>(); + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + const insets = useSafeAreaInsets(); + const navigation = useNavigation(); + const { t } = useTranslation(); + const { playQueue } = useMusicPlayer(); + + const { data: album, isLoading: loadingAlbum } = useQuery({ + queryKey: ["music-album", albumId, user?.Id], + queryFn: async () => { + const response = await getUserLibraryApi(api!).getItem({ + userId: user?.Id, + itemId: albumId!, + }); + return response.data; + }, + enabled: !!api && !!user?.Id && !!albumId, + }); + + const { data: tracks, isLoading: loadingTracks } = useQuery({ + queryKey: ["music-album-tracks", albumId, user?.Id], + queryFn: async () => { + const response = await getItemsApi(api!).getItems({ + userId: user?.Id, + parentId: albumId, + sortBy: ["IndexNumber"], + sortOrder: ["Ascending"], + }); + return response.data.Items || []; + }, + enabled: !!api && !!user?.Id && !!albumId, + }); + + useEffect(() => { + navigation.setOptions({ title: album?.Name ?? "" }); + }, [album?.Name, navigation]); + + const imageUrl = useMemo( + () => (album ? getPrimaryImageUrl({ api, item: album }) : null), + [api, album], + ); + + const totalDuration = useMemo(() => { + if (!tracks) return ""; + const totalTicks = tracks.reduce( + (acc, track) => acc + (track.RunTimeTicks || 0), + 0, + ); + return runtimeTicksToMinutes(totalTicks); + }, [tracks]); + + const handlePlayAll = useCallback(() => { + if (tracks && tracks.length > 0) { + playQueue(tracks, 0); + } + }, [playQueue, tracks]); + + const handleShuffle = useCallback(() => { + if (tracks && tracks.length > 0) { + const shuffled = [...tracks].sort(() => Math.random() - 0.5); + playQueue(shuffled, 0); + } + }, [playQueue, tracks]); + + const isLoading = loadingAlbum || loadingTracks; + + if (isLoading) { + return ( + + + + ); + } + + if (!album) { + return ( + + {t("music.album_not_found")} + + ); + } + + return ( + + {/* Album artwork */} + + {imageUrl ? ( + + ) : ( + + + + )} + + + {/* Album info */} + + {album.Name} + + + {album.AlbumArtist || album.Artists?.join(", ")} + + + {album.ProductionYear && `${album.ProductionYear} • `} + {tracks?.length} tracks • {totalDuration} + + + {/* Play buttons */} + + + + + {t("music.play")} + + + + + + {t("music.shuffle")} + + + + + } + renderItem={({ item, index }) => ( + + + + )} + keyExtractor={(item) => item.Id!} + /> + ); +} diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/music/artist/[artistId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/music/artist/[artistId].tsx new file mode 100644 index 00000000..394d483e --- /dev/null +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/music/artist/[artistId].tsx @@ -0,0 +1,220 @@ +import { Ionicons } from "@expo/vector-icons"; +import { getItemsApi, getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api"; +import { FlashList } from "@shopify/flash-list"; +import { useQuery } from "@tanstack/react-query"; +import { Image } from "expo-image"; +import { useLocalSearchParams, useNavigation } from "expo-router"; +import { useAtom } from "jotai"; +import { useCallback, useEffect, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { Dimensions, TouchableOpacity, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { HorizontalScroll } from "@/components/common/HorizontalScroll"; +import { Text } from "@/components/common/Text"; +import { Loader } from "@/components/Loader"; +import { MusicAlbumCard } from "@/components/music/MusicAlbumCard"; +import { MusicTrackItem } from "@/components/music/MusicTrackItem"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; +import { useMusicPlayer } from "@/providers/MusicPlayerProvider"; +import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl"; + +const { width: SCREEN_WIDTH } = Dimensions.get("window"); +const ARTWORK_SIZE = SCREEN_WIDTH * 0.4; + +export default function ArtistDetailScreen() { + const { artistId } = useLocalSearchParams<{ artistId: string }>(); + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + const insets = useSafeAreaInsets(); + const navigation = useNavigation(); + const { t } = useTranslation(); + const { playQueue } = useMusicPlayer(); + + const { data: artist, isLoading: loadingArtist } = useQuery({ + queryKey: ["music-artist", artistId, user?.Id], + queryFn: async () => { + const response = await getUserLibraryApi(api!).getItem({ + userId: user?.Id, + itemId: artistId!, + }); + return response.data; + }, + enabled: !!api && !!user?.Id && !!artistId, + }); + + const { data: albums, isLoading: loadingAlbums } = useQuery({ + queryKey: ["music-artist-albums", artistId, user?.Id], + queryFn: async () => { + const response = await getItemsApi(api!).getItems({ + userId: user?.Id, + artistIds: [artistId!], + includeItemTypes: ["MusicAlbum"], + sortBy: ["ProductionYear", "SortName"], + sortOrder: ["Descending", "Ascending"], + recursive: true, + }); + return response.data.Items || []; + }, + enabled: !!api && !!user?.Id && !!artistId, + }); + + const { data: topTracks, isLoading: loadingTracks } = useQuery({ + queryKey: ["music-artist-top-tracks", artistId, user?.Id], + queryFn: async () => { + const response = await getItemsApi(api!).getItems({ + userId: user?.Id, + artistIds: [artistId!], + includeItemTypes: ["Audio"], + sortBy: ["PlayCount"], + sortOrder: ["Descending"], + limit: 10, + recursive: true, + filters: ["IsPlayed"], + }); + return response.data.Items || []; + }, + enabled: !!api && !!user?.Id && !!artistId, + }); + + useEffect(() => { + navigation.setOptions({ title: artist?.Name ?? "" }); + }, [artist?.Name, navigation]); + + const imageUrl = useMemo( + () => (artist ? getPrimaryImageUrl({ api, item: artist }) : null), + [api, artist], + ); + + const handlePlayAllTracks = useCallback(() => { + if (topTracks && topTracks.length > 0) { + playQueue(topTracks, 0); + } + }, [playQueue, topTracks]); + + const isLoading = loadingArtist || loadingAlbums || loadingTracks; + + if (isLoading) { + return ( + + + + ); + } + + if (!artist) { + return ( + + {t("music.artist_not_found")} + + ); + } + + const sections = []; + + // Top tracks section + if (topTracks && topTracks.length > 0) { + sections.push({ + id: "top-tracks", + title: t("music.top_tracks"), + type: "tracks" as const, + data: topTracks, + }); + } + + // Albums section + if (albums && albums.length > 0) { + sections.push({ + id: "albums", + title: t("music.tabs.albums"), + type: "albums" as const, + data: albums, + }); + } + + return ( + + {/* Artist image */} + + {imageUrl ? ( + + ) : ( + + + + )} + + + {/* Artist info */} + + {artist.Name} + + + {albums?.length || 0} {t("music.tabs.albums").toLowerCase()} + + + {/* Play button */} + {topTracks && topTracks.length > 0 && ( + + + + {t("music.play_top_tracks")} + + + )} + + } + renderItem={({ item: section }) => ( + + {section.title} + {section.type === "albums" ? ( + item.Id!} + renderItem={(item) => } + /> + ) : ( + + {section.data.slice(0, 5).map((track, index) => ( + + ))} + + )} + + )} + keyExtractor={(item) => item.id} + /> + ); +} diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/music/playlist/[playlistId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/music/playlist/[playlistId].tsx new file mode 100644 index 00000000..1a987954 --- /dev/null +++ b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/music/playlist/[playlistId].tsx @@ -0,0 +1,183 @@ +import { Ionicons } from "@expo/vector-icons"; +import { getItemsApi, getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api"; +import { FlashList } from "@shopify/flash-list"; +import { useQuery } from "@tanstack/react-query"; +import { Image } from "expo-image"; +import { useLocalSearchParams, useNavigation } from "expo-router"; +import { useAtom } from "jotai"; +import { useCallback, useEffect, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { Dimensions, TouchableOpacity, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { Text } from "@/components/common/Text"; +import { Loader } from "@/components/Loader"; +import { MusicTrackItem } from "@/components/music/MusicTrackItem"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; +import { useMusicPlayer } from "@/providers/MusicPlayerProvider"; +import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl"; +import { runtimeTicksToMinutes } from "@/utils/time"; + +const { width: SCREEN_WIDTH } = Dimensions.get("window"); +const ARTWORK_SIZE = SCREEN_WIDTH * 0.5; + +export default function PlaylistDetailScreen() { + const { playlistId } = useLocalSearchParams<{ playlistId: string }>(); + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + const insets = useSafeAreaInsets(); + const navigation = useNavigation(); + const { t } = useTranslation(); + const { playQueue } = useMusicPlayer(); + + const { data: playlist, isLoading: loadingPlaylist } = useQuery({ + queryKey: ["music-playlist", playlistId, user?.Id], + queryFn: async () => { + const response = await getUserLibraryApi(api!).getItem({ + userId: user?.Id, + itemId: playlistId!, + }); + return response.data; + }, + enabled: !!api && !!user?.Id && !!playlistId, + }); + + const { data: tracks, isLoading: loadingTracks } = useQuery({ + queryKey: ["music-playlist-tracks", playlistId, user?.Id], + queryFn: async () => { + const response = await getItemsApi(api!).getItems({ + userId: user?.Id, + parentId: playlistId, + }); + return response.data.Items || []; + }, + enabled: !!api && !!user?.Id && !!playlistId, + }); + + useEffect(() => { + navigation.setOptions({ title: playlist?.Name ?? "" }); + }, [playlist?.Name, navigation]); + + const imageUrl = useMemo( + () => (playlist ? getPrimaryImageUrl({ api, item: playlist }) : null), + [api, playlist], + ); + + const totalDuration = useMemo(() => { + if (!tracks) return ""; + const totalTicks = tracks.reduce( + (acc, track) => acc + (track.RunTimeTicks || 0), + 0, + ); + return runtimeTicksToMinutes(totalTicks); + }, [tracks]); + + const handlePlayAll = useCallback(() => { + if (tracks && tracks.length > 0) { + playQueue(tracks, 0); + } + }, [playQueue, tracks]); + + const handleShuffle = useCallback(() => { + if (tracks && tracks.length > 0) { + const shuffled = [...tracks].sort(() => Math.random() - 0.5); + playQueue(shuffled, 0); + } + }, [playQueue, tracks]); + + const isLoading = loadingPlaylist || loadingTracks; + + if (isLoading) { + return ( + + + + ); + } + + if (!playlist) { + return ( + + + {t("music.playlist_not_found")} + + + ); + } + + return ( + + {/* Playlist artwork */} + + {imageUrl ? ( + + ) : ( + + + + )} + + + {/* Playlist info */} + + {playlist.Name} + + + {tracks?.length} tracks • {totalDuration} + + + {/* Play buttons */} + + + + + {t("music.play")} + + + + + + {t("music.shuffle")} + + + + + } + renderItem={({ item, index }) => ( + + + + )} + keyExtractor={(item) => item.Id!} + /> + ); +} diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/persons/[personId].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/persons/[personId].tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search,favorites)/persons/[personId].tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/persons/[personId].tsx diff --git a/app/(auth)/(tabs)/(home,libraries,search,favorites)/series/[id].tsx b/app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/series/[id].tsx similarity index 100% rename from app/(auth)/(tabs)/(home,libraries,search,favorites)/series/[id].tsx rename to app/(auth)/(tabs)/(home,libraries,search,favorites,watchlists)/series/[id].tsx diff --git a/app/(auth)/(tabs)/(libraries)/[libraryId].tsx b/app/(auth)/(tabs)/(libraries)/[libraryId].tsx index 481881fc..e2850631 100644 --- a/app/(auth)/(tabs)/(libraries)/[libraryId].tsx +++ b/app/(auth)/(tabs)/(libraries)/[libraryId].tsx @@ -2,6 +2,7 @@ import type { BaseItemDto, BaseItemDtoQueryResult, BaseItemKind, + ItemFilter, } from "@jellyfin/sdk/lib/generated-client/models"; import { getFilterApi, @@ -27,7 +28,11 @@ import { useOrientation } from "@/hooks/useOrientation"; import * as ScreenOrientation from "@/packages/expo-screen-orientation"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { + FilterByOption, + FilterByPreferenceAtom, + filterByAtom, genreFilterAtom, + getFilterByPreference, getSortByPreference, getSortOrderPreference, SortByOption, @@ -39,8 +44,10 @@ import { sortOrderOptions, sortOrderPreferenceAtom, tagsFilterAtom, + useFilterOptions, yearFilterAtom, } from "@/utils/atoms/filters"; +import { useSettings } from "@/utils/atoms/settings"; const Page = () => { const searchParams = useLocalSearchParams(); @@ -54,9 +61,13 @@ const Page = () => { const [selectedYears, setSelectedYears] = useAtom(yearFilterAtom); const [selectedTags, setSelectedTags] = useAtom(tagsFilterAtom); const [sortBy, _setSortBy] = useAtom(sortByAtom); + const [filterBy, _setFilterBy] = useAtom(filterByAtom); const [sortOrder, _setSortOrder] = useAtom(sortOrderAtom); const [sortByPreference, setSortByPreference] = useAtom(sortByPreferenceAtom); - const [sortOrderPreference, setOderByPreference] = useAtom( + const [filterByPreference, setFilterByPreference] = useAtom( + FilterByPreferenceAtom, + ); + const [sortOrderPreference, setOrderByPreference] = useAtom( sortOrderPreferenceAtom, ); @@ -77,12 +88,20 @@ const Page = () => { } else { _setSortBy([SortByOption.SortName]); } + const fp = getFilterByPreference(libraryId, filterByPreference); + if (fp) { + _setFilterBy([fp]); + } else { + _setFilterBy([]); + } }, [ libraryId, sortOrderPreference, sortByPreference, _setSortOrder, _setSortBy, + filterByPreference, + _setFilterBy, ]); const setSortBy = useCallback( @@ -100,14 +119,28 @@ const Page = () => { (sortOrder: SortOrderOption[]) => { const sop = getSortOrderPreference(libraryId, sortOrderPreference); if (sortOrder[0] !== sop) { - setOderByPreference({ + setOrderByPreference({ ...sortOrderPreference, [libraryId]: sortOrder[0], }); } _setSortOrder(sortOrder); }, - [libraryId, sortOrderPreference, setOderByPreference, _setSortOrder], + [libraryId, sortOrderPreference, setOrderByPreference, _setSortOrder], + ); + + const setFilter = useCallback( + (filterBy: FilterByOption[]) => { + const fp = getFilterByPreference(libraryId, filterByPreference); + if (filterBy[0] !== fp) { + setFilterByPreference({ + ...filterByPreference, + [libraryId]: filterBy[0], + }); + } + _setFilterBy(filterBy); + }, + [libraryId, filterByPreference, setFilterByPreference, _setFilterBy], ); const nrOfCols = useMemo(() => { @@ -168,6 +201,7 @@ const Page = () => { sortBy: [sortBy[0], "SortName", "ProductionYear"], sortOrder: [sortOrder[0]], enableImageTypes: ["Primary", "Backdrop", "Banner", "Thumb"], + filters: filterBy as ItemFilter[], // true is needed for merged versions recursive: true, imageTypeLimit: 1, @@ -190,6 +224,7 @@ const Page = () => { selectedTags, sortBy, sortOrder, + filterBy, ], ); @@ -203,6 +238,7 @@ const Page = () => { selectedTags, sortBy, sortOrder, + filterBy, ], queryFn: fetchItems, getNextPageParam: (lastPage, pages) => { @@ -268,7 +304,8 @@ const Page = () => { ); const keyExtractor = useCallback((item: BaseItemDto) => item.Id || "", []); - + const generalFilters = useFilterOptions(); + const settings = useSettings(); const ListHeaderComponent = useCallback( () => ( { /> ), }, + { + key: "filterOptions", + component: ( + generalFilters.map((s) => s.key)} + set={setFilter} + values={filterBy} + title={t("library.filters.filter_by")} + renderItemLabel={(item) => + generalFilters.find((i) => i.key === item)?.value || "" + } + searchFilter={(item, search) => + item.toLowerCase().includes(search.toLowerCase()) + } + /> + ), + }, ]} renderItem={({ item }) => item.component} keyExtractor={(item) => item.key} @@ -424,6 +481,9 @@ const Page = () => { sortOrder, setSortOrder, isFetching, + filterBy, + setFilter, + settings, ], ); diff --git a/app/(auth)/(tabs)/(libraries)/index.tsx b/app/(auth)/(tabs)/(libraries)/index.tsx index b56397f9..37b89bbf 100644 --- a/app/(auth)/(tabs)/(libraries)/index.tsx +++ b/app/(auth)/(tabs)/(libraries)/index.tsx @@ -39,7 +39,6 @@ export default function index() { () => data ?.filter((l) => !settings?.hiddenLibraries?.includes(l.Id!)) - .filter((l) => l.CollectionType !== "music") .filter((l) => l.CollectionType !== "books") || [], [data, settings?.hiddenLibraries], ); diff --git a/app/(auth)/(tabs)/(libraries)/music/[libraryId]/_layout.tsx b/app/(auth)/(tabs)/(libraries)/music/[libraryId]/_layout.tsx new file mode 100644 index 00000000..8606d406 --- /dev/null +++ b/app/(auth)/(tabs)/(libraries)/music/[libraryId]/_layout.tsx @@ -0,0 +1,81 @@ +import { + createMaterialTopTabNavigator, + MaterialTopTabNavigationEventMap, + MaterialTopTabNavigationOptions, +} from "@react-navigation/material-top-tabs"; +import type { + ParamListBase, + TabNavigationState, +} from "@react-navigation/native"; +import { Stack, useLocalSearchParams, withLayoutContext } from "expo-router"; +import { useTranslation } from "react-i18next"; + +const { Navigator } = createMaterialTopTabNavigator(); + +const TAB_LABEL_FONT_SIZE = 13; +const TAB_ITEM_HORIZONTAL_PADDING = 18; +const TAB_ITEM_MIN_WIDTH = 110; + +export const Tab = withLayoutContext< + MaterialTopTabNavigationOptions, + typeof Navigator, + TabNavigationState, + MaterialTopTabNavigationEventMap +>(Navigator); + +const Layout = () => { + const { libraryId } = useLocalSearchParams<{ libraryId: string }>(); + const { t } = useTranslation(); + + return ( + <> + + + + + + + + + ); +}; + +export default Layout; diff --git a/app/(auth)/(tabs)/(libraries)/music/[libraryId]/albums.tsx b/app/(auth)/(tabs)/(libraries)/music/[libraryId]/albums.tsx new file mode 100644 index 00000000..5ccefa9d --- /dev/null +++ b/app/(auth)/(tabs)/(libraries)/music/[libraryId]/albums.tsx @@ -0,0 +1,136 @@ +import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; +import { useRoute } from "@react-navigation/native"; +import { FlashList } from "@shopify/flash-list"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import { useLocalSearchParams } from "expo-router"; +import { useAtom } from "jotai"; +import { useCallback, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { Dimensions, RefreshControl, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { Text } from "@/components/common/Text"; +import { Loader } from "@/components/Loader"; +import { MusicAlbumCard } from "@/components/music/MusicAlbumCard"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; + +const ITEMS_PER_PAGE = 40; + +export default function AlbumsScreen() { + const localParams = useLocalSearchParams<{ libraryId?: string | string[] }>(); + const route = useRoute(); + const libraryId = + (Array.isArray(localParams.libraryId) + ? localParams.libraryId[0] + : localParams.libraryId) ?? route?.params?.libraryId; + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + const insets = useSafeAreaInsets(); + const { t } = useTranslation(); + + const { + data, + isLoading, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + refetch, + } = useInfiniteQuery({ + queryKey: ["music-albums", libraryId, user?.Id], + queryFn: async ({ pageParam = 0 }) => { + const response = await getItemsApi(api!).getItems({ + userId: user?.Id, + parentId: libraryId, + includeItemTypes: ["MusicAlbum"], + sortBy: ["SortName"], + sortOrder: ["Ascending"], + limit: ITEMS_PER_PAGE, + startIndex: pageParam, + recursive: true, + }); + return { + items: response.data.Items || [], + totalCount: response.data.TotalRecordCount || 0, + startIndex: pageParam, + }; + }, + getNextPageParam: (lastPage) => { + const nextStart = lastPage.startIndex + ITEMS_PER_PAGE; + return nextStart < lastPage.totalCount ? nextStart : undefined; + }, + initialPageParam: 0, + enabled: !!api && !!user?.Id && !!libraryId, + }); + + const albums = useMemo(() => { + return data?.pages.flatMap((page) => page.items) || []; + }, [data]); + + const numColumns = 2; + const screenWidth = Dimensions.get("window").width; + const gap = 12; + const padding = 16; + const itemWidth = + (screenWidth - padding * 2 - gap * (numColumns - 1)) / numColumns; + + const handleEndReached = useCallback(() => { + if (hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } + }, [hasNextPage, isFetchingNextPage, fetchNextPage]); + + if (isLoading) { + return ( + + + + ); + } + + if (albums.length === 0) { + return ( + + {t("music.no_albums")} + + ); + } + + return ( + + } + onEndReached={handleEndReached} + onEndReachedThreshold={0.5} + renderItem={({ item, index }) => ( + + + + )} + keyExtractor={(item) => item.Id!} + ListFooterComponent={ + isFetchingNextPage ? ( + + + + ) : null + } + /> + ); +} diff --git a/app/(auth)/(tabs)/(libraries)/music/[libraryId]/artists.tsx b/app/(auth)/(tabs)/(libraries)/music/[libraryId]/artists.tsx new file mode 100644 index 00000000..f612b537 --- /dev/null +++ b/app/(auth)/(tabs)/(libraries)/music/[libraryId]/artists.tsx @@ -0,0 +1,170 @@ +import { getArtistsApi } from "@jellyfin/sdk/lib/utils/api"; +import { useRoute } from "@react-navigation/native"; +import { FlashList } from "@shopify/flash-list"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import { useLocalSearchParams } from "expo-router"; +import { useAtom } from "jotai"; +import { useCallback, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { Dimensions, RefreshControl, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { Text } from "@/components/common/Text"; +import { Loader } from "@/components/Loader"; +import { MusicArtistCard } from "@/components/music/MusicArtistCard"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; + +// Web uses Limit=100 +const ITEMS_PER_PAGE = 100; + +export default function ArtistsScreen() { + const localParams = useLocalSearchParams<{ libraryId?: string | string[] }>(); + const route = useRoute(); + const libraryId = + (Array.isArray(localParams.libraryId) + ? localParams.libraryId[0] + : localParams.libraryId) ?? route?.params?.libraryId; + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + const insets = useSafeAreaInsets(); + const { t } = useTranslation(); + + const isReady = Boolean(api && user?.Id && libraryId); + + const { + data, + isLoading, + isError, + error, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + refetch, + } = useInfiniteQuery({ + queryKey: ["music-artists", libraryId, user?.Id], + queryFn: async ({ pageParam = 0 }) => { + const response = await getArtistsApi(api!).getArtists({ + userId: user?.Id, + parentId: libraryId, + sortBy: ["SortName"], + sortOrder: ["Ascending"], + fields: ["PrimaryImageAspectRatio", "SortName"], + imageTypeLimit: 1, + enableImageTypes: ["Primary", "Backdrop", "Banner", "Thumb"], + limit: ITEMS_PER_PAGE, + startIndex: pageParam, + }); + return { + items: response.data.Items || [], + totalCount: response.data.TotalRecordCount || 0, + startIndex: pageParam, + }; + }, + getNextPageParam: (lastPage) => { + const nextStart = lastPage.startIndex + ITEMS_PER_PAGE; + return nextStart < lastPage.totalCount ? nextStart : undefined; + }, + initialPageParam: 0, + enabled: isReady, + }); + + const artists = useMemo(() => { + return data?.pages.flatMap((page) => page.items) || []; + }, [data]); + + const numColumns = 3; + const screenWidth = Dimensions.get("window").width; + const gap = 12; + const padding = 16; + const itemWidth = + (screenWidth - padding * 2 - gap * (numColumns - 1)) / numColumns; + + const handleEndReached = useCallback(() => { + if (hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } + }, [hasNextPage, isFetchingNextPage, fetchNextPage]); + + if (!api || !user?.Id) { + return ( + + + + ); + } + + if (!libraryId) { + return ( + + + Missing music library id. + + + ); + } + + if (isLoading) { + return ( + + + + ); + } + + if (isError) { + return ( + + + Failed to load artists: {(error as Error)?.message || "Unknown error"} + + + ); + } + + if (artists.length === 0) { + return ( + + {t("music.no_artists")} + + ); + } + + return ( + + } + onEndReached={handleEndReached} + onEndReachedThreshold={0.5} + renderItem={({ item, index }) => ( + + + + )} + keyExtractor={(item) => item.Id!} + ListFooterComponent={ + isFetchingNextPage ? ( + + + + ) : null + } + /> + ); +} diff --git a/app/(auth)/(tabs)/(libraries)/music/[libraryId]/playlists.tsx b/app/(auth)/(tabs)/(libraries)/music/[libraryId]/playlists.tsx new file mode 100644 index 00000000..6093295b --- /dev/null +++ b/app/(auth)/(tabs)/(libraries)/music/[libraryId]/playlists.tsx @@ -0,0 +1,170 @@ +import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; +import { useRoute } from "@react-navigation/native"; +import { FlashList } from "@shopify/flash-list"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import { useLocalSearchParams } from "expo-router"; +import { useAtom } from "jotai"; +import { useCallback, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { Dimensions, RefreshControl, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { Text } from "@/components/common/Text"; +import { Loader } from "@/components/Loader"; +import { MusicPlaylistCard } from "@/components/music/MusicPlaylistCard"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; + +const ITEMS_PER_PAGE = 40; + +export default function PlaylistsScreen() { + const localParams = useLocalSearchParams<{ libraryId?: string | string[] }>(); + const route = useRoute(); + const libraryId = + (Array.isArray(localParams.libraryId) + ? localParams.libraryId[0] + : localParams.libraryId) ?? route?.params?.libraryId; + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + const insets = useSafeAreaInsets(); + const { t } = useTranslation(); + + const isReady = Boolean(api && user?.Id && libraryId); + + const { + data, + isLoading, + isError, + error, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + refetch, + } = useInfiniteQuery({ + queryKey: ["music-playlists", libraryId, user?.Id], + queryFn: async ({ pageParam = 0 }) => { + const response = await getItemsApi(api!).getItems({ + userId: user?.Id, + parentId: libraryId, + includeItemTypes: ["Playlist"], + sortBy: ["SortName"], + sortOrder: ["Ascending"], + limit: ITEMS_PER_PAGE, + startIndex: pageParam, + recursive: true, + mediaTypes: ["Audio"], + }); + return { + items: response.data.Items || [], + totalCount: response.data.TotalRecordCount || 0, + startIndex: pageParam, + }; + }, + getNextPageParam: (lastPage) => { + const nextStart = lastPage.startIndex + ITEMS_PER_PAGE; + return nextStart < lastPage.totalCount ? nextStart : undefined; + }, + initialPageParam: 0, + enabled: isReady, + }); + + const playlists = useMemo(() => { + return data?.pages.flatMap((page) => page.items) || []; + }, [data]); + + const numColumns = 2; + const screenWidth = Dimensions.get("window").width; + const gap = 12; + const padding = 16; + const itemWidth = + (screenWidth - padding * 2 - gap * (numColumns - 1)) / numColumns; + + const handleEndReached = useCallback(() => { + if (hasNextPage && !isFetchingNextPage) { + fetchNextPage(); + } + }, [hasNextPage, isFetchingNextPage, fetchNextPage]); + + if (!api || !user?.Id) { + return ( + + + + ); + } + + if (!libraryId) { + return ( + + + Missing music library id. + + + ); + } + + if (isLoading) { + return ( + + + + ); + } + + if (isError) { + return ( + + + Failed to load playlists:{" "} + {(error as Error)?.message || "Unknown error"} + + + ); + } + + if (playlists.length === 0) { + return ( + + {t("music.no_playlists")} + + ); + } + + return ( + + } + onEndReached={handleEndReached} + onEndReachedThreshold={0.5} + renderItem={({ item, index }) => ( + + + + )} + keyExtractor={(item) => item.Id!} + ListFooterComponent={ + isFetchingNextPage ? ( + + + + ) : null + } + /> + ); +} diff --git a/app/(auth)/(tabs)/(libraries)/music/[libraryId]/suggestions.tsx b/app/(auth)/(tabs)/(libraries)/music/[libraryId]/suggestions.tsx new file mode 100644 index 00000000..b32ea4c9 --- /dev/null +++ b/app/(auth)/(tabs)/(libraries)/music/[libraryId]/suggestions.tsx @@ -0,0 +1,286 @@ +import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; +import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; +import { useRoute } from "@react-navigation/native"; +import { FlashList } from "@shopify/flash-list"; +import { useQuery } from "@tanstack/react-query"; +import { useLocalSearchParams } from "expo-router"; +import { useAtom } from "jotai"; +import { useCallback, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { RefreshControl, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { HorizontalScroll } from "@/components/common/HorizontalScroll"; +import { Text } from "@/components/common/Text"; +import { Loader } from "@/components/Loader"; +import { MusicAlbumCard } from "@/components/music/MusicAlbumCard"; +import { MusicTrackItem } from "@/components/music/MusicTrackItem"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; +import { writeDebugLog } from "@/utils/log"; + +export default function SuggestionsScreen() { + const localParams = useLocalSearchParams<{ libraryId?: string | string[] }>(); + const route = useRoute(); + const libraryId = + (Array.isArray(localParams.libraryId) + ? localParams.libraryId[0] + : localParams.libraryId) ?? route?.params?.libraryId; + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + const insets = useSafeAreaInsets(); + const { t } = useTranslation(); + + const isReady = Boolean(api && user?.Id && libraryId); + + writeDebugLog("Music suggestions params", { + libraryId, + localParams, + routeParams: route?.params, + isReady, + }); + + // Latest audio - uses the same endpoint as web: /Users/{userId}/Items/Latest + // This returns the most recently added albums + const { + data: latestAlbums, + isLoading: loadingLatest, + isError: isLatestError, + error: latestError, + refetch: refetchLatest, + } = useQuery({ + queryKey: ["music-latest", libraryId, user?.Id], + queryFn: async () => { + // Prefer the exact endpoint the Web client calls (HAR): + // /Users/{userId}/Items/Latest?IncludeItemTypes=Audio&ParentId=... + // IMPORTANT: must use api.get(...) (not axiosInstance.get(fullUrl)) so the auth header is attached. + const res = await api!.get( + `/Users/${user!.Id}/Items/Latest`, + { + params: { + IncludeItemTypes: "Audio", + Limit: 20, + Fields: "PrimaryImageAspectRatio", + ParentId: libraryId, + ImageTypeLimit: 1, + EnableImageTypes: "Primary,Backdrop,Banner,Thumb", + EnableTotalRecordCount: false, + }, + }, + ); + + if (Array.isArray(res.data) && res.data.length > 0) { + return res.data; + } + + // Fallback: ask for albums directly via /Items (more reliable across server variants) + const fallback = await getItemsApi(api!).getItems({ + userId: user!.Id, + parentId: libraryId, + includeItemTypes: ["MusicAlbum"], + sortBy: ["DateCreated"], + sortOrder: ["Descending"], + limit: 20, + recursive: true, + fields: ["PrimaryImageAspectRatio", "SortName"], + imageTypeLimit: 1, + enableImageTypes: ["Primary", "Backdrop", "Banner", "Thumb"], + enableTotalRecordCount: false, + }); + return fallback.data.Items || []; + }, + enabled: isReady, + }); + + // Recently played - matches web: SortBy=DatePlayed, Filters=IsPlayed + const { + data: recentlyPlayed, + isLoading: loadingRecentlyPlayed, + isError: isRecentlyPlayedError, + error: recentlyPlayedError, + refetch: refetchRecentlyPlayed, + } = useQuery({ + queryKey: ["music-recently-played", libraryId, user?.Id], + queryFn: async () => { + const response = await getItemsApi(api!).getItems({ + userId: user?.Id, + parentId: libraryId, + includeItemTypes: ["Audio"], + sortBy: ["DatePlayed"], + sortOrder: ["Descending"], + limit: 10, + recursive: true, + fields: ["PrimaryImageAspectRatio", "SortName"], + filters: ["IsPlayed"], + imageTypeLimit: 1, + enableImageTypes: ["Primary", "Backdrop", "Banner", "Thumb"], + enableTotalRecordCount: false, + }); + return response.data.Items || []; + }, + enabled: isReady, + }); + + // Frequently played - matches web: SortBy=PlayCount, Filters=IsPlayed + const { + data: frequentlyPlayed, + isLoading: loadingFrequent, + isError: isFrequentError, + error: frequentError, + refetch: refetchFrequent, + } = useQuery({ + queryKey: ["music-frequently-played", libraryId, user?.Id], + queryFn: async () => { + const response = await getItemsApi(api!).getItems({ + userId: user?.Id, + parentId: libraryId, + includeItemTypes: ["Audio"], + sortBy: ["PlayCount"], + sortOrder: ["Descending"], + limit: 10, + recursive: true, + fields: ["PrimaryImageAspectRatio", "SortName"], + filters: ["IsPlayed"], + imageTypeLimit: 1, + enableImageTypes: ["Primary", "Backdrop", "Banner", "Thumb"], + enableTotalRecordCount: false, + }); + return response.data.Items || []; + }, + enabled: isReady, + }); + + const isLoading = loadingLatest || loadingRecentlyPlayed || loadingFrequent; + + const handleRefresh = useCallback(() => { + refetchLatest(); + refetchRecentlyPlayed(); + refetchFrequent(); + }, [refetchLatest, refetchRecentlyPlayed, refetchFrequent]); + + const sections = useMemo(() => { + const result: { + title: string; + data: BaseItemDto[]; + type: "albums" | "tracks"; + }[] = []; + + // Latest albums section + if (latestAlbums && latestAlbums.length > 0) { + result.push({ + title: t("music.recently_added"), + data: latestAlbums, + type: "albums", + }); + } + + // Recently played tracks + if (recentlyPlayed && recentlyPlayed.length > 0) { + result.push({ + title: t("music.recently_played"), + data: recentlyPlayed, + type: "tracks", + }); + } + + // Frequently played tracks + if (frequentlyPlayed && frequentlyPlayed.length > 0) { + result.push({ + title: t("music.frequently_played"), + data: frequentlyPlayed, + type: "tracks", + }); + } + + return result; + }, [latestAlbums, frequentlyPlayed, recentlyPlayed, t]); + + if (!api || !user?.Id) { + return ( + + + + ); + } + + if (!libraryId) { + return ( + + + Missing music library id. + + + ); + } + + if (isLoading) { + return ( + + + + ); + } + + if (isLatestError || isRecentlyPlayedError || isFrequentError) { + const msg = + (latestError as Error | undefined)?.message || + (recentlyPlayedError as Error | undefined)?.message || + (frequentError as Error | undefined)?.message || + "Unknown error"; + return ( + + + Failed to load music: {msg} + + + ); + } + + if (sections.length === 0) { + return ( + + {t("music.no_suggestions")} + + ); + } + + return ( + + } + renderItem={({ item: section }) => ( + + {section.title} + {section.type === "albums" ? ( + item.Id!} + renderItem={(item) => } + /> + ) : ( + + {section.data.slice(0, 5).map((track, index, _tracks) => ( + + ))} + + )} + + )} + keyExtractor={(item) => item.title} + /> + ); +} diff --git a/app/(auth)/(tabs)/(search)/index.tsx b/app/(auth)/(tabs)/(search)/index.tsx index bedf5ffa..d124e0eb 100644 --- a/app/(auth)/(tabs)/(search)/index.tsx +++ b/app/(auth)/(tabs)/(search)/index.tsx @@ -39,6 +39,7 @@ import { useJellyseerr } from "@/hooks/useJellyseerr"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { useSettings } from "@/utils/atoms/settings"; import { eventBus } from "@/utils/eventBus"; +import { createStreamystatsApi } from "@/utils/streamystats"; type SearchType = "Library" | "Discover"; @@ -117,6 +118,54 @@ export default function search() { return (searchApi.data.Items as BaseItemDto[]) || []; } + + if (searchEngine === "Streamystats") { + if (!settings?.streamyStatsServerUrl || !api.accessToken) { + return []; + } + + const streamyStatsApi = createStreamystatsApi({ + serverUrl: settings.streamyStatsServerUrl, + jellyfinToken: api.accessToken, + }); + + const typeMap: Record = { + Movie: "movies", + Series: "series", + Episode: "episodes", + Person: "actors", + BoxSet: "movies", + Audio: "audio", + } as Record; + + const searchType = types.length === 1 ? typeMap[types[0]] : "media"; + const response = await streamyStatsApi.searchIds( + query, + searchType as "movies" | "series" | "episodes" | "actors" | "media", + 10, + ); + + const allIds: string[] = [ + ...(response.data.movies || []), + ...(response.data.series || []), + ...(response.data.episodes || []), + ...(response.data.actors || []), + ...(response.data.audio || []), + ]; + + if (!allIds.length) { + return []; + } + + const itemsResponse = await getItemsApi(api).getItems({ + ids: allIds, + enableImageTypes: ["Primary", "Backdrop", "Thumb"], + }); + + return (itemsResponse.data.Items as BaseItemDto[]) || []; + } + + // Marlin search if (!settings?.marlinServerUrl) { return []; } @@ -141,12 +190,11 @@ export default function search() { }); return (response2.data.Items as BaseItemDto[]) || []; - } catch (error) { - console.error("Error during search:", error); - return []; // Ensure an empty array is returned in case of an error + } catch (_error) { + return []; } }, - [api, searchEngine, settings], + [api, searchEngine, settings, user?.Id], ); type HeaderSearchBarRef = { diff --git a/app/(auth)/(tabs)/(watchlists)/[watchlistId].tsx b/app/(auth)/(tabs)/(watchlists)/[watchlistId].tsx new file mode 100644 index 00000000..69e4618e --- /dev/null +++ b/app/(auth)/(tabs)/(watchlists)/[watchlistId].tsx @@ -0,0 +1,297 @@ +import { Ionicons } from "@expo/vector-icons"; +import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; +import { FlashList } from "@shopify/flash-list"; +import { useLocalSearchParams, useNavigation, useRouter } from "expo-router"; +import { useAtomValue } from "jotai"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + ActivityIndicator, + Alert, + RefreshControl, + TouchableOpacity, + useWindowDimensions, + View, +} from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { HeaderBackButton } from "@/components/common/HeaderBackButton"; +import { Text } from "@/components/common/Text"; +import { TouchableItemRouter } from "@/components/common/TouchableItemRouter"; +import { ItemCardText } from "@/components/ItemCardText"; +import { ItemPoster } from "@/components/posters/ItemPoster"; +import { useOrientation } from "@/hooks/useOrientation"; +import { + useDeleteWatchlist, + useRemoveFromWatchlist, +} from "@/hooks/useWatchlistMutations"; +import { + useWatchlistDetailQuery, + useWatchlistItemsQuery, +} from "@/hooks/useWatchlists"; +import * as ScreenOrientation from "@/packages/expo-screen-orientation"; +import { userAtom } from "@/providers/JellyfinProvider"; + +export default function WatchlistDetailScreen() { + const { t } = useTranslation(); + const router = useRouter(); + const navigation = useNavigation(); + const insets = useSafeAreaInsets(); + const { watchlistId } = useLocalSearchParams<{ watchlistId: string }>(); + const user = useAtomValue(userAtom); + const { width: screenWidth } = useWindowDimensions(); + const { orientation } = useOrientation(); + + const watchlistIdNum = watchlistId + ? Number.parseInt(watchlistId, 10) + : undefined; + + const nrOfCols = useMemo(() => { + if (screenWidth < 300) return 2; + if (screenWidth < 500) return 3; + if (screenWidth < 800) return 5; + if (screenWidth < 1000) return 6; + if (screenWidth < 1500) return 7; + return 6; + }, [screenWidth]); + + const { + data: watchlist, + isLoading: watchlistLoading, + refetch: refetchWatchlist, + } = useWatchlistDetailQuery(watchlistIdNum); + + const { + data: items, + isLoading: itemsLoading, + refetch: refetchItems, + } = useWatchlistItemsQuery(watchlistIdNum); + + const deleteWatchlist = useDeleteWatchlist(); + const removeFromWatchlist = useRemoveFromWatchlist(); + const [refreshing, setRefreshing] = useState(false); + + const isOwner = useMemo( + () => watchlist?.userId === user?.Id, + [watchlist?.userId, user?.Id], + ); + + // Set up header + useEffect(() => { + navigation.setOptions({ + headerTitle: watchlist?.name || "", + headerLeft: () => , + headerRight: isOwner + ? () => ( + + + router.push(`/(auth)/(tabs)/(watchlists)/edit/${watchlistId}`) + } + className='p-2' + > + + + + + + + ) + : undefined, + }); + }, [navigation, watchlist?.name, isOwner, watchlistId]); + + const handleRefresh = useCallback(async () => { + setRefreshing(true); + await Promise.all([refetchWatchlist(), refetchItems()]); + setRefreshing(false); + }, [refetchWatchlist, refetchItems]); + + const handleDelete = useCallback(() => { + Alert.alert( + t("watchlists.delete_confirm_title"), + t("watchlists.delete_confirm_message", { name: watchlist?.name }), + [ + { text: t("watchlists.cancel_button"), style: "cancel" }, + { + text: t("watchlists.delete_button"), + style: "destructive", + onPress: async () => { + if (watchlistIdNum) { + await deleteWatchlist.mutateAsync(watchlistIdNum); + router.back(); + } + }, + }, + ], + ); + }, [deleteWatchlist, watchlistIdNum, watchlist?.name, router, t]); + + const handleRemoveItem = useCallback( + (item: BaseItemDto) => { + if (!watchlistIdNum || !item.Id) return; + + Alert.alert( + t("watchlists.remove_item_title"), + t("watchlists.remove_item_message", { name: item.Name }), + [ + { text: t("watchlists.cancel_button"), style: "cancel" }, + { + text: t("watchlists.remove_button"), + style: "destructive", + onPress: async () => { + await removeFromWatchlist.mutateAsync({ + watchlistId: watchlistIdNum, + itemId: item.Id!, + watchlistName: watchlist?.name, + }); + }, + }, + ], + ); + }, + [removeFromWatchlist, watchlistIdNum, watchlist?.name, t], + ); + + const renderItem = useCallback( + ({ item, index }: { item: BaseItemDto; index: number }) => ( + handleRemoveItem(item) : undefined} + > + + + + + + ), + [isOwner, handleRemoveItem, orientation, nrOfCols], + ); + + const ListHeader = useMemo( + () => + watchlist ? ( + + {watchlist.description && ( + + {watchlist.description} + + )} + + + + + {items?.length ?? 0}{" "} + {(items?.length ?? 0) === 1 + ? t("watchlists.item") + : t("watchlists.items")} + + + + + + {watchlist.isPublic + ? t("watchlists.public") + : t("watchlists.private")} + + + {!isOwner && ( + + {t("watchlists.by_owner")} + + )} + + + ) : null, + [watchlist, items?.length, isOwner, t], + ); + + const EmptyComponent = useMemo( + () => ( + + + + {t("watchlists.empty_watchlist")} + + {isOwner && ( + + {t("watchlists.empty_watchlist_hint")} + + )} + + ), + [isOwner, t], + ); + + const keyExtractor = useCallback((item: BaseItemDto) => item.Id || "", []); + + if (watchlistLoading || itemsLoading) { + return ( + + + + ); + } + + if (!watchlist) { + return ( + + + {t("watchlists.not_found")} + + + ); + } + + return ( + + } + renderItem={renderItem} + ItemSeparatorComponent={() => ( + + )} + /> + ); +} diff --git a/app/(auth)/(tabs)/(watchlists)/_layout.tsx b/app/(auth)/(tabs)/(watchlists)/_layout.tsx new file mode 100644 index 00000000..05cede49 --- /dev/null +++ b/app/(auth)/(tabs)/(watchlists)/_layout.tsx @@ -0,0 +1,74 @@ +import { Ionicons } from "@expo/vector-icons"; +import { Stack, useRouter } from "expo-router"; +import { useTranslation } from "react-i18next"; +import { Platform, TouchableOpacity } from "react-native"; +import { nestedTabPageScreenOptions } from "@/components/stacks/NestedTabPageStack"; +import { useStreamystatsEnabled } from "@/hooks/useWatchlists"; + +export default function WatchlistsLayout() { + const { t } = useTranslation(); + const router = useRouter(); + const streamystatsEnabled = useStreamystatsEnabled(); + + return ( + + ( + + router.push("/(auth)/(tabs)/(watchlists)/create") + } + className='p-1.5' + > + + + ) + : undefined, + }} + /> + + + + {Object.entries(nestedTabPageScreenOptions).map(([name, options]) => ( + + ))} + + ); +} diff --git a/app/(auth)/(tabs)/(watchlists)/create.tsx b/app/(auth)/(tabs)/(watchlists)/create.tsx new file mode 100644 index 00000000..aab8104d --- /dev/null +++ b/app/(auth)/(tabs)/(watchlists)/create.tsx @@ -0,0 +1,221 @@ +import { Ionicons } from "@expo/vector-icons"; +import { useRouter } from "expo-router"; +import { useCallback, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + ActivityIndicator, + KeyboardAvoidingView, + Platform, + ScrollView, + Switch, + TextInput, + TouchableOpacity, + View, +} from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { Button } from "@/components/Button"; +import { Text } from "@/components/common/Text"; +import { useCreateWatchlist } from "@/hooks/useWatchlistMutations"; +import type { + StreamystatsWatchlistAllowedItemType, + StreamystatsWatchlistSortOrder, +} from "@/utils/streamystats/types"; + +const ITEM_TYPES: Array<{ + value: StreamystatsWatchlistAllowedItemType; + label: string; +}> = [ + { value: null, label: "All Types" }, + { value: "Movie", label: "Movies Only" }, + { value: "Series", label: "Series Only" }, + { value: "Episode", label: "Episodes Only" }, +]; + +const SORT_OPTIONS: Array<{ + value: StreamystatsWatchlistSortOrder; + label: string; +}> = [ + { value: "custom", label: "Custom Order" }, + { value: "name", label: "Name" }, + { value: "dateAdded", label: "Date Added" }, + { value: "releaseDate", label: "Release Date" }, +]; + +export default function CreateWatchlistScreen() { + const { t } = useTranslation(); + const router = useRouter(); + const insets = useSafeAreaInsets(); + const createWatchlist = useCreateWatchlist(); + + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [isPublic, setIsPublic] = useState(false); + const [allowedItemType, setAllowedItemType] = + useState(null); + const [defaultSortOrder, setDefaultSortOrder] = + useState("custom"); + + const handleCreate = useCallback(async () => { + if (!name.trim()) return; + + try { + await createWatchlist.mutateAsync({ + name: name.trim(), + description: description.trim() || undefined, + isPublic, + allowedItemType, + defaultSortOrder, + }); + router.back(); + } catch { + // Error handled by mutation + } + }, [ + name, + description, + isPublic, + allowedItemType, + defaultSortOrder, + createWatchlist, + router, + ]); + + return ( + + + {/* Name */} + + + {t("watchlists.name_label")} * + + + + + {/* Description */} + + + {t("watchlists.description_label")} + + + + + {/* Public Toggle */} + + + + {t("watchlists.is_public_label")} + + + {t("watchlists.is_public_description")} + + + + + + {/* Content Type */} + + + {t("watchlists.allowed_type_label")} + + + {ITEM_TYPES.map((type) => ( + setAllowedItemType(type.value)} + className={`px-4 py-2 rounded-lg ${allowedItemType === type.value ? "bg-purple-600" : "bg-neutral-800"}`} + > + + {type.label} + + + ))} + + + + {/* Sort Order */} + + + {t("watchlists.sort_order_label")} + + + {SORT_OPTIONS.map((sort) => ( + setDefaultSortOrder(sort.value)} + className={`px-4 py-2 rounded-lg ${defaultSortOrder === sort.value ? "bg-purple-600" : "bg-neutral-800"}`} + > + + {sort.label} + + + ))} + + + + {/* Create Button */} + + + + + + ); +} diff --git a/app/(auth)/(tabs)/(watchlists)/edit/[watchlistId].tsx b/app/(auth)/(tabs)/(watchlists)/edit/[watchlistId].tsx new file mode 100644 index 00000000..e9108f53 --- /dev/null +++ b/app/(auth)/(tabs)/(watchlists)/edit/[watchlistId].tsx @@ -0,0 +1,273 @@ +import { Ionicons } from "@expo/vector-icons"; +import { useLocalSearchParams, useRouter } from "expo-router"; +import { useCallback, useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + ActivityIndicator, + KeyboardAvoidingView, + Platform, + ScrollView, + Switch, + TextInput, + TouchableOpacity, + View, +} from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { Button } from "@/components/Button"; +import { Text } from "@/components/common/Text"; +import { useUpdateWatchlist } from "@/hooks/useWatchlistMutations"; +import { useWatchlistDetailQuery } from "@/hooks/useWatchlists"; +import type { + StreamystatsWatchlistAllowedItemType, + StreamystatsWatchlistSortOrder, +} from "@/utils/streamystats/types"; + +const ITEM_TYPES: Array<{ + value: StreamystatsWatchlistAllowedItemType; + label: string; +}> = [ + { value: null, label: "All Types" }, + { value: "Movie", label: "Movies Only" }, + { value: "Series", label: "Series Only" }, + { value: "Episode", label: "Episodes Only" }, +]; + +const SORT_OPTIONS: Array<{ + value: StreamystatsWatchlistSortOrder; + label: string; +}> = [ + { value: "custom", label: "Custom Order" }, + { value: "name", label: "Name" }, + { value: "dateAdded", label: "Date Added" }, + { value: "releaseDate", label: "Release Date" }, +]; + +export default function EditWatchlistScreen() { + const { t } = useTranslation(); + const router = useRouter(); + const insets = useSafeAreaInsets(); + const { watchlistId } = useLocalSearchParams<{ watchlistId: string }>(); + const watchlistIdNum = watchlistId + ? Number.parseInt(watchlistId, 10) + : undefined; + + const { data: watchlist, isLoading } = + useWatchlistDetailQuery(watchlistIdNum); + const updateWatchlist = useUpdateWatchlist(); + + const [name, setName] = useState(""); + const [description, setDescription] = useState(""); + const [isPublic, setIsPublic] = useState(false); + const [allowedItemType, setAllowedItemType] = + useState(null); + const [defaultSortOrder, setDefaultSortOrder] = + useState("custom"); + + // Initialize form with watchlist data + useEffect(() => { + if (watchlist) { + setName(watchlist.name); + setDescription(watchlist.description ?? ""); + setIsPublic(watchlist.isPublic); + setAllowedItemType( + (watchlist.allowedItemType as StreamystatsWatchlistAllowedItemType) ?? + null, + ); + setDefaultSortOrder( + (watchlist.defaultSortOrder as StreamystatsWatchlistSortOrder) ?? + "custom", + ); + } + }, [watchlist]); + + const handleSave = useCallback(async () => { + if (!name.trim() || !watchlistIdNum) return; + + try { + await updateWatchlist.mutateAsync({ + watchlistId: watchlistIdNum, + data: { + name: name.trim(), + description: description.trim() || undefined, + isPublic, + allowedItemType, + defaultSortOrder, + }, + }); + router.back(); + } catch { + // Error handled by mutation + } + }, [ + name, + description, + isPublic, + allowedItemType, + defaultSortOrder, + watchlistIdNum, + updateWatchlist, + router, + ]); + + if (isLoading) { + return ( + + + + ); + } + + if (!watchlist) { + return ( + + + {t("watchlists.not_found")} + + + ); + } + + return ( + + + {/* Name */} + + + {t("watchlists.name_label")} * + + + + + {/* Description */} + + + {t("watchlists.description_label")} + + + + + {/* Public Toggle */} + + + + {t("watchlists.is_public_label")} + + + {t("watchlists.is_public_description")} + + + + + + {/* Content Type */} + + + {t("watchlists.allowed_type_label")} + + + {ITEM_TYPES.map((type) => ( + setAllowedItemType(type.value)} + className={`px-4 py-2 rounded-lg ${allowedItemType === type.value ? "bg-purple-600" : "bg-neutral-800"}`} + > + + {type.label} + + + ))} + + + + {/* Sort Order */} + + + {t("watchlists.sort_order_label")} + + + {SORT_OPTIONS.map((sort) => ( + setDefaultSortOrder(sort.value)} + className={`px-4 py-2 rounded-lg ${defaultSortOrder === sort.value ? "bg-purple-600" : "bg-neutral-800"}`} + > + + {sort.label} + + + ))} + + + + {/* Save Button */} + + + + + + ); +} diff --git a/app/(auth)/(tabs)/(watchlists)/index.tsx b/app/(auth)/(tabs)/(watchlists)/index.tsx new file mode 100644 index 00000000..de2e2db8 --- /dev/null +++ b/app/(auth)/(tabs)/(watchlists)/index.tsx @@ -0,0 +1,239 @@ +import { Ionicons } from "@expo/vector-icons"; +import { FlashList } from "@shopify/flash-list"; +import { useRouter } from "expo-router"; +import { useAtomValue } from "jotai"; +import { useCallback, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { Platform, RefreshControl, TouchableOpacity, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { Button } from "@/components/Button"; +import { Text } from "@/components/common/Text"; +import { + useStreamystatsEnabled, + useWatchlistsQuery, +} from "@/hooks/useWatchlists"; +import { userAtom } from "@/providers/JellyfinProvider"; +import type { StreamystatsWatchlist } from "@/utils/streamystats/types"; + +interface WatchlistCardProps { + watchlist: StreamystatsWatchlist; + isOwner: boolean; + onPress: () => void; +} + +const WatchlistCard: React.FC = ({ + watchlist, + isOwner, + onPress, +}) => { + const { t } = useTranslation(); + + return ( + + + + {watchlist.name} + + + {isOwner && ( + + + {t("watchlists.you")} + + + )} + + + + + {watchlist.description && ( + + {watchlist.description} + + )} + + + + + + {watchlist.itemCount ?? 0}{" "} + {(watchlist.itemCount ?? 0) === 1 + ? t("watchlists.item") + : t("watchlists.items")} + + + {watchlist.allowedItemType && ( + + + {watchlist.allowedItemType} + + + )} + + + ); +}; + +const EmptyState: React.FC<{ onCreatePress: () => void }> = ({ + onCreatePress: _onCreatePress, +}) => { + const { t } = useTranslation(); + + return ( + + + + {t("watchlists.empty_title")} + + + {t("watchlists.empty_description")} + + + ); +}; + +const NotConfiguredState: React.FC = () => { + const { t } = useTranslation(); + const router = useRouter(); + + return ( + + + + {t("watchlists.not_configured_title")} + + + {t("watchlists.not_configured_description")} + + + + ); +}; + +export default function WatchlistsScreen() { + const { t } = useTranslation(); + const router = useRouter(); + const insets = useSafeAreaInsets(); + const user = useAtomValue(userAtom); + const streamystatsEnabled = useStreamystatsEnabled(); + const { data: watchlists, isLoading, refetch } = useWatchlistsQuery(); + const [refreshing, setRefreshing] = useState(false); + + const handleRefresh = useCallback(async () => { + setRefreshing(true); + await refetch(); + setRefreshing(false); + }, [refetch]); + + const handleCreatePress = useCallback(() => { + router.push("/(auth)/(tabs)/(watchlists)/create"); + }, [router]); + + const handleWatchlistPress = useCallback( + (watchlistId: number) => { + router.push(`/(auth)/(tabs)/(watchlists)/${watchlistId}`); + }, + [router], + ); + + // Separate watchlists into "mine" and "public" + const { myWatchlists, publicWatchlists } = useMemo(() => { + if (!watchlists) return { myWatchlists: [], publicWatchlists: [] }; + + const mine: StreamystatsWatchlist[] = []; + const pub: StreamystatsWatchlist[] = []; + + for (const w of watchlists) { + if (w.userId === user?.Id) { + mine.push(w); + } else { + pub.push(w); + } + } + + return { myWatchlists: mine, publicWatchlists: pub }; + }, [watchlists, user?.Id]); + + // Combine into sections for FlashList + const sections = useMemo(() => { + const result: Array< + | { type: "header"; title: string } + | { type: "watchlist"; data: StreamystatsWatchlist; isOwner: boolean } + > = []; + + if (myWatchlists.length > 0) { + result.push({ type: "header", title: t("watchlists.my_watchlists") }); + for (const w of myWatchlists) { + result.push({ type: "watchlist", data: w, isOwner: true }); + } + } + + if (publicWatchlists.length > 0) { + result.push({ type: "header", title: t("watchlists.public_watchlists") }); + for (const w of publicWatchlists) { + result.push({ type: "watchlist", data: w, isOwner: false }); + } + } + + return result; + }, [myWatchlists, publicWatchlists, t]); + + if (!streamystatsEnabled) { + return ; + } + + if (!isLoading && (!watchlists || watchlists.length === 0)) { + return ; + } + + return ( + + } + renderItem={({ item }) => { + if (item.type === "header") { + return ( + + {item.title} + + ); + } + + return ( + handleWatchlistPress(item.data.id)} + /> + ); + }} + getItemType={(item) => item.type} + /> + ); +} diff --git a/app/(auth)/(tabs)/_layout.tsx b/app/(auth)/(tabs)/_layout.tsx index e8a79728..ac184d6f 100644 --- a/app/(auth)/(tabs)/_layout.tsx +++ b/app/(auth)/(tabs)/_layout.tsx @@ -10,8 +10,10 @@ import type { import { useFocusEffect, useRouter, withLayoutContext } from "expo-router"; import { useCallback } from "react"; import { useTranslation } from "react-i18next"; -import { Platform } from "react-native"; +import { Platform, View } from "react-native"; import { SystemBars } from "react-native-edge-to-edge"; +import { MiniPlayerBar } from "@/components/music/MiniPlayerBar"; +import { MusicPlaybackEngine } from "@/components/music/MusicPlaybackEngine"; import { Colors } from "@/constants/Colors"; import { useSettings } from "@/utils/atoms/settings"; import { eventBus } from "@/utils/eventBus"; @@ -47,7 +49,7 @@ export default function TabLayout() { ); return ( - <> + ); } diff --git a/app/(auth)/now-playing.tsx b/app/(auth)/now-playing.tsx new file mode 100644 index 00000000..ba62fea2 --- /dev/null +++ b/app/(auth)/now-playing.tsx @@ -0,0 +1,539 @@ +import { Ionicons } from "@expo/vector-icons"; +import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; +import { Image } from "expo-image"; +import { useRouter } from "expo-router"; +import { useAtom } from "jotai"; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { + Dimensions, + FlatList, + Platform, + ScrollView, + TouchableOpacity, + View, +} from "react-native"; +import { Slider } from "react-native-awesome-slider"; +import { useSharedValue } from "react-native-reanimated"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { Text } from "@/components/common/Text"; +import { apiAtom } from "@/providers/JellyfinProvider"; +import { + type RepeatMode, + useMusicPlayer, +} from "@/providers/MusicPlayerProvider"; +import { formatDuration } from "@/utils/time"; + +const { width: SCREEN_WIDTH } = Dimensions.get("window"); +const ARTWORK_SIZE = SCREEN_WIDTH - 80; + +type ViewMode = "player" | "queue"; + +export default function NowPlayingScreen() { + const [api] = useAtom(apiAtom); + const router = useRouter(); + const insets = useSafeAreaInsets(); + const [viewMode, setViewMode] = useState("player"); + + const { + currentTrack, + queue, + queueIndex, + isPlaying, + progress, + duration, + repeatMode, + shuffleEnabled, + togglePlayPause, + next, + previous, + seek, + setRepeatMode, + toggleShuffle, + jumpToIndex, + removeFromQueue, + stop, + } = useMusicPlayer(); + + const sliderProgress = useSharedValue(0); + const sliderMin = useSharedValue(0); + const sliderMax = useSharedValue(1); + + useEffect(() => { + sliderProgress.value = progress; + }, [progress, sliderProgress]); + + useEffect(() => { + sliderMax.value = duration > 0 ? duration : 1; + }, [duration, sliderMax]); + + const imageUrl = useMemo(() => { + if (!api || !currentTrack) return null; + const albumId = currentTrack.AlbumId || currentTrack.ParentId; + if (albumId) { + return `${api.basePath}/Items/${albumId}/Images/Primary?maxHeight=600&maxWidth=600`; + } + return `${api.basePath}/Items/${currentTrack.Id}/Images/Primary?maxHeight=600&maxWidth=600`; + }, [api, currentTrack]); + + const progressText = useMemo(() => { + const progressTicks = progress * 10000000; + return formatDuration(progressTicks); + }, [progress]); + + const durationText = useMemo(() => { + const durationTicks = duration * 10000000; + return formatDuration(durationTicks); + }, [duration]); + + const handleSliderComplete = useCallback( + (value: number) => { + seek(value); + }, + [seek], + ); + + const handleClose = useCallback(() => { + router.back(); + }, [router]); + + const handleStop = useCallback(() => { + stop(); + router.back(); + }, [stop, router]); + + const cycleRepeatMode = useCallback(() => { + const modes: RepeatMode[] = ["off", "all", "one"]; + const currentIndex = modes.indexOf(repeatMode); + const nextMode = modes[(currentIndex + 1) % modes.length]; + setRepeatMode(nextMode); + }, [repeatMode, setRepeatMode]); + + const getRepeatIcon = (): string => { + switch (repeatMode) { + case "one": + return "repeat"; + case "all": + return "repeat"; + default: + return "repeat"; + } + }; + + const canGoNext = queueIndex < queue.length - 1 || repeatMode === "all"; + const canGoPrevious = queueIndex > 0 || progress > 3 || repeatMode === "all"; + + if (!currentTrack) { + return ( + + No track playing + + ); + } + + return ( + + {/* Header */} + + + + + + + setViewMode("player")} + className='px-3 py-1' + > + + Now Playing + + + setViewMode("queue")} + className='px-3 py-1' + > + + Queue ({queue.length}) + + + + + + + + + + {viewMode === "player" ? ( + + ) : ( + + )} + + ); +} + +interface PlayerViewProps { + api: any; + currentTrack: BaseItemDto; + imageUrl: string | null; + sliderProgress: any; + sliderMin: any; + sliderMax: any; + progressText: string; + durationText: string; + isPlaying: boolean; + repeatMode: RepeatMode; + shuffleEnabled: boolean; + canGoNext: boolean; + canGoPrevious: boolean; + onSliderComplete: (value: number) => void; + onTogglePlayPause: () => void; + onNext: () => void; + onPrevious: () => void; + onCycleRepeat: () => void; + onToggleShuffle: () => void; + getRepeatIcon: () => string; + queue: BaseItemDto[]; + queueIndex: number; +} + +const PlayerView: React.FC = ({ + currentTrack, + imageUrl, + sliderProgress, + sliderMin, + sliderMax, + progressText, + durationText, + isPlaying, + repeatMode, + shuffleEnabled, + canGoNext, + canGoPrevious, + onSliderComplete, + onTogglePlayPause, + onNext, + onPrevious, + onCycleRepeat, + onToggleShuffle, + getRepeatIcon, + queue, + queueIndex, +}) => { + return ( + + {/* Album artwork */} + + {imageUrl ? ( + + ) : ( + + + + )} + + + {/* Track info */} + + + {currentTrack.Name} + + + {currentTrack.Artists?.join(", ") || currentTrack.AlbumArtist} + + {currentTrack.Album && ( + + {currentTrack.Album} + + )} + + + {/* Progress slider */} + + null} + /> + + {progressText} + {durationText} + + + + {/* Main Controls */} + + + + + + + + + + + + + + + {/* Shuffle & Repeat Controls */} + + + + + + + + {repeatMode === "one" && ( + + 1 + + )} + + + + {/* Queue info */} + {queue.length > 1 && ( + + + {queueIndex + 1} of {queue.length} + + + )} + + ); +}; + +interface QueueViewProps { + api: any; + queue: BaseItemDto[]; + queueIndex: number; + onJumpToIndex: (index: number) => void; + onRemoveFromQueue: (index: number) => void; +} + +const QueueView: React.FC = ({ + api, + queue, + queueIndex, + onJumpToIndex, + onRemoveFromQueue, +}) => { + const renderQueueItem = useCallback( + ({ item, index }: { item: BaseItemDto; index: number }) => { + const isCurrentTrack = index === queueIndex; + const isPast = index < queueIndex; + + const albumId = item.AlbumId || item.ParentId; + const imageUrl = api + ? albumId + ? `${api.basePath}/Items/${albumId}/Images/Primary?maxHeight=80&maxWidth=80` + : `${api.basePath}/Items/${item.Id}/Images/Primary?maxHeight=80&maxWidth=80` + : null; + + return ( + onJumpToIndex(index)} + className={`flex-row items-center px-4 py-3 ${isCurrentTrack ? "bg-purple-900/30" : ""}`} + style={{ opacity: isPast ? 0.5 : 1 }} + > + {/* Track number / Now playing indicator */} + + {isCurrentTrack ? ( + + ) : ( + {index + 1} + )} + + + {/* Album art */} + + {imageUrl ? ( + + ) : ( + + + + )} + + + {/* Track info */} + + + {item.Name} + + + {item.Artists?.join(", ") || item.AlbumArtist} + + + + {/* Remove button (not for current track) */} + {!isCurrentTrack && ( + onRemoveFromQueue(index)} + hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }} + className='p-2' + > + + + )} + + ); + }, + [api, queueIndex, onJumpToIndex, onRemoveFromQueue], + ); + + const _upNext = queue.slice(queueIndex + 1); + const history = queue.slice(0, queueIndex); + + return ( + `${item.Id}-${index}`} + renderItem={renderQueueItem} + showsVerticalScrollIndicator={false} + initialScrollIndex={queueIndex > 2 ? queueIndex - 2 : 0} + getItemLayout={(_, index) => ({ + length: 72, + offset: 72 * index, + index, + })} + ListHeaderComponent={ + + + {history.length > 0 ? "Playing from queue" : "Up next"} + + + } + ListEmptyComponent={ + + Queue is empty + + } + /> + ); +}; diff --git a/app/(auth)/player/_layout.tsx b/app/(auth)/player/_layout.tsx index 97a3f7eb..5604160e 100644 --- a/app/(auth)/player/_layout.tsx +++ b/app/(auth)/player/_layout.tsx @@ -1,7 +1,33 @@ import { Stack } from "expo-router"; +import { useEffect } from "react"; +import { AppState } from "react-native"; import { SystemBars } from "react-native-edge-to-edge"; +import { useOrientation } from "@/hooks/useOrientation"; +import { useSettings } from "@/utils/atoms/settings"; + export default function Layout() { + const { settings } = useSettings(); + const { lockOrientation, unlockOrientation } = useOrientation(); + + useEffect(() => { + if (settings?.defaultVideoOrientation) { + lockOrientation(settings.defaultVideoOrientation); + } + + // Re-apply orientation lock when app returns to foreground (iOS resets it) + const subscription = AppState.addEventListener("change", (nextAppState) => { + if (nextAppState === "active" && settings?.defaultVideoOrientation) { + lockOrientation(settings.defaultVideoOrientation); + } + }); + + return () => { + subscription.remove(); + unlockOrientation(); + }; + }, [settings?.defaultVideoOrientation, lockOrientation, unlockOrientation]); + return ( <> diff --git a/components/PlayedStatus.tsx b/components/PlayedStatus.tsx index 0023b014..f4cf1ed6 100644 --- a/components/PlayedStatus.tsx +++ b/components/PlayedStatus.tsx @@ -1,5 +1,6 @@ import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; import type React from "react"; +import { useCallback } from "react"; import { View, type ViewProps } from "react-native"; import { useMarkAsPlayed } from "@/hooks/useMarkAsPlayed"; import { RoundButton } from "./RoundButton"; @@ -14,14 +15,16 @@ export const PlayedStatus: React.FC = ({ items, ...props }) => { const allPlayed = items.every((item) => item.UserData?.Played); const toggle = useMarkAsPlayed(items); + const handlePress = useCallback(() => { + void toggle(!allPlayed); + }, [allPlayed, toggle]); + return ( { - await toggle(!allPlayed); - }} + onPress={handlePress} size={props.size} /> diff --git a/components/SimilarItems.tsx b/components/SimilarItems.tsx index b73e5518..ce09cbcf 100644 --- a/components/SimilarItems.tsx +++ b/components/SimilarItems.tsx @@ -6,6 +6,7 @@ import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { View, type ViewProps } from "react-native"; import MoviePoster from "@/components/posters/MoviePoster"; +import { POSTER_CAROUSEL_HEIGHT } from "@/constants/Values"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { HorizontalScroll } from "./common/HorizontalScroll"; import { Text } from "./common/Text"; @@ -53,7 +54,7 @@ export const SimilarItems: React.FC = ({ ( = ({ if (currentItem) { setSelectedOptions({ bitrate: defaultBitrate, - mediaSource: defaultMediaSource, + mediaSource: defaultMediaSource ?? undefined, subtitleIndex: defaultSubtitleIndex ?? -1, audioIndex: defaultAudioIndex, }); diff --git a/components/common/HorizontalScroll.tsx b/components/common/HorizontalScroll.tsx index 6c6af8e7..e907f52f 100644 --- a/components/common/HorizontalScroll.tsx +++ b/components/common/HorizontalScroll.tsx @@ -58,7 +58,7 @@ export const HorizontalScroll = ( if (!data || loading) { return ( - + diff --git a/components/common/SectionHeader.tsx b/components/common/SectionHeader.tsx new file mode 100644 index 00000000..aa9efc43 --- /dev/null +++ b/components/common/SectionHeader.tsx @@ -0,0 +1,42 @@ +import { TouchableOpacity, View } from "react-native"; +import { Colors } from "@/constants/Colors"; +import { Text } from "./Text"; + +type Props = { + title: string; + actionLabel?: string; + actionDisabled?: boolean; + onPressAction?: () => void; +}; + +export const SectionHeader: React.FC = ({ + title, + actionLabel, + actionDisabled = false, + onPressAction, +}) => { + const shouldShowAction = Boolean(actionLabel) && Boolean(onPressAction); + + return ( + + {title} + {shouldShowAction && ( + + + {actionLabel} + + + )} + + ); +}; diff --git a/components/common/TouchableItemRouter.tsx b/components/common/TouchableItemRouter.tsx index e95fd5e7..e776b7cf 100644 --- a/components/common/TouchableItemRouter.tsx +++ b/components/common/TouchableItemRouter.tsx @@ -16,6 +16,10 @@ export const itemRouter = (item: BaseItemDto, from: string) => { return `/(auth)/(tabs)/${from}/livetv`; } + if ("CollectionType" in item && item.CollectionType === "music") { + return `/(auth)/(tabs)/(libraries)/music/${item.Id}`; + } + if (item.Type === "Series") { return `/(auth)/(tabs)/${from}/series/${item.Id}`; } @@ -50,6 +54,13 @@ export const getItemNavigation = (item: BaseItemDto, _from: string) => { }; } + if ("CollectionType" in item && item.CollectionType === "music") { + return { + pathname: "/music/[libraryId]" as const, + params: { libraryId: item.Id! }, + }; + } + if (item.Type === "Series") { return { pathname: "/series/[id]" as const, @@ -99,6 +110,25 @@ export const TouchableItemRouter: React.FC> = ({ const from = (segments as string[])[2] || "(home)"; + const handlePress = useCallback(() => { + // For offline mode, we still need to use query params + if (isOffline) { + const url = `${itemRouter(item, from)}&offline=true`; + router.push(url as any); + return; + } + + // Force music libraries to navigate via the explicit string route. + // This avoids losing the dynamic [libraryId] param when going through a nested navigator. + if ("CollectionType" in item && item.CollectionType === "music") { + router.push(itemRouter(item, from) as any); + return; + } + + const navigation = getItemNavigation(item, from); + router.push(navigation as any); + }, [from, isOffline, item, router]); + const showActionSheet = useCallback(() => { if ( !( @@ -108,13 +138,14 @@ export const TouchableItemRouter: React.FC> = ({ ) ) return; - const options = [ + + const options: string[] = [ "Mark as Played", "Mark as Not Played", isFavorite ? "Unmark as Favorite" : "Mark as Favorite", "Cancel", ]; - const cancelButtonIndex = 3; + const cancelButtonIndex = options.length - 1; showActionSheetWithOptions( { @@ -131,28 +162,24 @@ export const TouchableItemRouter: React.FC> = ({ } }, ); - }, [showActionSheetWithOptions, isFavorite, markAsPlayedStatus]); + }, [ + showActionSheetWithOptions, + isFavorite, + markAsPlayedStatus, + toggleFavorite, + ]); if ( from === "(home)" || from === "(search)" || from === "(libraries)" || - from === "(favorites)" + from === "(favorites)" || + from === "(watchlists)" ) return ( { - if (isOffline) { - // For offline mode, we still need to use query params - const url = `${itemRouter(item, from)}&offline=true`; - router.push(url as any); - return; - } - - const navigation = getItemNavigation(item, from); - router.push(navigation as any); - }} + onPress={handlePress} {...props} > {children} diff --git a/components/filters/ResetFiltersButton.tsx b/components/filters/ResetFiltersButton.tsx index c0cc6d68..856ccd3b 100644 --- a/components/filters/ResetFiltersButton.tsx +++ b/components/filters/ResetFiltersButton.tsx @@ -2,6 +2,7 @@ import { Ionicons } from "@expo/vector-icons"; import { useAtom } from "jotai"; import { TouchableOpacity, type TouchableOpacityProps } from "react-native"; import { + filterByAtom, genreFilterAtom, tagsFilterAtom, yearFilterAtom, @@ -13,11 +14,13 @@ export const ResetFiltersButton: React.FC = ({ ...props }) => { const [selectedGenres, setSelectedGenres] = useAtom(genreFilterAtom); const [selectedTags, setSelectedTags] = useAtom(tagsFilterAtom); const [selectedYears, setSelectedYears] = useAtom(yearFilterAtom); + const [selectedFilters, setSelectedFilters] = useAtom(filterByAtom); if ( selectedGenres.length === 0 && selectedTags.length === 0 && - selectedYears.length === 0 + selectedYears.length === 0 && + selectedFilters.length === 0 ) { return null; } @@ -28,6 +31,7 @@ export const ResetFiltersButton: React.FC = ({ ...props }) => { setSelectedGenres([]); setSelectedTags([]); setSelectedYears([]); + setSelectedFilters([]); }} className='bg-purple-600 rounded-full w-[30px] h-[30px] flex items-center justify-center mr-1' {...props} diff --git a/components/home/Favorites.tsx b/components/home/Favorites.tsx index b1b32d95..9a9234ab 100644 --- a/components/home/Favorites.tsx +++ b/components/home/Favorites.tsx @@ -2,6 +2,7 @@ import type { Api } from "@jellyfin/sdk"; import type { BaseItemKind } from "@jellyfin/sdk/lib/generated-client"; import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; import { Image } from "expo-image"; +import { useRouter } from "expo-router"; import { t } from "i18next"; import { useAtom } from "jotai"; import { useCallback, useEffect, useState } from "react"; @@ -22,8 +23,10 @@ type FavoriteTypes = type EmptyState = Record; export const Favorites = () => { + const router = useRouter(); const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); + const pageSize = 20; const [emptyState, setEmptyState] = useState({ Series: false, Movie: false, @@ -91,35 +94,77 @@ export const Favorites = () => { const fetchFavoriteSeries = useCallback( ({ pageParam }: { pageParam: number }) => - fetchFavoritesByType("Series", pageParam), - [fetchFavoritesByType], + fetchFavoritesByType("Series", pageParam, pageSize), + [fetchFavoritesByType, pageSize], ); const fetchFavoriteMovies = useCallback( ({ pageParam }: { pageParam: number }) => - fetchFavoritesByType("Movie", pageParam), - [fetchFavoritesByType], + fetchFavoritesByType("Movie", pageParam, pageSize), + [fetchFavoritesByType, pageSize], ); const fetchFavoriteEpisodes = useCallback( ({ pageParam }: { pageParam: number }) => - fetchFavoritesByType("Episode", pageParam), - [fetchFavoritesByType], + fetchFavoritesByType("Episode", pageParam, pageSize), + [fetchFavoritesByType, pageSize], ); const fetchFavoriteVideos = useCallback( ({ pageParam }: { pageParam: number }) => - fetchFavoritesByType("Video", pageParam), - [fetchFavoritesByType], + fetchFavoritesByType("Video", pageParam, pageSize), + [fetchFavoritesByType, pageSize], ); const fetchFavoriteBoxsets = useCallback( ({ pageParam }: { pageParam: number }) => - fetchFavoritesByType("BoxSet", pageParam), - [fetchFavoritesByType], + fetchFavoritesByType("BoxSet", pageParam, pageSize), + [fetchFavoritesByType, pageSize], ); const fetchFavoritePlaylists = useCallback( ({ pageParam }: { pageParam: number }) => - fetchFavoritesByType("Playlist", pageParam), - [fetchFavoritesByType], + fetchFavoritesByType("Playlist", pageParam, pageSize), + [fetchFavoritesByType, pageSize], ); + const handleSeeAllSeries = useCallback(() => { + router.push({ + pathname: "/(auth)/(tabs)/(favorites)/see-all", + params: { type: "Series", title: t("favorites.series") }, + } as any); + }, [router]); + + const handleSeeAllMovies = useCallback(() => { + router.push({ + pathname: "/(auth)/(tabs)/(favorites)/see-all", + params: { type: "Movie", title: t("favorites.movies") }, + } as any); + }, [router]); + + const handleSeeAllEpisodes = useCallback(() => { + router.push({ + pathname: "/(auth)/(tabs)/(favorites)/see-all", + params: { type: "Episode", title: t("favorites.episodes") }, + } as any); + }, [router]); + + const handleSeeAllVideos = useCallback(() => { + router.push({ + pathname: "/(auth)/(tabs)/(favorites)/see-all", + params: { type: "Video", title: t("favorites.videos") }, + } as any); + }, [router]); + + const handleSeeAllBoxsets = useCallback(() => { + router.push({ + pathname: "/(auth)/(tabs)/(favorites)/see-all", + params: { type: "BoxSet", title: t("favorites.boxsets") }, + } as any); + }, [router]); + + const handleSeeAllPlaylists = useCallback(() => { + router.push({ + pathname: "/(auth)/(tabs)/(favorites)/see-all", + params: { type: "Playlist", title: t("favorites.playlists") }, + } as any); + }, [router]); + return ( {areAllEmpty() && ( @@ -143,6 +188,8 @@ export const Favorites = () => { queryKey={["home", "favorites", "series"]} title={t("favorites.series")} hideIfEmpty + pageSize={pageSize} + onPressSeeAll={handleSeeAllSeries} /> { title={t("favorites.movies")} hideIfEmpty orientation='vertical' + pageSize={pageSize} + onPressSeeAll={handleSeeAllMovies} /> ); diff --git a/components/home/Home.tsx b/components/home/Home.tsx index 5803083d..ef7a1e9a 100644 --- a/components/home/Home.tsx +++ b/components/home/Home.tsx @@ -28,6 +28,8 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Button } from "@/components/Button"; import { Text } from "@/components/common/Text"; import { InfiniteScrollingCollectionList } from "@/components/home/InfiniteScrollingCollectionList"; +import { StreamystatsPromotedWatchlists } from "@/components/home/StreamystatsPromotedWatchlists"; +import { StreamystatsRecommendations } from "@/components/home/StreamystatsRecommendations"; import { Loader } from "@/components/Loader"; import { MediaListSection } from "@/components/medialists/MediaListSection"; import { Colors } from "@/constants/Colors"; @@ -45,12 +47,14 @@ type InfiniteScrollingCollectionListSection = { queryFn: QueryFunction; orientation?: "horizontal" | "vertical"; pageSize?: number; + priority?: 1 | 2; // 1 = high priority (loads first), 2 = low priority }; type MediaListSectionType = { type: "MediaListSection"; queryKey: (string | undefined)[]; queryFn: QueryFunction; + priority?: 1 | 2; }; type Section = InfiniteScrollingCollectionListSection | MediaListSectionType; @@ -74,7 +78,7 @@ export const Home = () => { retryCheck, } = useNetworkStatus(); const invalidateCache = useInvalidatePlaybackProgressCache(); - const [scrollY, setScrollY] = useState(0); + const [loadedSections, setLoadedSections] = useState>(new Set()); useEffect(() => { if (isConnected && !prevIsConnected.current) { @@ -172,6 +176,7 @@ export const Home = () => { const refetch = async () => { setLoading(true); + setLoadedSections(new Set()); await refreshStreamyfinPluginSettings(); await invalidateCache(); setLoading(false); @@ -194,10 +199,10 @@ export const Home = () => { ( await getUserLibraryApi(api).getLatestMedia({ userId: user?.Id, - limit: 100, // Fetch a larger set for pagination - fields: ["PrimaryImageAspectRatio", "Path", "Genres"], + limit: 10, + fields: ["PrimaryImageAspectRatio"], imageTypeLimit: 1, - enableImageTypes: ["Primary", "Backdrop", "Thumb", "Logo"], + enableImageTypes: ["Primary", "Backdrop", "Thumb"], includeItemTypes, parentId, }) @@ -236,64 +241,143 @@ export const Home = () => { ); }); + // Helper to sort items by most recent activity + const sortByRecentActivity = (items: BaseItemDto[]): BaseItemDto[] => { + return items.sort((a, b) => { + const dateA = a.UserData?.LastPlayedDate || a.DateCreated || ""; + const dateB = b.UserData?.LastPlayedDate || b.DateCreated || ""; + return new Date(dateB).getTime() - new Date(dateA).getTime(); + }); + }; + + // Helper to deduplicate items by ID + const deduplicateById = (items: BaseItemDto[]): BaseItemDto[] => { + const seen = new Set(); + return items.filter((item) => { + if (!item.Id || seen.has(item.Id)) return false; + seen.add(item.Id); + return true; + }); + }; + + // Build the first sections based on merge setting + const firstSections: Section[] = settings.mergeNextUpAndContinueWatching + ? [ + { + title: t("home.continue_and_next_up"), + queryKey: ["home", "continueAndNextUp"], + queryFn: async ({ pageParam = 0 }) => { + // Fetch both in parallel + const [resumeResponse, nextUpResponse] = await Promise.all([ + getItemsApi(api).getResumeItems({ + userId: user.Id, + enableImageTypes: ["Primary", "Backdrop", "Thumb"], + includeItemTypes: ["Movie", "Series", "Episode"], + startIndex: 0, + limit: 20, + }), + getTvShowsApi(api).getNextUp({ + userId: user?.Id, + startIndex: 0, + limit: 20, + enableImageTypes: ["Primary", "Backdrop", "Thumb"], + enableResumable: false, + }), + ]); + + const resumeItems = resumeResponse.data.Items || []; + const nextUpItems = nextUpResponse.data.Items || []; + + // Combine, sort by recent activity, deduplicate + const combined = [...resumeItems, ...nextUpItems]; + const sorted = sortByRecentActivity(combined); + const deduplicated = deduplicateById(sorted); + + // Paginate client-side + return deduplicated.slice(pageParam, pageParam + 10); + }, + type: "InfiniteScrollingCollectionList", + orientation: "horizontal", + pageSize: 10, + priority: 1, + }, + ] + : [ + { + title: t("home.continue_watching"), + queryKey: ["home", "resumeItems"], + queryFn: async ({ pageParam = 0 }) => + ( + await getItemsApi(api).getResumeItems({ + userId: user.Id, + enableImageTypes: ["Primary", "Backdrop", "Thumb"], + includeItemTypes: ["Movie", "Series", "Episode"], + startIndex: pageParam, + limit: 10, + }) + ).data.Items || [], + type: "InfiniteScrollingCollectionList", + orientation: "horizontal", + pageSize: 10, + priority: 1, + }, + { + title: t("home.next_up"), + queryKey: ["home", "nextUp-all"], + queryFn: async ({ pageParam = 0 }) => + ( + await getTvShowsApi(api).getNextUp({ + userId: user?.Id, + startIndex: pageParam, + limit: 10, + enableImageTypes: ["Primary", "Backdrop", "Thumb"], + enableResumable: false, + }) + ).data.Items || [], + type: "InfiniteScrollingCollectionList", + orientation: "horizontal", + pageSize: 10, + priority: 1, + }, + ]; + const ss: Section[] = [ - { - title: t("home.continue_watching"), - queryKey: ["home", "resumeItems"], - queryFn: async ({ pageParam = 0 }) => - ( - await getItemsApi(api).getResumeItems({ - userId: user.Id, - enableImageTypes: ["Primary", "Backdrop", "Thumb", "Logo"], - includeItemTypes: ["Movie", "Series", "Episode"], - fields: ["Genres"], - startIndex: pageParam, - limit: 10, - }) - ).data.Items || [], - type: "InfiniteScrollingCollectionList", - orientation: "horizontal", - pageSize: 10, - }, - { - title: t("home.next_up"), - queryKey: ["home", "nextUp-all"], - queryFn: async ({ pageParam = 0 }) => - ( - await getTvShowsApi(api).getNextUp({ - userId: user?.Id, - fields: ["MediaSourceCount", "Genres"], - startIndex: pageParam, - limit: 10, - enableImageTypes: ["Primary", "Backdrop", "Thumb", "Logo"], - enableResumable: false, - }) - ).data.Items || [], - type: "InfiniteScrollingCollectionList", - orientation: "horizontal", - pageSize: 10, - }, - ...latestMediaViews, - { - title: t("home.suggested_movies"), - queryKey: ["home", "suggestedMovies", user?.Id], - queryFn: async ({ pageParam = 0 }) => - ( - await getSuggestionsApi(api).getSuggestions({ - userId: user?.Id, - startIndex: pageParam, - limit: 10, - mediaType: ["Video"], - type: ["Movie"], - }) - ).data.Items || [], - type: "InfiniteScrollingCollectionList", - orientation: "vertical", - pageSize: 10, - }, + ...firstSections, + ...latestMediaViews.map((s) => ({ ...s, priority: 2 as const })), + // Only show Jellyfin suggested movies if StreamyStats recommendations are disabled + ...(!settings?.streamyStatsMovieRecommendations + ? [ + { + title: t("home.suggested_movies"), + queryKey: ["home", "suggestedMovies", user?.Id], + queryFn: async ({ pageParam = 0 }: { pageParam?: number }) => + ( + await getSuggestionsApi(api).getSuggestions({ + userId: user?.Id, + startIndex: pageParam, + limit: 10, + mediaType: ["Video"], + type: ["Movie"], + }) + ).data.Items || [], + type: "InfiniteScrollingCollectionList" as const, + orientation: "vertical" as const, + pageSize: 10, + priority: 2 as const, + }, + ] + : []), ]; return ss; - }, [api, user?.Id, collections, t, createCollectionConfig]); + }, [ + api, + user?.Id, + collections, + t, + createCollectionConfig, + settings?.streamyStatsMovieRecommendations, + settings.mergeNextUpAndContinueWatching, + ]); const customSections = useMemo(() => { if (!api || !user?.Id || !settings?.home?.sections) return []; @@ -322,10 +406,9 @@ export const Home = () => { if (section.nextUp) { const response = await getTvShowsApi(api).getNextUp({ userId: user?.Id, - fields: ["MediaSourceCount", "Genres"], startIndex: pageParam, limit: section.nextUp?.limit || pageSize, - enableImageTypes: ["Primary", "Backdrop", "Thumb", "Logo"], + enableImageTypes: ["Primary", "Backdrop", "Thumb"], enableResumable: section.nextUp?.enableResumable, enableRewatching: section.nextUp?.enableRewatching, }); @@ -338,7 +421,7 @@ export const Home = () => { await getUserLibraryApi(api).getLatestMedia({ userId: user?.Id, includeItemTypes: section.latest?.includeItemTypes, - limit: section.latest?.limit || 100, // Fetch larger set + limit: section.latest?.limit || 10, isPlayed: section.latest?.isPlayed, groupItems: section.latest?.groupItems, }) @@ -367,6 +450,8 @@ export const Home = () => { type: "InfiniteScrollingCollectionList", orientation: section?.orientation || "vertical", pageSize, + // First 2 custom sections are high priority + priority: index < 2 ? 1 : 2, }); }); return ss; @@ -374,6 +459,25 @@ export const Home = () => { const sections = settings?.home?.sections ? customSections : defaultSections; + // Get all high priority section keys and check if all have loaded + const highPrioritySectionKeys = useMemo(() => { + return sections + .filter((s) => s.priority === 1) + .map((s) => s.queryKey.join("-")); + }, [sections]); + + const allHighPriorityLoaded = useMemo(() => { + return highPrioritySectionKeys.every((key) => loadedSections.has(key)); + }, [highPrioritySectionKeys, loadedSections]); + + const markSectionLoaded = useCallback( + (queryKey: (string | undefined | null)[]) => { + const key = queryKey.join("-"); + setLoadedSections((prev) => new Set(prev).add(key)); + }, + [], + ); + if (!isConnected || serverConnected !== true) { let title = ""; let subtitle = ""; @@ -451,10 +555,6 @@ export const Home = () => { ref={scrollRef} nestedScrollEnabled contentInsetAdjustmentBehavior='automatic' - onScroll={(event) => { - setScrollY(event.nativeEvent.contentOffset.y - 500); - }} - scrollEventThrottle={16} refreshControl={ { style={{ paddingTop: Platform.OS === "android" ? 10 : 0 }} > {sections.map((section, index) => { + // Render Streamystats sections after Continue Watching and Next Up + // When merged, they appear after index 0; otherwise after index 1 + const streamystatsIndex = settings.mergeNextUpAndContinueWatching + ? 0 + : 1; + const hasStreamystatsContent = + settings.streamyStatsMovieRecommendations || + settings.streamyStatsSeriesRecommendations || + settings.streamyStatsPromotedWatchlists; + const streamystatsSections = + index === streamystatsIndex && hasStreamystatsContent ? ( + + {settings.streamyStatsMovieRecommendations && ( + + )} + {settings.streamyStatsSeriesRecommendations && ( + + )} + {settings.streamyStatsPromotedWatchlists && ( + + )} + + ) : null; if (section.type === "InfiniteScrollingCollectionList") { + const isHighPriority = section.priority === 1; return ( - + + markSectionLoaded(section.queryKey) + : undefined + } + /> + {streamystatsSections} + ); } if (section.type === "MediaListSection") { return ( - + + + {streamystatsSections} + ); } return null; diff --git a/components/home/HomeWithCarousel.tsx b/components/home/HomeWithCarousel.tsx index 43e7fefb..ba74c15c 100644 --- a/components/home/HomeWithCarousel.tsx +++ b/components/home/HomeWithCarousel.tsx @@ -30,6 +30,8 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"; import { Button } from "@/components/Button"; import { Text } from "@/components/common/Text"; import { InfiniteScrollingCollectionList } from "@/components/home/InfiniteScrollingCollectionList"; +import { StreamystatsPromotedWatchlists } from "@/components/home/StreamystatsPromotedWatchlists"; +import { StreamystatsRecommendations } from "@/components/home/StreamystatsRecommendations"; import { Loader } from "@/components/Loader"; import { MediaListSection } from "@/components/medialists/MediaListSection"; import { Colors } from "@/constants/Colors"; @@ -241,64 +243,143 @@ export const HomeWithCarousel = () => { ); }); + // Helper to sort items by most recent activity + const sortByRecentActivity = (items: BaseItemDto[]): BaseItemDto[] => { + return items.sort((a, b) => { + const dateA = a.UserData?.LastPlayedDate || a.DateCreated || ""; + const dateB = b.UserData?.LastPlayedDate || b.DateCreated || ""; + return new Date(dateB).getTime() - new Date(dateA).getTime(); + }); + }; + + // Helper to deduplicate items by ID + const deduplicateById = (items: BaseItemDto[]): BaseItemDto[] => { + const seen = new Set(); + return items.filter((item) => { + if (!item.Id || seen.has(item.Id)) return false; + seen.add(item.Id); + return true; + }); + }; + + // Build the first sections based on merge setting + const firstSections: Section[] = settings.mergeNextUpAndContinueWatching + ? [ + { + title: t("home.continue_and_next_up"), + queryKey: ["home", "continueAndNextUp"], + queryFn: async ({ pageParam = 0 }) => { + // Fetch both in parallel + const [resumeResponse, nextUpResponse] = await Promise.all([ + getItemsApi(api).getResumeItems({ + userId: user.Id, + enableImageTypes: ["Primary", "Backdrop", "Thumb", "Logo"], + includeItemTypes: ["Movie", "Series", "Episode"], + fields: ["Genres"], + startIndex: 0, + limit: 20, + }), + getTvShowsApi(api).getNextUp({ + userId: user?.Id, + fields: ["MediaSourceCount", "Genres"], + startIndex: 0, + limit: 20, + enableImageTypes: ["Primary", "Backdrop", "Thumb", "Logo"], + enableResumable: false, + }), + ]); + + const resumeItems = resumeResponse.data.Items || []; + const nextUpItems = nextUpResponse.data.Items || []; + + // Combine, sort by recent activity, deduplicate + const combined = [...resumeItems, ...nextUpItems]; + const sorted = sortByRecentActivity(combined); + const deduplicated = deduplicateById(sorted); + + // Paginate client-side + return deduplicated.slice(pageParam, pageParam + 10); + }, + type: "InfiniteScrollingCollectionList", + orientation: "horizontal", + pageSize: 10, + }, + ] + : [ + { + title: t("home.continue_watching"), + queryKey: ["home", "resumeItems"], + queryFn: async ({ pageParam = 0 }) => + ( + await getItemsApi(api).getResumeItems({ + userId: user.Id, + enableImageTypes: ["Primary", "Backdrop", "Thumb", "Logo"], + includeItemTypes: ["Movie", "Series", "Episode"], + fields: ["Genres"], + startIndex: pageParam, + limit: 10, + }) + ).data.Items || [], + type: "InfiniteScrollingCollectionList", + orientation: "horizontal", + pageSize: 10, + }, + { + title: t("home.next_up"), + queryKey: ["home", "nextUp-all"], + queryFn: async ({ pageParam = 0 }) => + ( + await getTvShowsApi(api).getNextUp({ + userId: user?.Id, + fields: ["MediaSourceCount", "Genres"], + startIndex: pageParam, + limit: 10, + enableImageTypes: ["Primary", "Backdrop", "Thumb", "Logo"], + enableResumable: false, + }) + ).data.Items || [], + type: "InfiniteScrollingCollectionList", + orientation: "horizontal", + pageSize: 10, + }, + ]; + const ss: Section[] = [ - { - title: t("home.continue_watching"), - queryKey: ["home", "resumeItems"], - queryFn: async ({ pageParam = 0 }) => - ( - await getItemsApi(api).getResumeItems({ - userId: user.Id, - enableImageTypes: ["Primary", "Backdrop", "Thumb", "Logo"], - includeItemTypes: ["Movie", "Series", "Episode"], - fields: ["Genres"], - startIndex: pageParam, - limit: 10, - }) - ).data.Items || [], - type: "InfiniteScrollingCollectionList", - orientation: "horizontal", - pageSize: 10, - }, - { - title: t("home.next_up"), - queryKey: ["home", "nextUp-all"], - queryFn: async ({ pageParam = 0 }) => - ( - await getTvShowsApi(api).getNextUp({ - userId: user?.Id, - fields: ["MediaSourceCount", "Genres"], - startIndex: pageParam, - limit: 10, - enableImageTypes: ["Primary", "Backdrop", "Thumb", "Logo"], - enableResumable: false, - }) - ).data.Items || [], - type: "InfiniteScrollingCollectionList", - orientation: "horizontal", - pageSize: 10, - }, + ...firstSections, ...latestMediaViews, - { - title: t("home.suggested_movies"), - queryKey: ["home", "suggestedMovies", user?.Id], - queryFn: async ({ pageParam = 0 }) => - ( - await getSuggestionsApi(api).getSuggestions({ - userId: user?.Id, - startIndex: pageParam, - limit: 10, - mediaType: ["Video"], - type: ["Movie"], - }) - ).data.Items || [], - type: "InfiniteScrollingCollectionList", - orientation: "vertical", - pageSize: 10, - }, + // Only show Jellyfin suggested movies if StreamyStats recommendations are disabled + ...(!settings?.streamyStatsMovieRecommendations + ? [ + { + title: t("home.suggested_movies"), + queryKey: ["home", "suggestedMovies", user?.Id], + queryFn: async ({ pageParam = 0 }: { pageParam?: number }) => + ( + await getSuggestionsApi(api).getSuggestions({ + userId: user?.Id, + startIndex: pageParam, + limit: 10, + mediaType: ["Video"], + type: ["Movie"], + }) + ).data.Items || [], + type: "InfiniteScrollingCollectionList" as const, + orientation: "vertical" as const, + pageSize: 10, + }, + ] + : []), ]; return ss; - }, [api, user?.Id, collections, t, createCollectionConfig]); + }, [ + api, + user?.Id, + collections, + t, + createCollectionConfig, + settings?.streamyStatsMovieRecommendations, + settings.mergeNextUpAndContinueWatching, + ]); const customSections = useMemo(() => { if (!api || !user?.Id || !settings?.home?.sections) return []; @@ -477,28 +558,66 @@ export const HomeWithCarousel = () => { > {sections.map((section, index) => { + // Render Streamystats sections after Continue Watching and Next Up + // When merged, they appear after index 0; otherwise after index 1 + const streamystatsIndex = settings.mergeNextUpAndContinueWatching + ? 0 + : 1; + const hasStreamystatsContent = + settings.streamyStatsMovieRecommendations || + settings.streamyStatsSeriesRecommendations || + settings.streamyStatsPromotedWatchlists; + const streamystatsSections = + index === streamystatsIndex && hasStreamystatsContent ? ( + <> + {settings.streamyStatsMovieRecommendations && ( + + )} + {settings.streamyStatsSeriesRecommendations && ( + + )} + {settings.streamyStatsPromotedWatchlists && ( + + )} + + ) : null; + if (section.type === "InfiniteScrollingCollectionList") { return ( - + + + {streamystatsSections} + ); } if (section.type === "MediaListSection") { return ( - + + + {streamystatsSections} + ); } return null; diff --git a/components/home/InfiniteScrollingCollectionList.tsx b/components/home/InfiniteScrollingCollectionList.tsx index 73a1c21d..e7435589 100644 --- a/components/home/InfiniteScrollingCollectionList.tsx +++ b/components/home/InfiniteScrollingCollectionList.tsx @@ -4,7 +4,7 @@ import { type QueryKey, useInfiniteQuery, } from "@tanstack/react-query"; -import { useMemo } from "react"; +import { useEffect, useMemo, useRef } from "react"; import { useTranslation } from "react-i18next"; import { ActivityIndicator, @@ -12,6 +12,7 @@ import { View, type ViewProps, } from "react-native"; +import { SectionHeader } from "@/components/common/SectionHeader"; import { Text } from "@/components/common/Text"; import MoviePoster from "@/components/posters/MoviePoster"; import { Colors } from "../../constants/Colors"; @@ -28,6 +29,9 @@ interface Props extends ViewProps { queryFn: QueryFunction; hideIfEmpty?: boolean; pageSize?: number; + onPressSeeAll?: () => void; + enabled?: boolean; + onLoaded?: () => void; } export const InfiniteScrollingCollectionList: React.FC = ({ @@ -38,32 +42,67 @@ export const InfiniteScrollingCollectionList: React.FC = ({ queryKey, hideIfEmpty = false, pageSize = 10, + onPressSeeAll, + enabled = true, + onLoaded, ...props }) => { - const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = - useInfiniteQuery({ - queryKey: queryKey, - queryFn: ({ pageParam = 0, ...context }) => - queryFn({ ...context, queryKey, pageParam }), - getNextPageParam: (lastPage, allPages) => { - // If the last page has fewer items than pageSize, we've reached the end - if (lastPage.length < pageSize) { - return undefined; - } - // Otherwise, return the next start index - return allPages.length * pageSize; - }, - initialPageParam: 0, - staleTime: 60 * 1000, // 1 minute - refetchOnMount: false, - refetchOnWindowFocus: false, - refetchOnReconnect: true, - }); + const effectivePageSize = Math.max(1, pageSize); + const hasCalledOnLoaded = useRef(false); + const { + data, + isLoading, + isFetchingNextPage, + hasNextPage, + fetchNextPage, + isSuccess, + } = useInfiniteQuery({ + queryKey: queryKey, + queryFn: ({ pageParam = 0, ...context }) => + queryFn({ ...context, queryKey, pageParam }), + getNextPageParam: (lastPage, allPages) => { + // If the last page has fewer items than pageSize, we've reached the end + if (lastPage.length < effectivePageSize) { + return undefined; + } + // Otherwise, return the next start index based on how many items we already loaded. + // This avoids overlaps if the server/page size differs from our configured page size. + return allPages.reduce((acc, page) => acc + page.length, 0); + }, + initialPageParam: 0, + staleTime: 60 * 1000, // 1 minute + refetchOnMount: false, + refetchOnWindowFocus: false, + refetchOnReconnect: true, + enabled, + }); + + // Notify parent when data has loaded + useEffect(() => { + if (isSuccess && !hasCalledOnLoaded.current && onLoaded) { + hasCalledOnLoaded.current = true; + onLoaded(); + } + }, [isSuccess, onLoaded]); const { t } = useTranslation(); - // Flatten all pages into a single array - const allItems = data?.pages.flat() || []; + // Flatten all pages into a single array (and de-dupe by Id to avoid UI duplicates) + const allItems = useMemo(() => { + const items = data?.pages.flat() ?? []; + const seen = new Set(); + const deduped: BaseItemDto[] = []; + + for (const item of items) { + const id = item.Id; + if (!id) continue; + if (seen.has(id)) continue; + seen.add(id); + deduped.push(item); + } + + return deduped; + }, [data]); const snapOffsets = useMemo(() => { const itemWidth = orientation === "horizontal" ? 184 : 120; // w-44 (176px) + mr-2 (8px) or w-28 (112px) + mr-2 (8px) @@ -90,9 +129,12 @@ export const InfiniteScrollingCollectionList: React.FC = ({ return ( - - {title} - + {isLoading === false && allItems.length === 0 && ( {t("home.no_items")} diff --git a/components/home/StreamystatsPromotedWatchlists.tsx b/components/home/StreamystatsPromotedWatchlists.tsx new file mode 100644 index 00000000..474b9660 --- /dev/null +++ b/components/home/StreamystatsPromotedWatchlists.tsx @@ -0,0 +1,245 @@ +import type { + BaseItemDto, + PublicSystemInfo, +} from "@jellyfin/sdk/lib/generated-client/models"; +import { getItemsApi, getSystemApi } from "@jellyfin/sdk/lib/utils/api"; +import { useQuery } from "@tanstack/react-query"; +import { useAtomValue } from "jotai"; +import { useMemo } from "react"; +import { ScrollView, View, type ViewProps } from "react-native"; +import { SectionHeader } from "@/components/common/SectionHeader"; +import { Text } from "@/components/common/Text"; +import MoviePoster from "@/components/posters/MoviePoster"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; +import { useSettings } from "@/utils/atoms/settings"; +import { createStreamystatsApi } from "@/utils/streamystats/api"; +import type { StreamystatsWatchlist } from "@/utils/streamystats/types"; +import { TouchableItemRouter } from "../common/TouchableItemRouter"; +import { ItemCardText } from "../ItemCardText"; +import SeriesPoster from "../posters/SeriesPoster"; + +const ITEM_WIDTH = 120; // w-28 (112px) + mr-2 (8px) + +interface WatchlistSectionProps extends ViewProps { + watchlist: StreamystatsWatchlist; + jellyfinServerId: string; +} + +const WatchlistSection: React.FC = ({ + watchlist, + jellyfinServerId, + ...props +}) => { + const api = useAtomValue(apiAtom); + const user = useAtomValue(userAtom); + const { settings } = useSettings(); + + const { data: items, isLoading } = useQuery({ + queryKey: [ + "streamystats", + "watchlist", + watchlist.id, + jellyfinServerId, + settings?.streamyStatsServerUrl, + ], + queryFn: async (): Promise => { + if (!settings?.streamyStatsServerUrl || !api?.accessToken || !user?.Id) { + return []; + } + + const streamystatsApi = createStreamystatsApi({ + serverUrl: settings.streamyStatsServerUrl, + jellyfinToken: api.accessToken, + }); + + const watchlistDetail = await streamystatsApi.getWatchlistItemIds({ + watchlistId: watchlist.id, + jellyfinServerId, + }); + + const itemIds = watchlistDetail.data?.items; + if (!itemIds?.length) { + return []; + } + + const response = await getItemsApi(api).getItems({ + userId: user.Id, + ids: itemIds, + fields: ["PrimaryImageAspectRatio", "Genres"], + enableImageTypes: ["Primary", "Backdrop", "Thumb"], + }); + + return response.data.Items || []; + }, + enabled: + Boolean(settings?.streamyStatsServerUrl) && + Boolean(api?.accessToken) && + Boolean(user?.Id), + staleTime: 5 * 60 * 1000, + refetchOnMount: false, + refetchOnWindowFocus: false, + }); + + const snapOffsets = useMemo(() => { + return items?.map((_, index) => index * ITEM_WIDTH) ?? []; + }, [items]); + + if (!isLoading && (!items || items.length === 0)) return null; + + return ( + + + {isLoading ? ( + + {[1, 2, 3].map((i) => ( + + + + + Loading... + + + + ))} + + ) : ( + + + {items?.map((item) => ( + + {item.Type === "Movie" && } + {item.Type === "Series" && } + + + ))} + + + )} + + ); +}; + +interface StreamystatsPromotedWatchlistsProps extends ViewProps { + enabled?: boolean; +} + +export const StreamystatsPromotedWatchlists: React.FC< + StreamystatsPromotedWatchlistsProps +> = ({ enabled = true, ...props }) => { + const api = useAtomValue(apiAtom); + const user = useAtomValue(userAtom); + const { settings } = useSettings(); + + const streamyStatsEnabled = useMemo(() => { + return Boolean(settings?.streamyStatsServerUrl); + }, [settings?.streamyStatsServerUrl]); + + // Fetch server info to get the Jellyfin server ID + const { data: serverInfo } = useQuery({ + queryKey: ["jellyfin", "serverInfo"], + queryFn: async (): Promise => { + if (!api) return null; + const response = await getSystemApi(api).getPublicSystemInfo(); + return response.data; + }, + enabled: enabled && Boolean(api) && streamyStatsEnabled, + staleTime: 60 * 60 * 1000, + }); + + const jellyfinServerId = serverInfo?.Id; + + const { + data: watchlists, + isLoading, + isError, + } = useQuery({ + queryKey: [ + "streamystats", + "promotedWatchlists", + jellyfinServerId, + settings?.streamyStatsServerUrl, + ], + queryFn: async (): Promise => { + if ( + !settings?.streamyStatsServerUrl || + !api?.accessToken || + !jellyfinServerId + ) { + return []; + } + + const streamystatsApi = createStreamystatsApi({ + serverUrl: settings.streamyStatsServerUrl, + jellyfinToken: api.accessToken, + }); + + const response = await streamystatsApi.getPromotedWatchlists({ + jellyfinServerId, + includePreview: false, + }); + + return response.data || []; + }, + enabled: + enabled && + streamyStatsEnabled && + Boolean(api?.accessToken) && + Boolean(jellyfinServerId) && + Boolean(user?.Id), + staleTime: 5 * 60 * 1000, + refetchOnMount: false, + refetchOnWindowFocus: false, + }); + + if (!streamyStatsEnabled) return null; + if (isError) return null; + if (!isLoading && (!watchlists || watchlists.length === 0)) return null; + + if (isLoading) { + return ( + + + + {[1, 2, 3].map((i) => ( + + + + + Loading... + + + + ))} + + + ); + } + + return ( + <> + {watchlists?.map((watchlist) => ( + + ))} + + ); +}; diff --git a/components/home/StreamystatsRecommendations.tsx b/components/home/StreamystatsRecommendations.tsx new file mode 100644 index 00000000..fb26708c --- /dev/null +++ b/components/home/StreamystatsRecommendations.tsx @@ -0,0 +1,197 @@ +import type { + BaseItemDto, + PublicSystemInfo, +} from "@jellyfin/sdk/lib/generated-client/models"; +import { getItemsApi, getSystemApi } from "@jellyfin/sdk/lib/utils/api"; +import { useQuery } from "@tanstack/react-query"; +import { useAtomValue } from "jotai"; +import { useMemo } from "react"; +import { ScrollView, View, type ViewProps } from "react-native"; + +import { SectionHeader } from "@/components/common/SectionHeader"; +import { Text } from "@/components/common/Text"; +import MoviePoster from "@/components/posters/MoviePoster"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; +import { useSettings } from "@/utils/atoms/settings"; +import { createStreamystatsApi } from "@/utils/streamystats/api"; +import type { StreamystatsRecommendationsIdsResponse } from "@/utils/streamystats/types"; +import { TouchableItemRouter } from "../common/TouchableItemRouter"; +import { ItemCardText } from "../ItemCardText"; +import SeriesPoster from "../posters/SeriesPoster"; + +const ITEM_WIDTH = 120; // w-28 (112px) + mr-2 (8px) + +interface Props extends ViewProps { + title: string; + type: "Movie" | "Series"; + limit?: number; + enabled?: boolean; +} + +export const StreamystatsRecommendations: React.FC = ({ + title, + type, + limit = 20, + enabled = true, + ...props +}) => { + const api = useAtomValue(apiAtom); + const user = useAtomValue(userAtom); + const { settings } = useSettings(); + + const streamyStatsEnabled = useMemo(() => { + return Boolean(settings?.streamyStatsServerUrl); + }, [settings?.streamyStatsServerUrl]); + + // Fetch server info to get the Jellyfin server ID + const { data: serverInfo } = useQuery({ + queryKey: ["jellyfin", "serverInfo"], + queryFn: async (): Promise => { + if (!api) return null; + const response = await getSystemApi(api).getPublicSystemInfo(); + return response.data; + }, + enabled: enabled && Boolean(api) && streamyStatsEnabled, + staleTime: 60 * 60 * 1000, // 1 hour - server info rarely changes + }); + + const jellyfinServerId = serverInfo?.Id; + + const { + data: recommendationIds, + isLoading: isLoadingRecommendations, + isError: isRecommendationsError, + } = useQuery({ + queryKey: [ + "streamystats", + "recommendations", + type, + jellyfinServerId, + settings?.streamyStatsServerUrl, + ], + queryFn: async (): Promise => { + if ( + !settings?.streamyStatsServerUrl || + !api?.accessToken || + !jellyfinServerId + ) { + return []; + } + + const streamyStatsApi = createStreamystatsApi({ + serverUrl: settings.streamyStatsServerUrl, + jellyfinToken: api.accessToken, + }); + + const response = await streamyStatsApi.getRecommendationIds( + jellyfinServerId, + type, + limit, + ); + + const data = response as StreamystatsRecommendationsIdsResponse; + + if (type === "Movie") { + return data.data.movies || []; + } + return data.data.series || []; + }, + enabled: + enabled && + streamyStatsEnabled && + Boolean(api?.accessToken) && + Boolean(jellyfinServerId) && + Boolean(user?.Id), + staleTime: 5 * 60 * 1000, // 5 minutes + refetchOnMount: false, + refetchOnWindowFocus: false, + }); + + const { + data: items, + isLoading: isLoadingItems, + isError: isItemsError, + } = useQuery({ + queryKey: [ + "streamystats", + "recommendations", + "items", + type, + recommendationIds, + ], + queryFn: async (): Promise => { + if (!api || !user?.Id || !recommendationIds?.length) { + return []; + } + + const response = await getItemsApi(api).getItems({ + userId: user.Id, + ids: recommendationIds, + fields: ["PrimaryImageAspectRatio", "Genres"], + enableImageTypes: ["Primary", "Backdrop", "Thumb"], + }); + + return response.data.Items || []; + }, + enabled: + Boolean(recommendationIds?.length) && Boolean(api) && Boolean(user?.Id), + staleTime: 5 * 60 * 1000, + refetchOnMount: false, + refetchOnWindowFocus: false, + }); + + const isLoading = isLoadingRecommendations || isLoadingItems; + const isError = isRecommendationsError || isItemsError; + + const snapOffsets = useMemo(() => { + return items?.map((_, index) => index * ITEM_WIDTH) ?? []; + }, [items]); + + if (!streamyStatsEnabled) return null; + if (isError) return null; + if (!isLoading && (!items || items.length === 0)) return null; + + return ( + + + {isLoading ? ( + + {[1, 2, 3].map((i) => ( + + + + + Loading title... + + + + ))} + + ) : ( + + + {items?.map((item) => ( + + {item.Type === "Movie" && } + {item.Type === "Series" && } + + + ))} + + + )} + + ); +}; diff --git a/components/item/ItemPeopleSections.tsx b/components/item/ItemPeopleSections.tsx new file mode 100644 index 00000000..f6c2dab3 --- /dev/null +++ b/components/item/ItemPeopleSections.tsx @@ -0,0 +1,80 @@ +import type { + BaseItemDto, + BaseItemPerson, +} from "@jellyfin/sdk/lib/generated-client/models"; +import type React from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; +import { InteractionManager, View, type ViewProps } from "react-native"; +import { MoreMoviesWithActor } from "@/components/MoreMoviesWithActor"; +import { CastAndCrew } from "@/components/series/CastAndCrew"; +import { useItemPeopleQuery } from "@/hooks/useItemPeopleQuery"; + +interface Props extends ViewProps { + item: BaseItemDto; + isOffline: boolean; +} + +export const ItemPeopleSections: React.FC = ({ + item, + isOffline, + ...props +}) => { + const [enabled, setEnabled] = useState(false); + + useEffect(() => { + if (isOffline) return; + const task = InteractionManager.runAfterInteractions(() => + setEnabled(true), + ); + return () => task.cancel(); + }, [isOffline]); + + const { data, isLoading } = useItemPeopleQuery( + item.Id, + enabled && !isOffline, + ); + + const people = useMemo(() => (Array.isArray(data) ? data : []), [data]); + + const itemWithPeople = useMemo(() => { + return { ...item, People: people } as BaseItemDto; + }, [item, people]); + + const topPeople = useMemo(() => people.slice(0, 3), [people]); + + const renderActorSection = useCallback( + (person: BaseItemPerson, idx: number, total: number) => { + if (!person.Id) return null; + + const spacingClassName = idx === total - 1 ? undefined : "mb-2"; + + return ( + + ); + }, + [item], + ); + + if (isOffline || !enabled) return null; + + const shouldSpaceCastAndCrew = topPeople.length > 0; + + return ( + + + {topPeople.map((person, idx) => + renderActorSection(person, idx, topPeople.length), + )} + + ); +}; diff --git a/components/list/ListItem.tsx b/components/list/ListItem.tsx index 7ce33986..fed62315 100644 --- a/components/list/ListItem.tsx +++ b/components/list/ListItem.tsx @@ -6,6 +6,7 @@ import { Text } from "../common/Text"; interface Props extends ViewProps { title?: string | null | undefined; subtitle?: string | null | undefined; + subtitleColor?: "default" | "red"; value?: string | null | undefined; children?: ReactNode; iconAfter?: ReactNode; @@ -14,6 +15,7 @@ interface Props extends ViewProps { textColor?: "default" | "blue" | "red"; onPress?: () => void; disabled?: boolean; + disabledByAdmin?: boolean; } export const ListItem: React.FC> = ({ @@ -27,21 +29,23 @@ export const ListItem: React.FC> = ({ textColor = "default", onPress, disabled = false, + disabledByAdmin = false, ...viewProps }) => { + const effectiveSubtitle = disabledByAdmin ? "Disabled by admin" : subtitle; + const isDisabled = disabled || disabledByAdmin; if (onPress) return ( > = ({ ); return ( > = ({ const ListItemContent = ({ title, subtitle, + subtitleColor, textColor, icon, value, @@ -107,7 +111,7 @@ const ListItemContent = ({ {subtitle && ( {subtitle} diff --git a/components/music/MiniPlayerBar.tsx b/components/music/MiniPlayerBar.tsx new file mode 100644 index 00000000..3768ef7b --- /dev/null +++ b/components/music/MiniPlayerBar.tsx @@ -0,0 +1,236 @@ +import { Ionicons } from "@expo/vector-icons"; +import { BlurView } from "expo-blur"; +import { Image } from "expo-image"; +import { useRouter } from "expo-router"; +import { useAtom } from "jotai"; +import React, { useCallback, useMemo } from "react"; +import { Platform, StyleSheet, TouchableOpacity, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { Text } from "@/components/common/Text"; +import { apiAtom } from "@/providers/JellyfinProvider"; +import { useMusicPlayer } from "@/providers/MusicPlayerProvider"; + +const HORIZONTAL_MARGIN = Platform.OS === "android" ? 8 : 16; +const BOTTOM_TAB_HEIGHT = Platform.OS === "android" ? 56 : 52; +const BAR_HEIGHT = Platform.OS === "android" ? 58 : 50; + +export const MiniPlayerBar: React.FC = () => { + const [api] = useAtom(apiAtom); + const insets = useSafeAreaInsets(); + const router = useRouter(); + const { currentTrack, isPlaying, progress, duration, togglePlayPause, next } = + useMusicPlayer(); + + const imageUrl = useMemo(() => { + if (!api || !currentTrack) return null; + const albumId = currentTrack.AlbumId || currentTrack.ParentId; + if (albumId) { + return `${api.basePath}/Items/${albumId}/Images/Primary?maxHeight=100&maxWidth=100`; + } + return `${api.basePath}/Items/${currentTrack.Id}/Images/Primary?maxHeight=100&maxWidth=100`; + }, [api, currentTrack]); + + const _progressPercentage = useMemo(() => { + if (!duration || duration === 0) return 0; + return (progress / duration) * 100; + }, [progress, duration]); + + const handlePress = useCallback(() => { + router.push("/(auth)/now-playing"); + }, [router]); + + const handlePlayPause = useCallback( + (e: any) => { + e.stopPropagation(); + togglePlayPause(); + }, + [togglePlayPause], + ); + + const handleNext = useCallback( + (e: any) => { + e.stopPropagation(); + next(); + }, + [next], + ); + + if (!currentTrack) return null; + + const content = ( + <> + {/* Album art */} + + {imageUrl ? ( + + ) : ( + + + + )} + + + {/* Track info */} + + + {currentTrack.Name} + + + {currentTrack.Artists?.join(", ") || currentTrack.AlbumArtist} + + + + {/* Controls */} + + + + + + + + + + {/* Progress bar at bottom */} + {/* + + */} + + ); + + return ( + + + {Platform.OS === "ios" ? ( + + {content} + + ) : ( + {content} + )} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + position: "absolute", + left: HORIZONTAL_MARGIN, + right: HORIZONTAL_MARGIN, + shadowColor: "#000", + shadowOffset: { width: 0, height: 4 }, + shadowOpacity: 0.3, + shadowRadius: 8, + elevation: 8, + }, + touchable: { + borderRadius: 50, + overflow: "hidden", + }, + blurContainer: { + flexDirection: "row", + alignItems: "center", + paddingRight: 10, + paddingLeft: 20, + paddingVertical: 0, + height: BAR_HEIGHT, + backgroundColor: "rgba(40, 40, 40, 0.5)", + }, + androidContainer: { + flexDirection: "row", + alignItems: "center", + paddingHorizontal: 10, + paddingVertical: 8, + height: BAR_HEIGHT, + backgroundColor: "rgba(28, 28, 30, 0.97)", + borderRadius: 14, + borderWidth: 0.5, + borderColor: "rgba(255, 255, 255, 0.1)", + }, + albumArt: { + width: 32, + height: 32, + borderRadius: 8, + overflow: "hidden", + backgroundColor: "#333", + }, + albumImage: { + width: "100%", + height: "100%", + }, + albumPlaceholder: { + flex: 1, + alignItems: "center", + justifyContent: "center", + backgroundColor: "#2a2a2a", + }, + trackInfo: { + flex: 1, + marginLeft: 12, + marginRight: 8, + justifyContent: "center", + }, + trackTitle: { + color: "white", + fontSize: 14, + fontWeight: "600", + }, + artistName: { + color: "rgba(255, 255, 255, 0.6)", + fontSize: 12, + }, + controls: { + flexDirection: "row", + alignItems: "center", + }, + controlButton: { + padding: 8, + }, + progressContainer: { + position: "absolute", + bottom: 0, + left: 10, + right: 10, + height: 3, + backgroundColor: "rgba(255, 255, 255, 0.15)", + borderRadius: 1.5, + }, + progressFill: { + height: "100%", + backgroundColor: "white", + borderRadius: 1.5, + }, +}); diff --git a/components/music/MusicAlbumCard.tsx b/components/music/MusicAlbumCard.tsx new file mode 100644 index 00000000..0efb0082 --- /dev/null +++ b/components/music/MusicAlbumCard.tsx @@ -0,0 +1,68 @@ +import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; +import { Image } from "expo-image"; +import { useRouter } from "expo-router"; +import { useAtom } from "jotai"; +import React, { useCallback, useMemo } from "react"; +import { TouchableOpacity, View } from "react-native"; +import { Text } from "@/components/common/Text"; +import { apiAtom } from "@/providers/JellyfinProvider"; +import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl"; + +interface Props { + album: BaseItemDto; + width?: number; +} + +export const MusicAlbumCard: React.FC = ({ album, width = 150 }) => { + const [api] = useAtom(apiAtom); + const router = useRouter(); + + const imageUrl = useMemo( + () => getPrimaryImageUrl({ api, item: album }), + [api, album], + ); + + const handlePress = useCallback(() => { + router.push({ + pathname: "/music/album/[albumId]", + params: { albumId: album.Id! }, + }); + }, [router, album.Id]); + + return ( + + + {imageUrl ? ( + + ) : ( + + 🎵 + + )} + + + {album.Name} + + + {album.AlbumArtist || album.Artists?.join(", ")} + + + ); +}; diff --git a/components/music/MusicArtistCard.tsx b/components/music/MusicArtistCard.tsx new file mode 100644 index 00000000..22637fb5 --- /dev/null +++ b/components/music/MusicArtistCard.tsx @@ -0,0 +1,68 @@ +import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; +import { Image } from "expo-image"; +import { useRouter } from "expo-router"; +import { useAtom } from "jotai"; +import React, { useCallback, useMemo } from "react"; +import { TouchableOpacity, View } from "react-native"; +import { Text } from "@/components/common/Text"; +import { apiAtom } from "@/providers/JellyfinProvider"; +import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl"; + +interface Props { + artist: BaseItemDto; + size?: number; +} + +export const MusicArtistCard: React.FC = ({ artist, size = 100 }) => { + const [api] = useAtom(apiAtom); + const router = useRouter(); + + const imageUrl = useMemo( + () => getPrimaryImageUrl({ api, item: artist }), + [api, artist], + ); + + const handlePress = useCallback(() => { + router.push({ + pathname: "/music/artist/[artistId]", + params: { artistId: artist.Id! }, + }); + }, [router, artist.Id]); + + return ( + + + {imageUrl ? ( + + ) : ( + + 👤 + + )} + + + {artist.Name} + + + ); +}; diff --git a/components/music/MusicPlaybackEngine.tsx b/components/music/MusicPlaybackEngine.tsx new file mode 100644 index 00000000..09f47372 --- /dev/null +++ b/components/music/MusicPlaybackEngine.tsx @@ -0,0 +1,83 @@ +import { useEffect, useRef } from "react"; +import TrackPlayer, { + Event, + type PlaybackActiveTrackChangedEvent, + State, + useActiveTrack, + usePlaybackState, + useProgress, +} from "react-native-track-player"; +import { useMusicPlayer } from "@/providers/MusicPlayerProvider"; + +export const MusicPlaybackEngine: React.FC = () => { + const { position, duration } = useProgress(1000); + const playbackState = usePlaybackState(); + const activeTrack = useActiveTrack(); + const { + setProgress, + setDuration, + setIsPlaying, + reportProgress, + onTrackEnd, + syncFromTrackPlayer, + } = useMusicPlayer(); + + const lastReportedProgressRef = useRef(0); + + // Sync progress from TrackPlayer to our state + useEffect(() => { + if (position > 0) { + setProgress(position); + } + }, [position, setProgress]); + + // Sync duration from TrackPlayer to our state + useEffect(() => { + if (duration > 0) { + setDuration(duration); + } + }, [duration, setDuration]); + + // Sync playback state from TrackPlayer to our state + useEffect(() => { + const isPlaying = playbackState.state === State.Playing; + setIsPlaying(isPlaying); + }, [playbackState.state, setIsPlaying]); + + // Sync active track changes + useEffect(() => { + if (activeTrack) { + syncFromTrackPlayer(); + } + }, [activeTrack?.id, syncFromTrackPlayer]); + + // Report progress every ~10 seconds + useEffect(() => { + if ( + Math.floor(position) - Math.floor(lastReportedProgressRef.current) >= + 10 + ) { + lastReportedProgressRef.current = position; + reportProgress(); + } + }, [position, reportProgress]); + + // Listen for track end + useEffect(() => { + const subscription = + TrackPlayer.addEventListener( + Event.PlaybackActiveTrackChanged, + async (event) => { + // If there's no next track and the previous track ended, call onTrackEnd + if (event.lastTrack && !event.track) { + onTrackEnd(); + } + }, + ); + + return () => subscription.remove(); + }, [onTrackEnd]); + + // No visual component needed - TrackPlayer is headless + return null; +}; diff --git a/components/music/MusicPlaylistCard.tsx b/components/music/MusicPlaylistCard.tsx new file mode 100644 index 00000000..b158be49 --- /dev/null +++ b/components/music/MusicPlaylistCard.tsx @@ -0,0 +1,71 @@ +import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; +import { Image } from "expo-image"; +import { useRouter } from "expo-router"; +import { useAtom } from "jotai"; +import React, { useCallback, useMemo } from "react"; +import { TouchableOpacity, View } from "react-native"; +import { Text } from "@/components/common/Text"; +import { apiAtom } from "@/providers/JellyfinProvider"; +import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl"; + +interface Props { + playlist: BaseItemDto; + width?: number; +} + +export const MusicPlaylistCard: React.FC = ({ + playlist, + width = 150, +}) => { + const [api] = useAtom(apiAtom); + const router = useRouter(); + + const imageUrl = useMemo( + () => getPrimaryImageUrl({ api, item: playlist }), + [api, playlist], + ); + + const handlePress = useCallback(() => { + router.push({ + pathname: "/music/playlist/[playlistId]", + params: { playlistId: playlist.Id! }, + }); + }, [router, playlist.Id]); + + return ( + + + {imageUrl ? ( + + ) : ( + + 🎶 + + )} + + + {playlist.Name} + + + {playlist.ChildCount} tracks + + + ); +}; diff --git a/components/music/MusicTrackItem.tsx b/components/music/MusicTrackItem.tsx new file mode 100644 index 00000000..bb0e2d05 --- /dev/null +++ b/components/music/MusicTrackItem.tsx @@ -0,0 +1,130 @@ +import { useActionSheet } from "@expo/react-native-action-sheet"; +import { Ionicons } from "@expo/vector-icons"; +import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; +import { Image } from "expo-image"; +import { useAtom } from "jotai"; +import React, { useCallback, useMemo } from "react"; +import { TouchableOpacity, View } from "react-native"; +import { Text } from "@/components/common/Text"; +import { apiAtom } from "@/providers/JellyfinProvider"; +import { useMusicPlayer } from "@/providers/MusicPlayerProvider"; +import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl"; +import { formatDuration } from "@/utils/time"; + +interface Props { + track: BaseItemDto; + index?: number; + queue?: BaseItemDto[]; + showArtwork?: boolean; +} + +export const MusicTrackItem: React.FC = ({ + track, + index, + queue, + showArtwork = true, +}) => { + const [api] = useAtom(apiAtom); + const { showActionSheetWithOptions } = useActionSheet(); + const { playTrack, playNext, addToQueue, currentTrack, isPlaying } = + useMusicPlayer(); + + const imageUrl = useMemo(() => { + const albumId = track.AlbumId || track.ParentId; + if (albumId) { + return `${api?.basePath}/Items/${albumId}/Images/Primary?maxHeight=100&maxWidth=100`; + } + return getPrimaryImageUrl({ api, item: track }); + }, [api, track]); + + const isCurrentTrack = currentTrack?.Id === track.Id; + + const duration = useMemo(() => { + if (!track.RunTimeTicks) return ""; + return formatDuration(track.RunTimeTicks); + }, [track.RunTimeTicks]); + + const handlePress = useCallback(() => { + playTrack(track, queue); + }, [playTrack, track, queue]); + + const handleLongPress = useCallback(() => { + const options = ["Play Next", "Add to Queue", "Cancel"]; + const cancelButtonIndex = 2; + + showActionSheetWithOptions( + { + options, + cancelButtonIndex, + title: track.Name ?? undefined, + message: (track.Artists?.join(", ") || track.AlbumArtist) ?? undefined, + }, + (selectedIndex) => { + if (selectedIndex === 0) { + playNext(track); + } else if (selectedIndex === 1) { + addToQueue(track); + } + }, + ); + }, [showActionSheetWithOptions, track, playNext, addToQueue]); + + return ( + + {index !== undefined && ( + + {isCurrentTrack && isPlaying ? ( + + ) : ( + {index} + )} + + )} + + {showArtwork && ( + + {imageUrl ? ( + + ) : ( + + + + )} + + )} + + + + {track.Name} + + + {track.Artists?.join(", ") || track.AlbumArtist} + + + + {duration} + + ); +}; diff --git a/components/music/index.ts b/components/music/index.ts new file mode 100644 index 00000000..3d0767ad --- /dev/null +++ b/components/music/index.ts @@ -0,0 +1,6 @@ +export * from "./MiniPlayerBar"; +export * from "./MusicAlbumCard"; +export * from "./MusicArtistCard"; +export * from "./MusicPlaybackEngine"; +export * from "./MusicPlaylistCard"; +export * from "./MusicTrackItem"; diff --git a/components/series/CastAndCrew.tsx b/components/series/CastAndCrew.tsx index 037206e9..7dabcc76 100644 --- a/components/series/CastAndCrew.tsx +++ b/components/series/CastAndCrew.tsx @@ -8,6 +8,7 @@ import type React from "react"; import { useMemo } from "react"; import { useTranslation } from "react-i18next"; import { TouchableOpacity, View, type ViewProps } from "react-native"; +import { POSTER_CAROUSEL_HEIGHT } from "@/constants/Values"; import { apiAtom } from "@/providers/JellyfinProvider"; import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl"; import { HorizontalScroll } from "../common/HorizontalScroll"; @@ -50,7 +51,7 @@ export const CastAndCrew: React.FC = ({ item, loading, ...props }) => { i.Id?.toString() || ""} - height={247} + height={POSTER_CAROUSEL_HEIGHT} data={destinctPeople} renderItem={(i) => ( = ({ item, loading, ...props }) => { className='flex flex-col w-28' > - {i.Name} - {i.Role} + + {i.Name} + + + {i.Role} + )} /> diff --git a/components/series/CurrentSeries.tsx b/components/series/CurrentSeries.tsx index c3d0e0d9..e4d38c82 100644 --- a/components/series/CurrentSeries.tsx +++ b/components/series/CurrentSeries.tsx @@ -4,6 +4,7 @@ import { useAtom } from "jotai"; import type React from "react"; import { useTranslation } from "react-i18next"; import { TouchableOpacity, View, type ViewProps } from "react-native"; +import { POSTER_CAROUSEL_HEIGHT } from "@/constants/Values"; import { apiAtom } from "@/providers/JellyfinProvider"; import { getPrimaryImageUrlById } from "@/utils/jellyfin/image/getPrimaryImageUrlById"; import { HorizontalScroll } from "../common/HorizontalScroll"; @@ -25,7 +26,7 @@ export const CurrentSeries: React.FC = ({ item, ...props }) => { ( = ({ item, ...props }) => { id={item?.Id} url={getPrimaryImageUrlById({ api, id: item?.ParentId })} /> - {item?.SeriesName} + {item?.SeriesName} )} /> diff --git a/components/settings/AppearanceSettings.tsx b/components/settings/AppearanceSettings.tsx index 3953c73f..f8ff8470 100644 --- a/components/settings/AppearanceSettings.tsx +++ b/components/settings/AppearanceSettings.tsx @@ -50,6 +50,16 @@ export const AppearanceSettings: React.FC = () => { } /> + + + updateSettings({ mergeNextUpAndContinueWatching: value }) + } + /> + router.push("/settings/appearance/hide-libraries/page") diff --git a/components/settings/DisabledSetting.tsx b/components/settings/DisabledSetting.tsx index 04e24f86..2775331b 100644 --- a/components/settings/DisabledSetting.tsx +++ b/components/settings/DisabledSetting.tsx @@ -11,12 +11,12 @@ const DisabledSetting: React.FC< }} > + {children} {disabled && showText && ( - - {text ?? "Currently disabled by admin."} + + {text ?? "Disabled by admin"} )} - {children} ); diff --git a/components/settings/KSPlayerSettings.tsx b/components/settings/KSPlayerSettings.tsx new file mode 100644 index 00000000..1f99b5b1 --- /dev/null +++ b/components/settings/KSPlayerSettings.tsx @@ -0,0 +1,40 @@ +import type React from "react"; +import { useCallback } from "react"; +import { useTranslation } from "react-i18next"; +import { Platform, Switch } from "react-native"; +import { setHardwareDecode } from "@/modules/sf-player"; +import { useSettings } from "@/utils/atoms/settings"; +import { ListGroup } from "../list/ListGroup"; +import { ListItem } from "../list/ListItem"; + +export const KSPlayerSettings: React.FC = () => { + const { settings, updateSettings } = useSettings(); + const { t } = useTranslation(); + + const handleHardwareDecodeChange = useCallback( + (value: boolean) => { + updateSettings({ ksHardwareDecode: value }); + setHardwareDecode(value); + }, + [updateSettings], + ); + + if (Platform.OS !== "ios" || !settings) return null; + + return ( + + + + + + ); +}; diff --git a/components/settings/KefinTweaks.tsx b/components/settings/KefinTweaks.tsx new file mode 100644 index 00000000..228b4692 --- /dev/null +++ b/components/settings/KefinTweaks.tsx @@ -0,0 +1,33 @@ +import { useTranslation } from "react-i18next"; +import { Switch, Text, View } from "react-native"; +import { useSettings } from "@/utils/atoms/settings"; + +export const KefinTweaksSettings = () => { + const { settings, updateSettings } = useSettings(); + const { t } = useTranslation(); + + const isEnabled = settings?.useKefinTweaks ?? false; + + return ( + + + + {t("home.settings.plugins.kefinTweaks.watchlist_enabler")} + + + + + {isEnabled ? t("Watchlist On") : t("Watchlist Off")} + + + updateSettings({ useKefinTweaks: value })} + trackColor={{ false: "#555", true: "purple" }} + thumbColor={isEnabled ? "#fff" : "#ccc"} + /> + + + + ); +}; diff --git a/components/settings/PlaybackControlsSettings.tsx b/components/settings/PlaybackControlsSettings.tsx index c64db5ed..26c16da0 100644 --- a/components/settings/PlaybackControlsSettings.tsx +++ b/components/settings/PlaybackControlsSettings.tsx @@ -12,6 +12,7 @@ import { ScreenOrientationEnum, useSettings } from "@/utils/atoms/settings"; import { Text } from "../common/Text"; import { ListGroup } from "../list/ListGroup"; import { ListItem } from "../list/ListItem"; +import { VideoPlayerSettings } from "./VideoPlayerSettings"; export const PlaybackControlsSettings: React.FC = () => { const { settings, updateSettings, pluginSettings } = useSettings(); @@ -190,6 +191,8 @@ export const PlaybackControlsSettings: React.FC = () => { /> + + ); }; diff --git a/components/settings/PluginSettings.tsx b/components/settings/PluginSettings.tsx index 317136f1..4a46416c 100644 --- a/components/settings/PluginSettings.tsx +++ b/components/settings/PluginSettings.tsx @@ -28,6 +28,16 @@ export const PluginSettings = () => { title='Marlin Search' showArrow /> + router.push("/settings/plugins/streamystats/page")} + title='Streamystats' + showArrow + /> + router.push("/settings/plugins/kefinTweaks/page")} + title='KefinTweaks' + showArrow + /> ); }; diff --git a/components/settings/SubtitleToggles.tsx b/components/settings/SubtitleToggles.tsx index 5a6bfee3..526cb862 100644 --- a/components/settings/SubtitleToggles.tsx +++ b/components/settings/SubtitleToggles.tsx @@ -5,12 +5,6 @@ import { useTranslation } from "react-i18next"; import { Platform, View, type ViewProps } from "react-native"; import { Switch } from "react-native-gesture-handler"; import { Stepper } from "@/components/inputs/Stepper"; -import { - OUTLINE_THICKNESS, - type OutlineThickness, - VLC_COLORS, - type VLCColor, -} from "@/constants/SubtitleConstants"; import { useSettings } from "@/utils/atoms/settings"; import { Text } from "../common/Text"; import { ListGroup } from "../list/ListGroup"; @@ -92,84 +86,6 @@ export const SubtitleToggles: React.FC = ({ ...props }) => { ]; }, [settings?.subtitleMode, t, updateSettings]); - const textColorOptionGroups = useMemo(() => { - const colors = Object.keys(VLC_COLORS) as VLCColor[]; - const options = colors.map((color) => ({ - type: "radio" as const, - label: t(`home.settings.subtitles.colors.${color}`), - value: color, - selected: (settings?.vlcTextColor || "White") === color, - onPress: () => updateSettings({ vlcTextColor: color }), - })); - - return [{ options }]; - }, [settings?.vlcTextColor, t, updateSettings]); - - const backgroundColorOptionGroups = useMemo(() => { - const colors = Object.keys(VLC_COLORS) as VLCColor[]; - const options = colors.map((color) => ({ - type: "radio" as const, - label: t(`home.settings.subtitles.colors.${color}`), - value: color, - selected: (settings?.vlcBackgroundColor || "Black") === color, - onPress: () => updateSettings({ vlcBackgroundColor: color }), - })); - - return [{ options }]; - }, [settings?.vlcBackgroundColor, t, updateSettings]); - - const outlineColorOptionGroups = useMemo(() => { - const colors = Object.keys(VLC_COLORS) as VLCColor[]; - const options = colors.map((color) => ({ - type: "radio" as const, - label: t(`home.settings.subtitles.colors.${color}`), - value: color, - selected: (settings?.vlcOutlineColor || "Black") === color, - onPress: () => updateSettings({ vlcOutlineColor: color }), - })); - - return [{ options }]; - }, [settings?.vlcOutlineColor, t, updateSettings]); - - const outlineThicknessOptionGroups = useMemo(() => { - const thicknesses = Object.keys(OUTLINE_THICKNESS) as OutlineThickness[]; - const options = thicknesses.map((thickness) => ({ - type: "radio" as const, - label: t(`home.settings.subtitles.thickness.${thickness}`), - value: thickness, - selected: (settings?.vlcOutlineThickness || "Normal") === thickness, - onPress: () => updateSettings({ vlcOutlineThickness: thickness }), - })); - - return [{ options }]; - }, [settings?.vlcOutlineThickness, t, updateSettings]); - - const backgroundOpacityOptionGroups = useMemo(() => { - const opacities = [0, 32, 64, 96, 128, 160, 192, 224, 255]; - const options = opacities.map((opacity) => ({ - type: "radio" as const, - label: `${Math.round((opacity / 255) * 100)}%`, - value: opacity, - selected: (settings?.vlcBackgroundOpacity ?? 128) === opacity, - onPress: () => updateSettings({ vlcBackgroundOpacity: opacity }), - })); - - return [{ options }]; - }, [settings?.vlcBackgroundOpacity, updateSettings]); - - const outlineOpacityOptionGroups = useMemo(() => { - const opacities = [0, 32, 64, 96, 128, 160, 192, 224, 255]; - const options = opacities.map((opacity) => ({ - type: "radio" as const, - label: `${Math.round((opacity / 255) * 100)}%`, - value: opacity, - selected: (settings?.vlcOutlineOpacity ?? 255) === opacity, - onPress: () => updateSettings({ vlcOutlineOpacity: opacity }), - })); - - return [{ options }]; - }, [settings?.vlcOutlineOpacity, updateSettings]); - if (isTv) return null; if (!settings) return null; @@ -244,130 +160,14 @@ export const SubtitleToggles: React.FC = ({ ...props }) => { disabled={pluginSettings?.subtitleSize?.locked} > updateSettings({ subtitleSize })} - /> - - - - - {t( - `home.settings.subtitles.colors.${settings?.vlcTextColor || "White"}`, - )} - - - + step={0.1} + min={0.3} + max={1.5} + onUpdate={(value) => + updateSettings({ subtitleSize: Math.round(value * 100) }) } - title={t("home.settings.subtitles.text_color")} - /> - - - - - {t( - `home.settings.subtitles.colors.${settings?.vlcBackgroundColor || "Black"}`, - )} - - - - } - title={t("home.settings.subtitles.background_color")} - /> - - - - - {t( - `home.settings.subtitles.colors.${settings?.vlcOutlineColor || "Black"}`, - )} - - - - } - title={t("home.settings.subtitles.outline_color")} - /> - - - - - {t( - `home.settings.subtitles.thickness.${settings?.vlcOutlineThickness || "Normal"}`, - )} - - - - } - title={t("home.settings.subtitles.outline_thickness")} - /> - - - - {`${Math.round(((settings?.vlcBackgroundOpacity ?? 128) / 255) * 100)}%`} - - - } - title={t("home.settings.subtitles.background_opacity")} - /> - - - - {`${Math.round(((settings?.vlcOutlineOpacity ?? 255) / 255) * 100)}%`} - - - } - title={t("home.settings.subtitles.outline_opacity")} - /> - - - updateSettings({ vlcIsBold: value })} /> diff --git a/components/settings/VideoPlayerSettings.tsx b/components/settings/VideoPlayerSettings.tsx new file mode 100644 index 00000000..5dc546da --- /dev/null +++ b/components/settings/VideoPlayerSettings.tsx @@ -0,0 +1,93 @@ +import { Ionicons } from "@expo/vector-icons"; +import type React from "react"; +import { useCallback, useMemo } from "react"; +import { useTranslation } from "react-i18next"; +import { Platform, Switch, View } from "react-native"; +import { setHardwareDecode } from "@/modules/sf-player"; +import { useSettings, VideoPlayerIOS } from "@/utils/atoms/settings"; +import { Text } from "../common/Text"; +import { ListGroup } from "../list/ListGroup"; +import { ListItem } from "../list/ListItem"; +import { PlatformDropdown } from "../PlatformDropdown"; + +export const VideoPlayerSettings: React.FC = () => { + const { settings, updateSettings } = useSettings(); + const { t } = useTranslation(); + + const handleHardwareDecodeChange = useCallback( + (value: boolean) => { + updateSettings({ ksHardwareDecode: value }); + setHardwareDecode(value); + }, + [updateSettings], + ); + + const videoPlayerOptions = useMemo( + () => [ + { + options: [ + { + type: "radio" as const, + label: t("home.settings.video_player.ksplayer"), + value: VideoPlayerIOS.KSPlayer, + selected: settings?.videoPlayerIOS === VideoPlayerIOS.KSPlayer, + onPress: () => + updateSettings({ videoPlayerIOS: VideoPlayerIOS.KSPlayer }), + }, + { + type: "radio" as const, + label: t("home.settings.video_player.vlc"), + value: VideoPlayerIOS.VLC, + selected: settings?.videoPlayerIOS === VideoPlayerIOS.VLC, + onPress: () => + updateSettings({ videoPlayerIOS: VideoPlayerIOS.VLC }), + }, + ], + }, + ], + [settings?.videoPlayerIOS, t, updateSettings], + ); + + const getPlayerLabel = useCallback(() => { + switch (settings?.videoPlayerIOS) { + case VideoPlayerIOS.VLC: + return t("home.settings.video_player.vlc"); + default: + return t("home.settings.video_player.ksplayer"); + } + }, [settings?.videoPlayerIOS, t]); + + if (Platform.OS !== "ios" || !settings) return null; + + return ( + + + + {getPlayerLabel()} + + + } + title={t("home.settings.video_player.video_player")} + /> + + + {settings.videoPlayerIOS === VideoPlayerIOS.KSPlayer && ( + + + + )} + + ); +}; diff --git a/components/video-player/controls/BottomControls.tsx b/components/video-player/controls/BottomControls.tsx index 7519b1ed..457a11f8 100644 --- a/components/video-player/controls/BottomControls.tsx +++ b/components/video-player/controls/BottomControls.tsx @@ -18,7 +18,6 @@ interface BottomControlsProps { showRemoteBubble: boolean; currentTime: number; remainingTime: number; - isVlc: boolean; showSkipButton: boolean; showSkipCreditButton: boolean; hasContentAfterCredits: boolean; @@ -67,7 +66,6 @@ export const BottomControls: FC = ({ showRemoteBubble, currentTime, remainingTime, - isVlc, showSkipButton, showSkipCreditButton, hasContentAfterCredits, @@ -157,7 +155,7 @@ export const BottomControls: FC = ({ ? false : // Show during credits if no content after, OR near end of video (showSkipCreditButton && !hasContentAfterCredits) || - (isVlc ? remainingTime < 10000 : remainingTime < 10) + remainingTime < 10000 } onFinish={handleNextEpisodeAutoPlay} onPress={handleNextEpisodeManual} @@ -215,7 +213,6 @@ export const BottomControls: FC = ({ diff --git a/components/video-player/controls/Controls.tsx b/components/video-player/controls/Controls.tsx index 53837482..a0ac938b 100644 --- a/components/video-player/controls/Controls.tsx +++ b/components/video-player/controls/Controls.tsx @@ -4,16 +4,8 @@ import type { MediaSourceInfo, } from "@jellyfin/sdk/lib/generated-client"; import { useLocalSearchParams, useRouter } from "expo-router"; -import { - type Dispatch, - type FC, - type MutableRefObject, - type SetStateAction, - useCallback, - useEffect, - useState, -} from "react"; -import { useWindowDimensions } from "react-native"; +import { type FC, useCallback, useEffect, useState } from "react"; +import { StyleSheet, useWindowDimensions, View } from "react-native"; import Animated, { Easing, type SharedValue, @@ -28,7 +20,6 @@ import { useHaptic } from "@/hooks/useHaptic"; import { useIntroSkipper } from "@/hooks/useIntroSkipper"; import { usePlaybackManager } from "@/hooks/usePlaybackManager"; import { useTrickplay } from "@/hooks/useTrickplay"; -import type { TrackInfo, VlcPlayerViewRef } from "@/modules/VlcPlayer.types"; import { DownloadedItem } from "@/providers/Downloads/types"; import { useSettings } from "@/utils/atoms/settings"; import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings"; @@ -36,7 +27,6 @@ import { ticksToMs } from "@/utils/time"; import { BottomControls } from "./BottomControls"; import { CenterControls } from "./CenterControls"; import { CONTROLS_CONSTANTS } from "./constants"; -import { ControlProvider } from "./contexts/ControlContext"; import { EpisodeList } from "./EpisodeList"; import { GestureOverlay } from "./GestureOverlay"; import { HeaderControls } from "./HeaderControls"; @@ -44,42 +34,30 @@ import { useRemoteControl } from "./hooks/useRemoteControl"; import { useVideoNavigation } from "./hooks/useVideoNavigation"; import { useVideoSlider } from "./hooks/useVideoSlider"; import { useVideoTime } from "./hooks/useVideoTime"; -import { type ScaleFactor } from "./ScaleFactorSelector"; import { useControlsTimeout } from "./useControlsTimeout"; import { type AspectRatio } from "./VideoScalingModeSelector"; interface Props { item: BaseItemDto; - videoRef: MutableRefObject; isPlaying: boolean; isSeeking: SharedValue; cacheProgress: SharedValue; progress: SharedValue; isBuffering: boolean; showControls: boolean; - enableTrickplay?: boolean; togglePlay: () => void; setShowControls: (shown: boolean) => void; offline?: boolean; - isVideoLoaded?: boolean; mediaSource?: MediaSourceInfo | null; seek: (ticks: number) => void; startPictureInPicture?: () => Promise; play: () => void; pause: () => void; - getAudioTracks?: (() => Promise) | (() => TrackInfo[]); - getSubtitleTracks?: (() => Promise) | (() => TrackInfo[]); - setSubtitleURL?: (url: string, customName: string) => void; - setSubtitleTrack?: (index: number) => void; - setAudioTrack?: (index: number) => void; setVideoAspectRatio?: (aspectRatio: string | null) => Promise; - setVideoScaleFactor?: (scaleFactor: number) => Promise; aspectRatio?: AspectRatio; - scaleFactor?: ScaleFactor; - setAspectRatio?: Dispatch>; - setScaleFactor?: Dispatch>; - isVlc?: boolean; + isZoomedToFill?: boolean; + onZoomToggle?: () => void; api?: Api | null; downloadedFiles?: DownloadedItem[]; } @@ -99,20 +77,11 @@ export const Controls: FC = ({ showControls, setShowControls, mediaSource, - isVideoLoaded, - getAudioTracks, - getSubtitleTracks, - setSubtitleURL, - setSubtitleTrack, - setAudioTrack, setVideoAspectRatio, - setVideoScaleFactor, aspectRatio = "default", - scaleFactor = 1.0, - setAspectRatio, - setScaleFactor, + isZoomedToFill = false, + onZoomToggle, offline = false, - isVlc = false, api = null, downloadedFiles = undefined, }) => { @@ -194,17 +163,13 @@ export const Controls: FC = ({ zIndex: 10, })); - // Initialize progress values + // Initialize progress values - MPV uses milliseconds useEffect(() => { if (item) { - progress.value = isVlc - ? ticksToMs(item?.UserData?.PlaybackPositionTicks) - : item?.UserData?.PlaybackPositionTicks || 0; - max.value = isVlc - ? ticksToMs(item.RunTimeTicks || 0) - : item.RunTimeTicks || 0; + progress.value = ticksToMs(item?.UserData?.PlaybackPositionTicks); + max.value = ticksToMs(item.RunTimeTicks || 0); } - }, [item, isVlc, progress, max]); + }, [item, progress, max]); // Navigation hooks const { @@ -215,7 +180,6 @@ export const Controls: FC = ({ } = useVideoNavigation({ progress, isPlaying, - isVlc, seek, play, }); @@ -225,7 +189,6 @@ export const Controls: FC = ({ progress, max, isSeeking, - isVlc, }); const toggleControls = useCallback(() => { @@ -248,7 +211,6 @@ export const Controls: FC = ({ progress, min, max, - isVlc, showControls, isPlaying, seek, @@ -273,7 +235,6 @@ export const Controls: FC = ({ progress, isSeeking, isPlaying, - isVlc, seek, play, pause, @@ -302,9 +263,8 @@ export const Controls: FC = ({ : current.actual; } else { // When not scrubbing, only update if progress changed significantly (1 second) - const progressUnit = isVlc - ? CONTROLS_CONSTANTS.PROGRESS_UNIT_MS - : CONTROLS_CONSTANTS.PROGRESS_UNIT_TICKS; + // MPV uses milliseconds + const progressUnit = CONTROLS_CONSTANTS.PROGRESS_UNIT_MS; const progressDiff = Math.abs(current.actual - effectiveProgress.value); if (progressDiff >= progressUnit) { effectiveProgress.value = current.actual; @@ -325,7 +285,6 @@ export const Controls: FC = ({ currentTime, seek, play, - isVlc, offline, api, downloadedFiles, @@ -337,7 +296,6 @@ export const Controls: FC = ({ currentTime, seek, play, - isVlc, offline, api, downloadedFiles, @@ -361,12 +319,10 @@ export const Controls: FC = ({ mediaSource: newMediaSource, audioIndex: defaultAudioIndex, subtitleIndex: defaultSubtitleIndex, - } = getDefaultPlaySettings( - item, - settings, - previousIndexes, - mediaSource ?? undefined, - ); + } = getDefaultPlaySettings(item, settings, { + indexes: previousIndexes, + source: mediaSource ?? undefined, + }); const queryParams = new URLSearchParams({ ...(offline && { offline: "true" }), @@ -379,8 +335,6 @@ export const Controls: FC = ({ item.UserData?.PlaybackPositionTicks?.toString() ?? "", }).toString(); - console.log("queryParams", queryParams); - router.replace(`player/direct-player?${queryParams}` as any); }, [settings, subtitleIndex, audioIndex, mediaSource, bitrateValue, router], @@ -471,6 +425,7 @@ export const Controls: FC = ({ episodeView, onHideControls: hideControls, timeout: CONTROLS_CONSTANTS.TIMEOUT, + disabled: true, }); const switchOnEpisodeMode = useCallback(() => { @@ -481,11 +436,7 @@ export const Controls: FC = ({ }, [isPlaying, togglePlay]); return ( - + {episodeView ? ( = ({ goToNextItem={goToNextItem} previousItem={previousItem} nextItem={nextItem} - getAudioTracks={getAudioTracks} - getSubtitleTracks={getSubtitleTracks} - setAudioTrack={setAudioTrack} - setSubtitleTrack={setSubtitleTrack} - setSubtitleURL={setSubtitleURL} aspectRatio={aspectRatio} - scaleFactor={scaleFactor} - setAspectRatio={setAspectRatio} - setScaleFactor={setScaleFactor} setVideoAspectRatio={setVideoAspectRatio} - setVideoScaleFactor={setVideoScaleFactor} + isZoomedToFill={isZoomedToFill} + onZoomToggle={onZoomToggle} /> = ({ showRemoteBubble={showRemoteBubble} currentTime={currentTime} remainingTime={remainingTime} - isVlc={isVlc} showSkipButton={showSkipButton} showSkipCreditButton={showSkipCreditButton} hasContentAfterCredits={hasContentAfterCredits} @@ -585,6 +528,16 @@ export const Controls: FC = ({ {settings.maxAutoPlayEpisodeCount.value !== -1 && ( )} - + ); }; + +const styles = StyleSheet.create({ + controlsContainer: { + position: "absolute", + top: 0, + left: 0, + right: 0, + bottom: 0, + }, +}); diff --git a/components/video-player/controls/HeaderControls.tsx b/components/video-player/controls/HeaderControls.tsx index d8e4dcfe..52b203be 100644 --- a/components/video-player/controls/HeaderControls.tsx +++ b/components/video-player/controls/HeaderControls.tsx @@ -4,7 +4,7 @@ import type { MediaSourceInfo, } from "@jellyfin/sdk/lib/generated-client"; import { useRouter } from "expo-router"; -import { type Dispatch, type FC, type SetStateAction } from "react"; +import type { FC } from "react"; import { Platform, TouchableOpacity, @@ -13,15 +13,11 @@ import { } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useHaptic } from "@/hooks/useHaptic"; -import { useSettings, VideoPlayer } from "@/utils/atoms/settings"; +import { useSettings } from "@/utils/atoms/settings"; import { ICON_SIZES } from "./constants"; -import { VideoProvider } from "./contexts/VideoContext"; import DropdownView from "./dropdown/DropdownView"; -import { type ScaleFactor, ScaleFactorSelector } from "./ScaleFactorSelector"; -import { - type AspectRatio, - AspectRatioSelector, -} from "./VideoScalingModeSelector"; +import { type AspectRatio } from "./VideoScalingModeSelector"; +import { ZoomToggle } from "./ZoomToggle"; interface HeaderControlsProps { item: BaseItemDto; @@ -34,17 +30,10 @@ interface HeaderControlsProps { goToNextItem: (options: { isAutoPlay?: boolean }) => void; previousItem?: BaseItemDto | null; nextItem?: BaseItemDto | null; - getAudioTracks?: (() => Promise) | (() => any[]); - getSubtitleTracks?: (() => Promise) | (() => any[]); - setAudioTrack?: (index: number) => void; - setSubtitleTrack?: (index: number) => void; - setSubtitleURL?: (url: string, customName: string) => void; aspectRatio?: AspectRatio; - scaleFactor?: ScaleFactor; - setAspectRatio?: Dispatch>; - setScaleFactor?: Dispatch>; setVideoAspectRatio?: (aspectRatio: string | null) => Promise; - setVideoScaleFactor?: (scaleFactor: number) => Promise; + isZoomedToFill?: boolean; + onZoomToggle?: () => void; } export const HeaderControls: FC = ({ @@ -58,39 +47,17 @@ export const HeaderControls: FC = ({ goToNextItem, previousItem, nextItem, - getAudioTracks, - getSubtitleTracks, - setAudioTrack, - setSubtitleTrack, - setSubtitleURL, - aspectRatio = "default", - scaleFactor = 1.0, - setAspectRatio, - setScaleFactor, - setVideoAspectRatio, - setVideoScaleFactor, + aspectRatio: _aspectRatio = "default", + setVideoAspectRatio: _setVideoAspectRatio, + isZoomedToFill = false, + onZoomToggle, }) => { const { settings } = useSettings(); const router = useRouter(); const insets = useSafeAreaInsets(); - const { width: screenWidth } = useWindowDimensions(); + const { width: _screenWidth } = useWindowDimensions(); const lightHapticFeedback = useHaptic("light"); - const handleAspectRatioChange = async (newRatio: AspectRatio) => { - if (!setAspectRatio || !setVideoAspectRatio) return; - - setAspectRatio(newRatio); - const aspectRatioString = newRatio === "default" ? null : newRatio; - await setVideoAspectRatio(aspectRatioString); - }; - - const handleScaleFactorChange = async (newScale: ScaleFactor) => { - if (!setScaleFactor || !setVideoScaleFactor) return; - - setScaleFactor(newScale); - await setVideoScaleFactor(newScale); - }; - const onClose = async () => { lightHapticFeedback(); router.back(); @@ -102,46 +69,34 @@ export const HeaderControls: FC = ({ { position: "absolute", top: settings?.safeAreaInControlsEnabled ? insets.top : 0, + left: settings?.safeAreaInControlsEnabled ? insets.left : 0, right: settings?.safeAreaInControlsEnabled ? insets.right : 0, - width: settings?.safeAreaInControlsEnabled - ? screenWidth - insets.left - insets.right - : screenWidth, }, ]} pointerEvents={showControls ? "auto" : "none"} - className={"flex flex-row w-full pt-2"} + className='flex flex-row justify-between' > - + {!Platform.isTV && (!offline || !mediaSource?.TranscodingUrl) && ( - - - - - + + + )} - {!Platform.isTV && - (settings.defaultPlayer === VideoPlayer.VLC_4 || - Platform.OS === "android") && ( - - - - )} + {!Platform.isTV && startPictureInPicture && ( + + + + )} {item?.Type === "Episode" && ( = ({ /> )} - { + if (setVideoAspectRatio) { + const aspectRatioString = newRatio === "default" ? null : newRatio; + await setVideoAspectRatio(aspectRatioString); + } + }} disabled={!setVideoAspectRatio} - /> - */} + {})} + disabled={!onZoomToggle} /> void; - disabled?: boolean; -} - -interface ScaleFactorOption { - id: ScaleFactor; - label: string; - description: string; -} - -const SCALE_FACTOR_OPTIONS: ScaleFactorOption[] = [ - { - id: 1.0, - label: "1.0x", - description: "Original size", - }, - { - id: 1.1, - label: "1.1x", - description: "10% larger", - }, - { - id: 1.2, - label: "1.2x", - description: "20% larger", - }, - { - id: 1.3, - label: "1.3x", - description: "30% larger", - }, - { - id: 1.4, - label: "1.4x", - description: "40% larger", - }, - { - id: 1.5, - label: "1.5x", - description: "50% larger", - }, - { - id: 1.6, - label: "1.6x", - description: "60% larger", - }, - { - id: 1.7, - label: "1.7x", - description: "70% larger", - }, - { - id: 1.8, - label: "1.8x", - description: "80% larger", - }, - { - id: 1.9, - label: "1.9x", - description: "90% larger", - }, - { - id: 2.0, - label: "2.0x", - description: "Double size", - }, -]; - -export const ScaleFactorSelector: React.FC = ({ - currentScale, - onScaleChange, - disabled = false, -}) => { - const lightHapticFeedback = useHaptic("light"); - - const handleScaleSelect = (scale: ScaleFactor) => { - onScaleChange(scale); - lightHapticFeedback(); - }; - - const optionGroups = useMemo(() => { - return [ - { - options: SCALE_FACTOR_OPTIONS.map((option) => ({ - type: "radio" as const, - label: option.label, - value: option.id, - selected: option.id === currentScale, - onPress: () => handleScaleSelect(option.id), - disabled, - })), - }, - ]; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentScale, disabled]); - - const trigger = useMemo( - () => ( - - - - ), - [disabled], - ); - - // Hide on TV platforms - if (Platform.isTV) return null; - - return ( - - ); -}; diff --git a/components/video-player/controls/TimeDisplay.tsx b/components/video-player/controls/TimeDisplay.tsx index 37ba755b..3f8cfd69 100644 --- a/components/video-player/controls/TimeDisplay.tsx +++ b/components/video-player/controls/TimeDisplay.tsx @@ -6,18 +6,20 @@ import { formatTimeString } from "@/utils/time"; interface TimeDisplayProps { currentTime: number; remainingTime: number; - isVlc: boolean; } +/** + * Displays current time and remaining time. + * MPV player uses milliseconds for time values. + */ export const TimeDisplay: FC = ({ currentTime, remainingTime, - isVlc, }) => { const getFinishTime = () => { const now = new Date(); - const remainingMs = isVlc ? remainingTime : remainingTime * 1000; - const finishTime = new Date(now.getTime() + remainingMs); + // remainingTime is in ms + const finishTime = new Date(now.getTime() + remainingTime); return finishTime.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", @@ -28,11 +30,11 @@ export const TimeDisplay: FC = ({ return ( - {formatTimeString(currentTime, isVlc ? "ms" : "s")} + {formatTimeString(currentTime, "ms")} - -{formatTimeString(remainingTime, isVlc ? "ms" : "s")} + -{formatTimeString(remainingTime, "ms")} ends at {getFinishTime()} diff --git a/components/video-player/controls/ZoomToggle.tsx b/components/video-player/controls/ZoomToggle.tsx new file mode 100644 index 00000000..ede70401 --- /dev/null +++ b/components/video-player/controls/ZoomToggle.tsx @@ -0,0 +1,44 @@ +import { Ionicons } from "@expo/vector-icons"; +import React from "react"; +import { Platform, TouchableOpacity, View } from "react-native"; +import { useHaptic } from "@/hooks/useHaptic"; +import { ICON_SIZES } from "./constants"; + +interface ZoomToggleProps { + isZoomedToFill: boolean; + onToggle: () => void; + disabled?: boolean; +} + +export const ZoomToggle: React.FC = ({ + isZoomedToFill, + onToggle, + disabled = false, +}) => { + const lightHapticFeedback = useHaptic("light"); + + const handlePress = () => { + if (disabled) return; + lightHapticFeedback(); + onToggle(); + }; + + // Hide on TV platforms + if (Platform.isTV) return null; + + return ( + + + + + + ); +}; diff --git a/components/video-player/controls/contexts/ControlContext.tsx b/components/video-player/controls/contexts/ControlContext.tsx deleted file mode 100644 index c13211c9..00000000 --- a/components/video-player/controls/contexts/ControlContext.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import type { - BaseItemDto, - MediaSourceInfo, -} from "@jellyfin/sdk/lib/generated-client"; -import type React from "react"; -import { createContext, type ReactNode, useContext } from "react"; - -interface ControlContextProps { - item: BaseItemDto; - mediaSource: MediaSourceInfo | null | undefined; - isVideoLoaded: boolean | undefined; -} - -const ControlContext = createContext( - undefined, -); - -interface ControlProviderProps { - children: ReactNode; - item: BaseItemDto; - mediaSource: MediaSourceInfo | null | undefined; - isVideoLoaded: boolean | undefined; -} - -export const ControlProvider: React.FC = ({ - children, - item, - mediaSource, - isVideoLoaded, -}) => { - return ( - - {children} - - ); -}; - -export const useControlContext = () => { - const context = useContext(ControlContext); - if (context === undefined) { - throw new Error("useControlContext must be used within a ControlProvider"); - } - return context; -}; diff --git a/components/video-player/controls/contexts/PlayerContext.tsx b/components/video-player/controls/contexts/PlayerContext.tsx new file mode 100644 index 00000000..71624287 --- /dev/null +++ b/components/video-player/controls/contexts/PlayerContext.tsx @@ -0,0 +1,115 @@ +import type { + BaseItemDto, + MediaSourceInfo, +} from "@jellyfin/sdk/lib/generated-client"; +import React, { + createContext, + type MutableRefObject, + type ReactNode, + useContext, + useMemo, +} from "react"; +import type { SfPlayerViewRef, VlcPlayerViewRef } from "@/modules"; + +// Union type for both player refs +type PlayerRef = SfPlayerViewRef | VlcPlayerViewRef; + +interface PlayerContextProps { + playerRef: MutableRefObject; + item: BaseItemDto; + mediaSource: MediaSourceInfo | null | undefined; + isVideoLoaded: boolean; + tracksReady: boolean; +} + +const PlayerContext = createContext(undefined); + +interface PlayerProviderProps { + children: ReactNode; + playerRef: MutableRefObject; + item: BaseItemDto; + mediaSource: MediaSourceInfo | null | undefined; + isVideoLoaded: boolean; + tracksReady: boolean; +} + +export const PlayerProvider: React.FC = ({ + children, + playerRef, + item, + mediaSource, + isVideoLoaded, + tracksReady, +}) => { + const value = useMemo( + () => ({ playerRef, item, mediaSource, isVideoLoaded, tracksReady }), + [playerRef, item, mediaSource, isVideoLoaded, tracksReady], + ); + + return ( + {children} + ); +}; + +// Core context hook +export const usePlayerContext = () => { + const context = useContext(PlayerContext); + if (!context) + throw new Error("usePlayerContext must be used within PlayerProvider"); + return context; +}; + +// Player controls hook - supports both SfPlayer (iOS) and VlcPlayer (Android) +export const usePlayerControls = () => { + const { playerRef } = usePlayerContext(); + + // Helper to get SfPlayer-specific ref (for iOS-only features) + const getSfRef = () => playerRef.current as SfPlayerViewRef | null; + + return { + // Subtitle controls (both players support these, but with different interfaces) + getSubtitleTracks: async () => { + return playerRef.current?.getSubtitleTracks?.() ?? null; + }, + setSubtitleTrack: (trackId: number) => { + playerRef.current?.setSubtitleTrack?.(trackId); + }, + // iOS only (SfPlayer) + disableSubtitles: () => { + getSfRef()?.disableSubtitles?.(); + }, + addSubtitleFile: (url: string, select = true) => { + getSfRef()?.addSubtitleFile?.(url, select); + }, + + // Audio controls (both players) + getAudioTracks: async () => { + return playerRef.current?.getAudioTracks?.() ?? null; + }, + setAudioTrack: (trackId: number) => { + playerRef.current?.setAudioTrack?.(trackId); + }, + + // Playback controls (both players) + play: () => playerRef.current?.play?.(), + pause: () => playerRef.current?.pause?.(), + seekTo: (position: number) => playerRef.current?.seekTo?.(position), + // iOS only (SfPlayer) + seekBy: (offset: number) => getSfRef()?.seekBy?.(offset), + setSpeed: (speed: number) => getSfRef()?.setSpeed?.(speed), + + // Subtitle positioning - iOS only (SfPlayer) + setSubtitleScale: (scale: number) => getSfRef()?.setSubtitleScale?.(scale), + setSubtitlePosition: (position: number) => + getSfRef()?.setSubtitlePosition?.(position), + setSubtitleMarginY: (margin: number) => + getSfRef()?.setSubtitleMarginY?.(margin), + setSubtitleFontSize: (size: number) => + getSfRef()?.setSubtitleFontSize?.(size), + + // PiP (both players) + startPictureInPicture: () => playerRef.current?.startPictureInPicture?.(), + // iOS only (SfPlayer) + stopPictureInPicture: () => getSfRef()?.stopPictureInPicture?.(), + }; +}; diff --git a/components/video-player/controls/contexts/VideoContext.tsx b/components/video-player/controls/contexts/VideoContext.tsx index ed7fa1e0..ef8042cb 100644 --- a/components/video-player/controls/contexts/VideoContext.tsx +++ b/components/video-player/controls/contexts/VideoContext.tsx @@ -1,3 +1,51 @@ +/** + * VideoContext.tsx + * + * Manages subtitle and audio track state for the video player UI. + * + * ============================================================================ + * ARCHITECTURE + * ============================================================================ + * + * - Jellyfin is source of truth for subtitle list (embedded + external) + * - KSPlayer only knows about: + * - Embedded subs it finds in the video stream + * - External subs we explicitly add via addSubtitleFile() + * - UI shows Jellyfin's complete list + * - On selection: either select embedded track or load external URL + * + * ============================================================================ + * INDEX TYPES + * ============================================================================ + * + * 1. SERVER INDEX (sub.Index / track.index) + * - Jellyfin's server-side stream index + * - Used to report playback state to Jellyfin server + * - Value of -1 means disabled/none + * + * 2. MPV INDEX (track.mpvIndex) + * - KSPlayer's internal track ID + * - KSPlayer orders tracks as: [all embedded, then all external] + * - IDs: 1..embeddedCount for embedded, embeddedCount+1.. for external + * - Value of -1 means track needs replacePlayer() (e.g., burned-in sub) + * + * ============================================================================ + * SUBTITLE HANDLING + * ============================================================================ + * + * Embedded (DeliveryMethod.Embed): + * - Already in KSPlayer's track list + * - Select via setSubtitleTrack(mpvId) + * + * External (DeliveryMethod.External): + * - Loaded into KSPlayer's srtControl on video start + * - Select via setSubtitleTrack(embeddedCount + externalPosition + 1) + * + * Image-based during transcoding: + * - Burned into video by Jellyfin, not in KSPlayer + * - Requires replacePlayer() to change + */ + import { SubtitleDeliveryMethod } from "@jellyfin/sdk/lib/generated-client"; import { router, useLocalSearchParams } from "expo-router"; import type React from "react"; @@ -9,52 +57,26 @@ import { useMemo, useState, } from "react"; -import type { TrackInfo } from "@/modules/VlcPlayer.types"; +import type { SfAudioTrack } from "@/modules"; +import { isImageBasedSubtitle } from "@/utils/jellyfin/subtitleUtils"; import type { Track } from "../types"; -import { useControlContext } from "./ControlContext"; +import { usePlayerContext, usePlayerControls } from "./PlayerContext"; interface VideoContextProps { - audioTracks: Track[] | null; subtitleTracks: Track[] | null; - setAudioTrack: ((index: number) => void) | undefined; - setSubtitleTrack: ((index: number) => void) | undefined; - setSubtitleURL: ((url: string, customName: string) => void) | undefined; + audioTracks: Track[] | null; } const VideoContext = createContext(undefined); -interface VideoProviderProps { - children: ReactNode; - getAudioTracks: - | (() => Promise) - | (() => TrackInfo[]) - | undefined; - getSubtitleTracks: - | (() => Promise) - | (() => TrackInfo[]) - | undefined; - setAudioTrack: ((index: number) => void) | undefined; - setSubtitleTrack: ((index: number) => void) | undefined; - setSubtitleURL: ((url: string, customName: string) => void) | undefined; -} - -export const VideoProvider: React.FC = ({ +export const VideoProvider: React.FC<{ children: ReactNode }> = ({ children, - getSubtitleTracks, - getAudioTracks, - setSubtitleTrack, - setSubtitleURL, - setAudioTrack, }) => { - const [audioTracks, setAudioTracks] = useState(null); const [subtitleTracks, setSubtitleTracks] = useState(null); + const [audioTracks, setAudioTracks] = useState(null); - const ControlContext = useControlContext(); - const isVideoLoaded = ControlContext?.isVideoLoaded; - const mediaSource = ControlContext?.mediaSource; - - const allSubs = - mediaSource?.MediaStreams?.filter((s) => s.Type === "Subtitle") || []; + const { tracksReady, mediaSource } = usePlayerContext(); + const playerControls = usePlayerControls(); const { itemId, audioIndex, bitrateValue, subtitleIndex, playbackPosition } = useLocalSearchParams<{ @@ -66,185 +88,189 @@ export const VideoProvider: React.FC = ({ playbackPosition: string; }>(); - const onTextBasedSubtitle = useMemo(() => { - return ( - allSubs.find( - (s) => - s.Index?.toString() === subtitleIndex && - (s.DeliveryMethod === SubtitleDeliveryMethod.Embed || - s.DeliveryMethod === SubtitleDeliveryMethod.Hls || - s.DeliveryMethod === SubtitleDeliveryMethod.External), - ) || subtitleIndex === "-1" + const allSubs = + mediaSource?.MediaStreams?.filter((s) => s.Type === "Subtitle") || []; + const allAudio = + mediaSource?.MediaStreams?.filter((s) => s.Type === "Audio") || []; + + const isTranscoding = Boolean(mediaSource?.TranscodingUrl); + + /** + * Check if the currently selected subtitle is image-based. + * Used to determine if we need to refresh the player when changing subs. + */ + const isCurrentSubImageBased = useMemo(() => { + if (subtitleIndex === "-1") return false; + const currentSub = allSubs.find( + (s) => s.Index?.toString() === subtitleIndex, ); + return currentSub ? isImageBasedSubtitle(currentSub) : false; }, [allSubs, subtitleIndex]); - const setPlayerParams = ({ - chosenAudioIndex = audioIndex, - chosenSubtitleIndex = subtitleIndex, - }: { - chosenAudioIndex?: string; - chosenSubtitleIndex?: string; + /** + * Refresh the player with new parameters. + * This triggers Jellyfin to re-process the stream (e.g., burn in image subs). + */ + const replacePlayer = (params: { + audioIndex?: string; + subtitleIndex?: string; }) => { - console.log("chosenSubtitleIndex", chosenSubtitleIndex); const queryParams = new URLSearchParams({ itemId: itemId ?? "", - audioIndex: chosenAudioIndex, - subtitleIndex: chosenSubtitleIndex, + audioIndex: params.audioIndex ?? audioIndex, + subtitleIndex: params.subtitleIndex ?? subtitleIndex, mediaSourceId: mediaSource?.Id ?? "", bitrateValue: bitrateValue, playbackPosition: playbackPosition, }).toString(); - router.replace(`player/direct-player?${queryParams}` as any); }; - const setTrackParams = ( - type: "audio" | "subtitle", - index: number, - serverIndex: number, - ) => { - const setTrack = type === "audio" ? setAudioTrack : setSubtitleTrack; - const paramKey = type === "audio" ? "audioIndex" : "subtitleIndex"; - - // If we're transcoding and we're going from a image based subtitle - // to a text based subtitle, we need to change the player params. - - const shouldChangePlayerParams = - type === "subtitle" && - mediaSource?.TranscodingUrl && - !onTextBasedSubtitle; - - console.log("Set player params", index, serverIndex); - if (shouldChangePlayerParams) { - setPlayerParams({ - chosenSubtitleIndex: serverIndex.toString(), - }); - return; - } - setTrack?.(serverIndex); - router.setParams({ - [paramKey]: serverIndex.toString(), - }); - }; - + // Fetch tracks when ready useEffect(() => { + if (!tracksReady) return; + const fetchTracks = async () => { - if (getSubtitleTracks) { - let subtitleData: TrackInfo[] | null = null; - try { - subtitleData = await getSubtitleTracks(); - } catch (error) { - console.log("[VideoContext] Failed to get subtitle tracks:", error); - return; - } - // Only FOR VLC 3, If we're transcoding, we need to reverse the subtitle data, because VLC reverses the HLS subtitles. - if ( - mediaSource?.TranscodingUrl && - subtitleData && - subtitleData.length > 1 - ) { - subtitleData = [subtitleData[0], ...subtitleData.slice(1).reverse()]; - } + const audioData = await playerControls.getAudioTracks().catch(() => null); + const playerAudio = (audioData as SfAudioTrack[]) ?? []; - let embedSubIndex = 1; - const processedSubs: Track[] = allSubs?.map((sub) => { - /** A boolean value determining if we should increment the embedSubIndex, currently only Embed and Hls subtitles are automatically added into VLC Player */ - const shouldIncrement = - sub.DeliveryMethod === SubtitleDeliveryMethod.Embed || - sub.DeliveryMethod === SubtitleDeliveryMethod.Hls || - sub.DeliveryMethod === SubtitleDeliveryMethod.External; - /** The index of subtitle inside VLC Player Itself */ - const vlcIndex = subtitleData?.at(embedSubIndex)?.index ?? -1; - if (shouldIncrement) embedSubIndex++; - return { - name: sub.DisplayTitle || "Undefined Subtitle", + // Separate embedded vs external subtitles from Jellyfin's list + // KSPlayer orders tracks as: [all embedded, then all external] + const embeddedSubs = allSubs.filter( + (s) => s.DeliveryMethod === SubtitleDeliveryMethod.Embed, + ); + const externalSubs = allSubs.filter( + (s) => s.DeliveryMethod === SubtitleDeliveryMethod.External, + ); + + // Count embedded subs that will be in KSPlayer + // (excludes image-based subs during transcoding as they're burned in) + const embeddedInPlayer = embeddedSubs.filter( + (s) => !isTranscoding || !isImageBasedSubtitle(s), + ); + + const subs: Track[] = []; + + // Process all Jellyfin subtitles + for (const sub of allSubs) { + const isEmbedded = sub.DeliveryMethod === SubtitleDeliveryMethod.Embed; + const isExternal = + sub.DeliveryMethod === SubtitleDeliveryMethod.External; + + // For image-based subs during transcoding, need to refresh player + if (isTranscoding && isImageBasedSubtitle(sub)) { + subs.push({ + name: sub.DisplayTitle || "Unknown", index: sub.Index ?? -1, - setTrack: () => - shouldIncrement - ? setTrackParams("subtitle", vlcIndex, sub.Index ?? -1) - : setPlayerParams({ - chosenSubtitleIndex: sub.Index?.toString(), - }), - }; - }); - - // Step 3: Restore the original order - const subtitles: Track[] = processedSubs.sort( - (a, b) => a.index - b.index, - ); - - // Add a "Disable Subtitles" option - subtitles.unshift({ - name: "Disable", - index: -1, - setTrack: () => - !mediaSource?.TranscodingUrl || onTextBasedSubtitle - ? setTrackParams("subtitle", -1, -1) - : setPlayerParams({ chosenSubtitleIndex: "-1" }), - }); - setSubtitleTracks(subtitles); - } - if (getAudioTracks) { - let audioData: TrackInfo[] | null = null; - try { - audioData = await getAudioTracks(); - } catch (error) { - console.log("[VideoContext] Failed to get audio tracks:", error); - return; - } - const allAudio = - mediaSource?.MediaStreams?.filter((s) => s.Type === "Audio") || []; - const audioTracks: Track[] = allAudio?.map((audio, idx) => { - if (!mediaSource?.TranscodingUrl) { - const vlcIndex = audioData?.at(idx + 1)?.index ?? -1; - return { - name: audio.DisplayTitle ?? "Undefined Audio", - index: audio.Index ?? -1, - setTrack: () => - setTrackParams("audio", vlcIndex, audio.Index ?? -1), - }; - } - return { - name: audio.DisplayTitle ?? "Undefined Audio", - index: audio.Index ?? -1, - setTrack: () => - setPlayerParams({ chosenAudioIndex: audio.Index?.toString() }), - }; - }); - - // Add a "Disable Audio" option if its not transcoding. - if (!mediaSource?.TranscodingUrl) { - audioTracks.unshift({ - name: "Disable", - index: -1, - setTrack: () => setTrackParams("audio", -1, -1), + mpvIndex: -1, + setTrack: () => { + replacePlayer({ subtitleIndex: String(sub.Index) }); + }, }); + continue; } - setAudioTracks(audioTracks); + + // Calculate KSPlayer track ID based on type + // KSPlayer IDs: [1..embeddedCount] for embedded, [embeddedCount+1..] for external + let mpvId = -1; + + if (isEmbedded) { + // Find position among embedded subs that are in player + const embeddedPosition = embeddedInPlayer.findIndex( + (s) => s.Index === sub.Index, + ); + if (embeddedPosition !== -1) { + mpvId = embeddedPosition + 1; // 1-based ID + } + } else if (isExternal) { + // Find position among external subs, offset by embedded count + const externalPosition = externalSubs.findIndex( + (s) => s.Index === sub.Index, + ); + if (externalPosition !== -1) { + mpvId = embeddedInPlayer.length + externalPosition + 1; + } + } + + subs.push({ + name: sub.DisplayTitle || "Unknown", + index: sub.Index ?? -1, + mpvIndex: mpvId, + setTrack: () => { + // Transcoding + switching to/from image-based sub + if ( + isTranscoding && + (isImageBasedSubtitle(sub) || isCurrentSubImageBased) + ) { + replacePlayer({ subtitleIndex: String(sub.Index) }); + return; + } + + // Direct switch in player + if (mpvId !== -1) { + playerControls.setSubtitleTrack(mpvId); + router.setParams({ subtitleIndex: String(sub.Index) }); + return; + } + + // Fallback - refresh player + replacePlayer({ subtitleIndex: String(sub.Index) }); + }, + }); } + + // Add "Disable" option at the beginning + subs.unshift({ + name: "Disable", + index: -1, + mpvIndex: -1, + setTrack: () => { + if (isTranscoding && isCurrentSubImageBased) { + replacePlayer({ subtitleIndex: "-1" }); + } else { + playerControls.setSubtitleTrack(-1); + router.setParams({ subtitleIndex: "-1" }); + } + }, + }); + + // Process audio tracks + const audio: Track[] = allAudio.map((a, idx) => { + const playerTrack = playerAudio[idx]; + const mpvId = playerTrack?.id ?? idx + 1; + + return { + name: a.DisplayTitle || "Unknown", + index: a.Index ?? -1, + mpvIndex: mpvId, + setTrack: () => { + if (isTranscoding) { + replacePlayer({ audioIndex: String(a.Index) }); + return; + } + playerControls.setAudioTrack(mpvId); + router.setParams({ audioIndex: String(a.Index) }); + }, + }; + }); + + setSubtitleTracks(subs.sort((a, b) => a.index - b.index)); + setAudioTracks(audio); }; + fetchTracks(); - }, [isVideoLoaded, getAudioTracks, getSubtitleTracks]); + }, [tracksReady, mediaSource]); return ( - + {children} ); }; export const useVideoContext = () => { - const context = useContext(VideoContext); - if (context === undefined) { - throw new Error("useVideoContext must be used within a VideoProvider"); - } - return context; + const ctx = useContext(VideoContext); + if (!ctx) + throw new Error("useVideoContext must be used within VideoProvider"); + return ctx; }; diff --git a/components/video-player/controls/dropdown/DropdownView.tsx b/components/video-player/controls/dropdown/DropdownView.tsx index e1332e43..910b3eed 100644 --- a/components/video-player/controls/dropdown/DropdownView.tsx +++ b/components/video-player/controls/dropdown/DropdownView.tsx @@ -7,17 +7,26 @@ import { type OptionGroup, PlatformDropdown, } from "@/components/PlatformDropdown"; -import { useControlContext } from "../contexts/ControlContext"; +import { useSettings } from "@/utils/atoms/settings"; +import { usePlayerContext } from "../contexts/PlayerContext"; import { useVideoContext } from "../contexts/VideoContext"; +// Subtitle size presets (stored as scale * 100, so 1.0 = 100) +const SUBTITLE_SIZE_PRESETS = [ + { label: "0.5", value: 50 }, + { label: "0.6", value: 60 }, + { label: "0.7", value: 70 }, + { label: "0.8", value: 80 }, + { label: "0.9", value: 90 }, + { label: "1.0", value: 100 }, + { label: "1.1", value: 110 }, + { label: "1.2", value: 120 }, +] as const; + const DropdownView = () => { - const videoContext = useVideoContext(); - const { subtitleTracks, audioTracks } = videoContext; - const ControlContext = useControlContext(); - const [item, mediaSource] = [ - ControlContext?.item, - ControlContext?.mediaSource, - ]; + const { subtitleTracks, audioTracks } = useVideoContext(); + const { item, mediaSource } = usePlayerContext(); + const { settings, updateSettings } = useSettings(); const router = useRouter(); const { subtitleIndex, audioIndex, bitrateValue, playbackPosition, offline } = @@ -100,6 +109,18 @@ const DropdownView = () => { onPress: () => sub.setTrack(), })), }); + + // Subtitle Size Section + groups.push({ + title: "Subtitle Size", + options: SUBTITLE_SIZE_PRESETS.map((preset) => ({ + type: "radio" as const, + label: preset.label, + value: preset.value.toString(), + selected: settings.subtitleSize === preset.value, + onPress: () => updateSettings({ subtitleSize: preset.value }), + })), + }); } // Audio Section @@ -126,6 +147,8 @@ const DropdownView = () => { audioTracksKey, subtitleIndex, audioIndex, + settings.subtitleSize, + updateSettings, // Note: subtitleTracks and audioTracks are intentionally excluded // because we use subtitleTracksKey and audioTracksKey for stability ]); @@ -148,6 +171,7 @@ const DropdownView = () => { title='Playback Options' groups={optionGroups} trigger={trigger} + expoUIConfig={{}} bottomSheetConfig={{ enablePanDownToClose: true, }} diff --git a/components/video-player/controls/hooks/useRemoteControl.ts b/components/video-player/controls/hooks/useRemoteControl.ts index 8eba2c45..0a71b8b0 100644 --- a/components/video-player/controls/hooks/useRemoteControl.ts +++ b/components/video-player/controls/hooks/useRemoteControl.ts @@ -22,7 +22,6 @@ interface UseRemoteControlProps { progress: SharedValue; min: SharedValue; max: SharedValue; - isVlc: boolean; showControls: boolean; isPlaying: boolean; seek: (value: number) => void; @@ -34,11 +33,14 @@ interface UseRemoteControlProps { handleSeekBackward: (seconds: number) => void; } +/** + * Hook to manage TV remote control interactions. + * MPV player uses milliseconds for time values. + */ export function useRemoteControl({ progress, min, max, - isVlc, showControls, isPlaying, seek, @@ -61,21 +63,18 @@ export function useRemoteControl({ const longPressTimeoutRef = useRef | null>( null, ); - const SCRUB_INTERVAL = isVlc - ? CONTROLS_CONSTANTS.SCRUB_INTERVAL_MS - : CONTROLS_CONSTANTS.SCRUB_INTERVAL_TICKS; + // MPV uses ms + const SCRUB_INTERVAL = CONTROLS_CONSTANTS.SCRUB_INTERVAL_MS; - const updateTime = useCallback( - (progressValue: number) => { - const progressInTicks = isVlc ? msToTicks(progressValue) : progressValue; - const progressInSeconds = Math.floor(ticksToSeconds(progressInTicks)); - const hours = Math.floor(progressInSeconds / 3600); - const minutes = Math.floor((progressInSeconds % 3600) / 60); - const seconds = progressInSeconds % 60; - setTime({ hours, minutes, seconds }); - }, - [isVlc], - ); + const updateTime = useCallback((progressValue: number) => { + // Convert ms to ticks for calculation + const progressInTicks = msToTicks(progressValue); + const progressInSeconds = Math.floor(ticksToSeconds(progressInTicks)); + const hours = Math.floor(progressInSeconds / 3600); + const minutes = Math.floor((progressInSeconds % 3600) / 60); + const seconds = progressInSeconds % 60; + setTime({ hours, minutes, seconds }); + }, []); // TV remote control handling (no-op on non-TV platforms) useTVEventHandler((evt) => { @@ -102,7 +101,8 @@ export function useRemoteControl({ Math.min(max.value, base + direction * SCRUB_INTERVAL), ); remoteScrubProgress.value = updated; - const progressInTicks = isVlc ? msToTicks(updated) : updated; + // Convert ms to ticks for trickplay + const progressInTicks = msToTicks(updated); calculateTrickplayUrl(progressInTicks); updateTime(updated); break; @@ -111,9 +111,8 @@ export function useRemoteControl({ if (isRemoteScrubbing.value && remoteScrubProgress.value != null) { progress.value = remoteScrubProgress.value; - const seekTarget = isVlc - ? Math.max(0, remoteScrubProgress.value) - : Math.max(0, ticksToSeconds(remoteScrubProgress.value)); + // MPV uses ms, seek expects ms + const seekTarget = Math.max(0, remoteScrubProgress.value); seek(seekTarget); if (isPlaying) play(); diff --git a/components/video-player/controls/hooks/useVideoNavigation.ts b/components/video-player/controls/hooks/useVideoNavigation.ts index 0573d6e4..5468c790 100644 --- a/components/video-player/controls/hooks/useVideoNavigation.ts +++ b/components/video-player/controls/hooks/useVideoNavigation.ts @@ -3,20 +3,22 @@ import type { SharedValue } from "react-native-reanimated"; import { useHaptic } from "@/hooks/useHaptic"; import { useSettings } from "@/utils/atoms/settings"; import { writeToLog } from "@/utils/log"; -import { secondsToMs, ticksToSeconds } from "@/utils/time"; +import { secondsToMs } from "@/utils/time"; interface UseVideoNavigationProps { progress: SharedValue; isPlaying: boolean; - isVlc: boolean; seek: (value: number) => void; play: () => void; } +/** + * Hook to manage video navigation (seeking forward/backward). + * MPV player uses milliseconds for time values. + */ export function useVideoNavigation({ progress, isPlaying, - isVlc, seek, play, }: UseVideoNavigationProps) { @@ -30,16 +32,15 @@ export function useVideoNavigation({ try { const curr = progress.value; if (curr !== undefined) { - const newTime = isVlc - ? Math.max(0, curr - secondsToMs(seconds)) - : Math.max(0, ticksToSeconds(curr) - seconds); + // MPV uses ms + const newTime = Math.max(0, curr - secondsToMs(seconds)); seek(newTime); } } catch (error) { writeToLog("ERROR", "Error seeking video backwards", error); } }, - [isPlaying, isVlc, seek, progress], + [isPlaying, seek, progress], ); const handleSeekForward = useCallback( @@ -48,16 +49,15 @@ export function useVideoNavigation({ try { const curr = progress.value; if (curr !== undefined) { - const newTime = isVlc - ? curr + secondsToMs(seconds) - : ticksToSeconds(curr) + seconds; + // MPV uses ms + const newTime = curr + secondsToMs(seconds); seek(Math.max(0, newTime)); } } catch (error) { writeToLog("ERROR", "Error seeking video forwards", error); } }, - [isPlaying, isVlc, seek, progress], + [isPlaying, seek, progress], ); const handleSkipBackward = useCallback(async () => { @@ -69,9 +69,11 @@ export function useVideoNavigation({ try { const curr = progress.value; if (curr !== undefined) { - const newTime = isVlc - ? Math.max(0, curr - secondsToMs(settings.rewindSkipTime)) - : Math.max(0, ticksToSeconds(curr) - settings.rewindSkipTime); + // MPV uses ms + const newTime = Math.max( + 0, + curr - secondsToMs(settings.rewindSkipTime), + ); seek(newTime); if (wasPlayingRef.current) { play(); @@ -80,7 +82,7 @@ export function useVideoNavigation({ } catch (error) { writeToLog("ERROR", "Error seeking video backwards", error); } - }, [settings, isPlaying, isVlc, play, seek, progress, lightHapticFeedback]); + }, [settings, isPlaying, play, seek, progress, lightHapticFeedback]); const handleSkipForward = useCallback(async () => { if (!settings?.forwardSkipTime) { @@ -91,9 +93,8 @@ export function useVideoNavigation({ try { const curr = progress.value; if (curr !== undefined) { - const newTime = isVlc - ? curr + secondsToMs(settings.forwardSkipTime) - : ticksToSeconds(curr) + settings.forwardSkipTime; + // MPV uses ms + const newTime = curr + secondsToMs(settings.forwardSkipTime); seek(Math.max(0, newTime)); if (wasPlayingRef.current) { play(); @@ -102,7 +103,7 @@ export function useVideoNavigation({ } catch (error) { writeToLog("ERROR", "Error seeking video forwards", error); } - }, [settings, isPlaying, isVlc, play, seek, progress, lightHapticFeedback]); + }, [settings, isPlaying, play, seek, progress, lightHapticFeedback]); return { handleSeekBackward, diff --git a/components/video-player/controls/hooks/useVideoSlider.ts b/components/video-player/controls/hooks/useVideoSlider.ts index 85072954..dfc1164b 100644 --- a/components/video-player/controls/hooks/useVideoSlider.ts +++ b/components/video-player/controls/hooks/useVideoSlider.ts @@ -8,7 +8,6 @@ interface UseVideoSliderProps { progress: SharedValue; isSeeking: SharedValue; isPlaying: boolean; - isVlc: boolean; seek: (value: number) => void; play: () => void; pause: () => void; @@ -16,11 +15,14 @@ interface UseVideoSliderProps { showControls: boolean; } +/** + * Hook to manage video slider interactions. + * MPV player uses milliseconds for time values. + */ export function useVideoSlider({ progress, isSeeking, isPlaying, - isVlc, seek, play, pause, @@ -62,21 +64,20 @@ export function useVideoSlider({ setIsSliding(false); isSeeking.value = false; progress.value = value; - const seekValue = Math.max( - 0, - Math.floor(isVlc ? value : ticksToSeconds(value)), - ); + // MPV uses ms, seek expects ms + const seekValue = Math.max(0, Math.floor(value)); seek(seekValue); if (wasPlayingRef.current) { play(); } }, - [isVlc, seek, play, progress, isSeeking], + [seek, play, progress, isSeeking], ); const handleSliderChange = useCallback( debounce((value: number) => { - const progressInTicks = isVlc ? msToTicks(value) : value; + // Convert ms to ticks for trickplay + const progressInTicks = msToTicks(value); calculateTrickplayUrl(progressInTicks); const progressInSeconds = Math.floor(ticksToSeconds(progressInTicks)); const hours = Math.floor(progressInSeconds / 3600); @@ -84,7 +85,7 @@ export function useVideoSlider({ const seconds = progressInSeconds % 60; setTime({ hours, minutes, seconds }); }, CONTROLS_CONSTANTS.SLIDER_DEBOUNCE_MS), - [isVlc, calculateTrickplayUrl], + [calculateTrickplayUrl], ); return { diff --git a/components/video-player/controls/hooks/useVideoTime.ts b/components/video-player/controls/hooks/useVideoTime.ts index bb0fa77d..ad680081 100644 --- a/components/video-player/controls/hooks/useVideoTime.ts +++ b/components/video-player/controls/hooks/useVideoTime.ts @@ -4,21 +4,18 @@ import { type SharedValue, useAnimatedReaction, } from "react-native-reanimated"; -import { ticksToSeconds } from "@/utils/time"; interface UseVideoTimeProps { progress: SharedValue; max: SharedValue; isSeeking: SharedValue; - isVlc: boolean; } -export function useVideoTime({ - progress, - max, - isSeeking, - isVlc, -}: UseVideoTimeProps) { +/** + * Hook to manage video time display. + * MPV player uses milliseconds for time values. + */ +export function useVideoTime({ progress, max, isSeeking }: UseVideoTimeProps) { const [currentTime, setCurrentTime] = useState(0); const [remainingTime, setRemainingTime] = useState(Number.POSITIVE_INFINITY); @@ -27,19 +24,16 @@ export function useVideoTime({ const updateTimes = useCallback( (currentProgress: number, maxValue: number) => { - const current = isVlc ? currentProgress : ticksToSeconds(currentProgress); - const remaining = isVlc - ? maxValue - currentProgress - : ticksToSeconds(maxValue - currentProgress); + // MPV uses milliseconds + const current = currentProgress; + const remaining = maxValue - currentProgress; // Only update state if the displayed time actually changed (avoid sub-second updates) - const currentSeconds = Math.floor(current / (isVlc ? 1000 : 1)); - const remainingSeconds = Math.floor(remaining / (isVlc ? 1000 : 1)); - const lastCurrentSeconds = Math.floor( - lastCurrentTimeRef.current / (isVlc ? 1000 : 1), - ); + const currentSeconds = Math.floor(current / 1000); + const remainingSeconds = Math.floor(remaining / 1000); + const lastCurrentSeconds = Math.floor(lastCurrentTimeRef.current / 1000); const lastRemainingSeconds = Math.floor( - lastRemainingTimeRef.current / (isVlc ? 1000 : 1), + lastRemainingTimeRef.current / 1000, ); if ( @@ -52,7 +46,7 @@ export function useVideoTime({ lastRemainingTimeRef.current = remaining; } }, - [isVlc], + [], ); useAnimatedReaction( diff --git a/components/video-player/controls/types.ts b/components/video-player/controls/types.ts index f6c0e00a..5ec03edd 100644 --- a/components/video-player/controls/types.ts +++ b/components/video-player/controls/types.ts @@ -20,6 +20,7 @@ type TranscodedSubtitle = { type Track = { name: string; index: number; + mpvIndex?: number; setTrack: () => void; }; diff --git a/components/video-player/controls/useControlsTimeout.ts b/components/video-player/controls/useControlsTimeout.ts index 80d41af2..9bbd8138 100644 --- a/components/video-player/controls/useControlsTimeout.ts +++ b/components/video-player/controls/useControlsTimeout.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef } from "react"; +import { useCallback, useEffect, useRef } from "react"; interface UseControlsTimeoutProps { showControls: boolean; @@ -6,6 +6,7 @@ interface UseControlsTimeoutProps { episodeView: boolean; onHideControls: () => void; timeout?: number; + disabled?: boolean; } export const useControlsTimeout = ({ @@ -14,6 +15,7 @@ export const useControlsTimeout = ({ episodeView, onHideControls, timeout = 10000, + disabled = false, }: UseControlsTimeoutProps) => { const controlsTimeoutRef = useRef | null>(null); @@ -23,7 +25,7 @@ export const useControlsTimeout = ({ clearTimeout(controlsTimeoutRef.current); } - if (showControls && !isSliding && !episodeView) { + if (!disabled && showControls && !isSliding && !episodeView) { controlsTimeoutRef.current = setTimeout(() => { onHideControls(); }, timeout); @@ -37,18 +39,18 @@ export const useControlsTimeout = ({ clearTimeout(controlsTimeoutRef.current); } }; - }, [showControls, isSliding, episodeView, timeout, onHideControls]); + }, [showControls, isSliding, episodeView, timeout, onHideControls, disabled]); - const handleControlsInteraction = () => { - if (showControls) { - if (controlsTimeoutRef.current) { - clearTimeout(controlsTimeoutRef.current); - } - controlsTimeoutRef.current = setTimeout(() => { - onHideControls(); - }, timeout); + const handleControlsInteraction = useCallback(() => { + if (disabled || !showControls) return; + + if (controlsTimeoutRef.current) { + clearTimeout(controlsTimeoutRef.current); } - }; + controlsTimeoutRef.current = setTimeout(() => { + onHideControls(); + }, timeout); + }, [disabled, showControls, onHideControls, timeout]); return { handleControlsInteraction, diff --git a/components/vlc/VideoDebugInfo.tsx b/components/vlc/VideoDebugInfo.tsx deleted file mode 100644 index 40b74b6d..00000000 --- a/components/vlc/VideoDebugInfo.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import type React from "react"; -import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; -import { TouchableOpacity, View, type ViewProps } from "react-native"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; -import type { TrackInfo, VlcPlayerViewRef } from "@/modules/VlcPlayer.types"; -import { Text } from "../common/Text"; - -interface Props extends ViewProps { - playerRef: React.RefObject; -} - -export const VideoDebugInfo: React.FC = ({ playerRef, ...props }) => { - const [audioTracks, setAudioTracks] = useState(null); - const [subtitleTracks, setSubtitleTracks] = useState( - null, - ); - - useEffect(() => { - const fetchTracks = async () => { - if (playerRef.current) { - try { - const audio = await playerRef.current.getAudioTracks(); - const subtitles = await playerRef.current.getSubtitleTracks(); - setAudioTracks(audio); - setSubtitleTracks(subtitles); - } catch (error) { - console.log("[VideoDebugInfo] Failed to fetch tracks:", error); - } - } - }; - - fetchTracks(); - }, [playerRef]); - - const insets = useSafeAreaInsets(); - - const { t } = useTranslation(); - - return ( - - {t("player.playback_state")} - {t("player.audio_tracks")} - {audioTracks?.map((track, index) => ( - - {track.name} ({t("player.index")} {track.index}) - - ))} - {t("player.subtitles_tracks")} - {subtitleTracks?.map((track, index) => ( - - {track.name} ({t("player.index")} {track.index}) - - ))} - { - if (playerRef.current) { - playerRef.current - .getAudioTracks() - .then(setAudioTracks) - .catch((err) => { - console.log( - "[VideoDebugInfo] Failed to get audio tracks:", - err, - ); - }); - playerRef.current - .getSubtitleTracks() - .then(setSubtitleTracks) - .catch((err) => { - console.log( - "[VideoDebugInfo] Failed to get subtitle tracks:", - err, - ); - }); - } - }} - > - - {t("player.refresh_tracks")} - - - - ); -}; diff --git a/components/watchlists/WatchlistSheet.tsx b/components/watchlists/WatchlistSheet.tsx new file mode 100644 index 00000000..74764902 --- /dev/null +++ b/components/watchlists/WatchlistSheet.tsx @@ -0,0 +1,318 @@ +import { Ionicons } from "@expo/vector-icons"; +import { + BottomSheetBackdrop, + type BottomSheetBackdropProps, + BottomSheetModal, + BottomSheetView, +} from "@gorhom/bottom-sheet"; +import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; +import { useRouter } from "expo-router"; +import React, { + forwardRef, + useCallback, + useImperativeHandle, + useMemo, + useRef, +} from "react"; +import { useTranslation } from "react-i18next"; +import { + ActivityIndicator, + StyleSheet, + TouchableOpacity, + View, +} from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { Text } from "@/components/common/Text"; +import { + useAddToWatchlist, + useRemoveFromWatchlist, +} from "@/hooks/useWatchlistMutations"; +import { + useItemInWatchlists, + useMyWatchlistsQuery, +} from "@/hooks/useWatchlists"; +import type { StreamystatsWatchlist } from "@/utils/streamystats/types"; + +export interface WatchlistSheetRef { + open: (item: BaseItemDto) => void; + close: () => void; +} + +interface WatchlistRowProps { + watchlist: StreamystatsWatchlist; + isInWatchlist: boolean; + isCompatible: boolean; + onToggle: () => void; + isLoading: boolean; +} + +const WatchlistRow: React.FC = ({ + watchlist, + isInWatchlist, + isCompatible, + onToggle, + isLoading, +}) => { + const disabled = !isCompatible && !isInWatchlist; + + return ( + + + + + {watchlist.name} + + {watchlist.allowedItemType && ( + + + {watchlist.allowedItemType} + + + )} + + {watchlist.description && ( + + {watchlist.description} + + )} + + {watchlist.itemCount ?? 0} items + + + + {isLoading ? ( + + ) : isInWatchlist ? ( + + ) : isCompatible ? ( + + ) : ( + + )} + + + ); +}; + +interface WatchlistSheetContentProps { + item: BaseItemDto; + onClose: () => void; +} + +const WatchlistSheetContent: React.FC = ({ + item, + onClose, +}) => { + const { t } = useTranslation(); + const router = useRouter(); + const insets = useSafeAreaInsets(); + + const { data: myWatchlists, isLoading: watchlistsLoading } = + useMyWatchlistsQuery(); + const { data: watchlistsContainingItem, isLoading: checkingLoading } = + useItemInWatchlists(item.Id); + + const addToWatchlist = useAddToWatchlist(); + const removeFromWatchlist = useRemoveFromWatchlist(); + + const isLoading = watchlistsLoading || checkingLoading; + + // Sort watchlists: ones containing item first, then compatible ones, then incompatible + const sortedWatchlists = useMemo(() => { + if (!myWatchlists) return []; + + return [...myWatchlists].sort((a, b) => { + const aInWatchlist = watchlistsContainingItem?.includes(a.id) ?? false; + const bInWatchlist = watchlistsContainingItem?.includes(b.id) ?? false; + + const aCompatible = !a.allowedItemType || a.allowedItemType === item.Type; + const bCompatible = !b.allowedItemType || b.allowedItemType === item.Type; + + // Items in watchlist first + if (aInWatchlist && !bInWatchlist) return -1; + if (!aInWatchlist && bInWatchlist) return 1; + + // Then compatible items + if (aCompatible && !bCompatible) return -1; + if (!aCompatible && bCompatible) return 1; + + // Then alphabetically + return a.name.localeCompare(b.name); + }); + }, [myWatchlists, watchlistsContainingItem, item.Type]); + + const handleToggle = useCallback( + async (watchlist: StreamystatsWatchlist) => { + if (!item.Id) return; + + const isInWatchlist = watchlistsContainingItem?.includes(watchlist.id); + + if (isInWatchlist) { + await removeFromWatchlist.mutateAsync({ + watchlistId: watchlist.id, + itemId: item.Id, + watchlistName: watchlist.name, + }); + } else { + await addToWatchlist.mutateAsync({ + watchlistId: watchlist.id, + itemId: item.Id, + watchlistName: watchlist.name, + }); + } + }, + [item.Id, watchlistsContainingItem, addToWatchlist, removeFromWatchlist], + ); + + const handleCreateNew = useCallback(() => { + onClose(); + router.push("/(auth)/(tabs)/(watchlists)/create"); + }, [onClose, router]); + + const isItemCompatible = useCallback( + (watchlist: StreamystatsWatchlist) => { + if (!watchlist.allowedItemType) return true; + return watchlist.allowedItemType === item.Type; + }, + [item.Type], + ); + + if (isLoading) { + return ( + + + {t("watchlists.loading")} + + ); + } + + return ( + + {/* Header */} + + + {t("watchlists.select_watchlist")} + + + {item.Name} + + + + {/* Watchlist List */} + {sortedWatchlists.length === 0 ? ( + + + + {t("watchlists.empty_title")} + + + {t("watchlists.empty_description")} + + + ) : ( + + {sortedWatchlists.map((watchlist, index) => ( + + handleToggle(watchlist)} + isLoading={ + addToWatchlist.isPending || removeFromWatchlist.isPending + } + /> + {index < sortedWatchlists.length - 1 && ( + + )} + + ))} + + )} + + {/* Create New Button */} + + + + {t("watchlists.create_new")} + + + + ); +}; + +export const WatchlistSheet = forwardRef( + (_props, ref) => { + const bottomSheetModalRef = useRef(null); + const [currentItem, setCurrentItem] = React.useState( + null, + ); + const insets = useSafeAreaInsets(); + + useImperativeHandle(ref, () => ({ + open: (item: BaseItemDto) => { + setCurrentItem(item); + bottomSheetModalRef.current?.present(); + }, + close: () => { + bottomSheetModalRef.current?.dismiss(); + }, + })); + + const handleClose = useCallback(() => { + bottomSheetModalRef.current?.dismiss(); + }, []); + + const renderBackdrop = useCallback( + (props: BottomSheetBackdropProps) => ( + + ), + [], + ); + + return ( + + + {currentItem && ( + + )} + + + ); + }, +); diff --git a/constants/SubtitleConstants.ts b/constants/SubtitleConstants.ts deleted file mode 100644 index 7fc7a8e6..00000000 --- a/constants/SubtitleConstants.ts +++ /dev/null @@ -1,45 +0,0 @@ -export type VLCColor = - | "Black" - | "Gray" - | "Silver" - | "White" - | "Maroon" - | "Red" - | "Fuchsia" - | "Yellow" - | "Olive" - | "Green" - | "Teal" - | "Lime" - | "Purple" - | "Navy" - | "Blue" - | "Aqua"; - -export type OutlineThickness = "None" | "Thin" | "Normal" | "Thick"; - -export const VLC_COLORS: Record = { - Black: 0, - Gray: 8421504, - Silver: 12632256, - White: 16777215, - Maroon: 8388608, - Red: 16711680, - Fuchsia: 16711935, - Yellow: 16776960, - Olive: 8421376, - Green: 32768, - Teal: 32896, - Lime: 65280, - Purple: 8388736, - Navy: 128, - Blue: 255, - Aqua: 65535, -}; - -export const OUTLINE_THICKNESS: Record = { - None: 0, - Thin: 2, - Normal: 4, - Thick: 6, -}; diff --git a/constants/Values.ts b/constants/Values.ts index 4c3e4d81..5029d852 100644 --- a/constants/Values.ts +++ b/constants/Values.ts @@ -1,3 +1,6 @@ import { Platform } from "react-native"; export const TAB_HEIGHT = Platform.OS === "android" ? 58 : 74; + +// Matches `w-28` poster cards (approx 112px wide, 10/15 aspect ratio) + 2 lines of text. +export const POSTER_CAROUSEL_HEIGHT = 220; diff --git a/docs/jellyfin-openapi-stable.json b/docs/jellyfin-openapi-stable.json new file mode 100644 index 00000000..728a442b --- /dev/null +++ b/docs/jellyfin-openapi-stable.json @@ -0,0 +1,69435 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "Jellyfin API", + "version": "10.11.5", + "x-jellyfin-version": "10.11.5" + }, + "servers": [ + { + "url": "http://localhost" + } + ], + "paths": { + "/System/ActivityLog/Entries": { + "get": { + "tags": [ + "ActivityLog" + ], + "summary": "Gets activity log entries.", + "operationId": "GetLogEntries", + "parameters": [ + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minDate", + "in": "query", + "description": "Optional. The minimum date. Format = ISO.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "hasUserId", + "in": "query", + "description": "Optional. Filter log entries if it has user id, or not.", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Activity log returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ActivityLogEntryQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ActivityLogEntryQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ActivityLogEntryQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Auth/Keys": { + "get": { + "tags": [ + "ApiKey" + ], + "summary": "Get all keys.", + "operationId": "GetKeys", + "responses": { + "200": { + "description": "Api keys retrieved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationInfoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/AuthenticationInfoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/AuthenticationInfoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + }, + "post": { + "tags": [ + "ApiKey" + ], + "summary": "Create a new api key.", + "operationId": "CreateKey", + "parameters": [ + { + "name": "app", + "in": "query", + "description": "Name of the app using the authentication key.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Api key created." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Auth/Keys/{key}": { + "delete": { + "tags": [ + "ApiKey" + ], + "summary": "Remove an api key.", + "operationId": "RevokeKey", + "parameters": [ + { + "name": "key", + "in": "path", + "description": "The access token to delete.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Api key deleted." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Artists": { + "get": { + "tags": [ + "Artists" + ], + "summary": "Gets all artists from a given item, folder, or the entire library.", + "operationId": "GetArtists", + "parameters": [ + { + "name": "minCommunityRating", + "in": "query", + "description": "Optional filter by minimum community rating.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "searchTerm", + "in": "query", + "description": "Optional. Search term.", + "schema": { + "type": "string" + } + }, + { + "name": "parentId", + "in": "query", + "description": "Specify this to localize the search to a specific item or folder. Omit to use the root.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "excludeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "includeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "filters", + "in": "query", + "description": "Optional. Specify additional filters to apply.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFilter" + } + } + }, + { + "name": "isFavorite", + "in": "query", + "description": "Optional filter by items that are marked as favorite, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "mediaTypes", + "in": "query", + "description": "Optional filter by MediaType. Allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaType" + } + } + }, + { + "name": "genres", + "in": "query", + "description": "Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "genreIds", + "in": "query", + "description": "Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "officialRatings", + "in": "query", + "description": "Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "tags", + "in": "query", + "description": "Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "years", + "in": "query", + "description": "Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional, include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional, the max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "person", + "in": "query", + "description": "Optional. If specified, results will be filtered to include only those containing the specified person.", + "schema": { + "type": "string" + } + }, + { + "name": "personIds", + "in": "query", + "description": "Optional. If specified, results will be filtered to include only those containing the specified person ids.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "personTypes", + "in": "query", + "description": "Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "studios", + "in": "query", + "description": "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "studioIds", + "in": "query", + "description": "Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "nameStartsWithOrGreater", + "in": "query", + "description": "Optional filter by items whose name is sorted equally or greater than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "nameStartsWith", + "in": "query", + "description": "Optional filter by items whose name is sorted equally than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "nameLessThan", + "in": "query", + "description": "Optional filter by items whose name is equally or lesser than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Optional. Specify one or more sort orders, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemSortBy" + } + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Sort Order - Ascending,Descending.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SortOrder" + } + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional, include image information in output.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "enableTotalRecordCount", + "in": "query", + "description": "Total record count.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Artists returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Artists/{name}": { + "get": { + "tags": [ + "Artists" + ], + "summary": "Gets an artist by name.", + "operationId": "GetArtistByName", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Studio name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Artist returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Artists/AlbumArtists": { + "get": { + "tags": [ + "Artists" + ], + "summary": "Gets all album artists from a given item, folder, or the entire library.", + "operationId": "GetAlbumArtists", + "parameters": [ + { + "name": "minCommunityRating", + "in": "query", + "description": "Optional filter by minimum community rating.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "searchTerm", + "in": "query", + "description": "Optional. Search term.", + "schema": { + "type": "string" + } + }, + { + "name": "parentId", + "in": "query", + "description": "Specify this to localize the search to a specific item or folder. Omit to use the root.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "excludeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "includeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "filters", + "in": "query", + "description": "Optional. Specify additional filters to apply.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFilter" + } + } + }, + { + "name": "isFavorite", + "in": "query", + "description": "Optional filter by items that are marked as favorite, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "mediaTypes", + "in": "query", + "description": "Optional filter by MediaType. Allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaType" + } + } + }, + { + "name": "genres", + "in": "query", + "description": "Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "genreIds", + "in": "query", + "description": "Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "officialRatings", + "in": "query", + "description": "Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "tags", + "in": "query", + "description": "Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "years", + "in": "query", + "description": "Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional, include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional, the max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "person", + "in": "query", + "description": "Optional. If specified, results will be filtered to include only those containing the specified person.", + "schema": { + "type": "string" + } + }, + { + "name": "personIds", + "in": "query", + "description": "Optional. If specified, results will be filtered to include only those containing the specified person ids.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "personTypes", + "in": "query", + "description": "Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "studios", + "in": "query", + "description": "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "studioIds", + "in": "query", + "description": "Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "nameStartsWithOrGreater", + "in": "query", + "description": "Optional filter by items whose name is sorted equally or greater than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "nameStartsWith", + "in": "query", + "description": "Optional filter by items whose name is sorted equally than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "nameLessThan", + "in": "query", + "description": "Optional filter by items whose name is equally or lesser than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Optional. Specify one or more sort orders, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemSortBy" + } + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Sort Order - Ascending,Descending.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SortOrder" + } + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional, include image information in output.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "enableTotalRecordCount", + "in": "query", + "description": "Total record count.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Album artists returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Audio/{itemId}/stream": { + "get": { + "tags": [ + "Audio" + ], + "summary": "Gets an audio stream.", + "operationId": "GetAudioStream", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "container", + "in": "query", + "description": "The audio container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "static", + "in": "query", + "description": "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "params", + "in": "query", + "description": "The streaming parameters.", + "schema": { + "type": "string" + } + }, + { + "name": "tag", + "in": "query", + "description": "The tag.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceProfileId", + "in": "query", + "description": "Optional. The dlna device profile id to utilize.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "query", + "description": "The segment container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The segment length.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minSegments", + "in": "query", + "description": "The minimum number of segments.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. Specify an audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "enableAutoStreamCopy", + "in": "query", + "description": "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the video stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the audio stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean" + } + }, + { + "name": "audioSampleRate", + "in": "query", + "description": "Optional. Specify a specific audio sample rate, e.g. 44100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioChannels", + "in": "query", + "description": "Optional. Specify a specific number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. Specify a maximum number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "profile", + "in": "query", + "description": "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", + "schema": { + "type": "string" + } + }, + { + "name": "level", + "in": "query", + "description": "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", + "schema": { + "pattern": "-?[0-9]+(?:\\.[0-9]+)?", + "type": "string" + } + }, + { + "name": "framerate", + "in": "query", + "description": "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxFramerate", + "in": "query", + "description": "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "width", + "in": "query", + "description": "Optional. The fixed horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "Optional. The fixed vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoBitRate", + "in": "query", + "description": "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleMethod", + "in": "query", + "description": "Optional. Specify the subtitle delivery method.", + "schema": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ] + } + }, + { + "name": "maxRefFrames", + "in": "query", + "description": "Optional.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxVideoBitDepth", + "in": "query", + "description": "Optional. The maximum video bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "requireAvc", + "in": "query", + "description": "Optional. Whether to require avc.", + "schema": { + "type": "boolean" + } + }, + { + "name": "deInterlace", + "in": "query", + "description": "Optional. Whether to deinterlace the video.", + "schema": { + "type": "boolean" + } + }, + { + "name": "requireNonAnamorphic", + "in": "query", + "description": "Optional. Whether to require a non anamorphic stream.", + "schema": { + "type": "boolean" + } + }, + { + "name": "transcodingMaxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels to transcode.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "cpuCoreLimit", + "in": "query", + "description": "Optional. The limit of how many cpu cores to use.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "enableMpegtsM2TsMode", + "in": "query", + "description": "Optional. Whether to enable the MpegtsM2Ts mode.", + "schema": { + "type": "boolean" + } + }, + { + "name": "videoCodec", + "in": "query", + "description": "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "subtitleCodec", + "in": "query", + "description": "Optional. Specify a subtitle codec to encode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodeReasons", + "in": "query", + "description": "Optional. The transcoding reason.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoStreamIndex", + "in": "query", + "description": "Optional. The index of the video stream to use. If omitted the first video stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "context", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Dlna.EncodingContext.", + "schema": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ] + } + }, + { + "name": "streamOptions", + "in": "query", + "description": "Optional. The streaming options.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Optional. Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Audio stream returned.", + "content": { + "audio/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "head": { + "tags": [ + "Audio" + ], + "summary": "Gets an audio stream.", + "operationId": "HeadAudioStream", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "container", + "in": "query", + "description": "The audio container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "static", + "in": "query", + "description": "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "params", + "in": "query", + "description": "The streaming parameters.", + "schema": { + "type": "string" + } + }, + { + "name": "tag", + "in": "query", + "description": "The tag.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceProfileId", + "in": "query", + "description": "Optional. The dlna device profile id to utilize.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "query", + "description": "The segment container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The segment length.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minSegments", + "in": "query", + "description": "The minimum number of segments.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. Specify an audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "enableAutoStreamCopy", + "in": "query", + "description": "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the video stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the audio stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean" + } + }, + { + "name": "audioSampleRate", + "in": "query", + "description": "Optional. Specify a specific audio sample rate, e.g. 44100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioChannels", + "in": "query", + "description": "Optional. Specify a specific number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. Specify a maximum number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "profile", + "in": "query", + "description": "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", + "schema": { + "type": "string" + } + }, + { + "name": "level", + "in": "query", + "description": "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", + "schema": { + "pattern": "-?[0-9]+(?:\\.[0-9]+)?", + "type": "string" + } + }, + { + "name": "framerate", + "in": "query", + "description": "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxFramerate", + "in": "query", + "description": "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "width", + "in": "query", + "description": "Optional. The fixed horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "Optional. The fixed vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoBitRate", + "in": "query", + "description": "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleMethod", + "in": "query", + "description": "Optional. Specify the subtitle delivery method.", + "schema": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ] + } + }, + { + "name": "maxRefFrames", + "in": "query", + "description": "Optional.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxVideoBitDepth", + "in": "query", + "description": "Optional. The maximum video bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "requireAvc", + "in": "query", + "description": "Optional. Whether to require avc.", + "schema": { + "type": "boolean" + } + }, + { + "name": "deInterlace", + "in": "query", + "description": "Optional. Whether to deinterlace the video.", + "schema": { + "type": "boolean" + } + }, + { + "name": "requireNonAnamorphic", + "in": "query", + "description": "Optional. Whether to require a non anamorphic stream.", + "schema": { + "type": "boolean" + } + }, + { + "name": "transcodingMaxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels to transcode.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "cpuCoreLimit", + "in": "query", + "description": "Optional. The limit of how many cpu cores to use.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "enableMpegtsM2TsMode", + "in": "query", + "description": "Optional. Whether to enable the MpegtsM2Ts mode.", + "schema": { + "type": "boolean" + } + }, + { + "name": "videoCodec", + "in": "query", + "description": "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "subtitleCodec", + "in": "query", + "description": "Optional. Specify a subtitle codec to encode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodeReasons", + "in": "query", + "description": "Optional. The transcoding reason.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoStreamIndex", + "in": "query", + "description": "Optional. The index of the video stream to use. If omitted the first video stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "context", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Dlna.EncodingContext.", + "schema": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ] + } + }, + { + "name": "streamOptions", + "in": "query", + "description": "Optional. The streaming options.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Optional. Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Audio stream returned.", + "content": { + "audio/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Audio/{itemId}/stream.{container}": { + "get": { + "tags": [ + "Audio" + ], + "summary": "Gets an audio stream.", + "operationId": "GetAudioStreamByContainer", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "container", + "in": "path", + "description": "The audio container.", + "required": true, + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "static", + "in": "query", + "description": "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "params", + "in": "query", + "description": "The streaming parameters.", + "schema": { + "type": "string" + } + }, + { + "name": "tag", + "in": "query", + "description": "The tag.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceProfileId", + "in": "query", + "description": "Optional. The dlna device profile id to utilize.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "query", + "description": "The segment container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The segment length.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minSegments", + "in": "query", + "description": "The minimum number of segments.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. Specify an audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "enableAutoStreamCopy", + "in": "query", + "description": "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the video stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the audio stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean" + } + }, + { + "name": "audioSampleRate", + "in": "query", + "description": "Optional. Specify a specific audio sample rate, e.g. 44100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioChannels", + "in": "query", + "description": "Optional. Specify a specific number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. Specify a maximum number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "profile", + "in": "query", + "description": "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", + "schema": { + "type": "string" + } + }, + { + "name": "level", + "in": "query", + "description": "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", + "schema": { + "pattern": "-?[0-9]+(?:\\.[0-9]+)?", + "type": "string" + } + }, + { + "name": "framerate", + "in": "query", + "description": "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxFramerate", + "in": "query", + "description": "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "width", + "in": "query", + "description": "Optional. The fixed horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "Optional. The fixed vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoBitRate", + "in": "query", + "description": "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleMethod", + "in": "query", + "description": "Optional. Specify the subtitle delivery method.", + "schema": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ] + } + }, + { + "name": "maxRefFrames", + "in": "query", + "description": "Optional.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxVideoBitDepth", + "in": "query", + "description": "Optional. The maximum video bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "requireAvc", + "in": "query", + "description": "Optional. Whether to require avc.", + "schema": { + "type": "boolean" + } + }, + { + "name": "deInterlace", + "in": "query", + "description": "Optional. Whether to deinterlace the video.", + "schema": { + "type": "boolean" + } + }, + { + "name": "requireNonAnamorphic", + "in": "query", + "description": "Optional. Whether to require a non anamorphic stream.", + "schema": { + "type": "boolean" + } + }, + { + "name": "transcodingMaxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels to transcode.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "cpuCoreLimit", + "in": "query", + "description": "Optional. The limit of how many cpu cores to use.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "enableMpegtsM2TsMode", + "in": "query", + "description": "Optional. Whether to enable the MpegtsM2Ts mode.", + "schema": { + "type": "boolean" + } + }, + { + "name": "videoCodec", + "in": "query", + "description": "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "subtitleCodec", + "in": "query", + "description": "Optional. Specify a subtitle codec to encode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodeReasons", + "in": "query", + "description": "Optional. The transcoding reason.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoStreamIndex", + "in": "query", + "description": "Optional. The index of the video stream to use. If omitted the first video stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "context", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Dlna.EncodingContext.", + "schema": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ] + } + }, + { + "name": "streamOptions", + "in": "query", + "description": "Optional. The streaming options.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Optional. Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Audio stream returned.", + "content": { + "audio/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "head": { + "tags": [ + "Audio" + ], + "summary": "Gets an audio stream.", + "operationId": "HeadAudioStreamByContainer", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "container", + "in": "path", + "description": "The audio container.", + "required": true, + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "static", + "in": "query", + "description": "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "params", + "in": "query", + "description": "The streaming parameters.", + "schema": { + "type": "string" + } + }, + { + "name": "tag", + "in": "query", + "description": "The tag.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceProfileId", + "in": "query", + "description": "Optional. The dlna device profile id to utilize.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "query", + "description": "The segment container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The segment length.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minSegments", + "in": "query", + "description": "The minimum number of segments.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. Specify an audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "enableAutoStreamCopy", + "in": "query", + "description": "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the video stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the audio stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean" + } + }, + { + "name": "audioSampleRate", + "in": "query", + "description": "Optional. Specify a specific audio sample rate, e.g. 44100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioChannels", + "in": "query", + "description": "Optional. Specify a specific number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. Specify a maximum number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "profile", + "in": "query", + "description": "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", + "schema": { + "type": "string" + } + }, + { + "name": "level", + "in": "query", + "description": "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", + "schema": { + "pattern": "-?[0-9]+(?:\\.[0-9]+)?", + "type": "string" + } + }, + { + "name": "framerate", + "in": "query", + "description": "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxFramerate", + "in": "query", + "description": "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "width", + "in": "query", + "description": "Optional. The fixed horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "Optional. The fixed vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoBitRate", + "in": "query", + "description": "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleMethod", + "in": "query", + "description": "Optional. Specify the subtitle delivery method.", + "schema": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ] + } + }, + { + "name": "maxRefFrames", + "in": "query", + "description": "Optional.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxVideoBitDepth", + "in": "query", + "description": "Optional. The maximum video bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "requireAvc", + "in": "query", + "description": "Optional. Whether to require avc.", + "schema": { + "type": "boolean" + } + }, + { + "name": "deInterlace", + "in": "query", + "description": "Optional. Whether to deinterlace the video.", + "schema": { + "type": "boolean" + } + }, + { + "name": "requireNonAnamorphic", + "in": "query", + "description": "Optional. Whether to require a non anamorphic stream.", + "schema": { + "type": "boolean" + } + }, + { + "name": "transcodingMaxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels to transcode.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "cpuCoreLimit", + "in": "query", + "description": "Optional. The limit of how many cpu cores to use.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "enableMpegtsM2TsMode", + "in": "query", + "description": "Optional. Whether to enable the MpegtsM2Ts mode.", + "schema": { + "type": "boolean" + } + }, + { + "name": "videoCodec", + "in": "query", + "description": "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "subtitleCodec", + "in": "query", + "description": "Optional. Specify a subtitle codec to encode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodeReasons", + "in": "query", + "description": "Optional. The transcoding reason.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoStreamIndex", + "in": "query", + "description": "Optional. The index of the video stream to use. If omitted the first video stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "context", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Dlna.EncodingContext.", + "schema": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ] + } + }, + { + "name": "streamOptions", + "in": "query", + "description": "Optional. The streaming options.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Optional. Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Audio stream returned.", + "content": { + "audio/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Backup": { + "get": { + "tags": [ + "Backup" + ], + "summary": "Gets a list of all currently present backups in the backup directory.", + "operationId": "ListBackups", + "responses": { + "200": { + "description": "Backups available.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BackupManifestDto" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BackupManifestDto" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BackupManifestDto" + } + } + } + } + }, + "403": { + "description": "User does not have permission to retrieve information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Backup/Create": { + "post": { + "tags": [ + "Backup" + ], + "summary": "Creates a new Backup.", + "operationId": "CreateBackup", + "requestBody": { + "description": "The backup options.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BackupOptionsDto" + } + ], + "description": "Defines the optional contents of the backup archive." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BackupOptionsDto" + } + ], + "description": "Defines the optional contents of the backup archive." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BackupOptionsDto" + } + ], + "description": "Defines the optional contents of the backup archive." + } + } + } + }, + "responses": { + "200": { + "description": "Backup created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BackupManifestDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BackupManifestDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BackupManifestDto" + } + } + } + }, + "403": { + "description": "User does not have permission to retrieve information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Backup/Manifest": { + "get": { + "tags": [ + "Backup" + ], + "summary": "Gets the descriptor from an existing archive is present.", + "operationId": "GetBackup", + "parameters": [ + { + "name": "path", + "in": "query", + "description": "The data to start a restore process.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Backup archive manifest.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BackupManifestDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BackupManifestDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BackupManifestDto" + } + } + } + }, + "204": { + "description": "Not a valid jellyfin Archive." + }, + "404": { + "description": "Not a valid path.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "User does not have permission to retrieve information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Backup/Restore": { + "post": { + "tags": [ + "Backup" + ], + "summary": "Restores to a backup by restarting the server and applying the backup.", + "operationId": "StartRestoreBackup", + "requestBody": { + "description": "The data to start a restore process.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BackupRestoreRequestDto" + } + ], + "description": "Defines properties used to start a restore process." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BackupRestoreRequestDto" + } + ], + "description": "Defines properties used to start a restore process." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BackupRestoreRequestDto" + } + ], + "description": "Defines properties used to start a restore process." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Backup restore started." + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "User does not have permission to retrieve information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Branding/Configuration": { + "get": { + "tags": [ + "Branding" + ], + "summary": "Gets branding configuration.", + "operationId": "GetBrandingOptions", + "responses": { + "200": { + "description": "Branding configuration returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BrandingOptionsDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BrandingOptionsDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BrandingOptionsDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Branding/Css": { + "get": { + "tags": [ + "Branding" + ], + "summary": "Gets branding css.", + "operationId": "GetBrandingCss", + "responses": { + "200": { + "description": "Branding css returned.", + "content": { + "text/css": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "string" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "string" + } + } + } + }, + "204": { + "description": "No branding css configured." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Branding/Css.css": { + "get": { + "tags": [ + "Branding" + ], + "summary": "Gets branding css.", + "operationId": "GetBrandingCss_2", + "responses": { + "200": { + "description": "Branding css returned.", + "content": { + "text/css": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "string" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "string" + } + } + } + }, + "204": { + "description": "No branding css configured." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Channels": { + "get": { + "tags": [ + "Channels" + ], + "summary": "Gets available channels.", + "operationId": "GetChannels", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User Id to filter by. Use System.Guid.Empty to not filter by user.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "supportsLatestItems", + "in": "query", + "description": "Optional. Filter by channels that support getting latest items.", + "schema": { + "type": "boolean" + } + }, + { + "name": "supportsMediaDeletion", + "in": "query", + "description": "Optional. Filter by channels that support media deletion.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isFavorite", + "in": "query", + "description": "Optional. Filter by channels that are favorite.", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Channels returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Channels/{channelId}/Features": { + "get": { + "tags": [ + "Channels" + ], + "summary": "Get channel features.", + "operationId": "GetChannelFeatures", + "parameters": [ + { + "name": "channelId", + "in": "path", + "description": "Channel id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Channel features returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelFeatures" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ChannelFeatures" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ChannelFeatures" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Channels/{channelId}/Items": { + "get": { + "tags": [ + "Channels" + ], + "summary": "Get channel items.", + "operationId": "GetChannelItems", + "parameters": [ + { + "name": "channelId", + "in": "path", + "description": "Channel Id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "folderId", + "in": "query", + "description": "Optional. Folder Id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. User Id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Optional. Sort Order - Ascending,Descending.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SortOrder" + } + } + }, + { + "name": "filters", + "in": "query", + "description": "Optional. Specify additional filters to apply.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFilter" + } + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemSortBy" + } + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + } + ], + "responses": { + "200": { + "description": "Channel items returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Channels/Features": { + "get": { + "tags": [ + "Channels" + ], + "summary": "Get all channel features.", + "operationId": "GetAllChannelFeatures", + "responses": { + "200": { + "description": "All channel features returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChannelFeatures" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChannelFeatures" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChannelFeatures" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Channels/Items/Latest": { + "get": { + "tags": [ + "Channels" + ], + "summary": "Gets latest channel items.", + "operationId": "GetLatestChannelItems", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "Optional. User Id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "filters", + "in": "query", + "description": "Optional. Specify additional filters to apply.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFilter" + } + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "channelIds", + "in": "query", + "description": "Optional. Specify one or more channel id's, comma delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + ], + "responses": { + "200": { + "description": "Latest channel items returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/ClientLog/Document": { + "post": { + "tags": [ + "ClientLog" + ], + "summary": "Upload a document.", + "operationId": "LogFile", + "requestBody": { + "content": { + "text/plain": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "200": { + "description": "Document saved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ClientLogDocumentResponseDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ClientLogDocumentResponseDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ClientLogDocumentResponseDto" + } + } + } + }, + "403": { + "description": "Event logging disabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "413": { + "description": "Upload size too large.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Collections": { + "post": { + "tags": [ + "Collection" + ], + "summary": "Creates a new collection.", + "operationId": "CreateCollection", + "parameters": [ + { + "name": "name", + "in": "query", + "description": "The name of the collection.", + "schema": { + "type": "string" + } + }, + { + "name": "ids", + "in": "query", + "description": "Item Ids to add to the collection.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "parentId", + "in": "query", + "description": "Optional. Create the collection within a specific folder.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "isLocked", + "in": "query", + "description": "Whether or not to lock the new collection.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Collection created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CollectionCreationResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/CollectionCreationResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/CollectionCreationResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "CollectionManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Collections/{collectionId}/Items": { + "post": { + "tags": [ + "Collection" + ], + "summary": "Adds items to a collection.", + "operationId": "AddToCollection", + "parameters": [ + { + "name": "collectionId", + "in": "path", + "description": "The collection id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "ids", + "in": "query", + "description": "Item ids, comma delimited.", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + ], + "responses": { + "204": { + "description": "Items added to collection." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "CollectionManagement", + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "Collection" + ], + "summary": "Removes items from a collection.", + "operationId": "RemoveFromCollection", + "parameters": [ + { + "name": "collectionId", + "in": "path", + "description": "The collection id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "ids", + "in": "query", + "description": "Item ids, comma delimited.", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + ], + "responses": { + "204": { + "description": "Items removed from collection." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "CollectionManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/System/Configuration": { + "get": { + "tags": [ + "Configuration" + ], + "summary": "Gets application configuration.", + "operationId": "GetConfiguration", + "responses": { + "200": { + "description": "Application configuration returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ServerConfiguration" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ServerConfiguration" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ServerConfiguration" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "post": { + "tags": [ + "Configuration" + ], + "summary": "Updates application configuration.", + "operationId": "UpdateConfiguration", + "requestBody": { + "description": "Configuration.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ServerConfiguration" + } + ], + "description": "Represents the server configuration." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ServerConfiguration" + } + ], + "description": "Represents the server configuration." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ServerConfiguration" + } + ], + "description": "Represents the server configuration." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Configuration updated." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation", + "DefaultAuthorization" + ] + } + ] + } + }, + "/System/Configuration/{key}": { + "get": { + "tags": [ + "Configuration" + ], + "summary": "Gets a named configuration.", + "operationId": "GetNamedConfiguration", + "parameters": [ + { + "name": "key", + "in": "path", + "description": "Configuration key.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Configuration returned.", + "content": { + "application/json": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "post": { + "tags": [ + "Configuration" + ], + "summary": "Updates named configuration.", + "operationId": "UpdateNamedConfiguration", + "parameters": [ + { + "name": "key", + "in": "path", + "description": "Configuration key.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Configuration.", + "content": { + "application/json": { + "schema": { } + }, + "text/json": { + "schema": { } + }, + "application/*+json": { + "schema": { } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Named configuration updated." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation", + "DefaultAuthorization" + ] + } + ] + } + }, + "/System/Configuration/Branding": { + "post": { + "tags": [ + "Configuration" + ], + "summary": "Updates branding configuration.", + "operationId": "UpdateBrandingConfiguration", + "requestBody": { + "description": "Branding configuration.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BrandingOptionsDto" + } + ], + "description": "The branding options DTO for API use.\r\nThis DTO excludes SplashscreenLocation to prevent it from being updated via API." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BrandingOptionsDto" + } + ], + "description": "The branding options DTO for API use.\r\nThis DTO excludes SplashscreenLocation to prevent it from being updated via API." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BrandingOptionsDto" + } + ], + "description": "The branding options DTO for API use.\r\nThis DTO excludes SplashscreenLocation to prevent it from being updated via API." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Branding configuration updated." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation", + "DefaultAuthorization" + ] + } + ] + } + }, + "/System/Configuration/MetadataOptions/Default": { + "get": { + "tags": [ + "Configuration" + ], + "summary": "Gets a default MetadataOptions object.", + "operationId": "GetDefaultMetadataOptions", + "responses": { + "200": { + "description": "Metadata options returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MetadataOptions" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/MetadataOptions" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/MetadataOptions" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation", + "DefaultAuthorization" + ] + } + ] + } + }, + "/web/ConfigurationPage": { + "get": { + "tags": [ + "Dashboard" + ], + "summary": "Gets a dashboard configuration page.", + "operationId": "GetDashboardConfigurationPage", + "parameters": [ + { + "name": "name", + "in": "query", + "description": "The name of the page.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "ConfigurationPage returned.", + "content": { + "text/html": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "application/x-javascript": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Plugin configuration page not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/web/ConfigurationPages": { + "get": { + "tags": [ + "Dashboard" + ], + "summary": "Gets the configuration pages.", + "operationId": "GetConfigurationPages", + "parameters": [ + { + "name": "enableInMainMenu", + "in": "query", + "description": "Whether to enable in the main menu.", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "ConfigurationPages returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConfigurationPageInfo" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConfigurationPageInfo" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ConfigurationPageInfo" + } + } + } + } + }, + "404": { + "description": "Server still loading.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Devices": { + "get": { + "tags": [ + "Devices" + ], + "summary": "Get Devices.", + "operationId": "GetDevices", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "Gets or sets the user identifier.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Devices retrieved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceInfoDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/DeviceInfoDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/DeviceInfoDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + }, + "delete": { + "tags": [ + "Devices" + ], + "summary": "Deletes a device.", + "operationId": "DeleteDevice", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "Device Id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Device deleted." + }, + "404": { + "description": "Device not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Devices/Info": { + "get": { + "tags": [ + "Devices" + ], + "summary": "Get info for a device.", + "operationId": "GetDeviceInfo", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "Device Id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Device info retrieved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceInfoDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/DeviceInfoDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/DeviceInfoDto" + } + } + } + }, + "404": { + "description": "Device not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Devices/Options": { + "get": { + "tags": [ + "Devices" + ], + "summary": "Get options for a device.", + "operationId": "GetDeviceOptions", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "Device Id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Device options retrieved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DeviceOptionsDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/DeviceOptionsDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/DeviceOptionsDto" + } + } + } + }, + "404": { + "description": "Device not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + }, + "post": { + "tags": [ + "Devices" + ], + "summary": "Update device options.", + "operationId": "UpdateDeviceOptions", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "Device Id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Device Options.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/DeviceOptionsDto" + } + ], + "description": "A dto representing custom options for a device." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/DeviceOptionsDto" + } + ], + "description": "A dto representing custom options for a device." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/DeviceOptionsDto" + } + ], + "description": "A dto representing custom options for a device." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Device options updated." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/DisplayPreferences/{displayPreferencesId}": { + "get": { + "tags": [ + "DisplayPreferences" + ], + "summary": "Get Display Preferences.", + "operationId": "GetDisplayPreferences", + "parameters": [ + { + "name": "displayPreferencesId", + "in": "path", + "description": "Display preferences id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "client", + "in": "query", + "description": "Client.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Display preferences retrieved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DisplayPreferencesDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/DisplayPreferencesDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/DisplayPreferencesDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "post": { + "tags": [ + "DisplayPreferences" + ], + "summary": "Update Display Preferences.", + "operationId": "UpdateDisplayPreferences", + "parameters": [ + { + "name": "displayPreferencesId", + "in": "path", + "description": "Display preferences id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "query", + "description": "User Id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "client", + "in": "query", + "description": "Client.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "New Display Preferences object.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/DisplayPreferencesDto" + } + ], + "description": "Defines the display preferences for any item that supports them (usually Folders)." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/DisplayPreferencesDto" + } + ], + "description": "Defines the display preferences for any item that supports them (usually Folders)." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/DisplayPreferencesDto" + } + ], + "description": "Defines the display preferences for any item that supports them (usually Folders)." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Display preferences updated." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Audio/{itemId}/hls1/{playlistId}/{segmentId}.{container}": { + "get": { + "tags": [ + "DynamicHls" + ], + "summary": "Gets a video stream using HTTP live streaming.", + "operationId": "GetHlsAudioSegment", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "playlistId", + "in": "path", + "description": "The playlist id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "segmentId", + "in": "path", + "description": "The segment id.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "container", + "in": "path", + "description": "The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv.", + "required": true, + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "runtimeTicks", + "in": "query", + "description": "The position of the requested segment in ticks.", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "actualSegmentLengthTicks", + "in": "query", + "description": "The length of the requested segment in ticks.", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "static", + "in": "query", + "description": "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "params", + "in": "query", + "description": "The streaming parameters.", + "schema": { + "type": "string" + } + }, + { + "name": "tag", + "in": "query", + "description": "The tag.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceProfileId", + "in": "query", + "description": "Optional. The dlna device profile id to utilize.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "query", + "description": "The segment container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The segment length.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minSegments", + "in": "query", + "description": "The minimum number of segments.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. Specify an audio codec to encode to, e.g. mp3.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "enableAutoStreamCopy", + "in": "query", + "description": "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the video stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the audio stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean" + } + }, + { + "name": "audioSampleRate", + "in": "query", + "description": "Optional. Specify a specific audio sample rate, e.g. 44100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxStreamingBitrate", + "in": "query", + "description": "Optional. The maximum streaming bitrate.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioChannels", + "in": "query", + "description": "Optional. Specify a specific number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. Specify a maximum number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "profile", + "in": "query", + "description": "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", + "schema": { + "type": "string" + } + }, + { + "name": "level", + "in": "query", + "description": "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", + "schema": { + "pattern": "-?[0-9]+(?:\\.[0-9]+)?", + "type": "string" + } + }, + { + "name": "framerate", + "in": "query", + "description": "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxFramerate", + "in": "query", + "description": "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "width", + "in": "query", + "description": "Optional. The fixed horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "Optional. The fixed vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoBitRate", + "in": "query", + "description": "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleMethod", + "in": "query", + "description": "Optional. Specify the subtitle delivery method.", + "schema": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ] + } + }, + { + "name": "maxRefFrames", + "in": "query", + "description": "Optional.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxVideoBitDepth", + "in": "query", + "description": "Optional. The maximum video bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "requireAvc", + "in": "query", + "description": "Optional. Whether to require avc.", + "schema": { + "type": "boolean" + } + }, + { + "name": "deInterlace", + "in": "query", + "description": "Optional. Whether to deinterlace the video.", + "schema": { + "type": "boolean" + } + }, + { + "name": "requireNonAnamorphic", + "in": "query", + "description": "Optional. Whether to require a non anamorphic stream.", + "schema": { + "type": "boolean" + } + }, + { + "name": "transcodingMaxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels to transcode.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "cpuCoreLimit", + "in": "query", + "description": "Optional. The limit of how many cpu cores to use.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "enableMpegtsM2TsMode", + "in": "query", + "description": "Optional. Whether to enable the MpegtsM2Ts mode.", + "schema": { + "type": "boolean" + } + }, + { + "name": "videoCodec", + "in": "query", + "description": "Optional. Specify a video codec to encode to, e.g. h264.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "subtitleCodec", + "in": "query", + "description": "Optional. Specify a subtitle codec to encode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodeReasons", + "in": "query", + "description": "Optional. The transcoding reason.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoStreamIndex", + "in": "query", + "description": "Optional. The index of the video stream to use. If omitted the first video stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "context", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Dlna.EncodingContext.", + "schema": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ] + } + }, + { + "name": "streamOptions", + "in": "query", + "description": "Optional. The streaming options.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Optional. Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Video stream returned.", + "content": { + "audio/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Audio/{itemId}/main.m3u8": { + "get": { + "tags": [ + "DynamicHls" + ], + "summary": "Gets an audio stream using HTTP live streaming.", + "operationId": "GetVariantHlsAudioPlaylist", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "static", + "in": "query", + "description": "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "params", + "in": "query", + "description": "The streaming parameters.", + "schema": { + "type": "string" + } + }, + { + "name": "tag", + "in": "query", + "description": "The tag.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceProfileId", + "in": "query", + "description": "Optional. The dlna device profile id to utilize.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "query", + "description": "The segment container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The segment length.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minSegments", + "in": "query", + "description": "The minimum number of segments.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. Specify an audio codec to encode to, e.g. mp3.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "enableAutoStreamCopy", + "in": "query", + "description": "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the video stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the audio stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean" + } + }, + { + "name": "audioSampleRate", + "in": "query", + "description": "Optional. Specify a specific audio sample rate, e.g. 44100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxStreamingBitrate", + "in": "query", + "description": "Optional. The maximum streaming bitrate.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioChannels", + "in": "query", + "description": "Optional. Specify a specific number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. Specify a maximum number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "profile", + "in": "query", + "description": "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", + "schema": { + "type": "string" + } + }, + { + "name": "level", + "in": "query", + "description": "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", + "schema": { + "pattern": "-?[0-9]+(?:\\.[0-9]+)?", + "type": "string" + } + }, + { + "name": "framerate", + "in": "query", + "description": "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxFramerate", + "in": "query", + "description": "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "width", + "in": "query", + "description": "Optional. The fixed horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "Optional. The fixed vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoBitRate", + "in": "query", + "description": "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleMethod", + "in": "query", + "description": "Optional. Specify the subtitle delivery method.", + "schema": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ] + } + }, + { + "name": "maxRefFrames", + "in": "query", + "description": "Optional.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxVideoBitDepth", + "in": "query", + "description": "Optional. The maximum video bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "requireAvc", + "in": "query", + "description": "Optional. Whether to require avc.", + "schema": { + "type": "boolean" + } + }, + { + "name": "deInterlace", + "in": "query", + "description": "Optional. Whether to deinterlace the video.", + "schema": { + "type": "boolean" + } + }, + { + "name": "requireNonAnamorphic", + "in": "query", + "description": "Optional. Whether to require a non anamorphic stream.", + "schema": { + "type": "boolean" + } + }, + { + "name": "transcodingMaxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels to transcode.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "cpuCoreLimit", + "in": "query", + "description": "Optional. The limit of how many cpu cores to use.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "enableMpegtsM2TsMode", + "in": "query", + "description": "Optional. Whether to enable the MpegtsM2Ts mode.", + "schema": { + "type": "boolean" + } + }, + { + "name": "videoCodec", + "in": "query", + "description": "Optional. Specify a video codec to encode to, e.g. h264.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "subtitleCodec", + "in": "query", + "description": "Optional. Specify a subtitle codec to encode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodeReasons", + "in": "query", + "description": "Optional. The transcoding reason.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoStreamIndex", + "in": "query", + "description": "Optional. The index of the video stream to use. If omitted the first video stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "context", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Dlna.EncodingContext.", + "schema": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ] + } + }, + { + "name": "streamOptions", + "in": "query", + "description": "Optional. The streaming options.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Optional. Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Audio stream returned.", + "content": { + "application/x-mpegURL": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Audio/{itemId}/master.m3u8": { + "get": { + "tags": [ + "DynamicHls" + ], + "summary": "Gets an audio hls playlist stream.", + "operationId": "GetMasterHlsAudioPlaylist", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "static", + "in": "query", + "description": "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "params", + "in": "query", + "description": "The streaming parameters.", + "schema": { + "type": "string" + } + }, + { + "name": "tag", + "in": "query", + "description": "The tag.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceProfileId", + "in": "query", + "description": "Optional. The dlna device profile id to utilize.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "query", + "description": "The segment container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The segment length.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minSegments", + "in": "query", + "description": "The minimum number of segments.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. Specify an audio codec to encode to, e.g. mp3.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "enableAutoStreamCopy", + "in": "query", + "description": "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the video stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the audio stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean" + } + }, + { + "name": "audioSampleRate", + "in": "query", + "description": "Optional. Specify a specific audio sample rate, e.g. 44100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxStreamingBitrate", + "in": "query", + "description": "Optional. The maximum streaming bitrate.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioChannels", + "in": "query", + "description": "Optional. Specify a specific number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. Specify a maximum number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "profile", + "in": "query", + "description": "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", + "schema": { + "type": "string" + } + }, + { + "name": "level", + "in": "query", + "description": "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", + "schema": { + "pattern": "-?[0-9]+(?:\\.[0-9]+)?", + "type": "string" + } + }, + { + "name": "framerate", + "in": "query", + "description": "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxFramerate", + "in": "query", + "description": "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "width", + "in": "query", + "description": "Optional. The fixed horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "Optional. The fixed vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoBitRate", + "in": "query", + "description": "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleMethod", + "in": "query", + "description": "Optional. Specify the subtitle delivery method.", + "schema": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ] + } + }, + { + "name": "maxRefFrames", + "in": "query", + "description": "Optional.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxVideoBitDepth", + "in": "query", + "description": "Optional. The maximum video bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "requireAvc", + "in": "query", + "description": "Optional. Whether to require avc.", + "schema": { + "type": "boolean" + } + }, + { + "name": "deInterlace", + "in": "query", + "description": "Optional. Whether to deinterlace the video.", + "schema": { + "type": "boolean" + } + }, + { + "name": "requireNonAnamorphic", + "in": "query", + "description": "Optional. Whether to require a non anamorphic stream.", + "schema": { + "type": "boolean" + } + }, + { + "name": "transcodingMaxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels to transcode.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "cpuCoreLimit", + "in": "query", + "description": "Optional. The limit of how many cpu cores to use.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "enableMpegtsM2TsMode", + "in": "query", + "description": "Optional. Whether to enable the MpegtsM2Ts mode.", + "schema": { + "type": "boolean" + } + }, + { + "name": "videoCodec", + "in": "query", + "description": "Optional. Specify a video codec to encode to, e.g. h264.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "subtitleCodec", + "in": "query", + "description": "Optional. Specify a subtitle codec to encode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodeReasons", + "in": "query", + "description": "Optional. The transcoding reason.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoStreamIndex", + "in": "query", + "description": "Optional. The index of the video stream to use. If omitted the first video stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "context", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Dlna.EncodingContext.", + "schema": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ] + } + }, + { + "name": "streamOptions", + "in": "query", + "description": "Optional. The streaming options.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + { + "name": "enableAdaptiveBitrateStreaming", + "in": "query", + "description": "Enable adaptive bitrate streaming.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Optional. Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Audio stream returned.", + "content": { + "application/x-mpegURL": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "head": { + "tags": [ + "DynamicHls" + ], + "summary": "Gets an audio hls playlist stream.", + "operationId": "HeadMasterHlsAudioPlaylist", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "static", + "in": "query", + "description": "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "params", + "in": "query", + "description": "The streaming parameters.", + "schema": { + "type": "string" + } + }, + { + "name": "tag", + "in": "query", + "description": "The tag.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceProfileId", + "in": "query", + "description": "Optional. The dlna device profile id to utilize.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "query", + "description": "The segment container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The segment length.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minSegments", + "in": "query", + "description": "The minimum number of segments.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. Specify an audio codec to encode to, e.g. mp3.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "enableAutoStreamCopy", + "in": "query", + "description": "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the video stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the audio stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean" + } + }, + { + "name": "audioSampleRate", + "in": "query", + "description": "Optional. Specify a specific audio sample rate, e.g. 44100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxStreamingBitrate", + "in": "query", + "description": "Optional. The maximum streaming bitrate.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioChannels", + "in": "query", + "description": "Optional. Specify a specific number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. Specify a maximum number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "profile", + "in": "query", + "description": "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", + "schema": { + "type": "string" + } + }, + { + "name": "level", + "in": "query", + "description": "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", + "schema": { + "pattern": "-?[0-9]+(?:\\.[0-9]+)?", + "type": "string" + } + }, + { + "name": "framerate", + "in": "query", + "description": "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxFramerate", + "in": "query", + "description": "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "width", + "in": "query", + "description": "Optional. The fixed horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "Optional. The fixed vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoBitRate", + "in": "query", + "description": "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleMethod", + "in": "query", + "description": "Optional. Specify the subtitle delivery method.", + "schema": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ] + } + }, + { + "name": "maxRefFrames", + "in": "query", + "description": "Optional.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxVideoBitDepth", + "in": "query", + "description": "Optional. The maximum video bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "requireAvc", + "in": "query", + "description": "Optional. Whether to require avc.", + "schema": { + "type": "boolean" + } + }, + { + "name": "deInterlace", + "in": "query", + "description": "Optional. Whether to deinterlace the video.", + "schema": { + "type": "boolean" + } + }, + { + "name": "requireNonAnamorphic", + "in": "query", + "description": "Optional. Whether to require a non anamorphic stream.", + "schema": { + "type": "boolean" + } + }, + { + "name": "transcodingMaxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels to transcode.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "cpuCoreLimit", + "in": "query", + "description": "Optional. The limit of how many cpu cores to use.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "enableMpegtsM2TsMode", + "in": "query", + "description": "Optional. Whether to enable the MpegtsM2Ts mode.", + "schema": { + "type": "boolean" + } + }, + { + "name": "videoCodec", + "in": "query", + "description": "Optional. Specify a video codec to encode to, e.g. h264.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "subtitleCodec", + "in": "query", + "description": "Optional. Specify a subtitle codec to encode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodeReasons", + "in": "query", + "description": "Optional. The transcoding reason.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoStreamIndex", + "in": "query", + "description": "Optional. The index of the video stream to use. If omitted the first video stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "context", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Dlna.EncodingContext.", + "schema": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ] + } + }, + { + "name": "streamOptions", + "in": "query", + "description": "Optional. The streaming options.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + { + "name": "enableAdaptiveBitrateStreaming", + "in": "query", + "description": "Enable adaptive bitrate streaming.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Optional. Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Audio stream returned.", + "content": { + "application/x-mpegURL": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Videos/{itemId}/hls1/{playlistId}/{segmentId}.{container}": { + "get": { + "tags": [ + "DynamicHls" + ], + "summary": "Gets a video stream using HTTP live streaming.", + "operationId": "GetHlsVideoSegment", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "playlistId", + "in": "path", + "description": "The playlist id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "segmentId", + "in": "path", + "description": "The segment id.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "container", + "in": "path", + "description": "The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv.", + "required": true, + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "runtimeTicks", + "in": "query", + "description": "The position of the requested segment in ticks.", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "actualSegmentLengthTicks", + "in": "query", + "description": "The length of the requested segment in ticks.", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "static", + "in": "query", + "description": "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "params", + "in": "query", + "description": "The streaming parameters.", + "schema": { + "type": "string" + } + }, + { + "name": "tag", + "in": "query", + "description": "The tag.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceProfileId", + "in": "query", + "description": "Optional. The dlna device profile id to utilize.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "query", + "description": "The segment container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The desired segment length.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minSegments", + "in": "query", + "description": "The minimum number of segments.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. Specify an audio codec to encode to, e.g. mp3.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "enableAutoStreamCopy", + "in": "query", + "description": "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the video stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the audio stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean" + } + }, + { + "name": "audioSampleRate", + "in": "query", + "description": "Optional. Specify a specific audio sample rate, e.g. 44100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioChannels", + "in": "query", + "description": "Optional. Specify a specific number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. Specify a maximum number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "profile", + "in": "query", + "description": "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", + "schema": { + "type": "string" + } + }, + { + "name": "level", + "in": "query", + "description": "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", + "schema": { + "pattern": "-?[0-9]+(?:\\.[0-9]+)?", + "type": "string" + } + }, + { + "name": "framerate", + "in": "query", + "description": "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxFramerate", + "in": "query", + "description": "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "width", + "in": "query", + "description": "Optional. The fixed horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "Optional. The fixed vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "Optional. The maximum horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "Optional. The maximum vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoBitRate", + "in": "query", + "description": "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleMethod", + "in": "query", + "description": "Optional. Specify the subtitle delivery method.", + "schema": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ] + } + }, + { + "name": "maxRefFrames", + "in": "query", + "description": "Optional.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxVideoBitDepth", + "in": "query", + "description": "Optional. The maximum video bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "requireAvc", + "in": "query", + "description": "Optional. Whether to require avc.", + "schema": { + "type": "boolean" + } + }, + { + "name": "deInterlace", + "in": "query", + "description": "Optional. Whether to deinterlace the video.", + "schema": { + "type": "boolean" + } + }, + { + "name": "requireNonAnamorphic", + "in": "query", + "description": "Optional. Whether to require a non anamorphic stream.", + "schema": { + "type": "boolean" + } + }, + { + "name": "transcodingMaxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels to transcode.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "cpuCoreLimit", + "in": "query", + "description": "Optional. The limit of how many cpu cores to use.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "enableMpegtsM2TsMode", + "in": "query", + "description": "Optional. Whether to enable the MpegtsM2Ts mode.", + "schema": { + "type": "boolean" + } + }, + { + "name": "videoCodec", + "in": "query", + "description": "Optional. Specify a video codec to encode to, e.g. h264.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "subtitleCodec", + "in": "query", + "description": "Optional. Specify a subtitle codec to encode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodeReasons", + "in": "query", + "description": "Optional. The transcoding reason.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoStreamIndex", + "in": "query", + "description": "Optional. The index of the video stream to use. If omitted the first video stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "context", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Dlna.EncodingContext.", + "schema": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ] + } + }, + { + "name": "streamOptions", + "in": "query", + "description": "Optional. The streaming options.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Optional. Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "alwaysBurnInSubtitleWhenTranscoding", + "in": "query", + "description": "Whether to always burn in subtitles when transcoding.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Video stream returned.", + "content": { + "video/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Videos/{itemId}/live.m3u8": { + "get": { + "tags": [ + "DynamicHls" + ], + "summary": "Gets a hls live stream.", + "operationId": "GetLiveHlsStream", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "container", + "in": "query", + "description": "The audio container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "static", + "in": "query", + "description": "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "params", + "in": "query", + "description": "The streaming parameters.", + "schema": { + "type": "string" + } + }, + { + "name": "tag", + "in": "query", + "description": "The tag.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceProfileId", + "in": "query", + "description": "Optional. The dlna device profile id to utilize.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "query", + "description": "The segment container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The segment length.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minSegments", + "in": "query", + "description": "The minimum number of segments.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. Specify an audio codec to encode to, e.g. mp3.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "enableAutoStreamCopy", + "in": "query", + "description": "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the video stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the audio stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean" + } + }, + { + "name": "audioSampleRate", + "in": "query", + "description": "Optional. Specify a specific audio sample rate, e.g. 44100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioChannels", + "in": "query", + "description": "Optional. Specify a specific number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. Specify a maximum number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "profile", + "in": "query", + "description": "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", + "schema": { + "type": "string" + } + }, + { + "name": "level", + "in": "query", + "description": "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", + "schema": { + "pattern": "-?[0-9]+(?:\\.[0-9]+)?", + "type": "string" + } + }, + { + "name": "framerate", + "in": "query", + "description": "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxFramerate", + "in": "query", + "description": "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "width", + "in": "query", + "description": "Optional. The fixed horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "Optional. The fixed vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoBitRate", + "in": "query", + "description": "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleMethod", + "in": "query", + "description": "Optional. Specify the subtitle delivery method.", + "schema": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ] + } + }, + { + "name": "maxRefFrames", + "in": "query", + "description": "Optional.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxVideoBitDepth", + "in": "query", + "description": "Optional. The maximum video bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "requireAvc", + "in": "query", + "description": "Optional. Whether to require avc.", + "schema": { + "type": "boolean" + } + }, + { + "name": "deInterlace", + "in": "query", + "description": "Optional. Whether to deinterlace the video.", + "schema": { + "type": "boolean" + } + }, + { + "name": "requireNonAnamorphic", + "in": "query", + "description": "Optional. Whether to require a non anamorphic stream.", + "schema": { + "type": "boolean" + } + }, + { + "name": "transcodingMaxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels to transcode.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "cpuCoreLimit", + "in": "query", + "description": "Optional. The limit of how many cpu cores to use.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "enableMpegtsM2TsMode", + "in": "query", + "description": "Optional. Whether to enable the MpegtsM2Ts mode.", + "schema": { + "type": "boolean" + } + }, + { + "name": "videoCodec", + "in": "query", + "description": "Optional. Specify a video codec to encode to, e.g. h264.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "subtitleCodec", + "in": "query", + "description": "Optional. Specify a subtitle codec to encode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodeReasons", + "in": "query", + "description": "Optional. The transcoding reason.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoStreamIndex", + "in": "query", + "description": "Optional. The index of the video stream to use. If omitted the first video stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "context", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Dlna.EncodingContext.", + "schema": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ] + } + }, + { + "name": "streamOptions", + "in": "query", + "description": "Optional. The streaming options.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "Optional. The max width.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "Optional. The max height.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableSubtitlesInManifest", + "in": "query", + "description": "Optional. Whether to enable subtitles in the manifest.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Optional. Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "alwaysBurnInSubtitleWhenTranscoding", + "in": "query", + "description": "Whether to always burn in subtitles when transcoding.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Hls live stream retrieved.", + "content": { + "application/x-mpegURL": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Videos/{itemId}/main.m3u8": { + "get": { + "tags": [ + "DynamicHls" + ], + "summary": "Gets a video stream using HTTP live streaming.", + "operationId": "GetVariantHlsVideoPlaylist", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "static", + "in": "query", + "description": "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "params", + "in": "query", + "description": "The streaming parameters.", + "schema": { + "type": "string" + } + }, + { + "name": "tag", + "in": "query", + "description": "The tag.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceProfileId", + "in": "query", + "description": "Optional. The dlna device profile id to utilize.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "query", + "description": "The segment container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The segment length.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minSegments", + "in": "query", + "description": "The minimum number of segments.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. Specify an audio codec to encode to, e.g. mp3.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "enableAutoStreamCopy", + "in": "query", + "description": "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the video stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the audio stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean" + } + }, + { + "name": "audioSampleRate", + "in": "query", + "description": "Optional. Specify a specific audio sample rate, e.g. 44100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioChannels", + "in": "query", + "description": "Optional. Specify a specific number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. Specify a maximum number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "profile", + "in": "query", + "description": "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", + "schema": { + "type": "string" + } + }, + { + "name": "level", + "in": "query", + "description": "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", + "schema": { + "pattern": "-?[0-9]+(?:\\.[0-9]+)?", + "type": "string" + } + }, + { + "name": "framerate", + "in": "query", + "description": "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxFramerate", + "in": "query", + "description": "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "width", + "in": "query", + "description": "Optional. The fixed horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "Optional. The fixed vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "Optional. The maximum horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "Optional. The maximum vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoBitRate", + "in": "query", + "description": "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleMethod", + "in": "query", + "description": "Optional. Specify the subtitle delivery method.", + "schema": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ] + } + }, + { + "name": "maxRefFrames", + "in": "query", + "description": "Optional.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxVideoBitDepth", + "in": "query", + "description": "Optional. The maximum video bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "requireAvc", + "in": "query", + "description": "Optional. Whether to require avc.", + "schema": { + "type": "boolean" + } + }, + { + "name": "deInterlace", + "in": "query", + "description": "Optional. Whether to deinterlace the video.", + "schema": { + "type": "boolean" + } + }, + { + "name": "requireNonAnamorphic", + "in": "query", + "description": "Optional. Whether to require a non anamorphic stream.", + "schema": { + "type": "boolean" + } + }, + { + "name": "transcodingMaxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels to transcode.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "cpuCoreLimit", + "in": "query", + "description": "Optional. The limit of how many cpu cores to use.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "enableMpegtsM2TsMode", + "in": "query", + "description": "Optional. Whether to enable the MpegtsM2Ts mode.", + "schema": { + "type": "boolean" + } + }, + { + "name": "videoCodec", + "in": "query", + "description": "Optional. Specify a video codec to encode to, e.g. h264.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "subtitleCodec", + "in": "query", + "description": "Optional. Specify a subtitle codec to encode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodeReasons", + "in": "query", + "description": "Optional. The transcoding reason.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoStreamIndex", + "in": "query", + "description": "Optional. The index of the video stream to use. If omitted the first video stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "context", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Dlna.EncodingContext.", + "schema": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ] + } + }, + { + "name": "streamOptions", + "in": "query", + "description": "Optional. The streaming options.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Optional. Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "alwaysBurnInSubtitleWhenTranscoding", + "in": "query", + "description": "Whether to always burn in subtitles when transcoding.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Video stream returned.", + "content": { + "application/x-mpegURL": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Videos/{itemId}/master.m3u8": { + "get": { + "tags": [ + "DynamicHls" + ], + "summary": "Gets a video hls playlist stream.", + "operationId": "GetMasterHlsVideoPlaylist", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "static", + "in": "query", + "description": "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "params", + "in": "query", + "description": "The streaming parameters.", + "schema": { + "type": "string" + } + }, + { + "name": "tag", + "in": "query", + "description": "The tag.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceProfileId", + "in": "query", + "description": "Optional. The dlna device profile id to utilize.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "query", + "description": "The segment container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The segment length.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minSegments", + "in": "query", + "description": "The minimum number of segments.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. Specify an audio codec to encode to, e.g. mp3.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "enableAutoStreamCopy", + "in": "query", + "description": "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the video stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the audio stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean" + } + }, + { + "name": "audioSampleRate", + "in": "query", + "description": "Optional. Specify a specific audio sample rate, e.g. 44100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioChannels", + "in": "query", + "description": "Optional. Specify a specific number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. Specify a maximum number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "profile", + "in": "query", + "description": "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", + "schema": { + "type": "string" + } + }, + { + "name": "level", + "in": "query", + "description": "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", + "schema": { + "pattern": "-?[0-9]+(?:\\.[0-9]+)?", + "type": "string" + } + }, + { + "name": "framerate", + "in": "query", + "description": "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxFramerate", + "in": "query", + "description": "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "width", + "in": "query", + "description": "Optional. The fixed horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "Optional. The fixed vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "Optional. The maximum horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "Optional. The maximum vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoBitRate", + "in": "query", + "description": "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleMethod", + "in": "query", + "description": "Optional. Specify the subtitle delivery method.", + "schema": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ] + } + }, + { + "name": "maxRefFrames", + "in": "query", + "description": "Optional.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxVideoBitDepth", + "in": "query", + "description": "Optional. The maximum video bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "requireAvc", + "in": "query", + "description": "Optional. Whether to require avc.", + "schema": { + "type": "boolean" + } + }, + { + "name": "deInterlace", + "in": "query", + "description": "Optional. Whether to deinterlace the video.", + "schema": { + "type": "boolean" + } + }, + { + "name": "requireNonAnamorphic", + "in": "query", + "description": "Optional. Whether to require a non anamorphic stream.", + "schema": { + "type": "boolean" + } + }, + { + "name": "transcodingMaxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels to transcode.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "cpuCoreLimit", + "in": "query", + "description": "Optional. The limit of how many cpu cores to use.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "enableMpegtsM2TsMode", + "in": "query", + "description": "Optional. Whether to enable the MpegtsM2Ts mode.", + "schema": { + "type": "boolean" + } + }, + { + "name": "videoCodec", + "in": "query", + "description": "Optional. Specify a video codec to encode to, e.g. h264.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "subtitleCodec", + "in": "query", + "description": "Optional. Specify a subtitle codec to encode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodeReasons", + "in": "query", + "description": "Optional. The transcoding reason.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoStreamIndex", + "in": "query", + "description": "Optional. The index of the video stream to use. If omitted the first video stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "context", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Dlna.EncodingContext.", + "schema": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ] + } + }, + { + "name": "streamOptions", + "in": "query", + "description": "Optional. The streaming options.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + { + "name": "enableAdaptiveBitrateStreaming", + "in": "query", + "description": "Enable adaptive bitrate streaming.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "enableTrickplay", + "in": "query", + "description": "Enable trickplay image playlists being added to master playlist.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "alwaysBurnInSubtitleWhenTranscoding", + "in": "query", + "description": "Whether to always burn in subtitles when transcoding.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Video stream returned.", + "content": { + "application/x-mpegURL": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "head": { + "tags": [ + "DynamicHls" + ], + "summary": "Gets a video hls playlist stream.", + "operationId": "HeadMasterHlsVideoPlaylist", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "static", + "in": "query", + "description": "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "params", + "in": "query", + "description": "The streaming parameters.", + "schema": { + "type": "string" + } + }, + { + "name": "tag", + "in": "query", + "description": "The tag.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceProfileId", + "in": "query", + "description": "Optional. The dlna device profile id to utilize.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "query", + "description": "The segment container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The segment length.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minSegments", + "in": "query", + "description": "The minimum number of segments.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. Specify an audio codec to encode to, e.g. mp3.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "enableAutoStreamCopy", + "in": "query", + "description": "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the video stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the audio stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean" + } + }, + { + "name": "audioSampleRate", + "in": "query", + "description": "Optional. Specify a specific audio sample rate, e.g. 44100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioChannels", + "in": "query", + "description": "Optional. Specify a specific number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. Specify a maximum number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "profile", + "in": "query", + "description": "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", + "schema": { + "type": "string" + } + }, + { + "name": "level", + "in": "query", + "description": "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", + "schema": { + "pattern": "-?[0-9]+(?:\\.[0-9]+)?", + "type": "string" + } + }, + { + "name": "framerate", + "in": "query", + "description": "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxFramerate", + "in": "query", + "description": "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "width", + "in": "query", + "description": "Optional. The fixed horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "Optional. The fixed vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "Optional. The maximum horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "Optional. The maximum vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoBitRate", + "in": "query", + "description": "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleMethod", + "in": "query", + "description": "Optional. Specify the subtitle delivery method.", + "schema": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ] + } + }, + { + "name": "maxRefFrames", + "in": "query", + "description": "Optional.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxVideoBitDepth", + "in": "query", + "description": "Optional. The maximum video bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "requireAvc", + "in": "query", + "description": "Optional. Whether to require avc.", + "schema": { + "type": "boolean" + } + }, + { + "name": "deInterlace", + "in": "query", + "description": "Optional. Whether to deinterlace the video.", + "schema": { + "type": "boolean" + } + }, + { + "name": "requireNonAnamorphic", + "in": "query", + "description": "Optional. Whether to require a non anamorphic stream.", + "schema": { + "type": "boolean" + } + }, + { + "name": "transcodingMaxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels to transcode.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "cpuCoreLimit", + "in": "query", + "description": "Optional. The limit of how many cpu cores to use.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "enableMpegtsM2TsMode", + "in": "query", + "description": "Optional. Whether to enable the MpegtsM2Ts mode.", + "schema": { + "type": "boolean" + } + }, + { + "name": "videoCodec", + "in": "query", + "description": "Optional. Specify a video codec to encode to, e.g. h264.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "subtitleCodec", + "in": "query", + "description": "Optional. Specify a subtitle codec to encode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodeReasons", + "in": "query", + "description": "Optional. The transcoding reason.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoStreamIndex", + "in": "query", + "description": "Optional. The index of the video stream to use. If omitted the first video stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "context", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Dlna.EncodingContext.", + "schema": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ] + } + }, + { + "name": "streamOptions", + "in": "query", + "description": "Optional. The streaming options.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + { + "name": "enableAdaptiveBitrateStreaming", + "in": "query", + "description": "Enable adaptive bitrate streaming.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "enableTrickplay", + "in": "query", + "description": "Enable trickplay image playlists being added to master playlist.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "alwaysBurnInSubtitleWhenTranscoding", + "in": "query", + "description": "Whether to always burn in subtitles when transcoding.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Video stream returned.", + "content": { + "application/x-mpegURL": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Environment/DefaultDirectoryBrowser": { + "get": { + "tags": [ + "Environment" + ], + "summary": "Get Default directory browser.", + "operationId": "GetDefaultDirectoryBrowser", + "responses": { + "200": { + "description": "Default directory browser returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DefaultDirectoryBrowserInfoDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/DefaultDirectoryBrowserInfoDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/DefaultDirectoryBrowserInfoDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Environment/DirectoryContents": { + "get": { + "tags": [ + "Environment" + ], + "summary": "Gets the contents of a given directory in the file system.", + "operationId": "GetDirectoryContents", + "parameters": [ + { + "name": "path", + "in": "query", + "description": "The path.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "includeFiles", + "in": "query", + "description": "An optional filter to include or exclude files from the results. true/false.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "includeDirectories", + "in": "query", + "description": "An optional filter to include or exclude folders from the results. true/false.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Directory contents returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileSystemEntryInfo" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileSystemEntryInfo" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileSystemEntryInfo" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Environment/Drives": { + "get": { + "tags": [ + "Environment" + ], + "summary": "Gets available drives from the server's file system.", + "operationId": "GetDrives", + "responses": { + "200": { + "description": "List of entries returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileSystemEntryInfo" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileSystemEntryInfo" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileSystemEntryInfo" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Environment/NetworkShares": { + "get": { + "tags": [ + "Environment" + ], + "summary": "Gets network paths.", + "operationId": "GetNetworkShares", + "responses": { + "200": { + "description": "Empty array returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileSystemEntryInfo" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileSystemEntryInfo" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FileSystemEntryInfo" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "deprecated": true, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Environment/ParentPath": { + "get": { + "tags": [ + "Environment" + ], + "summary": "Gets the parent path of a given path.", + "operationId": "GetParentPath", + "parameters": [ + { + "name": "path", + "in": "query", + "description": "The path.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "string" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "string" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "string" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Environment/ValidatePath": { + "post": { + "tags": [ + "Environment" + ], + "summary": "Validates path.", + "operationId": "ValidatePath", + "requestBody": { + "description": "Validate request object.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ValidatePathDto" + } + ], + "description": "Validate path object." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ValidatePathDto" + } + ], + "description": "Validate path object." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ValidatePathDto" + } + ], + "description": "Validate path object." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Path validated." + }, + "404": { + "description": "Path not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/Filters": { + "get": { + "tags": [ + "Filter" + ], + "summary": "Gets legacy query filters.", + "operationId": "GetQueryFiltersLegacy", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "Optional. User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "parentId", + "in": "query", + "description": "Optional. Parent id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "includeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "mediaTypes", + "in": "query", + "description": "Optional. Filter by MediaType. Allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaType" + } + } + } + ], + "responses": { + "200": { + "description": "Legacy filters retrieved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QueryFiltersLegacy" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/QueryFiltersLegacy" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/QueryFiltersLegacy" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/Filters2": { + "get": { + "tags": [ + "Filter" + ], + "summary": "Gets query filters.", + "operationId": "GetQueryFilters", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "Optional. User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "parentId", + "in": "query", + "description": "Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "includeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "isAiring", + "in": "query", + "description": "Optional. Is item airing.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isMovie", + "in": "query", + "description": "Optional. Is item movie.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isSports", + "in": "query", + "description": "Optional. Is item sports.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isKids", + "in": "query", + "description": "Optional. Is item kids.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isNews", + "in": "query", + "description": "Optional. Is item news.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isSeries", + "in": "query", + "description": "Optional. Is item series.", + "schema": { + "type": "boolean" + } + }, + { + "name": "recursive", + "in": "query", + "description": "Optional. Search recursive.", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Filters retrieved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QueryFilters" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/QueryFilters" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/QueryFilters" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Genres": { + "get": { + "tags": [ + "Genres" + ], + "summary": "Gets all genres from a given item, folder, or the entire library.", + "operationId": "GetGenres", + "parameters": [ + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "searchTerm", + "in": "query", + "description": "The search term.", + "schema": { + "type": "string" + } + }, + { + "name": "parentId", + "in": "query", + "description": "Specify this to localize the search to a specific item or folder. Omit to use the root.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "excludeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "includeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "isFavorite", + "in": "query", + "description": "Optional filter by items that are marked as favorite, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional, the max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "nameStartsWithOrGreater", + "in": "query", + "description": "Optional filter by items whose name is sorted equally or greater than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "nameStartsWith", + "in": "query", + "description": "Optional filter by items whose name is sorted equally than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "nameLessThan", + "in": "query", + "description": "Optional filter by items whose name is equally or lesser than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Optional. Specify one or more sort orders, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemSortBy" + } + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Sort Order - Ascending,Descending.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SortOrder" + } + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional, include image information in output.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "enableTotalRecordCount", + "in": "query", + "description": "Optional. Include total record count.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Genres returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Genres/{genreName}": { + "get": { + "tags": [ + "Genres" + ], + "summary": "Gets a genre, by name.", + "operationId": "GetGenre", + "parameters": [ + { + "name": "genreName", + "in": "path", + "description": "The genre name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "query", + "description": "The user id.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Genres returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Audio/{itemId}/hls/{segmentId}/stream.aac": { + "get": { + "tags": [ + "HlsSegment" + ], + "summary": "Gets the specified audio segment for an audio item.", + "operationId": "GetHlsAudioSegmentLegacyAac", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "segmentId", + "in": "path", + "description": "The segment id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Hls audio segment returned.", + "content": { + "audio/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Audio/{itemId}/hls/{segmentId}/stream.mp3": { + "get": { + "tags": [ + "HlsSegment" + ], + "summary": "Gets the specified audio segment for an audio item.", + "operationId": "GetHlsAudioSegmentLegacyMp3", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "segmentId", + "in": "path", + "description": "The segment id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Hls audio segment returned.", + "content": { + "audio/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Videos/{itemId}/hls/{playlistId}/{segmentId}.{segmentContainer}": { + "get": { + "tags": [ + "HlsSegment" + ], + "summary": "Gets a hls video segment.", + "operationId": "GetHlsVideoSegmentLegacy", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "playlistId", + "in": "path", + "description": "The playlist id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "segmentId", + "in": "path", + "description": "The segment id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "path", + "description": "The segment container.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Hls video segment returned.", + "content": { + "video/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Hls segment not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Videos/{itemId}/hls/{playlistId}/stream.m3u8": { + "get": { + "tags": [ + "HlsSegment" + ], + "summary": "Gets a hls video playlist.", + "operationId": "GetHlsPlaylistLegacy", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The video id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "playlistId", + "in": "path", + "description": "The playlist id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Hls video playlist returned.", + "content": { + "application/x-mpegURL": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Videos/ActiveEncodings": { + "delete": { + "tags": [ + "HlsSegment" + ], + "summary": "Stops an active encoding.", + "operationId": "StopEncodingProcess", + "parameters": [ + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Encoding stopped successfully." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Artists/{name}/Images/{imageType}/{imageIndex}": { + "get": { + "tags": [ + "Image" + ], + "summary": "Get artist image by name.", + "operationId": "GetArtistImage", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Artist name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + }, + { + "name": "imageIndex", + "in": "path", + "description": "Image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "head": { + "tags": [ + "Image" + ], + "summary": "Get artist image by name.", + "operationId": "HeadArtistImage", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Artist name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + }, + { + "name": "imageIndex", + "in": "path", + "description": "Image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Branding/Splashscreen": { + "get": { + "tags": [ + "Image" + ], + "summary": "Generates or gets the splashscreen.", + "operationId": "GetSplashscreen", + "parameters": [ + { + "name": "tag", + "in": "query", + "description": "Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + } + ], + "responses": { + "200": { + "description": "Splashscreen returned successfully.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "post": { + "tags": [ + "Image" + ], + "summary": "Uploads a custom splashscreen.\r\nThe body is expected to the image contents base64 encoded.", + "operationId": "UploadCustomSplashscreen", + "requestBody": { + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "204": { + "description": "Successfully uploaded new splashscreen." + }, + "400": { + "description": "Error reading MimeType from uploaded image.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "User does not have permission to upload splashscreen..", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + }, + "delete": { + "tags": [ + "Image" + ], + "summary": "Delete a custom splashscreen.", + "operationId": "DeleteCustomSplashscreen", + "responses": { + "204": { + "description": "Successfully deleted the custom splashscreen." + }, + "403": { + "description": "User does not have permission to delete splashscreen.." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Genres/{name}/Images/{imageType}": { + "get": { + "tags": [ + "Image" + ], + "summary": "Get genre image by name.", + "operationId": "GetGenreImage", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Genre name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + }, + { + "name": "imageIndex", + "in": "query", + "description": "Image index.", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "head": { + "tags": [ + "Image" + ], + "summary": "Get genre image by name.", + "operationId": "HeadGenreImage", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Genre name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + }, + { + "name": "imageIndex", + "in": "query", + "description": "Image index.", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Genres/{name}/Images/{imageType}/{imageIndex}": { + "get": { + "tags": [ + "Image" + ], + "summary": "Get genre image by name.", + "operationId": "GetGenreImageByIndex", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Genre name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "imageIndex", + "in": "path", + "description": "Image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "head": { + "tags": [ + "Image" + ], + "summary": "Get genre image by name.", + "operationId": "HeadGenreImageByIndex", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Genre name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "imageIndex", + "in": "path", + "description": "Image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Items/{itemId}/Images": { + "get": { + "tags": [ + "Image" + ], + "summary": "Get item image infos.", + "operationId": "GetItemImageInfos", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Item images returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageInfo" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageInfo" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageInfo" + } + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/Images/{imageType}": { + "delete": { + "tags": [ + "Image" + ], + "summary": "Delete an item's image.", + "operationId": "DeleteItemImage", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "imageIndex", + "in": "query", + "description": "The image index.", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Image deleted." + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + }, + "post": { + "tags": [ + "Image" + ], + "summary": "Set item image.", + "operationId": "SetItemImage", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + } + ], + "requestBody": { + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "204": { + "description": "Image saved." + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + }, + "get": { + "tags": [ + "Image" + ], + "summary": "Gets the item's image.", + "operationId": "GetItemImage", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Drawing.ImageFormat of the returned image.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + }, + { + "name": "imageIndex", + "in": "query", + "description": "Image index.", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "head": { + "tags": [ + "Image" + ], + "summary": "Gets the item's image.", + "operationId": "HeadItemImage", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Drawing.ImageFormat of the returned image.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + }, + { + "name": "imageIndex", + "in": "query", + "description": "Image index.", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Items/{itemId}/Images/{imageType}/{imageIndex}": { + "delete": { + "tags": [ + "Image" + ], + "summary": "Delete an item's image.", + "operationId": "DeleteItemImageByIndex", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "imageIndex", + "in": "path", + "description": "The image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Image deleted." + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + }, + "post": { + "tags": [ + "Image" + ], + "summary": "Set item image.", + "operationId": "SetItemImageByIndex", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "imageIndex", + "in": "path", + "description": "(Unused) Image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "requestBody": { + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "204": { + "description": "Image saved." + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + }, + "get": { + "tags": [ + "Image" + ], + "summary": "Gets the item's image.", + "operationId": "GetItemImageByIndex", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "imageIndex", + "in": "path", + "description": "Image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Drawing.ImageFormat of the returned image.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "head": { + "tags": [ + "Image" + ], + "summary": "Gets the item's image.", + "operationId": "HeadItemImageByIndex", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "imageIndex", + "in": "path", + "description": "Image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Drawing.ImageFormat of the returned image.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Items/{itemId}/Images/{imageType}/{imageIndex}/{tag}/{format}/{maxWidth}/{maxHeight}/{percentPlayed}/{unplayedCount}": { + "get": { + "tags": [ + "Image" + ], + "summary": "Gets the item's image.", + "operationId": "GetItemImage2", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "maxWidth", + "in": "path", + "description": "The maximum image width to return.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "path", + "description": "The maximum image height to return.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "tag", + "in": "path", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "path", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "required": true, + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ], + "description": "Enum ImageOutputFormat." + } + }, + { + "name": "percentPlayed", + "in": "path", + "description": "Optional. Percent to render for the percent played overlay.", + "required": true, + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "path", + "description": "Optional. Unplayed count overlay to render.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + }, + { + "name": "imageIndex", + "in": "path", + "description": "Image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "head": { + "tags": [ + "Image" + ], + "summary": "Gets the item's image.", + "operationId": "HeadItemImage2", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "maxWidth", + "in": "path", + "description": "The maximum image width to return.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "path", + "description": "The maximum image height to return.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "tag", + "in": "path", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "path", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "required": true, + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ], + "description": "Enum ImageOutputFormat." + } + }, + { + "name": "percentPlayed", + "in": "path", + "description": "Optional. Percent to render for the percent played overlay.", + "required": true, + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "path", + "description": "Optional. Unplayed count overlay to render.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + }, + { + "name": "imageIndex", + "in": "path", + "description": "Image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Items/{itemId}/Images/{imageType}/{imageIndex}/Index": { + "post": { + "tags": [ + "Image" + ], + "summary": "Updates the index for an item image.", + "operationId": "UpdateItemImageIndex", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "imageIndex", + "in": "path", + "description": "Old image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "newIndex", + "in": "query", + "description": "New image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Image index updated." + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/MusicGenres/{name}/Images/{imageType}": { + "get": { + "tags": [ + "Image" + ], + "summary": "Get music genre image by name.", + "operationId": "GetMusicGenreImage", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Music genre name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + }, + { + "name": "imageIndex", + "in": "query", + "description": "Image index.", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "head": { + "tags": [ + "Image" + ], + "summary": "Get music genre image by name.", + "operationId": "HeadMusicGenreImage", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Music genre name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + }, + { + "name": "imageIndex", + "in": "query", + "description": "Image index.", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/MusicGenres/{name}/Images/{imageType}/{imageIndex}": { + "get": { + "tags": [ + "Image" + ], + "summary": "Get music genre image by name.", + "operationId": "GetMusicGenreImageByIndex", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Music genre name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "imageIndex", + "in": "path", + "description": "Image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "head": { + "tags": [ + "Image" + ], + "summary": "Get music genre image by name.", + "operationId": "HeadMusicGenreImageByIndex", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Music genre name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "imageIndex", + "in": "path", + "description": "Image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Persons/{name}/Images/{imageType}": { + "get": { + "tags": [ + "Image" + ], + "summary": "Get person image by name.", + "operationId": "GetPersonImage", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Person name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + }, + { + "name": "imageIndex", + "in": "query", + "description": "Image index.", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "head": { + "tags": [ + "Image" + ], + "summary": "Get person image by name.", + "operationId": "HeadPersonImage", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Person name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + }, + { + "name": "imageIndex", + "in": "query", + "description": "Image index.", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Persons/{name}/Images/{imageType}/{imageIndex}": { + "get": { + "tags": [ + "Image" + ], + "summary": "Get person image by name.", + "operationId": "GetPersonImageByIndex", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Person name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "imageIndex", + "in": "path", + "description": "Image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "head": { + "tags": [ + "Image" + ], + "summary": "Get person image by name.", + "operationId": "HeadPersonImageByIndex", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Person name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "imageIndex", + "in": "path", + "description": "Image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Studios/{name}/Images/{imageType}": { + "get": { + "tags": [ + "Image" + ], + "summary": "Get studio image by name.", + "operationId": "GetStudioImage", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Studio name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + }, + { + "name": "imageIndex", + "in": "query", + "description": "Image index.", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "head": { + "tags": [ + "Image" + ], + "summary": "Get studio image by name.", + "operationId": "HeadStudioImage", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Studio name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + }, + { + "name": "imageIndex", + "in": "query", + "description": "Image index.", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Studios/{name}/Images/{imageType}/{imageIndex}": { + "get": { + "tags": [ + "Image" + ], + "summary": "Get studio image by name.", + "operationId": "GetStudioImageByIndex", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Studio name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "imageIndex", + "in": "path", + "description": "Image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "head": { + "tags": [ + "Image" + ], + "summary": "Get studio image by name.", + "operationId": "HeadStudioImageByIndex", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Studio name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "imageType", + "in": "path", + "description": "Image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "imageIndex", + "in": "path", + "description": "Image index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "The maximum image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "The maximum image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "percentPlayed", + "in": "query", + "description": "Optional. Percent to render for the percent played overlay.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "unplayedCount", + "in": "query", + "description": "Optional. Unplayed count overlay to render.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "width", + "in": "query", + "description": "The fixed image width to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "The fixed image height to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "quality", + "in": "query", + "description": "Optional. Quality setting, from 0-100. Defaults to 90 and should suffice in most cases.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillWidth", + "in": "query", + "description": "Width of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fillHeight", + "in": "query", + "description": "Height of box to fill.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "blur", + "in": "query", + "description": "Optional. Blur image.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "backgroundColor", + "in": "query", + "description": "Optional. Apply a background color for transparent images.", + "schema": { + "type": "string" + } + }, + { + "name": "foregroundLayer", + "in": "query", + "description": "Optional. Apply a foreground layer on top of the image.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/UserImage": { + "post": { + "tags": [ + "Image" + ], + "summary": "Sets the user image.", + "operationId": "PostUserImage", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User Id.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "204": { + "description": "Image updated." + }, + "400": { + "description": "Bad Request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "User does not have permission to delete the image.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "Image" + ], + "summary": "Delete the user's image.", + "operationId": "DeleteUserImage", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User Id.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Image deleted." + }, + "403": { + "description": "User does not have permission to delete the image.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "get": { + "tags": [ + "Image" + ], + "summary": "Get user profile image.", + "operationId": "GetUserImage", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "description": "User id not provided.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "head": { + "tags": [ + "Image" + ], + "summary": "Get user profile image.", + "operationId": "HeadUserImage", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "tag", + "in": "query", + "description": "Optional. Supply the cache tag from the item object to receive strong caching headers.", + "schema": { + "type": "string" + } + }, + { + "name": "format", + "in": "query", + "description": "Determines the output format of the image - original,gif,jpg,png.", + "schema": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageFormat" + } + ] + } + } + ], + "responses": { + "200": { + "description": "Image stream returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "400": { + "description": "User id not provided.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Albums/{itemId}/InstantMix": { + "get": { + "tags": [ + "InstantMix" + ], + "summary": "Creates an instant playlist based on a given album.", + "operationId": "GetInstantMixFromAlbum", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + } + ], + "responses": { + "200": { + "description": "Instant playlist returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Artists/{itemId}/InstantMix": { + "get": { + "tags": [ + "InstantMix" + ], + "summary": "Creates an instant playlist based on a given artist.", + "operationId": "GetInstantMixFromArtists", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + } + ], + "responses": { + "200": { + "description": "Instant playlist returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Artists/InstantMix": { + "get": { + "tags": [ + "InstantMix" + ], + "summary": "Creates an instant playlist based on a given artist.", + "operationId": "GetInstantMixFromArtists2", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + } + ], + "responses": { + "200": { + "description": "Instant playlist returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "deprecated": true, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/InstantMix": { + "get": { + "tags": [ + "InstantMix" + ], + "summary": "Creates an instant playlist based on a given item.", + "operationId": "GetInstantMixFromItem", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + } + ], + "responses": { + "200": { + "description": "Instant playlist returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/MusicGenres/{name}/InstantMix": { + "get": { + "tags": [ + "InstantMix" + ], + "summary": "Creates an instant playlist based on a given genre.", + "operationId": "GetInstantMixFromMusicGenreByName", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "The genre name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + } + ], + "responses": { + "200": { + "description": "Instant playlist returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/MusicGenres/InstantMix": { + "get": { + "tags": [ + "InstantMix" + ], + "summary": "Creates an instant playlist based on a given genre.", + "operationId": "GetInstantMixFromMusicGenreById", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + } + ], + "responses": { + "200": { + "description": "Instant playlist returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Playlists/{itemId}/InstantMix": { + "get": { + "tags": [ + "InstantMix" + ], + "summary": "Creates an instant playlist based on a given playlist.", + "operationId": "GetInstantMixFromPlaylist", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + } + ], + "responses": { + "200": { + "description": "Instant playlist returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Songs/{itemId}/InstantMix": { + "get": { + "tags": [ + "InstantMix" + ], + "summary": "Creates an instant playlist based on a given song.", + "operationId": "GetInstantMixFromSong", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + } + ], + "responses": { + "200": { + "description": "Instant playlist returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/ExternalIdInfos": { + "get": { + "tags": [ + "ItemLookup" + ], + "summary": "Get the item's external id info.", + "operationId": "GetExternalIdInfos", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "External id info retrieved.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExternalIdInfo" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExternalIdInfo" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExternalIdInfo" + } + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/RemoteSearch/Apply/{itemId}": { + "post": { + "tags": [ + "ItemLookup" + ], + "summary": "Applies search criteria to an item and refreshes metadata.", + "operationId": "ApplySearchCriteria", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "replaceAllImages", + "in": "query", + "description": "Optional. Whether or not to replace all images. Default: True.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "requestBody": { + "description": "The remote search result.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/RemoteSearchResult" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/RemoteSearchResult" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/RemoteSearchResult" + } + ] + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Item metadata refreshed." + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/RemoteSearch/Book": { + "post": { + "tags": [ + "ItemLookup" + ], + "summary": "Get book remote search.", + "operationId": "GetBookRemoteSearchResults", + "requestBody": { + "description": "Remote search query.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BookInfoRemoteSearchQuery" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BookInfoRemoteSearchQuery" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BookInfoRemoteSearchQuery" + } + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Book remote search executed.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/RemoteSearch/BoxSet": { + "post": { + "tags": [ + "ItemLookup" + ], + "summary": "Get box set remote search.", + "operationId": "GetBoxSetRemoteSearchResults", + "requestBody": { + "description": "Remote search query.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BoxSetInfoRemoteSearchQuery" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BoxSetInfoRemoteSearchQuery" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BoxSetInfoRemoteSearchQuery" + } + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Box set remote search executed.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/RemoteSearch/Movie": { + "post": { + "tags": [ + "ItemLookup" + ], + "summary": "Get movie remote search.", + "operationId": "GetMovieRemoteSearchResults", + "requestBody": { + "description": "Remote search query.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MovieInfoRemoteSearchQuery" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MovieInfoRemoteSearchQuery" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MovieInfoRemoteSearchQuery" + } + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Movie remote search executed.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/RemoteSearch/MusicAlbum": { + "post": { + "tags": [ + "ItemLookup" + ], + "summary": "Get music album remote search.", + "operationId": "GetMusicAlbumRemoteSearchResults", + "requestBody": { + "description": "Remote search query.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/AlbumInfoRemoteSearchQuery" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/AlbumInfoRemoteSearchQuery" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/AlbumInfoRemoteSearchQuery" + } + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Music album remote search executed.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/RemoteSearch/MusicArtist": { + "post": { + "tags": [ + "ItemLookup" + ], + "summary": "Get music artist remote search.", + "operationId": "GetMusicArtistRemoteSearchResults", + "requestBody": { + "description": "Remote search query.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ArtistInfoRemoteSearchQuery" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ArtistInfoRemoteSearchQuery" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ArtistInfoRemoteSearchQuery" + } + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Music artist remote search executed.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/RemoteSearch/MusicVideo": { + "post": { + "tags": [ + "ItemLookup" + ], + "summary": "Get music video remote search.", + "operationId": "GetMusicVideoRemoteSearchResults", + "requestBody": { + "description": "Remote search query.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MusicVideoInfoRemoteSearchQuery" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MusicVideoInfoRemoteSearchQuery" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MusicVideoInfoRemoteSearchQuery" + } + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Music video remote search executed.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/RemoteSearch/Person": { + "post": { + "tags": [ + "ItemLookup" + ], + "summary": "Get person remote search.", + "operationId": "GetPersonRemoteSearchResults", + "requestBody": { + "description": "Remote search query.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PersonLookupInfoRemoteSearchQuery" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PersonLookupInfoRemoteSearchQuery" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PersonLookupInfoRemoteSearchQuery" + } + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Person remote search executed.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/RemoteSearch/Series": { + "post": { + "tags": [ + "ItemLookup" + ], + "summary": "Get series remote search.", + "operationId": "GetSeriesRemoteSearchResults", + "requestBody": { + "description": "Remote search query.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SeriesInfoRemoteSearchQuery" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SeriesInfoRemoteSearchQuery" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SeriesInfoRemoteSearchQuery" + } + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Series remote search executed.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/RemoteSearch/Trailer": { + "post": { + "tags": [ + "ItemLookup" + ], + "summary": "Get trailer remote search.", + "operationId": "GetTrailerRemoteSearchResults", + "requestBody": { + "description": "Remote search query.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/TrailerInfoRemoteSearchQuery" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/TrailerInfoRemoteSearchQuery" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/TrailerInfoRemoteSearchQuery" + } + ] + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Trailer remote search executed.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/Refresh": { + "post": { + "tags": [ + "ItemRefresh" + ], + "summary": "Refreshes metadata for an item.", + "operationId": "RefreshItem", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "metadataRefreshMode", + "in": "query", + "description": "(Optional) Specifies the metadata refresh mode.", + "schema": { + "enum": [ + "None", + "ValidationOnly", + "Default", + "FullRefresh" + ], + "allOf": [ + { + "$ref": "#/components/schemas/MetadataRefreshMode" + } + ], + "default": "None" + } + }, + { + "name": "imageRefreshMode", + "in": "query", + "description": "(Optional) Specifies the image refresh mode.", + "schema": { + "enum": [ + "None", + "ValidationOnly", + "Default", + "FullRefresh" + ], + "allOf": [ + { + "$ref": "#/components/schemas/MetadataRefreshMode" + } + ], + "default": "None" + } + }, + { + "name": "replaceAllMetadata", + "in": "query", + "description": "(Optional) Determines if metadata should be replaced. Only applicable if mode is FullRefresh.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "replaceAllImages", + "in": "query", + "description": "(Optional) Determines if images should be replaced. Only applicable if mode is FullRefresh.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "regenerateTrickplay", + "in": "query", + "description": "(Optional) Determines if trickplay images should be replaced. Only applicable if mode is FullRefresh.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "204": { + "description": "Item metadata refresh queued." + }, + "404": { + "description": "Item to refresh not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Items": { + "get": { + "tags": [ + "Items" + ], + "summary": "Gets items based on a query.", + "operationId": "GetItems", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "The user id supplied as query parameter; this is required when not using an API key.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "maxOfficialRating", + "in": "query", + "description": "Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).", + "schema": { + "type": "string" + } + }, + { + "name": "hasThemeSong", + "in": "query", + "description": "Optional filter by items with theme songs.", + "schema": { + "type": "boolean" + } + }, + { + "name": "hasThemeVideo", + "in": "query", + "description": "Optional filter by items with theme videos.", + "schema": { + "type": "boolean" + } + }, + { + "name": "hasSubtitles", + "in": "query", + "description": "Optional filter by items with subtitles.", + "schema": { + "type": "boolean" + } + }, + { + "name": "hasSpecialFeature", + "in": "query", + "description": "Optional filter by items with special features.", + "schema": { + "type": "boolean" + } + }, + { + "name": "hasTrailer", + "in": "query", + "description": "Optional filter by items with trailers.", + "schema": { + "type": "boolean" + } + }, + { + "name": "adjacentTo", + "in": "query", + "description": "Optional. Return items that are siblings of a supplied item.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "indexNumber", + "in": "query", + "description": "Optional filter by index number.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "parentIndexNumber", + "in": "query", + "description": "Optional filter by parent index number.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "hasParentalRating", + "in": "query", + "description": "Optional filter by items that have or do not have a parental rating.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isHd", + "in": "query", + "description": "Optional filter by items that are HD or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "is4K", + "in": "query", + "description": "Optional filter by items that are 4K or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "locationTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LocationType" + } + } + }, + { + "name": "excludeLocationTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered based on the LocationType. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LocationType" + } + } + }, + { + "name": "isMissing", + "in": "query", + "description": "Optional filter by items that are missing episodes or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isUnaired", + "in": "query", + "description": "Optional filter by items that are unaired episodes or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "minCommunityRating", + "in": "query", + "description": "Optional filter by minimum community rating.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "minCriticRating", + "in": "query", + "description": "Optional filter by minimum critic rating.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "minPremiereDate", + "in": "query", + "description": "Optional. The minimum premiere date. Format = ISO.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "minDateLastSaved", + "in": "query", + "description": "Optional. The minimum last saved date. Format = ISO.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "minDateLastSavedForUser", + "in": "query", + "description": "Optional. The minimum last saved date for the current user. Format = ISO.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "maxPremiereDate", + "in": "query", + "description": "Optional. The maximum premiere date. Format = ISO.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "hasOverview", + "in": "query", + "description": "Optional filter by items that have an overview or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "hasImdbId", + "in": "query", + "description": "Optional filter by items that have an IMDb id or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "hasTmdbId", + "in": "query", + "description": "Optional filter by items that have a TMDb id or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "hasTvdbId", + "in": "query", + "description": "Optional filter by items that have a TVDb id or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isMovie", + "in": "query", + "description": "Optional filter for live tv movies.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isSeries", + "in": "query", + "description": "Optional filter for live tv series.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isNews", + "in": "query", + "description": "Optional filter for live tv news.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isKids", + "in": "query", + "description": "Optional filter for live tv kids.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isSports", + "in": "query", + "description": "Optional filter for live tv sports.", + "schema": { + "type": "boolean" + } + }, + { + "name": "excludeItemIds", + "in": "query", + "description": "Optional. If specified, results will be filtered by excluding item ids. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "recursive", + "in": "query", + "description": "When searching within folders, this determines whether or not the search will be recursive. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "searchTerm", + "in": "query", + "description": "Optional. Filter based on a search term.", + "schema": { + "type": "string" + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Sort Order - Ascending, Descending.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SortOrder" + } + } + }, + { + "name": "parentId", + "in": "query", + "description": "Specify this to localize the search to a specific item or folder. Omit to use the root.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "excludeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "includeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "filters", + "in": "query", + "description": "Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFilter" + } + } + }, + { + "name": "isFavorite", + "in": "query", + "description": "Optional filter by items that are marked as favorite, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "mediaTypes", + "in": "query", + "description": "Optional filter by MediaType. Allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaType" + } + } + }, + { + "name": "imageTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered based on those containing image types. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemSortBy" + } + } + }, + { + "name": "isPlayed", + "in": "query", + "description": "Optional filter by items that are played, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "genres", + "in": "query", + "description": "Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "officialRatings", + "in": "query", + "description": "Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "tags", + "in": "query", + "description": "Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "years", + "in": "query", + "description": "Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional, include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional, the max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "person", + "in": "query", + "description": "Optional. If specified, results will be filtered to include only those containing the specified person.", + "schema": { + "type": "string" + } + }, + { + "name": "personIds", + "in": "query", + "description": "Optional. If specified, results will be filtered to include only those containing the specified person id.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "personTypes", + "in": "query", + "description": "Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "studios", + "in": "query", + "description": "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "artists", + "in": "query", + "description": "Optional. If specified, results will be filtered based on artists. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "excludeArtistIds", + "in": "query", + "description": "Optional. If specified, results will be filtered based on artist id. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "artistIds", + "in": "query", + "description": "Optional. If specified, results will be filtered to include only those containing the specified artist id.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "albumArtistIds", + "in": "query", + "description": "Optional. If specified, results will be filtered to include only those containing the specified album artist id.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "contributingArtistIds", + "in": "query", + "description": "Optional. If specified, results will be filtered to include only those containing the specified contributing artist id.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "albums", + "in": "query", + "description": "Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "albumIds", + "in": "query", + "description": "Optional. If specified, results will be filtered based on album id. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "ids", + "in": "query", + "description": "Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "videoTypes", + "in": "query", + "description": "Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VideoType" + } + } + }, + { + "name": "minOfficialRating", + "in": "query", + "description": "Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).", + "schema": { + "type": "string" + } + }, + { + "name": "isLocked", + "in": "query", + "description": "Optional filter by items that are locked.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isPlaceHolder", + "in": "query", + "description": "Optional filter by items that are placeholders.", + "schema": { + "type": "boolean" + } + }, + { + "name": "hasOfficialRating", + "in": "query", + "description": "Optional filter by items that have official ratings.", + "schema": { + "type": "boolean" + } + }, + { + "name": "collapseBoxSetItems", + "in": "query", + "description": "Whether or not to hide items behind their boxsets.", + "schema": { + "type": "boolean" + } + }, + { + "name": "minWidth", + "in": "query", + "description": "Optional. Filter by the minimum width of the item.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minHeight", + "in": "query", + "description": "Optional. Filter by the minimum height of the item.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "Optional. Filter by the maximum width of the item.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "Optional. Filter by the maximum height of the item.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "is3D", + "in": "query", + "description": "Optional filter by items that are 3D, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "seriesStatus", + "in": "query", + "description": "Optional filter by Series Status. Allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SeriesStatus" + } + } + }, + { + "name": "nameStartsWithOrGreater", + "in": "query", + "description": "Optional filter by items whose name is sorted equally or greater than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "nameStartsWith", + "in": "query", + "description": "Optional filter by items whose name is sorted equally than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "nameLessThan", + "in": "query", + "description": "Optional filter by items whose name is equally or lesser than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "studioIds", + "in": "query", + "description": "Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "genreIds", + "in": "query", + "description": "Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "enableTotalRecordCount", + "in": "query", + "description": "Optional. Enable the total record count.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional, include image information in output.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "Library" + ], + "summary": "Deletes items from the library and filesystem.", + "operationId": "DeleteItems", + "parameters": [ + { + "name": "ids", + "in": "query", + "description": "The item ids.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + ], + "responses": { + "204": { + "description": "Items deleted." + }, + "401": { + "description": "Unauthorized access.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/UserItems/{itemId}/UserData": { + "get": { + "tags": [ + "Items" + ], + "summary": "Get Item User Data.", + "operationId": "GetItemUserData", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "The user id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "return item user data.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + } + } + }, + "404": { + "description": "Item is not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "post": { + "tags": [ + "Items" + ], + "summary": "Update Item User Data.", + "operationId": "UpdateItemUserData", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "The user id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "New user data object.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdateUserItemDataDto" + } + ], + "description": "This is used by the api to get information about a item user data." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdateUserItemDataDto" + } + ], + "description": "This is used by the api to get information about a item user data." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdateUserItemDataDto" + } + ], + "description": "This is used by the api to get information about a item user data." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "return updated user item data.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + } + } + }, + "404": { + "description": "Item is not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/UserItems/Resume": { + "get": { + "tags": [ + "Items" + ], + "summary": "Gets items based on a query.", + "operationId": "GetResumeItems", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "The user id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "startIndex", + "in": "query", + "description": "The start index.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "The item limit.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "searchTerm", + "in": "query", + "description": "The search term.", + "schema": { + "type": "string" + } + }, + { + "name": "parentId", + "in": "query", + "description": "Specify this to localize the search to a specific item or folder. Omit to use the root.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "mediaTypes", + "in": "query", + "description": "Optional. Filter by MediaType. Allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaType" + } + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "excludeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "includeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered based on the item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "enableTotalRecordCount", + "in": "query", + "description": "Optional. Enable the total record count.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "excludeActiveSessions", + "in": "query", + "description": "Optional. Whether to exclude the currently active sessions.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Items returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}": { + "post": { + "tags": [ + "ItemUpdate" + ], + "summary": "Updates an item.", + "operationId": "UpdateItem", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "The new item properties.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseItemDto" + } + ], + "description": "This is strictly used as a data transfer object from the api layer.\r\nThis holds information about a BaseItem in a format that is convenient for the client." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseItemDto" + } + ], + "description": "This is strictly used as a data transfer object from the api layer.\r\nThis holds information about a BaseItem in a format that is convenient for the client." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseItemDto" + } + ], + "description": "This is strictly used as a data transfer object from the api layer.\r\nThis holds information about a BaseItem in a format that is convenient for the client." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Item updated." + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + }, + "delete": { + "tags": [ + "Library" + ], + "summary": "Deletes an item from the library and filesystem.", + "operationId": "DeleteItem", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Item deleted." + }, + "401": { + "description": "Unauthorized access.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "get": { + "tags": [ + "UserLibrary" + ], + "summary": "Gets an item from a user's library.", + "operationId": "GetItem", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Item returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/ContentType": { + "post": { + "tags": [ + "ItemUpdate" + ], + "summary": "Updates an item's content type.", + "operationId": "UpdateItemContentType", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "contentType", + "in": "query", + "description": "The content type of the item.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Item content type updated." + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Items/{itemId}/MetadataEditor": { + "get": { + "tags": [ + "ItemUpdate" + ], + "summary": "Gets metadata editor info for an item.", + "operationId": "GetMetadataEditorInfo", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Item metadata editor returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MetadataEditorInfo" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/MetadataEditorInfo" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/MetadataEditorInfo" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Albums/{itemId}/Similar": { + "get": { + "tags": [ + "Library" + ], + "summary": "Gets similar items.", + "operationId": "GetSimilarAlbums", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "excludeArtistIds", + "in": "query", + "description": "Exclude artist ids.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + } + ], + "responses": { + "200": { + "description": "Similar items returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Artists/{itemId}/Similar": { + "get": { + "tags": [ + "Library" + ], + "summary": "Gets similar items.", + "operationId": "GetSimilarArtists", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "excludeArtistIds", + "in": "query", + "description": "Exclude artist ids.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + } + ], + "responses": { + "200": { + "description": "Similar items returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/Ancestors": { + "get": { + "tags": [ + "Library" + ], + "summary": "Gets all parents of an item.", + "operationId": "GetAncestors", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Item parents returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/CriticReviews": { + "get": { + "tags": [ + "Library" + ], + "summary": "Gets critic review for an item.", + "operationId": "GetCriticReviews", + "parameters": [ + { + "name": "itemId", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Critic reviews returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "deprecated": true, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/Download": { + "get": { + "tags": [ + "Library" + ], + "summary": "Downloads item media.", + "operationId": "GetDownload", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Media downloaded.", + "content": { + "video/*": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "audio/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "Download", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/File": { + "get": { + "tags": [ + "Library" + ], + "summary": "Get the original file of an item.", + "operationId": "GetFile", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "File stream returned.", + "content": { + "video/*": { + "schema": { + "type": "string", + "format": "binary" + } + }, + "audio/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/Similar": { + "get": { + "tags": [ + "Library" + ], + "summary": "Gets similar items.", + "operationId": "GetSimilarItems", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "excludeArtistIds", + "in": "query", + "description": "Exclude artist ids.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + } + ], + "responses": { + "200": { + "description": "Similar items returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/ThemeMedia": { + "get": { + "tags": [ + "Library" + ], + "summary": "Get theme songs and videos for an item.", + "operationId": "GetThemeMedia", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "inheritFromParent", + "in": "query", + "description": "Optional. Determines whether or not parent items should be searched for theme media.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemSortBy" + } + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Optional. Sort Order - Ascending, Descending.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SortOrder" + } + } + } + ], + "responses": { + "200": { + "description": "Theme songs and videos returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AllThemeMediaResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/AllThemeMediaResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/AllThemeMediaResult" + } + } + } + }, + "404": { + "description": "Item not found." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/ThemeSongs": { + "get": { + "tags": [ + "Library" + ], + "summary": "Get theme songs for an item.", + "operationId": "GetThemeSongs", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "inheritFromParent", + "in": "query", + "description": "Optional. Determines whether or not parent items should be searched for theme media.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemSortBy" + } + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Optional. Sort Order - Ascending, Descending.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SortOrder" + } + } + } + ], + "responses": { + "200": { + "description": "Theme songs returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ThemeMediaResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ThemeMediaResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ThemeMediaResult" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/ThemeVideos": { + "get": { + "tags": [ + "Library" + ], + "summary": "Get theme videos for an item.", + "operationId": "GetThemeVideos", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "inheritFromParent", + "in": "query", + "description": "Optional. Determines whether or not parent items should be searched for theme media.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemSortBy" + } + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Optional. Sort Order - Ascending, Descending.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SortOrder" + } + } + } + ], + "responses": { + "200": { + "description": "Theme videos returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ThemeMediaResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ThemeMediaResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ThemeMediaResult" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/Counts": { + "get": { + "tags": [ + "Library" + ], + "summary": "Get item counts.", + "operationId": "GetItemCounts", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "Optional. Get counts from a specific user's library.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "isFavorite", + "in": "query", + "description": "Optional. Get counts of favorite items.", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Item counts returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ItemCounts" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ItemCounts" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ItemCounts" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Libraries/AvailableOptions": { + "get": { + "tags": [ + "Library" + ], + "summary": "Gets the library options info.", + "operationId": "GetLibraryOptionsInfo", + "parameters": [ + { + "name": "libraryContentType", + "in": "query", + "description": "Library content type.", + "schema": { + "enum": [ + "unknown", + "movies", + "tvshows", + "music", + "musicvideos", + "trailers", + "homevideos", + "boxsets", + "books", + "photos", + "livetv", + "playlists", + "folders" + ], + "allOf": [ + { + "$ref": "#/components/schemas/CollectionType" + } + ] + } + }, + { + "name": "isNewLibrary", + "in": "query", + "description": "Whether this is a new library.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Library options info returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LibraryOptionsResultDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/LibraryOptionsResultDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/LibraryOptionsResultDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrDefault", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Library/Media/Updated": { + "post": { + "tags": [ + "Library" + ], + "summary": "Reports that new movies have been added by an external source.", + "operationId": "PostUpdatedMedia", + "requestBody": { + "description": "The update paths.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MediaUpdateInfoDto" + } + ], + "description": "Media Update Info Dto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MediaUpdateInfoDto" + } + ], + "description": "Media Update Info Dto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MediaUpdateInfoDto" + } + ], + "description": "Media Update Info Dto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Report success." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Library/MediaFolders": { + "get": { + "tags": [ + "Library" + ], + "summary": "Gets all user media folders.", + "operationId": "GetMediaFolders", + "parameters": [ + { + "name": "isHidden", + "in": "query", + "description": "Optional. Filter by folders that are marked hidden, or not.", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Media folders returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Library/Movies/Added": { + "post": { + "tags": [ + "Library" + ], + "summary": "Reports that new movies have been added by an external source.", + "operationId": "PostAddedMovies", + "parameters": [ + { + "name": "tmdbId", + "in": "query", + "description": "The tmdbId.", + "schema": { + "type": "string" + } + }, + { + "name": "imdbId", + "in": "query", + "description": "The imdbId.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Report success." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Library/Movies/Updated": { + "post": { + "tags": [ + "Library" + ], + "summary": "Reports that new movies have been added by an external source.", + "operationId": "PostUpdatedMovies", + "parameters": [ + { + "name": "tmdbId", + "in": "query", + "description": "The tmdbId.", + "schema": { + "type": "string" + } + }, + { + "name": "imdbId", + "in": "query", + "description": "The imdbId.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Report success." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Library/PhysicalPaths": { + "get": { + "tags": [ + "Library" + ], + "summary": "Gets a list of physical paths from virtual folders.", + "operationId": "GetPhysicalPaths", + "responses": { + "200": { + "description": "Physical paths returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Library/Refresh": { + "post": { + "tags": [ + "Library" + ], + "summary": "Starts a library scan.", + "operationId": "RefreshLibrary", + "responses": { + "204": { + "description": "Library scan started." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Library/Series/Added": { + "post": { + "tags": [ + "Library" + ], + "summary": "Reports that new episodes of a series have been added by an external source.", + "operationId": "PostAddedSeries", + "parameters": [ + { + "name": "tvdbId", + "in": "query", + "description": "The tvdbId.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Report success." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Library/Series/Updated": { + "post": { + "tags": [ + "Library" + ], + "summary": "Reports that new episodes of a series have been added by an external source.", + "operationId": "PostUpdatedSeries", + "parameters": [ + { + "name": "tvdbId", + "in": "query", + "description": "The tvdbId.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Report success." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Movies/{itemId}/Similar": { + "get": { + "tags": [ + "Library" + ], + "summary": "Gets similar items.", + "operationId": "GetSimilarMovies", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "excludeArtistIds", + "in": "query", + "description": "Exclude artist ids.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + } + ], + "responses": { + "200": { + "description": "Similar items returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Shows/{itemId}/Similar": { + "get": { + "tags": [ + "Library" + ], + "summary": "Gets similar items.", + "operationId": "GetSimilarShows", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "excludeArtistIds", + "in": "query", + "description": "Exclude artist ids.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + } + ], + "responses": { + "200": { + "description": "Similar items returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Trailers/{itemId}/Similar": { + "get": { + "tags": [ + "Library" + ], + "summary": "Gets similar items.", + "operationId": "GetSimilarTrailers", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "excludeArtistIds", + "in": "query", + "description": "Exclude artist ids.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + } + ], + "responses": { + "200": { + "description": "Similar items returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Library/VirtualFolders": { + "get": { + "tags": [ + "LibraryStructure" + ], + "summary": "Gets all virtual folders.", + "operationId": "GetVirtualFolders", + "responses": { + "200": { + "description": "Virtual folders retrieved.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VirtualFolderInfo" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VirtualFolderInfo" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VirtualFolderInfo" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + }, + "post": { + "tags": [ + "LibraryStructure" + ], + "summary": "Adds a virtual folder.", + "operationId": "AddVirtualFolder", + "parameters": [ + { + "name": "name", + "in": "query", + "description": "The name of the virtual folder.", + "schema": { + "type": "string" + } + }, + { + "name": "collectionType", + "in": "query", + "description": "The type of the collection.", + "schema": { + "enum": [ + "movies", + "tvshows", + "music", + "musicvideos", + "homevideos", + "boxsets", + "books", + "mixed" + ], + "allOf": [ + { + "$ref": "#/components/schemas/CollectionTypeOptions" + } + ] + } + }, + { + "name": "paths", + "in": "query", + "description": "The paths of the virtual folder.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "refreshLibrary", + "in": "query", + "description": "Whether to refresh the library.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "requestBody": { + "description": "The library options.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/AddVirtualFolderDto" + } + ], + "description": "Add virtual folder dto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/AddVirtualFolderDto" + } + ], + "description": "Add virtual folder dto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/AddVirtualFolderDto" + } + ], + "description": "Add virtual folder dto." + } + } + } + }, + "responses": { + "204": { + "description": "Folder added." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "LibraryStructure" + ], + "summary": "Removes a virtual folder.", + "operationId": "RemoveVirtualFolder", + "parameters": [ + { + "name": "name", + "in": "query", + "description": "The name of the folder.", + "schema": { + "type": "string" + } + }, + { + "name": "refreshLibrary", + "in": "query", + "description": "Whether to refresh the library.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "204": { + "description": "Folder removed." + }, + "404": { + "description": "Folder not found." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Library/VirtualFolders/LibraryOptions": { + "post": { + "tags": [ + "LibraryStructure" + ], + "summary": "Update library options.", + "operationId": "UpdateLibraryOptions", + "requestBody": { + "description": "The library name and options.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdateLibraryOptionsDto" + } + ], + "description": "Update library options dto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdateLibraryOptionsDto" + } + ], + "description": "Update library options dto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdateLibraryOptionsDto" + } + ], + "description": "Update library options dto." + } + } + } + }, + "responses": { + "204": { + "description": "Library updated." + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Library/VirtualFolders/Name": { + "post": { + "tags": [ + "LibraryStructure" + ], + "summary": "Renames a virtual folder.", + "operationId": "RenameVirtualFolder", + "parameters": [ + { + "name": "name", + "in": "query", + "description": "The name of the virtual folder.", + "schema": { + "type": "string" + } + }, + { + "name": "newName", + "in": "query", + "description": "The new name.", + "schema": { + "type": "string" + } + }, + { + "name": "refreshLibrary", + "in": "query", + "description": "Whether to refresh the library.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "204": { + "description": "Folder renamed." + }, + "404": { + "description": "Library doesn't exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "409": { + "description": "Library already exists.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Library/VirtualFolders/Paths": { + "post": { + "tags": [ + "LibraryStructure" + ], + "summary": "Add a media path to a library.", + "operationId": "AddMediaPath", + "parameters": [ + { + "name": "refreshLibrary", + "in": "query", + "description": "Whether to refresh the library.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "requestBody": { + "description": "The media path dto.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MediaPathDto" + } + ], + "description": "Media Path dto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MediaPathDto" + } + ], + "description": "Media Path dto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MediaPathDto" + } + ], + "description": "Media Path dto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Media path added." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "LibraryStructure" + ], + "summary": "Remove a media path.", + "operationId": "RemoveMediaPath", + "parameters": [ + { + "name": "name", + "in": "query", + "description": "The name of the library.", + "schema": { + "type": "string" + } + }, + { + "name": "path", + "in": "query", + "description": "The path to remove.", + "schema": { + "type": "string" + } + }, + { + "name": "refreshLibrary", + "in": "query", + "description": "Whether to refresh the library.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "204": { + "description": "Media path removed." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Library/VirtualFolders/Paths/Update": { + "post": { + "tags": [ + "LibraryStructure" + ], + "summary": "Updates a media path.", + "operationId": "UpdateMediaPath", + "requestBody": { + "description": "The name of the library and path infos.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdateMediaPathRequestDto" + } + ], + "description": "Update library options dto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdateMediaPathRequestDto" + } + ], + "description": "Update library options dto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdateMediaPathRequestDto" + } + ], + "description": "Update library options dto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Media path updated." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/ChannelMappingOptions": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Get channel mapping options.", + "operationId": "GetChannelMappingOptions", + "parameters": [ + { + "name": "providerId", + "in": "query", + "description": "Provider id.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Channel mapping options returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ChannelMappingOptionsDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ChannelMappingOptionsDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ChannelMappingOptionsDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/ChannelMappings": { + "post": { + "tags": [ + "LiveTv" + ], + "summary": "Set channel mappings.", + "operationId": "SetChannelMapping", + "requestBody": { + "description": "The set channel mapping dto.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SetChannelMappingDto" + } + ], + "description": "Set channel mapping dto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SetChannelMappingDto" + } + ], + "description": "Set channel mapping dto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SetChannelMappingDto" + } + ], + "description": "Set channel mapping dto." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Created channel mapping returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TunerChannelMapping" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/TunerChannelMapping" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/TunerChannelMapping" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/Channels": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets available live tv channels.", + "operationId": "GetLiveTvChannels", + "parameters": [ + { + "name": "type", + "in": "query", + "description": "Optional. Filter by channel type.", + "schema": { + "enum": [ + "TV", + "Radio" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ChannelType" + } + ] + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "isMovie", + "in": "query", + "description": "Optional. Filter for movies.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isSeries", + "in": "query", + "description": "Optional. Filter for series.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isNews", + "in": "query", + "description": "Optional. Filter for news.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isKids", + "in": "query", + "description": "Optional. Filter for kids.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isSports", + "in": "query", + "description": "Optional. Filter for sports.", + "schema": { + "type": "boolean" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "isFavorite", + "in": "query", + "description": "Optional. Filter by channels that are favorites, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isLiked", + "in": "query", + "description": "Optional. Filter by channels that are liked, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isDisliked", + "in": "query", + "description": "Optional. Filter by channels that are disliked, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "\"Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Optional. Key to sort by.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemSortBy" + } + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Optional. Sort order.", + "schema": { + "enum": [ + "Ascending", + "Descending" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SortOrder" + } + ] + } + }, + { + "name": "enableFavoriteSorting", + "in": "query", + "description": "Optional. Incorporate favorite and like status into channel sorting.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "addCurrentProgram", + "in": "query", + "description": "Optional. Adds current program info to each channel.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Available live tv channels returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/Channels/{channelId}": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets a live tv channel.", + "operationId": "GetChannel", + "parameters": [ + { + "name": "channelId", + "in": "path", + "description": "Channel id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Live tv channel returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/GuideInfo": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Get guide info.", + "operationId": "GetGuideInfo", + "responses": { + "200": { + "description": "Guide info returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GuideInfo" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/GuideInfo" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/GuideInfo" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/Info": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets available live tv services.", + "operationId": "GetLiveTvInfo", + "responses": { + "200": { + "description": "Available live tv services returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LiveTvInfo" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/LiveTvInfo" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/LiveTvInfo" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/ListingProviders": { + "post": { + "tags": [ + "LiveTv" + ], + "summary": "Adds a listings provider.", + "operationId": "AddListingProvider", + "parameters": [ + { + "name": "pw", + "in": "query", + "description": "Password.", + "schema": { + "type": "string" + } + }, + { + "name": "validateListings", + "in": "query", + "description": "Validate listings.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "validateLogin", + "in": "query", + "description": "Validate login.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "requestBody": { + "description": "New listings info.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ListingsProviderInfo" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ListingsProviderInfo" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ListingsProviderInfo" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Created listings provider returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListingsProviderInfo" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ListingsProviderInfo" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ListingsProviderInfo" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvManagement", + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "LiveTv" + ], + "summary": "Delete listing provider.", + "operationId": "DeleteListingProvider", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "Listing provider id.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Listing provider deleted." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/ListingProviders/Default": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets default listings provider info.", + "operationId": "GetDefaultListingProvider", + "responses": { + "200": { + "description": "Default listings provider info returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ListingsProviderInfo" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ListingsProviderInfo" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ListingsProviderInfo" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/ListingProviders/Lineups": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets available lineups.", + "operationId": "GetLineups", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "Provider id.", + "schema": { + "type": "string" + } + }, + { + "name": "type", + "in": "query", + "description": "Provider type.", + "schema": { + "type": "string" + } + }, + { + "name": "location", + "in": "query", + "description": "Location.", + "schema": { + "type": "string" + } + }, + { + "name": "country", + "in": "query", + "description": "Country.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Available lineups returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameIdPair" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameIdPair" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameIdPair" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/ListingProviders/SchedulesDirect/Countries": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets available countries.", + "operationId": "GetSchedulesDirectCountries", + "responses": { + "200": { + "description": "Available countries returned.", + "content": { + "application/json": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/LiveRecordings/{recordingId}/stream": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets a live tv recording stream.", + "operationId": "GetLiveRecordingFile", + "parameters": [ + { + "name": "recordingId", + "in": "path", + "description": "Recording id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Recording stream returned.", + "content": { + "video/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Recording not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/LiveTv/LiveStreamFiles/{streamId}/stream.{container}": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets a live tv channel stream.", + "operationId": "GetLiveStreamFile", + "parameters": [ + { + "name": "streamId", + "in": "path", + "description": "Stream id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "container", + "in": "path", + "description": "Container type.", + "required": true, + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Stream returned.", + "content": { + "video/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Stream not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/LiveTv/Programs": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets available live tv epgs.", + "operationId": "GetLiveTvPrograms", + "parameters": [ + { + "name": "channelIds", + "in": "query", + "description": "The channels to return guide information for.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "minStartDate", + "in": "query", + "description": "Optional. The minimum premiere start date.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "hasAired", + "in": "query", + "description": "Optional. Filter by programs that have completed airing, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isAiring", + "in": "query", + "description": "Optional. Filter by programs that are currently airing, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "maxStartDate", + "in": "query", + "description": "Optional. The maximum premiere start date.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "minEndDate", + "in": "query", + "description": "Optional. The minimum premiere end date.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "maxEndDate", + "in": "query", + "description": "Optional. The maximum premiere end date.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "isMovie", + "in": "query", + "description": "Optional. Filter for movies.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isSeries", + "in": "query", + "description": "Optional. Filter for series.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isNews", + "in": "query", + "description": "Optional. Filter for news.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isKids", + "in": "query", + "description": "Optional. Filter for kids.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isSports", + "in": "query", + "description": "Optional. Filter for sports.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Optional. Specify one or more sort orders, comma delimited. Options: Name, StartDate.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemSortBy" + } + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Sort Order - Ascending,Descending.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SortOrder" + } + } + }, + { + "name": "genres", + "in": "query", + "description": "The genres to return guide information for.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "genreIds", + "in": "query", + "description": "The genre ids to return guide information for.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "seriesTimerId", + "in": "query", + "description": "Optional. Filter by series timer id.", + "schema": { + "type": "string" + } + }, + { + "name": "librarySeriesId", + "in": "query", + "description": "Optional. Filter by library series id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "enableTotalRecordCount", + "in": "query", + "description": "Retrieve total record count.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Live tv epgs returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + }, + "post": { + "tags": [ + "LiveTv" + ], + "summary": "Gets available live tv epgs.", + "operationId": "GetPrograms", + "requestBody": { + "description": "Request body.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/GetProgramsDto" + } + ], + "description": "Get programs dto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/GetProgramsDto" + } + ], + "description": "Get programs dto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/GetProgramsDto" + } + ], + "description": "Get programs dto." + } + } + } + }, + "responses": { + "200": { + "description": "Live tv epgs returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/Programs/{programId}": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets a live tv program.", + "operationId": "GetProgram", + "parameters": [ + { + "name": "programId", + "in": "path", + "description": "Program id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Program returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/Programs/Recommended": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets recommended live tv epgs.", + "operationId": "GetRecommendedPrograms", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "Optional. filter by user id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "isAiring", + "in": "query", + "description": "Optional. Filter by programs that are currently airing, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "hasAired", + "in": "query", + "description": "Optional. Filter by programs that have completed airing, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isSeries", + "in": "query", + "description": "Optional. Filter for series.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isMovie", + "in": "query", + "description": "Optional. Filter for movies.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isNews", + "in": "query", + "description": "Optional. Filter for news.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isKids", + "in": "query", + "description": "Optional. Filter for kids.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isSports", + "in": "query", + "description": "Optional. Filter for sports.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "genreIds", + "in": "query", + "description": "The genres to return guide information for.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableTotalRecordCount", + "in": "query", + "description": "Retrieve total record count.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Recommended epgs returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/Recordings": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets live tv recordings.", + "operationId": "GetRecordings", + "parameters": [ + { + "name": "channelId", + "in": "query", + "description": "Optional. Filter by channel id.", + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "status", + "in": "query", + "description": "Optional. Filter by recording status.", + "schema": { + "enum": [ + "New", + "InProgress", + "Completed", + "Cancelled", + "ConflictedOk", + "ConflictedNotOk", + "Error" + ], + "allOf": [ + { + "$ref": "#/components/schemas/RecordingStatus" + } + ] + } + }, + { + "name": "isInProgress", + "in": "query", + "description": "Optional. Filter by recordings that are in progress, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "seriesTimerId", + "in": "query", + "description": "Optional. Filter by recordings belonging to a series timer.", + "schema": { + "type": "string" + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isMovie", + "in": "query", + "description": "Optional. Filter for movies.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isSeries", + "in": "query", + "description": "Optional. Filter for series.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isKids", + "in": "query", + "description": "Optional. Filter for kids.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isSports", + "in": "query", + "description": "Optional. Filter for sports.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isNews", + "in": "query", + "description": "Optional. Filter for news.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isLibraryItem", + "in": "query", + "description": "Optional. Filter for is library item.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableTotalRecordCount", + "in": "query", + "description": "Optional. Return total record count.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Live tv recordings returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/Recordings/{recordingId}": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets a live tv recording.", + "operationId": "GetRecording", + "parameters": [ + { + "name": "recordingId", + "in": "path", + "description": "Recording id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Recording returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "LiveTv" + ], + "summary": "Deletes a live tv recording.", + "operationId": "DeleteRecording", + "parameters": [ + { + "name": "recordingId", + "in": "path", + "description": "Recording id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Recording deleted." + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/Recordings/Folders": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets recording folders.", + "operationId": "GetRecordingFolders", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Recording folders returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/Recordings/Groups": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets live tv recording groups.", + "operationId": "GetRecordingGroups", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Recording groups returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "deprecated": true, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/Recordings/Groups/{groupId}": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Get recording group.", + "operationId": "GetRecordingGroup", + "parameters": [ + { + "name": "groupId", + "in": "path", + "description": "Group id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "deprecated": true, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/Recordings/Series": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets live tv recording series.", + "operationId": "GetRecordingsSeries", + "parameters": [ + { + "name": "channelId", + "in": "query", + "description": "Optional. Filter by channel id.", + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "groupId", + "in": "query", + "description": "Optional. Filter by recording group.", + "schema": { + "type": "string" + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "status", + "in": "query", + "description": "Optional. Filter by recording status.", + "schema": { + "enum": [ + "New", + "InProgress", + "Completed", + "Cancelled", + "ConflictedOk", + "ConflictedNotOk", + "Error" + ], + "allOf": [ + { + "$ref": "#/components/schemas/RecordingStatus" + } + ] + } + }, + { + "name": "isInProgress", + "in": "query", + "description": "Optional. Filter by recordings that are in progress, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "seriesTimerId", + "in": "query", + "description": "Optional. Filter by recordings belonging to a series timer.", + "schema": { + "type": "string" + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableTotalRecordCount", + "in": "query", + "description": "Optional. Return total record count.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Live tv recordings returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "deprecated": true, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/SeriesTimers": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets live tv series timers.", + "operationId": "GetSeriesTimers", + "parameters": [ + { + "name": "sortBy", + "in": "query", + "description": "Optional. Sort by SortName or Priority.", + "schema": { + "type": "string" + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Optional. Sort in Ascending or Descending order.", + "schema": { + "enum": [ + "Ascending", + "Descending" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SortOrder" + } + ] + } + } + ], + "responses": { + "200": { + "description": "Timers returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SeriesTimerInfoDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/SeriesTimerInfoDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/SeriesTimerInfoDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + }, + "post": { + "tags": [ + "LiveTv" + ], + "summary": "Creates a live tv series timer.", + "operationId": "CreateSeriesTimer", + "requestBody": { + "description": "New series timer info.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SeriesTimerInfoDto" + } + ], + "description": "Class SeriesTimerInfoDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SeriesTimerInfoDto" + } + ], + "description": "Class SeriesTimerInfoDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SeriesTimerInfoDto" + } + ], + "description": "Class SeriesTimerInfoDto." + } + } + } + }, + "responses": { + "204": { + "description": "Series timer info created." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/SeriesTimers/{timerId}": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets a live tv series timer.", + "operationId": "GetSeriesTimer", + "parameters": [ + { + "name": "timerId", + "in": "path", + "description": "Timer id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Series timer returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SeriesTimerInfoDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/SeriesTimerInfoDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/SeriesTimerInfoDto" + } + } + } + }, + "404": { + "description": "Series timer not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "LiveTv" + ], + "summary": "Cancels a live tv series timer.", + "operationId": "CancelSeriesTimer", + "parameters": [ + { + "name": "timerId", + "in": "path", + "description": "Timer id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Timer cancelled." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvManagement", + "DefaultAuthorization" + ] + } + ] + }, + "post": { + "tags": [ + "LiveTv" + ], + "summary": "Updates a live tv series timer.", + "operationId": "UpdateSeriesTimer", + "parameters": [ + { + "name": "timerId", + "in": "path", + "description": "Timer id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "New series timer info.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SeriesTimerInfoDto" + } + ], + "description": "Class SeriesTimerInfoDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SeriesTimerInfoDto" + } + ], + "description": "Class SeriesTimerInfoDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SeriesTimerInfoDto" + } + ], + "description": "Class SeriesTimerInfoDto." + } + } + } + }, + "responses": { + "204": { + "description": "Series timer updated." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/Timers": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets the live tv timers.", + "operationId": "GetTimers", + "parameters": [ + { + "name": "channelId", + "in": "query", + "description": "Optional. Filter by channel id.", + "schema": { + "type": "string" + } + }, + { + "name": "seriesTimerId", + "in": "query", + "description": "Optional. Filter by timers belonging to a series timer.", + "schema": { + "type": "string" + } + }, + { + "name": "isActive", + "in": "query", + "description": "Optional. Filter by timers that are active.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isScheduled", + "in": "query", + "description": "Optional. Filter by timers that are scheduled.", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimerInfoDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/TimerInfoDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/TimerInfoDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + }, + "post": { + "tags": [ + "LiveTv" + ], + "summary": "Creates a live tv timer.", + "operationId": "CreateTimer", + "requestBody": { + "description": "New timer info.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/TimerInfoDto" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/TimerInfoDto" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/TimerInfoDto" + } + ] + } + } + } + }, + "responses": { + "204": { + "description": "Timer created." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/Timers/{timerId}": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets a timer.", + "operationId": "GetTimer", + "parameters": [ + { + "name": "timerId", + "in": "path", + "description": "Timer id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Timer returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TimerInfoDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/TimerInfoDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/TimerInfoDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "LiveTv" + ], + "summary": "Cancels a live tv timer.", + "operationId": "CancelTimer", + "parameters": [ + { + "name": "timerId", + "in": "path", + "description": "Timer id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Timer deleted." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvManagement", + "DefaultAuthorization" + ] + } + ] + }, + "post": { + "tags": [ + "LiveTv" + ], + "summary": "Updates a live tv timer.", + "operationId": "UpdateTimer", + "parameters": [ + { + "name": "timerId", + "in": "path", + "description": "Timer id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "New timer info.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/TimerInfoDto" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/TimerInfoDto" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/TimerInfoDto" + } + ] + } + } + } + }, + "responses": { + "204": { + "description": "Timer updated." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/Timers/Defaults": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Gets the default values for a new timer.", + "operationId": "GetDefaultTimer", + "parameters": [ + { + "name": "programId", + "in": "query", + "description": "Optional. To attach default values based on a program.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Default values returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SeriesTimerInfoDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/SeriesTimerInfoDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/SeriesTimerInfoDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/TunerHosts": { + "post": { + "tags": [ + "LiveTv" + ], + "summary": "Adds a tuner host.", + "operationId": "AddTunerHost", + "requestBody": { + "description": "New tuner host.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/TunerHostInfo" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/TunerHostInfo" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/TunerHostInfo" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "Created tuner host returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TunerHostInfo" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/TunerHostInfo" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/TunerHostInfo" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvManagement", + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "LiveTv" + ], + "summary": "Deletes a tuner host.", + "operationId": "DeleteTunerHost", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "Tuner host id.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Tuner host deleted." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/TunerHosts/Types": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Get tuner host types.", + "operationId": "GetTunerHostTypes", + "responses": { + "200": { + "description": "Tuner host types returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameIdPair" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameIdPair" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameIdPair" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/Tuners/{tunerId}/Reset": { + "post": { + "tags": [ + "LiveTv" + ], + "summary": "Resets a tv tuner.", + "operationId": "ResetTuner", + "parameters": [ + { + "name": "tunerId", + "in": "path", + "description": "Tuner id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Tuner reset." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/Tuners/Discover": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Discover tuners.", + "operationId": "DiscoverTuners", + "parameters": [ + { + "name": "newDevicesOnly", + "in": "query", + "description": "Only discover new tuners.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Tuners returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TunerHostInfo" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TunerHostInfo" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TunerHostInfo" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveTv/Tuners/Discvover": { + "get": { + "tags": [ + "LiveTv" + ], + "summary": "Discover tuners.", + "operationId": "DiscvoverTuners", + "parameters": [ + { + "name": "newDevicesOnly", + "in": "query", + "description": "Only discover new tuners.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Tuners returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TunerHostInfo" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TunerHostInfo" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TunerHostInfo" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LiveTvManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Localization/Countries": { + "get": { + "tags": [ + "Localization" + ], + "summary": "Gets known countries.", + "operationId": "GetCountries", + "responses": { + "200": { + "description": "Known countries returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CountryInfo" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CountryInfo" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CountryInfo" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrDefault", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Localization/Cultures": { + "get": { + "tags": [ + "Localization" + ], + "summary": "Gets known cultures.", + "operationId": "GetCultures", + "responses": { + "200": { + "description": "Known cultures returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CultureDto" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CultureDto" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CultureDto" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrDefault", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Localization/Options": { + "get": { + "tags": [ + "Localization" + ], + "summary": "Gets localization options.", + "operationId": "GetLocalizationOptions", + "responses": { + "200": { + "description": "Localization options returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LocalizationOption" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LocalizationOption" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LocalizationOption" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrDefault", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Localization/ParentalRatings": { + "get": { + "tags": [ + "Localization" + ], + "summary": "Gets known parental ratings.", + "operationId": "GetParentalRatings", + "responses": { + "200": { + "description": "Known parental ratings returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ParentalRating" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ParentalRating" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ParentalRating" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrDefault", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Audio/{itemId}/Lyrics": { + "get": { + "tags": [ + "Lyrics" + ], + "summary": "Gets an item's lyrics.", + "operationId": "GetLyrics", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Lyrics returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LyricDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/LyricDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/LyricDto" + } + } + } + }, + "404": { + "description": "Something went wrong. No Lyrics will be returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "post": { + "tags": [ + "Lyrics" + ], + "summary": "Upload an external lyric file.", + "operationId": "UploadLyrics", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item the lyric belongs to.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "fileName", + "in": "query", + "description": "Name of the file being uploaded.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "text/plain": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "200": { + "description": "Lyrics uploaded.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LyricDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/LyricDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/LyricDto" + } + } + } + }, + "400": { + "description": "Error processing upload.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LyricManagement", + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "Lyrics" + ], + "summary": "Deletes an external lyric file.", + "operationId": "DeleteLyrics", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Lyric deleted." + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LyricManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Audio/{itemId}/RemoteSearch/Lyrics": { + "get": { + "tags": [ + "Lyrics" + ], + "summary": "Search remote lyrics.", + "operationId": "SearchRemoteLyrics", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Lyrics retrieved.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteLyricInfoDto" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteLyricInfoDto" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteLyricInfoDto" + } + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LyricManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Audio/{itemId}/RemoteSearch/Lyrics/{lyricId}": { + "post": { + "tags": [ + "Lyrics" + ], + "summary": "Downloads a remote lyric.", + "operationId": "DownloadRemoteLyrics", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "lyricId", + "in": "path", + "description": "The lyric id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Lyric downloaded.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LyricDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/LyricDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/LyricDto" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LyricManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Providers/Lyrics/{lyricId}": { + "get": { + "tags": [ + "Lyrics" + ], + "summary": "Gets the remote lyrics.", + "operationId": "GetRemoteLyrics", + "parameters": [ + { + "name": "lyricId", + "in": "path", + "description": "The remote provider item id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "File returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LyricDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/LyricDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/LyricDto" + } + } + } + }, + "404": { + "description": "Lyric not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LyricManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/PlaybackInfo": { + "get": { + "tags": [ + "MediaInfo" + ], + "summary": "Gets live playback media info for an item.", + "operationId": "GetPlaybackInfo", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "The user id.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Playback info returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlaybackInfoResponse" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/PlaybackInfoResponse" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/PlaybackInfoResponse" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "post": { + "tags": [ + "MediaInfo" + ], + "summary": "Gets live playback media info for an item.", + "description": "For backwards compatibility parameters can be sent via Query or Body, with Query having higher precedence.\r\nQuery parameters are obsolete.", + "operationId": "GetPostedPlaybackInfo", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "The user id.", + "deprecated": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "maxStreamingBitrate", + "in": "query", + "description": "The maximum streaming bitrate.", + "deprecated": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "The start time in ticks.", + "deprecated": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "The audio stream index.", + "deprecated": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "The subtitle stream index.", + "deprecated": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "The maximum number of audio channels.", + "deprecated": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media source id.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The livestream id.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "autoOpenLiveStream", + "in": "query", + "description": "Whether to auto open the livestream.", + "deprecated": true, + "schema": { + "type": "boolean" + } + }, + { + "name": "enableDirectPlay", + "in": "query", + "description": "Whether to enable direct play. Default: true.", + "deprecated": true, + "schema": { + "type": "boolean" + } + }, + { + "name": "enableDirectStream", + "in": "query", + "description": "Whether to enable direct stream. Default: true.", + "deprecated": true, + "schema": { + "type": "boolean" + } + }, + { + "name": "enableTranscoding", + "in": "query", + "description": "Whether to enable transcoding. Default: true.", + "deprecated": true, + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether to allow to copy the video stream. Default: true.", + "deprecated": true, + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether to allow to copy the audio stream. Default: true.", + "deprecated": true, + "schema": { + "type": "boolean" + } + } + ], + "requestBody": { + "description": "The playback info.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PlaybackInfoDto" + } + ], + "description": "Playback info dto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PlaybackInfoDto" + } + ], + "description": "Playback info dto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PlaybackInfoDto" + } + ], + "description": "Playback info dto." + } + } + } + }, + "responses": { + "200": { + "description": "Playback info returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlaybackInfoResponse" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/PlaybackInfoResponse" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/PlaybackInfoResponse" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveStreams/Close": { + "post": { + "tags": [ + "MediaInfo" + ], + "summary": "Closes a media source.", + "operationId": "CloseLiveStream", + "parameters": [ + { + "name": "liveStreamId", + "in": "query", + "description": "The livestream id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Livestream closed." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/LiveStreams/Open": { + "post": { + "tags": [ + "MediaInfo" + ], + "summary": "Opens a media source.", + "operationId": "OpenLiveStream", + "parameters": [ + { + "name": "openToken", + "in": "query", + "description": "The open token.", + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "query", + "description": "The user id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "maxStreamingBitrate", + "in": "query", + "description": "The maximum streaming bitrate.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "The start time in ticks.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "The audio stream index.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "The subtitle stream index.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "The maximum number of audio channels.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "itemId", + "in": "query", + "description": "The item id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "enableDirectPlay", + "in": "query", + "description": "Whether to enable direct play. Default: true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableDirectStream", + "in": "query", + "description": "Whether to enable direct stream. Default: true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "alwaysBurnInSubtitleWhenTranscoding", + "in": "query", + "description": "Always burn-in subtitle when transcoding.", + "schema": { + "type": "boolean" + } + } + ], + "requestBody": { + "description": "The open live stream dto.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/OpenLiveStreamDto" + } + ], + "description": "Open live stream dto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/OpenLiveStreamDto" + } + ], + "description": "Open live stream dto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/OpenLiveStreamDto" + } + ], + "description": "Open live stream dto." + } + } + } + }, + "responses": { + "200": { + "description": "Media source opened.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/LiveStreamResponse" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/LiveStreamResponse" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/LiveStreamResponse" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Playback/BitrateTest": { + "get": { + "tags": [ + "MediaInfo" + ], + "summary": "Tests the network with a request with the size of the bitrate.", + "operationId": "GetBitrateTestBytes", + "parameters": [ + { + "name": "size", + "in": "query", + "description": "The bitrate. Defaults to 102400.", + "schema": { + "maximum": 100000000, + "minimum": 1, + "type": "integer", + "format": "int32", + "default": 102400 + } + } + ], + "responses": { + "200": { + "description": "Test buffer returned.", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/MediaSegments/{itemId}": { + "get": { + "tags": [ + "MediaSegments" + ], + "summary": "Gets all media segments based on an itemId.", + "operationId": "GetItemSegments", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The ItemId.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "includeSegmentTypes", + "in": "query", + "description": "Optional filter of requested segment types.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaSegmentType" + } + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MediaSegmentDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/MediaSegmentDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/MediaSegmentDtoQueryResult" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Movies/Recommendations": { + "get": { + "tags": [ + "Movies" + ], + "summary": "Gets movie recommendations.", + "operationId": "GetMovieRecommendations", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "parentId", + "in": "query", + "description": "Specify this to localize the search to a specific item or folder. Omit to use the root.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. The fields to return.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "categoryLimit", + "in": "query", + "description": "The max number of categories to return.", + "schema": { + "type": "integer", + "format": "int32", + "default": 5 + } + }, + { + "name": "itemLimit", + "in": "query", + "description": "The max number of items to return per category.", + "schema": { + "type": "integer", + "format": "int32", + "default": 8 + } + } + ], + "responses": { + "200": { + "description": "Movie recommendations returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RecommendationDto" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RecommendationDto" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RecommendationDto" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/MusicGenres": { + "get": { + "tags": [ + "MusicGenres" + ], + "summary": "Gets all music genres from a given item, folder, or the entire library.", + "operationId": "GetMusicGenres", + "parameters": [ + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "searchTerm", + "in": "query", + "description": "The search term.", + "schema": { + "type": "string" + } + }, + { + "name": "parentId", + "in": "query", + "description": "Specify this to localize the search to a specific item or folder. Omit to use the root.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "excludeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "includeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered in based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "isFavorite", + "in": "query", + "description": "Optional filter by items that are marked as favorite, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional, the max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "nameStartsWithOrGreater", + "in": "query", + "description": "Optional filter by items whose name is sorted equally or greater than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "nameStartsWith", + "in": "query", + "description": "Optional filter by items whose name is sorted equally than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "nameLessThan", + "in": "query", + "description": "Optional filter by items whose name is equally or lesser than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Optional. Specify one or more sort orders, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemSortBy" + } + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Sort Order - Ascending,Descending.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SortOrder" + } + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional, include image information in output.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "enableTotalRecordCount", + "in": "query", + "description": "Optional. Include total record count.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Music genres returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "deprecated": true, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/MusicGenres/{genreName}": { + "get": { + "tags": [ + "MusicGenres" + ], + "summary": "Gets a music genre, by name.", + "operationId": "GetMusicGenre", + "parameters": [ + { + "name": "genreName", + "in": "path", + "description": "The genre name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Packages": { + "get": { + "tags": [ + "Package" + ], + "summary": "Gets available packages.", + "operationId": "GetPackages", + "responses": { + "200": { + "description": "Available packages returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PackageInfo" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PackageInfo" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PackageInfo" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Packages/{name}": { + "get": { + "tags": [ + "Package" + ], + "summary": "Gets a package by name or assembly GUID.", + "operationId": "GetPackageInfo", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "The name of the package.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "assemblyGuid", + "in": "query", + "description": "The GUID of the associated assembly.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Package retrieved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PackageInfo" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/PackageInfo" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/PackageInfo" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Packages/Installed/{name}": { + "post": { + "tags": [ + "Package" + ], + "summary": "Installs a package.", + "operationId": "InstallPackage", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Package name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "assemblyGuid", + "in": "query", + "description": "GUID of the associated assembly.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "version", + "in": "query", + "description": "Optional version. Defaults to latest version.", + "schema": { + "type": "string" + } + }, + { + "name": "repositoryUrl", + "in": "query", + "description": "Optional. Specify the repository to install from.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Package found." + }, + "404": { + "description": "Package not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Packages/Installing/{packageId}": { + "delete": { + "tags": [ + "Package" + ], + "summary": "Cancels a package installation.", + "operationId": "CancelPackageInstallation", + "parameters": [ + { + "name": "packageId", + "in": "path", + "description": "Installation Id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Installation cancelled." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Repositories": { + "get": { + "tags": [ + "Package" + ], + "summary": "Gets all package repositories.", + "operationId": "GetRepositories", + "responses": { + "200": { + "description": "Package repositories returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RepositoryInfo" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RepositoryInfo" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RepositoryInfo" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + }, + "post": { + "tags": [ + "Package" + ], + "summary": "Sets the enabled and existing package repositories.", + "operationId": "SetRepositories", + "requestBody": { + "description": "The list of package repositories.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RepositoryInfo" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RepositoryInfo" + } + } + }, + "application/*+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RepositoryInfo" + } + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Package repositories saved." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Persons": { + "get": { + "tags": [ + "Persons" + ], + "summary": "Gets all persons.", + "operationId": "GetPersons", + "parameters": [ + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "searchTerm", + "in": "query", + "description": "The search term.", + "schema": { + "type": "string" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "filters", + "in": "query", + "description": "Optional. Specify additional filters to apply.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFilter" + } + } + }, + { + "name": "isFavorite", + "in": "query", + "description": "Optional filter by items that are marked as favorite, or not. userId is required.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional, include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional, the max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "excludePersonTypes", + "in": "query", + "description": "Optional. If specified results will be filtered to exclude those containing the specified PersonType. Allows multiple, comma-delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "personTypes", + "in": "query", + "description": "Optional. If specified results will be filtered to include only those containing the specified PersonType. Allows multiple, comma-delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "appearsInItemId", + "in": "query", + "description": "Optional. If specified, person results will be filtered on items related to said persons.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional, include image information in output.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Persons returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Persons/{name}": { + "get": { + "tags": [ + "Persons" + ], + "summary": "Get person by name.", + "operationId": "GetPerson", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Person name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Person returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + } + }, + "404": { + "description": "Person not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Playlists": { + "post": { + "tags": [ + "Playlists" + ], + "summary": "Creates a new playlist.", + "description": "For backwards compatibility parameters can be sent via Query or Body, with Query having higher precedence.\r\nQuery parameters are obsolete.", + "operationId": "CreatePlaylist", + "parameters": [ + { + "name": "name", + "in": "query", + "description": "The playlist name.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "ids", + "in": "query", + "description": "The item ids.", + "deprecated": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "userId", + "in": "query", + "description": "The user id.", + "deprecated": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "mediaType", + "in": "query", + "description": "The media type.", + "deprecated": true, + "schema": { + "enum": [ + "Unknown", + "Video", + "Audio", + "Photo", + "Book" + ], + "allOf": [ + { + "$ref": "#/components/schemas/MediaType" + } + ] + } + } + ], + "requestBody": { + "description": "The create playlist payload.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/CreatePlaylistDto" + } + ], + "description": "Create new playlist dto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/CreatePlaylistDto" + } + ], + "description": "Create new playlist dto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/CreatePlaylistDto" + } + ], + "description": "Create new playlist dto." + } + } + } + }, + "responses": { + "200": { + "description": "Playlist created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlaylistCreationResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/PlaylistCreationResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/PlaylistCreationResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Playlists/{playlistId}": { + "post": { + "tags": [ + "Playlists" + ], + "summary": "Updates a playlist.", + "operationId": "UpdatePlaylist", + "parameters": [ + { + "name": "playlistId", + "in": "path", + "description": "The playlist id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "The Jellyfin.Api.Models.PlaylistDtos.UpdatePlaylistDto id.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdatePlaylistDto" + } + ], + "description": "Update existing playlist dto. Fields set to `null` will not be updated and keep their current values." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdatePlaylistDto" + } + ], + "description": "Update existing playlist dto. Fields set to `null` will not be updated and keep their current values." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdatePlaylistDto" + } + ], + "description": "Update existing playlist dto. Fields set to `null` will not be updated and keep their current values." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Playlist updated." + }, + "403": { + "description": "Access forbidden.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Playlist not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "get": { + "tags": [ + "Playlists" + ], + "summary": "Get a playlist.", + "operationId": "GetPlaylist", + "parameters": [ + { + "name": "playlistId", + "in": "path", + "description": "The playlist id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "The playlist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlaylistDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/PlaylistDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/PlaylistDto" + } + } + } + }, + "404": { + "description": "Playlist not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Playlists/{playlistId}/Items": { + "post": { + "tags": [ + "Playlists" + ], + "summary": "Adds items to a playlist.", + "operationId": "AddItemToPlaylist", + "parameters": [ + { + "name": "playlistId", + "in": "path", + "description": "The playlist id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "ids", + "in": "query", + "description": "Item id, comma delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "userId", + "in": "query", + "description": "The userId.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Items added to playlist." + }, + "403": { + "description": "Access forbidden.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Playlist not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "Playlists" + ], + "summary": "Removes items from a playlist.", + "operationId": "RemoveItemFromPlaylist", + "parameters": [ + { + "name": "playlistId", + "in": "path", + "description": "The playlist id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "entryIds", + "in": "query", + "description": "The item ids, comma delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "204": { + "description": "Items removed." + }, + "403": { + "description": "Access forbidden.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Playlist not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "get": { + "tags": [ + "Playlists" + ], + "summary": "Gets the original items of a playlist.", + "operationId": "GetPlaylistItems", + "parameters": [ + { + "name": "playlistId", + "in": "path", + "description": "The playlist id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + } + ], + "responses": { + "200": { + "description": "Original playlist returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Playlist not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Playlists/{playlistId}/Items/{itemId}/Move/{newIndex}": { + "post": { + "tags": [ + "Playlists" + ], + "summary": "Moves a playlist item.", + "operationId": "MoveItem", + "parameters": [ + { + "name": "playlistId", + "in": "path", + "description": "The playlist id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "newIndex", + "in": "path", + "description": "The new index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Item moved to new index." + }, + "403": { + "description": "Access forbidden.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Playlist not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Playlists/{playlistId}/Users": { + "get": { + "tags": [ + "Playlists" + ], + "summary": "Get a playlist's users.", + "operationId": "GetPlaylistUsers", + "parameters": [ + { + "name": "playlistId", + "in": "path", + "description": "The playlist id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Found shares.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlaylistUserPermissions" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlaylistUserPermissions" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlaylistUserPermissions" + } + } + } + } + }, + "403": { + "description": "Access forbidden.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Playlist not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Playlists/{playlistId}/Users/{userId}": { + "get": { + "tags": [ + "Playlists" + ], + "summary": "Get a playlist user.", + "operationId": "GetPlaylistUser", + "parameters": [ + { + "name": "playlistId", + "in": "path", + "description": "The playlist id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "path", + "description": "The user id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "User permission found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PlaylistUserPermissions" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/PlaylistUserPermissions" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/PlaylistUserPermissions" + } + } + } + }, + "403": { + "description": "Access forbidden.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Playlist not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "post": { + "tags": [ + "Playlists" + ], + "summary": "Modify a user of a playlist's users.", + "operationId": "UpdatePlaylistUser", + "parameters": [ + { + "name": "playlistId", + "in": "path", + "description": "The playlist id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "path", + "description": "The user id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "The Jellyfin.Api.Models.PlaylistDtos.UpdatePlaylistUserDto.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdatePlaylistUserDto" + } + ], + "description": "Update existing playlist user dto. Fields set to `null` will not be updated and keep their current values." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdatePlaylistUserDto" + } + ], + "description": "Update existing playlist user dto. Fields set to `null` will not be updated and keep their current values." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdatePlaylistUserDto" + } + ], + "description": "Update existing playlist user dto. Fields set to `null` will not be updated and keep their current values." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "User's permissions modified." + }, + "403": { + "description": "Access forbidden.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Playlist not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "Playlists" + ], + "summary": "Remove a user from a playlist's users.", + "operationId": "RemoveUserFromPlaylist", + "parameters": [ + { + "name": "playlistId", + "in": "path", + "description": "The playlist id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "path", + "description": "The user id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "User permissions removed from playlist." + }, + "403": { + "description": "Forbidden", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "No playlist or user permissions found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "401": { + "description": "Unauthorized access." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/PlayingItems/{itemId}": { + "post": { + "tags": [ + "Playstate" + ], + "summary": "Reports that a session has begun playing an item.", + "operationId": "OnPlaybackStart", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The id of the MediaSource.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "The audio stream index.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "The subtitle stream index.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "playMethod", + "in": "query", + "description": "The play method.", + "schema": { + "enum": [ + "Transcode", + "DirectStream", + "DirectPlay" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PlayMethod" + } + ] + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "canSeek", + "in": "query", + "description": "Indicates if the client can seek.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "204": { + "description": "Play start recorded." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "deprecated": true, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "Playstate" + ], + "summary": "Reports that a session has stopped playing an item.", + "operationId": "OnPlaybackStopped", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The id of the MediaSource.", + "schema": { + "type": "string" + } + }, + { + "name": "nextMediaType", + "in": "query", + "description": "The next media type that will play.", + "schema": { + "type": "string" + } + }, + { + "name": "positionTicks", + "in": "query", + "description": "Optional. The position, in ticks, where playback stopped. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Playback stop recorded." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "deprecated": true, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/PlayingItems/{itemId}/Progress": { + "post": { + "tags": [ + "Playstate" + ], + "summary": "Reports a session's playback progress.", + "operationId": "OnPlaybackProgress", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The id of the MediaSource.", + "schema": { + "type": "string" + } + }, + { + "name": "positionTicks", + "in": "query", + "description": "Optional. The current position, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "The audio stream index.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "The subtitle stream index.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "volumeLevel", + "in": "query", + "description": "Scale of 0-100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "playMethod", + "in": "query", + "description": "The play method.", + "schema": { + "enum": [ + "Transcode", + "DirectStream", + "DirectPlay" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PlayMethod" + } + ] + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "repeatMode", + "in": "query", + "description": "The repeat mode.", + "schema": { + "enum": [ + "RepeatNone", + "RepeatAll", + "RepeatOne" + ], + "allOf": [ + { + "$ref": "#/components/schemas/RepeatMode" + } + ] + } + }, + { + "name": "isPaused", + "in": "query", + "description": "Indicates if the player is paused.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "isMuted", + "in": "query", + "description": "Indicates if the player is muted.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "204": { + "description": "Play progress recorded." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "deprecated": true, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Sessions/Playing": { + "post": { + "tags": [ + "Playstate" + ], + "summary": "Reports playback has started within a session.", + "operationId": "ReportPlaybackStart", + "requestBody": { + "description": "The playback start info.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PlaybackStartInfo" + } + ], + "description": "Class PlaybackStartInfo." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PlaybackStartInfo" + } + ], + "description": "Class PlaybackStartInfo." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PlaybackStartInfo" + } + ], + "description": "Class PlaybackStartInfo." + } + } + } + }, + "responses": { + "204": { + "description": "Playback start recorded." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Sessions/Playing/Ping": { + "post": { + "tags": [ + "Playstate" + ], + "summary": "Pings a playback session.", + "operationId": "PingPlaybackSession", + "parameters": [ + { + "name": "playSessionId", + "in": "query", + "description": "Playback session id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Playback session pinged." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Sessions/Playing/Progress": { + "post": { + "tags": [ + "Playstate" + ], + "summary": "Reports playback progress within a session.", + "operationId": "ReportPlaybackProgress", + "requestBody": { + "description": "The playback progress info.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PlaybackProgressInfo" + } + ], + "description": "Class PlaybackProgressInfo." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PlaybackProgressInfo" + } + ], + "description": "Class PlaybackProgressInfo." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PlaybackProgressInfo" + } + ], + "description": "Class PlaybackProgressInfo." + } + } + } + }, + "responses": { + "204": { + "description": "Playback progress recorded." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Sessions/Playing/Stopped": { + "post": { + "tags": [ + "Playstate" + ], + "summary": "Reports playback has stopped within a session.", + "operationId": "ReportPlaybackStopped", + "requestBody": { + "description": "The playback stop info.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PlaybackStopInfo" + } + ], + "description": "Class PlaybackStopInfo." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PlaybackStopInfo" + } + ], + "description": "Class PlaybackStopInfo." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PlaybackStopInfo" + } + ], + "description": "Class PlaybackStopInfo." + } + } + } + }, + "responses": { + "204": { + "description": "Playback stop recorded." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/UserPlayedItems/{itemId}": { + "post": { + "tags": [ + "Playstate" + ], + "summary": "Marks an item as played for user.", + "operationId": "MarkPlayedItem", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "datePlayed", + "in": "query", + "description": "Optional. The date the item was played.", + "schema": { + "type": "string", + "format": "date-time" + } + } + ], + "responses": { + "200": { + "description": "Item marked as played.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "Playstate" + ], + "summary": "Marks an item as unplayed for user.", + "operationId": "MarkUnplayedItem", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Item marked as unplayed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Plugins": { + "get": { + "tags": [ + "Plugins" + ], + "summary": "Gets a list of currently installed plugins.", + "operationId": "GetPlugins", + "responses": { + "200": { + "description": "Installed plugins returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PluginInfo" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PluginInfo" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PluginInfo" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Plugins/{pluginId}": { + "delete": { + "tags": [ + "Plugins" + ], + "summary": "Uninstalls a plugin.", + "operationId": "UninstallPlugin", + "parameters": [ + { + "name": "pluginId", + "in": "path", + "description": "Plugin id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Plugin uninstalled." + }, + "404": { + "description": "Plugin not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "deprecated": true, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Plugins/{pluginId}/{version}": { + "delete": { + "tags": [ + "Plugins" + ], + "summary": "Uninstalls a plugin by version.", + "operationId": "UninstallPluginByVersion", + "parameters": [ + { + "name": "pluginId", + "in": "path", + "description": "Plugin id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "version", + "in": "path", + "description": "Plugin version.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Plugin uninstalled." + }, + "404": { + "description": "Plugin not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Plugins/{pluginId}/{version}/Disable": { + "post": { + "tags": [ + "Plugins" + ], + "summary": "Disable a plugin.", + "operationId": "DisablePlugin", + "parameters": [ + { + "name": "pluginId", + "in": "path", + "description": "Plugin id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "version", + "in": "path", + "description": "Plugin version.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Plugin disabled." + }, + "404": { + "description": "Plugin not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Plugins/{pluginId}/{version}/Enable": { + "post": { + "tags": [ + "Plugins" + ], + "summary": "Enables a disabled plugin.", + "operationId": "EnablePlugin", + "parameters": [ + { + "name": "pluginId", + "in": "path", + "description": "Plugin id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "version", + "in": "path", + "description": "Plugin version.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Plugin enabled." + }, + "404": { + "description": "Plugin not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Plugins/{pluginId}/{version}/Image": { + "get": { + "tags": [ + "Plugins" + ], + "summary": "Gets a plugin's image.", + "operationId": "GetPluginImage", + "parameters": [ + { + "name": "pluginId", + "in": "path", + "description": "Plugin id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "version", + "in": "path", + "description": "Plugin version.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Plugin image returned.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Plugins/{pluginId}/Configuration": { + "get": { + "tags": [ + "Plugins" + ], + "summary": "Gets plugin configuration.", + "operationId": "GetPluginConfiguration", + "parameters": [ + { + "name": "pluginId", + "in": "path", + "description": "Plugin id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Plugin configuration returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BasePluginConfiguration" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BasePluginConfiguration" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BasePluginConfiguration" + } + } + } + }, + "404": { + "description": "Plugin not found or plugin configuration not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + }, + "post": { + "tags": [ + "Plugins" + ], + "summary": "Updates plugin configuration.", + "description": "Accepts plugin configuration as JSON body.", + "operationId": "UpdatePluginConfiguration", + "parameters": [ + { + "name": "pluginId", + "in": "path", + "description": "Plugin id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Plugin configuration updated." + }, + "404": { + "description": "Plugin not found or plugin does not have configuration.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Plugins/{pluginId}/Manifest": { + "post": { + "tags": [ + "Plugins" + ], + "summary": "Gets a plugin's manifest.", + "operationId": "GetPluginManifest", + "parameters": [ + { + "name": "pluginId", + "in": "path", + "description": "Plugin id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Plugin manifest returned." + }, + "404": { + "description": "Plugin not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/QuickConnect/Authorize": { + "post": { + "tags": [ + "QuickConnect" + ], + "summary": "Authorizes a pending quick connect request.", + "operationId": "AuthorizeQuickConnect", + "parameters": [ + { + "name": "code", + "in": "query", + "description": "Quick connect code to authorize.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "query", + "description": "The user the authorize. Access to the requested user is required.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Quick connect result authorized successfully.", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "boolean" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "boolean" + } + } + } + }, + "403": { + "description": "Unknown user id.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/QuickConnect/Connect": { + "get": { + "tags": [ + "QuickConnect" + ], + "summary": "Attempts to retrieve authentication information.", + "operationId": "GetQuickConnectState", + "parameters": [ + { + "name": "secret", + "in": "query", + "description": "Secret previously returned from the Initiate endpoint.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Quick connect result returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuickConnectResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/QuickConnectResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/QuickConnectResult" + } + } + } + }, + "404": { + "description": "Unknown quick connect secret.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/QuickConnect/Enabled": { + "get": { + "tags": [ + "QuickConnect" + ], + "summary": "Gets the current quick connect state.", + "operationId": "GetQuickConnectEnabled", + "responses": { + "200": { + "description": "Quick connect state returned.", + "content": { + "application/json": { + "schema": { + "type": "boolean" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "boolean" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "boolean" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/QuickConnect/Initiate": { + "post": { + "tags": [ + "QuickConnect" + ], + "summary": "Initiate a new quick connect request.", + "operationId": "InitiateQuickConnect", + "responses": { + "200": { + "description": "Quick connect request successfully created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/QuickConnectResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/QuickConnectResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/QuickConnectResult" + } + } + } + }, + "401": { + "description": "Quick connect is not active on this server." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Items/{itemId}/RemoteImages": { + "get": { + "tags": [ + "RemoteImage" + ], + "summary": "Gets available remote images for an item.", + "operationId": "GetRemoteImages", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item Id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "type", + "in": "query", + "description": "The image type.", + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ] + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "providerName", + "in": "query", + "description": "Optional. The image provider to use.", + "schema": { + "type": "string" + } + }, + { + "name": "includeAllLanguages", + "in": "query", + "description": "Optional. Include all languages.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Remote Images returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RemoteImageResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/RemoteImageResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/RemoteImageResult" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/RemoteImages/Download": { + "post": { + "tags": [ + "RemoteImage" + ], + "summary": "Downloads a remote image for an item.", + "operationId": "DownloadRemoteImage", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item Id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "type", + "in": "query", + "description": "The image type.", + "required": true, + "schema": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Enum ImageType." + } + }, + { + "name": "imageUrl", + "in": "query", + "description": "The image url.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Remote image downloaded." + }, + "404": { + "description": "Remote image not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Items/{itemId}/RemoteImages/Providers": { + "get": { + "tags": [ + "RemoteImage" + ], + "summary": "Gets available remote image providers for an item.", + "operationId": "GetRemoteImageProviders", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "Item Id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Returned remote image providers.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageProviderInfo" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageProviderInfo" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageProviderInfo" + } + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/ScheduledTasks": { + "get": { + "tags": [ + "ScheduledTasks" + ], + "summary": "Get tasks.", + "operationId": "GetTasks", + "parameters": [ + { + "name": "isHidden", + "in": "query", + "description": "Optional filter tasks that are hidden, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isEnabled", + "in": "query", + "description": "Optional filter tasks that are enabled, or not.", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Scheduled tasks retrieved.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskInfo" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskInfo" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskInfo" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/ScheduledTasks/{taskId}": { + "get": { + "tags": [ + "ScheduledTasks" + ], + "summary": "Get task by id.", + "operationId": "GetTask", + "parameters": [ + { + "name": "taskId", + "in": "path", + "description": "Task Id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Task retrieved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/TaskInfo" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/TaskInfo" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/TaskInfo" + } + } + } + }, + "404": { + "description": "Task not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/ScheduledTasks/{taskId}/Triggers": { + "post": { + "tags": [ + "ScheduledTasks" + ], + "summary": "Update specified task triggers.", + "operationId": "UpdateTask", + "parameters": [ + { + "name": "taskId", + "in": "path", + "description": "Task Id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Triggers.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskTriggerInfo" + } + } + }, + "text/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskTriggerInfo" + } + } + }, + "application/*+json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskTriggerInfo" + } + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Task triggers updated." + }, + "404": { + "description": "Task not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/ScheduledTasks/Running/{taskId}": { + "post": { + "tags": [ + "ScheduledTasks" + ], + "summary": "Start specified task.", + "operationId": "StartTask", + "parameters": [ + { + "name": "taskId", + "in": "path", + "description": "Task Id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Task started." + }, + "404": { + "description": "Task not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + }, + "delete": { + "tags": [ + "ScheduledTasks" + ], + "summary": "Stop specified task.", + "operationId": "StopTask", + "parameters": [ + { + "name": "taskId", + "in": "path", + "description": "Task Id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Task stopped." + }, + "404": { + "description": "Task not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Search/Hints": { + "get": { + "tags": [ + "Search" + ], + "summary": "Gets the search hint result.", + "operationId": "GetSearchHints", + "parameters": [ + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Supply a user id to search within a user's library or omit to search all.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "searchTerm", + "in": "query", + "description": "The search term to filter on.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "includeItemTypes", + "in": "query", + "description": "If specified, only results with the specified item types are returned. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "excludeItemTypes", + "in": "query", + "description": "If specified, results with these item types are filtered out. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "mediaTypes", + "in": "query", + "description": "If specified, only results with the specified media types are returned. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaType" + } + } + }, + { + "name": "parentId", + "in": "query", + "description": "If specified, only children of the parent are returned.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "isMovie", + "in": "query", + "description": "Optional filter for movies.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isSeries", + "in": "query", + "description": "Optional filter for series.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isNews", + "in": "query", + "description": "Optional filter for news.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isKids", + "in": "query", + "description": "Optional filter for kids.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isSports", + "in": "query", + "description": "Optional filter for sports.", + "schema": { + "type": "boolean" + } + }, + { + "name": "includePeople", + "in": "query", + "description": "Optional filter whether to include people.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "includeMedia", + "in": "query", + "description": "Optional filter whether to include media.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "includeGenres", + "in": "query", + "description": "Optional filter whether to include genres.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "includeStudios", + "in": "query", + "description": "Optional filter whether to include studios.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "includeArtists", + "in": "query", + "description": "Optional filter whether to include artists.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Search hint returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchHintResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/SearchHintResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/SearchHintResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Auth/PasswordResetProviders": { + "get": { + "tags": [ + "Session" + ], + "summary": "Get all password reset providers.", + "operationId": "GetPasswordResetProviders", + "responses": { + "200": { + "description": "Password reset providers retrieved.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameIdPair" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameIdPair" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameIdPair" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Auth/Providers": { + "get": { + "tags": [ + "Session" + ], + "summary": "Get all auth providers.", + "operationId": "GetAuthProviders", + "responses": { + "200": { + "description": "Auth providers retrieved.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameIdPair" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameIdPair" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameIdPair" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Sessions": { + "get": { + "tags": [ + "Session" + ], + "summary": "Gets a list of sessions.", + "operationId": "GetSessions", + "parameters": [ + { + "name": "controllableByUserId", + "in": "query", + "description": "Filter by sessions that a given user is allowed to remote control.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "Filter by device Id.", + "schema": { + "type": "string" + } + }, + { + "name": "activeWithinSeconds", + "in": "query", + "description": "Optional. Filter by sessions that were active in the last n seconds.", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "List of sessions returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionInfoDto" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionInfoDto" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionInfoDto" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Sessions/{sessionId}/Command": { + "post": { + "tags": [ + "Session" + ], + "summary": "Issues a full general command to a client.", + "operationId": "SendFullGeneralCommand", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "description": "The session id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The MediaBrowser.Model.Session.GeneralCommand.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/GeneralCommand" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/GeneralCommand" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/GeneralCommand" + } + ] + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Full general command sent to session." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Sessions/{sessionId}/Command/{command}": { + "post": { + "tags": [ + "Session" + ], + "summary": "Issues a general command to a client.", + "operationId": "SendGeneralCommand", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "description": "The session id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "command", + "in": "path", + "description": "The command to send.", + "required": true, + "schema": { + "enum": [ + "MoveUp", + "MoveDown", + "MoveLeft", + "MoveRight", + "PageUp", + "PageDown", + "PreviousLetter", + "NextLetter", + "ToggleOsd", + "ToggleContextMenu", + "Select", + "Back", + "TakeScreenshot", + "SendKey", + "SendString", + "GoHome", + "GoToSettings", + "VolumeUp", + "VolumeDown", + "Mute", + "Unmute", + "ToggleMute", + "SetVolume", + "SetAudioStreamIndex", + "SetSubtitleStreamIndex", + "ToggleFullscreen", + "DisplayContent", + "GoToSearch", + "DisplayMessage", + "SetRepeatMode", + "ChannelUp", + "ChannelDown", + "Guide", + "ToggleStats", + "PlayMediaSource", + "PlayTrailers", + "SetShuffleQueue", + "PlayState", + "PlayNext", + "ToggleOsdMenu", + "Play", + "SetMaxStreamingBitrate", + "SetPlaybackOrder" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GeneralCommandType" + } + ], + "description": "This exists simply to identify a set of known commands." + } + } + ], + "responses": { + "204": { + "description": "General command sent to session." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Sessions/{sessionId}/Message": { + "post": { + "tags": [ + "Session" + ], + "summary": "Issues a command to a client to display a message to the user.", + "operationId": "SendMessageCommand", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "description": "The session id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The MediaBrowser.Model.Session.MessageCommand object containing Header, Message Text, and TimeoutMs.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MessageCommand" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MessageCommand" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MessageCommand" + } + ] + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Message sent." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Sessions/{sessionId}/Playing": { + "post": { + "tags": [ + "Session" + ], + "summary": "Instructs a session to play an item.", + "operationId": "Play", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "description": "The session id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "playCommand", + "in": "query", + "description": "The type of play command to issue (PlayNow, PlayNext, PlayLast). Clients who have not yet implemented play next and play last may play now.", + "required": true, + "schema": { + "enum": [ + "PlayNow", + "PlayNext", + "PlayLast", + "PlayInstantMix", + "PlayShuffle" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PlayCommand" + } + ], + "description": "Enum PlayCommand." + } + }, + { + "name": "itemIds", + "in": "query", + "description": "The ids of the items to play, comma delimited.", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "startPositionTicks", + "in": "query", + "description": "The starting position of the first item.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "Optional. The media source id.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to play.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to play.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The start index.", + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Instruction sent to session." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Sessions/{sessionId}/Playing/{command}": { + "post": { + "tags": [ + "Session" + ], + "summary": "Issues a playstate command to a client.", + "operationId": "SendPlaystateCommand", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "description": "The session id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "command", + "in": "path", + "description": "The MediaBrowser.Model.Session.PlaystateCommand.", + "required": true, + "schema": { + "enum": [ + "Stop", + "Pause", + "Unpause", + "NextTrack", + "PreviousTrack", + "Seek", + "Rewind", + "FastForward", + "PlayPause" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PlaystateCommand" + } + ], + "description": "Enum PlaystateCommand." + } + }, + { + "name": "seekPositionTicks", + "in": "query", + "description": "The optional position ticks.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "controllingUserId", + "in": "query", + "description": "The optional controlling user id.", + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Playstate command sent to session." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Sessions/{sessionId}/System/{command}": { + "post": { + "tags": [ + "Session" + ], + "summary": "Issues a system command to a client.", + "operationId": "SendSystemCommand", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "description": "The session id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "command", + "in": "path", + "description": "The command to send.", + "required": true, + "schema": { + "enum": [ + "MoveUp", + "MoveDown", + "MoveLeft", + "MoveRight", + "PageUp", + "PageDown", + "PreviousLetter", + "NextLetter", + "ToggleOsd", + "ToggleContextMenu", + "Select", + "Back", + "TakeScreenshot", + "SendKey", + "SendString", + "GoHome", + "GoToSettings", + "VolumeUp", + "VolumeDown", + "Mute", + "Unmute", + "ToggleMute", + "SetVolume", + "SetAudioStreamIndex", + "SetSubtitleStreamIndex", + "ToggleFullscreen", + "DisplayContent", + "GoToSearch", + "DisplayMessage", + "SetRepeatMode", + "ChannelUp", + "ChannelDown", + "Guide", + "ToggleStats", + "PlayMediaSource", + "PlayTrailers", + "SetShuffleQueue", + "PlayState", + "PlayNext", + "ToggleOsdMenu", + "Play", + "SetMaxStreamingBitrate", + "SetPlaybackOrder" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GeneralCommandType" + } + ], + "description": "This exists simply to identify a set of known commands." + } + } + ], + "responses": { + "204": { + "description": "System command sent to session." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Sessions/{sessionId}/User/{userId}": { + "post": { + "tags": [ + "Session" + ], + "summary": "Adds an additional user to a session.", + "operationId": "AddUserToSession", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "description": "The session id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "path", + "description": "The user id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "User added to session." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "Session" + ], + "summary": "Removes an additional user from a session.", + "operationId": "RemoveUserFromSession", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "description": "The session id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "path", + "description": "The user id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "User removed from session." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Sessions/{sessionId}/Viewing": { + "post": { + "tags": [ + "Session" + ], + "summary": "Instructs a session to browse to an item or view.", + "operationId": "DisplayContent", + "parameters": [ + { + "name": "sessionId", + "in": "path", + "description": "The session Id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "itemType", + "in": "query", + "description": "The type of item to browse to.", + "required": true, + "schema": { + "enum": [ + "AggregateFolder", + "Audio", + "AudioBook", + "BasePluginFolder", + "Book", + "BoxSet", + "Channel", + "ChannelFolderItem", + "CollectionFolder", + "Episode", + "Folder", + "Genre", + "ManualPlaylistsFolder", + "Movie", + "LiveTvChannel", + "LiveTvProgram", + "MusicAlbum", + "MusicArtist", + "MusicGenre", + "MusicVideo", + "Person", + "Photo", + "PhotoAlbum", + "Playlist", + "PlaylistsFolder", + "Program", + "Recording", + "Season", + "Series", + "Studio", + "Trailer", + "TvChannel", + "TvProgram", + "UserRootFolder", + "UserView", + "Video", + "Year" + ], + "allOf": [ + { + "$ref": "#/components/schemas/BaseItemKind" + } + ], + "description": "The base item kind." + } + }, + { + "name": "itemId", + "in": "query", + "description": "The Id of the item.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "itemName", + "in": "query", + "description": "The name of the item.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Instruction sent to session." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Sessions/Capabilities": { + "post": { + "tags": [ + "Session" + ], + "summary": "Updates capabilities for a device.", + "operationId": "PostCapabilities", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "The session id.", + "schema": { + "type": "string" + } + }, + { + "name": "playableMediaTypes", + "in": "query", + "description": "A list of playable media types, comma delimited. Audio, Video, Book, Photo.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaType" + } + } + }, + { + "name": "supportedCommands", + "in": "query", + "description": "A list of supported remote control commands, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GeneralCommandType" + } + } + }, + { + "name": "supportsMediaControl", + "in": "query", + "description": "Determines whether media can be played remotely..", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "supportsPersistentIdentifier", + "in": "query", + "description": "Determines whether the device supports a unique identifier.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "204": { + "description": "Capabilities posted." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Sessions/Capabilities/Full": { + "post": { + "tags": [ + "Session" + ], + "summary": "Updates capabilities for a device.", + "operationId": "PostFullCapabilities", + "parameters": [ + { + "name": "id", + "in": "query", + "description": "The session id.", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "The MediaBrowser.Model.Session.ClientCapabilities.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ClientCapabilitiesDto" + } + ], + "description": "Client capabilities dto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ClientCapabilitiesDto" + } + ], + "description": "Client capabilities dto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ClientCapabilitiesDto" + } + ], + "description": "Client capabilities dto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Capabilities updated." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Sessions/Logout": { + "post": { + "tags": [ + "Session" + ], + "summary": "Reports that a session has ended.", + "operationId": "ReportSessionEnded", + "responses": { + "204": { + "description": "Session end reported to server." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Sessions/Viewing": { + "post": { + "tags": [ + "Session" + ], + "summary": "Reports that a session is viewing an item.", + "operationId": "ReportViewing", + "parameters": [ + { + "name": "sessionId", + "in": "query", + "description": "The session id.", + "schema": { + "type": "string" + } + }, + { + "name": "itemId", + "in": "query", + "description": "The item id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Session reported to server." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Startup/Complete": { + "post": { + "tags": [ + "Startup" + ], + "summary": "Completes the startup wizard.", + "operationId": "CompleteWizard", + "responses": { + "204": { + "description": "Startup wizard completed." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Startup/Configuration": { + "get": { + "tags": [ + "Startup" + ], + "summary": "Gets the initial startup wizard configuration.", + "operationId": "GetStartupConfiguration", + "responses": { + "200": { + "description": "Initial startup wizard configuration retrieved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StartupConfigurationDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/StartupConfigurationDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/StartupConfigurationDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + }, + "post": { + "tags": [ + "Startup" + ], + "summary": "Sets the initial startup wizard configuration.", + "operationId": "UpdateInitialConfiguration", + "requestBody": { + "description": "The updated startup configuration.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/StartupConfigurationDto" + } + ], + "description": "The startup configuration DTO." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/StartupConfigurationDto" + } + ], + "description": "The startup configuration DTO." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/StartupConfigurationDto" + } + ], + "description": "The startup configuration DTO." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Configuration saved." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Startup/FirstUser": { + "get": { + "tags": [ + "Startup" + ], + "summary": "Gets the first user.", + "operationId": "GetFirstUser_2", + "responses": { + "200": { + "description": "Initial user retrieved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StartupUserDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/StartupUserDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/StartupUserDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Startup/RemoteAccess": { + "post": { + "tags": [ + "Startup" + ], + "summary": "Sets remote access and UPnP.", + "operationId": "SetRemoteAccess", + "requestBody": { + "description": "The startup remote access dto.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/StartupRemoteAccessDto" + } + ], + "description": "Startup remote access dto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/StartupRemoteAccessDto" + } + ], + "description": "Startup remote access dto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/StartupRemoteAccessDto" + } + ], + "description": "Startup remote access dto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Configuration saved." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Startup/User": { + "get": { + "tags": [ + "Startup" + ], + "summary": "Gets the first user.", + "operationId": "GetFirstUser", + "responses": { + "200": { + "description": "Initial user retrieved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/StartupUserDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/StartupUserDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/StartupUserDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + }, + "post": { + "tags": [ + "Startup" + ], + "summary": "Sets the user name and password.", + "operationId": "UpdateStartupUser", + "requestBody": { + "description": "The DTO containing username and password.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/StartupUserDto" + } + ], + "description": "The startup user DTO." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/StartupUserDto" + } + ], + "description": "The startup user DTO." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/StartupUserDto" + } + ], + "description": "The startup user DTO." + } + } + } + }, + "responses": { + "204": { + "description": "Updated user name and password." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrElevated", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Studios": { + "get": { + "tags": [ + "Studios" + ], + "summary": "Gets all studios from a given item, folder, or the entire library.", + "operationId": "GetStudios", + "parameters": [ + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "searchTerm", + "in": "query", + "description": "Optional. Search term.", + "schema": { + "type": "string" + } + }, + { + "name": "parentId", + "in": "query", + "description": "Specify this to localize the search to a specific item or folder. Omit to use the root.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "excludeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered out based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "includeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "isFavorite", + "in": "query", + "description": "Optional filter by items that are marked as favorite, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional, include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional, the max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "nameStartsWithOrGreater", + "in": "query", + "description": "Optional filter by items whose name is sorted equally or greater than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "nameStartsWith", + "in": "query", + "description": "Optional filter by items whose name is sorted equally than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "nameLessThan", + "in": "query", + "description": "Optional filter by items whose name is equally or lesser than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional, include image information in output.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "enableTotalRecordCount", + "in": "query", + "description": "Total record count.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Studios returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Studios/{name}": { + "get": { + "tags": [ + "Studios" + ], + "summary": "Gets a studio by name.", + "operationId": "GetStudio", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "Studio name.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Studio returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/FallbackFont/Fonts": { + "get": { + "tags": [ + "Subtitle" + ], + "summary": "Gets a list of available fallback font files.", + "operationId": "GetFallbackFontList", + "responses": { + "200": { + "description": "Information retrieved.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FontFile" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FontFile" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FontFile" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/FallbackFont/Fonts/{name}": { + "get": { + "tags": [ + "Subtitle" + ], + "summary": "Gets a fallback font file.", + "operationId": "GetFallbackFont", + "parameters": [ + { + "name": "name", + "in": "path", + "description": "The name of the fallback font file to get.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Fallback font file retrieved.", + "content": { + "font/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/RemoteSearch/Subtitles/{language}": { + "get": { + "tags": [ + "Subtitle" + ], + "summary": "Search remote subtitles.", + "operationId": "SearchRemoteSubtitles", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "language", + "in": "path", + "description": "The language of the subtitles.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "isPerfectMatch", + "in": "query", + "description": "Optional. Only show subtitles which are a perfect match.", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Subtitles retrieved.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSubtitleInfo" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSubtitleInfo" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSubtitleInfo" + } + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SubtitleManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/RemoteSearch/Subtitles/{subtitleId}": { + "post": { + "tags": [ + "Subtitle" + ], + "summary": "Downloads a remote subtitle.", + "operationId": "DownloadRemoteSubtitles", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "subtitleId", + "in": "path", + "description": "The subtitle id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "204": { + "description": "Subtitle downloaded." + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SubtitleManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Providers/Subtitles/Subtitles/{subtitleId}": { + "get": { + "tags": [ + "Subtitle" + ], + "summary": "Gets the remote subtitles.", + "operationId": "GetRemoteSubtitles", + "parameters": [ + { + "name": "subtitleId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "File returned.", + "content": { + "text/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SubtitleManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Videos/{itemId}/{mediaSourceId}/Subtitles/{index}/subtitles.m3u8": { + "get": { + "tags": [ + "Subtitle" + ], + "summary": "Gets an HLS subtitle playlist.", + "operationId": "GetSubtitlePlaylist", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "index", + "in": "path", + "description": "The subtitle stream index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "path", + "description": "The media source id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The subtitle segment length.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Subtitle playlist retrieved.", + "content": { + "application/x-mpegURL": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Videos/{itemId}/Subtitles": { + "post": { + "tags": [ + "Subtitle" + ], + "summary": "Upload an external subtitle file.", + "operationId": "UploadSubtitle", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item the subtitle belongs to.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "The request body.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UploadSubtitleDto" + } + ], + "description": "Upload subtitles dto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UploadSubtitleDto" + } + ], + "description": "Upload subtitles dto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UploadSubtitleDto" + } + ], + "description": "Upload subtitles dto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Subtitle uploaded." + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SubtitleManagement", + "DefaultAuthorization" + ] + } + ] + } + }, + "/Videos/{itemId}/Subtitles/{index}": { + "delete": { + "tags": [ + "Subtitle" + ], + "summary": "Deletes an external subtitle file.", + "operationId": "DeleteSubtitle", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "index", + "in": "path", + "description": "The index of the subtitle file.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "204": { + "description": "Subtitle deleted." + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Videos/{routeItemId}/{routeMediaSourceId}/Subtitles/{routeIndex}/{routeStartPositionTicks}/Stream.{routeFormat}": { + "get": { + "tags": [ + "Subtitle" + ], + "summary": "Gets subtitles in a specified format.", + "operationId": "GetSubtitleWithTicks", + "parameters": [ + { + "name": "routeItemId", + "in": "path", + "description": "The (route) item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "routeMediaSourceId", + "in": "path", + "description": "The (route) media source id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "routeIndex", + "in": "path", + "description": "The (route) subtitle stream index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "routeStartPositionTicks", + "in": "path", + "description": "The (route) start position of the subtitle in ticks.", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "routeFormat", + "in": "path", + "description": "The (route) format of the returned subtitle.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "itemId", + "in": "query", + "description": "The item id.", + "deprecated": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media source id.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "index", + "in": "query", + "description": "The subtitle stream index.", + "deprecated": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "startPositionTicks", + "in": "query", + "description": "The start position of the subtitle in ticks.", + "deprecated": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "format", + "in": "query", + "description": "The format of the returned subtitle.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "endPositionTicks", + "in": "query", + "description": "Optional. The end position of the subtitle in ticks.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Optional. Whether to copy the timestamps.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "addVttTimeMap", + "in": "query", + "description": "Optional. Whether to add a VTT time map.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "File returned.", + "content": { + "text/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Videos/{routeItemId}/{routeMediaSourceId}/Subtitles/{routeIndex}/Stream.{routeFormat}": { + "get": { + "tags": [ + "Subtitle" + ], + "summary": "Gets subtitles in a specified format.", + "operationId": "GetSubtitle", + "parameters": [ + { + "name": "routeItemId", + "in": "path", + "description": "The (route) item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "routeMediaSourceId", + "in": "path", + "description": "The (route) media source id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "routeIndex", + "in": "path", + "description": "The (route) subtitle stream index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "routeFormat", + "in": "path", + "description": "The (route) format of the returned subtitle.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "itemId", + "in": "query", + "description": "The item id.", + "deprecated": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media source id.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "index", + "in": "query", + "description": "The subtitle stream index.", + "deprecated": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "format", + "in": "query", + "description": "The format of the returned subtitle.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "endPositionTicks", + "in": "query", + "description": "Optional. The end position of the subtitle in ticks.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Optional. Whether to copy the timestamps.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "addVttTimeMap", + "in": "query", + "description": "Optional. Whether to add a VTT time map.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "startPositionTicks", + "in": "query", + "description": "The start position of the subtitle in ticks.", + "schema": { + "type": "integer", + "format": "int64", + "default": 0 + } + } + ], + "responses": { + "200": { + "description": "File returned.", + "content": { + "text/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Items/Suggestions": { + "get": { + "tags": [ + "Suggestions" + ], + "summary": "Gets suggestions.", + "operationId": "GetSuggestions", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "The user id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "mediaType", + "in": "query", + "description": "The media types.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaType" + } + } + }, + { + "name": "type", + "in": "query", + "description": "The type.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The start index.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The limit.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableTotalRecordCount", + "in": "query", + "description": "Whether to enable the total record count.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Suggestions returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/{id}": { + "get": { + "tags": [ + "SyncPlay" + ], + "summary": "Gets a SyncPlay group by id.", + "operationId": "SyncPlayGetGroup", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The id of the group.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Group returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupInfoDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/GroupInfoDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/GroupInfoDto" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayJoinGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/Buffering": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Notify SyncPlay group that member is buffering.", + "operationId": "SyncPlayBuffering", + "requestBody": { + "description": "The player status.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BufferRequestDto" + } + ], + "description": "Class BufferRequestDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BufferRequestDto" + } + ], + "description": "Class BufferRequestDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/BufferRequestDto" + } + ], + "description": "Class BufferRequestDto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Group state update sent to all group members." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayIsInGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/Join": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Join an existing SyncPlay group.", + "operationId": "SyncPlayJoinGroup", + "requestBody": { + "description": "The group to join.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/JoinGroupRequestDto" + } + ], + "description": "Class JoinGroupRequestDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/JoinGroupRequestDto" + } + ], + "description": "Class JoinGroupRequestDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/JoinGroupRequestDto" + } + ], + "description": "Class JoinGroupRequestDto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Group join successful." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayJoinGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/Leave": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Leave the joined SyncPlay group.", + "operationId": "SyncPlayLeaveGroup", + "responses": { + "204": { + "description": "Group leave successful." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayIsInGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/List": { + "get": { + "tags": [ + "SyncPlay" + ], + "summary": "Gets all SyncPlay groups.", + "operationId": "SyncPlayGetGroups", + "responses": { + "200": { + "description": "Groups returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GroupInfoDto" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GroupInfoDto" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GroupInfoDto" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayJoinGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/MovePlaylistItem": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Request to move an item in the playlist in SyncPlay group.", + "operationId": "SyncPlayMovePlaylistItem", + "requestBody": { + "description": "The new position for the item.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MovePlaylistItemRequestDto" + } + ], + "description": "Class MovePlaylistItemRequestDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MovePlaylistItemRequestDto" + } + ], + "description": "Class MovePlaylistItemRequestDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/MovePlaylistItemRequestDto" + } + ], + "description": "Class MovePlaylistItemRequestDto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Queue update sent to all group members." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayIsInGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/New": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Create a new SyncPlay group.", + "operationId": "SyncPlayCreateGroup", + "requestBody": { + "description": "The settings of the new group.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/NewGroupRequestDto" + } + ], + "description": "Class NewGroupRequestDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/NewGroupRequestDto" + } + ], + "description": "Class NewGroupRequestDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/NewGroupRequestDto" + } + ], + "description": "Class NewGroupRequestDto." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GroupInfoDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/GroupInfoDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/GroupInfoDto" + } + } + } + }, + "204": { + "description": "New group created." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayCreateGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/NextItem": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Request next item in SyncPlay group.", + "operationId": "SyncPlayNextItem", + "requestBody": { + "description": "The current item information.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/NextItemRequestDto" + } + ], + "description": "Class NextItemRequestDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/NextItemRequestDto" + } + ], + "description": "Class NextItemRequestDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/NextItemRequestDto" + } + ], + "description": "Class NextItemRequestDto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Next item update sent to all group members." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayIsInGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/Pause": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Request pause in SyncPlay group.", + "operationId": "SyncPlayPause", + "responses": { + "204": { + "description": "Pause update sent to all group members." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayIsInGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/Ping": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Update session ping.", + "operationId": "SyncPlayPing", + "requestBody": { + "description": "The new ping.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PingRequestDto" + } + ], + "description": "Class PingRequestDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PingRequestDto" + } + ], + "description": "Class PingRequestDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PingRequestDto" + } + ], + "description": "Class PingRequestDto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Ping updated." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/PreviousItem": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Request previous item in SyncPlay group.", + "operationId": "SyncPlayPreviousItem", + "requestBody": { + "description": "The current item information.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PreviousItemRequestDto" + } + ], + "description": "Class PreviousItemRequestDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PreviousItemRequestDto" + } + ], + "description": "Class PreviousItemRequestDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PreviousItemRequestDto" + } + ], + "description": "Class PreviousItemRequestDto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Previous item update sent to all group members." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayIsInGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/Queue": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Request to queue items to the playlist of a SyncPlay group.", + "operationId": "SyncPlayQueue", + "requestBody": { + "description": "The items to add.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/QueueRequestDto" + } + ], + "description": "Class QueueRequestDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/QueueRequestDto" + } + ], + "description": "Class QueueRequestDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/QueueRequestDto" + } + ], + "description": "Class QueueRequestDto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Queue update sent to all group members." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayIsInGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/Ready": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Notify SyncPlay group that member is ready for playback.", + "operationId": "SyncPlayReady", + "requestBody": { + "description": "The player status.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ReadyRequestDto" + } + ], + "description": "Class ReadyRequest." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ReadyRequestDto" + } + ], + "description": "Class ReadyRequest." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ReadyRequestDto" + } + ], + "description": "Class ReadyRequest." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Group state update sent to all group members." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayIsInGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/RemoveFromPlaylist": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Request to remove items from the playlist in SyncPlay group.", + "operationId": "SyncPlayRemoveFromPlaylist", + "requestBody": { + "description": "The items to remove.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/RemoveFromPlaylistRequestDto" + } + ], + "description": "Class RemoveFromPlaylistRequestDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/RemoveFromPlaylistRequestDto" + } + ], + "description": "Class RemoveFromPlaylistRequestDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/RemoveFromPlaylistRequestDto" + } + ], + "description": "Class RemoveFromPlaylistRequestDto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Queue update sent to all group members." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayIsInGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/Seek": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Request seek in SyncPlay group.", + "operationId": "SyncPlaySeek", + "requestBody": { + "description": "The new playback position.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SeekRequestDto" + } + ], + "description": "Class SeekRequestDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SeekRequestDto" + } + ], + "description": "Class SeekRequestDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SeekRequestDto" + } + ], + "description": "Class SeekRequestDto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Seek update sent to all group members." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayIsInGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/SetIgnoreWait": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Request SyncPlay group to ignore member during group-wait.", + "operationId": "SyncPlaySetIgnoreWait", + "requestBody": { + "description": "The settings to set.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/IgnoreWaitRequestDto" + } + ], + "description": "Class IgnoreWaitRequestDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/IgnoreWaitRequestDto" + } + ], + "description": "Class IgnoreWaitRequestDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/IgnoreWaitRequestDto" + } + ], + "description": "Class IgnoreWaitRequestDto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Member state updated." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayIsInGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/SetNewQueue": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Request to set new playlist in SyncPlay group.", + "operationId": "SyncPlaySetNewQueue", + "requestBody": { + "description": "The new playlist to play in the group.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PlayRequestDto" + } + ], + "description": "Class PlayRequestDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PlayRequestDto" + } + ], + "description": "Class PlayRequestDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PlayRequestDto" + } + ], + "description": "Class PlayRequestDto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Queue update sent to all group members." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayIsInGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/SetPlaylistItem": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Request to change playlist item in SyncPlay group.", + "operationId": "SyncPlaySetPlaylistItem", + "requestBody": { + "description": "The new item to play.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SetPlaylistItemRequestDto" + } + ], + "description": "Class SetPlaylistItemRequestDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SetPlaylistItemRequestDto" + } + ], + "description": "Class SetPlaylistItemRequestDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SetPlaylistItemRequestDto" + } + ], + "description": "Class SetPlaylistItemRequestDto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Queue update sent to all group members." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayIsInGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/SetRepeatMode": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Request to set repeat mode in SyncPlay group.", + "operationId": "SyncPlaySetRepeatMode", + "requestBody": { + "description": "The new repeat mode.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SetRepeatModeRequestDto" + } + ], + "description": "Class SetRepeatModeRequestDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SetRepeatModeRequestDto" + } + ], + "description": "Class SetRepeatModeRequestDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SetRepeatModeRequestDto" + } + ], + "description": "Class SetRepeatModeRequestDto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Play queue update sent to all group members." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayIsInGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/SetShuffleMode": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Request to set shuffle mode in SyncPlay group.", + "operationId": "SyncPlaySetShuffleMode", + "requestBody": { + "description": "The new shuffle mode.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SetShuffleModeRequestDto" + } + ], + "description": "Class SetShuffleModeRequestDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SetShuffleModeRequestDto" + } + ], + "description": "Class SetShuffleModeRequestDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/SetShuffleModeRequestDto" + } + ], + "description": "Class SetShuffleModeRequestDto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Play queue update sent to all group members." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayIsInGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/Stop": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Request stop in SyncPlay group.", + "operationId": "SyncPlayStop", + "responses": { + "204": { + "description": "Stop update sent to all group members." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayIsInGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/SyncPlay/Unpause": { + "post": { + "tags": [ + "SyncPlay" + ], + "summary": "Request unpause in SyncPlay group.", + "operationId": "SyncPlayUnpause", + "responses": { + "204": { + "description": "Unpause update sent to all group members." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "SyncPlayIsInGroup", + "SyncPlayHasAccess", + "DefaultAuthorization" + ] + } + ] + } + }, + "/System/Endpoint": { + "get": { + "tags": [ + "System" + ], + "summary": "Gets information about the request endpoint.", + "operationId": "GetEndpointInfo", + "responses": { + "200": { + "description": "Information retrieved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EndPointInfo" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/EndPointInfo" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/EndPointInfo" + } + } + } + }, + "403": { + "description": "User does not have permission to get endpoint information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/System/Info": { + "get": { + "tags": [ + "System" + ], + "summary": "Gets information about the server.", + "operationId": "GetSystemInfo", + "responses": { + "200": { + "description": "Information retrieved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemInfo" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/SystemInfo" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/SystemInfo" + } + } + } + }, + "403": { + "description": "User does not have permission to retrieve information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "FirstTimeSetupOrIgnoreParentalControl", + "DefaultAuthorization" + ] + } + ] + } + }, + "/System/Info/Public": { + "get": { + "tags": [ + "System" + ], + "summary": "Gets public information about the server.", + "operationId": "GetPublicSystemInfo", + "responses": { + "200": { + "description": "Information retrieved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PublicSystemInfo" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/PublicSystemInfo" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/PublicSystemInfo" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/System/Info/Storage": { + "get": { + "tags": [ + "System" + ], + "summary": "Gets information about the server.", + "operationId": "GetSystemStorage", + "responses": { + "200": { + "description": "Information retrieved.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SystemStorageDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/SystemStorageDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/SystemStorageDto" + } + } + } + }, + "403": { + "description": "User does not have permission to retrieve information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/System/Logs": { + "get": { + "tags": [ + "System" + ], + "summary": "Gets a list of available server log files.", + "operationId": "GetServerLogs", + "responses": { + "200": { + "description": "Information retrieved.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LogFile" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LogFile" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LogFile" + } + } + } + } + }, + "403": { + "description": "User does not have permission to get server logs.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/System/Logs/Log": { + "get": { + "tags": [ + "System" + ], + "summary": "Gets a log file.", + "operationId": "GetLogFile", + "parameters": [ + { + "name": "name", + "in": "query", + "description": "The name of the log file to get.", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Log file retrieved.", + "content": { + "text/plain": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "403": { + "description": "User does not have permission to get log files.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "Could not find a log file with the name.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/System/Ping": { + "get": { + "tags": [ + "System" + ], + "summary": "Pings the system.", + "operationId": "GetPingSystem", + "responses": { + "200": { + "description": "Information retrieved.", + "content": { + "application/json": { + "schema": { + "type": "string" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "string" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "string" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "post": { + "tags": [ + "System" + ], + "summary": "Pings the system.", + "operationId": "PostPingSystem", + "responses": { + "200": { + "description": "Information retrieved.", + "content": { + "application/json": { + "schema": { + "type": "string" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "string" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "string" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/System/Restart": { + "post": { + "tags": [ + "System" + ], + "summary": "Restarts the application.", + "operationId": "RestartApplication", + "responses": { + "204": { + "description": "Server restarted." + }, + "403": { + "description": "User does not have permission to restart server.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "LocalAccessOrRequiresElevation" + ] + } + ] + } + }, + "/System/Shutdown": { + "post": { + "tags": [ + "System" + ], + "summary": "Shuts down the application.", + "operationId": "ShutdownApplication", + "responses": { + "204": { + "description": "Server shut down." + }, + "403": { + "description": "User does not have permission to shutdown server.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/GetUtcTime": { + "get": { + "tags": [ + "TimeSync" + ], + "summary": "Gets the current UTC time.", + "operationId": "GetUtcTime", + "responses": { + "200": { + "description": "Time returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UtcTimeResponse" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/UtcTimeResponse" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/UtcTimeResponse" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Tmdb/ClientConfiguration": { + "get": { + "tags": [ + "Tmdb" + ], + "summary": "Gets the TMDb image configuration options.", + "operationId": "TmdbClientConfiguration", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ConfigImageTypes" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Trailers": { + "get": { + "tags": [ + "Trailers" + ], + "summary": "Finds movies and trailers similar to a given trailer.", + "operationId": "GetTrailers", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "The user id supplied as query parameter; this is required when not using an API key.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "maxOfficialRating", + "in": "query", + "description": "Optional filter by maximum official rating (PG, PG-13, TV-MA, etc).", + "schema": { + "type": "string" + } + }, + { + "name": "hasThemeSong", + "in": "query", + "description": "Optional filter by items with theme songs.", + "schema": { + "type": "boolean" + } + }, + { + "name": "hasThemeVideo", + "in": "query", + "description": "Optional filter by items with theme videos.", + "schema": { + "type": "boolean" + } + }, + { + "name": "hasSubtitles", + "in": "query", + "description": "Optional filter by items with subtitles.", + "schema": { + "type": "boolean" + } + }, + { + "name": "hasSpecialFeature", + "in": "query", + "description": "Optional filter by items with special features.", + "schema": { + "type": "boolean" + } + }, + { + "name": "hasTrailer", + "in": "query", + "description": "Optional filter by items with trailers.", + "schema": { + "type": "boolean" + } + }, + { + "name": "adjacentTo", + "in": "query", + "description": "Optional. Return items that are siblings of a supplied item.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "parentIndexNumber", + "in": "query", + "description": "Optional filter by parent index number.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "hasParentalRating", + "in": "query", + "description": "Optional filter by items that have or do not have a parental rating.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isHd", + "in": "query", + "description": "Optional filter by items that are HD or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "is4K", + "in": "query", + "description": "Optional filter by items that are 4K or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "locationTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered based on LocationType. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LocationType" + } + } + }, + { + "name": "excludeLocationTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered based on the LocationType. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LocationType" + } + } + }, + { + "name": "isMissing", + "in": "query", + "description": "Optional filter by items that are missing episodes or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isUnaired", + "in": "query", + "description": "Optional filter by items that are unaired episodes or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "minCommunityRating", + "in": "query", + "description": "Optional filter by minimum community rating.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "minCriticRating", + "in": "query", + "description": "Optional filter by minimum critic rating.", + "schema": { + "type": "number", + "format": "double" + } + }, + { + "name": "minPremiereDate", + "in": "query", + "description": "Optional. The minimum premiere date. Format = ISO.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "minDateLastSaved", + "in": "query", + "description": "Optional. The minimum last saved date. Format = ISO.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "minDateLastSavedForUser", + "in": "query", + "description": "Optional. The minimum last saved date for the current user. Format = ISO.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "maxPremiereDate", + "in": "query", + "description": "Optional. The maximum premiere date. Format = ISO.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "hasOverview", + "in": "query", + "description": "Optional filter by items that have an overview or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "hasImdbId", + "in": "query", + "description": "Optional filter by items that have an IMDb id or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "hasTmdbId", + "in": "query", + "description": "Optional filter by items that have a TMDb id or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "hasTvdbId", + "in": "query", + "description": "Optional filter by items that have a TVDb id or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isMovie", + "in": "query", + "description": "Optional filter for live tv movies.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isSeries", + "in": "query", + "description": "Optional filter for live tv series.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isNews", + "in": "query", + "description": "Optional filter for live tv news.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isKids", + "in": "query", + "description": "Optional filter for live tv kids.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isSports", + "in": "query", + "description": "Optional filter for live tv sports.", + "schema": { + "type": "boolean" + } + }, + { + "name": "excludeItemIds", + "in": "query", + "description": "Optional. If specified, results will be filtered by excluding item ids. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "recursive", + "in": "query", + "description": "When searching within folders, this determines whether or not the search will be recursive. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "searchTerm", + "in": "query", + "description": "Optional. Filter based on a search term.", + "schema": { + "type": "string" + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Sort Order - Ascending, Descending.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SortOrder" + } + } + }, + { + "name": "parentId", + "in": "query", + "description": "Specify this to localize the search to a specific item or folder. Omit to use the root.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "excludeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "filters", + "in": "query", + "description": "Optional. Specify additional filters to apply. This allows multiple, comma delimited. Options: IsFolder, IsNotFolder, IsUnplayed, IsPlayed, IsFavorite, IsResumable, Likes, Dislikes.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFilter" + } + } + }, + { + "name": "isFavorite", + "in": "query", + "description": "Optional filter by items that are marked as favorite, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "mediaTypes", + "in": "query", + "description": "Optional filter by MediaType. Allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaType" + } + } + }, + { + "name": "imageTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered based on those containing image types. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemSortBy" + } + } + }, + { + "name": "isPlayed", + "in": "query", + "description": "Optional filter by items that are played, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "genres", + "in": "query", + "description": "Optional. If specified, results will be filtered based on genre. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "officialRatings", + "in": "query", + "description": "Optional. If specified, results will be filtered based on OfficialRating. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "tags", + "in": "query", + "description": "Optional. If specified, results will be filtered based on tag. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "years", + "in": "query", + "description": "Optional. If specified, results will be filtered based on production year. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + } + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional, include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional, the max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "person", + "in": "query", + "description": "Optional. If specified, results will be filtered to include only those containing the specified person.", + "schema": { + "type": "string" + } + }, + { + "name": "personIds", + "in": "query", + "description": "Optional. If specified, results will be filtered to include only those containing the specified person id.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "personTypes", + "in": "query", + "description": "Optional. If specified, along with Person, results will be filtered to include only those containing the specified person and PersonType. Allows multiple, comma-delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "studios", + "in": "query", + "description": "Optional. If specified, results will be filtered based on studio. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "artists", + "in": "query", + "description": "Optional. If specified, results will be filtered based on artists. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "excludeArtistIds", + "in": "query", + "description": "Optional. If specified, results will be filtered based on artist id. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "artistIds", + "in": "query", + "description": "Optional. If specified, results will be filtered to include only those containing the specified artist id.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "albumArtistIds", + "in": "query", + "description": "Optional. If specified, results will be filtered to include only those containing the specified album artist id.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "contributingArtistIds", + "in": "query", + "description": "Optional. If specified, results will be filtered to include only those containing the specified contributing artist id.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "albums", + "in": "query", + "description": "Optional. If specified, results will be filtered based on album. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "albumIds", + "in": "query", + "description": "Optional. If specified, results will be filtered based on album id. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "ids", + "in": "query", + "description": "Optional. If specific items are needed, specify a list of item id's to retrieve. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "videoTypes", + "in": "query", + "description": "Optional filter by VideoType (videofile, dvd, bluray, iso). Allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VideoType" + } + } + }, + { + "name": "minOfficialRating", + "in": "query", + "description": "Optional filter by minimum official rating (PG, PG-13, TV-MA, etc).", + "schema": { + "type": "string" + } + }, + { + "name": "isLocked", + "in": "query", + "description": "Optional filter by items that are locked.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isPlaceHolder", + "in": "query", + "description": "Optional filter by items that are placeholders.", + "schema": { + "type": "boolean" + } + }, + { + "name": "hasOfficialRating", + "in": "query", + "description": "Optional filter by items that have official ratings.", + "schema": { + "type": "boolean" + } + }, + { + "name": "collapseBoxSetItems", + "in": "query", + "description": "Whether or not to hide items behind their boxsets.", + "schema": { + "type": "boolean" + } + }, + { + "name": "minWidth", + "in": "query", + "description": "Optional. Filter by the minimum width of the item.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minHeight", + "in": "query", + "description": "Optional. Filter by the minimum height of the item.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "Optional. Filter by the maximum width of the item.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "Optional. Filter by the maximum height of the item.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "is3D", + "in": "query", + "description": "Optional filter by items that are 3D, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "seriesStatus", + "in": "query", + "description": "Optional filter by Series Status. Allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SeriesStatus" + } + } + }, + { + "name": "nameStartsWithOrGreater", + "in": "query", + "description": "Optional filter by items whose name is sorted equally or greater than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "nameStartsWith", + "in": "query", + "description": "Optional filter by items whose name is sorted equally than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "nameLessThan", + "in": "query", + "description": "Optional filter by items whose name is equally or lesser than a given input string.", + "schema": { + "type": "string" + } + }, + { + "name": "studioIds", + "in": "query", + "description": "Optional. If specified, results will be filtered based on studio id. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "genreIds", + "in": "query", + "description": "Optional. If specified, results will be filtered based on genre id. This allows multiple, pipe delimited.", + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + }, + { + "name": "enableTotalRecordCount", + "in": "query", + "description": "Optional. Enable the total record count.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional, include image information in output.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Videos/{itemId}/Trickplay/{width}/{index}.jpg": { + "get": { + "tags": [ + "Trickplay" + ], + "summary": "Gets a trickplay tile image.", + "operationId": "GetTrickplayTileImage", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "width", + "in": "path", + "description": "The width of a single tile.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "index", + "in": "path", + "description": "The index of the desired tile.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if using an alternate version.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Tile image not found at specified index.", + "content": { + "image/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Videos/{itemId}/Trickplay/{width}/tiles.m3u8": { + "get": { + "tags": [ + "Trickplay" + ], + "summary": "Gets an image tiles playlist for trickplay.", + "operationId": "GetTrickplayHlsPlaylist", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "width", + "in": "path", + "description": "The width of a single tile.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if using an alternate version.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Tiles playlist returned.", + "content": { + "application/x-mpegURL": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Shows/{seriesId}/Episodes": { + "get": { + "tags": [ + "TvShows" + ], + "summary": "Gets episodes for a tv season.", + "operationId": "GetEpisodes", + "parameters": [ + { + "name": "seriesId", + "in": "path", + "description": "The series id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "The user id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "season", + "in": "query", + "description": "Optional filter by season number.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "seasonId", + "in": "query", + "description": "Optional. Filter by season id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "isMissing", + "in": "query", + "description": "Optional. Filter by items that are missing episodes or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "adjacentTo", + "in": "query", + "description": "Optional. Return items that are siblings of a supplied item.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "startItemId", + "in": "query", + "description": "Optional. Skip through the list until a given item is found.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional, include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional, the max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.", + "schema": { + "enum": [ + "Default", + "AiredEpisodeOrder", + "Album", + "AlbumArtist", + "Artist", + "DateCreated", + "OfficialRating", + "DatePlayed", + "PremiereDate", + "StartDate", + "SortName", + "Name", + "Random", + "Runtime", + "CommunityRating", + "ProductionYear", + "PlayCount", + "CriticRating", + "IsFolder", + "IsUnplayed", + "IsPlayed", + "SeriesSortName", + "VideoBitRate", + "AirTime", + "Studio", + "IsFavoriteOrLiked", + "DateLastContentAdded", + "SeriesDatePlayed", + "ParentIndexNumber", + "IndexNumber" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ItemSortBy" + } + ] + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Shows/{seriesId}/Seasons": { + "get": { + "tags": [ + "TvShows" + ], + "summary": "Gets seasons for a tv series.", + "operationId": "GetSeasons", + "parameters": [ + { + "name": "seriesId", + "in": "path", + "description": "The series id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "The user id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output. This allows multiple, comma delimited. Options: Budget, Chapters, DateCreated, Genres, HomePageUrl, IndexOptions, MediaStreams, Overview, ParentId, Path, People, ProviderIds, PrimaryImageAspectRatio, Revenue, SortName, Studios, Taglines, TrailerUrls.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "isSpecialSeason", + "in": "query", + "description": "Optional. Filter by special season.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isMissing", + "in": "query", + "description": "Optional. Filter by items that are missing episodes or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "adjacentTo", + "in": "query", + "description": "Optional. Return items that are siblings of a supplied item.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "404": { + "description": "Not Found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Shows/NextUp": { + "get": { + "tags": [ + "TvShows" + ], + "summary": "Gets a list of next up episodes.", + "operationId": "GetNextUp", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "The user id of the user to get the next up episodes for.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "seriesId", + "in": "query", + "description": "Optional. Filter by series id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "parentId", + "in": "query", + "description": "Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "nextUpDateCutoff", + "in": "query", + "description": "Optional. Starting date of shows to show in Next Up section.", + "schema": { + "type": "string", + "format": "date-time" + } + }, + { + "name": "enableTotalRecordCount", + "in": "query", + "description": "Whether to enable the total records count. Defaults to true.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "disableFirstEpisode", + "in": "query", + "description": "Whether to disable sending the first episode in a series as next up.", + "deprecated": true, + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "enableResumable", + "in": "query", + "description": "Whether to include resumable episodes in next up results.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "enableRewatching", + "in": "query", + "description": "Whether to include watched episodes in next up results.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Shows/Upcoming": { + "get": { + "tags": [ + "TvShows" + ], + "summary": "Gets a list of upcoming episodes.", + "operationId": "GetUpcomingEpisodes", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "The user id of the user to get the upcoming episodes for.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "startIndex", + "in": "query", + "description": "Optional. The record index to start at. All items with a lower index will be dropped from the results.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "parentId", + "in": "query", + "description": "Optional. Specify this to localize the search to a specific item or folder. Omit to use the root.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Audio/{itemId}/universal": { + "get": { + "tags": [ + "UniversalAudio" + ], + "summary": "Gets an audio stream.", + "operationId": "GetUniversalAudioStream", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "container", + "in": "query", + "description": "Optional. The audio container.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. The user id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. The audio codec to transcode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "transcodingAudioChannels", + "in": "query", + "description": "Optional. The number of how many audio channels to transcode to.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxStreamingBitrate", + "in": "query", + "description": "Optional. The maximum streaming bitrate.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "transcodingContainer", + "in": "query", + "description": "Optional. The container to transcode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodingProtocol", + "in": "query", + "description": "Optional. The transcoding protocol.", + "schema": { + "enum": [ + "http", + "hls" + ], + "allOf": [ + { + "$ref": "#/components/schemas/MediaStreamProtocol" + } + ] + } + }, + { + "name": "maxAudioSampleRate", + "in": "query", + "description": "Optional. The maximum audio sample rate.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableRemoteMedia", + "in": "query", + "description": "Optional. Whether to enable remote media.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Optional. Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "enableRedirection", + "in": "query", + "description": "Whether to enable redirection. Defaults to true.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Audio stream returned.", + "content": { + "audio/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "302": { + "description": "Redirected to remote audio stream." + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "head": { + "tags": [ + "UniversalAudio" + ], + "summary": "Gets an audio stream.", + "operationId": "HeadUniversalAudioStream", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "container", + "in": "query", + "description": "Optional. The audio container.", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. The user id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. The audio codec to transcode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "transcodingAudioChannels", + "in": "query", + "description": "Optional. The number of how many audio channels to transcode to.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxStreamingBitrate", + "in": "query", + "description": "Optional. The maximum streaming bitrate.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "transcodingContainer", + "in": "query", + "description": "Optional. The container to transcode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodingProtocol", + "in": "query", + "description": "Optional. The transcoding protocol.", + "schema": { + "enum": [ + "http", + "hls" + ], + "allOf": [ + { + "$ref": "#/components/schemas/MediaStreamProtocol" + } + ] + } + }, + { + "name": "maxAudioSampleRate", + "in": "query", + "description": "Optional. The maximum audio sample rate.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableRemoteMedia", + "in": "query", + "description": "Optional. Whether to enable remote media.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Optional. Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "enableRedirection", + "in": "query", + "description": "Whether to enable redirection. Defaults to true.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Audio stream returned.", + "content": { + "audio/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "302": { + "description": "Redirected to remote audio stream." + }, + "404": { + "description": "Item not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Users": { + "get": { + "tags": [ + "User" + ], + "summary": "Gets a list of users.", + "operationId": "GetUsers", + "parameters": [ + { + "name": "isHidden", + "in": "query", + "description": "Optional filter by IsHidden=true or false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "isDisabled", + "in": "query", + "description": "Optional filter by IsDisabled=true or false.", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Users returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserDto" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserDto" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserDto" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "post": { + "tags": [ + "User" + ], + "summary": "Updates a user.", + "operationId": "UpdateUser", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "The user id.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "The updated user model.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UserDto" + } + ], + "description": "Class UserDto." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UserDto" + } + ], + "description": "Class UserDto." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UserDto" + } + ], + "description": "Class UserDto." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "User updated." + }, + "400": { + "description": "User information was not supplied.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "User update forbidden.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Users/{userId}": { + "get": { + "tags": [ + "User" + ], + "summary": "Gets a user by Id.", + "operationId": "GetUserById", + "parameters": [ + { + "name": "userId", + "in": "path", + "description": "The user id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "User returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + } + } + }, + "404": { + "description": "User not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "IgnoreParentalControl", + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "User" + ], + "summary": "Deletes a user.", + "operationId": "DeleteUser", + "parameters": [ + { + "name": "userId", + "in": "path", + "description": "The user id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "User deleted." + }, + "404": { + "description": "User not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Users/{userId}/Policy": { + "post": { + "tags": [ + "User" + ], + "summary": "Updates a user policy.", + "operationId": "UpdateUserPolicy", + "parameters": [ + { + "name": "userId", + "in": "path", + "description": "The user id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "The new user policy.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UserPolicy" + } + ] + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UserPolicy" + } + ] + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UserPolicy" + } + ] + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "User policy updated." + }, + "400": { + "description": "User policy was not supplied.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "403": { + "description": "User policy update forbidden.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Users/AuthenticateByName": { + "post": { + "tags": [ + "User" + ], + "summary": "Authenticates a user by name.", + "operationId": "AuthenticateUserByName", + "requestBody": { + "description": "The M:Jellyfin.Api.Controllers.UserController.AuthenticateUserByName(Jellyfin.Api.Models.UserDtos.AuthenticateUserByName) request.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/AuthenticateUserByName" + } + ], + "description": "The authenticate user by name request body." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/AuthenticateUserByName" + } + ], + "description": "The authenticate user by name request body." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/AuthenticateUserByName" + } + ], + "description": "The authenticate user by name request body." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "User authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/AuthenticationResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/AuthenticationResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Users/AuthenticateWithQuickConnect": { + "post": { + "tags": [ + "User" + ], + "summary": "Authenticates a user with quick connect.", + "operationId": "AuthenticateWithQuickConnect", + "requestBody": { + "description": "The Jellyfin.Api.Models.UserDtos.QuickConnectDto request.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/QuickConnectDto" + } + ], + "description": "The quick connect request body." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/QuickConnectDto" + } + ], + "description": "The quick connect request body." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/QuickConnectDto" + } + ], + "description": "The quick connect request body." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "User authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthenticationResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/AuthenticationResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/AuthenticationResult" + } + } + } + }, + "400": { + "description": "Missing token." + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Users/Configuration": { + "post": { + "tags": [ + "User" + ], + "summary": "Updates a user configuration.", + "operationId": "UpdateUserConfiguration", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "The user id.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "The new user configuration.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UserConfiguration" + } + ], + "description": "Class UserConfiguration." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UserConfiguration" + } + ], + "description": "Class UserConfiguration." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UserConfiguration" + } + ], + "description": "Class UserConfiguration." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "User configuration updated." + }, + "403": { + "description": "User configuration update forbidden.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Users/ForgotPassword": { + "post": { + "tags": [ + "User" + ], + "summary": "Initiates the forgot password process for a local user.", + "operationId": "ForgotPassword", + "requestBody": { + "description": "The forgot password request containing the entered username.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ForgotPasswordDto" + } + ], + "description": "Forgot Password request body DTO." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ForgotPasswordDto" + } + ], + "description": "Forgot Password request body DTO." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ForgotPasswordDto" + } + ], + "description": "Forgot Password request body DTO." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Password reset process started.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ForgotPasswordResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ForgotPasswordResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ForgotPasswordResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Users/ForgotPassword/Pin": { + "post": { + "tags": [ + "User" + ], + "summary": "Redeems a forgot password pin.", + "operationId": "ForgotPasswordPin", + "requestBody": { + "description": "The forgot password pin request containing the entered pin.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ForgotPasswordPinDto" + } + ], + "description": "Forgot Password Pin enter request body DTO." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ForgotPasswordPinDto" + } + ], + "description": "Forgot Password Pin enter request body DTO." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/ForgotPasswordPinDto" + } + ], + "description": "Forgot Password Pin enter request body DTO." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Pin reset process started.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PinRedeemResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/PinRedeemResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/PinRedeemResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Users/Me": { + "get": { + "tags": [ + "User" + ], + "summary": "Gets the user based on auth token.", + "operationId": "GetCurrentUser", + "responses": { + "200": { + "description": "User returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + } + } + }, + "400": { + "description": "Token is not owned by a user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Users/New": { + "post": { + "tags": [ + "User" + ], + "summary": "Creates a user.", + "operationId": "CreateUserByName", + "requestBody": { + "description": "The create user by name request body.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/CreateUserByName" + } + ], + "description": "The create user by name request body." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/CreateUserByName" + } + ], + "description": "The create user by name request body." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/CreateUserByName" + } + ], + "description": "The create user by name request body." + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "User created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/UserDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Users/Password": { + "post": { + "tags": [ + "User" + ], + "summary": "Updates a user's password.", + "operationId": "UpdateUserPassword", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "The user id.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "The M:Jellyfin.Api.Controllers.UserController.UpdateUserPassword(System.Nullable{System.Guid},Jellyfin.Api.Models.UserDtos.UpdateUserPassword) request.", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdateUserPassword" + } + ], + "description": "The update user password request body." + } + }, + "text/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdateUserPassword" + } + ], + "description": "The update user password request body." + } + }, + "application/*+json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/UpdateUserPassword" + } + ], + "description": "The update user password request body." + } + } + }, + "required": true + }, + "responses": { + "204": { + "description": "Password successfully reset." + }, + "403": { + "description": "User is not allowed to update the password.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "404": { + "description": "User not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Users/Public": { + "get": { + "tags": [ + "User" + ], + "summary": "Gets a list of publicly visible users for display on a login screen.", + "operationId": "GetPublicUsers", + "responses": { + "200": { + "description": "Public users returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserDto" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserDto" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserDto" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Items/{itemId}/Intros": { + "get": { + "tags": [ + "UserLibrary" + ], + "summary": "Gets intros to play before the main media item plays.", + "operationId": "GetIntros", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Intros returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/LocalTrailers": { + "get": { + "tags": [ + "UserLibrary" + ], + "summary": "Gets local trailers for an item.", + "operationId": "GetLocalTrailers", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "An Microsoft.AspNetCore.Mvc.OkResult containing the item's local trailers.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/{itemId}/SpecialFeatures": { + "get": { + "tags": [ + "UserLibrary" + ], + "summary": "Gets special features for an item.", + "operationId": "GetSpecialFeatures", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Special features returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/Latest": { + "get": { + "tags": [ + "UserLibrary" + ], + "summary": "Gets latest media.", + "operationId": "GetLatestMedia", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "parentId", + "in": "query", + "description": "Specify this to localize the search to a specific item or folder. Omit to use the root.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "includeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be filtered based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "isPlayed", + "in": "query", + "description": "Filter by items that are played, or not.", + "schema": { + "type": "boolean" + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. include image information in output.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. the max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "limit", + "in": "query", + "description": "Return item limit.", + "schema": { + "type": "integer", + "format": "int32", + "default": 20 + } + }, + { + "name": "groupItems", + "in": "query", + "description": "Whether or not to group items into a parent container.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Latest media returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Items/Root": { + "get": { + "tags": [ + "UserLibrary" + ], + "summary": "Gets the root folder from a user's library.", + "operationId": "GetRootFolder", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Root folder returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/UserFavoriteItems/{itemId}": { + "post": { + "tags": [ + "UserLibrary" + ], + "summary": "Marks an item as a favorite.", + "operationId": "MarkFavoriteItem", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Item marked as favorite.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "delete": { + "tags": [ + "UserLibrary" + ], + "summary": "Unmarks item as a favorite.", + "operationId": "UnmarkFavoriteItem", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Item unmarked as favorite.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/UserItems/{itemId}/Rating": { + "delete": { + "tags": [ + "UserLibrary" + ], + "summary": "Deletes a user's saved personal rating for an item.", + "operationId": "DeleteUserItemRating", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Personal rating removed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + }, + "post": { + "tags": [ + "UserLibrary" + ], + "summary": "Updates a user's rating for an item.", + "operationId": "UpdateUserItemRating", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "itemId", + "in": "path", + "description": "Item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "likes", + "in": "query", + "description": "Whether this M:Jellyfin.Api.Controllers.UserLibraryController.UpdateUserItemRating(System.Nullable{System.Guid},System.Guid,System.Nullable{System.Boolean}) is likes.", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Item rating updated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/UserItemDataDto" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/UserViews": { + "get": { + "tags": [ + "UserViews" + ], + "summary": "Get user views.", + "operationId": "GetUserViews", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "includeExternalContent", + "in": "query", + "description": "Whether or not to include external views such as channels or live tv.", + "schema": { + "type": "boolean" + } + }, + { + "name": "presetViews", + "in": "query", + "description": "Preset views.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CollectionType" + } + } + }, + { + "name": "includeHidden", + "in": "query", + "description": "Whether or not to include hidden content.", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "User views returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/UserViews/GroupingOptions": { + "get": { + "tags": [ + "UserViews" + ], + "summary": "Get user view grouping options.", + "operationId": "GetGroupingOptions", + "parameters": [ + { + "name": "userId", + "in": "query", + "description": "User id.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "User view grouping options returned.", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SpecialViewOptionDto" + } + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SpecialViewOptionDto" + } + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SpecialViewOptionDto" + } + } + } + } + }, + "404": { + "description": "User not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Videos/{videoId}/{mediaSourceId}/Attachments/{index}": { + "get": { + "tags": [ + "VideoAttachments" + ], + "summary": "Get video attachment.", + "operationId": "GetAttachment", + "parameters": [ + { + "name": "videoId", + "in": "path", + "description": "Video ID.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "mediaSourceId", + "in": "path", + "description": "Media Source ID.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "index", + "in": "path", + "description": "Attachment Index.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "Attachment retrieved.", + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "404": { + "description": "Video or attachment not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Videos/{itemId}/AdditionalParts": { + "get": { + "tags": [ + "Videos" + ], + "summary": "Gets additional parts for a video.", + "operationId": "GetAdditionalPart", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Additional parts returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Videos/{itemId}/AlternateSources": { + "delete": { + "tags": [ + "Videos" + ], + "summary": "Removes alternate video sources.", + "operationId": "DeleteAlternateSources", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "Alternate sources deleted." + }, + "404": { + "description": "Video not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Videos/{itemId}/stream": { + "get": { + "tags": [ + "Videos" + ], + "summary": "Gets a video stream.", + "operationId": "GetVideoStream", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "container", + "in": "query", + "description": "The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "static", + "in": "query", + "description": "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "params", + "in": "query", + "description": "The streaming parameters.", + "schema": { + "type": "string" + } + }, + { + "name": "tag", + "in": "query", + "description": "The tag.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceProfileId", + "in": "query", + "description": "Optional. The dlna device profile id to utilize.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "query", + "description": "The segment container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The segment length.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minSegments", + "in": "query", + "description": "The minimum number of segments.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. Specify an audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "enableAutoStreamCopy", + "in": "query", + "description": "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the video stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the audio stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean" + } + }, + { + "name": "audioSampleRate", + "in": "query", + "description": "Optional. Specify a specific audio sample rate, e.g. 44100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioChannels", + "in": "query", + "description": "Optional. Specify a specific number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. Specify a maximum number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "profile", + "in": "query", + "description": "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", + "schema": { + "type": "string" + } + }, + { + "name": "level", + "in": "query", + "description": "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", + "schema": { + "pattern": "-?[0-9]+(?:\\.[0-9]+)?", + "type": "string" + } + }, + { + "name": "framerate", + "in": "query", + "description": "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxFramerate", + "in": "query", + "description": "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "width", + "in": "query", + "description": "Optional. The fixed horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "Optional. The fixed vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "Optional. The maximum horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "Optional. The maximum vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoBitRate", + "in": "query", + "description": "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleMethod", + "in": "query", + "description": "Optional. Specify the subtitle delivery method.", + "schema": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ] + } + }, + { + "name": "maxRefFrames", + "in": "query", + "description": "Optional.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxVideoBitDepth", + "in": "query", + "description": "Optional. The maximum video bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "requireAvc", + "in": "query", + "description": "Optional. Whether to require avc.", + "schema": { + "type": "boolean" + } + }, + { + "name": "deInterlace", + "in": "query", + "description": "Optional. Whether to deinterlace the video.", + "schema": { + "type": "boolean" + } + }, + { + "name": "requireNonAnamorphic", + "in": "query", + "description": "Optional. Whether to require a non anamorphic stream.", + "schema": { + "type": "boolean" + } + }, + { + "name": "transcodingMaxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels to transcode.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "cpuCoreLimit", + "in": "query", + "description": "Optional. The limit of how many cpu cores to use.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "enableMpegtsM2TsMode", + "in": "query", + "description": "Optional. Whether to enable the MpegtsM2Ts mode.", + "schema": { + "type": "boolean" + } + }, + { + "name": "videoCodec", + "in": "query", + "description": "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "subtitleCodec", + "in": "query", + "description": "Optional. Specify a subtitle codec to encode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodeReasons", + "in": "query", + "description": "Optional. The transcoding reason.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoStreamIndex", + "in": "query", + "description": "Optional. The index of the video stream to use. If omitted the first video stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "context", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Dlna.EncodingContext.", + "schema": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ] + } + }, + { + "name": "streamOptions", + "in": "query", + "description": "Optional. The streaming options.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Optional. Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Video stream returned.", + "content": { + "video/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "head": { + "tags": [ + "Videos" + ], + "summary": "Gets a video stream.", + "operationId": "HeadVideoStream", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "container", + "in": "query", + "description": "The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "static", + "in": "query", + "description": "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "params", + "in": "query", + "description": "The streaming parameters.", + "schema": { + "type": "string" + } + }, + { + "name": "tag", + "in": "query", + "description": "The tag.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceProfileId", + "in": "query", + "description": "Optional. The dlna device profile id to utilize.", + "deprecated": true, + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "query", + "description": "The segment container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The segment length.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minSegments", + "in": "query", + "description": "The minimum number of segments.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. Specify an audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "enableAutoStreamCopy", + "in": "query", + "description": "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the video stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the audio stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean" + } + }, + { + "name": "audioSampleRate", + "in": "query", + "description": "Optional. Specify a specific audio sample rate, e.g. 44100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioChannels", + "in": "query", + "description": "Optional. Specify a specific number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. Specify a maximum number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "profile", + "in": "query", + "description": "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", + "schema": { + "type": "string" + } + }, + { + "name": "level", + "in": "query", + "description": "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", + "schema": { + "pattern": "-?[0-9]+(?:\\.[0-9]+)?", + "type": "string" + } + }, + { + "name": "framerate", + "in": "query", + "description": "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxFramerate", + "in": "query", + "description": "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "width", + "in": "query", + "description": "Optional. The fixed horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "Optional. The fixed vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "Optional. The maximum horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "Optional. The maximum vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoBitRate", + "in": "query", + "description": "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleMethod", + "in": "query", + "description": "Optional. Specify the subtitle delivery method.", + "schema": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ] + } + }, + { + "name": "maxRefFrames", + "in": "query", + "description": "Optional.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxVideoBitDepth", + "in": "query", + "description": "Optional. The maximum video bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "requireAvc", + "in": "query", + "description": "Optional. Whether to require avc.", + "schema": { + "type": "boolean" + } + }, + { + "name": "deInterlace", + "in": "query", + "description": "Optional. Whether to deinterlace the video.", + "schema": { + "type": "boolean" + } + }, + { + "name": "requireNonAnamorphic", + "in": "query", + "description": "Optional. Whether to require a non anamorphic stream.", + "schema": { + "type": "boolean" + } + }, + { + "name": "transcodingMaxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels to transcode.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "cpuCoreLimit", + "in": "query", + "description": "Optional. The limit of how many cpu cores to use.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "enableMpegtsM2TsMode", + "in": "query", + "description": "Optional. Whether to enable the MpegtsM2Ts mode.", + "schema": { + "type": "boolean" + } + }, + { + "name": "videoCodec", + "in": "query", + "description": "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "subtitleCodec", + "in": "query", + "description": "Optional. Specify a subtitle codec to encode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodeReasons", + "in": "query", + "description": "Optional. The transcoding reason.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoStreamIndex", + "in": "query", + "description": "Optional. The index of the video stream to use. If omitted the first video stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "context", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Dlna.EncodingContext.", + "schema": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ] + } + }, + { + "name": "streamOptions", + "in": "query", + "description": "Optional. The streaming options.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Optional. Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Video stream returned.", + "content": { + "video/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Videos/{itemId}/stream.{container}": { + "get": { + "tags": [ + "Videos" + ], + "summary": "Gets a video stream.", + "operationId": "GetVideoStreamByContainer", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "container", + "in": "path", + "description": "The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv.", + "required": true, + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "static", + "in": "query", + "description": "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "params", + "in": "query", + "description": "The streaming parameters.", + "schema": { + "type": "string" + } + }, + { + "name": "tag", + "in": "query", + "description": "The tag.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceProfileId", + "in": "query", + "description": "Optional. The dlna device profile id to utilize.", + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "query", + "description": "The segment container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The segment length.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minSegments", + "in": "query", + "description": "The minimum number of segments.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. Specify an audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "enableAutoStreamCopy", + "in": "query", + "description": "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the video stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the audio stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean" + } + }, + { + "name": "audioSampleRate", + "in": "query", + "description": "Optional. Specify a specific audio sample rate, e.g. 44100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioChannels", + "in": "query", + "description": "Optional. Specify a specific number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. Specify a maximum number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "profile", + "in": "query", + "description": "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", + "schema": { + "type": "string" + } + }, + { + "name": "level", + "in": "query", + "description": "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", + "schema": { + "pattern": "-?[0-9]+(?:\\.[0-9]+)?", + "type": "string" + } + }, + { + "name": "framerate", + "in": "query", + "description": "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxFramerate", + "in": "query", + "description": "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "width", + "in": "query", + "description": "Optional. The fixed horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "Optional. The fixed vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "Optional. The maximum horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "Optional. The maximum vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoBitRate", + "in": "query", + "description": "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleMethod", + "in": "query", + "description": "Optional. Specify the subtitle delivery method.", + "schema": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ] + } + }, + { + "name": "maxRefFrames", + "in": "query", + "description": "Optional.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxVideoBitDepth", + "in": "query", + "description": "Optional. The maximum video bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "requireAvc", + "in": "query", + "description": "Optional. Whether to require avc.", + "schema": { + "type": "boolean" + } + }, + { + "name": "deInterlace", + "in": "query", + "description": "Optional. Whether to deinterlace the video.", + "schema": { + "type": "boolean" + } + }, + { + "name": "requireNonAnamorphic", + "in": "query", + "description": "Optional. Whether to require a non anamorphic stream.", + "schema": { + "type": "boolean" + } + }, + { + "name": "transcodingMaxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels to transcode.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "cpuCoreLimit", + "in": "query", + "description": "Optional. The limit of how many cpu cores to use.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "enableMpegtsM2TsMode", + "in": "query", + "description": "Optional. Whether to enable the MpegtsM2Ts mode.", + "schema": { + "type": "boolean" + } + }, + { + "name": "videoCodec", + "in": "query", + "description": "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "subtitleCodec", + "in": "query", + "description": "Optional. Specify a subtitle codec to encode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodeReasons", + "in": "query", + "description": "Optional. The transcoding reason.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoStreamIndex", + "in": "query", + "description": "Optional. The index of the video stream to use. If omitted the first video stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "context", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Dlna.EncodingContext.", + "schema": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ] + } + }, + { + "name": "streamOptions", + "in": "query", + "description": "Optional. The streaming options.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Optional. Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Video stream returned.", + "content": { + "video/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + }, + "head": { + "tags": [ + "Videos" + ], + "summary": "Gets a video stream.", + "operationId": "HeadVideoStreamByContainer", + "parameters": [ + { + "name": "itemId", + "in": "path", + "description": "The item id.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "container", + "in": "path", + "description": "The video container. Possible values are: ts, webm, asf, wmv, ogv, mp4, m4v, mkv, mpeg, mpg, avi, 3gp, wmv, wtv, m2ts, mov, iso, flv.", + "required": true, + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "static", + "in": "query", + "description": "Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "params", + "in": "query", + "description": "The streaming parameters.", + "schema": { + "type": "string" + } + }, + { + "name": "tag", + "in": "query", + "description": "The tag.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceProfileId", + "in": "query", + "description": "Optional. The dlna device profile id to utilize.", + "schema": { + "type": "string" + } + }, + { + "name": "playSessionId", + "in": "query", + "description": "The play session id.", + "schema": { + "type": "string" + } + }, + { + "name": "segmentContainer", + "in": "query", + "description": "The segment container.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "segmentLength", + "in": "query", + "description": "The segment length.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "minSegments", + "in": "query", + "description": "The minimum number of segments.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "mediaSourceId", + "in": "query", + "description": "The media version id, if playing an alternate version.", + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "in": "query", + "description": "The device id of the client requesting. Used to stop encoding processes when needed.", + "schema": { + "type": "string" + } + }, + { + "name": "audioCodec", + "in": "query", + "description": "Optional. Specify an audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "enableAutoStreamCopy", + "in": "query", + "description": "Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowVideoStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the video stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "allowAudioStreamCopy", + "in": "query", + "description": "Whether or not to allow copying of the audio stream url.", + "schema": { + "type": "boolean" + } + }, + { + "name": "breakOnNonKeyFrames", + "in": "query", + "description": "Optional. Whether to break on non key frames.", + "schema": { + "type": "boolean" + } + }, + { + "name": "audioSampleRate", + "in": "query", + "description": "Optional. Specify a specific audio sample rate, e.g. 44100.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioBitDepth", + "in": "query", + "description": "Optional. The maximum audio bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioBitRate", + "in": "query", + "description": "Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "audioChannels", + "in": "query", + "description": "Optional. Specify a specific number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxAudioChannels", + "in": "query", + "description": "Optional. Specify a maximum number of audio channels to encode to, e.g. 2.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "profile", + "in": "query", + "description": "Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.", + "schema": { + "type": "string" + } + }, + { + "name": "level", + "in": "query", + "description": "Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.", + "schema": { + "pattern": "-?[0-9]+(?:\\.[0-9]+)?", + "type": "string" + } + }, + { + "name": "framerate", + "in": "query", + "description": "Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "maxFramerate", + "in": "query", + "description": "Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.", + "schema": { + "type": "number", + "format": "float" + } + }, + { + "name": "copyTimestamps", + "in": "query", + "description": "Whether or not to copy timestamps when transcoding with an offset. Defaults to false.", + "schema": { + "type": "boolean" + } + }, + { + "name": "startTimeTicks", + "in": "query", + "description": "Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.", + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "width", + "in": "query", + "description": "Optional. The fixed horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "height", + "in": "query", + "description": "Optional. The fixed vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxWidth", + "in": "query", + "description": "Optional. The maximum horizontal resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxHeight", + "in": "query", + "description": "Optional. The maximum vertical resolution of the encoded video.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoBitRate", + "in": "query", + "description": "Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleStreamIndex", + "in": "query", + "description": "Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "subtitleMethod", + "in": "query", + "description": "Optional. Specify the subtitle delivery method.", + "schema": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ] + } + }, + { + "name": "maxRefFrames", + "in": "query", + "description": "Optional.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "maxVideoBitDepth", + "in": "query", + "description": "Optional. The maximum video bit depth.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "requireAvc", + "in": "query", + "description": "Optional. Whether to require avc.", + "schema": { + "type": "boolean" + } + }, + { + "name": "deInterlace", + "in": "query", + "description": "Optional. Whether to deinterlace the video.", + "schema": { + "type": "boolean" + } + }, + { + "name": "requireNonAnamorphic", + "in": "query", + "description": "Optional. Whether to require a non anamorphic stream.", + "schema": { + "type": "boolean" + } + }, + { + "name": "transcodingMaxAudioChannels", + "in": "query", + "description": "Optional. The maximum number of audio channels to transcode.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "cpuCoreLimit", + "in": "query", + "description": "Optional. The limit of how many cpu cores to use.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "liveStreamId", + "in": "query", + "description": "The live stream id.", + "schema": { + "type": "string" + } + }, + { + "name": "enableMpegtsM2TsMode", + "in": "query", + "description": "Optional. Whether to enable the MpegtsM2Ts mode.", + "schema": { + "type": "boolean" + } + }, + { + "name": "videoCodec", + "in": "query", + "description": "Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "subtitleCodec", + "in": "query", + "description": "Optional. Specify a subtitle codec to encode to.", + "schema": { + "pattern": "^[a-zA-Z0-9\\-\\._,|]{0,40}$", + "type": "string" + } + }, + { + "name": "transcodeReasons", + "in": "query", + "description": "Optional. The transcoding reason.", + "schema": { + "type": "string" + } + }, + { + "name": "audioStreamIndex", + "in": "query", + "description": "Optional. The index of the audio stream to use. If omitted the first audio stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "videoStreamIndex", + "in": "query", + "description": "Optional. The index of the video stream to use. If omitted the first video stream will be used.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "context", + "in": "query", + "description": "Optional. The MediaBrowser.Model.Dlna.EncodingContext.", + "schema": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ] + } + }, + { + "name": "streamOptions", + "in": "query", + "description": "Optional. The streaming options.", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + { + "name": "enableAudioVbrEncoding", + "in": "query", + "description": "Optional. Whether to enable Audio Encoding.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Video stream returned.", + "content": { + "video/*": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + } + } + } + }, + "/Videos/MergeVersions": { + "post": { + "tags": [ + "Videos" + ], + "summary": "Merges videos into a single record.", + "operationId": "MergeVersions", + "parameters": [ + { + "name": "ids", + "in": "query", + "description": "Item id list. This allows multiple, comma delimited.", + "required": true, + "schema": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + } + } + ], + "responses": { + "204": { + "description": "Videos merged." + }, + "400": { + "description": "Supply at least 2 video ids.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "RequiresElevation" + ] + } + ] + } + }, + "/Years": { + "get": { + "tags": [ + "Years" + ], + "summary": "Get years.", + "operationId": "GetYears", + "parameters": [ + { + "name": "startIndex", + "in": "query", + "description": "Skips over a given number of items within the results. Use for paging.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "limit", + "in": "query", + "description": "Optional. The maximum number of records to return.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "sortOrder", + "in": "query", + "description": "Sort Order - Ascending,Descending.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SortOrder" + } + } + }, + { + "name": "parentId", + "in": "query", + "description": "Specify this to localize the search to a specific item or folder. Omit to use the root.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "fields", + "in": "query", + "description": "Optional. Specify additional fields of information to return in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + } + } + }, + { + "name": "excludeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be excluded based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "includeItemTypes", + "in": "query", + "description": "Optional. If specified, results will be included based on item type. This allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemKind" + } + } + }, + { + "name": "mediaTypes", + "in": "query", + "description": "Optional. Filter by MediaType. Allows multiple, comma delimited.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaType" + } + } + }, + { + "name": "sortBy", + "in": "query", + "description": "Optional. Specify one or more sort orders, comma delimited. Options: Album, AlbumArtist, Artist, Budget, CommunityRating, CriticRating, DateCreated, DatePlayed, PlayCount, PremiereDate, ProductionYear, SortName, Random, Revenue, Runtime.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemSortBy" + } + } + }, + { + "name": "enableUserData", + "in": "query", + "description": "Optional. Include user data.", + "schema": { + "type": "boolean" + } + }, + { + "name": "imageTypeLimit", + "in": "query", + "description": "Optional. The max number of images to return, per image type.", + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "enableImageTypes", + "in": "query", + "description": "Optional. The image types to include in the output.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + } + } + }, + { + "name": "userId", + "in": "query", + "description": "User Id.", + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "recursive", + "in": "query", + "description": "Search recursively.", + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "enableImages", + "in": "query", + "description": "Optional. Include image information in output.", + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Year query returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDtoQueryResult" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + }, + "/Years/{year}": { + "get": { + "tags": [ + "Years" + ], + "summary": "Gets a year.", + "operationId": "GetYear", + "parameters": [ + { + "name": "year", + "in": "path", + "description": "The year.", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + { + "name": "userId", + "in": "query", + "description": "Optional. Filter by user id, and attach user data.", + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Year returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/BaseItemDto" + } + } + } + }, + "404": { + "description": "Year not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"CamelCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + }, + "application/json; profile=\"PascalCase\"": { + "schema": { + "$ref": "#/components/schemas/ProblemDetails" + } + } + } + }, + "503": { + "description": "The server is currently starting or is temporarily not available.", + "headers": { + "Retry-After": { + "description": "A hint for when to retry the operation in full seconds.", + "allowEmptyValue": true, + "schema": { + "type": "integer", + "format": "int32" + } + }, + "Message": { + "description": "A short plain-text reason why the server is not available.", + "allowEmptyValue": true, + "schema": { + "type": "string", + "format": "text" + } + } + }, + "content": { + "text/html": { } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + }, + "security": [ + { + "CustomAuthentication": [ + "DefaultAuthorization" + ] + } + ] + } + } + }, + "components": { + "schemas": { + "AccessSchedule": { + "type": "object", + "properties": { + "Id": { + "type": "integer", + "description": "Gets the id of this instance.", + "format": "int32", + "readOnly": true + }, + "UserId": { + "type": "string", + "description": "Gets the id of the associated user.", + "format": "uuid" + }, + "DayOfWeek": { + "enum": [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Everyday", + "Weekday", + "Weekend" + ], + "allOf": [ + { + "$ref": "#/components/schemas/DynamicDayOfWeek" + } + ], + "description": "Gets or sets the day of week." + }, + "StartHour": { + "type": "number", + "description": "Gets or sets the start hour.", + "format": "double" + }, + "EndHour": { + "type": "number", + "description": "Gets or sets the end hour.", + "format": "double" + } + }, + "additionalProperties": false, + "description": "An entity representing a user's access schedule." + }, + "ActivityLogEntry": { + "type": "object", + "properties": { + "Id": { + "type": "integer", + "description": "Gets or sets the identifier.", + "format": "int64" + }, + "Name": { + "type": "string", + "description": "Gets or sets the name." + }, + "Overview": { + "type": "string", + "description": "Gets or sets the overview.", + "nullable": true + }, + "ShortOverview": { + "type": "string", + "description": "Gets or sets the short overview.", + "nullable": true + }, + "Type": { + "type": "string", + "description": "Gets or sets the type." + }, + "ItemId": { + "type": "string", + "description": "Gets or sets the item identifier.", + "nullable": true + }, + "Date": { + "type": "string", + "description": "Gets or sets the date.", + "format": "date-time" + }, + "UserId": { + "type": "string", + "description": "Gets or sets the user identifier.", + "format": "uuid" + }, + "UserPrimaryImageTag": { + "type": "string", + "description": "Gets or sets the user primary image tag.", + "nullable": true, + "deprecated": true + }, + "Severity": { + "enum": [ + "Trace", + "Debug", + "Information", + "Warning", + "Error", + "Critical", + "None" + ], + "allOf": [ + { + "$ref": "#/components/schemas/LogLevel" + } + ], + "description": "Gets or sets the log severity." + } + }, + "additionalProperties": false, + "description": "An activity log entry." + }, + "ActivityLogEntryMessage": { + "type": "object", + "properties": { + "Data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ActivityLogEntry" + }, + "description": "Gets or sets the data.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "ActivityLogEntry", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Activity log created message." + }, + "ActivityLogEntryQueryResult": { + "type": "object", + "properties": { + "Items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ActivityLogEntry" + }, + "description": "Gets or sets the items." + }, + "TotalRecordCount": { + "type": "integer", + "description": "Gets or sets the total number of records available.", + "format": "int32" + }, + "StartIndex": { + "type": "integer", + "description": "Gets or sets the index of the first record in Items.", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "Query result container." + }, + "ActivityLogEntryStartMessage": { + "type": "object", + "properties": { + "Data": { + "type": "string", + "description": "Gets or sets the data.", + "nullable": true + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "ActivityLogEntryStart", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Activity log entry start message.\r\nData is the timing data encoded as \"$initialDelay,$interval\" in ms." + }, + "ActivityLogEntryStopMessage": { + "type": "object", + "properties": { + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "ActivityLogEntryStop", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Activity log entry stop message." + }, + "AddVirtualFolderDto": { + "type": "object", + "properties": { + "LibraryOptions": { + "allOf": [ + { + "$ref": "#/components/schemas/LibraryOptions" + } + ], + "description": "Gets or sets library options.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Add virtual folder dto." + }, + "AlbumInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "OriginalTitle": { + "type": "string", + "description": "Gets or sets the original title.", + "nullable": true + }, + "Path": { + "type": "string", + "description": "Gets or sets the path.", + "nullable": true + }, + "MetadataLanguage": { + "type": "string", + "description": "Gets or sets the metadata language.", + "nullable": true + }, + "MetadataCountryCode": { + "type": "string", + "description": "Gets or sets the metadata country code.", + "nullable": true + }, + "ProviderIds": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Gets or sets the provider ids.", + "nullable": true + }, + "Year": { + "type": "integer", + "description": "Gets or sets the year.", + "format": "int32", + "nullable": true + }, + "IndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "ParentIndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "PremiereDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "IsAutomated": { + "type": "boolean" + }, + "AlbumArtists": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the album artist." + }, + "ArtistProviderIds": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Gets or sets the artist provider ids." + }, + "SongInfos": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SongInfo" + } + } + }, + "additionalProperties": false + }, + "AlbumInfoRemoteSearchQuery": { + "type": "object", + "properties": { + "SearchInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/AlbumInfo" + } + ], + "nullable": true + }, + "ItemId": { + "type": "string", + "format": "uuid" + }, + "SearchProviderName": { + "type": "string", + "description": "Gets or sets the provider name to search within if set.", + "nullable": true + }, + "IncludeDisabledProviders": { + "type": "boolean", + "description": "Gets or sets a value indicating whether disabled providers should be included." + } + }, + "additionalProperties": false + }, + "AllThemeMediaResult": { + "type": "object", + "properties": { + "ThemeVideosResult": { + "allOf": [ + { + "$ref": "#/components/schemas/ThemeMediaResult" + } + ], + "description": "Class ThemeMediaResult.", + "nullable": true + }, + "ThemeSongsResult": { + "allOf": [ + { + "$ref": "#/components/schemas/ThemeMediaResult" + } + ], + "description": "Class ThemeMediaResult.", + "nullable": true + }, + "SoundtrackSongsResult": { + "allOf": [ + { + "$ref": "#/components/schemas/ThemeMediaResult" + } + ], + "description": "Class ThemeMediaResult.", + "nullable": true + } + }, + "additionalProperties": false + }, + "ArtistInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "OriginalTitle": { + "type": "string", + "description": "Gets or sets the original title.", + "nullable": true + }, + "Path": { + "type": "string", + "description": "Gets or sets the path.", + "nullable": true + }, + "MetadataLanguage": { + "type": "string", + "description": "Gets or sets the metadata language.", + "nullable": true + }, + "MetadataCountryCode": { + "type": "string", + "description": "Gets or sets the metadata country code.", + "nullable": true + }, + "ProviderIds": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Gets or sets the provider ids.", + "nullable": true + }, + "Year": { + "type": "integer", + "description": "Gets or sets the year.", + "format": "int32", + "nullable": true + }, + "IndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "ParentIndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "PremiereDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "IsAutomated": { + "type": "boolean" + }, + "SongInfos": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SongInfo" + } + } + }, + "additionalProperties": false + }, + "ArtistInfoRemoteSearchQuery": { + "type": "object", + "properties": { + "SearchInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/ArtistInfo" + } + ], + "nullable": true + }, + "ItemId": { + "type": "string", + "format": "uuid" + }, + "SearchProviderName": { + "type": "string", + "description": "Gets or sets the provider name to search within if set.", + "nullable": true + }, + "IncludeDisabledProviders": { + "type": "boolean", + "description": "Gets or sets a value indicating whether disabled providers should be included." + } + }, + "additionalProperties": false + }, + "AudioSpatialFormat": { + "enum": [ + "None", + "DolbyAtmos", + "DTSX" + ], + "type": "string", + "description": "An enum representing formats of spatial audio." + }, + "AuthenticateUserByName": { + "type": "object", + "properties": { + "Username": { + "type": "string", + "description": "Gets or sets the username.", + "nullable": true + }, + "Pw": { + "type": "string", + "description": "Gets or sets the plain text password.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "The authenticate user by name request body." + }, + "AuthenticationInfo": { + "type": "object", + "properties": { + "Id": { + "type": "integer", + "description": "Gets or sets the identifier.", + "format": "int64" + }, + "AccessToken": { + "type": "string", + "description": "Gets or sets the access token.", + "nullable": true + }, + "DeviceId": { + "type": "string", + "description": "Gets or sets the device identifier.", + "nullable": true + }, + "AppName": { + "type": "string", + "description": "Gets or sets the name of the application.", + "nullable": true + }, + "AppVersion": { + "type": "string", + "description": "Gets or sets the application version.", + "nullable": true + }, + "DeviceName": { + "type": "string", + "description": "Gets or sets the name of the device.", + "nullable": true + }, + "UserId": { + "type": "string", + "description": "Gets or sets the user identifier.", + "format": "uuid" + }, + "IsActive": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is active." + }, + "DateCreated": { + "type": "string", + "description": "Gets or sets the date created.", + "format": "date-time" + }, + "DateRevoked": { + "type": "string", + "description": "Gets or sets the date revoked.", + "format": "date-time", + "nullable": true + }, + "DateLastActivity": { + "type": "string", + "format": "date-time" + }, + "UserName": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "AuthenticationInfoQueryResult": { + "type": "object", + "properties": { + "Items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AuthenticationInfo" + }, + "description": "Gets or sets the items." + }, + "TotalRecordCount": { + "type": "integer", + "description": "Gets or sets the total number of records available.", + "format": "int32" + }, + "StartIndex": { + "type": "integer", + "description": "Gets or sets the index of the first record in Items.", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "Query result container." + }, + "AuthenticationResult": { + "type": "object", + "properties": { + "User": { + "allOf": [ + { + "$ref": "#/components/schemas/UserDto" + } + ], + "description": "Class UserDto.", + "nullable": true + }, + "SessionInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/SessionInfoDto" + } + ], + "description": "Session info DTO.", + "nullable": true + }, + "AccessToken": { + "type": "string", + "description": "Gets or sets the access token.", + "nullable": true + }, + "ServerId": { + "type": "string", + "description": "Gets or sets the server id.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "A class representing an authentication result." + }, + "BackupManifestDto": { + "type": "object", + "properties": { + "ServerVersion": { + "type": "string", + "description": "Gets or sets the jellyfin version this backup was created with." + }, + "BackupEngineVersion": { + "type": "string", + "description": "Gets or sets the backup engine version this backup was created with." + }, + "DateCreated": { + "type": "string", + "description": "Gets or sets the date this backup was created with.", + "format": "date-time" + }, + "Path": { + "type": "string", + "description": "Gets or sets the path to the backup on the system." + }, + "Options": { + "allOf": [ + { + "$ref": "#/components/schemas/BackupOptionsDto" + } + ], + "description": "Gets or sets the contents of the backup archive." + } + }, + "additionalProperties": false, + "description": "Manifest type for backups internal structure." + }, + "BackupOptionsDto": { + "type": "object", + "properties": { + "Metadata": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the archive contains the Metadata contents." + }, + "Trickplay": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the archive contains the Trickplay contents." + }, + "Subtitles": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the archive contains the Subtitle contents." + }, + "Database": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the archive contains the Database contents." + } + }, + "additionalProperties": false, + "description": "Defines the optional contents of the backup archive." + }, + "BackupRestoreRequestDto": { + "type": "object", + "properties": { + "ArchiveFileName": { + "type": "string", + "description": "Gets or Sets the name of the backup archive to restore from. Must be present in MediaBrowser.Common.Configuration.IApplicationPaths.BackupPath." + } + }, + "additionalProperties": false, + "description": "Defines properties used to start a restore process." + }, + "BaseItemDto": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "OriginalTitle": { + "type": "string", + "nullable": true + }, + "ServerId": { + "type": "string", + "description": "Gets or sets the server identifier.", + "nullable": true + }, + "Id": { + "type": "string", + "description": "Gets or sets the id.", + "format": "uuid" + }, + "Etag": { + "type": "string", + "description": "Gets or sets the etag.", + "nullable": true + }, + "SourceType": { + "type": "string", + "description": "Gets or sets the type of the source.", + "nullable": true + }, + "PlaylistItemId": { + "type": "string", + "description": "Gets or sets the playlist item identifier.", + "nullable": true + }, + "DateCreated": { + "type": "string", + "description": "Gets or sets the date created.", + "format": "date-time", + "nullable": true + }, + "DateLastMediaAdded": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "ExtraType": { + "enum": [ + "Unknown", + "Clip", + "Trailer", + "BehindTheScenes", + "DeletedScene", + "Interview", + "Scene", + "Sample", + "ThemeSong", + "ThemeVideo", + "Featurette", + "Short" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ExtraType" + } + ], + "nullable": true + }, + "AirsBeforeSeasonNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "AirsAfterSeasonNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "AirsBeforeEpisodeNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "CanDelete": { + "type": "boolean", + "nullable": true + }, + "CanDownload": { + "type": "boolean", + "nullable": true + }, + "HasLyrics": { + "type": "boolean", + "nullable": true + }, + "HasSubtitles": { + "type": "boolean", + "nullable": true + }, + "PreferredMetadataLanguage": { + "type": "string", + "nullable": true + }, + "PreferredMetadataCountryCode": { + "type": "string", + "nullable": true + }, + "Container": { + "type": "string", + "nullable": true + }, + "SortName": { + "type": "string", + "description": "Gets or sets the name of the sort.", + "nullable": true + }, + "ForcedSortName": { + "type": "string", + "nullable": true + }, + "Video3DFormat": { + "enum": [ + "HalfSideBySide", + "FullSideBySide", + "FullTopAndBottom", + "HalfTopAndBottom", + "MVC" + ], + "allOf": [ + { + "$ref": "#/components/schemas/Video3DFormat" + } + ], + "description": "Gets or sets the video3 D format.", + "nullable": true + }, + "PremiereDate": { + "type": "string", + "description": "Gets or sets the premiere date.", + "format": "date-time", + "nullable": true + }, + "ExternalUrls": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExternalUrl" + }, + "description": "Gets or sets the external urls.", + "nullable": true + }, + "MediaSources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaSourceInfo" + }, + "description": "Gets or sets the media versions.", + "nullable": true + }, + "CriticRating": { + "type": "number", + "description": "Gets or sets the critic rating.", + "format": "float", + "nullable": true + }, + "ProductionLocations": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "Path": { + "type": "string", + "description": "Gets or sets the path.", + "nullable": true + }, + "EnableMediaSourceDisplay": { + "type": "boolean", + "nullable": true + }, + "OfficialRating": { + "type": "string", + "description": "Gets or sets the official rating.", + "nullable": true + }, + "CustomRating": { + "type": "string", + "description": "Gets or sets the custom rating.", + "nullable": true + }, + "ChannelId": { + "type": "string", + "description": "Gets or sets the channel identifier.", + "format": "uuid", + "nullable": true + }, + "ChannelName": { + "type": "string", + "nullable": true + }, + "Overview": { + "type": "string", + "description": "Gets or sets the overview.", + "nullable": true + }, + "Taglines": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the taglines.", + "nullable": true + }, + "Genres": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the genres.", + "nullable": true + }, + "CommunityRating": { + "type": "number", + "description": "Gets or sets the community rating.", + "format": "float", + "nullable": true + }, + "CumulativeRunTimeTicks": { + "type": "integer", + "description": "Gets or sets the cumulative run time ticks.", + "format": "int64", + "nullable": true + }, + "RunTimeTicks": { + "type": "integer", + "description": "Gets or sets the run time ticks.", + "format": "int64", + "nullable": true + }, + "PlayAccess": { + "enum": [ + "Full", + "None" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PlayAccess" + } + ], + "description": "Gets or sets the play access.", + "nullable": true + }, + "AspectRatio": { + "type": "string", + "description": "Gets or sets the aspect ratio.", + "nullable": true + }, + "ProductionYear": { + "type": "integer", + "description": "Gets or sets the production year.", + "format": "int32", + "nullable": true + }, + "IsPlaceHolder": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is place holder.", + "nullable": true + }, + "Number": { + "type": "string", + "description": "Gets or sets the number.", + "nullable": true + }, + "ChannelNumber": { + "type": "string", + "nullable": true + }, + "IndexNumber": { + "type": "integer", + "description": "Gets or sets the index number.", + "format": "int32", + "nullable": true + }, + "IndexNumberEnd": { + "type": "integer", + "description": "Gets or sets the index number end.", + "format": "int32", + "nullable": true + }, + "ParentIndexNumber": { + "type": "integer", + "description": "Gets or sets the parent index number.", + "format": "int32", + "nullable": true + }, + "RemoteTrailers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaUrl" + }, + "description": "Gets or sets the trailer urls.", + "nullable": true + }, + "ProviderIds": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Gets or sets the provider ids.", + "nullable": true + }, + "IsHD": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is HD.", + "nullable": true + }, + "IsFolder": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is folder.", + "nullable": true + }, + "ParentId": { + "type": "string", + "description": "Gets or sets the parent id.", + "format": "uuid", + "nullable": true + }, + "Type": { + "enum": [ + "AggregateFolder", + "Audio", + "AudioBook", + "BasePluginFolder", + "Book", + "BoxSet", + "Channel", + "ChannelFolderItem", + "CollectionFolder", + "Episode", + "Folder", + "Genre", + "ManualPlaylistsFolder", + "Movie", + "LiveTvChannel", + "LiveTvProgram", + "MusicAlbum", + "MusicArtist", + "MusicGenre", + "MusicVideo", + "Person", + "Photo", + "PhotoAlbum", + "Playlist", + "PlaylistsFolder", + "Program", + "Recording", + "Season", + "Series", + "Studio", + "Trailer", + "TvChannel", + "TvProgram", + "UserRootFolder", + "UserView", + "Video", + "Year" + ], + "allOf": [ + { + "$ref": "#/components/schemas/BaseItemKind" + } + ], + "description": "Gets or sets the type." + }, + "People": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemPerson" + }, + "description": "Gets or sets the people.", + "nullable": true + }, + "Studios": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameGuidPair" + }, + "description": "Gets or sets the studios.", + "nullable": true + }, + "GenreItems": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameGuidPair" + }, + "nullable": true + }, + "ParentLogoItemId": { + "type": "string", + "description": "Gets or sets whether the item has a logo, this will hold the Id of the Parent that has one.", + "format": "uuid", + "nullable": true + }, + "ParentBackdropItemId": { + "type": "string", + "description": "Gets or sets whether the item has any backdrops, this will hold the Id of the Parent that has one.", + "format": "uuid", + "nullable": true + }, + "ParentBackdropImageTags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the parent backdrop image tags.", + "nullable": true + }, + "LocalTrailerCount": { + "type": "integer", + "description": "Gets or sets the local trailer count.", + "format": "int32", + "nullable": true + }, + "UserData": { + "allOf": [ + { + "$ref": "#/components/schemas/UserItemDataDto" + } + ], + "description": "Gets or sets the user data for this item based on the user it's being requested for.", + "nullable": true + }, + "RecursiveItemCount": { + "type": "integer", + "description": "Gets or sets the recursive item count.", + "format": "int32", + "nullable": true + }, + "ChildCount": { + "type": "integer", + "description": "Gets or sets the child count.", + "format": "int32", + "nullable": true + }, + "SeriesName": { + "type": "string", + "description": "Gets or sets the name of the series.", + "nullable": true + }, + "SeriesId": { + "type": "string", + "description": "Gets or sets the series id.", + "format": "uuid", + "nullable": true + }, + "SeasonId": { + "type": "string", + "description": "Gets or sets the season identifier.", + "format": "uuid", + "nullable": true + }, + "SpecialFeatureCount": { + "type": "integer", + "description": "Gets or sets the special feature count.", + "format": "int32", + "nullable": true + }, + "DisplayPreferencesId": { + "type": "string", + "description": "Gets or sets the display preferences id.", + "nullable": true + }, + "Status": { + "type": "string", + "description": "Gets or sets the status.", + "nullable": true + }, + "AirTime": { + "type": "string", + "description": "Gets or sets the air time.", + "nullable": true + }, + "AirDays": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DayOfWeek" + }, + "description": "Gets or sets the air days.", + "nullable": true + }, + "Tags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the tags.", + "nullable": true + }, + "PrimaryImageAspectRatio": { + "type": "number", + "description": "Gets or sets the primary image aspect ratio, after image enhancements.", + "format": "double", + "nullable": true + }, + "Artists": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the artists.", + "nullable": true + }, + "ArtistItems": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameGuidPair" + }, + "description": "Gets or sets the artist items.", + "nullable": true + }, + "Album": { + "type": "string", + "description": "Gets or sets the album.", + "nullable": true + }, + "CollectionType": { + "enum": [ + "unknown", + "movies", + "tvshows", + "music", + "musicvideos", + "trailers", + "homevideos", + "boxsets", + "books", + "photos", + "livetv", + "playlists", + "folders" + ], + "allOf": [ + { + "$ref": "#/components/schemas/CollectionType" + } + ], + "description": "Gets or sets the type of the collection.", + "nullable": true + }, + "DisplayOrder": { + "type": "string", + "description": "Gets or sets the display order.", + "nullable": true + }, + "AlbumId": { + "type": "string", + "description": "Gets or sets the album id.", + "format": "uuid", + "nullable": true + }, + "AlbumPrimaryImageTag": { + "type": "string", + "description": "Gets or sets the album image tag.", + "nullable": true + }, + "SeriesPrimaryImageTag": { + "type": "string", + "description": "Gets or sets the series primary image tag.", + "nullable": true + }, + "AlbumArtist": { + "type": "string", + "description": "Gets or sets the album artist.", + "nullable": true + }, + "AlbumArtists": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameGuidPair" + }, + "description": "Gets or sets the album artists.", + "nullable": true + }, + "SeasonName": { + "type": "string", + "description": "Gets or sets the name of the season.", + "nullable": true + }, + "MediaStreams": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaStream" + }, + "description": "Gets or sets the media streams.", + "nullable": true + }, + "VideoType": { + "enum": [ + "VideoFile", + "Iso", + "Dvd", + "BluRay" + ], + "allOf": [ + { + "$ref": "#/components/schemas/VideoType" + } + ], + "description": "Gets or sets the type of the video.", + "nullable": true + }, + "PartCount": { + "type": "integer", + "description": "Gets or sets the part count.", + "format": "int32", + "nullable": true + }, + "MediaSourceCount": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "ImageTags": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Gets or sets the image tags.", + "nullable": true + }, + "BackdropImageTags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the backdrop image tags.", + "nullable": true + }, + "ScreenshotImageTags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the screenshot image tags.", + "nullable": true + }, + "ParentLogoImageTag": { + "type": "string", + "description": "Gets or sets the parent logo image tag.", + "nullable": true + }, + "ParentArtItemId": { + "type": "string", + "description": "Gets or sets whether the item has fan art, this will hold the Id of the Parent that has one.", + "format": "uuid", + "nullable": true + }, + "ParentArtImageTag": { + "type": "string", + "description": "Gets or sets the parent art image tag.", + "nullable": true + }, + "SeriesThumbImageTag": { + "type": "string", + "description": "Gets or sets the series thumb image tag.", + "nullable": true + }, + "ImageBlurHashes": { + "type": "object", + "properties": { + "Primary": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Art": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Backdrop": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Banner": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Logo": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Thumb": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Disc": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Box": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Screenshot": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Menu": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Chapter": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "BoxRear": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Profile": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "description": "Gets or sets the blurhashes for the image tags.\r\nMaps image type to dictionary mapping image tag to blurhash value.", + "nullable": true + }, + "SeriesStudio": { + "type": "string", + "description": "Gets or sets the series studio.", + "nullable": true + }, + "ParentThumbItemId": { + "type": "string", + "description": "Gets or sets the parent thumb item id.", + "format": "uuid", + "nullable": true + }, + "ParentThumbImageTag": { + "type": "string", + "description": "Gets or sets the parent thumb image tag.", + "nullable": true + }, + "ParentPrimaryImageItemId": { + "type": "string", + "description": "Gets or sets the parent primary image item identifier.", + "format": "uuid", + "nullable": true + }, + "ParentPrimaryImageTag": { + "type": "string", + "description": "Gets or sets the parent primary image tag.", + "nullable": true + }, + "Chapters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChapterInfo" + }, + "description": "Gets or sets the chapters.", + "nullable": true + }, + "Trickplay": { + "type": "object", + "additionalProperties": { + "type": "object", + "additionalProperties": { + "$ref": "#/components/schemas/TrickplayInfoDto" + } + }, + "description": "Gets or sets the trickplay manifest.", + "nullable": true + }, + "LocationType": { + "enum": [ + "FileSystem", + "Remote", + "Virtual", + "Offline" + ], + "allOf": [ + { + "$ref": "#/components/schemas/LocationType" + } + ], + "description": "Gets or sets the type of the location.", + "nullable": true + }, + "IsoType": { + "enum": [ + "Dvd", + "BluRay" + ], + "allOf": [ + { + "$ref": "#/components/schemas/IsoType" + } + ], + "description": "Gets or sets the type of the iso.", + "nullable": true + }, + "MediaType": { + "enum": [ + "Unknown", + "Video", + "Audio", + "Photo", + "Book" + ], + "allOf": [ + { + "$ref": "#/components/schemas/MediaType" + } + ], + "description": "Gets or sets the type of the media.", + "default": "Unknown" + }, + "EndDate": { + "type": "string", + "description": "Gets or sets the end date.", + "format": "date-time", + "nullable": true + }, + "LockedFields": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MetadataField" + }, + "description": "Gets or sets the locked fields.", + "nullable": true + }, + "TrailerCount": { + "type": "integer", + "description": "Gets or sets the trailer count.", + "format": "int32", + "nullable": true + }, + "MovieCount": { + "type": "integer", + "description": "Gets or sets the movie count.", + "format": "int32", + "nullable": true + }, + "SeriesCount": { + "type": "integer", + "description": "Gets or sets the series count.", + "format": "int32", + "nullable": true + }, + "ProgramCount": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "EpisodeCount": { + "type": "integer", + "description": "Gets or sets the episode count.", + "format": "int32", + "nullable": true + }, + "SongCount": { + "type": "integer", + "description": "Gets or sets the song count.", + "format": "int32", + "nullable": true + }, + "AlbumCount": { + "type": "integer", + "description": "Gets or sets the album count.", + "format": "int32", + "nullable": true + }, + "ArtistCount": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "MusicVideoCount": { + "type": "integer", + "description": "Gets or sets the music video count.", + "format": "int32", + "nullable": true + }, + "LockData": { + "type": "boolean", + "description": "Gets or sets a value indicating whether [enable internet providers].", + "nullable": true + }, + "Width": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "Height": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "CameraMake": { + "type": "string", + "nullable": true + }, + "CameraModel": { + "type": "string", + "nullable": true + }, + "Software": { + "type": "string", + "nullable": true + }, + "ExposureTime": { + "type": "number", + "format": "double", + "nullable": true + }, + "FocalLength": { + "type": "number", + "format": "double", + "nullable": true + }, + "ImageOrientation": { + "enum": [ + "TopLeft", + "TopRight", + "BottomRight", + "BottomLeft", + "LeftTop", + "RightTop", + "RightBottom", + "LeftBottom" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageOrientation" + } + ], + "nullable": true + }, + "Aperture": { + "type": "number", + "format": "double", + "nullable": true + }, + "ShutterSpeed": { + "type": "number", + "format": "double", + "nullable": true + }, + "Latitude": { + "type": "number", + "format": "double", + "nullable": true + }, + "Longitude": { + "type": "number", + "format": "double", + "nullable": true + }, + "Altitude": { + "type": "number", + "format": "double", + "nullable": true + }, + "IsoSpeedRating": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "SeriesTimerId": { + "type": "string", + "description": "Gets or sets the series timer identifier.", + "nullable": true + }, + "ProgramId": { + "type": "string", + "description": "Gets or sets the program identifier.", + "nullable": true + }, + "ChannelPrimaryImageTag": { + "type": "string", + "description": "Gets or sets the channel primary image tag.", + "nullable": true + }, + "StartDate": { + "type": "string", + "description": "Gets or sets the start date of the recording, in UTC.", + "format": "date-time", + "nullable": true + }, + "CompletionPercentage": { + "type": "number", + "description": "Gets or sets the completion percentage.", + "format": "double", + "nullable": true + }, + "IsRepeat": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is repeat.", + "nullable": true + }, + "EpisodeTitle": { + "type": "string", + "description": "Gets or sets the episode title.", + "nullable": true + }, + "ChannelType": { + "enum": [ + "TV", + "Radio" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ChannelType" + } + ], + "description": "Gets or sets the type of the channel.", + "nullable": true + }, + "Audio": { + "enum": [ + "Mono", + "Stereo", + "Dolby", + "DolbyDigital", + "Thx", + "Atmos" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ProgramAudio" + } + ], + "description": "Gets or sets the audio.", + "nullable": true + }, + "IsMovie": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is movie.", + "nullable": true + }, + "IsSports": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is sports.", + "nullable": true + }, + "IsSeries": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is series.", + "nullable": true + }, + "IsLive": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is live.", + "nullable": true + }, + "IsNews": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is news.", + "nullable": true + }, + "IsKids": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is kids.", + "nullable": true + }, + "IsPremiere": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is premiere.", + "nullable": true + }, + "TimerId": { + "type": "string", + "description": "Gets or sets the timer identifier.", + "nullable": true + }, + "NormalizationGain": { + "type": "number", + "description": "Gets or sets the gain required for audio normalization.", + "format": "float", + "nullable": true + }, + "CurrentProgram": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseItemDto" + } + ], + "description": "Gets or sets the current program.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "This is strictly used as a data transfer object from the api layer.\r\nThis holds information about a BaseItem in a format that is convenient for the client." + }, + "BaseItemDtoQueryResult": { + "type": "object", + "properties": { + "Items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemDto" + }, + "description": "Gets or sets the items." + }, + "TotalRecordCount": { + "type": "integer", + "description": "Gets or sets the total number of records available.", + "format": "int32" + }, + "StartIndex": { + "type": "integer", + "description": "Gets or sets the index of the first record in Items.", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "Query result container." + }, + "BaseItemKind": { + "enum": [ + "AggregateFolder", + "Audio", + "AudioBook", + "BasePluginFolder", + "Book", + "BoxSet", + "Channel", + "ChannelFolderItem", + "CollectionFolder", + "Episode", + "Folder", + "Genre", + "ManualPlaylistsFolder", + "Movie", + "LiveTvChannel", + "LiveTvProgram", + "MusicAlbum", + "MusicArtist", + "MusicGenre", + "MusicVideo", + "Person", + "Photo", + "PhotoAlbum", + "Playlist", + "PlaylistsFolder", + "Program", + "Recording", + "Season", + "Series", + "Studio", + "Trailer", + "TvChannel", + "TvProgram", + "UserRootFolder", + "UserView", + "Video", + "Year" + ], + "type": "string", + "description": "The base item kind." + }, + "BaseItemPerson": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "Id": { + "type": "string", + "description": "Gets or sets the identifier.", + "format": "uuid" + }, + "Role": { + "type": "string", + "description": "Gets or sets the role.", + "nullable": true + }, + "Type": { + "enum": [ + "Unknown", + "Actor", + "Director", + "Composer", + "Writer", + "GuestStar", + "Producer", + "Conductor", + "Lyricist", + "Arranger", + "Engineer", + "Mixer", + "Remixer", + "Creator", + "Artist", + "AlbumArtist", + "Author", + "Illustrator", + "Penciller", + "Inker", + "Colorist", + "Letterer", + "CoverArtist", + "Editor", + "Translator" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PersonKind" + } + ], + "description": "Gets or sets the type.", + "default": "Unknown" + }, + "PrimaryImageTag": { + "type": "string", + "description": "Gets or sets the primary image tag.", + "nullable": true + }, + "ImageBlurHashes": { + "type": "object", + "properties": { + "Primary": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Art": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Backdrop": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Banner": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Logo": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Thumb": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Disc": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Box": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Screenshot": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Menu": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Chapter": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "BoxRear": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "Profile": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + "description": "Gets or sets the primary image blurhash.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "This is used by the api to get information about a Person within a BaseItem." + }, + "BasePluginConfiguration": { + "type": "object", + "additionalProperties": false, + "description": "Class BasePluginConfiguration." + }, + "BookInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "OriginalTitle": { + "type": "string", + "description": "Gets or sets the original title.", + "nullable": true + }, + "Path": { + "type": "string", + "description": "Gets or sets the path.", + "nullable": true + }, + "MetadataLanguage": { + "type": "string", + "description": "Gets or sets the metadata language.", + "nullable": true + }, + "MetadataCountryCode": { + "type": "string", + "description": "Gets or sets the metadata country code.", + "nullable": true + }, + "ProviderIds": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Gets or sets the provider ids.", + "nullable": true + }, + "Year": { + "type": "integer", + "description": "Gets or sets the year.", + "format": "int32", + "nullable": true + }, + "IndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "ParentIndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "PremiereDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "IsAutomated": { + "type": "boolean" + }, + "SeriesName": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "BookInfoRemoteSearchQuery": { + "type": "object", + "properties": { + "SearchInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/BookInfo" + } + ], + "nullable": true + }, + "ItemId": { + "type": "string", + "format": "uuid" + }, + "SearchProviderName": { + "type": "string", + "description": "Gets or sets the provider name to search within if set.", + "nullable": true + }, + "IncludeDisabledProviders": { + "type": "boolean", + "description": "Gets or sets a value indicating whether disabled providers should be included." + } + }, + "additionalProperties": false + }, + "BoxSetInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "OriginalTitle": { + "type": "string", + "description": "Gets or sets the original title.", + "nullable": true + }, + "Path": { + "type": "string", + "description": "Gets or sets the path.", + "nullable": true + }, + "MetadataLanguage": { + "type": "string", + "description": "Gets or sets the metadata language.", + "nullable": true + }, + "MetadataCountryCode": { + "type": "string", + "description": "Gets or sets the metadata country code.", + "nullable": true + }, + "ProviderIds": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Gets or sets the provider ids.", + "nullable": true + }, + "Year": { + "type": "integer", + "description": "Gets or sets the year.", + "format": "int32", + "nullable": true + }, + "IndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "ParentIndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "PremiereDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "IsAutomated": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "BoxSetInfoRemoteSearchQuery": { + "type": "object", + "properties": { + "SearchInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/BoxSetInfo" + } + ], + "nullable": true + }, + "ItemId": { + "type": "string", + "format": "uuid" + }, + "SearchProviderName": { + "type": "string", + "description": "Gets or sets the provider name to search within if set.", + "nullable": true + }, + "IncludeDisabledProviders": { + "type": "boolean", + "description": "Gets or sets a value indicating whether disabled providers should be included." + } + }, + "additionalProperties": false + }, + "BrandingOptionsDto": { + "type": "object", + "properties": { + "LoginDisclaimer": { + "type": "string", + "description": "Gets or sets the login disclaimer.", + "nullable": true + }, + "CustomCss": { + "type": "string", + "description": "Gets or sets the custom CSS.", + "nullable": true + }, + "SplashscreenEnabled": { + "type": "boolean", + "description": "Gets or sets a value indicating whether to enable the splashscreen." + } + }, + "additionalProperties": false, + "description": "The branding options DTO for API use.\r\nThis DTO excludes SplashscreenLocation to prevent it from being updated via API." + }, + "BufferRequestDto": { + "type": "object", + "properties": { + "When": { + "type": "string", + "description": "Gets or sets when the request has been made by the client.", + "format": "date-time" + }, + "PositionTicks": { + "type": "integer", + "description": "Gets or sets the position ticks.", + "format": "int64" + }, + "IsPlaying": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the client playback is unpaused." + }, + "PlaylistItemId": { + "type": "string", + "description": "Gets or sets the playlist item identifier of the playing item.", + "format": "uuid" + } + }, + "additionalProperties": false, + "description": "Class BufferRequestDto." + }, + "CastReceiverApplication": { + "type": "object", + "properties": { + "Id": { + "type": "string", + "description": "Gets or sets the cast receiver application id." + }, + "Name": { + "type": "string", + "description": "Gets or sets the cast receiver application name." + } + }, + "additionalProperties": false, + "description": "The cast receiver application model." + }, + "ChannelFeatures": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name." + }, + "Id": { + "type": "string", + "description": "Gets or sets the identifier.", + "format": "uuid" + }, + "CanSearch": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance can search." + }, + "MediaTypes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChannelMediaType" + }, + "description": "Gets or sets the media types." + }, + "ContentTypes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChannelMediaContentType" + }, + "description": "Gets or sets the content types." + }, + "MaxPageSize": { + "type": "integer", + "description": "Gets or sets the maximum number of records the channel allows retrieving at a time.", + "format": "int32", + "nullable": true + }, + "AutoRefreshLevels": { + "type": "integer", + "description": "Gets or sets the automatic refresh levels.", + "format": "int32", + "nullable": true + }, + "DefaultSortFields": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ChannelItemSortField" + }, + "description": "Gets or sets the default sort orders." + }, + "SupportsSortOrderToggle": { + "type": "boolean", + "description": "Gets or sets a value indicating whether a sort ascending/descending toggle is supported." + }, + "SupportsLatestMedia": { + "type": "boolean", + "description": "Gets or sets a value indicating whether [supports latest media]." + }, + "CanFilter": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance can filter." + }, + "SupportsContentDownloading": { + "type": "boolean", + "description": "Gets or sets a value indicating whether [supports content downloading]." + } + }, + "additionalProperties": false + }, + "ChannelItemSortField": { + "enum": [ + "Name", + "CommunityRating", + "PremiereDate", + "DateCreated", + "Runtime", + "PlayCount", + "CommunityPlayCount" + ], + "type": "string" + }, + "ChannelMappingOptionsDto": { + "type": "object", + "properties": { + "TunerChannels": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TunerChannelMapping" + }, + "description": "Gets or sets list of tuner channels." + }, + "ProviderChannels": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameIdPair" + }, + "description": "Gets or sets list of provider channels." + }, + "Mappings": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameValuePair" + }, + "description": "Gets or sets list of mappings." + }, + "ProviderName": { + "type": "string", + "description": "Gets or sets provider name.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Channel mapping options dto." + }, + "ChannelMediaContentType": { + "enum": [ + "Clip", + "Podcast", + "Trailer", + "Movie", + "Episode", + "Song", + "MovieExtra", + "TvExtra" + ], + "type": "string" + }, + "ChannelMediaType": { + "enum": [ + "Audio", + "Video", + "Photo" + ], + "type": "string" + }, + "ChannelType": { + "enum": [ + "TV", + "Radio" + ], + "type": "string", + "description": "Enum ChannelType." + }, + "ChapterInfo": { + "type": "object", + "properties": { + "StartPositionTicks": { + "type": "integer", + "description": "Gets or sets the start position ticks.", + "format": "int64" + }, + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "ImagePath": { + "type": "string", + "description": "Gets or sets the image path.", + "nullable": true + }, + "ImageDateModified": { + "type": "string", + "format": "date-time" + }, + "ImageTag": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class ChapterInfo." + }, + "ClientCapabilitiesDto": { + "type": "object", + "properties": { + "PlayableMediaTypes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaType" + }, + "description": "Gets or sets the list of playable media types." + }, + "SupportedCommands": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GeneralCommandType" + }, + "description": "Gets or sets the list of supported commands." + }, + "SupportsMediaControl": { + "type": "boolean", + "description": "Gets or sets a value indicating whether session supports media control." + }, + "SupportsPersistentIdentifier": { + "type": "boolean", + "description": "Gets or sets a value indicating whether session supports a persistent identifier." + }, + "DeviceProfile": { + "allOf": [ + { + "$ref": "#/components/schemas/DeviceProfile" + } + ], + "description": "Gets or sets the device profile.", + "nullable": true + }, + "AppStoreUrl": { + "type": "string", + "description": "Gets or sets the app store url.", + "nullable": true + }, + "IconUrl": { + "type": "string", + "description": "Gets or sets the icon url.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Client capabilities dto." + }, + "ClientLogDocumentResponseDto": { + "type": "object", + "properties": { + "FileName": { + "type": "string", + "description": "Gets the resulting filename." + } + }, + "additionalProperties": false, + "description": "Client log document response dto." + }, + "CodecProfile": { + "type": "object", + "properties": { + "Type": { + "enum": [ + "Video", + "VideoAudio", + "Audio" + ], + "allOf": [ + { + "$ref": "#/components/schemas/CodecType" + } + ], + "description": "Gets or sets the MediaBrowser.Model.Dlna.CodecType which this container must meet." + }, + "Conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProfileCondition" + }, + "description": "Gets or sets the list of MediaBrowser.Model.Dlna.ProfileCondition which this profile must meet." + }, + "ApplyConditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProfileCondition" + }, + "description": "Gets or sets the list of MediaBrowser.Model.Dlna.ProfileCondition to apply if this profile is met." + }, + "Codec": { + "type": "string", + "description": "Gets or sets the codec(s) that this profile applies to.", + "nullable": true + }, + "Container": { + "type": "string", + "description": "Gets or sets the container(s) which this profile will be applied to.", + "nullable": true + }, + "SubContainer": { + "type": "string", + "description": "Gets or sets the sub-container(s) which this profile will be applied to.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Defines the MediaBrowser.Model.Dlna.CodecProfile." + }, + "CodecType": { + "enum": [ + "Video", + "VideoAudio", + "Audio" + ], + "type": "string" + }, + "CollectionCreationResult": { + "type": "object", + "properties": { + "Id": { + "type": "string", + "format": "uuid" + } + }, + "additionalProperties": false + }, + "CollectionType": { + "enum": [ + "unknown", + "movies", + "tvshows", + "music", + "musicvideos", + "trailers", + "homevideos", + "boxsets", + "books", + "photos", + "livetv", + "playlists", + "folders" + ], + "type": "string", + "description": "Collection type." + }, + "CollectionTypeOptions": { + "enum": [ + "movies", + "tvshows", + "music", + "musicvideos", + "homevideos", + "boxsets", + "books", + "mixed" + ], + "type": "string", + "description": "The collection type options." + }, + "ConfigImageTypes": { + "type": "object", + "properties": { + "BackdropSizes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "BaseUrl": { + "type": "string", + "nullable": true + }, + "LogoSizes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "PosterSizes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "ProfileSizes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "SecureBaseUrl": { + "type": "string", + "nullable": true + }, + "StillSizes": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "ConfigurationPageInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name." + }, + "EnableInMainMenu": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the configurations page is enabled in the main menu." + }, + "MenuSection": { + "type": "string", + "description": "Gets or sets the menu section.", + "nullable": true + }, + "MenuIcon": { + "type": "string", + "description": "Gets or sets the menu icon.", + "nullable": true + }, + "DisplayName": { + "type": "string", + "description": "Gets or sets the display name.", + "nullable": true + }, + "PluginId": { + "type": "string", + "description": "Gets or sets the plugin id.", + "format": "uuid", + "nullable": true + } + }, + "additionalProperties": false, + "description": "The configuration page info." + }, + "ContainerProfile": { + "type": "object", + "properties": { + "Type": { + "enum": [ + "Audio", + "Video", + "Photo", + "Subtitle", + "Lyric" + ], + "allOf": [ + { + "$ref": "#/components/schemas/DlnaProfileType" + } + ], + "description": "Gets or sets the MediaBrowser.Model.Dlna.DlnaProfileType which this container must meet." + }, + "Conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProfileCondition" + }, + "description": "Gets or sets the list of MediaBrowser.Model.Dlna.ProfileCondition which this container will be applied to." + }, + "Container": { + "type": "string", + "description": "Gets or sets the container(s) which this container must meet.", + "nullable": true + }, + "SubContainer": { + "type": "string", + "description": "Gets or sets the sub container(s) which this container must meet.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Defines the MediaBrowser.Model.Dlna.ContainerProfile." + }, + "CountryInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "DisplayName": { + "type": "string", + "description": "Gets or sets the display name.", + "nullable": true + }, + "TwoLetterISORegionName": { + "type": "string", + "description": "Gets or sets the name of the two letter ISO region.", + "nullable": true + }, + "ThreeLetterISORegionName": { + "type": "string", + "description": "Gets or sets the name of the three letter ISO region.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class CountryInfo." + }, + "CreatePlaylistDto": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name of the new playlist." + }, + "Ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Gets or sets item ids to add to the playlist." + }, + "UserId": { + "type": "string", + "description": "Gets or sets the user id.", + "format": "uuid", + "nullable": true + }, + "MediaType": { + "enum": [ + "Unknown", + "Video", + "Audio", + "Photo", + "Book" + ], + "allOf": [ + { + "$ref": "#/components/schemas/MediaType" + } + ], + "description": "Gets or sets the media type.", + "nullable": true + }, + "Users": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlaylistUserPermissions" + }, + "description": "Gets or sets the playlist users." + }, + "IsPublic": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the playlist is public." + } + }, + "additionalProperties": false, + "description": "Create new playlist dto." + }, + "CreateUserByName": { + "required": [ + "Name" + ], + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the username." + }, + "Password": { + "type": "string", + "description": "Gets or sets the password.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "The create user by name request body." + }, + "CultureDto": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets the name." + }, + "DisplayName": { + "type": "string", + "description": "Gets the display name." + }, + "TwoLetterISOLanguageName": { + "type": "string", + "description": "Gets the name of the two letter ISO language." + }, + "ThreeLetterISOLanguageName": { + "type": "string", + "description": "Gets the name of the three letter ISO language.", + "nullable": true, + "readOnly": true + }, + "ThreeLetterISOLanguageNames": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "description": "Class CultureDto." + }, + "CustomDatabaseOption": { + "type": "object", + "properties": { + "Key": { + "type": "string", + "description": "Gets or sets the key of the value." + }, + "Value": { + "type": "string", + "description": "Gets or sets the value." + } + }, + "additionalProperties": false, + "description": "The custom value option for custom database providers." + }, + "CustomDatabaseOptions": { + "type": "object", + "properties": { + "PluginName": { + "type": "string", + "description": "Gets or sets the Plugin name to search for database providers." + }, + "PluginAssembly": { + "type": "string", + "description": "Gets or sets the plugin assembly to search for providers." + }, + "ConnectionString": { + "type": "string", + "description": "Gets or sets the connection string for the custom database provider." + }, + "Options": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomDatabaseOption" + }, + "description": "Gets or sets the list of extra options for the custom provider." + } + }, + "additionalProperties": false, + "description": "Defines the options for a custom database connector." + }, + "DatabaseConfigurationOptions": { + "type": "object", + "properties": { + "DatabaseType": { + "type": "string", + "description": "Gets or Sets the type of database jellyfin should use." + }, + "CustomProviderOptions": { + "allOf": [ + { + "$ref": "#/components/schemas/CustomDatabaseOptions" + } + ], + "description": "Gets or sets the options required to use a custom database provider.", + "nullable": true + }, + "LockingBehavior": { + "enum": [ + "NoLock", + "Pessimistic", + "Optimistic" + ], + "allOf": [ + { + "$ref": "#/components/schemas/DatabaseLockingBehaviorTypes" + } + ], + "description": "Gets or Sets the kind of locking behavior jellyfin should perform. Possible options are \"NoLock\", \"Pessimistic\", \"Optimistic\".\r\nDefaults to \"NoLock\"." + } + }, + "additionalProperties": false, + "description": "Options to configure jellyfins managed database." + }, + "DatabaseLockingBehaviorTypes": { + "enum": [ + "NoLock", + "Pessimistic", + "Optimistic" + ], + "type": "string", + "description": "Defines all possible methods for locking database access for concurrent queries." + }, + "DayOfWeek": { + "enum": [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + ], + "type": "string" + }, + "DayPattern": { + "enum": [ + "Daily", + "Weekdays", + "Weekends" + ], + "type": "string" + }, + "DefaultDirectoryBrowserInfoDto": { + "type": "object", + "properties": { + "Path": { + "type": "string", + "description": "Gets or sets the path.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Default directory browser info." + }, + "DeinterlaceMethod": { + "enum": [ + "yadif", + "bwdif" + ], + "type": "string", + "description": "Enum containing deinterlace methods." + }, + "DeviceInfoDto": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "CustomName": { + "type": "string", + "description": "Gets or sets the custom name.", + "nullable": true + }, + "AccessToken": { + "type": "string", + "description": "Gets or sets the access token.", + "nullable": true + }, + "Id": { + "type": "string", + "description": "Gets or sets the identifier.", + "nullable": true + }, + "LastUserName": { + "type": "string", + "description": "Gets or sets the last name of the user.", + "nullable": true + }, + "AppName": { + "type": "string", + "description": "Gets or sets the name of the application.", + "nullable": true + }, + "AppVersion": { + "type": "string", + "description": "Gets or sets the application version.", + "nullable": true + }, + "LastUserId": { + "type": "string", + "description": "Gets or sets the last user identifier.", + "format": "uuid", + "nullable": true + }, + "DateLastActivity": { + "type": "string", + "description": "Gets or sets the date last modified.", + "format": "date-time", + "nullable": true + }, + "Capabilities": { + "allOf": [ + { + "$ref": "#/components/schemas/ClientCapabilitiesDto" + } + ], + "description": "Gets or sets the capabilities." + }, + "IconUrl": { + "type": "string", + "description": "Gets or sets the icon URL.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "A DTO representing device information." + }, + "DeviceInfoDtoQueryResult": { + "type": "object", + "properties": { + "Items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeviceInfoDto" + }, + "description": "Gets or sets the items." + }, + "TotalRecordCount": { + "type": "integer", + "description": "Gets or sets the total number of records available.", + "format": "int32" + }, + "StartIndex": { + "type": "integer", + "description": "Gets or sets the index of the first record in Items.", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "Query result container." + }, + "DeviceOptionsDto": { + "type": "object", + "properties": { + "Id": { + "type": "integer", + "description": "Gets or sets the id.", + "format": "int32" + }, + "DeviceId": { + "type": "string", + "description": "Gets or sets the device id.", + "nullable": true + }, + "CustomName": { + "type": "string", + "description": "Gets or sets the custom name.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "A dto representing custom options for a device." + }, + "DeviceProfile": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name of this device profile. User profiles must have a unique name.", + "nullable": true + }, + "Id": { + "type": "string", + "description": "Gets or sets the unique internal identifier.", + "format": "uuid", + "nullable": true + }, + "MaxStreamingBitrate": { + "type": "integer", + "description": "Gets or sets the maximum allowed bitrate for all streamed content.", + "format": "int32", + "nullable": true + }, + "MaxStaticBitrate": { + "type": "integer", + "description": "Gets or sets the maximum allowed bitrate for statically streamed content (= direct played files).", + "format": "int32", + "nullable": true + }, + "MusicStreamingTranscodingBitrate": { + "type": "integer", + "description": "Gets or sets the maximum allowed bitrate for transcoded music streams.", + "format": "int32", + "nullable": true + }, + "MaxStaticMusicBitrate": { + "type": "integer", + "description": "Gets or sets the maximum allowed bitrate for statically streamed (= direct played) music files.", + "format": "int32", + "nullable": true + }, + "DirectPlayProfiles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DirectPlayProfile" + }, + "description": "Gets or sets the direct play profiles." + }, + "TranscodingProfiles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TranscodingProfile" + }, + "description": "Gets or sets the transcoding profiles." + }, + "ContainerProfiles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ContainerProfile" + }, + "description": "Gets or sets the container profiles. Failing to meet these optional conditions causes transcoding to occur." + }, + "CodecProfiles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CodecProfile" + }, + "description": "Gets or sets the codec profiles." + }, + "SubtitleProfiles": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SubtitleProfile" + }, + "description": "Gets or sets the subtitle profiles." + } + }, + "additionalProperties": false, + "description": "A MediaBrowser.Model.Dlna.DeviceProfile represents a set of metadata which determines which content a certain device is able to play.\r\n
\r\nSpecifically, it defines the supported containers and\r\ncodecs (video and/or audio, including codec profiles and levels)\r\nthe device is able to direct play (without transcoding or remuxing),\r\nas well as which containers/codecs to transcode to in case it isn't." + }, + "DirectPlayProfile": { + "type": "object", + "properties": { + "Container": { + "type": "string", + "description": "Gets or sets the container." + }, + "AudioCodec": { + "type": "string", + "description": "Gets or sets the audio codec.", + "nullable": true + }, + "VideoCodec": { + "type": "string", + "description": "Gets or sets the video codec.", + "nullable": true + }, + "Type": { + "enum": [ + "Audio", + "Video", + "Photo", + "Subtitle", + "Lyric" + ], + "allOf": [ + { + "$ref": "#/components/schemas/DlnaProfileType" + } + ], + "description": "Gets or sets the Dlna profile type." + } + }, + "additionalProperties": false, + "description": "Defines the MediaBrowser.Model.Dlna.DirectPlayProfile." + }, + "DisplayPreferencesDto": { + "type": "object", + "properties": { + "Id": { + "type": "string", + "description": "Gets or sets the user id.", + "nullable": true + }, + "ViewType": { + "type": "string", + "description": "Gets or sets the type of the view.", + "nullable": true + }, + "SortBy": { + "type": "string", + "description": "Gets or sets the sort by.", + "nullable": true + }, + "IndexBy": { + "type": "string", + "description": "Gets or sets the index by.", + "nullable": true + }, + "RememberIndexing": { + "type": "boolean", + "description": "Gets or sets a value indicating whether [remember indexing]." + }, + "PrimaryImageHeight": { + "type": "integer", + "description": "Gets or sets the height of the primary image.", + "format": "int32" + }, + "PrimaryImageWidth": { + "type": "integer", + "description": "Gets or sets the width of the primary image.", + "format": "int32" + }, + "CustomPrefs": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Gets or sets the custom prefs." + }, + "ScrollDirection": { + "enum": [ + "Horizontal", + "Vertical" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ScrollDirection" + } + ], + "description": "Gets or sets the scroll direction." + }, + "ShowBackdrop": { + "type": "boolean", + "description": "Gets or sets a value indicating whether to show backdrops on this item." + }, + "RememberSorting": { + "type": "boolean", + "description": "Gets or sets a value indicating whether [remember sorting]." + }, + "SortOrder": { + "enum": [ + "Ascending", + "Descending" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SortOrder" + } + ], + "description": "Gets or sets the sort order." + }, + "ShowSidebar": { + "type": "boolean", + "description": "Gets or sets a value indicating whether [show sidebar]." + }, + "Client": { + "type": "string", + "description": "Gets or sets the client.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Defines the display preferences for any item that supports them (usually Folders)." + }, + "DlnaProfileType": { + "enum": [ + "Audio", + "Video", + "Photo", + "Subtitle", + "Lyric" + ], + "type": "string" + }, + "DownMixStereoAlgorithms": { + "enum": [ + "None", + "Dave750", + "NightmodeDialogue", + "Rfc7845", + "Ac4" + ], + "type": "string", + "description": "An enum representing an algorithm to downmix surround sound to stereo." + }, + "DynamicDayOfWeek": { + "enum": [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", + "Everyday", + "Weekday", + "Weekend" + ], + "type": "string", + "description": "An enum that represents a day of the week, weekdays, weekends, or all days." + }, + "EmbeddedSubtitleOptions": { + "enum": [ + "AllowAll", + "AllowText", + "AllowImage", + "AllowNone" + ], + "type": "string", + "description": "An enum representing the options to disable embedded subs." + }, + "EncoderPreset": { + "enum": [ + "auto", + "placebo", + "veryslow", + "slower", + "slow", + "medium", + "fast", + "faster", + "veryfast", + "superfast", + "ultrafast" + ], + "type": "string", + "description": "Enum containing encoder presets." + }, + "EncodingContext": { + "enum": [ + "Streaming", + "Static" + ], + "type": "string" + }, + "EncodingOptions": { + "type": "object", + "properties": { + "EncodingThreadCount": { + "type": "integer", + "description": "Gets or sets the thread count used for encoding.", + "format": "int32" + }, + "TranscodingTempPath": { + "type": "string", + "description": "Gets or sets the temporary transcoding path.", + "nullable": true + }, + "FallbackFontPath": { + "type": "string", + "description": "Gets or sets the path to the fallback font.", + "nullable": true + }, + "EnableFallbackFont": { + "type": "boolean", + "description": "Gets or sets a value indicating whether to use the fallback font." + }, + "EnableAudioVbr": { + "type": "boolean", + "description": "Gets or sets a value indicating whether audio VBR is enabled." + }, + "DownMixAudioBoost": { + "type": "number", + "description": "Gets or sets the audio boost applied when downmixing audio.", + "format": "double" + }, + "DownMixStereoAlgorithm": { + "enum": [ + "None", + "Dave750", + "NightmodeDialogue", + "Rfc7845", + "Ac4" + ], + "allOf": [ + { + "$ref": "#/components/schemas/DownMixStereoAlgorithms" + } + ], + "description": "Gets or sets the algorithm used for downmixing audio to stereo." + }, + "MaxMuxingQueueSize": { + "type": "integer", + "description": "Gets or sets the maximum size of the muxing queue.", + "format": "int32" + }, + "EnableThrottling": { + "type": "boolean", + "description": "Gets or sets a value indicating whether throttling is enabled." + }, + "ThrottleDelaySeconds": { + "type": "integer", + "description": "Gets or sets the delay after which throttling happens.", + "format": "int32" + }, + "EnableSegmentDeletion": { + "type": "boolean", + "description": "Gets or sets a value indicating whether segment deletion is enabled." + }, + "SegmentKeepSeconds": { + "type": "integer", + "description": "Gets or sets seconds for which segments should be kept before being deleted.", + "format": "int32" + }, + "HardwareAccelerationType": { + "enum": [ + "none", + "amf", + "qsv", + "nvenc", + "v4l2m2m", + "vaapi", + "videotoolbox", + "rkmpp" + ], + "allOf": [ + { + "$ref": "#/components/schemas/HardwareAccelerationType" + } + ], + "description": "Gets or sets the hardware acceleration type." + }, + "EncoderAppPath": { + "type": "string", + "description": "Gets or sets the FFmpeg path as set by the user via the UI.", + "nullable": true + }, + "EncoderAppPathDisplay": { + "type": "string", + "description": "Gets or sets the current FFmpeg path being used by the system and displayed on the transcode page.", + "nullable": true + }, + "VaapiDevice": { + "type": "string", + "description": "Gets or sets the VA-API device.", + "nullable": true + }, + "QsvDevice": { + "type": "string", + "description": "Gets or sets the QSV device.", + "nullable": true + }, + "EnableTonemapping": { + "type": "boolean", + "description": "Gets or sets a value indicating whether tonemapping is enabled." + }, + "EnableVppTonemapping": { + "type": "boolean", + "description": "Gets or sets a value indicating whether VPP tonemapping is enabled." + }, + "EnableVideoToolboxTonemapping": { + "type": "boolean", + "description": "Gets or sets a value indicating whether videotoolbox tonemapping is enabled." + }, + "TonemappingAlgorithm": { + "enum": [ + "none", + "clip", + "linear", + "gamma", + "reinhard", + "hable", + "mobius", + "bt2390" + ], + "allOf": [ + { + "$ref": "#/components/schemas/TonemappingAlgorithm" + } + ], + "description": "Gets or sets the tone-mapping algorithm." + }, + "TonemappingMode": { + "enum": [ + "auto", + "max", + "rgb", + "lum", + "itp" + ], + "allOf": [ + { + "$ref": "#/components/schemas/TonemappingMode" + } + ], + "description": "Gets or sets the tone-mapping mode." + }, + "TonemappingRange": { + "enum": [ + "auto", + "tv", + "pc" + ], + "allOf": [ + { + "$ref": "#/components/schemas/TonemappingRange" + } + ], + "description": "Gets or sets the tone-mapping range." + }, + "TonemappingDesat": { + "type": "number", + "description": "Gets or sets the tone-mapping desaturation.", + "format": "double" + }, + "TonemappingPeak": { + "type": "number", + "description": "Gets or sets the tone-mapping peak.", + "format": "double" + }, + "TonemappingParam": { + "type": "number", + "description": "Gets or sets the tone-mapping parameters.", + "format": "double" + }, + "VppTonemappingBrightness": { + "type": "number", + "description": "Gets or sets the VPP tone-mapping brightness.", + "format": "double" + }, + "VppTonemappingContrast": { + "type": "number", + "description": "Gets or sets the VPP tone-mapping contrast.", + "format": "double" + }, + "H264Crf": { + "type": "integer", + "description": "Gets or sets the H264 CRF.", + "format": "int32" + }, + "H265Crf": { + "type": "integer", + "description": "Gets or sets the H265 CRF.", + "format": "int32" + }, + "EncoderPreset": { + "enum": [ + "auto", + "placebo", + "veryslow", + "slower", + "slow", + "medium", + "fast", + "faster", + "veryfast", + "superfast", + "ultrafast" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncoderPreset" + } + ], + "description": "Gets or sets the encoder preset.", + "nullable": true + }, + "DeinterlaceDoubleRate": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the framerate is doubled when deinterlacing." + }, + "DeinterlaceMethod": { + "enum": [ + "yadif", + "bwdif" + ], + "allOf": [ + { + "$ref": "#/components/schemas/DeinterlaceMethod" + } + ], + "description": "Gets or sets the deinterlace method." + }, + "EnableDecodingColorDepth10Hevc": { + "type": "boolean", + "description": "Gets or sets a value indicating whether 10bit HEVC decoding is enabled." + }, + "EnableDecodingColorDepth10Vp9": { + "type": "boolean", + "description": "Gets or sets a value indicating whether 10bit VP9 decoding is enabled." + }, + "EnableDecodingColorDepth10HevcRext": { + "type": "boolean", + "description": "Gets or sets a value indicating whether 8/10bit HEVC RExt decoding is enabled." + }, + "EnableDecodingColorDepth12HevcRext": { + "type": "boolean", + "description": "Gets or sets a value indicating whether 12bit HEVC RExt decoding is enabled." + }, + "EnableEnhancedNvdecDecoder": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the enhanced NVDEC is enabled." + }, + "PreferSystemNativeHwDecoder": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the system native hardware decoder should be used." + }, + "EnableIntelLowPowerH264HwEncoder": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the Intel H264 low-power hardware encoder should be used." + }, + "EnableIntelLowPowerHevcHwEncoder": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the Intel HEVC low-power hardware encoder should be used." + }, + "EnableHardwareEncoding": { + "type": "boolean", + "description": "Gets or sets a value indicating whether hardware encoding is enabled." + }, + "AllowHevcEncoding": { + "type": "boolean", + "description": "Gets or sets a value indicating whether HEVC encoding is enabled." + }, + "AllowAv1Encoding": { + "type": "boolean", + "description": "Gets or sets a value indicating whether AV1 encoding is enabled." + }, + "EnableSubtitleExtraction": { + "type": "boolean", + "description": "Gets or sets a value indicating whether subtitle extraction is enabled." + }, + "HardwareDecodingCodecs": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the codecs hardware encoding is used for.", + "nullable": true + }, + "AllowOnDemandMetadataBasedKeyframeExtractionForExtensions": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the file extensions on-demand metadata based keyframe extraction is enabled for.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class EncodingOptions." + }, + "EndPointInfo": { + "type": "object", + "properties": { + "IsLocal": { + "type": "boolean" + }, + "IsInNetwork": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "ExternalIdInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the display name of the external id provider (IE: IMDB, MusicBrainz, etc)." + }, + "Key": { + "type": "string", + "description": "Gets or sets the unique key for this id. This key should be unique across all providers." + }, + "Type": { + "enum": [ + "Album", + "AlbumArtist", + "Artist", + "BoxSet", + "Episode", + "Movie", + "OtherArtist", + "Person", + "ReleaseGroup", + "Season", + "Series", + "Track", + "Book", + "Recording" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ExternalIdMediaType" + } + ], + "description": "Gets or sets the specific media type for this id. This is used to distinguish between the different\r\nexternal id types for providers with multiple ids.\r\nA null value indicates there is no specific media type associated with the external id, or this is the\r\ndefault id for the external provider so there is no need to specify a type.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Represents the external id information for serialization to the client." + }, + "ExternalIdMediaType": { + "enum": [ + "Album", + "AlbumArtist", + "Artist", + "BoxSet", + "Episode", + "Movie", + "OtherArtist", + "Person", + "ReleaseGroup", + "Season", + "Series", + "Track", + "Book", + "Recording" + ], + "type": "string", + "description": "The specific media type of an MediaBrowser.Model.Providers.ExternalIdInfo." + }, + "ExternalUrl": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "Url": { + "type": "string", + "description": "Gets or sets the type of the item.", + "nullable": true + } + }, + "additionalProperties": false + }, + "ExtraType": { + "enum": [ + "Unknown", + "Clip", + "Trailer", + "BehindTheScenes", + "DeletedScene", + "Interview", + "Scene", + "Sample", + "ThemeSong", + "ThemeVideo", + "Featurette", + "Short" + ], + "type": "string" + }, + "FileSystemEntryInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets the name." + }, + "Path": { + "type": "string", + "description": "Gets the path." + }, + "Type": { + "enum": [ + "File", + "Directory", + "NetworkComputer", + "NetworkShare" + ], + "allOf": [ + { + "$ref": "#/components/schemas/FileSystemEntryType" + } + ], + "description": "Gets the type." + } + }, + "additionalProperties": false, + "description": "Class FileSystemEntryInfo." + }, + "FileSystemEntryType": { + "enum": [ + "File", + "Directory", + "NetworkComputer", + "NetworkShare" + ], + "type": "string", + "description": "Enum FileSystemEntryType." + }, + "FolderStorageDto": { + "type": "object", + "properties": { + "Path": { + "type": "string", + "description": "Gets the path of the folder in question." + }, + "FreeSpace": { + "type": "integer", + "description": "Gets the free space of the underlying storage device of the Jellyfin.Api.Models.SystemInfoDtos.FolderStorageDto.Path.", + "format": "int64" + }, + "UsedSpace": { + "type": "integer", + "description": "Gets the used space of the underlying storage device of the Jellyfin.Api.Models.SystemInfoDtos.FolderStorageDto.Path.", + "format": "int64" + }, + "StorageType": { + "type": "string", + "description": "Gets the kind of storage device of the Jellyfin.Api.Models.SystemInfoDtos.FolderStorageDto.Path.", + "nullable": true + }, + "DeviceId": { + "type": "string", + "description": "Gets the Device Identifier.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Contains information about a specific folder." + }, + "FontFile": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "Size": { + "type": "integer", + "description": "Gets or sets the size.", + "format": "int64" + }, + "DateCreated": { + "type": "string", + "description": "Gets or sets the date created.", + "format": "date-time" + }, + "DateModified": { + "type": "string", + "description": "Gets or sets the date modified.", + "format": "date-time" + } + }, + "additionalProperties": false, + "description": "Class FontFile." + }, + "ForceKeepAliveMessage": { + "type": "object", + "properties": { + "Data": { + "type": "integer", + "description": "Gets or sets the data.", + "format": "int32" + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "ForceKeepAlive", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Force keep alive websocket messages." + }, + "ForgotPasswordAction": { + "enum": [ + "ContactAdmin", + "PinCode", + "InNetworkRequired" + ], + "type": "string" + }, + "ForgotPasswordDto": { + "required": [ + "EnteredUsername" + ], + "type": "object", + "properties": { + "EnteredUsername": { + "type": "string", + "description": "Gets or sets the entered username to have its password reset." + } + }, + "additionalProperties": false, + "description": "Forgot Password request body DTO." + }, + "ForgotPasswordPinDto": { + "required": [ + "Pin" + ], + "type": "object", + "properties": { + "Pin": { + "type": "string", + "description": "Gets or sets the entered pin to have the password reset." + } + }, + "additionalProperties": false, + "description": "Forgot Password Pin enter request body DTO." + }, + "ForgotPasswordResult": { + "type": "object", + "properties": { + "Action": { + "enum": [ + "ContactAdmin", + "PinCode", + "InNetworkRequired" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ForgotPasswordAction" + } + ], + "description": "Gets or sets the action." + }, + "PinFile": { + "type": "string", + "description": "Gets or sets the pin file.", + "nullable": true + }, + "PinExpirationDate": { + "type": "string", + "description": "Gets or sets the pin expiration date.", + "format": "date-time", + "nullable": true + } + }, + "additionalProperties": false + }, + "GeneralCommand": { + "type": "object", + "properties": { + "Name": { + "enum": [ + "MoveUp", + "MoveDown", + "MoveLeft", + "MoveRight", + "PageUp", + "PageDown", + "PreviousLetter", + "NextLetter", + "ToggleOsd", + "ToggleContextMenu", + "Select", + "Back", + "TakeScreenshot", + "SendKey", + "SendString", + "GoHome", + "GoToSettings", + "VolumeUp", + "VolumeDown", + "Mute", + "Unmute", + "ToggleMute", + "SetVolume", + "SetAudioStreamIndex", + "SetSubtitleStreamIndex", + "ToggleFullscreen", + "DisplayContent", + "GoToSearch", + "DisplayMessage", + "SetRepeatMode", + "ChannelUp", + "ChannelDown", + "Guide", + "ToggleStats", + "PlayMediaSource", + "PlayTrailers", + "SetShuffleQueue", + "PlayState", + "PlayNext", + "ToggleOsdMenu", + "Play", + "SetMaxStreamingBitrate", + "SetPlaybackOrder" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GeneralCommandType" + } + ], + "description": "This exists simply to identify a set of known commands." + }, + "ControllingUserId": { + "type": "string", + "format": "uuid" + }, + "Arguments": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + } + } + }, + "additionalProperties": false + }, + "GeneralCommandMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/GeneralCommand" + } + ], + "description": "Gets or sets the data.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "GeneralCommand", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "General command websocket message." + }, + "GeneralCommandType": { + "enum": [ + "MoveUp", + "MoveDown", + "MoveLeft", + "MoveRight", + "PageUp", + "PageDown", + "PreviousLetter", + "NextLetter", + "ToggleOsd", + "ToggleContextMenu", + "Select", + "Back", + "TakeScreenshot", + "SendKey", + "SendString", + "GoHome", + "GoToSettings", + "VolumeUp", + "VolumeDown", + "Mute", + "Unmute", + "ToggleMute", + "SetVolume", + "SetAudioStreamIndex", + "SetSubtitleStreamIndex", + "ToggleFullscreen", + "DisplayContent", + "GoToSearch", + "DisplayMessage", + "SetRepeatMode", + "ChannelUp", + "ChannelDown", + "Guide", + "ToggleStats", + "PlayMediaSource", + "PlayTrailers", + "SetShuffleQueue", + "PlayState", + "PlayNext", + "ToggleOsdMenu", + "Play", + "SetMaxStreamingBitrate", + "SetPlaybackOrder" + ], + "type": "string", + "description": "This exists simply to identify a set of known commands." + }, + "GetProgramsDto": { + "type": "object", + "properties": { + "ChannelIds": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Gets or sets the channels to return guide information for.", + "nullable": true + }, + "UserId": { + "type": "string", + "description": "Gets or sets optional. Filter by user id.", + "format": "uuid", + "nullable": true + }, + "MinStartDate": { + "type": "string", + "description": "Gets or sets the minimum premiere start date.", + "format": "date-time", + "nullable": true + }, + "HasAired": { + "type": "boolean", + "description": "Gets or sets filter by programs that have completed airing, or not.", + "nullable": true + }, + "IsAiring": { + "type": "boolean", + "description": "Gets or sets filter by programs that are currently airing, or not.", + "nullable": true + }, + "MaxStartDate": { + "type": "string", + "description": "Gets or sets the maximum premiere start date.", + "format": "date-time", + "nullable": true + }, + "MinEndDate": { + "type": "string", + "description": "Gets or sets the minimum premiere end date.", + "format": "date-time", + "nullable": true + }, + "MaxEndDate": { + "type": "string", + "description": "Gets or sets the maximum premiere end date.", + "format": "date-time", + "nullable": true + }, + "IsMovie": { + "type": "boolean", + "description": "Gets or sets filter for movies.", + "nullable": true + }, + "IsSeries": { + "type": "boolean", + "description": "Gets or sets filter for series.", + "nullable": true + }, + "IsNews": { + "type": "boolean", + "description": "Gets or sets filter for news.", + "nullable": true + }, + "IsKids": { + "type": "boolean", + "description": "Gets or sets filter for kids.", + "nullable": true + }, + "IsSports": { + "type": "boolean", + "description": "Gets or sets filter for sports.", + "nullable": true + }, + "StartIndex": { + "type": "integer", + "description": "Gets or sets the record index to start at. All items with a lower index will be dropped from the results.", + "format": "int32", + "nullable": true + }, + "Limit": { + "type": "integer", + "description": "Gets or sets the maximum number of records to return.", + "format": "int32", + "nullable": true + }, + "SortBy": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemSortBy" + }, + "description": "Gets or sets specify one or more sort orders, comma delimited. Options: Name, StartDate.", + "nullable": true + }, + "SortOrder": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SortOrder" + }, + "description": "Gets or sets sort order.", + "nullable": true + }, + "Genres": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the genres to return guide information for.", + "nullable": true + }, + "GenreIds": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Gets or sets the genre ids to return guide information for.", + "nullable": true + }, + "EnableImages": { + "type": "boolean", + "description": "Gets or sets include image information in output.", + "nullable": true + }, + "EnableTotalRecordCount": { + "type": "boolean", + "description": "Gets or sets a value indicating whether retrieve total record count.", + "default": true + }, + "ImageTypeLimit": { + "type": "integer", + "description": "Gets or sets the max number of images to return, per image type.", + "format": "int32", + "nullable": true + }, + "EnableImageTypes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + }, + "description": "Gets or sets the image types to include in the output.", + "nullable": true + }, + "EnableUserData": { + "type": "boolean", + "description": "Gets or sets include user data.", + "nullable": true + }, + "SeriesTimerId": { + "type": "string", + "description": "Gets or sets filter by series timer id.", + "nullable": true + }, + "LibrarySeriesId": { + "type": "string", + "description": "Gets or sets filter by library series id.", + "format": "uuid", + "nullable": true + }, + "Fields": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ItemFields" + }, + "description": "Gets or sets specify additional fields of information to return in the output.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Get programs dto." + }, + "GroupInfoDto": { + "type": "object", + "properties": { + "GroupId": { + "type": "string", + "description": "Gets the group identifier.", + "format": "uuid" + }, + "GroupName": { + "type": "string", + "description": "Gets the group name." + }, + "State": { + "enum": [ + "Idle", + "Waiting", + "Paused", + "Playing" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GroupStateType" + } + ], + "description": "Gets the group state." + }, + "Participants": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets the participants." + }, + "LastUpdatedAt": { + "type": "string", + "description": "Gets the date when this DTO has been created.", + "format": "date-time" + } + }, + "additionalProperties": false, + "description": "Class GroupInfoDto." + }, + "GroupQueueMode": { + "enum": [ + "Queue", + "QueueNext" + ], + "type": "string", + "description": "Enum GroupQueueMode." + }, + "GroupRepeatMode": { + "enum": [ + "RepeatOne", + "RepeatAll", + "RepeatNone" + ], + "type": "string", + "description": "Enum GroupRepeatMode." + }, + "GroupShuffleMode": { + "enum": [ + "Sorted", + "Shuffle" + ], + "type": "string", + "description": "Enum GroupShuffleMode." + }, + "GroupStateType": { + "enum": [ + "Idle", + "Waiting", + "Paused", + "Playing" + ], + "type": "string", + "description": "Enum GroupState." + }, + "GroupStateUpdate": { + "type": "object", + "properties": { + "State": { + "enum": [ + "Idle", + "Waiting", + "Paused", + "Playing" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GroupStateType" + } + ], + "description": "Gets the state of the group." + }, + "Reason": { + "enum": [ + "Play", + "SetPlaylistItem", + "RemoveFromPlaylist", + "MovePlaylistItem", + "Queue", + "Unpause", + "Pause", + "Stop", + "Seek", + "Buffer", + "Ready", + "NextItem", + "PreviousItem", + "SetRepeatMode", + "SetShuffleMode", + "Ping", + "IgnoreWait" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PlaybackRequestType" + } + ], + "description": "Gets the reason of the state change." + } + }, + "additionalProperties": false, + "description": "Class GroupStateUpdate." + }, + "GroupUpdate": { + "type": "object", + "oneOf": [ + { + "$ref": "#/components/schemas/SyncPlayGroupDoesNotExistUpdate" + }, + { + "$ref": "#/components/schemas/SyncPlayGroupJoinedUpdate" + }, + { + "$ref": "#/components/schemas/SyncPlayGroupLeftUpdate" + }, + { + "$ref": "#/components/schemas/SyncPlayLibraryAccessDeniedUpdate" + }, + { + "$ref": "#/components/schemas/SyncPlayNotInGroupUpdate" + }, + { + "$ref": "#/components/schemas/SyncPlayPlayQueueUpdate" + }, + { + "$ref": "#/components/schemas/SyncPlayStateUpdate" + }, + { + "$ref": "#/components/schemas/SyncPlayUserJoinedUpdate" + }, + { + "$ref": "#/components/schemas/SyncPlayUserLeftUpdate" + } + ], + "description": "Represents the list of possible group update types", + "discriminator": { + "propertyName": "Type", + "mapping": { + "GroupDoesNotExist": "#/components/schemas/SyncPlayGroupDoesNotExistUpdate", + "GroupJoined": "#/components/schemas/SyncPlayGroupJoinedUpdate", + "GroupLeft": "#/components/schemas/SyncPlayGroupLeftUpdate", + "LibraryAccessDenied": "#/components/schemas/SyncPlayLibraryAccessDeniedUpdate", + "NotInGroup": "#/components/schemas/SyncPlayNotInGroupUpdate", + "PlayQueue": "#/components/schemas/SyncPlayPlayQueueUpdate", + "StateUpdate": "#/components/schemas/SyncPlayStateUpdate", + "UserJoined": "#/components/schemas/SyncPlayUserJoinedUpdate", + "UserLeft": "#/components/schemas/SyncPlayUserLeftUpdate" + } + } + }, + "GroupUpdateType": { + "enum": [ + "UserJoined", + "UserLeft", + "GroupJoined", + "GroupLeft", + "StateUpdate", + "PlayQueue", + "NotInGroup", + "GroupDoesNotExist", + "LibraryAccessDenied" + ], + "type": "string", + "description": "Enum GroupUpdateType." + }, + "GuideInfo": { + "type": "object", + "properties": { + "StartDate": { + "type": "string", + "description": "Gets or sets the start date.", + "format": "date-time" + }, + "EndDate": { + "type": "string", + "description": "Gets or sets the end date.", + "format": "date-time" + } + }, + "additionalProperties": false + }, + "HardwareAccelerationType": { + "enum": [ + "none", + "amf", + "qsv", + "nvenc", + "v4l2m2m", + "vaapi", + "videotoolbox", + "rkmpp" + ], + "type": "string", + "description": "Enum containing hardware acceleration types." + }, + "IgnoreWaitRequestDto": { + "type": "object", + "properties": { + "IgnoreWait": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the client should be ignored." + } + }, + "additionalProperties": false, + "description": "Class IgnoreWaitRequestDto." + }, + "ImageFormat": { + "enum": [ + "Bmp", + "Gif", + "Jpg", + "Png", + "Webp", + "Svg" + ], + "type": "string", + "description": "Enum ImageOutputFormat." + }, + "ImageInfo": { + "type": "object", + "properties": { + "ImageType": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Gets or sets the type of the image." + }, + "ImageIndex": { + "type": "integer", + "description": "Gets or sets the index of the image.", + "format": "int32", + "nullable": true + }, + "ImageTag": { + "type": "string", + "description": "Gets or sets the image tag.", + "nullable": true + }, + "Path": { + "type": "string", + "description": "Gets or sets the path.", + "nullable": true + }, + "BlurHash": { + "type": "string", + "description": "Gets or sets the blurhash.", + "nullable": true + }, + "Height": { + "type": "integer", + "description": "Gets or sets the height.", + "format": "int32", + "nullable": true + }, + "Width": { + "type": "integer", + "description": "Gets or sets the width.", + "format": "int32", + "nullable": true + }, + "Size": { + "type": "integer", + "description": "Gets or sets the size.", + "format": "int64" + } + }, + "additionalProperties": false, + "description": "Class ImageInfo." + }, + "ImageOption": { + "type": "object", + "properties": { + "Type": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Gets or sets the type." + }, + "Limit": { + "type": "integer", + "description": "Gets or sets the limit.", + "format": "int32" + }, + "MinWidth": { + "type": "integer", + "description": "Gets or sets the minimum width.", + "format": "int32" + } + }, + "additionalProperties": false + }, + "ImageOrientation": { + "enum": [ + "TopLeft", + "TopRight", + "BottomRight", + "BottomLeft", + "LeftTop", + "RightTop", + "RightBottom", + "LeftBottom" + ], + "type": "string" + }, + "ImageProviderInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets the name." + }, + "SupportedImages": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + }, + "description": "Gets the supported image types." + } + }, + "additionalProperties": false, + "description": "Class ImageProviderInfo." + }, + "ImageResolution": { + "enum": [ + "MatchSource", + "P144", + "P240", + "P360", + "P480", + "P720", + "P1080", + "P1440", + "P2160" + ], + "type": "string", + "description": "Enum ImageResolution." + }, + "ImageSavingConvention": { + "enum": [ + "Legacy", + "Compatible" + ], + "type": "string" + }, + "ImageType": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "type": "string", + "description": "Enum ImageType." + }, + "InboundKeepAliveMessage": { + "type": "object", + "properties": { + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "KeepAlive", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Keep alive websocket messages." + }, + "InboundWebSocketMessage": { + "type": "object", + "oneOf": [ + { + "$ref": "#/components/schemas/ActivityLogEntryStartMessage" + }, + { + "$ref": "#/components/schemas/ActivityLogEntryStopMessage" + }, + { + "$ref": "#/components/schemas/InboundKeepAliveMessage" + }, + { + "$ref": "#/components/schemas/ScheduledTasksInfoStartMessage" + }, + { + "$ref": "#/components/schemas/ScheduledTasksInfoStopMessage" + }, + { + "$ref": "#/components/schemas/SessionsStartMessage" + }, + { + "$ref": "#/components/schemas/SessionsStopMessage" + } + ], + "description": "Represents the list of possible inbound websocket types", + "discriminator": { + "propertyName": "MessageType", + "mapping": { + "ActivityLogEntryStart": "#/components/schemas/ActivityLogEntryStartMessage", + "ActivityLogEntryStop": "#/components/schemas/ActivityLogEntryStopMessage", + "KeepAlive": "#/components/schemas/InboundKeepAliveMessage", + "ScheduledTasksInfoStart": "#/components/schemas/ScheduledTasksInfoStartMessage", + "ScheduledTasksInfoStop": "#/components/schemas/ScheduledTasksInfoStopMessage", + "SessionsStart": "#/components/schemas/SessionsStartMessage", + "SessionsStop": "#/components/schemas/SessionsStopMessage" + } + } + }, + "InstallationInfo": { + "type": "object", + "properties": { + "Guid": { + "type": "string", + "description": "Gets or sets the Id.", + "format": "uuid" + }, + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "Version": { + "type": "string", + "description": "Gets or sets the version.", + "nullable": true + }, + "Changelog": { + "type": "string", + "description": "Gets or sets the changelog for this version.", + "nullable": true + }, + "SourceUrl": { + "type": "string", + "description": "Gets or sets the source URL.", + "nullable": true + }, + "Checksum": { + "type": "string", + "description": "Gets or sets a checksum for the binary.", + "nullable": true + }, + "PackageInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/PackageInfo" + } + ], + "description": "Gets or sets package information for the installation.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class InstallationInfo." + }, + "IPlugin": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets the name of the plugin.", + "nullable": true, + "readOnly": true + }, + "Description": { + "type": "string", + "description": "Gets the Description.", + "nullable": true, + "readOnly": true + }, + "Id": { + "type": "string", + "description": "Gets the unique id.", + "format": "uuid", + "readOnly": true + }, + "Version": { + "type": "string", + "description": "Gets the plugin version.", + "nullable": true, + "readOnly": true + }, + "AssemblyFilePath": { + "type": "string", + "description": "Gets the path to the assembly file.", + "nullable": true, + "readOnly": true + }, + "CanUninstall": { + "type": "boolean", + "description": "Gets a value indicating whether the plugin can be uninstalled.", + "readOnly": true + }, + "DataFolderPath": { + "type": "string", + "description": "Gets the full path to the data folder, where the plugin can store any miscellaneous files needed.", + "nullable": true, + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Defines the MediaBrowser.Common.Plugins.IPlugin." + }, + "IsoType": { + "enum": [ + "Dvd", + "BluRay" + ], + "type": "string", + "description": "Enum IsoType." + }, + "ItemCounts": { + "type": "object", + "properties": { + "MovieCount": { + "type": "integer", + "description": "Gets or sets the movie count.", + "format": "int32" + }, + "SeriesCount": { + "type": "integer", + "description": "Gets or sets the series count.", + "format": "int32" + }, + "EpisodeCount": { + "type": "integer", + "description": "Gets or sets the episode count.", + "format": "int32" + }, + "ArtistCount": { + "type": "integer", + "description": "Gets or sets the artist count.", + "format": "int32" + }, + "ProgramCount": { + "type": "integer", + "description": "Gets or sets the program count.", + "format": "int32" + }, + "TrailerCount": { + "type": "integer", + "description": "Gets or sets the trailer count.", + "format": "int32" + }, + "SongCount": { + "type": "integer", + "description": "Gets or sets the song count.", + "format": "int32" + }, + "AlbumCount": { + "type": "integer", + "description": "Gets or sets the album count.", + "format": "int32" + }, + "MusicVideoCount": { + "type": "integer", + "description": "Gets or sets the music video count.", + "format": "int32" + }, + "BoxSetCount": { + "type": "integer", + "description": "Gets or sets the box set count.", + "format": "int32" + }, + "BookCount": { + "type": "integer", + "description": "Gets or sets the book count.", + "format": "int32" + }, + "ItemCount": { + "type": "integer", + "description": "Gets or sets the item count.", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "Class LibrarySummary." + }, + "ItemFields": { + "enum": [ + "AirTime", + "CanDelete", + "CanDownload", + "ChannelInfo", + "Chapters", + "Trickplay", + "ChildCount", + "CumulativeRunTimeTicks", + "CustomRating", + "DateCreated", + "DateLastMediaAdded", + "DisplayPreferencesId", + "Etag", + "ExternalUrls", + "Genres", + "ItemCounts", + "MediaSourceCount", + "MediaSources", + "OriginalTitle", + "Overview", + "ParentId", + "Path", + "People", + "PlayAccess", + "ProductionLocations", + "ProviderIds", + "PrimaryImageAspectRatio", + "RecursiveItemCount", + "Settings", + "SeriesStudio", + "SortName", + "SpecialEpisodeNumbers", + "Studios", + "Taglines", + "Tags", + "RemoteTrailers", + "MediaStreams", + "SeasonUserData", + "DateLastRefreshed", + "DateLastSaved", + "RefreshState", + "ChannelImage", + "EnableMediaSourceDisplay", + "Width", + "Height", + "ExtraIds", + "LocalTrailerCount", + "IsHD", + "SpecialFeatureCount" + ], + "type": "string", + "description": "Used to control the data that gets attached to DtoBaseItems." + }, + "ItemFilter": { + "enum": [ + "IsFolder", + "IsNotFolder", + "IsUnplayed", + "IsPlayed", + "IsFavorite", + "IsResumable", + "Likes", + "Dislikes", + "IsFavoriteOrLikes" + ], + "type": "string", + "description": "Enum ItemFilter." + }, + "ItemSortBy": { + "enum": [ + "Default", + "AiredEpisodeOrder", + "Album", + "AlbumArtist", + "Artist", + "DateCreated", + "OfficialRating", + "DatePlayed", + "PremiereDate", + "StartDate", + "SortName", + "Name", + "Random", + "Runtime", + "CommunityRating", + "ProductionYear", + "PlayCount", + "CriticRating", + "IsFolder", + "IsUnplayed", + "IsPlayed", + "SeriesSortName", + "VideoBitRate", + "AirTime", + "Studio", + "IsFavoriteOrLiked", + "DateLastContentAdded", + "SeriesDatePlayed", + "ParentIndexNumber", + "IndexNumber" + ], + "type": "string", + "description": "These represent sort orders." + }, + "JoinGroupRequestDto": { + "type": "object", + "properties": { + "GroupId": { + "type": "string", + "description": "Gets or sets the group identifier.", + "format": "uuid" + } + }, + "additionalProperties": false, + "description": "Class JoinGroupRequestDto." + }, + "KeepUntil": { + "enum": [ + "UntilDeleted", + "UntilSpaceNeeded", + "UntilWatched", + "UntilDate" + ], + "type": "string" + }, + "LibraryChangedMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/LibraryUpdateInfo" + } + ], + "description": "Class LibraryUpdateInfo.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "LibraryChanged", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Library changed message." + }, + "LibraryOptionInfoDto": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets name.", + "nullable": true + }, + "DefaultEnabled": { + "type": "boolean", + "description": "Gets or sets a value indicating whether default enabled." + } + }, + "additionalProperties": false, + "description": "Library option info dto." + }, + "LibraryOptions": { + "type": "object", + "properties": { + "Enabled": { + "type": "boolean" + }, + "EnablePhotos": { + "type": "boolean" + }, + "EnableRealtimeMonitor": { + "type": "boolean" + }, + "EnableLUFSScan": { + "type": "boolean" + }, + "EnableChapterImageExtraction": { + "type": "boolean" + }, + "ExtractChapterImagesDuringLibraryScan": { + "type": "boolean" + }, + "EnableTrickplayImageExtraction": { + "type": "boolean" + }, + "ExtractTrickplayImagesDuringLibraryScan": { + "type": "boolean" + }, + "PathInfos": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaPathInfo" + } + }, + "SaveLocalMetadata": { + "type": "boolean" + }, + "EnableInternetProviders": { + "type": "boolean", + "deprecated": true + }, + "EnableAutomaticSeriesGrouping": { + "type": "boolean" + }, + "EnableEmbeddedTitles": { + "type": "boolean" + }, + "EnableEmbeddedExtrasTitles": { + "type": "boolean" + }, + "EnableEmbeddedEpisodeInfos": { + "type": "boolean" + }, + "AutomaticRefreshIntervalDays": { + "type": "integer", + "format": "int32" + }, + "PreferredMetadataLanguage": { + "type": "string", + "description": "Gets or sets the preferred metadata language.", + "nullable": true + }, + "MetadataCountryCode": { + "type": "string", + "description": "Gets or sets the metadata country code.", + "nullable": true + }, + "SeasonZeroDisplayName": { + "type": "string" + }, + "MetadataSavers": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "DisabledLocalMetadataReaders": { + "type": "array", + "items": { + "type": "string" + } + }, + "LocalMetadataReaderOrder": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "DisabledSubtitleFetchers": { + "type": "array", + "items": { + "type": "string" + } + }, + "SubtitleFetcherOrder": { + "type": "array", + "items": { + "type": "string" + } + }, + "DisabledMediaSegmentProviders": { + "type": "array", + "items": { + "type": "string" + } + }, + "MediaSegmentProviderOrder": { + "type": "array", + "items": { + "type": "string" + } + }, + "SkipSubtitlesIfEmbeddedSubtitlesPresent": { + "type": "boolean" + }, + "SkipSubtitlesIfAudioTrackMatches": { + "type": "boolean" + }, + "SubtitleDownloadLanguages": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "RequirePerfectSubtitleMatch": { + "type": "boolean" + }, + "SaveSubtitlesWithMedia": { + "type": "boolean" + }, + "SaveLyricsWithMedia": { + "type": "boolean", + "default": false + }, + "SaveTrickplayWithMedia": { + "type": "boolean", + "default": false + }, + "DisabledLyricFetchers": { + "type": "array", + "items": { + "type": "string" + } + }, + "LyricFetcherOrder": { + "type": "array", + "items": { + "type": "string" + } + }, + "PreferNonstandardArtistsTag": { + "type": "boolean", + "default": false + }, + "UseCustomTagDelimiters": { + "type": "boolean", + "default": false + }, + "CustomTagDelimiters": { + "type": "array", + "items": { + "type": "string" + } + }, + "DelimiterWhitelist": { + "type": "array", + "items": { + "type": "string" + } + }, + "AutomaticallyAddToCollection": { + "type": "boolean" + }, + "AllowEmbeddedSubtitles": { + "enum": [ + "AllowAll", + "AllowText", + "AllowImage", + "AllowNone" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EmbeddedSubtitleOptions" + } + ], + "description": "An enum representing the options to disable embedded subs." + }, + "TypeOptions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TypeOptions" + } + } + }, + "additionalProperties": false + }, + "LibraryOptionsResultDto": { + "type": "object", + "properties": { + "MetadataSavers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LibraryOptionInfoDto" + }, + "description": "Gets or sets the metadata savers." + }, + "MetadataReaders": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LibraryOptionInfoDto" + }, + "description": "Gets or sets the metadata readers." + }, + "SubtitleFetchers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LibraryOptionInfoDto" + }, + "description": "Gets or sets the subtitle fetchers." + }, + "LyricFetchers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LibraryOptionInfoDto" + }, + "description": "Gets or sets the list of lyric fetchers." + }, + "MediaSegmentProviders": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LibraryOptionInfoDto" + }, + "description": "Gets or sets the list of MediaSegment Providers." + }, + "TypeOptions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LibraryTypeOptionsDto" + }, + "description": "Gets or sets the type options." + } + }, + "additionalProperties": false, + "description": "Library options result dto." + }, + "LibraryStorageDto": { + "type": "object", + "properties": { + "Id": { + "type": "string", + "description": "Gets or sets the Library Id.", + "format": "uuid" + }, + "Name": { + "type": "string", + "description": "Gets or sets the name of the library." + }, + "Folders": { + "type": "array", + "items": { + "$ref": "#/components/schemas/FolderStorageDto" + }, + "description": "Gets or sets the storage informations about the folders used in a library." + } + }, + "additionalProperties": false, + "description": "Contains informations about a libraries storage informations." + }, + "LibraryTypeOptionsDto": { + "type": "object", + "properties": { + "Type": { + "type": "string", + "description": "Gets or sets the type.", + "nullable": true + }, + "MetadataFetchers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LibraryOptionInfoDto" + }, + "description": "Gets or sets the metadata fetchers." + }, + "ImageFetchers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LibraryOptionInfoDto" + }, + "description": "Gets or sets the image fetchers." + }, + "SupportedImageTypes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageType" + }, + "description": "Gets or sets the supported image types." + }, + "DefaultImageOptions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageOption" + }, + "description": "Gets or sets the default image options." + } + }, + "additionalProperties": false, + "description": "Library type options dto." + }, + "LibraryUpdateInfo": { + "type": "object", + "properties": { + "FoldersAddedTo": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the folders added to." + }, + "FoldersRemovedFrom": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the folders removed from." + }, + "ItemsAdded": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the items added." + }, + "ItemsRemoved": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the items removed." + }, + "ItemsUpdated": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the items updated." + }, + "CollectionFolders": { + "type": "array", + "items": { + "type": "string" + } + }, + "IsEmpty": { + "type": "boolean", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Class LibraryUpdateInfo." + }, + "ListingsProviderInfo": { + "type": "object", + "properties": { + "Id": { + "type": "string", + "nullable": true + }, + "Type": { + "type": "string", + "nullable": true + }, + "Username": { + "type": "string", + "nullable": true + }, + "Password": { + "type": "string", + "nullable": true + }, + "ListingsId": { + "type": "string", + "nullable": true + }, + "ZipCode": { + "type": "string", + "nullable": true + }, + "Country": { + "type": "string", + "nullable": true + }, + "Path": { + "type": "string", + "nullable": true + }, + "EnabledTuners": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "EnableAllTuners": { + "type": "boolean" + }, + "NewsCategories": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "SportsCategories": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "KidsCategories": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "MovieCategories": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "ChannelMappings": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameValuePair" + }, + "nullable": true + }, + "MoviePrefix": { + "type": "string", + "nullable": true + }, + "PreferredLanguage": { + "type": "string", + "nullable": true + }, + "UserAgent": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "LiveStreamResponse": { + "type": "object", + "properties": { + "MediaSource": { + "allOf": [ + { + "$ref": "#/components/schemas/MediaSourceInfo" + } + ] + } + }, + "additionalProperties": false + }, + "LiveTvInfo": { + "type": "object", + "properties": { + "Services": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LiveTvServiceInfo" + }, + "description": "Gets or sets the services." + }, + "IsEnabled": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is enabled." + }, + "EnabledUsers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the enabled users." + } + }, + "additionalProperties": false + }, + "LiveTvOptions": { + "type": "object", + "properties": { + "GuideDays": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "RecordingPath": { + "type": "string", + "nullable": true + }, + "MovieRecordingPath": { + "type": "string", + "nullable": true + }, + "SeriesRecordingPath": { + "type": "string", + "nullable": true + }, + "EnableRecordingSubfolders": { + "type": "boolean" + }, + "EnableOriginalAudioWithEncodedRecordings": { + "type": "boolean" + }, + "TunerHosts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TunerHostInfo" + }, + "nullable": true + }, + "ListingProviders": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ListingsProviderInfo" + }, + "nullable": true + }, + "PrePaddingSeconds": { + "type": "integer", + "format": "int32" + }, + "PostPaddingSeconds": { + "type": "integer", + "format": "int32" + }, + "MediaLocationsCreated": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "RecordingPostProcessor": { + "type": "string", + "nullable": true + }, + "RecordingPostProcessorArguments": { + "type": "string", + "nullable": true + }, + "SaveRecordingNFO": { + "type": "boolean" + }, + "SaveRecordingImages": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "LiveTvServiceInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "HomePageUrl": { + "type": "string", + "description": "Gets or sets the home page URL.", + "nullable": true + }, + "Status": { + "enum": [ + "Ok", + "Unavailable" + ], + "allOf": [ + { + "$ref": "#/components/schemas/LiveTvServiceStatus" + } + ], + "description": "Gets or sets the status." + }, + "StatusMessage": { + "type": "string", + "description": "Gets or sets the status message.", + "nullable": true + }, + "Version": { + "type": "string", + "description": "Gets or sets the version.", + "nullable": true + }, + "HasUpdateAvailable": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance has update available." + }, + "IsVisible": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is visible." + }, + "Tuners": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class ServiceInfo." + }, + "LiveTvServiceStatus": { + "enum": [ + "Ok", + "Unavailable" + ], + "type": "string" + }, + "LocalizationOption": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "nullable": true + }, + "Value": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "LocationType": { + "enum": [ + "FileSystem", + "Remote", + "Virtual", + "Offline" + ], + "type": "string", + "description": "Enum LocationType." + }, + "LogFile": { + "type": "object", + "properties": { + "DateCreated": { + "type": "string", + "description": "Gets or sets the date created.", + "format": "date-time" + }, + "DateModified": { + "type": "string", + "description": "Gets or sets the date modified.", + "format": "date-time" + }, + "Size": { + "type": "integer", + "description": "Gets or sets the size.", + "format": "int64" + }, + "Name": { + "type": "string", + "description": "Gets or sets the name." + } + }, + "additionalProperties": false + }, + "LogLevel": { + "enum": [ + "Trace", + "Debug", + "Information", + "Warning", + "Error", + "Critical", + "None" + ], + "type": "string" + }, + "LyricDto": { + "type": "object", + "properties": { + "Metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/LyricMetadata" + } + ], + "description": "Gets or sets Metadata for the lyrics." + }, + "Lyrics": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LyricLine" + }, + "description": "Gets or sets a collection of individual lyric lines." + } + }, + "additionalProperties": false, + "description": "LyricResponse model." + }, + "LyricLine": { + "type": "object", + "properties": { + "Text": { + "type": "string", + "description": "Gets the text of this lyric line." + }, + "Start": { + "type": "integer", + "description": "Gets the start time in ticks.", + "format": "int64", + "nullable": true + }, + "Cues": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LyricLineCue" + }, + "description": "Gets the time-aligned cues for the song's lyrics.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Lyric model." + }, + "LyricLineCue": { + "type": "object", + "properties": { + "Position": { + "type": "integer", + "description": "Gets the start character index of the cue.", + "format": "int32" + }, + "EndPosition": { + "type": "integer", + "description": "Gets the end character index of the cue.", + "format": "int32" + }, + "Start": { + "type": "integer", + "description": "Gets the timestamp the lyric is synced to in ticks.", + "format": "int64" + }, + "End": { + "type": "integer", + "description": "Gets the end timestamp the lyric is synced to in ticks.", + "format": "int64", + "nullable": true + } + }, + "additionalProperties": false, + "description": "LyricLineCue model, holds information about the timing of words within a LyricLine." + }, + "LyricMetadata": { + "type": "object", + "properties": { + "Artist": { + "type": "string", + "description": "Gets or sets the song artist.", + "nullable": true + }, + "Album": { + "type": "string", + "description": "Gets or sets the album this song is on.", + "nullable": true + }, + "Title": { + "type": "string", + "description": "Gets or sets the title of the song.", + "nullable": true + }, + "Author": { + "type": "string", + "description": "Gets or sets the author of the lyric data.", + "nullable": true + }, + "Length": { + "type": "integer", + "description": "Gets or sets the length of the song in ticks.", + "format": "int64", + "nullable": true + }, + "By": { + "type": "string", + "description": "Gets or sets who the LRC file was created by.", + "nullable": true + }, + "Offset": { + "type": "integer", + "description": "Gets or sets the lyric offset compared to audio in ticks.", + "format": "int64", + "nullable": true + }, + "Creator": { + "type": "string", + "description": "Gets or sets the software used to create the LRC file.", + "nullable": true + }, + "Version": { + "type": "string", + "description": "Gets or sets the version of the creator used.", + "nullable": true + }, + "IsSynced": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this lyric is synced.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "LyricMetadata model." + }, + "MediaAttachment": { + "type": "object", + "properties": { + "Codec": { + "type": "string", + "description": "Gets or sets the codec.", + "nullable": true + }, + "CodecTag": { + "type": "string", + "description": "Gets or sets the codec tag.", + "nullable": true + }, + "Comment": { + "type": "string", + "description": "Gets or sets the comment.", + "nullable": true + }, + "Index": { + "type": "integer", + "description": "Gets or sets the index.", + "format": "int32" + }, + "FileName": { + "type": "string", + "description": "Gets or sets the filename.", + "nullable": true + }, + "MimeType": { + "type": "string", + "description": "Gets or sets the MIME type.", + "nullable": true + }, + "DeliveryUrl": { + "type": "string", + "description": "Gets or sets the delivery URL.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class MediaAttachment." + }, + "MediaPathDto": { + "required": [ + "Name" + ], + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name of the library." + }, + "Path": { + "type": "string", + "description": "Gets or sets the path to add.", + "nullable": true + }, + "PathInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/MediaPathInfo" + } + ], + "description": "Gets or sets the path info.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Media Path dto." + }, + "MediaPathInfo": { + "type": "object", + "properties": { + "Path": { + "type": "string" + } + }, + "additionalProperties": false + }, + "MediaProtocol": { + "enum": [ + "File", + "Http", + "Rtmp", + "Rtsp", + "Udp", + "Rtp", + "Ftp" + ], + "type": "string" + }, + "MediaSegmentDto": { + "type": "object", + "properties": { + "Id": { + "type": "string", + "description": "Gets or sets the id of the media segment.", + "format": "uuid" + }, + "ItemId": { + "type": "string", + "description": "Gets or sets the id of the associated item.", + "format": "uuid" + }, + "Type": { + "enum": [ + "Unknown", + "Commercial", + "Preview", + "Recap", + "Outro", + "Intro" + ], + "allOf": [ + { + "$ref": "#/components/schemas/MediaSegmentType" + } + ], + "description": "Gets or sets the type of content this segment defines.", + "default": "Unknown" + }, + "StartTicks": { + "type": "integer", + "description": "Gets or sets the start of the segment.", + "format": "int64" + }, + "EndTicks": { + "type": "integer", + "description": "Gets or sets the end of the segment.", + "format": "int64" + } + }, + "additionalProperties": false, + "description": "Api model for MediaSegment's." + }, + "MediaSegmentDtoQueryResult": { + "type": "object", + "properties": { + "Items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaSegmentDto" + }, + "description": "Gets or sets the items." + }, + "TotalRecordCount": { + "type": "integer", + "description": "Gets or sets the total number of records available.", + "format": "int32" + }, + "StartIndex": { + "type": "integer", + "description": "Gets or sets the index of the first record in Items.", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "Query result container." + }, + "MediaSegmentType": { + "enum": [ + "Unknown", + "Commercial", + "Preview", + "Recap", + "Outro", + "Intro" + ], + "type": "string", + "description": "Defines the types of content an individual Jellyfin.Database.Implementations.Entities.MediaSegment represents." + }, + "MediaSourceInfo": { + "type": "object", + "properties": { + "Protocol": { + "enum": [ + "File", + "Http", + "Rtmp", + "Rtsp", + "Udp", + "Rtp", + "Ftp" + ], + "allOf": [ + { + "$ref": "#/components/schemas/MediaProtocol" + } + ] + }, + "Id": { + "type": "string", + "nullable": true + }, + "Path": { + "type": "string", + "nullable": true + }, + "EncoderPath": { + "type": "string", + "nullable": true + }, + "EncoderProtocol": { + "enum": [ + "File", + "Http", + "Rtmp", + "Rtsp", + "Udp", + "Rtp", + "Ftp" + ], + "allOf": [ + { + "$ref": "#/components/schemas/MediaProtocol" + } + ], + "nullable": true + }, + "Type": { + "enum": [ + "Default", + "Grouping", + "Placeholder" + ], + "allOf": [ + { + "$ref": "#/components/schemas/MediaSourceType" + } + ] + }, + "Container": { + "type": "string", + "nullable": true + }, + "Size": { + "type": "integer", + "format": "int64", + "nullable": true + }, + "Name": { + "type": "string", + "nullable": true + }, + "IsRemote": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the media is remote.\r\nDifferentiate internet url vs local network." + }, + "ETag": { + "type": "string", + "nullable": true + }, + "RunTimeTicks": { + "type": "integer", + "format": "int64", + "nullable": true + }, + "ReadAtNativeFramerate": { + "type": "boolean" + }, + "IgnoreDts": { + "type": "boolean" + }, + "IgnoreIndex": { + "type": "boolean" + }, + "GenPtsInput": { + "type": "boolean" + }, + "SupportsTranscoding": { + "type": "boolean" + }, + "SupportsDirectStream": { + "type": "boolean" + }, + "SupportsDirectPlay": { + "type": "boolean" + }, + "IsInfiniteStream": { + "type": "boolean" + }, + "UseMostCompatibleTranscodingProfile": { + "type": "boolean", + "default": false + }, + "RequiresOpening": { + "type": "boolean" + }, + "OpenToken": { + "type": "string", + "nullable": true + }, + "RequiresClosing": { + "type": "boolean" + }, + "LiveStreamId": { + "type": "string", + "nullable": true + }, + "BufferMs": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "RequiresLooping": { + "type": "boolean" + }, + "SupportsProbing": { + "type": "boolean" + }, + "VideoType": { + "enum": [ + "VideoFile", + "Iso", + "Dvd", + "BluRay" + ], + "allOf": [ + { + "$ref": "#/components/schemas/VideoType" + } + ], + "nullable": true + }, + "IsoType": { + "enum": [ + "Dvd", + "BluRay" + ], + "allOf": [ + { + "$ref": "#/components/schemas/IsoType" + } + ], + "nullable": true + }, + "Video3DFormat": { + "enum": [ + "HalfSideBySide", + "FullSideBySide", + "FullTopAndBottom", + "HalfTopAndBottom", + "MVC" + ], + "allOf": [ + { + "$ref": "#/components/schemas/Video3DFormat" + } + ], + "nullable": true + }, + "MediaStreams": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaStream" + }, + "nullable": true + }, + "MediaAttachments": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaAttachment" + }, + "nullable": true + }, + "Formats": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "Bitrate": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "FallbackMaxStreamingBitrate": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "Timestamp": { + "enum": [ + "None", + "Zero", + "Valid" + ], + "allOf": [ + { + "$ref": "#/components/schemas/TransportStreamTimestamp" + } + ], + "nullable": true + }, + "RequiredHttpHeaders": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "nullable": true + }, + "TranscodingUrl": { + "type": "string", + "nullable": true + }, + "TranscodingSubProtocol": { + "enum": [ + "http", + "hls" + ], + "allOf": [ + { + "$ref": "#/components/schemas/MediaStreamProtocol" + } + ], + "description": "Media streaming protocol.\r\nLowercase for backwards compatibility." + }, + "TranscodingContainer": { + "type": "string", + "nullable": true + }, + "AnalyzeDurationMs": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "DefaultAudioStreamIndex": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "DefaultSubtitleStreamIndex": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "HasSegments": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "MediaSourceType": { + "enum": [ + "Default", + "Grouping", + "Placeholder" + ], + "type": "string" + }, + "MediaStream": { + "type": "object", + "properties": { + "Codec": { + "type": "string", + "description": "Gets or sets the codec.", + "nullable": true + }, + "CodecTag": { + "type": "string", + "description": "Gets or sets the codec tag.", + "nullable": true + }, + "Language": { + "type": "string", + "description": "Gets or sets the language.", + "nullable": true + }, + "ColorRange": { + "type": "string", + "description": "Gets or sets the color range.", + "nullable": true + }, + "ColorSpace": { + "type": "string", + "description": "Gets or sets the color space.", + "nullable": true + }, + "ColorTransfer": { + "type": "string", + "description": "Gets or sets the color transfer.", + "nullable": true + }, + "ColorPrimaries": { + "type": "string", + "description": "Gets or sets the color primaries.", + "nullable": true + }, + "DvVersionMajor": { + "type": "integer", + "description": "Gets or sets the Dolby Vision version major.", + "format": "int32", + "nullable": true + }, + "DvVersionMinor": { + "type": "integer", + "description": "Gets or sets the Dolby Vision version minor.", + "format": "int32", + "nullable": true + }, + "DvProfile": { + "type": "integer", + "description": "Gets or sets the Dolby Vision profile.", + "format": "int32", + "nullable": true + }, + "DvLevel": { + "type": "integer", + "description": "Gets or sets the Dolby Vision level.", + "format": "int32", + "nullable": true + }, + "RpuPresentFlag": { + "type": "integer", + "description": "Gets or sets the Dolby Vision rpu present flag.", + "format": "int32", + "nullable": true + }, + "ElPresentFlag": { + "type": "integer", + "description": "Gets or sets the Dolby Vision el present flag.", + "format": "int32", + "nullable": true + }, + "BlPresentFlag": { + "type": "integer", + "description": "Gets or sets the Dolby Vision bl present flag.", + "format": "int32", + "nullable": true + }, + "DvBlSignalCompatibilityId": { + "type": "integer", + "description": "Gets or sets the Dolby Vision bl signal compatibility id.", + "format": "int32", + "nullable": true + }, + "Rotation": { + "type": "integer", + "description": "Gets or sets the Rotation in degrees.", + "format": "int32", + "nullable": true + }, + "Comment": { + "type": "string", + "description": "Gets or sets the comment.", + "nullable": true + }, + "TimeBase": { + "type": "string", + "description": "Gets or sets the time base.", + "nullable": true + }, + "CodecTimeBase": { + "type": "string", + "description": "Gets or sets the codec time base.", + "nullable": true + }, + "Title": { + "type": "string", + "description": "Gets or sets the title.", + "nullable": true + }, + "Hdr10PlusPresentFlag": { + "type": "boolean", + "nullable": true + }, + "VideoRange": { + "enum": [ + "Unknown", + "SDR", + "HDR" + ], + "allOf": [ + { + "$ref": "#/components/schemas/VideoRange" + } + ], + "description": "Gets the video range.", + "default": "Unknown", + "readOnly": true + }, + "VideoRangeType": { + "enum": [ + "Unknown", + "SDR", + "HDR10", + "HLG", + "DOVI", + "DOVIWithHDR10", + "DOVIWithHLG", + "DOVIWithSDR", + "DOVIWithEL", + "DOVIWithHDR10Plus", + "DOVIWithELHDR10Plus", + "DOVIInvalid", + "HDR10Plus" + ], + "allOf": [ + { + "$ref": "#/components/schemas/VideoRangeType" + } + ], + "description": "Gets the video range type.", + "default": "Unknown", + "readOnly": true + }, + "VideoDoViTitle": { + "type": "string", + "description": "Gets the video dovi title.", + "nullable": true, + "readOnly": true + }, + "AudioSpatialFormat": { + "enum": [ + "None", + "DolbyAtmos", + "DTSX" + ], + "allOf": [ + { + "$ref": "#/components/schemas/AudioSpatialFormat" + } + ], + "description": "Gets the audio spatial format.", + "default": "None", + "readOnly": true + }, + "LocalizedUndefined": { + "type": "string", + "nullable": true + }, + "LocalizedDefault": { + "type": "string", + "nullable": true + }, + "LocalizedForced": { + "type": "string", + "nullable": true + }, + "LocalizedExternal": { + "type": "string", + "nullable": true + }, + "LocalizedHearingImpaired": { + "type": "string", + "nullable": true + }, + "DisplayTitle": { + "type": "string", + "nullable": true, + "readOnly": true + }, + "NalLengthSize": { + "type": "string", + "nullable": true + }, + "IsInterlaced": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is interlaced." + }, + "IsAVC": { + "type": "boolean", + "nullable": true + }, + "ChannelLayout": { + "type": "string", + "description": "Gets or sets the channel layout.", + "nullable": true + }, + "BitRate": { + "type": "integer", + "description": "Gets or sets the bit rate.", + "format": "int32", + "nullable": true + }, + "BitDepth": { + "type": "integer", + "description": "Gets or sets the bit depth.", + "format": "int32", + "nullable": true + }, + "RefFrames": { + "type": "integer", + "description": "Gets or sets the reference frames.", + "format": "int32", + "nullable": true + }, + "PacketLength": { + "type": "integer", + "description": "Gets or sets the length of the packet.", + "format": "int32", + "nullable": true + }, + "Channels": { + "type": "integer", + "description": "Gets or sets the channels.", + "format": "int32", + "nullable": true + }, + "SampleRate": { + "type": "integer", + "description": "Gets or sets the sample rate.", + "format": "int32", + "nullable": true + }, + "IsDefault": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is default." + }, + "IsForced": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is forced." + }, + "IsHearingImpaired": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is for the hearing impaired." + }, + "Height": { + "type": "integer", + "description": "Gets or sets the height.", + "format": "int32", + "nullable": true + }, + "Width": { + "type": "integer", + "description": "Gets or sets the width.", + "format": "int32", + "nullable": true + }, + "AverageFrameRate": { + "type": "number", + "description": "Gets or sets the average frame rate.", + "format": "float", + "nullable": true + }, + "RealFrameRate": { + "type": "number", + "description": "Gets or sets the real frame rate.", + "format": "float", + "nullable": true + }, + "ReferenceFrameRate": { + "type": "number", + "description": "Gets the framerate used as reference.\r\nPrefer AverageFrameRate, if that is null or an unrealistic value\r\nthen fallback to RealFrameRate.", + "format": "float", + "nullable": true, + "readOnly": true + }, + "Profile": { + "type": "string", + "description": "Gets or sets the profile.", + "nullable": true + }, + "Type": { + "enum": [ + "Audio", + "Video", + "Subtitle", + "EmbeddedImage", + "Data", + "Lyric" + ], + "allOf": [ + { + "$ref": "#/components/schemas/MediaStreamType" + } + ], + "description": "Gets or sets the type." + }, + "AspectRatio": { + "type": "string", + "description": "Gets or sets the aspect ratio.", + "nullable": true + }, + "Index": { + "type": "integer", + "description": "Gets or sets the index.", + "format": "int32" + }, + "Score": { + "type": "integer", + "description": "Gets or sets the score.", + "format": "int32", + "nullable": true + }, + "IsExternal": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is external." + }, + "DeliveryMethod": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ], + "description": "Gets or sets the method.", + "nullable": true + }, + "DeliveryUrl": { + "type": "string", + "description": "Gets or sets the delivery URL.", + "nullable": true + }, + "IsExternalUrl": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is external URL.", + "nullable": true + }, + "IsTextSubtitleStream": { + "type": "boolean", + "readOnly": true + }, + "SupportsExternalStream": { + "type": "boolean", + "description": "Gets or sets a value indicating whether [supports external stream]." + }, + "Path": { + "type": "string", + "description": "Gets or sets the filename.", + "nullable": true + }, + "PixelFormat": { + "type": "string", + "description": "Gets or sets the pixel format.", + "nullable": true + }, + "Level": { + "type": "number", + "description": "Gets or sets the level.", + "format": "double", + "nullable": true + }, + "IsAnamorphic": { + "type": "boolean", + "description": "Gets or sets whether this instance is anamorphic.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class MediaStream." + }, + "MediaStreamProtocol": { + "enum": [ + "http", + "hls" + ], + "type": "string", + "description": "Media streaming protocol.\r\nLowercase for backwards compatibility." + }, + "MediaStreamType": { + "enum": [ + "Audio", + "Video", + "Subtitle", + "EmbeddedImage", + "Data", + "Lyric" + ], + "type": "string", + "description": "Enum MediaStreamType." + }, + "MediaType": { + "enum": [ + "Unknown", + "Video", + "Audio", + "Photo", + "Book" + ], + "type": "string", + "description": "Media types." + }, + "MediaUpdateInfoDto": { + "type": "object", + "properties": { + "Updates": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaUpdateInfoPathDto" + }, + "description": "Gets or sets the list of updates." + } + }, + "additionalProperties": false, + "description": "Media Update Info Dto." + }, + "MediaUpdateInfoPathDto": { + "type": "object", + "properties": { + "Path": { + "type": "string", + "description": "Gets or sets media path.", + "nullable": true + }, + "UpdateType": { + "type": "string", + "description": "Gets or sets media update type.\r\nCreated, Modified, Deleted.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "The media update info path." + }, + "MediaUrl": { + "type": "object", + "properties": { + "Url": { + "type": "string", + "nullable": true + }, + "Name": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "MessageCommand": { + "required": [ + "Text" + ], + "type": "object", + "properties": { + "Header": { + "type": "string", + "nullable": true + }, + "Text": { + "type": "string" + }, + "TimeoutMs": { + "type": "integer", + "format": "int64", + "nullable": true + } + }, + "additionalProperties": false + }, + "MetadataConfiguration": { + "type": "object", + "properties": { + "UseFileCreationTimeForDateAdded": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "MetadataEditorInfo": { + "type": "object", + "properties": { + "ParentalRatingOptions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ParentalRating" + }, + "description": "Gets or sets the parental rating options." + }, + "Countries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CountryInfo" + }, + "description": "Gets or sets the countries." + }, + "Cultures": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CultureDto" + }, + "description": "Gets or sets the cultures." + }, + "ExternalIdInfos": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ExternalIdInfo" + }, + "description": "Gets or sets the external id infos." + }, + "ContentType": { + "enum": [ + "unknown", + "movies", + "tvshows", + "music", + "musicvideos", + "trailers", + "homevideos", + "boxsets", + "books", + "photos", + "livetv", + "playlists", + "folders" + ], + "allOf": [ + { + "$ref": "#/components/schemas/CollectionType" + } + ], + "description": "Gets or sets the content type.", + "nullable": true + }, + "ContentTypeOptions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameValuePair" + }, + "description": "Gets or sets the content type options." + } + }, + "additionalProperties": false, + "description": "A class representing metadata editor information." + }, + "MetadataField": { + "enum": [ + "Cast", + "Genres", + "ProductionLocations", + "Studios", + "Tags", + "Name", + "Overview", + "Runtime", + "OfficialRating" + ], + "type": "string", + "description": "Enum MetadataFields." + }, + "MetadataOptions": { + "type": "object", + "properties": { + "ItemType": { + "type": "string", + "nullable": true + }, + "DisabledMetadataSavers": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "LocalMetadataReaderOrder": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "DisabledMetadataFetchers": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "MetadataFetcherOrder": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "DisabledImageFetchers": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "ImageFetcherOrder": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class MetadataOptions." + }, + "MetadataRefreshMode": { + "enum": [ + "None", + "ValidationOnly", + "Default", + "FullRefresh" + ], + "type": "string" + }, + "MovePlaylistItemRequestDto": { + "type": "object", + "properties": { + "PlaylistItemId": { + "type": "string", + "description": "Gets or sets the playlist identifier of the item.", + "format": "uuid" + }, + "NewIndex": { + "type": "integer", + "description": "Gets or sets the new position.", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "Class MovePlaylistItemRequestDto." + }, + "MovieInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "OriginalTitle": { + "type": "string", + "description": "Gets or sets the original title.", + "nullable": true + }, + "Path": { + "type": "string", + "description": "Gets or sets the path.", + "nullable": true + }, + "MetadataLanguage": { + "type": "string", + "description": "Gets or sets the metadata language.", + "nullable": true + }, + "MetadataCountryCode": { + "type": "string", + "description": "Gets or sets the metadata country code.", + "nullable": true + }, + "ProviderIds": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Gets or sets the provider ids.", + "nullable": true + }, + "Year": { + "type": "integer", + "description": "Gets or sets the year.", + "format": "int32", + "nullable": true + }, + "IndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "ParentIndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "PremiereDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "IsAutomated": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "MovieInfoRemoteSearchQuery": { + "type": "object", + "properties": { + "SearchInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/MovieInfo" + } + ], + "nullable": true + }, + "ItemId": { + "type": "string", + "format": "uuid" + }, + "SearchProviderName": { + "type": "string", + "description": "Gets or sets the provider name to search within if set.", + "nullable": true + }, + "IncludeDisabledProviders": { + "type": "boolean", + "description": "Gets or sets a value indicating whether disabled providers should be included." + } + }, + "additionalProperties": false + }, + "MusicVideoInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "OriginalTitle": { + "type": "string", + "description": "Gets or sets the original title.", + "nullable": true + }, + "Path": { + "type": "string", + "description": "Gets or sets the path.", + "nullable": true + }, + "MetadataLanguage": { + "type": "string", + "description": "Gets or sets the metadata language.", + "nullable": true + }, + "MetadataCountryCode": { + "type": "string", + "description": "Gets or sets the metadata country code.", + "nullable": true + }, + "ProviderIds": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Gets or sets the provider ids.", + "nullable": true + }, + "Year": { + "type": "integer", + "description": "Gets or sets the year.", + "format": "int32", + "nullable": true + }, + "IndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "ParentIndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "PremiereDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "IsAutomated": { + "type": "boolean" + }, + "Artists": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "MusicVideoInfoRemoteSearchQuery": { + "type": "object", + "properties": { + "SearchInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/MusicVideoInfo" + } + ], + "nullable": true + }, + "ItemId": { + "type": "string", + "format": "uuid" + }, + "SearchProviderName": { + "type": "string", + "description": "Gets or sets the provider name to search within if set.", + "nullable": true + }, + "IncludeDisabledProviders": { + "type": "boolean", + "description": "Gets or sets a value indicating whether disabled providers should be included." + } + }, + "additionalProperties": false + }, + "NameGuidPair": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "nullable": true + }, + "Id": { + "type": "string", + "format": "uuid" + } + }, + "additionalProperties": false + }, + "NameIdPair": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "Id": { + "type": "string", + "description": "Gets or sets the identifier.", + "nullable": true + } + }, + "additionalProperties": false + }, + "NameValuePair": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "Value": { + "type": "string", + "description": "Gets or sets the value.", + "nullable": true + } + }, + "additionalProperties": false + }, + "NetworkConfiguration": { + "type": "object", + "properties": { + "BaseUrl": { + "type": "string", + "description": "Gets or sets a value used to specify the URL prefix that your Jellyfin instance can be accessed at." + }, + "EnableHttps": { + "type": "boolean", + "description": "Gets or sets a value indicating whether to use HTTPS." + }, + "RequireHttps": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the server should force connections over HTTPS." + }, + "CertificatePath": { + "type": "string", + "description": "Gets or sets the filesystem path of an X.509 certificate to use for SSL." + }, + "CertificatePassword": { + "type": "string", + "description": "Gets or sets the password required to access the X.509 certificate data in the file specified by MediaBrowser.Common.Net.NetworkConfiguration.CertificatePath." + }, + "InternalHttpPort": { + "type": "integer", + "description": "Gets or sets the internal HTTP server port.", + "format": "int32" + }, + "InternalHttpsPort": { + "type": "integer", + "description": "Gets or sets the internal HTTPS server port.", + "format": "int32" + }, + "PublicHttpPort": { + "type": "integer", + "description": "Gets or sets the public HTTP port.", + "format": "int32" + }, + "PublicHttpsPort": { + "type": "integer", + "description": "Gets or sets the public HTTPS port.", + "format": "int32" + }, + "AutoDiscovery": { + "type": "boolean", + "description": "Gets or sets a value indicating whether Autodiscovery is enabled." + }, + "EnableUPnP": { + "type": "boolean", + "description": "Gets or sets a value indicating whether to enable automatic port forwarding.", + "deprecated": true + }, + "EnableIPv4": { + "type": "boolean", + "description": "Gets or sets a value indicating whether IPv6 is enabled." + }, + "EnableIPv6": { + "type": "boolean", + "description": "Gets or sets a value indicating whether IPv6 is enabled." + }, + "EnableRemoteAccess": { + "type": "boolean", + "description": "Gets or sets a value indicating whether access from outside of the LAN is permitted." + }, + "LocalNetworkSubnets": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the subnets that are deemed to make up the LAN." + }, + "LocalNetworkAddresses": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the interface addresses which Jellyfin will bind to. If empty, all interfaces will be used." + }, + "KnownProxies": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the known proxies." + }, + "IgnoreVirtualInterfaces": { + "type": "boolean", + "description": "Gets or sets a value indicating whether address names that match MediaBrowser.Common.Net.NetworkConfiguration.VirtualInterfaceNames should be ignored for the purposes of binding." + }, + "VirtualInterfaceNames": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets a value indicating the interface name prefixes that should be ignored. The list can be comma separated and values are case-insensitive. ." + }, + "EnablePublishedServerUriByRequest": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the published server uri is based on information in HTTP requests." + }, + "PublishedServerUriBySubnet": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the PublishedServerUriBySubnet\r\nGets or sets PublishedServerUri to advertise for specific subnets." + }, + "RemoteIPFilter": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the filter for remote IP connectivity. Used in conjunction with ." + }, + "IsRemoteIPFilterBlacklist": { + "type": "boolean", + "description": "Gets or sets a value indicating whether contains a blacklist or a whitelist. Default is a whitelist." + } + }, + "additionalProperties": false, + "description": "Defines the MediaBrowser.Common.Net.NetworkConfiguration." + }, + "NewGroupRequestDto": { + "type": "object", + "properties": { + "GroupName": { + "type": "string", + "description": "Gets or sets the group name." + } + }, + "additionalProperties": false, + "description": "Class NewGroupRequestDto." + }, + "NextItemRequestDto": { + "type": "object", + "properties": { + "PlaylistItemId": { + "type": "string", + "description": "Gets or sets the playing item identifier.", + "format": "uuid" + } + }, + "additionalProperties": false, + "description": "Class NextItemRequestDto." + }, + "OpenLiveStreamDto": { + "type": "object", + "properties": { + "OpenToken": { + "type": "string", + "description": "Gets or sets the open token.", + "nullable": true + }, + "UserId": { + "type": "string", + "description": "Gets or sets the user id.", + "format": "uuid", + "nullable": true + }, + "PlaySessionId": { + "type": "string", + "description": "Gets or sets the play session id.", + "nullable": true + }, + "MaxStreamingBitrate": { + "type": "integer", + "description": "Gets or sets the max streaming bitrate.", + "format": "int32", + "nullable": true + }, + "StartTimeTicks": { + "type": "integer", + "description": "Gets or sets the start time in ticks.", + "format": "int64", + "nullable": true + }, + "AudioStreamIndex": { + "type": "integer", + "description": "Gets or sets the audio stream index.", + "format": "int32", + "nullable": true + }, + "SubtitleStreamIndex": { + "type": "integer", + "description": "Gets or sets the subtitle stream index.", + "format": "int32", + "nullable": true + }, + "MaxAudioChannels": { + "type": "integer", + "description": "Gets or sets the max audio channels.", + "format": "int32", + "nullable": true + }, + "ItemId": { + "type": "string", + "description": "Gets or sets the item id.", + "format": "uuid", + "nullable": true + }, + "EnableDirectPlay": { + "type": "boolean", + "description": "Gets or sets a value indicating whether to enable direct play.", + "nullable": true + }, + "EnableDirectStream": { + "type": "boolean", + "description": "Gets or sets a value indicating whether to enable direct stream.", + "nullable": true + }, + "AlwaysBurnInSubtitleWhenTranscoding": { + "type": "boolean", + "description": "Gets or sets a value indicating whether always burn in subtitles when transcoding.", + "nullable": true + }, + "DeviceProfile": { + "allOf": [ + { + "$ref": "#/components/schemas/DeviceProfile" + } + ], + "description": "A MediaBrowser.Model.Dlna.DeviceProfile represents a set of metadata which determines which content a certain device is able to play.\r\n
\r\nSpecifically, it defines the supported containers and\r\ncodecs (video and/or audio, including codec profiles and levels)\r\nthe device is able to direct play (without transcoding or remuxing),\r\nas well as which containers/codecs to transcode to in case it isn't.", + "nullable": true + }, + "DirectPlayProtocols": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaProtocol" + }, + "description": "Gets or sets the device play protocols." + } + }, + "additionalProperties": false, + "description": "Open live stream dto." + }, + "OutboundKeepAliveMessage": { + "type": "object", + "properties": { + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "KeepAlive", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Keep alive websocket messages." + }, + "OutboundWebSocketMessage": { + "type": "object", + "oneOf": [ + { + "$ref": "#/components/schemas/ActivityLogEntryMessage" + }, + { + "$ref": "#/components/schemas/ForceKeepAliveMessage" + }, + { + "$ref": "#/components/schemas/GeneralCommandMessage" + }, + { + "$ref": "#/components/schemas/LibraryChangedMessage" + }, + { + "$ref": "#/components/schemas/OutboundKeepAliveMessage" + }, + { + "$ref": "#/components/schemas/PlayMessage" + }, + { + "$ref": "#/components/schemas/PlaystateMessage" + }, + { + "$ref": "#/components/schemas/PluginInstallationCancelledMessage" + }, + { + "$ref": "#/components/schemas/PluginInstallationCompletedMessage" + }, + { + "$ref": "#/components/schemas/PluginInstallationFailedMessage" + }, + { + "$ref": "#/components/schemas/PluginInstallingMessage" + }, + { + "$ref": "#/components/schemas/PluginUninstalledMessage" + }, + { + "$ref": "#/components/schemas/RefreshProgressMessage" + }, + { + "$ref": "#/components/schemas/RestartRequiredMessage" + }, + { + "$ref": "#/components/schemas/ScheduledTaskEndedMessage" + }, + { + "$ref": "#/components/schemas/ScheduledTasksInfoMessage" + }, + { + "$ref": "#/components/schemas/SeriesTimerCancelledMessage" + }, + { + "$ref": "#/components/schemas/SeriesTimerCreatedMessage" + }, + { + "$ref": "#/components/schemas/ServerRestartingMessage" + }, + { + "$ref": "#/components/schemas/ServerShuttingDownMessage" + }, + { + "$ref": "#/components/schemas/SessionsMessage" + }, + { + "$ref": "#/components/schemas/SyncPlayCommandMessage" + }, + { + "$ref": "#/components/schemas/TimerCancelledMessage" + }, + { + "$ref": "#/components/schemas/TimerCreatedMessage" + }, + { + "$ref": "#/components/schemas/UserDataChangedMessage" + }, + { + "$ref": "#/components/schemas/UserDeletedMessage" + }, + { + "$ref": "#/components/schemas/UserUpdatedMessage" + }, + { + "$ref": "#/components/schemas/SyncPlayGroupUpdateMessage" + } + ], + "description": "Represents the list of possible outbound websocket types", + "discriminator": { + "propertyName": "MessageType", + "mapping": { + "ActivityLogEntry": "#/components/schemas/ActivityLogEntryMessage", + "ForceKeepAlive": "#/components/schemas/ForceKeepAliveMessage", + "GeneralCommand": "#/components/schemas/GeneralCommandMessage", + "LibraryChanged": "#/components/schemas/LibraryChangedMessage", + "KeepAlive": "#/components/schemas/OutboundKeepAliveMessage", + "Play": "#/components/schemas/PlayMessage", + "Playstate": "#/components/schemas/PlaystateMessage", + "PackageInstallationCancelled": "#/components/schemas/PluginInstallationCancelledMessage", + "PackageInstallationCompleted": "#/components/schemas/PluginInstallationCompletedMessage", + "PackageInstallationFailed": "#/components/schemas/PluginInstallationFailedMessage", + "PackageInstalling": "#/components/schemas/PluginInstallingMessage", + "PackageUninstalled": "#/components/schemas/PluginUninstalledMessage", + "RefreshProgress": "#/components/schemas/RefreshProgressMessage", + "RestartRequired": "#/components/schemas/RestartRequiredMessage", + "ScheduledTaskEnded": "#/components/schemas/ScheduledTaskEndedMessage", + "ScheduledTasksInfo": "#/components/schemas/ScheduledTasksInfoMessage", + "SeriesTimerCancelled": "#/components/schemas/SeriesTimerCancelledMessage", + "SeriesTimerCreated": "#/components/schemas/SeriesTimerCreatedMessage", + "ServerRestarting": "#/components/schemas/ServerRestartingMessage", + "ServerShuttingDown": "#/components/schemas/ServerShuttingDownMessage", + "Sessions": "#/components/schemas/SessionsMessage", + "SyncPlayCommand": "#/components/schemas/SyncPlayCommandMessage", + "TimerCancelled": "#/components/schemas/TimerCancelledMessage", + "TimerCreated": "#/components/schemas/TimerCreatedMessage", + "UserDataChanged": "#/components/schemas/UserDataChangedMessage", + "UserDeleted": "#/components/schemas/UserDeletedMessage", + "UserUpdated": "#/components/schemas/UserUpdatedMessage", + "SyncPlayGroupUpdate": "#/components/schemas/SyncPlayGroupUpdateMessage" + } + } + }, + "PackageInfo": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Gets or sets the name." + }, + "description": { + "type": "string", + "description": "Gets or sets a long description of the plugin containing features or helpful explanations." + }, + "overview": { + "type": "string", + "description": "Gets or sets a short overview of what the plugin does." + }, + "owner": { + "type": "string", + "description": "Gets or sets the owner." + }, + "category": { + "type": "string", + "description": "Gets or sets the category." + }, + "guid": { + "type": "string", + "description": "Gets or sets the guid of the assembly associated with this plugin.\r\nThis is used to identify the proper item for automatic updates.", + "format": "uuid" + }, + "versions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/VersionInfo" + }, + "description": "Gets or sets the versions." + }, + "imageUrl": { + "type": "string", + "description": "Gets or sets the image url for the package.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class PackageInfo." + }, + "ParentalRating": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name." + }, + "Value": { + "type": "integer", + "description": "Gets or sets the value.", + "format": "int32", + "nullable": true + }, + "RatingScore": { + "allOf": [ + { + "$ref": "#/components/schemas/ParentalRatingScore" + } + ], + "description": "Gets or sets the rating score.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class ParentalRating." + }, + "ParentalRatingScore": { + "type": "object", + "properties": { + "score": { + "type": "integer", + "description": "Gets or sets the score.", + "format": "int32" + }, + "subScore": { + "type": "integer", + "description": "Gets or sets the sub score.", + "format": "int32", + "nullable": true + } + }, + "additionalProperties": false, + "description": "A class representing an parental rating score." + }, + "PathSubstitution": { + "type": "object", + "properties": { + "From": { + "type": "string", + "description": "Gets or sets the value to substitute." + }, + "To": { + "type": "string", + "description": "Gets or sets the value to substitution with." + } + }, + "additionalProperties": false, + "description": "Defines the MediaBrowser.Model.Configuration.PathSubstitution." + }, + "PersonKind": { + "enum": [ + "Unknown", + "Actor", + "Director", + "Composer", + "Writer", + "GuestStar", + "Producer", + "Conductor", + "Lyricist", + "Arranger", + "Engineer", + "Mixer", + "Remixer", + "Creator", + "Artist", + "AlbumArtist", + "Author", + "Illustrator", + "Penciller", + "Inker", + "Colorist", + "Letterer", + "CoverArtist", + "Editor", + "Translator" + ], + "type": "string", + "description": "The person kind." + }, + "PersonLookupInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "OriginalTitle": { + "type": "string", + "description": "Gets or sets the original title.", + "nullable": true + }, + "Path": { + "type": "string", + "description": "Gets or sets the path.", + "nullable": true + }, + "MetadataLanguage": { + "type": "string", + "description": "Gets or sets the metadata language.", + "nullable": true + }, + "MetadataCountryCode": { + "type": "string", + "description": "Gets or sets the metadata country code.", + "nullable": true + }, + "ProviderIds": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Gets or sets the provider ids.", + "nullable": true + }, + "Year": { + "type": "integer", + "description": "Gets or sets the year.", + "format": "int32", + "nullable": true + }, + "IndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "ParentIndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "PremiereDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "IsAutomated": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "PersonLookupInfoRemoteSearchQuery": { + "type": "object", + "properties": { + "SearchInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/PersonLookupInfo" + } + ], + "nullable": true + }, + "ItemId": { + "type": "string", + "format": "uuid" + }, + "SearchProviderName": { + "type": "string", + "description": "Gets or sets the provider name to search within if set.", + "nullable": true + }, + "IncludeDisabledProviders": { + "type": "boolean", + "description": "Gets or sets a value indicating whether disabled providers should be included." + } + }, + "additionalProperties": false + }, + "PingRequestDto": { + "type": "object", + "properties": { + "Ping": { + "type": "integer", + "description": "Gets or sets the ping time.", + "format": "int64" + } + }, + "additionalProperties": false, + "description": "Class PingRequestDto." + }, + "PinRedeemResult": { + "type": "object", + "properties": { + "Success": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this MediaBrowser.Model.Users.PinRedeemResult is success." + }, + "UsersReset": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the users reset." + } + }, + "additionalProperties": false + }, + "PlayAccess": { + "enum": [ + "Full", + "None" + ], + "type": "string" + }, + "PlaybackErrorCode": { + "enum": [ + "NotAllowed", + "NoCompatibleStream", + "RateLimitExceeded" + ], + "type": "string" + }, + "PlaybackInfoDto": { + "type": "object", + "properties": { + "UserId": { + "type": "string", + "description": "Gets or sets the playback userId.", + "format": "uuid", + "nullable": true + }, + "MaxStreamingBitrate": { + "type": "integer", + "description": "Gets or sets the max streaming bitrate.", + "format": "int32", + "nullable": true + }, + "StartTimeTicks": { + "type": "integer", + "description": "Gets or sets the start time in ticks.", + "format": "int64", + "nullable": true + }, + "AudioStreamIndex": { + "type": "integer", + "description": "Gets or sets the audio stream index.", + "format": "int32", + "nullable": true + }, + "SubtitleStreamIndex": { + "type": "integer", + "description": "Gets or sets the subtitle stream index.", + "format": "int32", + "nullable": true + }, + "MaxAudioChannels": { + "type": "integer", + "description": "Gets or sets the max audio channels.", + "format": "int32", + "nullable": true + }, + "MediaSourceId": { + "type": "string", + "description": "Gets or sets the media source id.", + "nullable": true + }, + "LiveStreamId": { + "type": "string", + "description": "Gets or sets the live stream id.", + "nullable": true + }, + "DeviceProfile": { + "allOf": [ + { + "$ref": "#/components/schemas/DeviceProfile" + } + ], + "description": "A MediaBrowser.Model.Dlna.DeviceProfile represents a set of metadata which determines which content a certain device is able to play.\r\n
\r\nSpecifically, it defines the supported containers and\r\ncodecs (video and/or audio, including codec profiles and levels)\r\nthe device is able to direct play (without transcoding or remuxing),\r\nas well as which containers/codecs to transcode to in case it isn't.", + "nullable": true + }, + "EnableDirectPlay": { + "type": "boolean", + "description": "Gets or sets a value indicating whether to enable direct play.", + "nullable": true + }, + "EnableDirectStream": { + "type": "boolean", + "description": "Gets or sets a value indicating whether to enable direct stream.", + "nullable": true + }, + "EnableTranscoding": { + "type": "boolean", + "description": "Gets or sets a value indicating whether to enable transcoding.", + "nullable": true + }, + "AllowVideoStreamCopy": { + "type": "boolean", + "description": "Gets or sets a value indicating whether to enable video stream copy.", + "nullable": true + }, + "AllowAudioStreamCopy": { + "type": "boolean", + "description": "Gets or sets a value indicating whether to allow audio stream copy.", + "nullable": true + }, + "AutoOpenLiveStream": { + "type": "boolean", + "description": "Gets or sets a value indicating whether to auto open the live stream.", + "nullable": true + }, + "AlwaysBurnInSubtitleWhenTranscoding": { + "type": "boolean", + "description": "Gets or sets a value indicating whether always burn in subtitles when transcoding.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Playback info dto." + }, + "PlaybackInfoResponse": { + "type": "object", + "properties": { + "MediaSources": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaSourceInfo" + }, + "description": "Gets or sets the media sources." + }, + "PlaySessionId": { + "type": "string", + "description": "Gets or sets the play session identifier.", + "nullable": true + }, + "ErrorCode": { + "enum": [ + "NotAllowed", + "NoCompatibleStream", + "RateLimitExceeded" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PlaybackErrorCode" + } + ], + "description": "Gets or sets the error code.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class PlaybackInfoResponse." + }, + "PlaybackOrder": { + "enum": [ + "Default", + "Shuffle" + ], + "type": "string", + "description": "Enum PlaybackOrder." + }, + "PlaybackProgressInfo": { + "type": "object", + "properties": { + "CanSeek": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance can seek." + }, + "Item": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseItemDto" + } + ], + "description": "Gets or sets the item.", + "nullable": true + }, + "ItemId": { + "type": "string", + "description": "Gets or sets the item identifier.", + "format": "uuid" + }, + "SessionId": { + "type": "string", + "description": "Gets or sets the session id.", + "nullable": true + }, + "MediaSourceId": { + "type": "string", + "description": "Gets or sets the media version identifier.", + "nullable": true + }, + "AudioStreamIndex": { + "type": "integer", + "description": "Gets or sets the index of the audio stream.", + "format": "int32", + "nullable": true + }, + "SubtitleStreamIndex": { + "type": "integer", + "description": "Gets or sets the index of the subtitle stream.", + "format": "int32", + "nullable": true + }, + "IsPaused": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is paused." + }, + "IsMuted": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is muted." + }, + "PositionTicks": { + "type": "integer", + "description": "Gets or sets the position ticks.", + "format": "int64", + "nullable": true + }, + "PlaybackStartTimeTicks": { + "type": "integer", + "format": "int64", + "nullable": true + }, + "VolumeLevel": { + "type": "integer", + "description": "Gets or sets the volume level.", + "format": "int32", + "nullable": true + }, + "Brightness": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "AspectRatio": { + "type": "string", + "nullable": true + }, + "PlayMethod": { + "enum": [ + "Transcode", + "DirectStream", + "DirectPlay" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PlayMethod" + } + ], + "description": "Gets or sets the play method." + }, + "LiveStreamId": { + "type": "string", + "description": "Gets or sets the live stream identifier.", + "nullable": true + }, + "PlaySessionId": { + "type": "string", + "description": "Gets or sets the play session identifier.", + "nullable": true + }, + "RepeatMode": { + "enum": [ + "RepeatNone", + "RepeatAll", + "RepeatOne" + ], + "allOf": [ + { + "$ref": "#/components/schemas/RepeatMode" + } + ], + "description": "Gets or sets the repeat mode." + }, + "PlaybackOrder": { + "enum": [ + "Default", + "Shuffle" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PlaybackOrder" + } + ], + "description": "Gets or sets the playback order." + }, + "NowPlayingQueue": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QueueItem" + }, + "nullable": true + }, + "PlaylistItemId": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class PlaybackProgressInfo." + }, + "PlaybackRequestType": { + "enum": [ + "Play", + "SetPlaylistItem", + "RemoveFromPlaylist", + "MovePlaylistItem", + "Queue", + "Unpause", + "Pause", + "Stop", + "Seek", + "Buffer", + "Ready", + "NextItem", + "PreviousItem", + "SetRepeatMode", + "SetShuffleMode", + "Ping", + "IgnoreWait" + ], + "type": "string", + "description": "Enum PlaybackRequestType." + }, + "PlaybackStartInfo": { + "type": "object", + "properties": { + "CanSeek": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance can seek." + }, + "Item": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseItemDto" + } + ], + "description": "Gets or sets the item.", + "nullable": true + }, + "ItemId": { + "type": "string", + "description": "Gets or sets the item identifier.", + "format": "uuid" + }, + "SessionId": { + "type": "string", + "description": "Gets or sets the session id.", + "nullable": true + }, + "MediaSourceId": { + "type": "string", + "description": "Gets or sets the media version identifier.", + "nullable": true + }, + "AudioStreamIndex": { + "type": "integer", + "description": "Gets or sets the index of the audio stream.", + "format": "int32", + "nullable": true + }, + "SubtitleStreamIndex": { + "type": "integer", + "description": "Gets or sets the index of the subtitle stream.", + "format": "int32", + "nullable": true + }, + "IsPaused": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is paused." + }, + "IsMuted": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is muted." + }, + "PositionTicks": { + "type": "integer", + "description": "Gets or sets the position ticks.", + "format": "int64", + "nullable": true + }, + "PlaybackStartTimeTicks": { + "type": "integer", + "format": "int64", + "nullable": true + }, + "VolumeLevel": { + "type": "integer", + "description": "Gets or sets the volume level.", + "format": "int32", + "nullable": true + }, + "Brightness": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "AspectRatio": { + "type": "string", + "nullable": true + }, + "PlayMethod": { + "enum": [ + "Transcode", + "DirectStream", + "DirectPlay" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PlayMethod" + } + ], + "description": "Gets or sets the play method." + }, + "LiveStreamId": { + "type": "string", + "description": "Gets or sets the live stream identifier.", + "nullable": true + }, + "PlaySessionId": { + "type": "string", + "description": "Gets or sets the play session identifier.", + "nullable": true + }, + "RepeatMode": { + "enum": [ + "RepeatNone", + "RepeatAll", + "RepeatOne" + ], + "allOf": [ + { + "$ref": "#/components/schemas/RepeatMode" + } + ], + "description": "Gets or sets the repeat mode." + }, + "PlaybackOrder": { + "enum": [ + "Default", + "Shuffle" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PlaybackOrder" + } + ], + "description": "Gets or sets the playback order." + }, + "NowPlayingQueue": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QueueItem" + }, + "nullable": true + }, + "PlaylistItemId": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class PlaybackStartInfo." + }, + "PlaybackStopInfo": { + "type": "object", + "properties": { + "Item": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseItemDto" + } + ], + "description": "Gets or sets the item.", + "nullable": true + }, + "ItemId": { + "type": "string", + "description": "Gets or sets the item identifier.", + "format": "uuid" + }, + "SessionId": { + "type": "string", + "description": "Gets or sets the session id.", + "nullable": true + }, + "MediaSourceId": { + "type": "string", + "description": "Gets or sets the media version identifier.", + "nullable": true + }, + "PositionTicks": { + "type": "integer", + "description": "Gets or sets the position ticks.", + "format": "int64", + "nullable": true + }, + "LiveStreamId": { + "type": "string", + "description": "Gets or sets the live stream identifier.", + "nullable": true + }, + "PlaySessionId": { + "type": "string", + "description": "Gets or sets the play session identifier.", + "nullable": true + }, + "Failed": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this MediaBrowser.Model.Session.PlaybackStopInfo is failed." + }, + "NextMediaType": { + "type": "string", + "nullable": true + }, + "PlaylistItemId": { + "type": "string", + "nullable": true + }, + "NowPlayingQueue": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QueueItem" + }, + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class PlaybackStopInfo." + }, + "PlayCommand": { + "enum": [ + "PlayNow", + "PlayNext", + "PlayLast", + "PlayInstantMix", + "PlayShuffle" + ], + "type": "string", + "description": "Enum PlayCommand." + }, + "PlayerStateInfo": { + "type": "object", + "properties": { + "PositionTicks": { + "type": "integer", + "description": "Gets or sets the now playing position ticks.", + "format": "int64", + "nullable": true + }, + "CanSeek": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance can seek." + }, + "IsPaused": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is paused." + }, + "IsMuted": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is muted." + }, + "VolumeLevel": { + "type": "integer", + "description": "Gets or sets the volume level.", + "format": "int32", + "nullable": true + }, + "AudioStreamIndex": { + "type": "integer", + "description": "Gets or sets the index of the now playing audio stream.", + "format": "int32", + "nullable": true + }, + "SubtitleStreamIndex": { + "type": "integer", + "description": "Gets or sets the index of the now playing subtitle stream.", + "format": "int32", + "nullable": true + }, + "MediaSourceId": { + "type": "string", + "description": "Gets or sets the now playing media version identifier.", + "nullable": true + }, + "PlayMethod": { + "enum": [ + "Transcode", + "DirectStream", + "DirectPlay" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PlayMethod" + } + ], + "description": "Gets or sets the play method.", + "nullable": true + }, + "RepeatMode": { + "enum": [ + "RepeatNone", + "RepeatAll", + "RepeatOne" + ], + "allOf": [ + { + "$ref": "#/components/schemas/RepeatMode" + } + ], + "description": "Gets or sets the repeat mode." + }, + "PlaybackOrder": { + "enum": [ + "Default", + "Shuffle" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PlaybackOrder" + } + ], + "description": "Gets or sets the playback order." + }, + "LiveStreamId": { + "type": "string", + "description": "Gets or sets the now playing live stream identifier.", + "nullable": true + } + }, + "additionalProperties": false + }, + "PlaylistCreationResult": { + "type": "object", + "properties": { + "Id": { + "type": "string" + } + }, + "additionalProperties": false + }, + "PlaylistDto": { + "type": "object", + "properties": { + "OpenAccess": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the playlist is publicly readable." + }, + "Shares": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlaylistUserPermissions" + }, + "description": "Gets or sets the share permissions." + }, + "ItemIds": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Gets or sets the item ids." + } + }, + "additionalProperties": false, + "description": "DTO for playlists." + }, + "PlaylistUserPermissions": { + "type": "object", + "properties": { + "UserId": { + "type": "string", + "description": "Gets or sets the user id.", + "format": "uuid" + }, + "CanEdit": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the user has edit permissions." + } + }, + "additionalProperties": false, + "description": "Class to hold data on user permissions for playlists." + }, + "PlayMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/PlayRequest" + } + ], + "description": "Class PlayRequest.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "Play", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Play command websocket message." + }, + "PlayMethod": { + "enum": [ + "Transcode", + "DirectStream", + "DirectPlay" + ], + "type": "string" + }, + "PlayQueueUpdate": { + "type": "object", + "properties": { + "Reason": { + "enum": [ + "NewPlaylist", + "SetCurrentItem", + "RemoveItems", + "MoveItem", + "Queue", + "QueueNext", + "NextItem", + "PreviousItem", + "RepeatMode", + "ShuffleMode" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PlayQueueUpdateReason" + } + ], + "description": "Gets the request type that originated this update." + }, + "LastUpdate": { + "type": "string", + "description": "Gets the UTC time of the last change to the playing queue.", + "format": "date-time" + }, + "Playlist": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SyncPlayQueueItem" + }, + "description": "Gets the playlist." + }, + "PlayingItemIndex": { + "type": "integer", + "description": "Gets the playing item index in the playlist.", + "format": "int32" + }, + "StartPositionTicks": { + "type": "integer", + "description": "Gets the start position ticks.", + "format": "int64" + }, + "IsPlaying": { + "type": "boolean", + "description": "Gets a value indicating whether the current item is playing." + }, + "ShuffleMode": { + "enum": [ + "Sorted", + "Shuffle" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GroupShuffleMode" + } + ], + "description": "Gets the shuffle mode." + }, + "RepeatMode": { + "enum": [ + "RepeatOne", + "RepeatAll", + "RepeatNone" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GroupRepeatMode" + } + ], + "description": "Gets the repeat mode." + } + }, + "additionalProperties": false, + "description": "Class PlayQueueUpdate." + }, + "PlayQueueUpdateReason": { + "enum": [ + "NewPlaylist", + "SetCurrentItem", + "RemoveItems", + "MoveItem", + "Queue", + "QueueNext", + "NextItem", + "PreviousItem", + "RepeatMode", + "ShuffleMode" + ], + "type": "string", + "description": "Enum PlayQueueUpdateReason." + }, + "PlayRequest": { + "type": "object", + "properties": { + "ItemIds": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Gets or sets the item ids.", + "nullable": true + }, + "StartPositionTicks": { + "type": "integer", + "description": "Gets or sets the start position ticks that the first item should be played at.", + "format": "int64", + "nullable": true + }, + "PlayCommand": { + "enum": [ + "PlayNow", + "PlayNext", + "PlayLast", + "PlayInstantMix", + "PlayShuffle" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PlayCommand" + } + ], + "description": "Gets or sets the play command." + }, + "ControllingUserId": { + "type": "string", + "description": "Gets or sets the controlling user identifier.", + "format": "uuid" + }, + "SubtitleStreamIndex": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "AudioStreamIndex": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "MediaSourceId": { + "type": "string", + "nullable": true + }, + "StartIndex": { + "type": "integer", + "format": "int32", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class PlayRequest." + }, + "PlayRequestDto": { + "type": "object", + "properties": { + "PlayingQueue": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Gets or sets the playing queue." + }, + "PlayingItemPosition": { + "type": "integer", + "description": "Gets or sets the position of the playing item in the queue.", + "format": "int32" + }, + "StartPositionTicks": { + "type": "integer", + "description": "Gets or sets the start position ticks.", + "format": "int64" + } + }, + "additionalProperties": false, + "description": "Class PlayRequestDto." + }, + "PlaystateCommand": { + "enum": [ + "Stop", + "Pause", + "Unpause", + "NextTrack", + "PreviousTrack", + "Seek", + "Rewind", + "FastForward", + "PlayPause" + ], + "type": "string", + "description": "Enum PlaystateCommand." + }, + "PlaystateMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/PlaystateRequest" + } + ], + "description": "Gets or sets the data.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "Playstate", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Playstate message." + }, + "PlaystateRequest": { + "type": "object", + "properties": { + "Command": { + "enum": [ + "Stop", + "Pause", + "Unpause", + "NextTrack", + "PreviousTrack", + "Seek", + "Rewind", + "FastForward", + "PlayPause" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PlaystateCommand" + } + ], + "description": "Enum PlaystateCommand." + }, + "SeekPositionTicks": { + "type": "integer", + "format": "int64", + "nullable": true + }, + "ControllingUserId": { + "type": "string", + "description": "Gets or sets the controlling user identifier.", + "nullable": true + } + }, + "additionalProperties": false + }, + "PluginInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name." + }, + "Version": { + "type": "string", + "description": "Gets or sets the version." + }, + "ConfigurationFileName": { + "type": "string", + "description": "Gets or sets the name of the configuration file.", + "nullable": true + }, + "Description": { + "type": "string", + "description": "Gets or sets the description." + }, + "Id": { + "type": "string", + "description": "Gets or sets the unique id.", + "format": "uuid" + }, + "CanUninstall": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the plugin can be uninstalled." + }, + "HasImage": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this plugin has a valid image." + }, + "Status": { + "enum": [ + "Active", + "Restart", + "Deleted", + "Superseded", + "Superceded", + "Malfunctioned", + "NotSupported", + "Disabled" + ], + "allOf": [ + { + "$ref": "#/components/schemas/PluginStatus" + } + ], + "description": "Gets or sets a value indicating the status of the plugin." + } + }, + "additionalProperties": false, + "description": "This is a serializable stub class that is used by the api to provide information about installed plugins." + }, + "PluginInstallationCancelledMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/InstallationInfo" + } + ], + "description": "Class InstallationInfo.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "PackageInstallationCancelled", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Plugin installation cancelled message." + }, + "PluginInstallationCompletedMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/InstallationInfo" + } + ], + "description": "Class InstallationInfo.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "PackageInstallationCompleted", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Plugin installation completed message." + }, + "PluginInstallationFailedMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/InstallationInfo" + } + ], + "description": "Class InstallationInfo.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "PackageInstallationFailed", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Plugin installation failed message." + }, + "PluginInstallingMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/InstallationInfo" + } + ], + "description": "Class InstallationInfo.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "PackageInstalling", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Package installing message." + }, + "PluginStatus": { + "enum": [ + "Active", + "Restart", + "Deleted", + "Superseded", + "Superceded", + "Malfunctioned", + "NotSupported", + "Disabled" + ], + "type": "string", + "description": "Plugin load status." + }, + "PluginUninstalledMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/PluginInfo" + } + ], + "description": "This is a serializable stub class that is used by the api to provide information about installed plugins.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "PackageUninstalled", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Plugin uninstalled message." + }, + "PreviousItemRequestDto": { + "type": "object", + "properties": { + "PlaylistItemId": { + "type": "string", + "description": "Gets or sets the playing item identifier.", + "format": "uuid" + } + }, + "additionalProperties": false, + "description": "Class PreviousItemRequestDto." + }, + "ProblemDetails": { + "type": "object", + "properties": { + "type": { + "type": "string", + "nullable": true + }, + "title": { + "type": "string", + "nullable": true + }, + "status": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "detail": { + "type": "string", + "nullable": true + }, + "instance": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": { } + }, + "ProcessPriorityClass": { + "enum": [ + "Normal", + "Idle", + "High", + "RealTime", + "BelowNormal", + "AboveNormal" + ], + "type": "string" + }, + "ProfileCondition": { + "type": "object", + "properties": { + "Condition": { + "enum": [ + "Equals", + "NotEquals", + "LessThanEqual", + "GreaterThanEqual", + "EqualsAny" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ProfileConditionType" + } + ] + }, + "Property": { + "enum": [ + "AudioChannels", + "AudioBitrate", + "AudioProfile", + "Width", + "Height", + "Has64BitOffsets", + "PacketLength", + "VideoBitDepth", + "VideoBitrate", + "VideoFramerate", + "VideoLevel", + "VideoProfile", + "VideoTimestamp", + "IsAnamorphic", + "RefFrames", + "NumAudioStreams", + "NumVideoStreams", + "IsSecondaryAudio", + "VideoCodecTag", + "IsAvc", + "IsInterlaced", + "AudioSampleRate", + "AudioBitDepth", + "VideoRangeType", + "NumStreams" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ProfileConditionValue" + } + ] + }, + "Value": { + "type": "string", + "nullable": true + }, + "IsRequired": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "ProfileConditionType": { + "enum": [ + "Equals", + "NotEquals", + "LessThanEqual", + "GreaterThanEqual", + "EqualsAny" + ], + "type": "string" + }, + "ProfileConditionValue": { + "enum": [ + "AudioChannels", + "AudioBitrate", + "AudioProfile", + "Width", + "Height", + "Has64BitOffsets", + "PacketLength", + "VideoBitDepth", + "VideoBitrate", + "VideoFramerate", + "VideoLevel", + "VideoProfile", + "VideoTimestamp", + "IsAnamorphic", + "RefFrames", + "NumAudioStreams", + "NumVideoStreams", + "IsSecondaryAudio", + "VideoCodecTag", + "IsAvc", + "IsInterlaced", + "AudioSampleRate", + "AudioBitDepth", + "VideoRangeType", + "NumStreams" + ], + "type": "string" + }, + "ProgramAudio": { + "enum": [ + "Mono", + "Stereo", + "Dolby", + "DolbyDigital", + "Thx", + "Atmos" + ], + "type": "string" + }, + "PublicSystemInfo": { + "type": "object", + "properties": { + "LocalAddress": { + "type": "string", + "description": "Gets or sets the local address.", + "nullable": true + }, + "ServerName": { + "type": "string", + "description": "Gets or sets the name of the server.", + "nullable": true + }, + "Version": { + "type": "string", + "description": "Gets or sets the server version.", + "nullable": true + }, + "ProductName": { + "type": "string", + "description": "Gets or sets the product name. This is the AssemblyProduct name.", + "nullable": true + }, + "OperatingSystem": { + "type": "string", + "description": "Gets or sets the operating system.", + "nullable": true, + "deprecated": true + }, + "Id": { + "type": "string", + "description": "Gets or sets the id.", + "nullable": true + }, + "StartupWizardCompleted": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the startup wizard is completed.", + "nullable": true + } + }, + "additionalProperties": false + }, + "QueryFilters": { + "type": "object", + "properties": { + "Genres": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameGuidPair" + }, + "nullable": true + }, + "Tags": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "QueryFiltersLegacy": { + "type": "object", + "properties": { + "Genres": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "Tags": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "OfficialRatings": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "Years": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "QueueItem": { + "type": "object", + "properties": { + "Id": { + "type": "string", + "format": "uuid" + }, + "PlaylistItemId": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "QueueRequestDto": { + "type": "object", + "properties": { + "ItemIds": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Gets or sets the items to enqueue." + }, + "Mode": { + "enum": [ + "Queue", + "QueueNext" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GroupQueueMode" + } + ], + "description": "Enum GroupQueueMode." + } + }, + "additionalProperties": false, + "description": "Class QueueRequestDto." + }, + "QuickConnectDto": { + "required": [ + "Secret" + ], + "type": "object", + "properties": { + "Secret": { + "type": "string", + "description": "Gets or sets the quick connect secret." + } + }, + "additionalProperties": false, + "description": "The quick connect request body." + }, + "QuickConnectResult": { + "type": "object", + "properties": { + "Authenticated": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this request is authorized." + }, + "Secret": { + "type": "string", + "description": "Gets the secret value used to uniquely identify this request. Can be used to retrieve authentication information." + }, + "Code": { + "type": "string", + "description": "Gets the user facing code used so the user can quickly differentiate this request from others." + }, + "DeviceId": { + "type": "string", + "description": "Gets the requesting device id." + }, + "DeviceName": { + "type": "string", + "description": "Gets the requesting device name." + }, + "AppName": { + "type": "string", + "description": "Gets the requesting app name." + }, + "AppVersion": { + "type": "string", + "description": "Gets the requesting app version." + }, + "DateAdded": { + "type": "string", + "description": "Gets or sets the DateTime that this request was created.", + "format": "date-time" + } + }, + "additionalProperties": false, + "description": "Stores the state of an quick connect request." + }, + "RatingType": { + "enum": [ + "Score", + "Likes" + ], + "type": "string" + }, + "ReadyRequestDto": { + "type": "object", + "properties": { + "When": { + "type": "string", + "description": "Gets or sets when the request has been made by the client.", + "format": "date-time" + }, + "PositionTicks": { + "type": "integer", + "description": "Gets or sets the position ticks.", + "format": "int64" + }, + "IsPlaying": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the client playback is unpaused." + }, + "PlaylistItemId": { + "type": "string", + "description": "Gets or sets the playlist item identifier of the playing item.", + "format": "uuid" + } + }, + "additionalProperties": false, + "description": "Class ReadyRequest." + }, + "RecommendationDto": { + "type": "object", + "properties": { + "Items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemDto" + }, + "nullable": true + }, + "RecommendationType": { + "enum": [ + "SimilarToRecentlyPlayed", + "SimilarToLikedItem", + "HasDirectorFromRecentlyPlayed", + "HasActorFromRecentlyPlayed", + "HasLikedDirector", + "HasLikedActor" + ], + "allOf": [ + { + "$ref": "#/components/schemas/RecommendationType" + } + ] + }, + "BaselineItemName": { + "type": "string", + "nullable": true + }, + "CategoryId": { + "type": "string", + "format": "uuid" + } + }, + "additionalProperties": false + }, + "RecommendationType": { + "enum": [ + "SimilarToRecentlyPlayed", + "SimilarToLikedItem", + "HasDirectorFromRecentlyPlayed", + "HasActorFromRecentlyPlayed", + "HasLikedDirector", + "HasLikedActor" + ], + "type": "string" + }, + "RecordingStatus": { + "enum": [ + "New", + "InProgress", + "Completed", + "Cancelled", + "ConflictedOk", + "ConflictedNotOk", + "Error" + ], + "type": "string" + }, + "RefreshProgressMessage": { + "type": "object", + "properties": { + "Data": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Gets or sets the data.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "RefreshProgress", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Refresh progress message." + }, + "RemoteImageInfo": { + "type": "object", + "properties": { + "ProviderName": { + "type": "string", + "description": "Gets or sets the name of the provider.", + "nullable": true + }, + "Url": { + "type": "string", + "description": "Gets or sets the URL.", + "nullable": true + }, + "ThumbnailUrl": { + "type": "string", + "description": "Gets or sets a url used for previewing a smaller version.", + "nullable": true + }, + "Height": { + "type": "integer", + "description": "Gets or sets the height.", + "format": "int32", + "nullable": true + }, + "Width": { + "type": "integer", + "description": "Gets or sets the width.", + "format": "int32", + "nullable": true + }, + "CommunityRating": { + "type": "number", + "description": "Gets or sets the community rating.", + "format": "double", + "nullable": true + }, + "VoteCount": { + "type": "integer", + "description": "Gets or sets the vote count.", + "format": "int32", + "nullable": true + }, + "Language": { + "type": "string", + "description": "Gets or sets the language.", + "nullable": true + }, + "Type": { + "enum": [ + "Primary", + "Art", + "Backdrop", + "Banner", + "Logo", + "Thumb", + "Disc", + "Box", + "Screenshot", + "Menu", + "Chapter", + "BoxRear", + "Profile" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageType" + } + ], + "description": "Gets or sets the type." + }, + "RatingType": { + "enum": [ + "Score", + "Likes" + ], + "allOf": [ + { + "$ref": "#/components/schemas/RatingType" + } + ], + "description": "Gets or sets the type of the rating." + } + }, + "additionalProperties": false, + "description": "Class RemoteImageInfo." + }, + "RemoteImageResult": { + "type": "object", + "properties": { + "Images": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteImageInfo" + }, + "description": "Gets or sets the images.", + "nullable": true + }, + "TotalRecordCount": { + "type": "integer", + "description": "Gets or sets the total record count.", + "format": "int32" + }, + "Providers": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the providers.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class RemoteImageResult." + }, + "RemoteLyricInfoDto": { + "type": "object", + "properties": { + "Id": { + "type": "string", + "description": "Gets or sets the id for the lyric." + }, + "ProviderName": { + "type": "string", + "description": "Gets the provider name." + }, + "Lyrics": { + "allOf": [ + { + "$ref": "#/components/schemas/LyricDto" + } + ], + "description": "Gets the lyrics." + } + }, + "additionalProperties": false, + "description": "The remote lyric info dto." + }, + "RemoteSearchResult": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "ProviderIds": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Gets or sets the provider ids.", + "nullable": true + }, + "ProductionYear": { + "type": "integer", + "description": "Gets or sets the year.", + "format": "int32", + "nullable": true + }, + "IndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "IndexNumberEnd": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "ParentIndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "PremiereDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "ImageUrl": { + "type": "string", + "nullable": true + }, + "SearchProviderName": { + "type": "string", + "nullable": true + }, + "Overview": { + "type": "string", + "nullable": true + }, + "AlbumArtist": { + "allOf": [ + { + "$ref": "#/components/schemas/RemoteSearchResult" + } + ], + "nullable": true + }, + "Artists": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RemoteSearchResult" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "RemoteSubtitleInfo": { + "type": "object", + "properties": { + "ThreeLetterISOLanguageName": { + "type": "string", + "nullable": true + }, + "Id": { + "type": "string", + "nullable": true + }, + "ProviderName": { + "type": "string", + "nullable": true + }, + "Name": { + "type": "string", + "nullable": true + }, + "Format": { + "type": "string", + "nullable": true + }, + "Author": { + "type": "string", + "nullable": true + }, + "Comment": { + "type": "string", + "nullable": true + }, + "DateCreated": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "CommunityRating": { + "type": "number", + "format": "float", + "nullable": true + }, + "FrameRate": { + "type": "number", + "format": "float", + "nullable": true + }, + "DownloadCount": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "IsHashMatch": { + "type": "boolean", + "nullable": true + }, + "AiTranslated": { + "type": "boolean", + "nullable": true + }, + "MachineTranslated": { + "type": "boolean", + "nullable": true + }, + "Forced": { + "type": "boolean", + "nullable": true + }, + "HearingImpaired": { + "type": "boolean", + "nullable": true + } + }, + "additionalProperties": false + }, + "RemoveFromPlaylistRequestDto": { + "type": "object", + "properties": { + "PlaylistItemIds": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Gets or sets the playlist identifiers of the items. Ignored when clearing the playlist." + }, + "ClearPlaylist": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the entire playlist should be cleared." + }, + "ClearPlayingItem": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the playing item should be removed as well. Used only when clearing the playlist." + } + }, + "additionalProperties": false, + "description": "Class RemoveFromPlaylistRequestDto." + }, + "RepeatMode": { + "enum": [ + "RepeatNone", + "RepeatAll", + "RepeatOne" + ], + "type": "string" + }, + "RepositoryInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "Url": { + "type": "string", + "description": "Gets or sets the URL.", + "nullable": true + }, + "Enabled": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the repository is enabled." + } + }, + "additionalProperties": false, + "description": "Class RepositoryInfo." + }, + "RestartRequiredMessage": { + "type": "object", + "properties": { + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "RestartRequired", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Restart required." + }, + "ScheduledTaskEndedMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/TaskResult" + } + ], + "description": "Class TaskExecutionInfo.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "ScheduledTaskEnded", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Scheduled task ended message." + }, + "ScheduledTasksInfoMessage": { + "type": "object", + "properties": { + "Data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskInfo" + }, + "description": "Gets or sets the data.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "ScheduledTasksInfo", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Scheduled tasks info message." + }, + "ScheduledTasksInfoStartMessage": { + "type": "object", + "properties": { + "Data": { + "type": "string", + "description": "Gets or sets the data.", + "nullable": true + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "ScheduledTasksInfoStart", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Scheduled tasks info start message.\r\nData is the timing data encoded as \"$initialDelay,$interval\" in ms." + }, + "ScheduledTasksInfoStopMessage": { + "type": "object", + "properties": { + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "ScheduledTasksInfoStop", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Scheduled tasks info stop message." + }, + "ScrollDirection": { + "enum": [ + "Horizontal", + "Vertical" + ], + "type": "string", + "description": "An enum representing the axis that should be scrolled." + }, + "SearchHint": { + "type": "object", + "properties": { + "ItemId": { + "type": "string", + "description": "Gets or sets the item id.", + "format": "uuid", + "deprecated": true + }, + "Id": { + "type": "string", + "description": "Gets or sets the item id.", + "format": "uuid" + }, + "Name": { + "type": "string", + "description": "Gets or sets the name." + }, + "MatchedTerm": { + "type": "string", + "description": "Gets or sets the matched term.", + "nullable": true + }, + "IndexNumber": { + "type": "integer", + "description": "Gets or sets the index number.", + "format": "int32", + "nullable": true + }, + "ProductionYear": { + "type": "integer", + "description": "Gets or sets the production year.", + "format": "int32", + "nullable": true + }, + "ParentIndexNumber": { + "type": "integer", + "description": "Gets or sets the parent index number.", + "format": "int32", + "nullable": true + }, + "PrimaryImageTag": { + "type": "string", + "description": "Gets or sets the image tag.", + "nullable": true + }, + "ThumbImageTag": { + "type": "string", + "description": "Gets or sets the thumb image tag.", + "nullable": true + }, + "ThumbImageItemId": { + "type": "string", + "description": "Gets or sets the thumb image item identifier.", + "nullable": true + }, + "BackdropImageTag": { + "type": "string", + "description": "Gets or sets the backdrop image tag.", + "nullable": true + }, + "BackdropImageItemId": { + "type": "string", + "description": "Gets or sets the backdrop image item identifier.", + "nullable": true + }, + "Type": { + "enum": [ + "AggregateFolder", + "Audio", + "AudioBook", + "BasePluginFolder", + "Book", + "BoxSet", + "Channel", + "ChannelFolderItem", + "CollectionFolder", + "Episode", + "Folder", + "Genre", + "ManualPlaylistsFolder", + "Movie", + "LiveTvChannel", + "LiveTvProgram", + "MusicAlbum", + "MusicArtist", + "MusicGenre", + "MusicVideo", + "Person", + "Photo", + "PhotoAlbum", + "Playlist", + "PlaylistsFolder", + "Program", + "Recording", + "Season", + "Series", + "Studio", + "Trailer", + "TvChannel", + "TvProgram", + "UserRootFolder", + "UserView", + "Video", + "Year" + ], + "allOf": [ + { + "$ref": "#/components/schemas/BaseItemKind" + } + ], + "description": "Gets or sets the type." + }, + "IsFolder": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is folder.", + "nullable": true + }, + "RunTimeTicks": { + "type": "integer", + "description": "Gets or sets the run time ticks.", + "format": "int64", + "nullable": true + }, + "MediaType": { + "enum": [ + "Unknown", + "Video", + "Audio", + "Photo", + "Book" + ], + "allOf": [ + { + "$ref": "#/components/schemas/MediaType" + } + ], + "description": "Gets or sets the type of the media.", + "default": "Unknown" + }, + "StartDate": { + "type": "string", + "description": "Gets or sets the start date.", + "format": "date-time", + "nullable": true + }, + "EndDate": { + "type": "string", + "description": "Gets or sets the end date.", + "format": "date-time", + "nullable": true + }, + "Series": { + "type": "string", + "description": "Gets or sets the series.", + "nullable": true + }, + "Status": { + "type": "string", + "description": "Gets or sets the status.", + "nullable": true + }, + "Album": { + "type": "string", + "description": "Gets or sets the album.", + "nullable": true + }, + "AlbumId": { + "type": "string", + "description": "Gets or sets the album id.", + "format": "uuid", + "nullable": true + }, + "AlbumArtist": { + "type": "string", + "description": "Gets or sets the album artist.", + "nullable": true + }, + "Artists": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the artists." + }, + "SongCount": { + "type": "integer", + "description": "Gets or sets the song count.", + "format": "int32", + "nullable": true + }, + "EpisodeCount": { + "type": "integer", + "description": "Gets or sets the episode count.", + "format": "int32", + "nullable": true + }, + "ChannelId": { + "type": "string", + "description": "Gets or sets the channel identifier.", + "format": "uuid", + "nullable": true + }, + "ChannelName": { + "type": "string", + "description": "Gets or sets the name of the channel.", + "nullable": true + }, + "PrimaryImageAspectRatio": { + "type": "number", + "description": "Gets or sets the primary image aspect ratio.", + "format": "double", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class SearchHintResult." + }, + "SearchHintResult": { + "type": "object", + "properties": { + "SearchHints": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SearchHint" + }, + "description": "Gets the search hints." + }, + "TotalRecordCount": { + "type": "integer", + "description": "Gets the total record count.", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "Class SearchHintResult." + }, + "SeekRequestDto": { + "type": "object", + "properties": { + "PositionTicks": { + "type": "integer", + "description": "Gets or sets the position ticks.", + "format": "int64" + } + }, + "additionalProperties": false, + "description": "Class SeekRequestDto." + }, + "SendCommand": { + "type": "object", + "properties": { + "GroupId": { + "type": "string", + "description": "Gets the group identifier.", + "format": "uuid" + }, + "PlaylistItemId": { + "type": "string", + "description": "Gets the playlist identifier of the playing item.", + "format": "uuid" + }, + "When": { + "type": "string", + "description": "Gets or sets the UTC time when to execute the command.", + "format": "date-time" + }, + "PositionTicks": { + "type": "integer", + "description": "Gets the position ticks.", + "format": "int64", + "nullable": true + }, + "Command": { + "enum": [ + "Unpause", + "Pause", + "Stop", + "Seek" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SendCommandType" + } + ], + "description": "Gets the command." + }, + "EmittedAt": { + "type": "string", + "description": "Gets the UTC time when this command has been emitted.", + "format": "date-time" + } + }, + "additionalProperties": false, + "description": "Class SendCommand." + }, + "SendCommandType": { + "enum": [ + "Unpause", + "Pause", + "Stop", + "Seek" + ], + "type": "string", + "description": "Enum SendCommandType." + }, + "SeriesInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "OriginalTitle": { + "type": "string", + "description": "Gets or sets the original title.", + "nullable": true + }, + "Path": { + "type": "string", + "description": "Gets or sets the path.", + "nullable": true + }, + "MetadataLanguage": { + "type": "string", + "description": "Gets or sets the metadata language.", + "nullable": true + }, + "MetadataCountryCode": { + "type": "string", + "description": "Gets or sets the metadata country code.", + "nullable": true + }, + "ProviderIds": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Gets or sets the provider ids.", + "nullable": true + }, + "Year": { + "type": "integer", + "description": "Gets or sets the year.", + "format": "int32", + "nullable": true + }, + "IndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "ParentIndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "PremiereDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "IsAutomated": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "SeriesInfoRemoteSearchQuery": { + "type": "object", + "properties": { + "SearchInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/SeriesInfo" + } + ], + "nullable": true + }, + "ItemId": { + "type": "string", + "format": "uuid" + }, + "SearchProviderName": { + "type": "string", + "description": "Gets or sets the provider name to search within if set.", + "nullable": true + }, + "IncludeDisabledProviders": { + "type": "boolean", + "description": "Gets or sets a value indicating whether disabled providers should be included." + } + }, + "additionalProperties": false + }, + "SeriesStatus": { + "enum": [ + "Continuing", + "Ended", + "Unreleased" + ], + "type": "string", + "description": "The status of a series." + }, + "SeriesTimerCancelledMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/TimerEventInfo" + } + ], + "description": "Gets or sets the data.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "SeriesTimerCancelled", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Series timer cancelled message." + }, + "SeriesTimerCreatedMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/TimerEventInfo" + } + ], + "description": "Gets or sets the data.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "SeriesTimerCreated", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Series timer created message." + }, + "SeriesTimerInfoDto": { + "type": "object", + "properties": { + "Id": { + "type": "string", + "description": "Gets or sets the Id of the recording.", + "nullable": true + }, + "Type": { + "type": "string", + "nullable": true + }, + "ServerId": { + "type": "string", + "description": "Gets or sets the server identifier.", + "nullable": true + }, + "ExternalId": { + "type": "string", + "description": "Gets or sets the external identifier.", + "nullable": true + }, + "ChannelId": { + "type": "string", + "description": "Gets or sets the channel id of the recording.", + "format": "uuid" + }, + "ExternalChannelId": { + "type": "string", + "description": "Gets or sets the external channel identifier.", + "nullable": true + }, + "ChannelName": { + "type": "string", + "description": "Gets or sets the channel name of the recording.", + "nullable": true + }, + "ChannelPrimaryImageTag": { + "type": "string", + "nullable": true + }, + "ProgramId": { + "type": "string", + "description": "Gets or sets the program identifier.", + "nullable": true + }, + "ExternalProgramId": { + "type": "string", + "description": "Gets or sets the external program identifier.", + "nullable": true + }, + "Name": { + "type": "string", + "description": "Gets or sets the name of the recording.", + "nullable": true + }, + "Overview": { + "type": "string", + "description": "Gets or sets the description of the recording.", + "nullable": true + }, + "StartDate": { + "type": "string", + "description": "Gets or sets the start date of the recording, in UTC.", + "format": "date-time" + }, + "EndDate": { + "type": "string", + "description": "Gets or sets the end date of the recording, in UTC.", + "format": "date-time" + }, + "ServiceName": { + "type": "string", + "description": "Gets or sets the name of the service.", + "nullable": true + }, + "Priority": { + "type": "integer", + "description": "Gets or sets the priority.", + "format": "int32" + }, + "PrePaddingSeconds": { + "type": "integer", + "description": "Gets or sets the pre padding seconds.", + "format": "int32" + }, + "PostPaddingSeconds": { + "type": "integer", + "description": "Gets or sets the post padding seconds.", + "format": "int32" + }, + "IsPrePaddingRequired": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is pre padding required." + }, + "ParentBackdropItemId": { + "type": "string", + "description": "Gets or sets the Id of the Parent that has a backdrop if the item does not have one.", + "nullable": true + }, + "ParentBackdropImageTags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the parent backdrop image tags.", + "nullable": true + }, + "IsPostPaddingRequired": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is post padding required." + }, + "KeepUntil": { + "enum": [ + "UntilDeleted", + "UntilSpaceNeeded", + "UntilWatched", + "UntilDate" + ], + "allOf": [ + { + "$ref": "#/components/schemas/KeepUntil" + } + ] + }, + "RecordAnyTime": { + "type": "boolean", + "description": "Gets or sets a value indicating whether [record any time]." + }, + "SkipEpisodesInLibrary": { + "type": "boolean" + }, + "RecordAnyChannel": { + "type": "boolean", + "description": "Gets or sets a value indicating whether [record any channel]." + }, + "KeepUpTo": { + "type": "integer", + "format": "int32" + }, + "RecordNewOnly": { + "type": "boolean", + "description": "Gets or sets a value indicating whether [record new only]." + }, + "Days": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DayOfWeek" + }, + "description": "Gets or sets the days.", + "nullable": true + }, + "DayPattern": { + "enum": [ + "Daily", + "Weekdays", + "Weekends" + ], + "allOf": [ + { + "$ref": "#/components/schemas/DayPattern" + } + ], + "description": "Gets or sets the day pattern.", + "nullable": true + }, + "ImageTags": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "description": "Gets or sets the image tags.", + "nullable": true + }, + "ParentThumbItemId": { + "type": "string", + "description": "Gets or sets the parent thumb item id.", + "nullable": true + }, + "ParentThumbImageTag": { + "type": "string", + "description": "Gets or sets the parent thumb image tag.", + "nullable": true + }, + "ParentPrimaryImageItemId": { + "type": "string", + "description": "Gets or sets the parent primary image item identifier.", + "format": "uuid", + "nullable": true + }, + "ParentPrimaryImageTag": { + "type": "string", + "description": "Gets or sets the parent primary image tag.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class SeriesTimerInfoDto." + }, + "SeriesTimerInfoDtoQueryResult": { + "type": "object", + "properties": { + "Items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SeriesTimerInfoDto" + }, + "description": "Gets or sets the items." + }, + "TotalRecordCount": { + "type": "integer", + "description": "Gets or sets the total number of records available.", + "format": "int32" + }, + "StartIndex": { + "type": "integer", + "description": "Gets or sets the index of the first record in Items.", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "Query result container." + }, + "ServerConfiguration": { + "type": "object", + "properties": { + "LogFileRetentionDays": { + "type": "integer", + "description": "Gets or sets the number of days we should retain log files.", + "format": "int32" + }, + "IsStartupWizardCompleted": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is first run." + }, + "CachePath": { + "type": "string", + "description": "Gets or sets the cache path.", + "nullable": true + }, + "PreviousVersion": { + "type": "string", + "description": "Gets or sets the last known version that was ran using the configuration.", + "nullable": true + }, + "PreviousVersionStr": { + "type": "string", + "description": "Gets or sets the stringified PreviousVersion to be stored/loaded,\r\nbecause System.Version itself isn't xml-serializable.", + "nullable": true + }, + "EnableMetrics": { + "type": "boolean", + "description": "Gets or sets a value indicating whether to enable prometheus metrics exporting." + }, + "EnableNormalizedItemByNameIds": { + "type": "boolean" + }, + "IsPortAuthorized": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is port authorized." + }, + "QuickConnectAvailable": { + "type": "boolean", + "description": "Gets or sets a value indicating whether quick connect is available for use on this server." + }, + "EnableCaseSensitiveItemIds": { + "type": "boolean", + "description": "Gets or sets a value indicating whether [enable case-sensitive item ids]." + }, + "DisableLiveTvChannelUserDataName": { + "type": "boolean" + }, + "MetadataPath": { + "type": "string", + "description": "Gets or sets the metadata path." + }, + "PreferredMetadataLanguage": { + "type": "string", + "description": "Gets or sets the preferred metadata language." + }, + "MetadataCountryCode": { + "type": "string", + "description": "Gets or sets the metadata country code." + }, + "SortReplaceCharacters": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets characters to be replaced with a ' ' in strings to create a sort name." + }, + "SortRemoveCharacters": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets characters to be removed from strings to create a sort name." + }, + "SortRemoveWords": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets words to be removed from strings to create a sort name." + }, + "MinResumePct": { + "type": "integer", + "description": "Gets or sets the minimum percentage of an item that must be played in order for playstate to be updated.", + "format": "int32" + }, + "MaxResumePct": { + "type": "integer", + "description": "Gets or sets the maximum percentage of an item that can be played while still saving playstate. If this percentage is crossed playstate will be reset to the beginning and the item will be marked watched.", + "format": "int32" + }, + "MinResumeDurationSeconds": { + "type": "integer", + "description": "Gets or sets the minimum duration that an item must have in order to be eligible for playstate updates..", + "format": "int32" + }, + "MinAudiobookResume": { + "type": "integer", + "description": "Gets or sets the minimum minutes of a book that must be played in order for playstate to be updated.", + "format": "int32" + }, + "MaxAudiobookResume": { + "type": "integer", + "description": "Gets or sets the remaining minutes of a book that can be played while still saving playstate. If this percentage is crossed playstate will be reset to the beginning and the item will be marked watched.", + "format": "int32" + }, + "InactiveSessionThreshold": { + "type": "integer", + "description": "Gets or sets the threshold in minutes after a inactive session gets closed automatically.\r\nIf set to 0 the check for inactive sessions gets disabled.", + "format": "int32" + }, + "LibraryMonitorDelay": { + "type": "integer", + "description": "Gets or sets the delay in seconds that we will wait after a file system change to try and discover what has been added/removed\r\nSome delay is necessary with some items because their creation is not atomic. It involves the creation of several\r\ndifferent directories and files.", + "format": "int32" + }, + "LibraryUpdateDuration": { + "type": "integer", + "description": "Gets or sets the duration in seconds that we will wait after a library updated event before executing the library changed notification.", + "format": "int32" + }, + "CacheSize": { + "type": "integer", + "description": "Gets or sets the maximum amount of items to cache.", + "format": "int32" + }, + "ImageSavingConvention": { + "enum": [ + "Legacy", + "Compatible" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageSavingConvention" + } + ], + "description": "Gets or sets the image saving convention." + }, + "MetadataOptions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MetadataOptions" + } + }, + "SkipDeserializationForBasicTypes": { + "type": "boolean" + }, + "ServerName": { + "type": "string" + }, + "UICulture": { + "type": "string" + }, + "SaveMetadataHidden": { + "type": "boolean" + }, + "ContentTypes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/NameValuePair" + } + }, + "RemoteClientBitrateLimit": { + "type": "integer", + "format": "int32" + }, + "EnableFolderView": { + "type": "boolean" + }, + "EnableGroupingMoviesIntoCollections": { + "type": "boolean" + }, + "EnableGroupingShowsIntoCollections": { + "type": "boolean" + }, + "DisplaySpecialsWithinSeasons": { + "type": "boolean" + }, + "CodecsUsed": { + "type": "array", + "items": { + "type": "string" + } + }, + "PluginRepositories": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RepositoryInfo" + } + }, + "EnableExternalContentInSuggestions": { + "type": "boolean" + }, + "ImageExtractionTimeoutMs": { + "type": "integer", + "format": "int32" + }, + "PathSubstitutions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PathSubstitution" + } + }, + "EnableSlowResponseWarning": { + "type": "boolean", + "description": "Gets or sets a value indicating whether slow server responses should be logged as a warning." + }, + "SlowResponseThresholdMs": { + "type": "integer", + "description": "Gets or sets the threshold for the slow response time warning in ms.", + "format": "int64" + }, + "CorsHosts": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the cors hosts." + }, + "ActivityLogRetentionDays": { + "type": "integer", + "description": "Gets or sets the number of days we should retain activity logs.", + "format": "int32", + "nullable": true + }, + "LibraryScanFanoutConcurrency": { + "type": "integer", + "description": "Gets or sets the how the library scan fans out.", + "format": "int32" + }, + "LibraryMetadataRefreshConcurrency": { + "type": "integer", + "description": "Gets or sets the how many metadata refreshes can run concurrently.", + "format": "int32" + }, + "AllowClientLogUpload": { + "type": "boolean", + "description": "Gets or sets a value indicating whether clients should be allowed to upload logs." + }, + "DummyChapterDuration": { + "type": "integer", + "description": "Gets or sets the dummy chapter duration in seconds, use 0 (zero) or less to disable generation altogether.", + "format": "int32" + }, + "ChapterImageResolution": { + "enum": [ + "MatchSource", + "P144", + "P240", + "P360", + "P480", + "P720", + "P1080", + "P1440", + "P2160" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ImageResolution" + } + ], + "description": "Gets or sets the chapter image resolution." + }, + "ParallelImageEncodingLimit": { + "type": "integer", + "description": "Gets or sets the limit for parallel image encoding.", + "format": "int32" + }, + "CastReceiverApplications": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CastReceiverApplication" + }, + "description": "Gets or sets the list of cast receiver applications." + }, + "TrickplayOptions": { + "allOf": [ + { + "$ref": "#/components/schemas/TrickplayOptions" + } + ], + "description": "Gets or sets the trickplay options." + }, + "EnableLegacyAuthorization": { + "type": "boolean", + "description": "Gets or sets a value indicating whether old authorization methods are allowed." + } + }, + "additionalProperties": false, + "description": "Represents the server configuration." + }, + "ServerDiscoveryInfo": { + "type": "object", + "properties": { + "Address": { + "type": "string", + "description": "Gets the address." + }, + "Id": { + "type": "string", + "description": "Gets the server identifier." + }, + "Name": { + "type": "string", + "description": "Gets the name." + }, + "EndpointAddress": { + "type": "string", + "description": "Gets the endpoint address.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "The server discovery info model." + }, + "ServerRestartingMessage": { + "type": "object", + "properties": { + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "ServerRestarting", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Server restarting down message." + }, + "ServerShuttingDownMessage": { + "type": "object", + "properties": { + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "ServerShuttingDown", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Server shutting down message." + }, + "SessionInfoDto": { + "type": "object", + "properties": { + "PlayState": { + "allOf": [ + { + "$ref": "#/components/schemas/PlayerStateInfo" + } + ], + "description": "Gets or sets the play state.", + "nullable": true + }, + "AdditionalUsers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionUserInfo" + }, + "description": "Gets or sets the additional users.", + "nullable": true + }, + "Capabilities": { + "allOf": [ + { + "$ref": "#/components/schemas/ClientCapabilitiesDto" + } + ], + "description": "Gets or sets the client capabilities.", + "nullable": true + }, + "RemoteEndPoint": { + "type": "string", + "description": "Gets or sets the remote end point.", + "nullable": true + }, + "PlayableMediaTypes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MediaType" + }, + "description": "Gets or sets the playable media types." + }, + "Id": { + "type": "string", + "description": "Gets or sets the id.", + "nullable": true + }, + "UserId": { + "type": "string", + "description": "Gets or sets the user id.", + "format": "uuid" + }, + "UserName": { + "type": "string", + "description": "Gets or sets the username.", + "nullable": true + }, + "Client": { + "type": "string", + "description": "Gets or sets the type of the client.", + "nullable": true + }, + "LastActivityDate": { + "type": "string", + "description": "Gets or sets the last activity date.", + "format": "date-time" + }, + "LastPlaybackCheckIn": { + "type": "string", + "description": "Gets or sets the last playback check in.", + "format": "date-time" + }, + "LastPausedDate": { + "type": "string", + "description": "Gets or sets the last paused date.", + "format": "date-time", + "nullable": true + }, + "DeviceName": { + "type": "string", + "description": "Gets or sets the name of the device.", + "nullable": true + }, + "DeviceType": { + "type": "string", + "description": "Gets or sets the type of the device.", + "nullable": true + }, + "NowPlayingItem": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseItemDto" + } + ], + "description": "Gets or sets the now playing item.", + "nullable": true + }, + "NowViewingItem": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseItemDto" + } + ], + "description": "Gets or sets the now viewing item.", + "nullable": true + }, + "DeviceId": { + "type": "string", + "description": "Gets or sets the device id.", + "nullable": true + }, + "ApplicationVersion": { + "type": "string", + "description": "Gets or sets the application version.", + "nullable": true + }, + "TranscodingInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/TranscodingInfo" + } + ], + "description": "Gets or sets the transcoding info.", + "nullable": true + }, + "IsActive": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this session is active." + }, + "SupportsMediaControl": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the session supports media control." + }, + "SupportsRemoteControl": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the session supports remote control." + }, + "NowPlayingQueue": { + "type": "array", + "items": { + "$ref": "#/components/schemas/QueueItem" + }, + "description": "Gets or sets the now playing queue.", + "nullable": true + }, + "NowPlayingQueueFullItems": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemDto" + }, + "description": "Gets or sets the now playing queue full items.", + "nullable": true + }, + "HasCustomDeviceName": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the session has a custom device name." + }, + "PlaylistItemId": { + "type": "string", + "description": "Gets or sets the playlist item id.", + "nullable": true + }, + "ServerId": { + "type": "string", + "description": "Gets or sets the server id.", + "nullable": true + }, + "UserPrimaryImageTag": { + "type": "string", + "description": "Gets or sets the user primary image tag.", + "nullable": true + }, + "SupportedCommands": { + "type": "array", + "items": { + "$ref": "#/components/schemas/GeneralCommandType" + }, + "description": "Gets or sets the supported commands." + } + }, + "additionalProperties": false, + "description": "Session info DTO." + }, + "SessionMessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "type": "string", + "description": "The different kinds of messages that are used in the WebSocket api." + }, + "SessionsMessage": { + "type": "object", + "properties": { + "Data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/SessionInfoDto" + }, + "description": "Gets or sets the data.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "Sessions", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Sessions message." + }, + "SessionsStartMessage": { + "type": "object", + "properties": { + "Data": { + "type": "string", + "description": "Gets or sets the data.", + "nullable": true + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "SessionsStart", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Sessions start message.\r\nData is the timing data encoded as \"$initialDelay,$interval\" in ms." + }, + "SessionsStopMessage": { + "type": "object", + "properties": { + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "SessionsStop", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Sessions stop message." + }, + "SessionUserInfo": { + "type": "object", + "properties": { + "UserId": { + "type": "string", + "description": "Gets or sets the user identifier.", + "format": "uuid" + }, + "UserName": { + "type": "string", + "description": "Gets or sets the name of the user.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class SessionUserInfo." + }, + "SetChannelMappingDto": { + "required": [ + "ProviderChannelId", + "ProviderId", + "TunerChannelId" + ], + "type": "object", + "properties": { + "ProviderId": { + "type": "string", + "description": "Gets or sets the provider id." + }, + "TunerChannelId": { + "type": "string", + "description": "Gets or sets the tuner channel id." + }, + "ProviderChannelId": { + "type": "string", + "description": "Gets or sets the provider channel id." + } + }, + "additionalProperties": false, + "description": "Set channel mapping dto." + }, + "SetPlaylistItemRequestDto": { + "type": "object", + "properties": { + "PlaylistItemId": { + "type": "string", + "description": "Gets or sets the playlist identifier of the playing item.", + "format": "uuid" + } + }, + "additionalProperties": false, + "description": "Class SetPlaylistItemRequestDto." + }, + "SetRepeatModeRequestDto": { + "type": "object", + "properties": { + "Mode": { + "enum": [ + "RepeatOne", + "RepeatAll", + "RepeatNone" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GroupRepeatMode" + } + ], + "description": "Enum GroupRepeatMode." + } + }, + "additionalProperties": false, + "description": "Class SetRepeatModeRequestDto." + }, + "SetShuffleModeRequestDto": { + "type": "object", + "properties": { + "Mode": { + "enum": [ + "Sorted", + "Shuffle" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GroupShuffleMode" + } + ], + "description": "Enum GroupShuffleMode." + } + }, + "additionalProperties": false, + "description": "Class SetShuffleModeRequestDto." + }, + "SongInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "OriginalTitle": { + "type": "string", + "description": "Gets or sets the original title.", + "nullable": true + }, + "Path": { + "type": "string", + "description": "Gets or sets the path.", + "nullable": true + }, + "MetadataLanguage": { + "type": "string", + "description": "Gets or sets the metadata language.", + "nullable": true + }, + "MetadataCountryCode": { + "type": "string", + "description": "Gets or sets the metadata country code.", + "nullable": true + }, + "ProviderIds": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Gets or sets the provider ids.", + "nullable": true + }, + "Year": { + "type": "integer", + "description": "Gets or sets the year.", + "format": "int32", + "nullable": true + }, + "IndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "ParentIndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "PremiereDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "IsAutomated": { + "type": "boolean" + }, + "AlbumArtists": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "Album": { + "type": "string", + "nullable": true + }, + "Artists": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "SortOrder": { + "enum": [ + "Ascending", + "Descending" + ], + "type": "string", + "description": "An enum representing the sorting order." + }, + "SpecialViewOptionDto": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets view option name.", + "nullable": true + }, + "Id": { + "type": "string", + "description": "Gets or sets view option id.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Special view option dto." + }, + "StartupConfigurationDto": { + "type": "object", + "properties": { + "ServerName": { + "type": "string", + "description": "Gets or sets the server name.", + "nullable": true + }, + "UICulture": { + "type": "string", + "description": "Gets or sets UI language culture.", + "nullable": true + }, + "MetadataCountryCode": { + "type": "string", + "description": "Gets or sets the metadata country code.", + "nullable": true + }, + "PreferredMetadataLanguage": { + "type": "string", + "description": "Gets or sets the preferred language for the metadata.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "The startup configuration DTO." + }, + "StartupRemoteAccessDto": { + "required": [ + "EnableAutomaticPortMapping", + "EnableRemoteAccess" + ], + "type": "object", + "properties": { + "EnableRemoteAccess": { + "type": "boolean", + "description": "Gets or sets a value indicating whether enable remote access." + }, + "EnableAutomaticPortMapping": { + "type": "boolean", + "description": "Gets or sets a value indicating whether enable automatic port mapping.", + "deprecated": true + } + }, + "additionalProperties": false, + "description": "Startup remote access dto." + }, + "StartupUserDto": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the username.", + "nullable": true + }, + "Password": { + "type": "string", + "description": "Gets or sets the user's password.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "The startup user DTO." + }, + "SubtitleDeliveryMethod": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "type": "string", + "description": "Delivery method to use during playback of a specific subtitle format." + }, + "SubtitleOptions": { + "type": "object", + "properties": { + "SkipIfEmbeddedSubtitlesPresent": { + "type": "boolean" + }, + "SkipIfAudioTrackMatches": { + "type": "boolean" + }, + "DownloadLanguages": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "DownloadMovieSubtitles": { + "type": "boolean" + }, + "DownloadEpisodeSubtitles": { + "type": "boolean" + }, + "OpenSubtitlesUsername": { + "type": "string", + "nullable": true + }, + "OpenSubtitlesPasswordHash": { + "type": "string", + "nullable": true + }, + "IsOpenSubtitleVipAccount": { + "type": "boolean" + }, + "RequirePerfectMatch": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "SubtitlePlaybackMode": { + "enum": [ + "Default", + "Always", + "OnlyForced", + "None", + "Smart" + ], + "type": "string", + "description": "An enum representing a subtitle playback mode." + }, + "SubtitleProfile": { + "type": "object", + "properties": { + "Format": { + "type": "string", + "description": "Gets or sets the format.", + "nullable": true + }, + "Method": { + "enum": [ + "Encode", + "Embed", + "External", + "Hls", + "Drop" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitleDeliveryMethod" + } + ], + "description": "Gets or sets the delivery method." + }, + "DidlMode": { + "type": "string", + "description": "Gets or sets the DIDL mode.", + "nullable": true + }, + "Language": { + "type": "string", + "description": "Gets or sets the language.", + "nullable": true + }, + "Container": { + "type": "string", + "description": "Gets or sets the container.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "A class for subtitle profile information." + }, + "SyncPlayCommandMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/SendCommand" + } + ], + "description": "Class SendCommand.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "SyncPlayCommand", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Sync play command." + }, + "SyncPlayGroupDoesNotExistUpdate": { + "type": "object", + "properties": { + "GroupId": { + "type": "string", + "description": "Gets the group identifier.", + "format": "uuid", + "readOnly": true + }, + "Data": { + "type": "string", + "description": "Gets the update data.", + "readOnly": true + }, + "Type": { + "enum": [ + "UserJoined", + "UserLeft", + "GroupJoined", + "GroupLeft", + "StateUpdate", + "PlayQueue", + "NotInGroup", + "GroupDoesNotExist", + "LibraryAccessDenied" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GroupUpdateType" + } + ], + "description": "Enum GroupUpdateType.", + "default": "GroupDoesNotExist", + "readOnly": true + } + }, + "additionalProperties": false + }, + "SyncPlayGroupJoinedUpdate": { + "type": "object", + "properties": { + "GroupId": { + "type": "string", + "description": "Gets the group identifier.", + "format": "uuid", + "readOnly": true + }, + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/GroupInfoDto" + } + ], + "description": "Gets the update data.", + "readOnly": true + }, + "Type": { + "enum": [ + "UserJoined", + "UserLeft", + "GroupJoined", + "GroupLeft", + "StateUpdate", + "PlayQueue", + "NotInGroup", + "GroupDoesNotExist", + "LibraryAccessDenied" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GroupUpdateType" + } + ], + "description": "Enum GroupUpdateType.", + "default": "GroupJoined", + "readOnly": true + } + }, + "additionalProperties": false + }, + "SyncPlayGroupLeftUpdate": { + "type": "object", + "properties": { + "GroupId": { + "type": "string", + "description": "Gets the group identifier.", + "format": "uuid", + "readOnly": true + }, + "Data": { + "type": "string", + "description": "Gets the update data.", + "readOnly": true + }, + "Type": { + "enum": [ + "UserJoined", + "UserLeft", + "GroupJoined", + "GroupLeft", + "StateUpdate", + "PlayQueue", + "NotInGroup", + "GroupDoesNotExist", + "LibraryAccessDenied" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GroupUpdateType" + } + ], + "description": "Enum GroupUpdateType.", + "default": "GroupLeft", + "readOnly": true + } + }, + "additionalProperties": false + }, + "SyncPlayGroupUpdateMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/GroupUpdate" + } + ], + "description": "Group update data" + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "SyncPlayGroupUpdate", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Untyped sync play command." + }, + "SyncPlayLibraryAccessDeniedUpdate": { + "type": "object", + "properties": { + "GroupId": { + "type": "string", + "description": "Gets the group identifier.", + "format": "uuid", + "readOnly": true + }, + "Data": { + "type": "string", + "description": "Gets the update data.", + "readOnly": true + }, + "Type": { + "enum": [ + "UserJoined", + "UserLeft", + "GroupJoined", + "GroupLeft", + "StateUpdate", + "PlayQueue", + "NotInGroup", + "GroupDoesNotExist", + "LibraryAccessDenied" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GroupUpdateType" + } + ], + "description": "Enum GroupUpdateType.", + "default": "LibraryAccessDenied", + "readOnly": true + } + }, + "additionalProperties": false + }, + "SyncPlayNotInGroupUpdate": { + "type": "object", + "properties": { + "GroupId": { + "type": "string", + "description": "Gets the group identifier.", + "format": "uuid", + "readOnly": true + }, + "Data": { + "type": "string", + "description": "Gets the update data.", + "readOnly": true + }, + "Type": { + "enum": [ + "UserJoined", + "UserLeft", + "GroupJoined", + "GroupLeft", + "StateUpdate", + "PlayQueue", + "NotInGroup", + "GroupDoesNotExist", + "LibraryAccessDenied" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GroupUpdateType" + } + ], + "description": "Enum GroupUpdateType.", + "default": "NotInGroup", + "readOnly": true + } + }, + "additionalProperties": false + }, + "SyncPlayPlayQueueUpdate": { + "type": "object", + "properties": { + "GroupId": { + "type": "string", + "description": "Gets the group identifier.", + "format": "uuid", + "readOnly": true + }, + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/PlayQueueUpdate" + } + ], + "description": "Gets the update data.", + "readOnly": true + }, + "Type": { + "enum": [ + "UserJoined", + "UserLeft", + "GroupJoined", + "GroupLeft", + "StateUpdate", + "PlayQueue", + "NotInGroup", + "GroupDoesNotExist", + "LibraryAccessDenied" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GroupUpdateType" + } + ], + "description": "Enum GroupUpdateType.", + "default": "PlayQueue", + "readOnly": true + } + }, + "additionalProperties": false + }, + "SyncPlayQueueItem": { + "type": "object", + "properties": { + "ItemId": { + "type": "string", + "description": "Gets the item identifier.", + "format": "uuid" + }, + "PlaylistItemId": { + "type": "string", + "description": "Gets the playlist identifier of the item.", + "format": "uuid", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Class QueueItem." + }, + "SyncPlayStateUpdate": { + "type": "object", + "properties": { + "GroupId": { + "type": "string", + "description": "Gets the group identifier.", + "format": "uuid", + "readOnly": true + }, + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/GroupStateUpdate" + } + ], + "description": "Gets the update data.", + "readOnly": true + }, + "Type": { + "enum": [ + "UserJoined", + "UserLeft", + "GroupJoined", + "GroupLeft", + "StateUpdate", + "PlayQueue", + "NotInGroup", + "GroupDoesNotExist", + "LibraryAccessDenied" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GroupUpdateType" + } + ], + "description": "Enum GroupUpdateType.", + "default": "StateUpdate", + "readOnly": true + } + }, + "additionalProperties": false + }, + "SyncPlayUserAccessType": { + "enum": [ + "CreateAndJoinGroups", + "JoinGroups", + "None" + ], + "type": "string", + "description": "Enum SyncPlayUserAccessType." + }, + "SyncPlayUserJoinedUpdate": { + "type": "object", + "properties": { + "GroupId": { + "type": "string", + "description": "Gets the group identifier.", + "format": "uuid", + "readOnly": true + }, + "Data": { + "type": "string", + "description": "Gets the update data.", + "readOnly": true + }, + "Type": { + "enum": [ + "UserJoined", + "UserLeft", + "GroupJoined", + "GroupLeft", + "StateUpdate", + "PlayQueue", + "NotInGroup", + "GroupDoesNotExist", + "LibraryAccessDenied" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GroupUpdateType" + } + ], + "description": "Enum GroupUpdateType.", + "default": "UserJoined", + "readOnly": true + } + }, + "additionalProperties": false + }, + "SyncPlayUserLeftUpdate": { + "type": "object", + "properties": { + "GroupId": { + "type": "string", + "description": "Gets the group identifier.", + "format": "uuid", + "readOnly": true + }, + "Data": { + "type": "string", + "description": "Gets the update data.", + "readOnly": true + }, + "Type": { + "enum": [ + "UserJoined", + "UserLeft", + "GroupJoined", + "GroupLeft", + "StateUpdate", + "PlayQueue", + "NotInGroup", + "GroupDoesNotExist", + "LibraryAccessDenied" + ], + "allOf": [ + { + "$ref": "#/components/schemas/GroupUpdateType" + } + ], + "description": "Enum GroupUpdateType.", + "default": "UserLeft", + "readOnly": true + } + }, + "additionalProperties": false + }, + "SystemInfo": { + "type": "object", + "properties": { + "LocalAddress": { + "type": "string", + "description": "Gets or sets the local address.", + "nullable": true + }, + "ServerName": { + "type": "string", + "description": "Gets or sets the name of the server.", + "nullable": true + }, + "Version": { + "type": "string", + "description": "Gets or sets the server version.", + "nullable": true + }, + "ProductName": { + "type": "string", + "description": "Gets or sets the product name. This is the AssemblyProduct name.", + "nullable": true + }, + "OperatingSystem": { + "type": "string", + "description": "Gets or sets the operating system.", + "nullable": true, + "deprecated": true + }, + "Id": { + "type": "string", + "description": "Gets or sets the id.", + "nullable": true + }, + "StartupWizardCompleted": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the startup wizard is completed.", + "nullable": true + }, + "OperatingSystemDisplayName": { + "type": "string", + "description": "Gets or sets the display name of the operating system.", + "nullable": true, + "deprecated": true + }, + "PackageName": { + "type": "string", + "description": "Gets or sets the package name.", + "nullable": true + }, + "HasPendingRestart": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance has pending restart." + }, + "IsShuttingDown": { + "type": "boolean" + }, + "SupportsLibraryMonitor": { + "type": "boolean", + "description": "Gets or sets a value indicating whether [supports library monitor]." + }, + "WebSocketPortNumber": { + "type": "integer", + "description": "Gets or sets the web socket port number.", + "format": "int32" + }, + "CompletedInstallations": { + "type": "array", + "items": { + "$ref": "#/components/schemas/InstallationInfo" + }, + "description": "Gets or sets the completed installations.", + "nullable": true + }, + "CanSelfRestart": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance can self restart.", + "default": true, + "deprecated": true + }, + "CanLaunchWebBrowser": { + "type": "boolean", + "default": false, + "deprecated": true + }, + "ProgramDataPath": { + "type": "string", + "description": "Gets or sets the program data path.", + "nullable": true, + "deprecated": true + }, + "WebPath": { + "type": "string", + "description": "Gets or sets the web UI resources path.", + "nullable": true, + "deprecated": true + }, + "ItemsByNamePath": { + "type": "string", + "description": "Gets or sets the items by name path.", + "nullable": true, + "deprecated": true + }, + "CachePath": { + "type": "string", + "description": "Gets or sets the cache path.", + "nullable": true, + "deprecated": true + }, + "LogPath": { + "type": "string", + "description": "Gets or sets the log path.", + "nullable": true, + "deprecated": true + }, + "InternalMetadataPath": { + "type": "string", + "description": "Gets or sets the internal metadata path.", + "nullable": true, + "deprecated": true + }, + "TranscodingTempPath": { + "type": "string", + "description": "Gets or sets the transcode path.", + "nullable": true, + "deprecated": true + }, + "CastReceiverApplications": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CastReceiverApplication" + }, + "description": "Gets or sets the list of cast receiver applications.", + "nullable": true + }, + "HasUpdateAvailable": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance has update available.", + "default": false, + "deprecated": true + }, + "EncoderLocation": { + "type": "string", + "default": "System", + "nullable": true, + "deprecated": true + }, + "SystemArchitecture": { + "type": "string", + "default": "X64", + "nullable": true, + "deprecated": true + } + }, + "additionalProperties": false, + "description": "Class SystemInfo." + }, + "SystemStorageDto": { + "type": "object", + "properties": { + "ProgramDataFolder": { + "allOf": [ + { + "$ref": "#/components/schemas/FolderStorageDto" + } + ], + "description": "Gets or sets the Storage information of the program data folder." + }, + "WebFolder": { + "allOf": [ + { + "$ref": "#/components/schemas/FolderStorageDto" + } + ], + "description": "Gets or sets the Storage information of the web UI resources folder." + }, + "ImageCacheFolder": { + "allOf": [ + { + "$ref": "#/components/schemas/FolderStorageDto" + } + ], + "description": "Gets or sets the Storage information of the folder where images are cached." + }, + "CacheFolder": { + "allOf": [ + { + "$ref": "#/components/schemas/FolderStorageDto" + } + ], + "description": "Gets or sets the Storage information of the cache folder." + }, + "LogFolder": { + "allOf": [ + { + "$ref": "#/components/schemas/FolderStorageDto" + } + ], + "description": "Gets or sets the Storage information of the folder where logfiles are saved to." + }, + "InternalMetadataFolder": { + "allOf": [ + { + "$ref": "#/components/schemas/FolderStorageDto" + } + ], + "description": "Gets or sets the Storage information of the folder where metadata is stored." + }, + "TranscodingTempFolder": { + "allOf": [ + { + "$ref": "#/components/schemas/FolderStorageDto" + } + ], + "description": "Gets or sets the Storage information of the transcoding cache." + }, + "Libraries": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LibraryStorageDto" + }, + "description": "Gets or sets the storage informations of all libraries." + } + }, + "additionalProperties": false, + "description": "Contains informations about the systems storage." + }, + "TaskCompletionStatus": { + "enum": [ + "Completed", + "Failed", + "Cancelled", + "Aborted" + ], + "type": "string", + "description": "Enum TaskCompletionStatus." + }, + "TaskInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "State": { + "enum": [ + "Idle", + "Cancelling", + "Running" + ], + "allOf": [ + { + "$ref": "#/components/schemas/TaskState" + } + ], + "description": "Gets or sets the state of the task." + }, + "CurrentProgressPercentage": { + "type": "number", + "description": "Gets or sets the progress.", + "format": "double", + "nullable": true + }, + "Id": { + "type": "string", + "description": "Gets or sets the id.", + "nullable": true + }, + "LastExecutionResult": { + "allOf": [ + { + "$ref": "#/components/schemas/TaskResult" + } + ], + "description": "Gets or sets the last execution result.", + "nullable": true + }, + "Triggers": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TaskTriggerInfo" + }, + "description": "Gets or sets the triggers.", + "nullable": true + }, + "Description": { + "type": "string", + "description": "Gets or sets the description.", + "nullable": true + }, + "Category": { + "type": "string", + "description": "Gets or sets the category.", + "nullable": true + }, + "IsHidden": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is hidden." + }, + "Key": { + "type": "string", + "description": "Gets or sets the key.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class TaskInfo." + }, + "TaskResult": { + "type": "object", + "properties": { + "StartTimeUtc": { + "type": "string", + "description": "Gets or sets the start time UTC.", + "format": "date-time" + }, + "EndTimeUtc": { + "type": "string", + "description": "Gets or sets the end time UTC.", + "format": "date-time" + }, + "Status": { + "enum": [ + "Completed", + "Failed", + "Cancelled", + "Aborted" + ], + "allOf": [ + { + "$ref": "#/components/schemas/TaskCompletionStatus" + } + ], + "description": "Gets or sets the status." + }, + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "Key": { + "type": "string", + "description": "Gets or sets the key.", + "nullable": true + }, + "Id": { + "type": "string", + "description": "Gets or sets the id.", + "nullable": true + }, + "ErrorMessage": { + "type": "string", + "description": "Gets or sets the error message.", + "nullable": true + }, + "LongErrorMessage": { + "type": "string", + "description": "Gets or sets the long error message.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class TaskExecutionInfo." + }, + "TaskState": { + "enum": [ + "Idle", + "Cancelling", + "Running" + ], + "type": "string", + "description": "Enum TaskState." + }, + "TaskTriggerInfo": { + "type": "object", + "properties": { + "Type": { + "enum": [ + "DailyTrigger", + "WeeklyTrigger", + "IntervalTrigger", + "StartupTrigger" + ], + "allOf": [ + { + "$ref": "#/components/schemas/TaskTriggerInfoType" + } + ], + "description": "Gets or sets the type." + }, + "TimeOfDayTicks": { + "type": "integer", + "description": "Gets or sets the time of day.", + "format": "int64", + "nullable": true + }, + "IntervalTicks": { + "type": "integer", + "description": "Gets or sets the interval.", + "format": "int64", + "nullable": true + }, + "DayOfWeek": { + "enum": [ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday" + ], + "allOf": [ + { + "$ref": "#/components/schemas/DayOfWeek" + } + ], + "description": "Gets or sets the day of week.", + "nullable": true + }, + "MaxRuntimeTicks": { + "type": "integer", + "description": "Gets or sets the maximum runtime ticks.", + "format": "int64", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class TaskTriggerInfo." + }, + "TaskTriggerInfoType": { + "enum": [ + "DailyTrigger", + "WeeklyTrigger", + "IntervalTrigger", + "StartupTrigger" + ], + "type": "string", + "description": "Enum TaskTriggerInfoType." + }, + "ThemeMediaResult": { + "type": "object", + "properties": { + "Items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/BaseItemDto" + }, + "description": "Gets or sets the items." + }, + "TotalRecordCount": { + "type": "integer", + "description": "Gets or sets the total number of records available.", + "format": "int32" + }, + "StartIndex": { + "type": "integer", + "description": "Gets or sets the index of the first record in Items.", + "format": "int32" + }, + "OwnerId": { + "type": "string", + "description": "Gets or sets the owner id.", + "format": "uuid" + } + }, + "additionalProperties": false, + "description": "Class ThemeMediaResult." + }, + "TimerCancelledMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/TimerEventInfo" + } + ], + "description": "Gets or sets the data.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "TimerCancelled", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Timer cancelled message." + }, + "TimerCreatedMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/TimerEventInfo" + } + ], + "description": "Gets or sets the data.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "TimerCreated", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "Timer created message." + }, + "TimerEventInfo": { + "type": "object", + "properties": { + "Id": { + "type": "string" + }, + "ProgramId": { + "type": "string", + "format": "uuid", + "nullable": true + } + }, + "additionalProperties": false + }, + "TimerInfoDto": { + "type": "object", + "properties": { + "Id": { + "type": "string", + "description": "Gets or sets the Id of the recording.", + "nullable": true + }, + "Type": { + "type": "string", + "nullable": true + }, + "ServerId": { + "type": "string", + "description": "Gets or sets the server identifier.", + "nullable": true + }, + "ExternalId": { + "type": "string", + "description": "Gets or sets the external identifier.", + "nullable": true + }, + "ChannelId": { + "type": "string", + "description": "Gets or sets the channel id of the recording.", + "format": "uuid" + }, + "ExternalChannelId": { + "type": "string", + "description": "Gets or sets the external channel identifier.", + "nullable": true + }, + "ChannelName": { + "type": "string", + "description": "Gets or sets the channel name of the recording.", + "nullable": true + }, + "ChannelPrimaryImageTag": { + "type": "string", + "nullable": true + }, + "ProgramId": { + "type": "string", + "description": "Gets or sets the program identifier.", + "nullable": true + }, + "ExternalProgramId": { + "type": "string", + "description": "Gets or sets the external program identifier.", + "nullable": true + }, + "Name": { + "type": "string", + "description": "Gets or sets the name of the recording.", + "nullable": true + }, + "Overview": { + "type": "string", + "description": "Gets or sets the description of the recording.", + "nullable": true + }, + "StartDate": { + "type": "string", + "description": "Gets or sets the start date of the recording, in UTC.", + "format": "date-time" + }, + "EndDate": { + "type": "string", + "description": "Gets or sets the end date of the recording, in UTC.", + "format": "date-time" + }, + "ServiceName": { + "type": "string", + "description": "Gets or sets the name of the service.", + "nullable": true + }, + "Priority": { + "type": "integer", + "description": "Gets or sets the priority.", + "format": "int32" + }, + "PrePaddingSeconds": { + "type": "integer", + "description": "Gets or sets the pre padding seconds.", + "format": "int32" + }, + "PostPaddingSeconds": { + "type": "integer", + "description": "Gets or sets the post padding seconds.", + "format": "int32" + }, + "IsPrePaddingRequired": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is pre padding required." + }, + "ParentBackdropItemId": { + "type": "string", + "description": "Gets or sets the Id of the Parent that has a backdrop if the item does not have one.", + "nullable": true + }, + "ParentBackdropImageTags": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the parent backdrop image tags.", + "nullable": true + }, + "IsPostPaddingRequired": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is post padding required." + }, + "KeepUntil": { + "enum": [ + "UntilDeleted", + "UntilSpaceNeeded", + "UntilWatched", + "UntilDate" + ], + "allOf": [ + { + "$ref": "#/components/schemas/KeepUntil" + } + ] + }, + "Status": { + "enum": [ + "New", + "InProgress", + "Completed", + "Cancelled", + "ConflictedOk", + "ConflictedNotOk", + "Error" + ], + "allOf": [ + { + "$ref": "#/components/schemas/RecordingStatus" + } + ], + "description": "Gets or sets the status." + }, + "SeriesTimerId": { + "type": "string", + "description": "Gets or sets the series timer identifier.", + "nullable": true + }, + "ExternalSeriesTimerId": { + "type": "string", + "description": "Gets or sets the external series timer identifier.", + "nullable": true + }, + "RunTimeTicks": { + "type": "integer", + "description": "Gets or sets the run time ticks.", + "format": "int64", + "nullable": true + }, + "ProgramInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/BaseItemDto" + } + ], + "description": "Gets or sets the program information.", + "nullable": true + } + }, + "additionalProperties": false + }, + "TimerInfoDtoQueryResult": { + "type": "object", + "properties": { + "Items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/TimerInfoDto" + }, + "description": "Gets or sets the items." + }, + "TotalRecordCount": { + "type": "integer", + "description": "Gets or sets the total number of records available.", + "format": "int32" + }, + "StartIndex": { + "type": "integer", + "description": "Gets or sets the index of the first record in Items.", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "Query result container." + }, + "TonemappingAlgorithm": { + "enum": [ + "none", + "clip", + "linear", + "gamma", + "reinhard", + "hable", + "mobius", + "bt2390" + ], + "type": "string", + "description": "Enum containing tonemapping algorithms." + }, + "TonemappingMode": { + "enum": [ + "auto", + "max", + "rgb", + "lum", + "itp" + ], + "type": "string", + "description": "Enum containing tonemapping modes." + }, + "TonemappingRange": { + "enum": [ + "auto", + "tv", + "pc" + ], + "type": "string", + "description": "Enum containing tonemapping ranges." + }, + "TrailerInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "OriginalTitle": { + "type": "string", + "description": "Gets or sets the original title.", + "nullable": true + }, + "Path": { + "type": "string", + "description": "Gets or sets the path.", + "nullable": true + }, + "MetadataLanguage": { + "type": "string", + "description": "Gets or sets the metadata language.", + "nullable": true + }, + "MetadataCountryCode": { + "type": "string", + "description": "Gets or sets the metadata country code.", + "nullable": true + }, + "ProviderIds": { + "type": "object", + "additionalProperties": { + "type": "string", + "nullable": true + }, + "description": "Gets or sets the provider ids.", + "nullable": true + }, + "Year": { + "type": "integer", + "description": "Gets or sets the year.", + "format": "int32", + "nullable": true + }, + "IndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "ParentIndexNumber": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "PremiereDate": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "IsAutomated": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "TrailerInfoRemoteSearchQuery": { + "type": "object", + "properties": { + "SearchInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/TrailerInfo" + } + ], + "nullable": true + }, + "ItemId": { + "type": "string", + "format": "uuid" + }, + "SearchProviderName": { + "type": "string", + "description": "Gets or sets the provider name to search within if set.", + "nullable": true + }, + "IncludeDisabledProviders": { + "type": "boolean", + "description": "Gets or sets a value indicating whether disabled providers should be included." + } + }, + "additionalProperties": false + }, + "TranscodeReason": { + "enum": [ + "ContainerNotSupported", + "VideoCodecNotSupported", + "AudioCodecNotSupported", + "SubtitleCodecNotSupported", + "AudioIsExternal", + "SecondaryAudioNotSupported", + "VideoProfileNotSupported", + "VideoLevelNotSupported", + "VideoResolutionNotSupported", + "VideoBitDepthNotSupported", + "VideoFramerateNotSupported", + "RefFramesNotSupported", + "AnamorphicVideoNotSupported", + "InterlacedVideoNotSupported", + "AudioChannelsNotSupported", + "AudioProfileNotSupported", + "AudioSampleRateNotSupported", + "AudioBitDepthNotSupported", + "ContainerBitrateExceedsLimit", + "VideoBitrateNotSupported", + "AudioBitrateNotSupported", + "UnknownVideoStreamInfo", + "UnknownAudioStreamInfo", + "DirectPlayError", + "VideoRangeTypeNotSupported", + "VideoCodecTagNotSupported", + "StreamCountExceedsLimit" + ], + "type": "string" + }, + "TranscodeSeekInfo": { + "enum": [ + "Auto", + "Bytes" + ], + "type": "string" + }, + "TranscodingInfo": { + "type": "object", + "properties": { + "AudioCodec": { + "type": "string", + "description": "Gets or sets the thread count used for encoding.", + "nullable": true + }, + "VideoCodec": { + "type": "string", + "description": "Gets or sets the thread count used for encoding.", + "nullable": true + }, + "Container": { + "type": "string", + "description": "Gets or sets the thread count used for encoding.", + "nullable": true + }, + "IsVideoDirect": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the video is passed through." + }, + "IsAudioDirect": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the audio is passed through." + }, + "Bitrate": { + "type": "integer", + "description": "Gets or sets the bitrate.", + "format": "int32", + "nullable": true + }, + "Framerate": { + "type": "number", + "description": "Gets or sets the framerate.", + "format": "float", + "nullable": true + }, + "CompletionPercentage": { + "type": "number", + "description": "Gets or sets the completion percentage.", + "format": "double", + "nullable": true + }, + "Width": { + "type": "integer", + "description": "Gets or sets the video width.", + "format": "int32", + "nullable": true + }, + "Height": { + "type": "integer", + "description": "Gets or sets the video height.", + "format": "int32", + "nullable": true + }, + "AudioChannels": { + "type": "integer", + "description": "Gets or sets the audio channels.", + "format": "int32", + "nullable": true + }, + "HardwareAccelerationType": { + "enum": [ + "none", + "amf", + "qsv", + "nvenc", + "v4l2m2m", + "vaapi", + "videotoolbox", + "rkmpp" + ], + "allOf": [ + { + "$ref": "#/components/schemas/HardwareAccelerationType" + } + ], + "description": "Gets or sets the hardware acceleration type.", + "nullable": true + }, + "TranscodeReasons": { + "enum": [ + "ContainerNotSupported", + "VideoCodecNotSupported", + "AudioCodecNotSupported", + "SubtitleCodecNotSupported", + "AudioIsExternal", + "SecondaryAudioNotSupported", + "VideoProfileNotSupported", + "VideoLevelNotSupported", + "VideoResolutionNotSupported", + "VideoBitDepthNotSupported", + "VideoFramerateNotSupported", + "RefFramesNotSupported", + "AnamorphicVideoNotSupported", + "InterlacedVideoNotSupported", + "AudioChannelsNotSupported", + "AudioProfileNotSupported", + "AudioSampleRateNotSupported", + "AudioBitDepthNotSupported", + "ContainerBitrateExceedsLimit", + "VideoBitrateNotSupported", + "AudioBitrateNotSupported", + "UnknownVideoStreamInfo", + "UnknownAudioStreamInfo", + "DirectPlayError", + "VideoRangeTypeNotSupported", + "VideoCodecTagNotSupported", + "StreamCountExceedsLimit" + ], + "type": "array", + "items": { + "$ref": "#/components/schemas/TranscodeReason" + }, + "description": "Gets or sets the transcode reasons." + } + }, + "additionalProperties": false, + "description": "Class holding information on a running transcode." + }, + "TranscodingProfile": { + "type": "object", + "properties": { + "Container": { + "type": "string", + "description": "Gets or sets the container." + }, + "Type": { + "enum": [ + "Audio", + "Video", + "Photo", + "Subtitle", + "Lyric" + ], + "allOf": [ + { + "$ref": "#/components/schemas/DlnaProfileType" + } + ], + "description": "Gets or sets the DLNA profile type." + }, + "VideoCodec": { + "type": "string", + "description": "Gets or sets the video codec." + }, + "AudioCodec": { + "type": "string", + "description": "Gets or sets the audio codec." + }, + "Protocol": { + "enum": [ + "http", + "hls" + ], + "allOf": [ + { + "$ref": "#/components/schemas/MediaStreamProtocol" + } + ], + "description": "Gets or sets the protocol." + }, + "EstimateContentLength": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the content length should be estimated.", + "default": false + }, + "EnableMpegtsM2TsMode": { + "type": "boolean", + "description": "Gets or sets a value indicating whether M2TS mode is enabled.", + "default": false + }, + "TranscodeSeekInfo": { + "enum": [ + "Auto", + "Bytes" + ], + "allOf": [ + { + "$ref": "#/components/schemas/TranscodeSeekInfo" + } + ], + "description": "Gets or sets the transcoding seek info mode.", + "default": "Auto" + }, + "CopyTimestamps": { + "type": "boolean", + "description": "Gets or sets a value indicating whether timestamps should be copied.", + "default": false + }, + "Context": { + "enum": [ + "Streaming", + "Static" + ], + "allOf": [ + { + "$ref": "#/components/schemas/EncodingContext" + } + ], + "description": "Gets or sets the encoding context.", + "default": "Streaming" + }, + "EnableSubtitlesInManifest": { + "type": "boolean", + "description": "Gets or sets a value indicating whether subtitles are allowed in the manifest.", + "default": false + }, + "MaxAudioChannels": { + "type": "string", + "description": "Gets or sets the maximum audio channels.", + "nullable": true + }, + "MinSegments": { + "type": "integer", + "description": "Gets or sets the minimum amount of segments.", + "format": "int32", + "default": 0 + }, + "SegmentLength": { + "type": "integer", + "description": "Gets or sets the segment length.", + "format": "int32", + "default": 0 + }, + "BreakOnNonKeyFrames": { + "type": "boolean", + "description": "Gets or sets a value indicating whether breaking the video stream on non-keyframes is supported.", + "default": false + }, + "Conditions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ProfileCondition" + }, + "description": "Gets or sets the profile conditions." + }, + "EnableAudioVbrEncoding": { + "type": "boolean", + "description": "Gets or sets a value indicating whether variable bitrate encoding is supported.", + "default": true + } + }, + "additionalProperties": false, + "description": "A class for transcoding profile information.\r\nNote for client developers: Conditions defined in MediaBrowser.Model.Dlna.CodecProfile has higher priority and can override values defined here." + }, + "TransportStreamTimestamp": { + "enum": [ + "None", + "Zero", + "Valid" + ], + "type": "string" + }, + "TrickplayInfoDto": { + "type": "object", + "properties": { + "Width": { + "type": "integer", + "description": "Gets the width of an individual thumbnail.", + "format": "int32" + }, + "Height": { + "type": "integer", + "description": "Gets the height of an individual thumbnail.", + "format": "int32" + }, + "TileWidth": { + "type": "integer", + "description": "Gets the amount of thumbnails per row.", + "format": "int32" + }, + "TileHeight": { + "type": "integer", + "description": "Gets the amount of thumbnails per column.", + "format": "int32" + }, + "ThumbnailCount": { + "type": "integer", + "description": "Gets the total amount of non-black thumbnails.", + "format": "int32" + }, + "Interval": { + "type": "integer", + "description": "Gets the interval in milliseconds between each trickplay thumbnail.", + "format": "int32" + }, + "Bandwidth": { + "type": "integer", + "description": "Gets the peak bandwidth usage in bits per second.", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "The trickplay api model." + }, + "TrickplayOptions": { + "type": "object", + "properties": { + "EnableHwAcceleration": { + "type": "boolean", + "description": "Gets or sets a value indicating whether or not to use HW acceleration." + }, + "EnableHwEncoding": { + "type": "boolean", + "description": "Gets or sets a value indicating whether or not to use HW accelerated MJPEG encoding." + }, + "EnableKeyFrameOnlyExtraction": { + "type": "boolean", + "description": "Gets or sets a value indicating whether to only extract key frames.\r\nSignificantly faster, but is not compatible with all decoders and/or video files." + }, + "ScanBehavior": { + "enum": [ + "Blocking", + "NonBlocking" + ], + "allOf": [ + { + "$ref": "#/components/schemas/TrickplayScanBehavior" + } + ], + "description": "Gets or sets the behavior used by trickplay provider on library scan/update." + }, + "ProcessPriority": { + "enum": [ + "Normal", + "Idle", + "High", + "RealTime", + "BelowNormal", + "AboveNormal" + ], + "allOf": [ + { + "$ref": "#/components/schemas/ProcessPriorityClass" + } + ], + "description": "Gets or sets the process priority for the ffmpeg process." + }, + "Interval": { + "type": "integer", + "description": "Gets or sets the interval, in ms, between each new trickplay image.", + "format": "int32" + }, + "WidthResolutions": { + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "description": "Gets or sets the target width resolutions, in px, to generates preview images for." + }, + "TileWidth": { + "type": "integer", + "description": "Gets or sets number of tile images to allow in X dimension.", + "format": "int32" + }, + "TileHeight": { + "type": "integer", + "description": "Gets or sets number of tile images to allow in Y dimension.", + "format": "int32" + }, + "Qscale": { + "type": "integer", + "description": "Gets or sets the ffmpeg output quality level.", + "format": "int32" + }, + "JpegQuality": { + "type": "integer", + "description": "Gets or sets the jpeg quality to use for image tiles.", + "format": "int32" + }, + "ProcessThreads": { + "type": "integer", + "description": "Gets or sets the number of threads to be used by ffmpeg.", + "format": "int32" + } + }, + "additionalProperties": false, + "description": "Class TrickplayOptions." + }, + "TrickplayScanBehavior": { + "enum": [ + "Blocking", + "NonBlocking" + ], + "type": "string", + "description": "Enum TrickplayScanBehavior." + }, + "TunerChannelMapping": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "nullable": true + }, + "ProviderChannelName": { + "type": "string", + "nullable": true + }, + "ProviderChannelId": { + "type": "string", + "nullable": true + }, + "Id": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false + }, + "TunerHostInfo": { + "type": "object", + "properties": { + "Id": { + "type": "string", + "nullable": true + }, + "Url": { + "type": "string", + "nullable": true + }, + "Type": { + "type": "string", + "nullable": true + }, + "DeviceId": { + "type": "string", + "nullable": true + }, + "FriendlyName": { + "type": "string", + "nullable": true + }, + "ImportFavoritesOnly": { + "type": "boolean" + }, + "AllowHWTranscoding": { + "type": "boolean" + }, + "AllowFmp4TranscodingContainer": { + "type": "boolean" + }, + "AllowStreamSharing": { + "type": "boolean" + }, + "FallbackMaxStreamingBitrate": { + "type": "integer", + "format": "int32" + }, + "EnableStreamLooping": { + "type": "boolean" + }, + "Source": { + "type": "string", + "nullable": true + }, + "TunerCount": { + "type": "integer", + "format": "int32" + }, + "UserAgent": { + "type": "string", + "nullable": true + }, + "IgnoreDts": { + "type": "boolean" + }, + "ReadAtNativeFramerate": { + "type": "boolean" + } + }, + "additionalProperties": false + }, + "TypeOptions": { + "type": "object", + "properties": { + "Type": { + "type": "string", + "nullable": true + }, + "MetadataFetchers": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "MetadataFetcherOrder": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "ImageFetchers": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "ImageFetcherOrder": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "ImageOptions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ImageOption" + }, + "nullable": true + } + }, + "additionalProperties": false + }, + "UnratedItem": { + "enum": [ + "Movie", + "Trailer", + "Series", + "Music", + "Book", + "LiveTvChannel", + "LiveTvProgram", + "ChannelContent", + "Other" + ], + "type": "string", + "description": "An enum representing an unrated item." + }, + "UpdateLibraryOptionsDto": { + "type": "object", + "properties": { + "Id": { + "type": "string", + "description": "Gets or sets the library item id.", + "format": "uuid" + }, + "LibraryOptions": { + "allOf": [ + { + "$ref": "#/components/schemas/LibraryOptions" + } + ], + "description": "Gets or sets library options.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Update library options dto." + }, + "UpdateMediaPathRequestDto": { + "required": [ + "Name", + "PathInfo" + ], + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the library name." + }, + "PathInfo": { + "allOf": [ + { + "$ref": "#/components/schemas/MediaPathInfo" + } + ], + "description": "Gets or sets library folder path information." + } + }, + "additionalProperties": false, + "description": "Update library options dto." + }, + "UpdatePlaylistDto": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name of the new playlist.", + "nullable": true + }, + "Ids": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "description": "Gets or sets item ids of the playlist.", + "nullable": true + }, + "Users": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PlaylistUserPermissions" + }, + "description": "Gets or sets the playlist users.", + "nullable": true + }, + "IsPublic": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the playlist is public.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Update existing playlist dto. Fields set to `null` will not be updated and keep their current values." + }, + "UpdatePlaylistUserDto": { + "type": "object", + "properties": { + "CanEdit": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the user can edit the playlist.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Update existing playlist user dto. Fields set to `null` will not be updated and keep their current values." + }, + "UpdateUserItemDataDto": { + "type": "object", + "properties": { + "Rating": { + "type": "number", + "description": "Gets or sets the rating.", + "format": "double", + "nullable": true + }, + "PlayedPercentage": { + "type": "number", + "description": "Gets or sets the played percentage.", + "format": "double", + "nullable": true + }, + "UnplayedItemCount": { + "type": "integer", + "description": "Gets or sets the unplayed item count.", + "format": "int32", + "nullable": true + }, + "PlaybackPositionTicks": { + "type": "integer", + "description": "Gets or sets the playback position ticks.", + "format": "int64", + "nullable": true + }, + "PlayCount": { + "type": "integer", + "description": "Gets or sets the play count.", + "format": "int32", + "nullable": true + }, + "IsFavorite": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is favorite.", + "nullable": true + }, + "Likes": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this MediaBrowser.Model.Dto.UpdateUserItemDataDto is likes.", + "nullable": true + }, + "LastPlayedDate": { + "type": "string", + "description": "Gets or sets the last played date.", + "format": "date-time", + "nullable": true + }, + "Played": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this MediaBrowser.Model.Dto.UserItemDataDto is played.", + "nullable": true + }, + "Key": { + "type": "string", + "description": "Gets or sets the key.", + "nullable": true + }, + "ItemId": { + "type": "string", + "description": "Gets or sets the item identifier.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "This is used by the api to get information about a item user data." + }, + "UpdateUserPassword": { + "type": "object", + "properties": { + "CurrentPassword": { + "type": "string", + "description": "Gets or sets the current sha1-hashed password.", + "nullable": true + }, + "CurrentPw": { + "type": "string", + "description": "Gets or sets the current plain text password.", + "nullable": true + }, + "NewPw": { + "type": "string", + "description": "Gets or sets the new plain text password.", + "nullable": true + }, + "ResetPassword": { + "type": "boolean", + "description": "Gets or sets a value indicating whether to reset the password." + } + }, + "additionalProperties": false, + "description": "The update user password request body." + }, + "UploadSubtitleDto": { + "required": [ + "Data", + "Format", + "IsForced", + "IsHearingImpaired", + "Language" + ], + "type": "object", + "properties": { + "Language": { + "type": "string", + "description": "Gets or sets the subtitle language." + }, + "Format": { + "type": "string", + "description": "Gets or sets the subtitle format." + }, + "IsForced": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the subtitle is forced." + }, + "IsHearingImpaired": { + "type": "boolean", + "description": "Gets or sets a value indicating whether the subtitle is for hearing impaired." + }, + "Data": { + "type": "string", + "description": "Gets or sets the subtitle data." + } + }, + "additionalProperties": false, + "description": "Upload subtitles dto." + }, + "UserConfiguration": { + "type": "object", + "properties": { + "AudioLanguagePreference": { + "type": "string", + "description": "Gets or sets the audio language preference.", + "nullable": true + }, + "PlayDefaultAudioTrack": { + "type": "boolean", + "description": "Gets or sets a value indicating whether [play default audio track]." + }, + "SubtitleLanguagePreference": { + "type": "string", + "description": "Gets or sets the subtitle language preference.", + "nullable": true + }, + "DisplayMissingEpisodes": { + "type": "boolean" + }, + "GroupedFolders": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "SubtitleMode": { + "enum": [ + "Default", + "Always", + "OnlyForced", + "None", + "Smart" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SubtitlePlaybackMode" + } + ], + "description": "An enum representing a subtitle playback mode." + }, + "DisplayCollectionsView": { + "type": "boolean" + }, + "EnableLocalPassword": { + "type": "boolean" + }, + "OrderedViews": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "LatestItemsExcludes": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "MyMediaExcludes": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + } + }, + "HidePlayedInLatest": { + "type": "boolean" + }, + "RememberAudioSelections": { + "type": "boolean" + }, + "RememberSubtitleSelections": { + "type": "boolean" + }, + "EnableNextEpisodeAutoPlay": { + "type": "boolean" + }, + "CastReceiverId": { + "type": "string", + "description": "Gets or sets the id of the selected cast receiver.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class UserConfiguration." + }, + "UserDataChangedMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/UserDataChangeInfo" + } + ], + "description": "Class UserDataChangeInfo.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "UserDataChanged", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "User data changed message." + }, + "UserDataChangeInfo": { + "type": "object", + "properties": { + "UserId": { + "type": "string", + "description": "Gets or sets the user id.", + "format": "uuid" + }, + "UserDataList": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UserItemDataDto" + }, + "description": "Gets or sets the user data list." + } + }, + "additionalProperties": false, + "description": "Class UserDataChangeInfo." + }, + "UserDeletedMessage": { + "type": "object", + "properties": { + "Data": { + "type": "string", + "description": "Gets or sets the data.", + "format": "uuid" + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "UserDeleted", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "User deleted message." + }, + "UserDto": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "ServerId": { + "type": "string", + "description": "Gets or sets the server identifier.", + "nullable": true + }, + "ServerName": { + "type": "string", + "description": "Gets or sets the name of the server.\r\nThis is not used by the server and is for client-side usage only.", + "nullable": true + }, + "Id": { + "type": "string", + "description": "Gets or sets the id.", + "format": "uuid" + }, + "PrimaryImageTag": { + "type": "string", + "description": "Gets or sets the primary image tag.", + "nullable": true + }, + "HasPassword": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance has password." + }, + "HasConfiguredPassword": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance has configured password." + }, + "HasConfiguredEasyPassword": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance has configured easy password.", + "deprecated": true + }, + "EnableAutoLogin": { + "type": "boolean", + "description": "Gets or sets whether async login is enabled or not.", + "nullable": true + }, + "LastLoginDate": { + "type": "string", + "description": "Gets or sets the last login date.", + "format": "date-time", + "nullable": true + }, + "LastActivityDate": { + "type": "string", + "description": "Gets or sets the last activity date.", + "format": "date-time", + "nullable": true + }, + "Configuration": { + "allOf": [ + { + "$ref": "#/components/schemas/UserConfiguration" + } + ], + "description": "Gets or sets the configuration.", + "nullable": true + }, + "Policy": { + "allOf": [ + { + "$ref": "#/components/schemas/UserPolicy" + } + ], + "description": "Gets or sets the policy.", + "nullable": true + }, + "PrimaryImageAspectRatio": { + "type": "number", + "description": "Gets or sets the primary image aspect ratio.", + "format": "double", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Class UserDto." + }, + "UserItemDataDto": { + "type": "object", + "properties": { + "Rating": { + "type": "number", + "description": "Gets or sets the rating.", + "format": "double", + "nullable": true + }, + "PlayedPercentage": { + "type": "number", + "description": "Gets or sets the played percentage.", + "format": "double", + "nullable": true + }, + "UnplayedItemCount": { + "type": "integer", + "description": "Gets or sets the unplayed item count.", + "format": "int32", + "nullable": true + }, + "PlaybackPositionTicks": { + "type": "integer", + "description": "Gets or sets the playback position ticks.", + "format": "int64" + }, + "PlayCount": { + "type": "integer", + "description": "Gets or sets the play count.", + "format": "int32" + }, + "IsFavorite": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is favorite." + }, + "Likes": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this MediaBrowser.Model.Dto.UserItemDataDto is likes.", + "nullable": true + }, + "LastPlayedDate": { + "type": "string", + "description": "Gets or sets the last played date.", + "format": "date-time", + "nullable": true + }, + "Played": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this MediaBrowser.Model.Dto.UserItemDataDto is played." + }, + "Key": { + "type": "string", + "description": "Gets or sets the key." + }, + "ItemId": { + "type": "string", + "description": "Gets or sets the item identifier.", + "format": "uuid" + } + }, + "additionalProperties": false, + "description": "Class UserItemDataDto." + }, + "UserPolicy": { + "required": [ + "AuthenticationProviderId", + "PasswordResetProviderId" + ], + "type": "object", + "properties": { + "IsAdministrator": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is administrator." + }, + "IsHidden": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is hidden." + }, + "EnableCollectionManagement": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance can manage collections.", + "default": false + }, + "EnableSubtitleManagement": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance can manage subtitles.", + "default": false + }, + "EnableLyricManagement": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this user can manage lyrics.", + "default": false + }, + "IsDisabled": { + "type": "boolean", + "description": "Gets or sets a value indicating whether this instance is disabled." + }, + "MaxParentalRating": { + "type": "integer", + "description": "Gets or sets the max parental rating.", + "format": "int32", + "nullable": true + }, + "MaxParentalSubRating": { + "type": "integer", + "format": "int32", + "nullable": true + }, + "BlockedTags": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "AllowedTags": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "EnableUserPreferenceAccess": { + "type": "boolean" + }, + "AccessSchedules": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AccessSchedule" + }, + "nullable": true + }, + "BlockUnratedItems": { + "type": "array", + "items": { + "$ref": "#/components/schemas/UnratedItem" + }, + "nullable": true + }, + "EnableRemoteControlOfOtherUsers": { + "type": "boolean" + }, + "EnableSharedDeviceControl": { + "type": "boolean" + }, + "EnableRemoteAccess": { + "type": "boolean" + }, + "EnableLiveTvManagement": { + "type": "boolean" + }, + "EnableLiveTvAccess": { + "type": "boolean" + }, + "EnableMediaPlayback": { + "type": "boolean" + }, + "EnableAudioPlaybackTranscoding": { + "type": "boolean" + }, + "EnableVideoPlaybackTranscoding": { + "type": "boolean" + }, + "EnablePlaybackRemuxing": { + "type": "boolean" + }, + "ForceRemoteSourceTranscoding": { + "type": "boolean" + }, + "EnableContentDeletion": { + "type": "boolean" + }, + "EnableContentDeletionFromFolders": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "EnableContentDownloading": { + "type": "boolean" + }, + "EnableSyncTranscoding": { + "type": "boolean", + "description": "Gets or sets a value indicating whether [enable synchronize]." + }, + "EnableMediaConversion": { + "type": "boolean" + }, + "EnabledDevices": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "EnableAllDevices": { + "type": "boolean" + }, + "EnabledChannels": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "nullable": true + }, + "EnableAllChannels": { + "type": "boolean" + }, + "EnabledFolders": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "nullable": true + }, + "EnableAllFolders": { + "type": "boolean" + }, + "InvalidLoginAttemptCount": { + "type": "integer", + "format": "int32" + }, + "LoginAttemptsBeforeLockout": { + "type": "integer", + "format": "int32" + }, + "MaxActiveSessions": { + "type": "integer", + "format": "int32" + }, + "EnablePublicSharing": { + "type": "boolean" + }, + "BlockedMediaFolders": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "nullable": true + }, + "BlockedChannels": { + "type": "array", + "items": { + "type": "string", + "format": "uuid" + }, + "nullable": true + }, + "RemoteClientBitrateLimit": { + "type": "integer", + "format": "int32" + }, + "AuthenticationProviderId": { + "type": "string" + }, + "PasswordResetProviderId": { + "type": "string" + }, + "SyncPlayAccess": { + "enum": [ + "CreateAndJoinGroups", + "JoinGroups", + "None" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SyncPlayUserAccessType" + } + ], + "description": "Gets or sets a value indicating what SyncPlay features the user can access." + } + }, + "additionalProperties": false + }, + "UserUpdatedMessage": { + "type": "object", + "properties": { + "Data": { + "allOf": [ + { + "$ref": "#/components/schemas/UserDto" + } + ], + "description": "Class UserDto.", + "nullable": true + }, + "MessageId": { + "type": "string", + "description": "Gets or sets the message id.", + "format": "uuid" + }, + "MessageType": { + "enum": [ + "ForceKeepAlive", + "GeneralCommand", + "UserDataChanged", + "Sessions", + "Play", + "SyncPlayCommand", + "SyncPlayGroupUpdate", + "Playstate", + "RestartRequired", + "ServerShuttingDown", + "ServerRestarting", + "LibraryChanged", + "UserDeleted", + "UserUpdated", + "SeriesTimerCreated", + "TimerCreated", + "SeriesTimerCancelled", + "TimerCancelled", + "RefreshProgress", + "ScheduledTaskEnded", + "PackageInstallationCancelled", + "PackageInstallationFailed", + "PackageInstallationCompleted", + "PackageInstalling", + "PackageUninstalled", + "ActivityLogEntry", + "ScheduledTasksInfo", + "ActivityLogEntryStart", + "ActivityLogEntryStop", + "SessionsStart", + "SessionsStop", + "ScheduledTasksInfoStart", + "ScheduledTasksInfoStop", + "KeepAlive" + ], + "allOf": [ + { + "$ref": "#/components/schemas/SessionMessageType" + } + ], + "description": "The different kinds of messages that are used in the WebSocket api.", + "default": "UserUpdated", + "readOnly": true + } + }, + "additionalProperties": false, + "description": "User updated message." + }, + "UtcTimeResponse": { + "type": "object", + "properties": { + "RequestReceptionTime": { + "type": "string", + "description": "Gets the UTC time when request has been received.", + "format": "date-time" + }, + "ResponseTransmissionTime": { + "type": "string", + "description": "Gets the UTC time when response has been sent.", + "format": "date-time" + } + }, + "additionalProperties": false, + "description": "Class UtcTimeResponse." + }, + "ValidatePathDto": { + "type": "object", + "properties": { + "ValidateWritable": { + "type": "boolean", + "description": "Gets or sets a value indicating whether validate if path is writable." + }, + "Path": { + "type": "string", + "description": "Gets or sets the path.", + "nullable": true + }, + "IsFile": { + "type": "boolean", + "description": "Gets or sets is path file.", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Validate path object." + }, + "VersionInfo": { + "type": "object", + "properties": { + "version": { + "type": "string", + "description": "Gets or sets the version." + }, + "VersionNumber": { + "type": "string", + "description": "Gets the version as a System.Version.", + "readOnly": true + }, + "changelog": { + "type": "string", + "description": "Gets or sets the changelog for this version.", + "nullable": true + }, + "targetAbi": { + "type": "string", + "description": "Gets or sets the ABI that this version was built against.", + "nullable": true + }, + "sourceUrl": { + "type": "string", + "description": "Gets or sets the source URL.", + "nullable": true + }, + "checksum": { + "type": "string", + "description": "Gets or sets a checksum for the binary.", + "nullable": true + }, + "timestamp": { + "type": "string", + "description": "Gets or sets a timestamp of when the binary was built.", + "nullable": true + }, + "repositoryName": { + "type": "string", + "description": "Gets or sets the repository name." + }, + "repositoryUrl": { + "type": "string", + "description": "Gets or sets the repository url." + } + }, + "additionalProperties": false, + "description": "Defines the MediaBrowser.Model.Updates.VersionInfo class." + }, + "Video3DFormat": { + "enum": [ + "HalfSideBySide", + "FullSideBySide", + "FullTopAndBottom", + "HalfTopAndBottom", + "MVC" + ], + "type": "string" + }, + "VideoRange": { + "enum": [ + "Unknown", + "SDR", + "HDR" + ], + "type": "string", + "description": "An enum representing video ranges." + }, + "VideoRangeType": { + "enum": [ + "Unknown", + "SDR", + "HDR10", + "HLG", + "DOVI", + "DOVIWithHDR10", + "DOVIWithHLG", + "DOVIWithSDR", + "DOVIWithEL", + "DOVIWithHDR10Plus", + "DOVIWithELHDR10Plus", + "DOVIInvalid", + "HDR10Plus" + ], + "type": "string", + "description": "An enum representing types of video ranges." + }, + "VideoType": { + "enum": [ + "VideoFile", + "Iso", + "Dvd", + "BluRay" + ], + "type": "string", + "description": "Enum VideoType." + }, + "VirtualFolderInfo": { + "type": "object", + "properties": { + "Name": { + "type": "string", + "description": "Gets or sets the name.", + "nullable": true + }, + "Locations": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Gets or sets the locations.", + "nullable": true + }, + "CollectionType": { + "enum": [ + "movies", + "tvshows", + "music", + "musicvideos", + "homevideos", + "boxsets", + "books", + "mixed" + ], + "allOf": [ + { + "$ref": "#/components/schemas/CollectionTypeOptions" + } + ], + "description": "Gets or sets the type of the collection.", + "nullable": true + }, + "LibraryOptions": { + "allOf": [ + { + "$ref": "#/components/schemas/LibraryOptions" + } + ], + "nullable": true + }, + "ItemId": { + "type": "string", + "description": "Gets or sets the item identifier.", + "nullable": true + }, + "PrimaryImageItemId": { + "type": "string", + "description": "Gets or sets the primary image item identifier.", + "nullable": true + }, + "RefreshProgress": { + "type": "number", + "format": "double", + "nullable": true + }, + "RefreshStatus": { + "type": "string", + "nullable": true + } + }, + "additionalProperties": false, + "description": "Used to hold information about a user's list of configured virtual folders." + }, + "WebSocketMessage": { + "type": "object", + "oneOf": [ + { + "$ref": "#/components/schemas/InboundWebSocketMessage" + }, + { + "$ref": "#/components/schemas/OutboundWebSocketMessage" + } + ], + "description": "Represents the possible websocket types" + }, + "XbmcMetadataOptions": { + "type": "object", + "properties": { + "UserId": { + "type": "string", + "nullable": true + }, + "ReleaseDateFormat": { + "type": "string" + }, + "SaveImagePathsInNfo": { + "type": "boolean" + }, + "EnablePathSubstitution": { + "type": "boolean" + }, + "EnableExtraThumbsDuplication": { + "type": "boolean" + } + }, + "additionalProperties": false + } + }, + "securitySchemes": { + "CustomAuthentication": { + "type": "apiKey", + "description": "API key header parameter", + "name": "Authorization", + "in": "header" + } + } + } +} \ No newline at end of file diff --git a/docs/ks-player/GettingStarted.md b/docs/ks-player/GettingStarted.md new file mode 100644 index 00000000..d5ec51cf --- /dev/null +++ b/docs/ks-player/GettingStarted.md @@ -0,0 +1,157 @@ +# Getting Started with KSPlayer + +KSPlayer is a powerful media playback framework for iOS, tvOS, macOS, xrOS, and visionOS. It supports both AVPlayer and FFmpeg-based playback with AppKit/UIKit/SwiftUI support. + +## Requirements + +- iOS 13+ +- macOS 10.15+ +- tvOS 13+ +- xrOS 1+ + +## Troubleshooting + +### Missing Metal Toolchain (CocoaPods builds) + +If your build fails compiling `Shaders.metal` with: + +`cannot execute tool 'metal' due to missing Metal Toolchain` + +Install the component: + +```bash +xcodebuild -downloadComponent MetalToolchain +``` + +Then verify: + +```bash +xcrun --find metal +xcrun metal -v +``` + +## Installation + +### Swift Package Manager + +Add KSPlayer to your `Package.swift`: + +```swift +dependencies: [ + .package(url: "https://github.com/kingslay/KSPlayer.git", .branch("main")) +] +``` + +Or in Xcode: File → Add Packages → Enter the repository URL. + +### CocoaPods + +Add to your `Podfile`: + +```ruby +target 'YourApp' do + use_frameworks! + pod 'KSPlayer', :git => 'https://github.com/kingslay/KSPlayer.git', :branch => 'main' + pod 'DisplayCriteria', :git => 'https://github.com/kingslay/KSPlayer.git', :branch => 'main' + pod 'FFmpegKit', :git => 'https://github.com/kingslay/FFmpegKit.git', :branch => 'main' + pod 'Libass', :git => 'https://github.com/kingslay/FFmpegKit.git', :branch => 'main' +end +``` + +Then run: + +```bash +pod install +``` + +## Initial Setup + +### Configure Player Type + +KSPlayer supports two player backends: +- `KSAVPlayer` - Uses AVPlayer (default first player) +- `KSMEPlayer` - Uses FFmpeg for decoding + +Configure the player type before creating any player views: + +```swift +import KSPlayer + +// Use KSMEPlayer as the secondary/fallback player +KSOptions.secondPlayerType = KSMEPlayer.self + +// Or set KSMEPlayer as the primary player +KSOptions.firstPlayerType = KSMEPlayer.self +``` + +### Player Type Selection Strategy + +The player uses `firstPlayerType` initially. If playback fails, it automatically switches to `secondPlayerType`. + +```swift +// Default configuration +KSOptions.firstPlayerType = KSAVPlayer.self // Uses AVPlayer first +KSOptions.secondPlayerType = KSMEPlayer.self // Falls back to FFmpeg +``` + +## Quick Start + +### UIKit + +```swift +import KSPlayer + +class VideoViewController: UIViewController { + private var playerView: IOSVideoPlayerView! + + override func viewDidLoad() { + super.viewDidLoad() + + KSOptions.secondPlayerType = KSMEPlayer.self + + playerView = IOSVideoPlayerView() + view.addSubview(playerView) + + playerView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + playerView.topAnchor.constraint(equalTo: view.topAnchor), + playerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + playerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + playerView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + + let url = URL(string: "https://example.com/video.mp4")! + playerView.set(url: url, options: KSOptions()) + } +} +``` + +### SwiftUI (iOS 16+) + +```swift +import KSPlayer +import SwiftUI + +struct VideoPlayerScreen: View { + let url: URL + + var body: some View { + KSVideoPlayerView(url: url, options: KSOptions()) + } +} +``` + +## Key Imports + +```swift +import KSPlayer +import AVFoundation // For AVMediaType, etc. +``` + +## Next Steps + +- [UIKit Usage](UIKitUsage.md) - Detailed UIKit integration +- [SwiftUI Usage](SwiftUIUsage.md) - SwiftUI views and modifiers +- [KSOptions](KSOptions.md) - Configuration options +- [Types and Protocols](TypesAndProtocols.md) - Core types reference + diff --git a/docs/ks-player/KSOptions.md b/docs/ks-player/KSOptions.md new file mode 100644 index 00000000..a7370de8 --- /dev/null +++ b/docs/ks-player/KSOptions.md @@ -0,0 +1,349 @@ +# KSOptions + +`KSOptions` is the configuration class for KSPlayer. It contains both instance properties (per-player settings) and static properties (global defaults). + +## Creating Options + +```swift +let options = KSOptions() + +// Configure instance properties +options.isLoopPlay = true +options.startPlayTime = 30.0 // Start at 30 seconds + +// Use with player +playerView.set(url: url, options: options) +``` + +## Instance Properties + +### Buffering + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `preferredForwardBufferDuration` | `TimeInterval` | `3.0` | Minimum buffer duration before playback starts | +| `maxBufferDuration` | `TimeInterval` | `30.0` | Maximum buffer duration | +| `isSecondOpen` | `Bool` | `false` | Enable fast open (instant playback) | + +### Seeking + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `isAccurateSeek` | `Bool` | `false` | Enable frame-accurate seeking | +| `seekFlags` | `Int32` | `1` | FFmpeg seek flags (AVSEEK_FLAG_BACKWARD) | +| `isSeekedAutoPlay` | `Bool` | `true` | Auto-play after seeking | + +### Playback + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `isLoopPlay` | `Bool` | `false` | Loop playback (for short videos) | +| `startPlayTime` | `TimeInterval` | `0` | Initial playback position (seconds) | +| `startPlayRate` | `Float` | `1.0` | Initial playback rate | +| `registerRemoteControll` | `Bool` | `true` | Enable system remote control | + +### Video + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `display` | `DisplayEnum` | `.plane` | Display mode (`.plane`, `.vr`, `.vrBox`) | +| `videoDelay` | `Double` | `0.0` | Video delay in seconds | +| `autoDeInterlace` | `Bool` | `false` | Auto-detect interlacing | +| `autoRotate` | `Bool` | `true` | Auto-rotate based on metadata | +| `destinationDynamicRange` | `DynamicRange?` | `nil` | Target HDR mode | +| `videoAdaptable` | `Bool` | `true` | Enable adaptive bitrate | +| `videoFilters` | `[String]` | `[]` | FFmpeg video filters | +| `syncDecodeVideo` | `Bool` | `false` | Synchronous video decoding | +| `hardwareDecode` | `Bool` | `true` | Use hardware decoding | +| `asynchronousDecompression` | `Bool` | `false` | Async hardware decompression | +| `videoDisable` | `Bool` | `false` | Disable video track | + +### Audio + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `audioFilters` | `[String]` | `[]` | FFmpeg audio filters | +| `syncDecodeAudio` | `Bool` | `false` | Synchronous audio decoding | + +### Subtitles + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `autoSelectEmbedSubtitle` | `Bool` | `true` | Auto-select embedded subtitles | +| `isSeekImageSubtitle` | `Bool` | `false` | Seek for image subtitles | + +### Picture-in-Picture + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `canStartPictureInPictureAutomaticallyFromInline` | `Bool` | `true` | Auto-start PiP when app backgrounds | + +### Window (macOS) + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `automaticWindowResize` | `Bool` | `true` | Auto-resize window to video aspect ratio | + +### Network/HTTP + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `referer` | `String?` | `nil` | HTTP referer header | +| `userAgent` | `String?` | `"KSPlayer"` | HTTP user agent | +| `cache` | `Bool` | `false` | Enable FFmpeg HTTP caching | +| `outputURL` | `URL?` | `nil` | URL to record/save stream | + +### FFmpeg Options + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `avOptions` | `[String: Any]` | `[:]` | AVURLAsset options | +| `formatContextOptions` | `[String: Any]` | See below | FFmpeg format context options | +| `decoderOptions` | `[String: Any]` | See below | FFmpeg decoder options | +| `probesize` | `Int64?` | `nil` | FFmpeg probe size | +| `maxAnalyzeDuration` | `Int64?` | `nil` | Max analyze duration | +| `lowres` | `UInt8` | `0` | Low resolution decoding | +| `nobuffer` | `Bool` | `false` | Disable buffering | +| `codecLowDelay` | `Bool` | `false` | Low delay codec mode | + +#### Default formatContextOptions + +```swift +[ + "user_agent": "KSPlayer", + "scan_all_pmts": 1, + "reconnect": 1, + "reconnect_streamed": 1 +] +``` + +#### Default decoderOptions + +```swift +[ + "threads": "auto", + "refcounted_frames": "1" +] +``` + +### Read-Only Timing Properties + +| Property | Type | Description | +|----------|------|-------------| +| `formatName` | `String` | Detected format name | +| `prepareTime` | `Double` | Time when prepare started | +| `dnsStartTime` | `Double` | DNS lookup start time | +| `tcpStartTime` | `Double` | TCP connection start time | +| `tcpConnectedTime` | `Double` | TCP connected time | +| `openTime` | `Double` | File open time | +| `findTime` | `Double` | Stream find time | +| `readyTime` | `Double` | Ready to play time | +| `readAudioTime` | `Double` | First audio read time | +| `readVideoTime` | `Double` | First video read time | +| `decodeAudioTime` | `Double` | First audio decode time | +| `decodeVideoTime` | `Double` | First video decode time | + +## Static Properties (Global Defaults) + +### Player Types + +```swift +// Primary player type (default: AVPlayer) +KSOptions.firstPlayerType: MediaPlayerProtocol.Type = KSAVPlayer.self + +// Fallback player type (default: FFmpeg) +KSOptions.secondPlayerType: MediaPlayerProtocol.Type? = KSMEPlayer.self +``` + +### Buffering Defaults + +```swift +KSOptions.preferredForwardBufferDuration: TimeInterval = 3.0 +KSOptions.maxBufferDuration: TimeInterval = 30.0 +KSOptions.isSecondOpen: Bool = false +``` + +### Playback Defaults + +```swift +KSOptions.isAccurateSeek: Bool = false +KSOptions.isLoopPlay: Bool = false +KSOptions.isAutoPlay: Bool = true +KSOptions.isSeekedAutoPlay: Bool = true +``` + +### Decoding + +```swift +KSOptions.hardwareDecode: Bool = true +KSOptions.asynchronousDecompression: Bool = false +KSOptions.canStartPictureInPictureAutomaticallyFromInline: Bool = true +``` + +### UI Options + +```swift +// Top bar visibility: .always, .horizantalOnly, .none +KSOptions.topBarShowInCase: KSPlayerTopBarShowCase = .always + +// Auto-hide controls delay +KSOptions.animateDelayTimeInterval: TimeInterval = 5.0 + +// Gesture controls +KSOptions.enableBrightnessGestures: Bool = true +KSOptions.enableVolumeGestures: Bool = true +KSOptions.enablePlaytimeGestures: Bool = true + +// Background playback +KSOptions.canBackgroundPlay: Bool = false +``` + +### PiP + +```swift +KSOptions.isPipPopViewController: Bool = false +``` + +### Logging + +```swift +// Log levels: .panic, .fatal, .error, .warning, .info, .verbose, .debug, .trace +KSOptions.logLevel: LogLevel = .warning +KSOptions.logger: LogHandler = OSLog(lable: "KSPlayer") +``` + +### System + +```swift +KSOptions.useSystemHTTPProxy: Bool = true +KSOptions.preferredFrame: Bool = true +``` + +### Subtitle Data Sources + +```swift +KSOptions.subtitleDataSouces: [SubtitleDataSouce] = [DirectorySubtitleDataSouce()] +``` + +## Methods + +### HTTP Headers + +```swift +let options = KSOptions() +options.appendHeader(["Referer": "https://example.com"]) +options.appendHeader(["Authorization": "Bearer token123"]) +``` + +### Cookies + +```swift +let cookies = [HTTPCookie(properties: [ + .name: "session", + .value: "abc123", + .domain: "example.com", + .path: "/" +])!] +options.setCookie(cookies) +``` + +## Overridable Methods + +Subclass `KSOptions` to customize behavior: + +### Buffering Algorithm + +```swift +class CustomOptions: KSOptions { + override func playable(capacitys: [CapacityProtocol], isFirst: Bool, isSeek: Bool) -> LoadingState { + // Custom buffering logic + super.playable(capacitys: capacitys, isFirst: isFirst, isSeek: isSeek) + } +} +``` + +### Adaptive Bitrate + +```swift +override func adaptable(state: VideoAdaptationState?) -> (Int64, Int64)? { + // Return (currentBitrate, targetBitrate) or nil + super.adaptable(state: state) +} +``` + +### Track Selection + +```swift +// Select preferred video track +override func wantedVideo(tracks: [MediaPlayerTrack]) -> Int? { + // Return index of preferred track or nil for auto + return tracks.firstIndex { $0.bitRate > 5_000_000 } +} + +// Select preferred audio track +override func wantedAudio(tracks: [MediaPlayerTrack]) -> Int? { + // Return index of preferred track or nil for auto + return tracks.firstIndex { $0.languageCode == "en" } +} +``` + +### Display Layer + +```swift +override func isUseDisplayLayer() -> Bool { + // Return true to use AVSampleBufferDisplayLayer (supports HDR10+) + // Return false for other display modes + display == .plane +} +``` + +### Track Processing + +```swift +override func process(assetTrack: some MediaPlayerTrack) { + super.process(assetTrack: assetTrack) + // Custom processing before decoder creation +} +``` + +### Live Playback Rate + +```swift +override func liveAdaptivePlaybackRate(loadingState: LoadingState) -> Float? { + // Return adjusted playback rate for live streams + // Return nil to keep current rate + if loadingState.loadedTime > preferredForwardBufferDuration + 5 { + return 1.2 // Speed up if too far behind + } + return nil +} +``` + +## Example: Custom Options + +```swift +class StreamingOptions: KSOptions { + override init() { + super.init() + + // Low latency settings + preferredForwardBufferDuration = 1.0 + isSecondOpen = true + nobuffer = true + codecLowDelay = true + + // Custom headers + appendHeader(["X-Custom-Header": "value"]) + } + + override func wantedAudio(tracks: [MediaPlayerTrack]) -> Int? { + // Prefer English audio + return tracks.firstIndex { $0.languageCode == "en" } + } +} + +// Usage +let options = StreamingOptions() +playerView.set(url: streamURL, options: options) +``` + diff --git a/docs/ks-player/KSPlayerLayer.md b/docs/ks-player/KSPlayerLayer.md new file mode 100644 index 00000000..e8d071ff --- /dev/null +++ b/docs/ks-player/KSPlayerLayer.md @@ -0,0 +1,442 @@ +# KSPlayerLayer + +`KSPlayerLayer` is the core playback controller that manages the media player instance and provides a high-level API for playback control. + +## Overview + +`KSPlayerLayer` wraps `MediaPlayerProtocol` implementations (`KSAVPlayer` or `KSMEPlayer`) and handles: +- Player lifecycle management +- Playback state transitions +- Remote control integration +- Picture-in-Picture support +- Background/foreground handling + +## Creating a KSPlayerLayer + +### Basic Initialization + +```swift +let url = URL(string: "https://example.com/video.mp4")! +let options = KSOptions() + +let playerLayer = KSPlayerLayer( + url: url, + isAutoPlay: true, // Default: KSOptions.isAutoPlay + options: options, + delegate: self +) +``` + +### Constructor Parameters + +```swift +public init( + url: URL, + isAutoPlay: Bool = KSOptions.isAutoPlay, + options: KSOptions, + delegate: KSPlayerLayerDelegate? = nil +) +``` + +## Properties + +### Core Properties + +| Property | Type | Description | +|----------|------|-------------| +| `url` | `URL` | Current media URL (read-only after init) | +| `options` | `KSOptions` | Player configuration (read-only) | +| `player` | `MediaPlayerProtocol` | Underlying player instance | +| `state` | `KSPlayerState` | Current playback state (read-only) | +| `delegate` | `KSPlayerLayerDelegate?` | Event delegate | + +### Published Properties (for Combine/SwiftUI) + +```swift +@Published public var bufferingProgress: Int = 0 // 0-100 +@Published public var loopCount: Int = 0 // Loop iteration count +@Published public var isPipActive: Bool = false // Picture-in-Picture state +``` + +## Playback Control Methods + +### play() + +Start or resume playback: + +```swift +playerLayer.play() +``` + +### pause() + +Pause playback: + +```swift +playerLayer.pause() +``` + +### stop() + +Stop playback and reset player state: + +```swift +playerLayer.stop() +``` + +### seek(time:autoPlay:completion:) + +Seek to a specific time: + +```swift +playerLayer.seek(time: 30.0, autoPlay: true) { finished in + if finished { + print("Seek completed") + } +} +``` + +Parameters: +- `time: TimeInterval` - Target time in seconds +- `autoPlay: Bool` - Whether to auto-play after seeking +- `completion: @escaping ((Bool) -> Void)` - Called when seek completes + +### prepareToPlay() + +Prepare the player (called automatically when `isAutoPlay` is true): + +```swift +playerLayer.prepareToPlay() +``` + +## URL Management + +### set(url:options:) + +Change the video URL: + +```swift +let newURL = URL(string: "https://example.com/another-video.mp4")! +playerLayer.set(url: newURL, options: KSOptions()) +``` + +### set(urls:options:) + +Set a playlist of URLs: + +```swift +let urls = [ + URL(string: "https://example.com/video1.mp4")!, + URL(string: "https://example.com/video2.mp4")!, + URL(string: "https://example.com/video3.mp4")! +] +playerLayer.set(urls: urls, options: KSOptions()) +``` + +The player automatically advances to the next URL when playback finishes. + +## Accessing the Player + +### Player Properties + +Access underlying player properties through `playerLayer.player`: + +```swift +// Duration +let duration = playerLayer.player.duration + +// Current time +let currentTime = playerLayer.player.currentPlaybackTime + +// Playing state +let isPlaying = playerLayer.player.isPlaying + +// Seekable +let canSeek = playerLayer.player.seekable + +// Natural size +let videoSize = playerLayer.player.naturalSize + +// File size (estimated) +let fileSize = playerLayer.player.fileSize +``` + +### Player Control + +```swift +// Volume (0.0 to 1.0) +playerLayer.player.playbackVolume = 0.5 + +// Mute +playerLayer.player.isMuted = true + +// Playback rate +playerLayer.player.playbackRate = 1.5 + +// Content mode +playerLayer.player.contentMode = .scaleAspectFit +``` + +### Tracks + +```swift +// Get audio tracks +let audioTracks = playerLayer.player.tracks(mediaType: .audio) + +// Get video tracks +let videoTracks = playerLayer.player.tracks(mediaType: .video) + +// Select a track +if let englishTrack = audioTracks.first(where: { $0.languageCode == "en" }) { + playerLayer.player.select(track: englishTrack) +} +``` + +### External Playback (AirPlay) + +```swift +// Enable AirPlay +playerLayer.player.allowsExternalPlayback = true + +// Check if actively using AirPlay +let isAirPlaying = playerLayer.player.isExternalPlaybackActive + +// Auto-switch to external when screen connected +playerLayer.player.usesExternalPlaybackWhileExternalScreenIsActive = true +``` + +### Picture-in-Picture + +```swift +// Available on tvOS 14.0+, iOS 14.0+ +if #available(tvOS 14.0, iOS 14.0, *) { + // Toggle PiP + playerLayer.isPipActive.toggle() + + // Or access controller directly + playerLayer.player.pipController?.start(view: playerLayer) + playerLayer.player.pipController?.stop(restoreUserInterface: true) +} +``` + +### Dynamic Info + +```swift +if let dynamicInfo = playerLayer.player.dynamicInfo { + print("FPS: \(dynamicInfo.displayFPS)") + print("A/V Sync: \(dynamicInfo.audioVideoSyncDiff)") + print("Dropped frames: \(dynamicInfo.droppedVideoFrameCount)") + print("Audio bitrate: \(dynamicInfo.audioBitrate)") + print("Video bitrate: \(dynamicInfo.videoBitrate)") + + // Metadata + if let title = dynamicInfo.metadata["title"] { + print("Title: \(title)") + } +} +``` + +### Chapters + +```swift +let chapters = playerLayer.player.chapters +for chapter in chapters { + print("\(chapter.title): \(chapter.start) - \(chapter.end)") +} +``` + +### Thumbnails + +```swift +Task { + if let thumbnail = await playerLayer.player.thumbnailImageAtCurrentTime() { + let image = UIImage(cgImage: thumbnail) + // Use thumbnail + } +} +``` + +## KSPlayerLayerDelegate + +Implement the delegate to receive events: + +```swift +extension MyViewController: KSPlayerLayerDelegate { + func player(layer: KSPlayerLayer, state: KSPlayerState) { + switch state { + case .initialized: + print("Player initialized") + case .preparing: + print("Preparing...") + case .readyToPlay: + print("Ready - Duration: \(layer.player.duration)") + case .buffering: + print("Buffering...") + case .bufferFinished: + print("Playing") + case .paused: + print("Paused") + case .playedToTheEnd: + print("Finished") + case .error: + print("Error occurred") + } + } + + func player(layer: KSPlayerLayer, currentTime: TimeInterval, totalTime: TimeInterval) { + let progress = totalTime > 0 ? currentTime / totalTime : 0 + print("Progress: \(Int(progress * 100))%") + } + + func player(layer: KSPlayerLayer, finish error: Error?) { + if let error = error { + print("Playback error: \(error.localizedDescription)") + } else { + print("Playback completed successfully") + } + } + + func player(layer: KSPlayerLayer, bufferedCount: Int, consumeTime: TimeInterval) { + // bufferedCount: 0 = initial load + // consumeTime: time spent buffering + print("Buffer #\(bufferedCount), took \(consumeTime)s") + } +} +``` + +## Player View Integration + +The player's view can be added to your view hierarchy: + +```swift +if let playerView = playerLayer.player.view { + containerView.addSubview(playerView) + playerView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + playerView.topAnchor.constraint(equalTo: containerView.topAnchor), + playerView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), + playerView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), + playerView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), + ]) +} +``` + +## Remote Control + +Remote control is automatically registered when `options.registerRemoteControll` is `true` (default). + +### Supported Commands + +- Play/Pause +- Stop +- Next/Previous track (for playlists) +- Skip forward/backward (15 seconds) +- Change playback position +- Change playback rate +- Change repeat mode +- Language/audio track selection + +### Customizing Remote Control + +```swift +// Disable auto-registration +options.registerRemoteControll = false + +// Manually register later +playerLayer.registerRemoteControllEvent() +``` + +### Now Playing Info + +```swift +import MediaPlayer + +// Set custom Now Playing info +MPNowPlayingInfoCenter.default().nowPlayingInfo = [ + MPMediaItemPropertyTitle: "Video Title", + MPMediaItemPropertyArtist: "Artist Name", + MPMediaItemPropertyPlaybackDuration: playerLayer.player.duration +] +``` + +## Background/Foreground Handling + +KSPlayerLayer automatically handles app lifecycle: + +- **Background**: Pauses video (unless `KSOptions.canBackgroundPlay` is `true`) +- **Foreground**: Resumes display + +```swift +// Enable background playback +KSOptions.canBackgroundPlay = true +``` + +## Player Type Switching + +The player automatically switches from `firstPlayerType` to `secondPlayerType` on failure: + +```swift +// Configure player types before creating KSPlayerLayer +KSOptions.firstPlayerType = KSAVPlayer.self +KSOptions.secondPlayerType = KSMEPlayer.self +``` + +## Complete Example + +```swift +class VideoPlayerController: UIViewController, KSPlayerLayerDelegate { + private var playerLayer: KSPlayerLayer! + private var containerView: UIView! + + override func viewDidLoad() { + super.viewDidLoad() + + containerView = UIView() + view.addSubview(containerView) + containerView.frame = view.bounds + + let url = URL(string: "https://example.com/video.mp4")! + let options = KSOptions() + options.isLoopPlay = true + + playerLayer = KSPlayerLayer( + url: url, + options: options, + delegate: self + ) + + if let playerView = playerLayer.player.view { + containerView.addSubview(playerView) + playerView.frame = containerView.bounds + playerView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + } + } + + // MARK: - KSPlayerLayerDelegate + + func player(layer: KSPlayerLayer, state: KSPlayerState) { + print("State: \(state)") + } + + func player(layer: KSPlayerLayer, currentTime: TimeInterval, totalTime: TimeInterval) { + // Update progress UI + } + + func player(layer: KSPlayerLayer, finish error: Error?) { + if let error = error { + showError(error) + } + } + + func player(layer: KSPlayerLayer, bufferedCount: Int, consumeTime: TimeInterval) { + if bufferedCount == 0 { + print("Initial load took \(consumeTime)s") + } + } + + deinit { + playerLayer.stop() + } +} +``` + diff --git a/docs/ks-player/SubtitleSupport.md b/docs/ks-player/SubtitleSupport.md new file mode 100644 index 00000000..5480e00f --- /dev/null +++ b/docs/ks-player/SubtitleSupport.md @@ -0,0 +1,490 @@ +# Subtitle Support + +KSPlayer provides comprehensive subtitle support including embedded subtitles, external subtitle files, and online subtitle search. + +## SubtitleModel + +`SubtitleModel` manages subtitle sources, selection, and rendering. + +### Properties + +```swift +open class SubtitleModel: ObservableObject { + // Available subtitle sources + @Published public private(set) var subtitleInfos: [any SubtitleInfo] + + // Current subtitle parts being displayed + @Published public private(set) var parts: [SubtitlePart] + + // Global subtitle delay (seconds) + public var subtitleDelay: Double = 0.0 + + // Current media URL + public var url: URL? + + // Selected subtitle + @Published public var selectedSubtitleInfo: (any SubtitleInfo)? +} +``` + +### Static Styling Properties + +```swift +SubtitleModel.textColor: Color = .white +SubtitleModel.textBackgroundColor: Color = .clear +SubtitleModel.textFontSize: CGFloat = SubtitleModel.Size.standard.rawValue +SubtitleModel.textBold: Bool = false +SubtitleModel.textItalic: Bool = false +SubtitleModel.textPosition: TextPosition = TextPosition() +``` + +### Font Sizes + +```swift +public enum Size { + case smaller // 12pt (iPhone), 20pt (iPad/Mac), 48pt (TV) + case standard // 16pt (iPhone), 26pt (iPad/Mac), 58pt (TV) + case large // 20pt (iPhone), 32pt (iPad/Mac), 68pt (TV) +} +``` + +### Methods + +```swift +// Add subtitle source +public func addSubtitle(info: any SubtitleInfo) + +// Add subtitle data source +public func addSubtitle(dataSouce: SubtitleDataSouce) + +// Search for subtitles online +public func searchSubtitle(query: String?, languages: [String]) + +// Get subtitle for current time (called internally) +public func subtitle(currentTime: TimeInterval) -> Bool +``` + +## SubtitleInfo Protocol + +Protocol for subtitle track information: + +```swift +public protocol SubtitleInfo: KSSubtitleProtocol, AnyObject, Hashable, Identifiable { + var subtitleID: String { get } + var name: String { get } + var delay: TimeInterval { get set } + var isEnabled: Bool { get set } +} +``` + +### KSSubtitleProtocol + +```swift +public protocol KSSubtitleProtocol { + func search(for time: TimeInterval) -> [SubtitlePart] +} +``` + +## URLSubtitleInfo + +Subtitle from a URL: + +```swift +public class URLSubtitleInfo: KSSubtitle, SubtitleInfo { + public private(set) var downloadURL: URL + public var delay: TimeInterval = 0 + public private(set) var name: String + public let subtitleID: String + public var comment: String? + public var isEnabled: Bool + + // Simple initializer + public convenience init(url: URL) + + // Full initializer + public init( + subtitleID: String, + name: String, + url: URL, + userAgent: String? = nil + ) +} +``` + +### Example: Loading External Subtitle + +```swift +let subtitleURL = URL(string: "https://example.com/subtitle.srt")! +let subtitleInfo = URLSubtitleInfo(url: subtitleURL) + +// Add to subtitle model +subtitleModel.addSubtitle(info: subtitleInfo) + +// Or select directly +subtitleModel.selectedSubtitleInfo = subtitleInfo +``` + +## SubtitlePart + +A single subtitle cue: + +```swift +public class SubtitlePart: CustomStringConvertible, Identifiable { + public var start: TimeInterval + public var end: TimeInterval + public var origin: CGPoint = .zero + public let text: NSAttributedString? + public var image: UIImage? // For image-based subtitles (e.g., SUP) + public var textPosition: TextPosition? + + public convenience init(_ start: TimeInterval, _ end: TimeInterval, _ string: String) + public init(_ start: TimeInterval, _ end: TimeInterval, attributedString: NSAttributedString?) +} +``` + +## SubtitleDataSouce Protocol + +Protocol for subtitle sources: + +```swift +public protocol SubtitleDataSouce: AnyObject { + var infos: [any SubtitleInfo] { get } +} +``` + +### FileURLSubtitleDataSouce + +For file-based subtitle sources: + +```swift +public protocol FileURLSubtitleDataSouce: SubtitleDataSouce { + func searchSubtitle(fileURL: URL?) async throws +} +``` + +### SearchSubtitleDataSouce + +For online subtitle search: + +```swift +public protocol SearchSubtitleDataSouce: SubtitleDataSouce { + func searchSubtitle(query: String?, languages: [String]) async throws +} +``` + +### CacheSubtitleDataSouce + +For cached subtitles: + +```swift +public protocol CacheSubtitleDataSouce: FileURLSubtitleDataSouce { + func addCache(fileURL: URL, downloadURL: URL) +} +``` + +## Built-in Data Sources + +### URLSubtitleDataSouce + +Simple URL-based subtitle source: + +```swift +public class URLSubtitleDataSouce: SubtitleDataSouce { + public var infos: [any SubtitleInfo] + + public init(urls: [URL]) +} + +// Example +let subtitleSource = URLSubtitleDataSouce(urls: [ + URL(string: "https://example.com/english.srt")!, + URL(string: "https://example.com/spanish.srt")! +]) +``` + +### DirectorySubtitleDataSouce + +Searches for subtitles in the same directory as the video: + +```swift +public class DirectorySubtitleDataSouce: FileURLSubtitleDataSouce { + public var infos: [any SubtitleInfo] + + public init() + public func searchSubtitle(fileURL: URL?) async throws +} +``` + +### PlistCacheSubtitleDataSouce + +Caches downloaded subtitle locations: + +```swift +public class PlistCacheSubtitleDataSouce: CacheSubtitleDataSouce { + public static let singleton: PlistCacheSubtitleDataSouce + public var infos: [any SubtitleInfo] + + public func searchSubtitle(fileURL: URL?) async throws + public func addCache(fileURL: URL, downloadURL: URL) +} +``` + +## Online Subtitle Providers + +### ShooterSubtitleDataSouce + +Shooter.cn subtitle search (for local files): + +```swift +public class ShooterSubtitleDataSouce: FileURLSubtitleDataSouce { + public var infos: [any SubtitleInfo] + + public init() + public func searchSubtitle(fileURL: URL?) async throws +} +``` + +### AssrtSubtitleDataSouce + +Assrt.net subtitle search: + +```swift +public class AssrtSubtitleDataSouce: SearchSubtitleDataSouce { + public var infos: [any SubtitleInfo] + + public init(token: String) + public func searchSubtitle(query: String?, languages: [String]) async throws +} + +// Example +let assrtSource = AssrtSubtitleDataSouce(token: "your-api-token") +``` + +### OpenSubtitleDataSouce + +OpenSubtitles.com API: + +```swift +public class OpenSubtitleDataSouce: SearchSubtitleDataSouce { + public var infos: [any SubtitleInfo] + + public init(apiKey: String, username: String? = nil, password: String? = nil) + + // Search by query + public func searchSubtitle(query: String?, languages: [String]) async throws + + // Search by IDs + public func searchSubtitle( + query: String?, + imdbID: Int, + tmdbID: Int, + languages: [String] + ) async throws + + // Search with custom parameters + public func searchSubtitle(queryItems: [String: String]) async throws +} + +// Example +let openSubSource = OpenSubtitleDataSouce(apiKey: "your-api-key") +``` + +## Configuring Default Data Sources + +```swift +// Set default subtitle data sources +KSOptions.subtitleDataSouces = [ + DirectorySubtitleDataSouce(), + PlistCacheSubtitleDataSouce.singleton +] + +// Add online search +KSOptions.subtitleDataSouces.append( + OpenSubtitleDataSouce(apiKey: "your-key") +) +``` + +## UIKit Integration + +### With VideoPlayerView + +```swift +class VideoViewController: UIViewController { + let playerView = IOSVideoPlayerView() + + func loadSubtitle(url: URL) { + let subtitleInfo = URLSubtitleInfo(url: url) + playerView.srtControl.addSubtitle(info: subtitleInfo) + playerView.srtControl.selectedSubtitleInfo = subtitleInfo + } + + func selectSubtitle(at index: Int) { + let subtitles = playerView.srtControl.subtitleInfos + if index < subtitles.count { + playerView.srtControl.selectedSubtitleInfo = subtitles[index] + } + } + + func disableSubtitles() { + playerView.srtControl.selectedSubtitleInfo = nil + } +} +``` + +### Subtitle Styling + +```swift +// Configure before creating player +SubtitleModel.textFontSize = 20 +SubtitleModel.textColor = .yellow +SubtitleModel.textBackgroundColor = Color.black.opacity(0.5) +SubtitleModel.textBold = true + +// Update during playback (VideoPlayerView only) +playerView.updateSrt() +``` + +## SwiftUI Integration + +### With KSVideoPlayer.Coordinator + +```swift +struct PlayerView: View { + @StateObject var coordinator = KSVideoPlayer.Coordinator() + + var body: some View { + VStack { + KSVideoPlayer(coordinator: coordinator, url: url, options: KSOptions()) + + // Subtitle picker + Picker("Subtitle", selection: $coordinator.subtitleModel.selectedSubtitleInfo) { + Text("Off").tag(nil as (any SubtitleInfo)?) + ForEach(coordinator.subtitleModel.subtitleInfos, id: \.subtitleID) { info in + Text(info.name).tag(info as (any SubtitleInfo)?) + } + } + } + } +} +``` + +### Adding External Subtitles + +```swift +func addSubtitle(url: URL) { + let info = URLSubtitleInfo(url: url) + coordinator.subtitleModel.addSubtitle(info: info) +} +``` + +### Searching Online Subtitles + +```swift +func searchSubtitles(title: String) { + coordinator.subtitleModel.searchSubtitle( + query: title, + languages: ["en", "es"] + ) +} +``` + +## TextPosition + +Subtitle text positioning: + +```swift +public struct TextPosition { + public var verticalAlign: VerticalAlignment = .bottom + public var horizontalAlign: HorizontalAlignment = .center + public var leftMargin: CGFloat = 0 + public var rightMargin: CGFloat = 0 + public var verticalMargin: CGFloat = 10 +} + +// Configure position +SubtitleModel.textPosition = TextPosition( + verticalAlign: .bottom, + horizontalAlign: .center, + verticalMargin: 50 +) +``` + +## Supported Subtitle Formats + +KSPlayer supports various subtitle formats through FFmpeg and built-in parsers: + +- **Text Formats**: SRT, ASS/SSA, VTT, TTML +- **Image Formats**: SUP/PGS, VobSub (IDX/SUB) +- **Embedded Subtitles**: From MKV, MP4, etc. + +## Parsing Subtitles Manually + +```swift +let subtitle = KSSubtitle() + +// Parse from URL +Task { + try await subtitle.parse(url: subtitleURL) + print("Loaded \(subtitle.parts.count) subtitle cues") +} + +// Parse from data +try subtitle.parse(data: subtitleData, encoding: .utf8) + +// Search for subtitle at time +let parts = subtitle.search(for: currentTime) +``` + +## Complete Example + +```swift +class SubtitlePlayerController: UIViewController, KSPlayerLayerDelegate { + private var playerLayer: KSPlayerLayer! + private var subtitleModel = SubtitleModel() + private var subtitleLabel = UILabel() + + override func viewDidLoad() { + super.viewDidLoad() + setupSubtitleLabel() + + // Configure subtitle sources + let subtitleSource = URLSubtitleDataSouce(urls: [ + URL(string: "https://example.com/english.srt")! + ]) + subtitleModel.addSubtitle(dataSouce: subtitleSource) + + // Create player + let url = URL(string: "https://example.com/video.mp4")! + playerLayer = KSPlayerLayer(url: url, options: KSOptions(), delegate: self) + subtitleModel.url = url + } + + func player(layer: KSPlayerLayer, state: KSPlayerState) { + if state == .readyToPlay { + // Add embedded subtitles + if let subtitleDataSource = layer.player.subtitleDataSouce { + subtitleModel.addSubtitle(dataSouce: subtitleDataSource) + } + + // Auto-select first subtitle + subtitleModel.selectedSubtitleInfo = subtitleModel.subtitleInfos.first + } + } + + func player(layer: KSPlayerLayer, currentTime: TimeInterval, totalTime: TimeInterval) { + if subtitleModel.subtitle(currentTime: currentTime) { + updateSubtitleDisplay() + } + } + + private func updateSubtitleDisplay() { + if let part = subtitleModel.parts.first { + subtitleLabel.attributedText = part.text + subtitleLabel.isHidden = false + } else { + subtitleLabel.isHidden = true + } + } +} +``` + diff --git a/docs/ks-player/SwiftUIUsage.md b/docs/ks-player/SwiftUIUsage.md new file mode 100644 index 00000000..c1fd8470 --- /dev/null +++ b/docs/ks-player/SwiftUIUsage.md @@ -0,0 +1,426 @@ +# SwiftUI Usage + +KSPlayer provides full SwiftUI support with `KSVideoPlayer` (a UIViewRepresentable) and `KSVideoPlayerView` (a complete player view with controls). + +**Minimum Requirements:** iOS 16.0, macOS 13.0, tvOS 16.0 + +## KSVideoPlayerView + +`KSVideoPlayerView` is a complete video player with built-in controls, subtitle display, and settings. + +### Basic Usage + +```swift +import KSPlayer +import SwiftUI + +struct VideoScreen: View { + let url = URL(string: "https://example.com/video.mp4")! + + var body: some View { + KSVideoPlayerView(url: url, options: KSOptions()) + } +} +``` + +### With Custom Title + +```swift +KSVideoPlayerView( + url: url, + options: KSOptions(), + title: "My Video Title" +) +``` + +### With Coordinator and Subtitle Data Source + +```swift +struct VideoScreen: View { + @StateObject private var coordinator = KSVideoPlayer.Coordinator() + let url: URL + let subtitleDataSource: SubtitleDataSouce? + + var body: some View { + KSVideoPlayerView( + coordinator: coordinator, + url: url, + options: KSOptions(), + title: "Video Title", + subtitleDataSouce: subtitleDataSource + ) + } +} +``` + +## KSVideoPlayer + +`KSVideoPlayer` is the lower-level UIViewRepresentable that provides the video rendering surface. Use this when you want full control over the UI. + +### Basic Usage + +```swift +import KSPlayer +import SwiftUI + +struct CustomPlayerView: View { + @StateObject private var coordinator = KSVideoPlayer.Coordinator() + let url: URL + let options: KSOptions + + var body: some View { + KSVideoPlayer(coordinator: coordinator, url: url, options: options) + .onStateChanged { layer, state in + print("State changed: \(state)") + } + .onPlay { currentTime, totalTime in + print("Playing: \(currentTime)/\(totalTime)") + } + .onFinish { layer, error in + if let error = error { + print("Error: \(error)") + } + } + } +} +``` + +### Initializer + +```swift +public struct KSVideoPlayer { + public init( + coordinator: Coordinator, + url: URL, + options: KSOptions + ) +} +``` + +## KSVideoPlayer.Coordinator + +The Coordinator manages player state and provides bindings for SwiftUI views. + +### Creating a Coordinator + +```swift +@StateObject private var coordinator = KSVideoPlayer.Coordinator() +``` + +### Published Properties + +```swift +@MainActor +public final class Coordinator: ObservableObject { + // Playback state (read-only computed property) + public var state: KSPlayerState { get } + + // Mute control + @Published public var isMuted: Bool = false + + // Volume (0.0 to 1.0) + @Published public var playbackVolume: Float = 1.0 + + // Content mode toggle + @Published public var isScaleAspectFill: Bool = false + + // Playback rate (1.0 = normal) + @Published public var playbackRate: Float = 1.0 + + // Controls visibility + @Published public var isMaskShow: Bool = true + + // Subtitle model + public var subtitleModel: SubtitleModel + + // Time model for progress display + public var timemodel: ControllerTimeModel + + // The underlying player layer + public var playerLayer: KSPlayerLayer? +} +``` + +### Coordinator Methods + +```swift +// Skip forward/backward by seconds +public func skip(interval: Int) + +// Seek to specific time +public func seek(time: TimeInterval) + +// Show/hide controls with optional auto-hide +public func mask(show: Bool, autoHide: Bool = true) + +// Reset player state (called automatically on view dismissal) +public func resetPlayer() +``` + +### Using Coordinator for Playback Control + +```swift +struct PlayerView: View { + @StateObject private var coordinator = KSVideoPlayer.Coordinator() + let url: URL + + var body: some View { + VStack { + KSVideoPlayer(coordinator: coordinator, url: url, options: KSOptions()) + + HStack { + Button("Play") { + coordinator.playerLayer?.play() + } + + Button("Pause") { + coordinator.playerLayer?.pause() + } + + Button("-15s") { + coordinator.skip(interval: -15) + } + + Button("+15s") { + coordinator.skip(interval: 15) + } + } + + Slider(value: $coordinator.playbackVolume, in: 0...1) + + Toggle("Mute", isOn: $coordinator.isMuted) + } + } +} +``` + +## View Modifiers + +### onStateChanged + +Called when playback state changes: + +```swift +KSVideoPlayer(coordinator: coordinator, url: url, options: options) + .onStateChanged { layer, state in + switch state { + case .initialized: break + case .preparing: break + case .readyToPlay: + // Access metadata + if let title = layer.player.dynamicInfo?.metadata["title"] { + print("Title: \(title)") + } + case .buffering: break + case .bufferFinished: break + case .paused: break + case .playedToTheEnd: break + case .error: break + } + } +``` + +### onPlay + +Called periodically during playback with current and total time: + +```swift +.onPlay { currentTime, totalTime in + let progress = currentTime / totalTime + print("Progress: \(Int(progress * 100))%") +} +``` + +### onFinish + +Called when playback ends (naturally or with error): + +```swift +.onFinish { layer, error in + if let error = error { + print("Playback failed: \(error.localizedDescription)") + } else { + print("Playback completed") + } +} +``` + +### onBufferChanged + +Called when buffering status changes: + +```swift +.onBufferChanged { bufferedCount, consumeTime in + // bufferedCount: 0 = initial loading + print("Buffer count: \(bufferedCount), time: \(consumeTime)") +} +``` + +### onSwipe (iOS only) + +Called on swipe gestures: + +```swift +#if canImport(UIKit) +.onSwipe { direction in + switch direction { + case .up: print("Swipe up") + case .down: print("Swipe down") + case .left: print("Swipe left") + case .right: print("Swipe right") + default: break + } +} +#endif +``` + +## ControllerTimeModel + +Used for displaying playback time: + +```swift +public class ControllerTimeModel: ObservableObject { + @Published public var currentTime: Int = 0 + @Published public var totalTime: Int = 1 +} +``` + +Usage: + +```swift +struct TimeDisplay: View { + @ObservedObject var timeModel: ControllerTimeModel + + var body: some View { + Text("\(timeModel.currentTime) / \(timeModel.totalTime)") + } +} + +// In your player view: +TimeDisplay(timeModel: coordinator.timemodel) +``` + +## Subtitle Integration + +Access subtitles through the coordinator: + +```swift +struct SubtitlePicker: View { + @ObservedObject var subtitleModel: SubtitleModel + + var body: some View { + Picker("Subtitle", selection: $subtitleModel.selectedSubtitleInfo) { + Text("Off").tag(nil as (any SubtitleInfo)?) + ForEach(subtitleModel.subtitleInfos, id: \.subtitleID) { info in + Text(info.name).tag(info as (any SubtitleInfo)?) + } + } + } +} + +// Usage: +SubtitlePicker(subtitleModel: coordinator.subtitleModel) +``` + +## Complete Example + +```swift +import KSPlayer +import SwiftUI + +@available(iOS 16.0, *) +struct FullPlayerView: View { + @StateObject private var coordinator = KSVideoPlayer.Coordinator() + @State private var url: URL + @State private var title: String + @Environment(\.dismiss) private var dismiss + + init(url: URL, title: String) { + _url = State(initialValue: url) + _title = State(initialValue: title) + } + + var body: some View { + ZStack { + KSVideoPlayer(coordinator: coordinator, url: url, options: KSOptions()) + .onStateChanged { layer, state in + if state == .readyToPlay { + if let movieTitle = layer.player.dynamicInfo?.metadata["title"] { + title = movieTitle + } + } + } + .onFinish { _, error in + if error != nil { + dismiss() + } + } + .ignoresSafeArea() + .onTapGesture { + coordinator.isMaskShow.toggle() + } + + // Custom controls overlay + if coordinator.isMaskShow { + VStack { + HStack { + Button("Back") { dismiss() } + Spacer() + Text(title) + } + .padding() + + Spacer() + + HStack(spacing: 40) { + Button(action: { coordinator.skip(interval: -15) }) { + Image(systemName: "gobackward.15") + } + + Button(action: { + if coordinator.state.isPlaying { + coordinator.playerLayer?.pause() + } else { + coordinator.playerLayer?.play() + } + }) { + Image(systemName: coordinator.state.isPlaying ? "pause.fill" : "play.fill") + } + + Button(action: { coordinator.skip(interval: 15) }) { + Image(systemName: "goforward.15") + } + } + .font(.largeTitle) + + Spacer() + } + .foregroundColor(.white) + } + } + .preferredColorScheme(.dark) + } +} +``` + +## URL Change Handling + +The player automatically detects URL changes: + +```swift +struct DynamicPlayerView: View { + @StateObject private var coordinator = KSVideoPlayer.Coordinator() + @State private var currentURL: URL + + var body: some View { + VStack { + KSVideoPlayer(coordinator: coordinator, url: currentURL, options: KSOptions()) + + Button("Load Next Video") { + currentURL = URL(string: "https://example.com/next-video.mp4")! + } + } + } +} +``` + diff --git a/docs/ks-player/TrackManagement.md b/docs/ks-player/TrackManagement.md new file mode 100644 index 00000000..f8eefd82 --- /dev/null +++ b/docs/ks-player/TrackManagement.md @@ -0,0 +1,473 @@ +# Track Management + +KSPlayer provides APIs for managing audio, video, and subtitle tracks within media files. + +## Overview + +Tracks represent individual streams within a media container (video tracks, audio tracks, subtitle tracks). You can: +- Query available tracks +- Get track metadata +- Select/enable specific tracks + +## Getting Tracks + +### From MediaPlayerProtocol + +```swift +// Get audio tracks +let audioTracks = player.tracks(mediaType: .audio) + +// Get video tracks +let videoTracks = player.tracks(mediaType: .video) + +// Get subtitle tracks +let subtitleTracks = player.tracks(mediaType: .subtitle) +``` + +### From KSPlayerLayer + +```swift +if let player = playerLayer.player { + let audioTracks = player.tracks(mediaType: .audio) + // ... +} +``` + +### From VideoPlayerView + +```swift +if let player = playerView.playerLayer?.player { + let tracks = player.tracks(mediaType: .audio) + // ... +} +``` + +### From SwiftUI Coordinator + +```swift +let audioTracks = coordinator.playerLayer?.player.tracks(mediaType: .audio) ?? [] +``` + +## MediaPlayerTrack Protocol + +All tracks conform to `MediaPlayerTrack`: + +```swift +public protocol MediaPlayerTrack: AnyObject, CustomStringConvertible { + var trackID: Int32 { get } + var name: String { get } + var languageCode: String? { get } + var mediaType: AVFoundation.AVMediaType { get } + var nominalFrameRate: Float { get set } + var bitRate: Int64 { get } + var bitDepth: Int32 { get } + var isEnabled: Bool { get set } + var isImageSubtitle: Bool { get } + var rotation: Int16 { get } + var dovi: DOVIDecoderConfigurationRecord? { get } + var fieldOrder: FFmpegFieldOrder { get } + var formatDescription: CMFormatDescription? { get } +} +``` + +## Track Properties + +### Basic Properties + +| Property | Type | Description | +|----------|------|-------------| +| `trackID` | `Int32` | Unique track identifier | +| `name` | `String` | Track name (often empty) | +| `languageCode` | `String?` | ISO 639-1/639-2 language code | +| `mediaType` | `AVMediaType` | `.audio`, `.video`, or `.subtitle` | +| `isEnabled` | `Bool` | Whether track is currently active | + +### Audio Properties + +| Property | Type | Description | +|----------|------|-------------| +| `bitRate` | `Int64` | Audio bitrate in bps | +| `audioStreamBasicDescription` | `AudioStreamBasicDescription?` | Core Audio format info | + +### Video Properties + +| Property | Type | Description | +|----------|------|-------------| +| `naturalSize` | `CGSize` | Video dimensions | +| `nominalFrameRate` | `Float` | Frame rate | +| `bitRate` | `Int64` | Video bitrate in bps | +| `bitDepth` | `Int32` | Color depth (8, 10, 12) | +| `rotation` | `Int16` | Rotation in degrees | +| `fieldOrder` | `FFmpegFieldOrder` | Interlacing type | +| `dynamicRange` | `DynamicRange?` | SDR/HDR/Dolby Vision | +| `dovi` | `DOVIDecoderConfigurationRecord?` | Dolby Vision config | + +### Color Properties + +| Property | Type | Description | +|----------|------|-------------| +| `colorPrimaries` | `String?` | Color primaries (e.g., "ITU_R_709_2") | +| `transferFunction` | `String?` | Transfer function | +| `yCbCrMatrix` | `String?` | YCbCr matrix | +| `colorSpace` | `CGColorSpace?` | Computed color space | + +### Subtitle Properties + +| Property | Type | Description | +|----------|------|-------------| +| `isImageSubtitle` | `Bool` | True for bitmap subtitles (SUP, VobSub) | + +### Computed Properties + +```swift +extension MediaPlayerTrack { + // Localized language name + var language: String? { + languageCode.flatMap { Locale.current.localizedString(forLanguageCode: $0) } + } + + // FourCC codec type + var codecType: FourCharCode + + // Video format subtype + var mediaSubType: CMFormatDescription.MediaSubType +} +``` + +## Selecting Tracks + +### Select a Track + +```swift +// Find English audio track +if let englishTrack = audioTracks.first(where: { $0.languageCode == "en" }) { + player.select(track: englishTrack) +} +``` + +### Select Track by Index + +```swift +let audioTracks = player.tracks(mediaType: .audio) +if audioTracks.count > 1 { + player.select(track: audioTracks[1]) +} +``` + +### Check Currently Selected Track + +```swift +let currentAudio = audioTracks.first(where: { $0.isEnabled }) +print("Current audio: \(currentAudio?.name ?? "none")") +``` + +## Track Selection Examples + +### Audio Track Selection + +```swift +func selectAudioTrack(languageCode: String) { + let audioTracks = player.tracks(mediaType: .audio) + + if let track = audioTracks.first(where: { $0.languageCode == languageCode }) { + player.select(track: track) + print("Selected: \(track.language ?? track.name)") + } +} + +// Usage +selectAudioTrack(languageCode: "en") // English +selectAudioTrack(languageCode: "es") // Spanish +selectAudioTrack(languageCode: "ja") // Japanese +``` + +### Video Track Selection (Multi-angle/quality) + +```swift +func selectVideoTrack(preferredBitrate: Int64) { + let videoTracks = player.tracks(mediaType: .video) + + // Find closest bitrate + let sorted = videoTracks.sorted { + abs($0.bitRate - preferredBitrate) < abs($1.bitRate - preferredBitrate) + } + + if let track = sorted.first { + player.select(track: track) + print("Selected video: \(track.naturalSize.width)x\(track.naturalSize.height)") + } +} +``` + +### HDR Track Selection + +```swift +func selectHDRTrack() { + let videoTracks = player.tracks(mediaType: .video) + + // Prefer Dolby Vision, then HDR10, then SDR + let preferredOrder: [DynamicRange] = [.dolbyVision, .hdr10, .hlg, .sdr] + + for range in preferredOrder { + if let track = videoTracks.first(where: { $0.dynamicRange == range }) { + player.select(track: track) + print("Selected: \(range)") + return + } + } +} +``` + +## UIKit Track Selection UI + +### Using UIAlertController + +```swift +func showAudioTrackPicker() { + guard let player = playerLayer?.player else { return } + + let audioTracks = player.tracks(mediaType: .audio) + guard !audioTracks.isEmpty else { return } + + let alert = UIAlertController( + title: "Select Audio Track", + message: nil, + preferredStyle: .actionSheet + ) + + for track in audioTracks { + let title = track.language ?? track.name + let action = UIAlertAction(title: title, style: .default) { _ in + player.select(track: track) + } + + if track.isEnabled { + action.setValue(true, forKey: "checked") + } + + alert.addAction(action) + } + + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + present(alert, animated: true) +} +``` + +### Track Info Display + +```swift +func displayTrackInfo() { + guard let player = playerLayer?.player else { return } + + // Video info + if let videoTrack = player.tracks(mediaType: .video).first(where: { $0.isEnabled }) { + print("Video: \(videoTrack.naturalSize.width)x\(videoTrack.naturalSize.height)") + print("FPS: \(videoTrack.nominalFrameRate)") + print("Bitrate: \(videoTrack.bitRate / 1000) kbps") + print("HDR: \(videoTrack.dynamicRange?.description ?? "SDR")") + } + + // Audio info + if let audioTrack = player.tracks(mediaType: .audio).first(where: { $0.isEnabled }) { + print("Audio: \(audioTrack.language ?? "Unknown")") + print("Bitrate: \(audioTrack.bitRate / 1000) kbps") + } +} +``` + +## SwiftUI Track Selection + +### Audio Track Picker + +```swift +struct AudioTrackPicker: View { + let player: MediaPlayerProtocol? + + var audioTracks: [MediaPlayerTrack] { + player?.tracks(mediaType: .audio) ?? [] + } + + var body: some View { + Menu { + ForEach(audioTracks, id: \.trackID) { track in + Button { + player?.select(track: track) + } label: { + HStack { + Text(track.language ?? track.name) + if track.isEnabled { + Image(systemName: "checkmark") + } + } + } + } + } label: { + Image(systemName: "waveform.circle.fill") + } + } +} +``` + +### Video Track Picker + +```swift +struct VideoTrackPicker: View { + let player: MediaPlayerProtocol? + + var videoTracks: [MediaPlayerTrack] { + player?.tracks(mediaType: .video) ?? [] + } + + var body: some View { + Picker("Video", selection: Binding( + get: { videoTracks.first(where: { $0.isEnabled })?.trackID }, + set: { newValue in + if let track = videoTracks.first(where: { $0.trackID == newValue }) { + player?.select(track: track) + } + } + )) { + ForEach(videoTracks, id: \.trackID) { track in + Text("\(Int(track.naturalSize.width))x\(Int(track.naturalSize.height))") + .tag(track.trackID as Int32?) + } + } + } +} +``` + +## Automatic Track Selection + +Configure `KSOptions` for automatic track selection: + +```swift +class CustomOptions: KSOptions { + // Prefer English audio + override func wantedAudio(tracks: [MediaPlayerTrack]) -> Int? { + if let index = tracks.firstIndex(where: { $0.languageCode == "en" }) { + return index + } + return nil // Use default selection + } + + // Prefer highest quality video + override func wantedVideo(tracks: [MediaPlayerTrack]) -> Int? { + if let index = tracks.enumerated().max(by: { $0.element.bitRate < $1.element.bitRate })?.offset { + return index + } + return nil + } +} +``` + +## Track Events + +### Detecting Track Changes + +```swift +func player(layer: KSPlayerLayer, state: KSPlayerState) { + if state == .readyToPlay { + let player = layer.player + + // Log available tracks + print("Audio tracks: \(player.tracks(mediaType: .audio).count)") + print("Video tracks: \(player.tracks(mediaType: .video).count)") + print("Subtitle tracks: \(player.tracks(mediaType: .subtitle).count)") + + // Get current selections + let currentAudio = player.tracks(mediaType: .audio).first(where: { $0.isEnabled }) + let currentVideo = player.tracks(mediaType: .video).first(where: { $0.isEnabled }) + + print("Current audio: \(currentAudio?.language ?? "unknown")") + print("Current video: \(currentVideo?.naturalSize ?? .zero)") + } +} +``` + +## Complete Example + +```swift +class TrackSelectionController: UIViewController, KSPlayerLayerDelegate { + private var playerLayer: KSPlayerLayer! + private var audioButton: UIButton! + private var videoInfoLabel: UILabel! + + override func viewDidLoad() { + super.viewDidLoad() + setupUI() + + let url = URL(string: "https://example.com/multi-track-video.mkv")! + playerLayer = KSPlayerLayer(url: url, options: KSOptions(), delegate: self) + } + + func player(layer: KSPlayerLayer, state: KSPlayerState) { + if state == .readyToPlay { + updateTrackUI() + } + } + + private func updateTrackUI() { + guard let player = playerLayer?.player else { return } + + // Update video info + if let video = player.tracks(mediaType: .video).first(where: { $0.isEnabled }) { + videoInfoLabel.text = """ + \(Int(video.naturalSize.width))x\(Int(video.naturalSize.height)) @ \(Int(video.nominalFrameRate))fps + \(video.dynamicRange?.description ?? "SDR") + """ + } + + // Update audio button + let audioCount = player.tracks(mediaType: .audio).count + audioButton.isHidden = audioCount < 2 + + if let audio = player.tracks(mediaType: .audio).first(where: { $0.isEnabled }) { + audioButton.setTitle(audio.language ?? "Audio", for: .normal) + } + } + + @objc private func audioButtonTapped() { + guard let player = playerLayer?.player else { return } + + let tracks = player.tracks(mediaType: .audio) + let alert = UIAlertController(title: "Audio Track", message: nil, preferredStyle: .actionSheet) + + for track in tracks { + let title = [track.language, track.name] + .compactMap { $0 } + .joined(separator: " - ") + + let action = UIAlertAction(title: title.isEmpty ? "Track \(track.trackID)" : title, style: .default) { [weak self] _ in + player.select(track: track) + self?.updateTrackUI() + } + + if track.isEnabled { + action.setValue(true, forKey: "checked") + alert.preferredAction = action + } + + alert.addAction(action) + } + + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel)) + present(alert, animated: true) + } + + private func setupUI() { + audioButton = UIButton(type: .system) + audioButton.addTarget(self, action: #selector(audioButtonTapped), for: .touchUpInside) + view.addSubview(audioButton) + + videoInfoLabel = UILabel() + videoInfoLabel.numberOfLines = 0 + view.addSubview(videoInfoLabel) + } + + // Delegate methods... + func player(layer: KSPlayerLayer, currentTime: TimeInterval, totalTime: TimeInterval) {} + func player(layer: KSPlayerLayer, finish error: Error?) {} + func player(layer: KSPlayerLayer, bufferedCount: Int, consumeTime: TimeInterval) {} +} +``` + diff --git a/docs/ks-player/TypesAndProtocols.md b/docs/ks-player/TypesAndProtocols.md new file mode 100644 index 00000000..c8b1eb0e --- /dev/null +++ b/docs/ks-player/TypesAndProtocols.md @@ -0,0 +1,543 @@ +# Types and Protocols + +This reference documents all core types, protocols, and enums in KSPlayer. + +## Protocols + +### MediaPlayerProtocol + +The main protocol that player implementations (`KSAVPlayer`, `KSMEPlayer`) must conform to. + +```swift +public protocol MediaPlayerProtocol: MediaPlayback { + var delegate: MediaPlayerDelegate? { get set } + var view: UIView? { get } + var playableTime: TimeInterval { get } + var isReadyToPlay: Bool { get } + var playbackState: MediaPlaybackState { get } + var loadState: MediaLoadState { get } + var isPlaying: Bool { get } + var seekable: Bool { get } + var isMuted: Bool { get set } + var allowsExternalPlayback: Bool { get set } + var usesExternalPlaybackWhileExternalScreenIsActive: Bool { get set } + var isExternalPlaybackActive: Bool { get } + var playbackRate: Float { get set } + var playbackVolume: Float { get set } + var contentMode: UIViewContentMode { get set } + var subtitleDataSouce: SubtitleDataSouce? { get } + var dynamicInfo: DynamicInfo? { get } + + @available(macOS 12.0, iOS 15.0, tvOS 15.0, *) + var playbackCoordinator: AVPlaybackCoordinator { get } + + @available(tvOS 14.0, *) + var pipController: KSPictureInPictureController? { get } + + init(url: URL, options: KSOptions) + func replace(url: URL, options: KSOptions) + func play() + func pause() + func enterBackground() + func enterForeground() + func thumbnailImageAtCurrentTime() async -> CGImage? + func tracks(mediaType: AVFoundation.AVMediaType) -> [MediaPlayerTrack] + func select(track: some MediaPlayerTrack) +} +``` + +### MediaPlayback + +Base protocol for playback functionality: + +```swift +public protocol MediaPlayback: AnyObject { + var duration: TimeInterval { get } + var fileSize: Double { get } + var naturalSize: CGSize { get } + var chapters: [Chapter] { get } + var currentPlaybackTime: TimeInterval { get } + + func prepareToPlay() + func shutdown() + func seek(time: TimeInterval, completion: @escaping ((Bool) -> Void)) +} +``` + +### MediaPlayerDelegate + +Delegate for receiving player events: + +```swift +@MainActor +public protocol MediaPlayerDelegate: AnyObject { + func readyToPlay(player: some MediaPlayerProtocol) + func changeLoadState(player: some MediaPlayerProtocol) + func changeBuffering(player: some MediaPlayerProtocol, progress: Int) + func playBack(player: some MediaPlayerProtocol, loopCount: Int) + func finish(player: some MediaPlayerProtocol, error: Error?) +} +``` + +### MediaPlayerTrack + +Protocol for audio/video/subtitle track information: + +```swift +public protocol MediaPlayerTrack: AnyObject, CustomStringConvertible { + var trackID: Int32 { get } + var name: String { get } + var languageCode: String? { get } + var mediaType: AVFoundation.AVMediaType { get } + var nominalFrameRate: Float { get set } + var bitRate: Int64 { get } + var bitDepth: Int32 { get } + var isEnabled: Bool { get set } + var isImageSubtitle: Bool { get } + var rotation: Int16 { get } + var dovi: DOVIDecoderConfigurationRecord? { get } + var fieldOrder: FFmpegFieldOrder { get } + var formatDescription: CMFormatDescription? { get } +} +``` + +#### Extension Properties + +```swift +extension MediaPlayerTrack { + var language: String? // Localized language name + var codecType: FourCharCode + var dynamicRange: DynamicRange? + var colorSpace: CGColorSpace? + var mediaSubType: CMFormatDescription.MediaSubType + var audioStreamBasicDescription: AudioStreamBasicDescription? + var naturalSize: CGSize + var colorPrimaries: String? + var transferFunction: String? + var yCbCrMatrix: String? +} +``` + +### KSPlayerLayerDelegate + +Delegate for `KSPlayerLayer` events: + +```swift +@MainActor +public protocol KSPlayerLayerDelegate: AnyObject { + func player(layer: KSPlayerLayer, state: KSPlayerState) + func player(layer: KSPlayerLayer, currentTime: TimeInterval, totalTime: TimeInterval) + func player(layer: KSPlayerLayer, finish error: Error?) + func player(layer: KSPlayerLayer, bufferedCount: Int, consumeTime: TimeInterval) +} +``` + +### PlayerControllerDelegate + +Delegate for `PlayerView` events: + +```swift +public protocol PlayerControllerDelegate: AnyObject { + func playerController(state: KSPlayerState) + func playerController(currentTime: TimeInterval, totalTime: TimeInterval) + func playerController(finish error: Error?) + func playerController(maskShow: Bool) + func playerController(action: PlayerButtonType) + func playerController(bufferedCount: Int, consumeTime: TimeInterval) + func playerController(seek: TimeInterval) +} +``` + +### CapacityProtocol + +Buffer capacity information: + +```swift +public protocol CapacityProtocol { + var fps: Float { get } + var packetCount: Int { get } + var frameCount: Int { get } + var frameMaxCount: Int { get } + var isEndOfFile: Bool { get } + var mediaType: AVFoundation.AVMediaType { get } +} +``` + +## Enums + +### KSPlayerState + +Player state enumeration: + +```swift +public enum KSPlayerState: CustomStringConvertible { + case initialized // Player created + case preparing // Loading media + case readyToPlay // Ready to start playback + case buffering // Buffering data + case bufferFinished // Buffer sufficient, playing + case paused // Playback paused + case playedToTheEnd // Reached end of media + case error // Error occurred + + public var isPlaying: Bool // true for .buffering or .bufferFinished +} +``` + +### MediaPlaybackState + +Low-level playback state: + +```swift +public enum MediaPlaybackState: Int { + case idle + case playing + case paused + case seeking + case finished + case stopped +} +``` + +### MediaLoadState + +Loading state: + +```swift +public enum MediaLoadState: Int { + case idle + case loading + case playable +} +``` + +### DynamicRange + +HDR/SDR content range: + +```swift +public enum DynamicRange: Int32 { + case sdr = 0 + case hdr10 = 2 + case hlg = 3 + case dolbyVision = 5 + + static var availableHDRModes: [DynamicRange] // Device-supported modes +} +``` + +### DisplayEnum + +Video display mode: + +```swift +@MainActor +public enum DisplayEnum { + case plane // Normal 2D display + case vr // VR mode (spherical) + case vrBox // VR Box mode (side-by-side) +} +``` + +### FFmpegFieldOrder + +Video interlacing: + +```swift +public enum FFmpegFieldOrder: UInt8 { + case unknown = 0 + case progressive + case tt // Top coded first, top displayed first + case bb // Bottom coded first, bottom displayed first + case tb // Top coded first, bottom displayed first + case bt // Bottom coded first, top displayed first +} +``` + +### VideoInterlacingType + +Detected interlacing type: + +```swift +public enum VideoInterlacingType: String { + case tff // Top field first + case bff // Bottom field first + case progressive // Progressive scan + case undetermined +} +``` + +### ClockProcessType + +Internal clock synchronization: + +```swift +public enum ClockProcessType { + case remain + case next + case dropNextFrame + case dropNextPacket + case dropGOPPacket + case flush + case seek +} +``` + +### PlayerButtonType + +UI button types: + +```swift +public enum PlayerButtonType: Int { + case play = 101 + case pause + case back + case srt // Subtitles + case landscape // Fullscreen + case replay + case lock + case rate // Playback speed + case definition // Quality + case pictureInPicture + case audioSwitch + case videoSwitch +} +``` + +### KSPlayerTopBarShowCase + +Top bar visibility: + +```swift +public enum KSPlayerTopBarShowCase { + case always // Always show + case horizantalOnly // Only in landscape + case none // Never show +} +``` + +### KSPanDirection + +Gesture direction: + +```swift +public enum KSPanDirection { + case horizontal + case vertical +} +``` + +### TimeType + +Time formatting: + +```swift +public enum TimeType { + case min // MM:SS + case hour // H:MM:SS + case minOrHour // MM:SS or H:MM:SS based on duration + case millisecond // HH:MM:SS.ms +} +``` + +### LogLevel + +Logging levels: + +```swift +public enum LogLevel: Int32 { + case panic = 0 + case fatal = 8 + case error = 16 + case warning = 24 + case info = 32 + case verbose = 40 + case debug = 48 + case trace = 56 +} +``` + +## Structs + +### Chapter + +Video chapter information: + +```swift +public struct Chapter { + public let start: TimeInterval + public let end: TimeInterval + public let title: String +} +``` + +### LoadingState + +Buffer loading state: + +```swift +public struct LoadingState { + public let loadedTime: TimeInterval + public let progress: TimeInterval + public let packetCount: Int + public let frameCount: Int + public let isEndOfFile: Bool + public let isPlayable: Bool + public let isFirst: Bool + public let isSeek: Bool +} +``` + +### VideoAdaptationState + +Adaptive bitrate state: + +```swift +public struct VideoAdaptationState { + public struct BitRateState { + let bitRate: Int64 + let time: TimeInterval + } + + public let bitRates: [Int64] + public let duration: TimeInterval + public internal(set) var fps: Float + public internal(set) var bitRateStates: [BitRateState] + public internal(set) var currentPlaybackTime: TimeInterval + public internal(set) var isPlayable: Bool + public internal(set) var loadedCount: Int +} +``` + +### DOVIDecoderConfigurationRecord + +Dolby Vision configuration: + +```swift +public struct DOVIDecoderConfigurationRecord { + public let dv_version_major: UInt8 + public let dv_version_minor: UInt8 + public let dv_profile: UInt8 + public let dv_level: UInt8 + public let rpu_present_flag: UInt8 + public let el_present_flag: UInt8 + public let bl_present_flag: UInt8 + public let dv_bl_signal_compatibility_id: UInt8 +} +``` + +### KSClock + +Internal clock for A/V sync: + +```swift +public struct KSClock { + public private(set) var lastMediaTime: CFTimeInterval + public internal(set) var position: Int64 + public internal(set) var time: CMTime + + func getTime() -> TimeInterval +} +``` + +### TextPosition + +Subtitle text positioning: + +```swift +public struct TextPosition { + public var verticalAlign: VerticalAlignment = .bottom + public var horizontalAlign: HorizontalAlignment = .center + public var leftMargin: CGFloat = 0 + public var rightMargin: CGFloat = 0 + public var verticalMargin: CGFloat = 10 + public var edgeInsets: EdgeInsets { get } +} +``` + +## Classes + +### DynamicInfo + +Runtime playback information: + +```swift +public class DynamicInfo: ObservableObject { + public var metadata: [String: String] // Media metadata + public var bytesRead: Int64 // Bytes transferred + public var audioBitrate: Int // Current audio bitrate + public var videoBitrate: Int // Current video bitrate + + @Published + public var displayFPS: Double = 0.0 // Current display FPS + public var audioVideoSyncDiff: Double = 0.0 // A/V sync difference + public var droppedVideoFrameCount: UInt32 // Dropped frames + public var droppedVideoPacketCount: UInt32 // Dropped packets +} +``` + +## Error Handling + +### KSPlayerErrorCode + +```swift +public enum KSPlayerErrorCode: Int { + case unknown + case formatCreate + case formatOpenInput + case formatOutputCreate + case formatWriteHeader + case formatFindStreamInfo + case readFrame + case codecContextCreate + case codecContextSetParam + case codecContextFindDecoder + case codesContextOpen + case codecVideoSendPacket + case codecAudioSendPacket + case codecVideoReceiveFrame + case codecAudioReceiveFrame + case auidoSwrInit + case codecSubtitleSendPacket + case videoTracksUnplayable + case subtitleUnEncoding + case subtitleUnParse + case subtitleFormatUnSupport + case subtitleParamsEmpty +} +``` + +### Error Domain + +```swift +public let KSPlayerErrorDomain = "KSPlayerErrorDomain" +``` + +## Extensions + +### TimeInterval Formatting + +```swift +extension TimeInterval { + func toString(for type: TimeType) -> String +} + +// Example: +let time: TimeInterval = 3661 // 1 hour, 1 minute, 1 second +time.toString(for: .min) // "61:01" +time.toString(for: .hour) // "1:01:01" +time.toString(for: .minOrHour) // "1:01:01" +``` + +### Int Formatting + +```swift +extension Int { + func toString(for type: TimeType) -> String +} + +extension FixedWidthInteger { + var kmFormatted: String // "1.5K", "2.3M", etc. +} +``` + diff --git a/docs/ks-player/UIKitUsage.md b/docs/ks-player/UIKitUsage.md new file mode 100644 index 00000000..7b67f1b2 --- /dev/null +++ b/docs/ks-player/UIKitUsage.md @@ -0,0 +1,337 @@ +# UIKit Usage + +This guide covers using KSPlayer with UIKit in iOS applications. + +## IOSVideoPlayerView + +`IOSVideoPlayerView` is the main UIKit video player view for iOS. It extends `VideoPlayerView` with iOS-specific features like fullscreen, gestures, and AirPlay. + +### Basic Setup + +```swift +import KSPlayer + +class VideoViewController: UIViewController { + private var playerView: IOSVideoPlayerView! + + override func viewDidLoad() { + super.viewDidLoad() + + playerView = IOSVideoPlayerView() + view.addSubview(playerView) + + playerView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + playerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + playerView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + playerView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + playerView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + } +} +``` + +### Setting Video URL + +#### Simple URL + +```swift +let url = URL(string: "https://example.com/video.mp4")! +playerView.set(url: url, options: KSOptions()) +``` + +#### With KSPlayerResource + +```swift +let resource = KSPlayerResource( + url: URL(string: "https://example.com/video.mp4")!, + options: KSOptions(), + name: "Video Title", + cover: URL(string: "https://example.com/cover.jpg"), + subtitleURLs: [URL(string: "https://example.com/subtitle.srt")!] +) +playerView.set(resource: resource) +``` + +#### Multiple Definitions (Quality Options) + +```swift +let hdDefinition = KSPlayerResourceDefinition( + url: URL(string: "https://example.com/video_hd.mp4")!, + definition: "1080p", + options: KSOptions() +) + +let sdDefinition = KSPlayerResourceDefinition( + url: URL(string: "https://example.com/video_sd.mp4")!, + definition: "480p", + options: KSOptions() +) + +let resource = KSPlayerResource( + name: "Video Title", + definitions: [hdDefinition, sdDefinition], + cover: URL(string: "https://example.com/cover.jpg") +) + +playerView.set(resource: resource, definitionIndex: 0) +``` + +### KSPlayerResource + +```swift +public class KSPlayerResource { + public let name: String + public let definitions: [KSPlayerResourceDefinition] + public let cover: URL? + public let subtitleDataSouce: SubtitleDataSouce? + public var nowPlayingInfo: KSNowPlayableMetadata? + + // Convenience initializer for single URL + public convenience init( + url: URL, + options: KSOptions = KSOptions(), + name: String = "", + cover: URL? = nil, + subtitleURLs: [URL]? = nil + ) + + // Full initializer + public init( + name: String, + definitions: [KSPlayerResourceDefinition], + cover: URL? = nil, + subtitleDataSouce: SubtitleDataSouce? = nil + ) +} +``` + +### KSPlayerResourceDefinition + +```swift +public struct KSPlayerResourceDefinition { + public let url: URL + public let definition: String + public let options: KSOptions + + public init(url: URL, definition: String, options: KSOptions = KSOptions()) +} +``` + +### PlayerControllerDelegate + +Implement `PlayerControllerDelegate` to receive playback events: + +```swift +class VideoViewController: UIViewController, PlayerControllerDelegate { + override func viewDidLoad() { + super.viewDidLoad() + playerView.delegate = self + } + + func playerController(state: KSPlayerState) { + switch state { + case .initialized: + print("Player initialized") + case .preparing: + print("Preparing to play") + case .readyToPlay: + print("Ready to play") + case .buffering: + print("Buffering...") + case .bufferFinished: + print("Buffer finished, playing") + case .paused: + print("Paused") + case .playedToTheEnd: + print("Playback completed") + case .error: + print("Error occurred") + } + } + + func playerController(currentTime: TimeInterval, totalTime: TimeInterval) { + // Called periodically during playback + print("Progress: \(currentTime)/\(totalTime)") + } + + func playerController(finish error: Error?) { + if let error = error { + print("Playback error: \(error)") + } else { + print("Playback finished") + } + } + + func playerController(maskShow: Bool) { + // Controls visibility changed + } + + func playerController(action: PlayerButtonType) { + // Button pressed + } + + func playerController(bufferedCount: Int, consumeTime: TimeInterval) { + // Buffer status update (bufferedCount: 0 = first load) + } + + func playerController(seek: TimeInterval) { + // Seek completed + } +} +``` + +### Playback Control + +```swift +// Play +playerView.play() + +// Pause +playerView.pause() + +// Seek to time +playerView.seek(time: 30.0) { finished in + print("Seek completed: \(finished)") +} + +// Reset player +playerView.resetPlayer() +``` + +### Time Callbacks + +```swift +// Listen to time changes +playerView.playTimeDidChange = { currentTime, totalTime in + print("Current: \(currentTime), Total: \(totalTime)") +} + +// Back button handler +playerView.backBlock = { [weak self] in + self?.navigationController?.popViewController(animated: true) +} +``` + +### Fullscreen Control + +```swift +// Check if in fullscreen +let isFullscreen = playerView.landscapeButton.isSelected + +// Toggle fullscreen +playerView.updateUI(isFullScreen: true) // Enter fullscreen +playerView.updateUI(isFullScreen: false) // Exit fullscreen +``` + +### Customizing IOSVideoPlayerView + +Subclass to customize behavior: + +```swift +class CustomVideoPlayerView: IOSVideoPlayerView { + override func customizeUIComponents() { + super.customizeUIComponents() + + // Hide playback rate button + toolBar.playbackRateButton.isHidden = true + } + + override func onButtonPressed(type: PlayerButtonType, button: UIButton) { + if type == .landscape { + // Custom landscape button behavior + } else { + super.onButtonPressed(type: type, button: button) + } + } + + override func updateUI(isLandscape: Bool) { + super.updateUI(isLandscape: isLandscape) + // Additional UI updates for orientation + } +} +``` + +## VideoPlayerView (Base Class) + +`VideoPlayerView` is the base class with playback controls. `IOSVideoPlayerView` extends it for iOS. + +### Key Properties + +```swift +public var playerLayer: KSPlayerLayer? +public weak var delegate: PlayerControllerDelegate? +public let toolBar: PlayerToolBar +public let srtControl: SubtitleModel +public var playTimeDidChange: ((TimeInterval, TimeInterval) -> Void)? +public var backBlock: (() -> Void)? +``` + +### Accessing Player Layer + +```swift +// Get the underlying player +if let player = playerView.playerLayer?.player { + // Access player properties + let duration = player.duration + let currentTime = player.currentPlaybackTime + let isPlaying = player.isPlaying +} +``` + +## IOSVideoPlayerView Properties + +```swift +// UI Components +public var backButton: UIButton +public var maskImageView: UIImageView // Cover image +public var airplayStatusView: UIView // AirPlay status indicator +public var routeButton: AVRoutePickerView // AirPlay route picker +public var landscapeButton: UIControl // Fullscreen toggle +public var volumeViewSlider: UXSlider // Volume control + +// State +public var isMaskShow: Bool // Controls visibility +``` + +## PlayerButtonType + +Button types for `onButtonPressed`: + +```swift +public enum PlayerButtonType: Int { + case play = 101 + case pause + case back + case srt // Subtitle selection + case landscape // Fullscreen toggle + case replay + case lock // Lock controls + case rate // Playback rate + case definition // Quality selection + case pictureInPicture + case audioSwitch // Audio track + case videoSwitch // Video track +} +``` + +## Document Picker Integration + +`IOSVideoPlayerView` supports opening files via document picker: + +```swift +extension IOSVideoPlayerView: UIDocumentPickerDelegate { + public func documentPicker(_ controller: UIDocumentPickerViewController, + didPickDocumentsAt urls: [URL]) { + if let url = urls.first { + if url.isMovie || url.isAudio { + set(url: url, options: KSOptions()) + } else { + // Assume subtitle file + srtControl.selectedSubtitleInfo = URLSubtitleInfo(url: url) + } + } + } +} +``` + diff --git a/eas.json b/eas.json index 08f54fd4..c3a1fdad 100644 --- a/eas.json +++ b/eas.json @@ -45,14 +45,14 @@ }, "production": { "environment": "production", - "channel": "0.48.0", + "channel": "0.50.1", "android": { "image": "latest" } }, "production-apk": { "environment": "production", - "channel": "0.48.0", + "channel": "0.50.1", "android": { "buildType": "apk", "image": "latest" @@ -60,7 +60,7 @@ }, "production-apk-tv": { "environment": "production", - "channel": "0.48.0", + "channel": "0.50.1", "android": { "buildType": "apk", "image": "latest" diff --git a/hooks/useCreditSkipper.ts b/hooks/useCreditSkipper.ts index 91db8c29..40c1d695 100644 --- a/hooks/useCreditSkipper.ts +++ b/hooks/useCreditSkipper.ts @@ -7,23 +7,13 @@ import { useHaptic } from "./useHaptic"; /** * Custom hook to handle skipping credits in a media player. - * - * @param {string} itemId - The ID of the media item. - * @param {number} currentTime - The current playback time (ms for VLC, seconds otherwise). - * @param {function} seek - Function to seek to a position. - * @param {function} play - Function to resume playback. - * @param {boolean} isVlc - Whether using VLC player (uses milliseconds). - * @param {boolean} isOffline - Whether in offline mode. - * @param {Api|null} api - The Jellyfin API client. - * @param {DownloadedItem[]|undefined} downloadedFiles - Downloaded files for offline mode. - * @param {number|undefined} totalDuration - Total duration of the video (ms for VLC, seconds otherwise). + * The player reports time values in milliseconds. */ export const useCreditSkipper = ( itemId: string, currentTime: number, - seek: (time: number) => void, + seek: (ms: number) => void, play: () => void, - isVlc = false, isOffline = false, api: Api | null = null, downloadedFiles: DownloadedItem[] | undefined = undefined, @@ -32,21 +22,15 @@ export const useCreditSkipper = ( const [showSkipCreditButton, setShowSkipCreditButton] = useState(false); const lightHapticFeedback = useHaptic("light"); - // Convert currentTime to seconds for consistent comparison (matching useIntroSkipper pattern) - if (isVlc) { - currentTime = msToSeconds(currentTime); - } + // Convert ms to seconds for comparison with timestamps + const currentTimeSeconds = msToSeconds(currentTime); const totalDurationInSeconds = - isVlc && totalDuration ? msToSeconds(totalDuration) : totalDuration; + totalDuration != null ? msToSeconds(totalDuration) : undefined; // Regular function (not useCallback) to match useIntroSkipper pattern const wrappedSeek = (seconds: number) => { - if (isVlc) { - seek(secondsToMs(seconds)); - return; - } - seek(seconds); + seek(secondsToMs(seconds)); }; const { data: segments } = useSegments( @@ -60,7 +44,13 @@ export const useCreditSkipper = ( // Determine if there's content after credits (credits don't extend to video end) // Use a 5-second buffer to account for timing discrepancies const hasContentAfterCredits = (() => { - if (!creditTimestamps || !totalDurationInSeconds) return false; + if ( + !creditTimestamps || + totalDurationInSeconds == null || + !Number.isFinite(totalDurationInSeconds) + ) { + return false; + } const creditsEndToVideoEnd = totalDurationInSeconds - creditTimestamps.endTime; // If credits end more than 5 seconds before video ends, there's content after @@ -70,8 +60,8 @@ export const useCreditSkipper = ( useEffect(() => { if (creditTimestamps) { const shouldShow = - currentTime > creditTimestamps.startTime && - currentTime < creditTimestamps.endTime; + currentTimeSeconds > creditTimestamps.startTime && + currentTimeSeconds < creditTimestamps.endTime; setShowSkipCreditButton(shouldShow); } else { @@ -80,7 +70,7 @@ export const useCreditSkipper = ( setShowSkipCreditButton(false); } } - }, [creditTimestamps, currentTime, showSkipCreditButton]); + }, [creditTimestamps, currentTimeSeconds, showSkipCreditButton]); const skipCredit = useCallback(() => { if (!creditTimestamps) return; diff --git a/hooks/useDefaultPlaySettings.ts b/hooks/useDefaultPlaySettings.ts index b9ab1d44..51b0e9ee 100644 --- a/hooks/useDefaultPlaySettings.ts +++ b/hooks/useDefaultPlaySettings.ts @@ -1,51 +1,23 @@ import { type BaseItemDto } from "@jellyfin/sdk/lib/generated-client"; import { useMemo } from "react"; -import { BITRATES } from "@/components/BitrateSelector"; import type { Settings } from "@/utils/atoms/settings"; +import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings"; -// Used only for initial play settings. -const useDefaultPlaySettings = ( - item: BaseItemDto, - settings: Settings | null, -) => { - const playSettings = useMemo(() => { - // 1. Get first media source - const mediaSource = item.MediaSources?.[0]; - - // 2. Get default or preferred audio - const defaultAudioIndex = mediaSource?.DefaultAudioStreamIndex; - const preferedAudioIndex = mediaSource?.MediaStreams?.find( - (x) => - x.Type === "Audio" && - x.Language === - settings?.defaultAudioLanguage?.ThreeLetterISOLanguageName, - )?.Index; - - const firstAudioIndex = mediaSource?.MediaStreams?.find( - (x) => x.Type === "Audio", - )?.Index; - - // 4. Get default bitrate from settings or fallback to max - let bitrate = settings?.defaultBitrate ?? BITRATES[0]; - // value undefined seems to get lost in settings. This is just a failsafe - if (bitrate.key === BITRATES[0].key) { - bitrate = BITRATES[0]; - } +/** + * React hook wrapper for getDefaultPlaySettings. + * Used in UI components for initial playback (no previous track state). + */ +const useDefaultPlaySettings = (item: BaseItemDto, settings: Settings | null) => + useMemo(() => { + const { mediaSource, audioIndex, subtitleIndex, bitrate } = + getDefaultPlaySettings(item, settings); return { - defaultAudioIndex: - preferedAudioIndex ?? defaultAudioIndex ?? firstAudioIndex ?? undefined, - defaultSubtitleIndex: mediaSource?.DefaultSubtitleStreamIndex ?? -1, - defaultMediaSource: mediaSource ?? undefined, - defaultBitrate: bitrate ?? undefined, + defaultMediaSource: mediaSource, + defaultAudioIndex: audioIndex, + defaultSubtitleIndex: subtitleIndex, + defaultBitrate: bitrate, }; - }, [ - item.MediaSources, - settings?.defaultAudioLanguage, - settings?.defaultSubtitleLanguage, - ]); - - return playSettings; -}; + }, [item, settings]); export default useDefaultPlaySettings; diff --git a/hooks/useFavorite.ts b/hooks/useFavorite.ts index b9e47e08..9e9cab94 100644 --- a/hooks/useFavorite.ts +++ b/hooks/useFavorite.ts @@ -2,108 +2,92 @@ import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client"; import { getUserLibraryApi } from "@jellyfin/sdk/lib/utils/api"; import { useMutation, useQueryClient } from "@tanstack/react-query"; import { useAtom } from "jotai"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; export const useFavorite = (item: BaseItemDto) => { const queryClient = useQueryClient(); const [api] = useAtom(apiAtom); const [user] = useAtom(userAtom); - const type = "item"; - const [isFavorite, setIsFavorite] = useState(item.UserData?.IsFavorite); + const [isFavorite, setIsFavorite] = useState( + item.UserData?.IsFavorite, + ); useEffect(() => { setIsFavorite(item.UserData?.IsFavorite); }, [item.UserData?.IsFavorite]); - const updateItemInQueries = (newData: Partial) => { - queryClient.setQueryData( - [type, item.Id], - (old) => { - if (!old) return old; - return { - ...old, - ...newData, - UserData: { ...old.UserData, ...newData.UserData }, - }; - }, - ); - }; + const itemQueryKeyPrefix = useMemo( + () => ["item", item.Id] as const, + [item.Id], + ); - const markFavoriteMutation = useMutation({ - mutationFn: async () => { - if (api && user) { + const updateItemInQueries = useCallback( + (newData: Partial) => { + queryClient.setQueriesData( + { queryKey: itemQueryKeyPrefix }, + (old) => { + if (!old) return old; + return { + ...old, + ...newData, + UserData: { ...old.UserData, ...newData.UserData }, + }; + }, + ); + }, + [itemQueryKeyPrefix, queryClient], + ); + + const favoriteMutation = useMutation({ + mutationFn: async (nextIsFavorite: boolean) => { + if (!api || !user || !item.Id) return; + if (nextIsFavorite) { await getUserLibraryApi(api).markFavoriteItem({ userId: user.Id, - itemId: item.Id!, + itemId: item.Id, }); + return; } + await getUserLibraryApi(api).unmarkFavoriteItem({ + userId: user.Id, + itemId: item.Id, + }); }, - onMutate: async () => { - await queryClient.cancelQueries({ queryKey: [type, item.Id] }); - const previousItem = queryClient.getQueryData([ - type, - item.Id, - ]); - updateItemInQueries({ UserData: { IsFavorite: true } }); + onMutate: async (nextIsFavorite: boolean) => { + await queryClient.cancelQueries({ queryKey: itemQueryKeyPrefix }); - return { previousItem }; + const previousIsFavorite = isFavorite; + const previousQueries = queryClient.getQueriesData({ + queryKey: itemQueryKeyPrefix, + }); + + setIsFavorite(nextIsFavorite); + updateItemInQueries({ UserData: { IsFavorite: nextIsFavorite } }); + + return { previousIsFavorite, previousQueries }; }, - onError: (_err, _variables, context) => { - if (context?.previousItem) { - queryClient.setQueryData([type, item.Id], context.previousItem); + onError: (_err, _nextIsFavorite, context) => { + if (context?.previousQueries) { + for (const [queryKey, data] of context.previousQueries) { + queryClient.setQueryData(queryKey, data); + } } + setIsFavorite(context?.previousIsFavorite); }, onSettled: () => { - queryClient.invalidateQueries({ queryKey: [type, item.Id] }); + queryClient.invalidateQueries({ queryKey: itemQueryKeyPrefix }); queryClient.invalidateQueries({ queryKey: ["home", "favorites"] }); - setIsFavorite(true); }, }); - const unmarkFavoriteMutation = useMutation({ - mutationFn: async () => { - if (api && user) { - await getUserLibraryApi(api).unmarkFavoriteItem({ - userId: user.Id, - itemId: item.Id!, - }); - } - }, - onMutate: async () => { - await queryClient.cancelQueries({ queryKey: [type, item.Id] }); - const previousItem = queryClient.getQueryData([ - type, - item.Id, - ]); - updateItemInQueries({ UserData: { IsFavorite: false } }); - - return { previousItem }; - }, - onError: (_err, _variables, context) => { - if (context?.previousItem) { - queryClient.setQueryData([type, item.Id], context.previousItem); - } - }, - onSettled: () => { - queryClient.invalidateQueries({ queryKey: [type, item.Id] }); - queryClient.invalidateQueries({ queryKey: ["home", "favorites"] }); - setIsFavorite(false); - }, - }); - - const toggleFavorite = () => { - if (isFavorite) { - unmarkFavoriteMutation.mutate(); - } else { - markFavoriteMutation.mutate(); - } - }; + const toggleFavorite = useCallback(() => { + favoriteMutation.mutate(!isFavorite); + }, [favoriteMutation, isFavorite]); return { isFavorite, toggleFavorite, - markFavoriteMutation, - unmarkFavoriteMutation, + favoriteMutation, }; }; diff --git a/hooks/useIntroSkipper.ts b/hooks/useIntroSkipper.ts index d11ed511..eeed9833 100644 --- a/hooks/useIntroSkipper.ts +++ b/hooks/useIntroSkipper.ts @@ -7,31 +7,26 @@ import { useHaptic } from "./useHaptic"; /** * Custom hook to handle skipping intros in a media player. + * MPV player uses milliseconds for time. * - * @param {number} currentTime - The current playback time in seconds. + * @param {number} currentTime - The current playback time in milliseconds. */ export const useIntroSkipper = ( itemId: string, currentTime: number, - seek: (ticks: number) => void, + seek: (ms: number) => void, play: () => void, - isVlc = false, isOffline = false, api: Api | null = null, downloadedFiles: DownloadedItem[] | undefined = undefined, ) => { const [showSkipButton, setShowSkipButton] = useState(false); - if (isVlc) { - currentTime = msToSeconds(currentTime); - } + // Convert ms to seconds for comparison with timestamps + const currentTimeSeconds = msToSeconds(currentTime); const lightHapticFeedback = useHaptic("light"); const wrappedSeek = (seconds: number) => { - if (isVlc) { - seek(secondsToMs(seconds)); - return; - } - seek(seconds); + seek(secondsToMs(seconds)); }; const { data: segments } = useSegments( @@ -45,8 +40,8 @@ export const useIntroSkipper = ( useEffect(() => { if (introTimestamps) { const shouldShow = - currentTime > introTimestamps.startTime && - currentTime < introTimestamps.endTime; + currentTimeSeconds > introTimestamps.startTime && + currentTimeSeconds < introTimestamps.endTime; setShowSkipButton(shouldShow); } else { @@ -54,7 +49,7 @@ export const useIntroSkipper = ( setShowSkipButton(false); } } - }, [introTimestamps, currentTime, showSkipButton]); + }, [introTimestamps, currentTimeSeconds, showSkipButton]); const skipIntro = useCallback(() => { if (!introTimestamps) return; diff --git a/hooks/useItemPeopleQuery.ts b/hooks/useItemPeopleQuery.ts new file mode 100644 index 00000000..2dbad062 --- /dev/null +++ b/hooks/useItemPeopleQuery.ts @@ -0,0 +1,37 @@ +import type { + BaseItemPerson, + ItemFields, +} from "@jellyfin/sdk/lib/generated-client/models"; +import { getItemsApi } from "@jellyfin/sdk/lib/utils/api"; +import { useQuery } from "@tanstack/react-query"; +import { useAtom } from "jotai"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; + +export const useItemPeopleQuery = ( + itemId: string | undefined, + enabled: boolean, +) => { + const [api] = useAtom(apiAtom); + const [user] = useAtom(userAtom); + + return useQuery({ + queryKey: ["item", itemId, "people"], + queryFn: async () => { + if (!api || !user?.Id || !itemId) return []; + + const response = await getItemsApi(api).getItems({ + ids: [itemId], + userId: user.Id, + fields: ["People" satisfies ItemFields], + }); + + const people = response.data.Items?.[0]?.People; + return Array.isArray(people) ? people : []; + }, + enabled: !!api && !!user?.Id && !!itemId && enabled, + staleTime: 10 * 60 * 1000, + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, + }); +}; diff --git a/hooks/useJellyseerr.ts b/hooks/useJellyseerr.ts index d57b9ebc..bba24fd4 100644 --- a/hooks/useJellyseerr.ts +++ b/hooks/useJellyseerr.ts @@ -528,7 +528,8 @@ export const useJellyseerr = () => { }; const jellyseerrRegion = useMemo( - () => jellyseerrUser?.settings?.region || "US", + // streamingRegion and discoverRegion exists. region doesn't + () => jellyseerrUser?.settings?.discoverRegion || "US", [jellyseerrUser], ); diff --git a/hooks/useMarkAsPlayed.ts b/hooks/useMarkAsPlayed.ts index c789e1bd..e21687fa 100644 --- a/hooks/useMarkAsPlayed.ts +++ b/hooks/useMarkAsPlayed.ts @@ -1,25 +1,77 @@ import type { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models"; +import { useQueryClient } from "@tanstack/react-query"; +import { useCallback } from "react"; import { useHaptic } from "./useHaptic"; import { usePlaybackManager } from "./usePlaybackManager"; import { useInvalidatePlaybackProgressCache } from "./useRevalidatePlaybackProgressCache"; export const useMarkAsPlayed = (items: BaseItemDto[]) => { + const queryClient = useQueryClient(); const lightHapticFeedback = useHaptic("light"); const { markItemPlayed, markItemUnplayed } = usePlaybackManager(); const invalidatePlaybackProgressCache = useInvalidatePlaybackProgressCache(); - const toggle = async (played: boolean) => { - lightHapticFeedback(); - // Process all items - await Promise.all( - items.map((item) => { - if (!item.Id) return Promise.resolve(); - return played ? markItemPlayed(item.Id) : markItemUnplayed(item.Id); - }), - ); + const toggle = useCallback( + async (played: boolean) => { + lightHapticFeedback(); - await invalidatePlaybackProgressCache(); - }; + const itemIds = items.map((item) => item.Id).filter(Boolean) as string[]; + + const previousQueriesByItemId = itemIds.map((itemId) => ({ + itemId, + queries: queryClient.getQueriesData({ + queryKey: ["item", itemId], + }), + })); + + for (const itemId of itemIds) { + queryClient.setQueriesData( + { queryKey: ["item", itemId] }, + (old) => { + if (!old) return old; + return { + ...old, + UserData: { + ...old.UserData, + Played: played, + PlaybackPositionTicks: 0, + PlayedPercentage: 0, + }, + }; + }, + ); + } + + // Process all items + try { + await Promise.all( + items.map((item) => { + if (!item.Id) return Promise.resolve(); + return played ? markItemPlayed(item.Id) : markItemUnplayed(item.Id); + }), + ); + } catch (_error) { + for (const { queries } of previousQueriesByItemId) { + for (const [queryKey, data] of queries) { + queryClient.setQueryData(queryKey, data); + } + } + } finally { + await invalidatePlaybackProgressCache(); + for (const itemId of itemIds) { + queryClient.invalidateQueries({ queryKey: ["item", itemId] }); + } + } + }, + [ + invalidatePlaybackProgressCache, + items, + lightHapticFeedback, + markItemPlayed, + markItemUnplayed, + queryClient, + ], + ); return toggle; }; diff --git a/hooks/useOrientation.ts b/hooks/useOrientation.ts index 3483aefb..9d77111d 100644 --- a/hooks/useOrientation.ts +++ b/hooks/useOrientation.ts @@ -1,5 +1,5 @@ import type { OrientationChangeEvent } from "expo-screen-orientation"; -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { Platform } from "react-native"; import { addOrientationChangeListener, @@ -53,27 +53,28 @@ export const useOrientation = () => { }; }, []); - const lockOrientation = async ( - lock: (typeof OrientationLock)[keyof typeof OrientationLock], - ) => { - if (Platform.isTV) return; + const lockOrientation = useCallback( + async (lock: (typeof OrientationLock)[keyof typeof OrientationLock]) => { + if (Platform.isTV) return; - if (lock === OrientationLock.DEFAULT) { - await unlockAsync(); - } else { - await lockAsync(lock); - } - }; + if (lock === OrientationLock.DEFAULT) { + await unlockAsync(); + } else { + await lockAsync(lock); + } + }, + [], + ); - const unlockOrientationFn = async () => { + const unlockOrientation = useCallback(async () => { if (Platform.isTV) return; await unlockAsync(); - }; + }, []); return { orientation, setOrientation, lockOrientation, - unlockOrientation: unlockOrientationFn, + unlockOrientation, }; }; diff --git a/hooks/usePlaybackManager.ts b/hooks/usePlaybackManager.ts index 5ea237cf..a38f718a 100644 --- a/hooks/usePlaybackManager.ts +++ b/hooks/usePlaybackManager.ts @@ -237,6 +237,7 @@ export const usePlaybackManager = ({ }); } catch (error) { console.error("Failed to mark item as played on server", error); + throw error; } } }; @@ -278,6 +279,7 @@ export const usePlaybackManager = ({ }); } catch (error) { console.error("Failed to mark item as unplayed on server", error); + throw error; } } }; diff --git a/hooks/useWatchlistMutations.ts b/hooks/useWatchlistMutations.ts new file mode 100644 index 00000000..ad15c0ac --- /dev/null +++ b/hooks/useWatchlistMutations.ts @@ -0,0 +1,295 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { useAtomValue } from "jotai"; +import { useCallback } from "react"; +import { toast } from "sonner-native"; +import { apiAtom } from "@/providers/JellyfinProvider"; +import { useSettings } from "@/utils/atoms/settings"; +import { createStreamystatsApi } from "@/utils/streamystats/api"; +import type { + CreateWatchlistRequest, + StreamystatsWatchlist, + UpdateWatchlistRequest, +} from "@/utils/streamystats/types"; + +/** + * Hook to create a new watchlist + */ +export const useCreateWatchlist = () => { + const api = useAtomValue(apiAtom); + const { settings } = useSettings(); + const queryClient = useQueryClient(); + + const mutation = useMutation({ + mutationFn: async ( + data: CreateWatchlistRequest, + ): Promise => { + if (!settings?.streamyStatsServerUrl || !api?.accessToken) { + throw new Error("Streamystats not configured"); + } + + const streamystatsApi = createStreamystatsApi({ + serverUrl: settings.streamyStatsServerUrl, + jellyfinToken: api.accessToken, + }); + + const response = await streamystatsApi.createWatchlist(data); + if (response.error) { + throw new Error(response.error); + } + return response.data; + }, + onSuccess: () => { + queryClient.invalidateQueries({ + queryKey: ["streamystats", "watchlists"], + }); + toast.success("Watchlist created"); + }, + onError: (error: Error) => { + toast.error(error.message || "Failed to create watchlist"); + }, + }); + + return mutation; +}; + +/** + * Hook to update a watchlist + */ +export const useUpdateWatchlist = () => { + const api = useAtomValue(apiAtom); + const { settings } = useSettings(); + const queryClient = useQueryClient(); + + const mutation = useMutation({ + mutationFn: async ({ + watchlistId, + data, + }: { + watchlistId: number; + data: UpdateWatchlistRequest; + }): Promise => { + if (!settings?.streamyStatsServerUrl || !api?.accessToken) { + throw new Error("Streamystats not configured"); + } + + const streamystatsApi = createStreamystatsApi({ + serverUrl: settings.streamyStatsServerUrl, + jellyfinToken: api.accessToken, + }); + + const response = await streamystatsApi.updateWatchlist(watchlistId, data); + if (response.error) { + throw new Error(response.error); + } + return response.data; + }, + onSuccess: (_data, variables) => { + queryClient.invalidateQueries({ + queryKey: ["streamystats", "watchlists"], + }); + queryClient.invalidateQueries({ + queryKey: ["streamystats", "watchlist", variables.watchlistId], + }); + toast.success("Watchlist updated"); + }, + onError: (error: Error) => { + toast.error(error.message || "Failed to update watchlist"); + }, + }); + + return mutation; +}; + +/** + * Hook to delete a watchlist + */ +export const useDeleteWatchlist = () => { + const api = useAtomValue(apiAtom); + const { settings } = useSettings(); + const queryClient = useQueryClient(); + + const mutation = useMutation({ + mutationFn: async (watchlistId: number): Promise => { + if (!settings?.streamyStatsServerUrl || !api?.accessToken) { + throw new Error("Streamystats not configured"); + } + + const streamystatsApi = createStreamystatsApi({ + serverUrl: settings.streamyStatsServerUrl, + jellyfinToken: api.accessToken, + }); + + const response = await streamystatsApi.deleteWatchlist(watchlistId); + if (response.error) { + throw new Error(response.error); + } + }, + onSuccess: (_data, watchlistId) => { + queryClient.invalidateQueries({ + queryKey: ["streamystats", "watchlists"], + }); + queryClient.removeQueries({ + queryKey: ["streamystats", "watchlist", watchlistId], + }); + toast.success("Watchlist deleted"); + }, + onError: (error: Error) => { + toast.error(error.message || "Failed to delete watchlist"); + }, + }); + + return mutation; +}; + +/** + * Hook to add an item to a watchlist with optimistic update + */ +export const useAddToWatchlist = () => { + const api = useAtomValue(apiAtom); + const { settings } = useSettings(); + const queryClient = useQueryClient(); + + const mutation = useMutation({ + mutationFn: async ({ + watchlistId, + itemId, + }: { + watchlistId: number; + itemId: string; + watchlistName?: string; + }): Promise => { + if (!settings?.streamyStatsServerUrl || !api?.accessToken) { + throw new Error("Streamystats not configured"); + } + + const streamystatsApi = createStreamystatsApi({ + serverUrl: settings.streamyStatsServerUrl, + jellyfinToken: api.accessToken, + }); + + const response = await streamystatsApi.addWatchlistItem( + watchlistId, + itemId, + ); + if (response.error) { + throw new Error(response.error); + } + }, + onSuccess: (_data, variables) => { + queryClient.invalidateQueries({ + queryKey: ["streamystats", "watchlist", variables.watchlistId], + }); + queryClient.invalidateQueries({ + queryKey: ["streamystats", "watchlistItems", variables.watchlistId], + }); + queryClient.invalidateQueries({ + queryKey: ["streamystats", "itemInWatchlists", variables.itemId], + }); + if (variables.watchlistName) { + toast.success(`Added to ${variables.watchlistName}`); + } else { + toast.success("Added to watchlist"); + } + }, + onError: (error: Error) => { + toast.error(error.message || "Failed to add to watchlist"); + }, + }); + + return mutation; +}; + +/** + * Hook to remove an item from a watchlist with optimistic update + */ +export const useRemoveFromWatchlist = () => { + const api = useAtomValue(apiAtom); + const { settings } = useSettings(); + const queryClient = useQueryClient(); + + const mutation = useMutation({ + mutationFn: async ({ + watchlistId, + itemId, + }: { + watchlistId: number; + itemId: string; + watchlistName?: string; + }): Promise => { + if (!settings?.streamyStatsServerUrl || !api?.accessToken) { + throw new Error("Streamystats not configured"); + } + + const streamystatsApi = createStreamystatsApi({ + serverUrl: settings.streamyStatsServerUrl, + jellyfinToken: api.accessToken, + }); + + const response = await streamystatsApi.removeWatchlistItem( + watchlistId, + itemId, + ); + if (response.error) { + throw new Error(response.error); + } + }, + onSuccess: (_data, variables) => { + queryClient.invalidateQueries({ + queryKey: ["streamystats", "watchlist", variables.watchlistId], + }); + queryClient.invalidateQueries({ + queryKey: ["streamystats", "watchlistItems", variables.watchlistId], + }); + queryClient.invalidateQueries({ + queryKey: ["streamystats", "itemInWatchlists", variables.itemId], + }); + if (variables.watchlistName) { + toast.success(`Removed from ${variables.watchlistName}`); + } else { + toast.success("Removed from watchlist"); + } + }, + onError: (error: Error) => { + toast.error(error.message || "Failed to remove from watchlist"); + }, + }); + + return mutation; +}; + +/** + * Hook to toggle an item in a watchlist + */ +export const useToggleWatchlistItem = () => { + const addMutation = useAddToWatchlist(); + const removeMutation = useRemoveFromWatchlist(); + + const toggle = useCallback( + async (params: { + watchlistId: number; + itemId: string; + isInWatchlist: boolean; + watchlistName?: string; + }) => { + if (params.isInWatchlist) { + await removeMutation.mutateAsync({ + watchlistId: params.watchlistId, + itemId: params.itemId, + watchlistName: params.watchlistName, + }); + } else { + await addMutation.mutateAsync({ + watchlistId: params.watchlistId, + itemId: params.itemId, + watchlistName: params.watchlistName, + }); + } + }, + [addMutation, removeMutation], + ); + + return { + toggle, + isLoading: addMutation.isPending || removeMutation.isPending, + }; +}; diff --git a/hooks/useWatchlists.ts b/hooks/useWatchlists.ts new file mode 100644 index 00000000..84891aa7 --- /dev/null +++ b/hooks/useWatchlists.ts @@ -0,0 +1,290 @@ +import type { + BaseItemDto, + PublicSystemInfo, +} from "@jellyfin/sdk/lib/generated-client/models"; +import { getItemsApi, getSystemApi } from "@jellyfin/sdk/lib/utils/api"; +import { useQuery } from "@tanstack/react-query"; +import { useAtomValue } from "jotai"; +import { useMemo } from "react"; +import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; +import { useSettings } from "@/utils/atoms/settings"; +import { createStreamystatsApi } from "@/utils/streamystats/api"; +import type { + GetWatchlistItemsParams, + StreamystatsWatchlist, +} from "@/utils/streamystats/types"; + +/** + * Hook to check if Streamystats is configured + */ +export const useStreamystatsEnabled = () => { + const { settings } = useSettings(); + return useMemo( + () => Boolean(settings?.streamyStatsServerUrl), + [settings?.streamyStatsServerUrl], + ); +}; + +/** + * Hook to get the Jellyfin server ID needed for Streamystats API calls + */ +export const useJellyfinServerId = () => { + const api = useAtomValue(apiAtom); + const streamystatsEnabled = useStreamystatsEnabled(); + + const { data: serverInfo, isLoading } = useQuery({ + queryKey: ["jellyfin", "serverInfo"], + queryFn: async (): Promise => { + if (!api) return null; + const response = await getSystemApi(api).getPublicSystemInfo(); + return response.data; + }, + enabled: Boolean(api) && streamystatsEnabled, + staleTime: 60 * 60 * 1000, // 1 hour + }); + + return { + jellyfinServerId: serverInfo?.Id, + isLoading, + }; +}; + +/** + * Hook to get all watchlists (own + public) + */ +export const useWatchlistsQuery = () => { + const api = useAtomValue(apiAtom); + const user = useAtomValue(userAtom); + const { settings } = useSettings(); + const streamystatsEnabled = useStreamystatsEnabled(); + + return useQuery({ + queryKey: [ + "streamystats", + "watchlists", + settings?.streamyStatsServerUrl, + user?.Id, + ], + queryFn: async (): Promise => { + if (!settings?.streamyStatsServerUrl || !api?.accessToken) { + return []; + } + + const streamystatsApi = createStreamystatsApi({ + serverUrl: settings.streamyStatsServerUrl, + jellyfinToken: api.accessToken, + }); + + const response = await streamystatsApi.getWatchlists(); + return response.data || []; + }, + enabled: streamystatsEnabled && Boolean(api?.accessToken), + staleTime: 60 * 1000, // 1 minute + }); +}; + +/** + * Hook to get a single watchlist with its items + */ +export const useWatchlistDetailQuery = ( + watchlistId: number | undefined, + params?: GetWatchlistItemsParams, +) => { + const api = useAtomValue(apiAtom); + const user = useAtomValue(userAtom); + const { settings } = useSettings(); + const streamystatsEnabled = useStreamystatsEnabled(); + + return useQuery({ + queryKey: [ + "streamystats", + "watchlist", + watchlistId, + params?.type, + params?.sort, + settings?.streamyStatsServerUrl, + ], + queryFn: async (): Promise => { + if ( + !settings?.streamyStatsServerUrl || + !api?.accessToken || + !watchlistId + ) { + return null; + } + + const streamystatsApi = createStreamystatsApi({ + serverUrl: settings.streamyStatsServerUrl, + jellyfinToken: api.accessToken, + }); + + const response = await streamystatsApi.getWatchlistDetail( + watchlistId, + params, + ); + return response.data || null; + }, + enabled: + streamystatsEnabled && + Boolean(api?.accessToken) && + Boolean(watchlistId) && + Boolean(user?.Id), + staleTime: 60 * 1000, // 1 minute + }); +}; + +/** + * Hook to get watchlist items enriched with Jellyfin item data + */ +export const useWatchlistItemsQuery = ( + watchlistId: number | undefined, + params?: GetWatchlistItemsParams, +) => { + const api = useAtomValue(apiAtom); + const user = useAtomValue(userAtom); + const { settings } = useSettings(); + const { jellyfinServerId } = useJellyfinServerId(); + const streamystatsEnabled = useStreamystatsEnabled(); + + return useQuery({ + queryKey: [ + "streamystats", + "watchlistItems", + watchlistId, + jellyfinServerId, + params?.type, + params?.sort, + settings?.streamyStatsServerUrl, + ], + queryFn: async (): Promise => { + if ( + !settings?.streamyStatsServerUrl || + !api?.accessToken || + !watchlistId || + !jellyfinServerId || + !user?.Id + ) { + return []; + } + + const streamystatsApi = createStreamystatsApi({ + serverUrl: settings.streamyStatsServerUrl, + jellyfinToken: api.accessToken, + }); + + // Get watchlist item IDs from Streamystats + const watchlistDetail = await streamystatsApi.getWatchlistItemIds({ + watchlistId, + jellyfinServerId, + }); + + const itemIds = watchlistDetail.data?.items; + if (!itemIds?.length) { + return []; + } + + // Fetch full item details from Jellyfin + const response = await getItemsApi(api).getItems({ + userId: user.Id, + ids: itemIds, + fields: [ + "PrimaryImageAspectRatio", + "Genres", + "Overview", + "DateCreated", + ], + enableImageTypes: ["Primary", "Backdrop", "Thumb"], + }); + + return response.data.Items || []; + }, + enabled: + streamystatsEnabled && + Boolean(api?.accessToken) && + Boolean(watchlistId) && + Boolean(jellyfinServerId) && + Boolean(user?.Id), + staleTime: 60 * 1000, // 1 minute + }); +}; + +/** + * Hook to get the user's own watchlists only (for add-to-watchlist picker) + */ +export const useMyWatchlistsQuery = () => { + const user = useAtomValue(userAtom); + const { data: allWatchlists, ...rest } = useWatchlistsQuery(); + + const myWatchlists = useMemo(() => { + if (!allWatchlists || !user?.Id) return []; + return allWatchlists.filter((w) => w.userId === user.Id); + }, [allWatchlists, user?.Id]); + + return { + data: myWatchlists, + ...rest, + }; +}; + +/** + * Hook to check which of the user's watchlists contain a specific item + */ +export const useItemInWatchlists = (itemId: string | undefined) => { + const { data: myWatchlists } = useMyWatchlistsQuery(); + const api = useAtomValue(apiAtom); + const { settings } = useSettings(); + const { jellyfinServerId } = useJellyfinServerId(); + const streamystatsEnabled = useStreamystatsEnabled(); + + return useQuery({ + queryKey: [ + "streamystats", + "itemInWatchlists", + itemId, + jellyfinServerId, + settings?.streamyStatsServerUrl, + ], + queryFn: async (): Promise => { + if ( + !settings?.streamyStatsServerUrl || + !api?.accessToken || + !itemId || + !jellyfinServerId || + !myWatchlists?.length + ) { + return []; + } + + const streamystatsApi = createStreamystatsApi({ + serverUrl: settings.streamyStatsServerUrl, + jellyfinToken: api.accessToken, + }); + + // Check each watchlist to see if it contains the item + const watchlistsContainingItem: number[] = []; + + for (const watchlist of myWatchlists) { + try { + const detail = await streamystatsApi.getWatchlistItemIds({ + watchlistId: watchlist.id, + jellyfinServerId, + }); + if (detail.data?.items?.includes(itemId)) { + watchlistsContainingItem.push(watchlist.id); + } + } catch { + // Ignore errors for individual watchlists + } + } + + return watchlistsContainingItem; + }, + enabled: + streamystatsEnabled && + Boolean(api?.accessToken) && + Boolean(itemId) && + Boolean(jellyfinServerId) && + Boolean(myWatchlists?.length), + staleTime: 30 * 1000, // 30 seconds + }); +}; diff --git a/hooks/useWebsockets.ts b/hooks/useWebsockets.ts index 32b110a4..87dd9162 100644 --- a/hooks/useWebsockets.ts +++ b/hooks/useWebsockets.ts @@ -96,8 +96,6 @@ export const useWebSocket = ({ | Record | undefined; // Arguments are Dictionary - console.log("[WS] ~ ", lastMessage); - if (command === "PlayPause") { console.log("Command ~ PlayPause"); togglePlay(); diff --git a/index.js b/index.js index 8e1414bc..dbc9139f 100644 --- a/index.js +++ b/index.js @@ -1,2 +1,6 @@ import "react-native-url-polyfill/auto"; +import TrackPlayer from "react-native-track-player"; +import { PlaybackService } from "./services/PlaybackService"; import "expo-router/entry"; + +TrackPlayer.registerPlaybackService(() => PlaybackService); diff --git a/modules/VlcPlayerView.tsx b/modules/VlcPlayerView.tsx index a5cac3af..9d262e7b 100644 --- a/modules/VlcPlayerView.tsx +++ b/modules/VlcPlayerView.tsx @@ -112,14 +112,19 @@ const VlcPlayerView = React.forwardRef( ...otherProps } = props; - const processedSource: VlcPlayerSource = + const baseSource: VlcPlayerSource = typeof source === "string" ? ({ uri: source } as unknown as VlcPlayerSource) : source; - if (processedSource.startPosition !== undefined) { - processedSource.startPosition = Math.floor(processedSource.startPosition); - } + // Create a new object to avoid mutating frozen source + const processedSource: VlcPlayerSource = { + ...baseSource, + startPosition: + baseSource.startPosition !== undefined + ? Math.floor(baseSource.startPosition) + : undefined, + }; return ( + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback + } + } + project.android { + compileSdkVersion safeExtGet("compileSdkVersion", 36) + defaultConfig { + minSdkVersion safeExtGet("minSdkVersion", 24) + targetSdkVersion safeExtGet("targetSdkVersion", 36) + } + } +} + +android { + namespace "expo.modules.mpvplayer" + defaultConfig { + versionCode 1 + versionName "0.7.6" + } + lintOptions { + abortOnError false + } +} diff --git a/modules/mpv-player/android/src/main/AndroidManifest.xml b/modules/mpv-player/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..bdae66c8 --- /dev/null +++ b/modules/mpv-player/android/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + + diff --git a/modules/mpv-player/android/src/main/java/expo/modules/mpvplayer/MpvPlayerModule.kt b/modules/mpv-player/android/src/main/java/expo/modules/mpvplayer/MpvPlayerModule.kt new file mode 100644 index 00000000..7c8a4b00 --- /dev/null +++ b/modules/mpv-player/android/src/main/java/expo/modules/mpvplayer/MpvPlayerModule.kt @@ -0,0 +1,50 @@ +package expo.modules.mpvplayer + +import expo.modules.kotlin.modules.Module +import expo.modules.kotlin.modules.ModuleDefinition +import java.net.URL + +class MpvPlayerModule : Module() { + // Each module class must implement the definition function. The definition consists of components + // that describes the module's functionality and behavior. + // See https://docs.expo.dev/modules/module-api for more details about available components. + override fun definition() = ModuleDefinition { + // Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument. + // Can be inferred from module's class name, but it's recommended to set it explicitly for clarity. + // The module will be accessible from `requireNativeModule('MpvPlayer')` in JavaScript. + Name("MpvPlayer") + + // Defines constant property on the module. + Constant("PI") { + Math.PI + } + + // Defines event names that the module can send to JavaScript. + Events("onChange") + + // Defines a JavaScript synchronous function that runs the native code on the JavaScript thread. + Function("hello") { + "Hello world! 👋" + } + + // Defines a JavaScript function that always returns a Promise and whose native code + // is by default dispatched on the different thread than the JavaScript runtime runs on. + AsyncFunction("setValueAsync") { value: String -> + // Send an event to JavaScript. + sendEvent("onChange", mapOf( + "value" to value + )) + } + + // Enables the module to be used as a native view. Definition components that are accepted as part of + // the view definition: Prop, Events. + View(MpvPlayerView::class) { + // Defines a setter for the `url` prop. + Prop("url") { view: MpvPlayerView, url: URL -> + view.webView.loadUrl(url.toString()) + } + // Defines an event that the view can send to JavaScript. + Events("onLoad") + } + } +} diff --git a/modules/mpv-player/android/src/main/java/expo/modules/mpvplayer/MpvPlayerView.kt b/modules/mpv-player/android/src/main/java/expo/modules/mpvplayer/MpvPlayerView.kt new file mode 100644 index 00000000..1c35cb5f --- /dev/null +++ b/modules/mpv-player/android/src/main/java/expo/modules/mpvplayer/MpvPlayerView.kt @@ -0,0 +1,30 @@ +package expo.modules.mpvplayer + +import android.content.Context +import android.webkit.WebView +import android.webkit.WebViewClient +import expo.modules.kotlin.AppContext +import expo.modules.kotlin.viewevent.EventDispatcher +import expo.modules.kotlin.views.ExpoView + +class MpvPlayerView(context: Context, appContext: AppContext) : ExpoView(context, appContext) { + // Creates and initializes an event dispatcher for the `onLoad` event. + // The name of the event is inferred from the value and needs to match the event name defined in the module. + private val onLoad by EventDispatcher() + + // Defines a WebView that will be used as the root subview. + internal val webView = WebView(context).apply { + layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView, url: String) { + // Sends an event to JavaScript. Triggers a callback defined on the view component in JavaScript. + onLoad(mapOf("url" to url)) + } + } + } + + init { + // Adds the WebView to the view hierarchy. + addView(webView) + } +} diff --git a/modules/mpv-player/expo-module.config.json b/modules/mpv-player/expo-module.config.json new file mode 100644 index 00000000..b9ad072b --- /dev/null +++ b/modules/mpv-player/expo-module.config.json @@ -0,0 +1,6 @@ +{ + "platforms": ["android", "web"], + "android": { + "modules": ["expo.modules.mpvplayer.MpvPlayerModule"] + } +} diff --git a/modules/mpv-player/index.ts b/modules/mpv-player/index.ts new file mode 100644 index 00000000..cab14e34 --- /dev/null +++ b/modules/mpv-player/index.ts @@ -0,0 +1,6 @@ +// Reexport the native module. On web, it will be resolved to MpvPlayerModule.web.ts +// and on native platforms to MpvPlayerModule.ts + +export * from "./src/MpvPlayer.types"; +export { default } from "./src/MpvPlayerModule"; +export { default as MpvPlayerView } from "./src/MpvPlayerView"; diff --git a/modules/mpv-player/ios/Logger.swift b/modules/mpv-player/ios/Logger.swift new file mode 100644 index 00000000..0d79f459 --- /dev/null +++ b/modules/mpv-player/ios/Logger.swift @@ -0,0 +1,154 @@ +import Foundation + +class Logger { + static let shared = Logger() + + struct LogEntry { + let message: String + let type: String + let timestamp: Date + } + + private let queue = DispatchQueue(label: "mpvkit.logger", attributes: .concurrent) + private var logs: [LogEntry] = [] + private let logFileURL: URL + + private let maxFileSize = 1024 * 512 + private let maxLogEntries = 1000 + + private init() { + let tmpDir = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true) + logFileURL = tmpDir.appendingPathComponent("logs.txt") + } + + func log(_ message: String, type: String = "General") { + let entry = LogEntry(message: message, type: type, timestamp: Date()) + + queue.async(flags: .barrier) { + self.logs.append(entry) + + if self.logs.count > self.maxLogEntries { + self.logs.removeFirst(self.logs.count - self.maxLogEntries) + } + + self.saveLogToFile(entry) + self.debugLog(entry) + + DispatchQueue.main.async { + NotificationCenter.default.post(name: NSNotification.Name("LoggerNotification"), object: nil, + userInfo: [ + "message": message, + "type": type, + "timestamp": entry.timestamp + ] + ) + } + } + } + + func getLogs() -> String { + var result = "" + queue.sync { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "dd-MM HH:mm:ss" + result = logs.map { "[\(dateFormatter.string(from: $0.timestamp))] [\($0.type)] \($0.message)" } + .joined(separator: "\n----\n") + } + return result + } + + func getLogsAsync() async -> String { + return await withCheckedContinuation { continuation in + queue.async { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "dd-MM HH:mm:ss" + let result = self.logs.map { "[\(dateFormatter.string(from: $0.timestamp))] [\($0.type)] \($0.message)" } + .joined(separator: "\n----\n") + continuation.resume(returning: result) + } + } + } + + func clearLogs() { + queue.async(flags: .barrier) { + self.logs.removeAll() + try? FileManager.default.removeItem(at: self.logFileURL) + } + } + + func clearLogsAsync() async { + await withCheckedContinuation { continuation in + queue.async(flags: .barrier) { + self.logs.removeAll() + try? FileManager.default.removeItem(at: self.logFileURL) + continuation.resume() + } + } + } + + private func saveLogToFile(_ log: LogEntry) { + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "dd-MM HH:mm:ss" + + let logString = "[\(dateFormatter.string(from: log.timestamp))] [\(log.type)] \(log.message)\n---\n" + + guard let data = logString.data(using: .utf8) else { + print("Failed to encode log string to UTF-8") + return + } + + do { + if FileManager.default.fileExists(atPath: logFileURL.path) { + let attributes = try FileManager.default.attributesOfItem(atPath: logFileURL.path) + let fileSize = attributes[.size] as? UInt64 ?? 0 + + if fileSize + UInt64(data.count) > maxFileSize { + self.truncateLogFile() + } + + if let handle = try? FileHandle(forWritingTo: logFileURL) { + handle.seekToEndOfFile() + handle.write(data) + handle.closeFile() + } + } else { + try data.write(to: logFileURL) + } + } catch { + print("Error managing log file: \(error)") + try? data.write(to: logFileURL) + } + } + + private func truncateLogFile() { + do { + guard let content = try? String(contentsOf: logFileURL, encoding: .utf8), + !content.isEmpty else { + return + } + + let entries = content.components(separatedBy: "\n---\n") + guard entries.count > 10 else { return } + + let keepCount = entries.count / 2 + let truncatedEntries = Array(entries.suffix(keepCount)) + let truncatedContent = truncatedEntries.joined(separator: "\n---\n") + + if let truncatedData = truncatedContent.data(using: .utf8) { + try truncatedData.write(to: logFileURL) + } + } catch { + print("Error truncating log file: \(error)") + try? FileManager.default.removeItem(at: logFileURL) + } + } + + private func debugLog(_ entry: LogEntry) { +#if DEBUG + let dateFormatter = DateFormatter() + dateFormatter.dateFormat = "dd-MM HH:mm:ss" + let formattedMessage = "[\(dateFormatter.string(from: entry.timestamp))] [\(entry.type)] \(entry.message)" + print(formattedMessage) +#endif + } +} diff --git a/modules/mpv-player/ios/MPVSoftwareRenderer.swift b/modules/mpv-player/ios/MPVSoftwareRenderer.swift new file mode 100644 index 00000000..df19c10e --- /dev/null +++ b/modules/mpv-player/ios/MPVSoftwareRenderer.swift @@ -0,0 +1,1389 @@ +import UIKit +import Libmpv +import CoreMedia +import CoreVideo +import AVFoundation + +protocol MPVSoftwareRendererDelegate: AnyObject { + func renderer(_ renderer: MPVSoftwareRenderer, didUpdatePosition position: Double, duration: Double) + func renderer(_ renderer: MPVSoftwareRenderer, didChangePause isPaused: Bool) + func renderer(_ renderer: MPVSoftwareRenderer, didChangeLoading isLoading: Bool) + func renderer(_ renderer: MPVSoftwareRenderer, didBecomeReadyToSeek: Bool) + func renderer(_ renderer: MPVSoftwareRenderer, didBecomeTracksReady: Bool) +} + +final class MPVSoftwareRenderer { + enum RendererError: Error { + case mpvCreationFailed + case mpvInitialization(Int32) + case renderContextCreation(Int32) + } + + private let displayLayer: AVSampleBufferDisplayLayer + private let renderQueue = DispatchQueue(label: "mpv.software.render", qos: .userInitiated) + private let eventQueue = DispatchQueue(label: "mpv.software.events", qos: .utility) + private let stateQueue = DispatchQueue(label: "mpv.software.state", attributes: .concurrent) + private let eventQueueGroup = DispatchGroup() + private let renderQueueKey = DispatchSpecificKey() + + private var dimensionsArray = [Int32](repeating: 0, count: 2) + private var renderParams = [mpv_render_param](repeating: mpv_render_param(type: MPV_RENDER_PARAM_INVALID, data: nil), count: 5) + + private var mpv: OpaquePointer? + private var renderContext: OpaquePointer? + private var videoSize: CGSize = .zero + private var pixelBufferPool: CVPixelBufferPool? + private var pixelBufferPoolAuxAttributes: CFDictionary? + private var formatDescription: CMVideoFormatDescription? + private var didFlushForFormatChange = false + private var poolWidth: Int = 0 + private var poolHeight: Int = 0 + private var preAllocatedBuffers: [CVPixelBuffer] = [] + private let maxPreAllocatedBuffers = 12 + + private var currentPreset: PlayerPreset? + private var currentURL: URL? + private var currentHeaders: [String: String]? + private var pendingExternalSubtitles: [String] = [] + private var initialSubtitleId: Int? + private var initialAudioId: Int? + + private var disposeBag: [() -> Void] = [] + + private var isRunning = false + private var isStopping = false + private var shouldClearPixelBuffer = false + private let bgraFormatCString: [CChar] = Array("bgra\0".utf8CString) + private let maxInFlightBuffers = 3 + private var inFlightBufferCount = 0 + private let inFlightLock = NSLock() + + weak var delegate: MPVSoftwareRendererDelegate? + + // Thread-safe state for playback (uses existing stateQueue to prevent races causing stutter) + private var _cachedDuration: Double = 0 + private var _cachedPosition: Double = 0 + private var _isPaused: Bool = true + private var _playbackSpeed: Double = 1.0 + private var _isSeeking: Bool = false + private var _positionUpdateTime: CFTimeInterval = 0 // Host time when position was last updated + private var _lastPTS: Double = 0 // Last presentation timestamp (ensures monotonic increase) + + // Thread-safe accessors + private var cachedDuration: Double { + get { stateQueue.sync { _cachedDuration } } + set { stateQueue.async(flags: .barrier) { self._cachedDuration = newValue } } + } + private var cachedPosition: Double { + get { stateQueue.sync { _cachedPosition } } + set { stateQueue.async(flags: .barrier) { self._cachedPosition = newValue } } + } + private var isPaused: Bool { + get { stateQueue.sync { _isPaused } } + set { stateQueue.async(flags: .barrier) { self._isPaused = newValue } } + } + private var playbackSpeed: Double { + get { stateQueue.sync { _playbackSpeed } } + set { stateQueue.async(flags: .barrier) { self._playbackSpeed = newValue } } + } + private var isSeeking: Bool { + get { stateQueue.sync { _isSeeking } } + set { stateQueue.async(flags: .barrier) { self._isSeeking = newValue } } + } + private var positionUpdateTime: CFTimeInterval { + get { stateQueue.sync { _positionUpdateTime } } + set { stateQueue.async(flags: .barrier) { self._positionUpdateTime = newValue } } + } + private var lastPTS: Double { + get { stateQueue.sync { _lastPTS } } + set { stateQueue.async(flags: .barrier) { self._lastPTS = newValue } } + } + + /// Get next monotonically increasing PTS based on video position + /// This ensures frames always have increasing timestamps (prevents stutter from drops) + private func nextMonotonicPTS() -> Double { + let currentPos = interpolatedPosition() + let last = lastPTS + + // Ensure PTS always increases (by at least 1ms) to prevent frame drops + let pts = max(currentPos, last + 0.001) + lastPTS = pts + return pts + } + + /// Calculate smooth interpolated position based on last known position + elapsed time + private func interpolatedPosition() -> Double { + let basePosition = cachedPosition + let lastUpdate = positionUpdateTime + let paused = isPaused + let speed = playbackSpeed + + guard !paused, lastUpdate > 0 else { + return basePosition + } + + let elapsed = CACurrentMediaTime() - lastUpdate + return basePosition + (elapsed * speed) + } + + private var isLoading: Bool = false + private var isRenderScheduled = false + private var lastRenderTime: CFTimeInterval = 0 + private var minRenderInterval: CFTimeInterval + private var isReadyToSeek: Bool = false + private var lastRenderDimensions: CGSize = .zero + + var isPausedState: Bool { + return isPaused + } + + init(displayLayer: AVSampleBufferDisplayLayer) { + guard + let screen = UIApplication.shared.connectedScenes + .compactMap({ ($0 as? UIWindowScene)?.screen }) + .first + else { + fatalError("⚠️ No active screen found — app may not have a visible window yet.") + } + self.displayLayer = displayLayer + let maxFPS = screen.maximumFramesPerSecond + let cappedFPS = min(maxFPS, 60) + self.minRenderInterval = 1.0 / CFTimeInterval(cappedFPS) + renderQueue.setSpecific(key: renderQueueKey, value: ()) + } + + deinit { + stop() + } + + func start() throws { + guard !isRunning else { return } + guard let handle = mpv_create() else { + throw RendererError.mpvCreationFailed + } + mpv = handle + setOption(name: "terminal", value: "yes") + setOption(name: "msg-level", value: "status") + setOption(name: "keep-open", value: "yes") + setOption(name: "idle", value: "yes") + setOption(name: "vo", value: "libmpv") + setOption(name: "hwdec", value: "videotoolbox-copy") + setOption(name: "gpu-api", value: "metal") + setOption(name: "gpu-context", value: "metal") + setOption(name: "demuxer-thread", value: "yes") + setOption(name: "ytdl", value: "yes") + setOption(name: "profile", value: "fast") + setOption(name: "vd-lavc-threads", value: "8") + setOption(name: "cache", value: "yes") + setOption(name: "demuxer-max-bytes", value: "150M") + setOption(name: "demuxer-readahead-secs", value: "20") + + // Subtitle options - use vf=sub to burn subtitles into video frames + // This happens at the filter level, BEFORE the software renderer + setOption(name: "vf", value: "sub") + setOption(name: "sub-visibility", value: "yes") + + let initStatus = mpv_initialize(handle) + guard initStatus >= 0 else { + throw RendererError.mpvInitialization(initStatus) + } + + mpv_request_log_messages(handle, "warn") + + try createRenderContext() + observeProperties() + installWakeupHandler() + isRunning = true + } + + func stop() { + if isStopping { return } + if !isRunning, mpv == nil { return } + isRunning = false + isStopping = true + + var handleForShutdown: OpaquePointer? + + renderQueue.sync { [weak self] in + guard let self else { return } + + if let ctx = self.renderContext { + mpv_render_context_set_update_callback(ctx, nil, nil) + mpv_render_context_free(ctx) + self.renderContext = nil + } + + handleForShutdown = self.mpv + if let handle = handleForShutdown { + mpv_set_wakeup_callback(handle, nil, nil) + self.command(handle, ["quit"]) + mpv_wakeup(handle) + } + + self.formatDescription = nil + self.preAllocatedBuffers.removeAll() + self.pixelBufferPool = nil + self.poolWidth = 0 + self.poolHeight = 0 + self.lastRenderDimensions = .zero + } + + eventQueueGroup.wait() + + renderQueue.sync { [weak self] in + guard let self else { return } + + if let handle = handleForShutdown { + mpv_destroy(handle) + } + self.mpv = nil + + self.preAllocatedBuffers.removeAll() + self.pixelBufferPool = nil + self.pixelBufferPoolAuxAttributes = nil + self.formatDescription = nil + self.poolWidth = 0 + self.poolHeight = 0 + self.lastRenderDimensions = .zero + + self.disposeBag.forEach { $0() } + self.disposeBag.removeAll() + } + + DispatchQueue.main.async { [weak self] in + guard let self else { return } + if #available(iOS 18.0, *) { + self.displayLayer.sampleBufferRenderer.flush(removingDisplayedImage: true, completionHandler: nil) + } else { + self.displayLayer.flushAndRemoveImage() + } + } + + isStopping = false + } + + func load( + url: URL, + with preset: PlayerPreset, + headers: [String: String]? = nil, + startPosition: Double? = nil, + externalSubtitles: [String]? = nil, + initialSubtitleId: Int? = nil, + initialAudioId: Int? = nil + ) { + currentPreset = preset + currentURL = url + currentHeaders = headers + pendingExternalSubtitles = externalSubtitles ?? [] + self.initialSubtitleId = initialSubtitleId + self.initialAudioId = initialAudioId + + renderQueue.async { [weak self] in + guard let self else { return } + self.isLoading = true + self.isReadyToSeek = false + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.delegate?.renderer(self, didChangeLoading: true) + } + + guard let handle = self.mpv else { return } + + self.apply(commands: preset.commands, on: handle) + // Sync stop to ensure previous playback is stopped before loading new file + self.commandSync(handle, ["stop"]) + self.updateHTTPHeaders(headers) + + // Set start position using property (setOption only works before mpv_initialize) + if let startPos = startPosition, startPos > 0 { + self.setProperty(name: "start", value: String(format: "%.2f", startPos)) + } else { + self.setProperty(name: "start", value: "0") + } + + // Set initial audio track if specified + if let audioId = self.initialAudioId, audioId > 0 { + self.setAudioTrack(audioId) + } + + // Set initial subtitle track if no external subs (external subs change track IDs) + if self.pendingExternalSubtitles.isEmpty { + if let subId = self.initialSubtitleId { + self.setSubtitleTrack(subId) + } else { + self.disableSubtitles() + } + } else { + // External subs will be added after file loads, set sid then + self.disableSubtitles() + } + + var finalURL = url + if !url.isFileURL { + finalURL = url + } + + let target = finalURL.isFileURL ? finalURL.path : finalURL.absoluteString + self.command(handle, ["loadfile", target, "replace"]) + } + } + + func reloadCurrentItem() { + guard let url = currentURL, let preset = currentPreset else { return } + load(url: url, with: preset, headers: currentHeaders) + } + + func applyPreset(_ preset: PlayerPreset) { + currentPreset = preset + guard let handle = mpv else { return } + renderQueue.async { [weak self] in + guard let self else { return } + self.apply(commands: preset.commands, on: handle) + } + } + + private func setOption(name: String, value: String) { + guard let handle = mpv else { return } + _ = value.withCString { valuePointer in + name.withCString { namePointer in + mpv_set_option_string(handle, namePointer, valuePointer) + } + } + } + + private func setProperty(name: String, value: String) { + guard let handle = mpv else { return } + let status = value.withCString { valuePointer in + name.withCString { namePointer in + mpv_set_property_string(handle, namePointer, valuePointer) + } + } + if status < 0 { + Logger.shared.log("Failed to set property \(name)=\(value) (\(status))", type: "Warn") + } + } + + private func clearProperty(name: String) { + guard let handle = mpv else { return } + let status = name.withCString { namePointer in + mpv_set_property(handle, namePointer, MPV_FORMAT_NONE, nil) + } + if status < 0 { + Logger.shared.log("Failed to clear property \(name) (\(status))", type: "Warn") + } + } + + private func updateHTTPHeaders(_ headers: [String: String]?) { + guard let headers, !headers.isEmpty else { + clearProperty(name: "http-header-fields") + return + } + + let headerString = headers + .map { key, value in + "\(key): \(value)" + } + .joined(separator: "\r\n") + setProperty(name: "http-header-fields", value: headerString) + } + + private func createRenderContext() throws { + guard let handle = mpv else { return } + + var apiType = MPV_RENDER_API_TYPE_SW + let status = withUnsafePointer(to: &apiType) { apiTypePtr in + var params = [ + mpv_render_param(type: MPV_RENDER_PARAM_API_TYPE, data: UnsafeMutableRawPointer(mutating: apiTypePtr)), + mpv_render_param(type: MPV_RENDER_PARAM_INVALID, data: nil) + ] + + return params.withUnsafeMutableBufferPointer { pointer -> Int32 in + pointer.baseAddress?.withMemoryRebound(to: mpv_render_param.self, capacity: pointer.count) { parameters in + return mpv_render_context_create(&renderContext, handle, parameters) + } ?? -1 + } + } + + guard status >= 0, renderContext != nil else { + throw RendererError.renderContextCreation(status) + } + + mpv_render_context_set_update_callback(renderContext, { context in + guard let context = context else { return } + let instance = Unmanaged.fromOpaque(context).takeUnretainedValue() + instance.scheduleRender() + }, Unmanaged.passUnretained(self).toOpaque()) + } + + private func observeProperties() { + guard let handle = mpv else { return } + let properties: [(String, mpv_format)] = [ + ("dwidth", MPV_FORMAT_INT64), + ("dheight", MPV_FORMAT_INT64), + ("duration", MPV_FORMAT_DOUBLE), + ("time-pos", MPV_FORMAT_DOUBLE), + ("pause", MPV_FORMAT_FLAG), + ("track-list/count", MPV_FORMAT_INT64) // Notify when tracks are available + ] + + for (name, format) in properties { + _ = name.withCString { pointer in + mpv_observe_property(handle, 0, pointer, format) + } + } + } + + private func installWakeupHandler() { + guard let handle = mpv else { return } + mpv_set_wakeup_callback(handle, { userdata in + guard let userdata else { return } + let instance = Unmanaged.fromOpaque(userdata).takeUnretainedValue() + instance.processEvents() + }, Unmanaged.passUnretained(self).toOpaque()) + renderQueue.async { [weak self] in + guard let self else { return } + self.disposeBag.append { [weak self] in + guard let self, let handle = self.mpv else { return } + mpv_set_wakeup_callback(handle, nil, nil) + } + } + } + + private func scheduleRender() { + renderQueue.async { [weak self] in + guard let self, self.isRunning, !self.isStopping else { return } + + let currentTime = CACurrentMediaTime() + let timeSinceLastRender = currentTime - self.lastRenderTime + if timeSinceLastRender < self.minRenderInterval { + let remaining = self.minRenderInterval - timeSinceLastRender + if self.isRenderScheduled { return } + self.isRenderScheduled = true + + self.renderQueue.asyncAfter(deadline: .now() + remaining) { [weak self] in + guard let self else { return } + self.lastRenderTime = CACurrentMediaTime() + self.performRenderUpdate() + self.isRenderScheduled = false + } + return + } + + self.isRenderScheduled = true + self.lastRenderTime = currentTime + self.performRenderUpdate() + self.isRenderScheduled = false + } + } + + private func performRenderUpdate() { + guard let context = renderContext else { return } + let status = mpv_render_context_update(context) + + let updateFlags = UInt32(status) + + if updateFlags & MPV_RENDER_UPDATE_FRAME.rawValue != 0 { + renderFrame() + } + + if status > 0 { + scheduleRender() + } + } + + private func renderFrame() { + guard let context = renderContext else { return } + let videoSize = currentVideoSize() + guard videoSize.width > 0, videoSize.height > 0 else { return } + + let targetSize = targetRenderSize(for: videoSize) + let width = Int(targetSize.width) + let height = Int(targetSize.height) + guard width > 0, height > 0 else { return } + if lastRenderDimensions != targetSize { + lastRenderDimensions = targetSize + if targetSize != videoSize { + Logger.shared.log("Rendering scaled output at \(width)x\(height) (source \(Int(videoSize.width))x\(Int(videoSize.height)))", type: "Info") + } else { + Logger.shared.log("Rendering output at native size \(width)x\(height)", type: "Info") + } + } + + if poolWidth != width || poolHeight != height { + recreatePixelBufferPool(width: width, height: height) + } + + var pixelBuffer: CVPixelBuffer? + var status: CVReturn = kCVReturnError + + if !preAllocatedBuffers.isEmpty { + pixelBuffer = preAllocatedBuffers.removeFirst() + status = kCVReturnSuccess + } else if let pool = pixelBufferPool { + status = CVPixelBufferPoolCreatePixelBufferWithAuxAttributes(kCFAllocatorDefault, pool, pixelBufferPoolAuxAttributes, &pixelBuffer) + } + + if status != kCVReturnSuccess || pixelBuffer == nil { + let attrs: [CFString: Any] = [ + kCVPixelBufferIOSurfacePropertiesKey: [:] as CFDictionary, + kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue!, + kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue!, + kCVPixelBufferMetalCompatibilityKey: kCFBooleanTrue!, + kCVPixelBufferWidthKey: width, + kCVPixelBufferHeightKey: height, + kCVPixelBufferPixelFormatTypeKey: kCVPixelFormatType_32BGRA + ] + status = CVPixelBufferCreate(kCFAllocatorDefault, width, height, kCVPixelFormatType_32BGRA, attrs as CFDictionary, &pixelBuffer) + } + + guard status == kCVReturnSuccess, let buffer = pixelBuffer else { + Logger.shared.log("Failed to create pixel buffer for rendering (status: \(status))", type: "Error") + return + } + + let actualFormat = CVPixelBufferGetPixelFormatType(buffer) + if actualFormat != kCVPixelFormatType_32BGRA { + Logger.shared.log("Pixel buffer format mismatch: expected BGRA (0x42475241), got \(actualFormat)", type: "Error") + } + + CVPixelBufferLockBaseAddress(buffer, []) + guard let baseAddress = CVPixelBufferGetBaseAddress(buffer) else { + CVPixelBufferUnlockBaseAddress(buffer, []) + return + } + + if shouldClearPixelBuffer { + let bufferDataSize = CVPixelBufferGetDataSize(buffer) + memset(baseAddress, 0, bufferDataSize) + shouldClearPixelBuffer = false + } + + dimensionsArray[0] = Int32(width) + dimensionsArray[1] = Int32(height) + let stride = Int32(CVPixelBufferGetBytesPerRow(buffer)) + let expectedMinStride = Int32(width * 4) + if stride < expectedMinStride { + Logger.shared.log("Unexpected pixel buffer stride \(stride) < expected \(expectedMinStride) — skipping render to avoid memory corruption", type: "Error") + CVPixelBufferUnlockBaseAddress(buffer, []) + return + } + + let pointerValue = baseAddress + dimensionsArray.withUnsafeMutableBufferPointer { dimsPointer in + bgraFormatCString.withUnsafeBufferPointer { formatPointer in + withUnsafePointer(to: stride) { stridePointer in + renderParams[0] = mpv_render_param(type: MPV_RENDER_PARAM_SW_SIZE, data: UnsafeMutableRawPointer(dimsPointer.baseAddress)) + renderParams[1] = mpv_render_param(type: MPV_RENDER_PARAM_SW_FORMAT, data: UnsafeMutableRawPointer(mutating: formatPointer.baseAddress)) + renderParams[2] = mpv_render_param(type: MPV_RENDER_PARAM_SW_STRIDE, data: UnsafeMutableRawPointer(mutating: stridePointer)) + renderParams[3] = mpv_render_param(type: MPV_RENDER_PARAM_SW_POINTER, data: pointerValue) + renderParams[4] = mpv_render_param(type: MPV_RENDER_PARAM_INVALID, data: nil) + + let rc = mpv_render_context_render(context, &renderParams) + if rc < 0 { + Logger.shared.log("mpv_render_context_render returned error \(rc)", type: "Error") + } + } + } + } + + CVPixelBufferUnlockBaseAddress(buffer, []) + + enqueue(buffer: buffer) + + if preAllocatedBuffers.count < 4 { + renderQueue.async { [weak self] in + self?.preAllocateBuffers() + } + } + } + + private func targetRenderSize(for videoSize: CGSize) -> CGSize { + guard videoSize.width > 0, videoSize.height > 0 else { return videoSize } + guard + let screen = UIApplication.shared.connectedScenes + .compactMap({ ($0 as? UIWindowScene)?.screen }) + .first + else { + fatalError("⚠️ No active screen found — app may not have a visible window yet.") + } + var scale = screen.scale + if scale <= 0 { scale = 1 } + let maxWidth = max(screen.bounds.width * scale, 1.0) + let maxHeight = max(screen.bounds.height * scale, 1.0) + if maxWidth <= 0 || maxHeight <= 0 { + return videoSize + } + let widthRatio = videoSize.width / maxWidth + let heightRatio = videoSize.height / maxHeight + let ratio = max(widthRatio, heightRatio, 1) + let targetWidth = max(1, Int(videoSize.width / ratio)) + let targetHeight = max(1, Int(videoSize.height / ratio)) + return CGSize(width: CGFloat(targetWidth), height: CGFloat(targetHeight)) + } + + private func createPixelBufferPool(width: Int, height: Int) { + guard width > 0, height > 0 else { return } + + let pixelFormat = kCVPixelFormatType_32BGRA + + let attrs: [CFString: Any] = [ + kCVPixelBufferPixelFormatTypeKey: pixelFormat, + kCVPixelBufferWidthKey: width, + kCVPixelBufferHeightKey: height, + kCVPixelBufferIOSurfacePropertiesKey: [:] as CFDictionary, + kCVPixelBufferMetalCompatibilityKey: kCFBooleanTrue!, + kCVPixelBufferCGImageCompatibilityKey: kCFBooleanTrue!, + kCVPixelBufferCGBitmapContextCompatibilityKey: kCFBooleanTrue! + ] + + let poolAttrs: [CFString: Any] = [ + kCVPixelBufferPoolMinimumBufferCountKey: maxPreAllocatedBuffers, + kCVPixelBufferPoolMaximumBufferAgeKey: 0 + ] + + let auxAttrs: [CFString: Any] = [ + kCVPixelBufferPoolAllocationThresholdKey: 8 + ] + + var pool: CVPixelBufferPool? + let status = CVPixelBufferPoolCreate(kCFAllocatorDefault, poolAttrs as CFDictionary, attrs as CFDictionary, &pool) + if status == kCVReturnSuccess, let pool { + renderQueueSync { + self.pixelBufferPool = pool + self.pixelBufferPoolAuxAttributes = auxAttrs as CFDictionary + self.poolWidth = width + self.poolHeight = height + } + + renderQueue.async { [weak self] in + self?.preAllocateBuffers() + } + } else { + Logger.shared.log("Failed to create CVPixelBufferPool (status: \(status))", type: "Error") + } + } + + private func recreatePixelBufferPool(width: Int, height: Int) { + renderQueueSync { + self.preAllocatedBuffers.removeAll() + self.pixelBufferPool = nil + self.formatDescription = nil + self.poolWidth = 0 + self.poolHeight = 0 + } + + createPixelBufferPool(width: width, height: height) + } + + private func preAllocateBuffers() { + guard DispatchQueue.getSpecific(key: renderQueueKey) != nil else { + renderQueue.async { [weak self] in + self?.preAllocateBuffers() + } + return + } + + guard let pool = pixelBufferPool else { return } + + let targetCount = min(maxPreAllocatedBuffers, 8) + let currentCount = preAllocatedBuffers.count + + guard currentCount < targetCount else { return } + + let bufferCount = targetCount - currentCount + + for _ in 0.. Bool { + var didChange = false + let width = Int32(CVPixelBufferGetWidth(buffer)) + let height = Int32(CVPixelBufferGetHeight(buffer)) + let pixelFormat = CVPixelBufferGetPixelFormatType(buffer) + + renderQueueSync { + var needsRecreate = false + + if let description = formatDescription { + let currentDimensions = CMVideoFormatDescriptionGetDimensions(description) + let currentPixelFormat = CMFormatDescriptionGetMediaSubType(description) + + if currentDimensions.width != width || + currentDimensions.height != height || + currentPixelFormat != pixelFormat { + needsRecreate = true + } + } else { + needsRecreate = true + } + + if needsRecreate { + var newDescription: CMVideoFormatDescription? + + let status = CMVideoFormatDescriptionCreateForImageBuffer( + allocator: kCFAllocatorDefault, + imageBuffer: buffer, + formatDescriptionOut: &newDescription + ) + + if status == noErr, let newDescription = newDescription { + formatDescription = newDescription + didChange = true + Logger.shared.log("Created new format description: \(width)x\(height), format: \(pixelFormat)", type: "Info") + } else { + Logger.shared.log("Failed to create format description (status: \(status))", type: "Error") + } + } + } + return didChange + } + + private func renderQueueSync(_ block: () -> Void) { + if DispatchQueue.getSpecific(key: renderQueueKey) != nil { + block() + } else { + renderQueue.sync(execute: block) + } + } + + private func currentVideoSize() -> CGSize { + stateQueue.sync { + videoSize + } + } + + private func updateVideoSize(width: Int, height: Int) { + let size = CGSize(width: max(width, 0), height: max(height, 0)) + stateQueue.async(flags: .barrier) { + self.videoSize = size + } + renderQueue.async { [weak self] in + guard let self else { return } + + if self.poolWidth != width || self.poolHeight != height { + self.recreatePixelBufferPool(width: max(width, 0), height: max(height, 0)) + } + } + } + + private func apply(commands: [[String]], on handle: OpaquePointer) { + for command in commands { + guard !command.isEmpty else { continue } + self.command(handle, command) + } + } + + /// Async command - returns immediately, mpv processes later + private func command(_ handle: OpaquePointer, _ args: [String]) { + guard !args.isEmpty else { return } + _ = withCStringArray(args) { pointer in + mpv_command_async(handle, 0, pointer) + } + } + + /// Sync command - waits for mpv to process before returning + private func commandSync(_ handle: OpaquePointer, _ args: [String]) -> Int32 { + guard !args.isEmpty else { return -1 } + return withCStringArray(args) { pointer in + mpv_command(handle, pointer) + } + } + + private func processEvents() { + eventQueueGroup.enter() + let group = eventQueueGroup + eventQueue.async { [weak self] in + defer { group.leave() } + guard let self else { return } + while !self.isStopping { + guard let handle = self.mpv else { return } + guard let eventPointer = mpv_wait_event(handle, 0) else { return } + let event = eventPointer.pointee + if event.event_id == MPV_EVENT_NONE { continue } + self.handleEvent(event) + if event.event_id == MPV_EVENT_SHUTDOWN { break } + } + } + } + + private func handleEvent(_ event: mpv_event) { + switch event.event_id { + case MPV_EVENT_VIDEO_RECONFIG: + refreshVideoState() + case MPV_EVENT_FILE_LOADED: + // Add external subtitles now that the file is loaded + let hadExternalSubs = !pendingExternalSubtitles.isEmpty + if hadExternalSubs, let handle = mpv { + for subUrl in pendingExternalSubtitles { + command(handle, ["sub-add", subUrl]) + } + pendingExternalSubtitles = [] + + // Set subtitle after external subs are added (track IDs have changed) + if let subId = initialSubtitleId { + setSubtitleTrack(subId) + } else { + disableSubtitles() + } + } + + if !isReadyToSeek { + isReadyToSeek = true + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.delegate?.renderer(self, didBecomeReadyToSeek: true) + } + } + case MPV_EVENT_PROPERTY_CHANGE: + if let property = event.data?.assumingMemoryBound(to: mpv_event_property.self).pointee.name { + let name = String(cString: property) + refreshProperty(named: name) + } + case MPV_EVENT_SHUTDOWN: + Logger.shared.log("mpv shutdown", type: "Warn") + case MPV_EVENT_LOG_MESSAGE: + if let logMessagePointer = event.data?.assumingMemoryBound(to: mpv_event_log_message.self) { + let component = String(cString: logMessagePointer.pointee.prefix) + let text = String(cString: logMessagePointer.pointee.text) + let lower = text.lowercased() + if lower.contains("error") { + Logger.shared.log("mpv[\(component)] \(text)", type: "Error") + } else if lower.contains("warn") || lower.contains("warning") || lower.contains("deprecated") { + Logger.shared.log("mpv[\(component)] \(text)", type: "Warn") + } + } + default: + break + } + } + + private func refreshVideoState() { + guard let handle = mpv else { return } + var width: Int64 = 0 + var height: Int64 = 0 + getProperty(handle: handle, name: "dwidth", format: MPV_FORMAT_INT64, value: &width) + getProperty(handle: handle, name: "dheight", format: MPV_FORMAT_INT64, value: &height) + updateVideoSize(width: Int(width), height: Int(height)) + } + + private func refreshProperty(named name: String) { + guard let handle = mpv else { return } + switch name { + case "duration": + var value = Double(0) + let status = getProperty(handle: handle, name: name, format: MPV_FORMAT_DOUBLE, value: &value) + if status >= 0 { + cachedDuration = value + delegate?.renderer(self, didUpdatePosition: cachedPosition, duration: cachedDuration) + } + case "time-pos": + // Skip updates while seeking to prevent race condition + guard !isSeeking else { return } + var value = Double(0) + let status = getProperty(handle: handle, name: name, format: MPV_FORMAT_DOUBLE, value: &value) + if status >= 0 { + cachedPosition = value + positionUpdateTime = CACurrentMediaTime() // Record when we got this update + delegate?.renderer(self, didUpdatePosition: cachedPosition, duration: cachedDuration) + } + case "pause": + var flag: Int32 = 0 + let status = getProperty(handle: handle, name: name, format: MPV_FORMAT_FLAG, value: &flag) + if status >= 0 { + let newPaused = flag != 0 + if newPaused != isPaused { + isPaused = newPaused + // Update timebase rate - use playbackSpeed when playing, 0 when paused + let speed = self.playbackSpeed + DispatchQueue.main.async { [weak self] in + if let timebase = self?.displayLayer.controlTimebase { + CMTimebaseSetRate(timebase, rate: newPaused ? 0 : speed) + } + } + delegate?.renderer(self, didChangePause: isPaused) + } + } + case "track-list/count": + var trackCount: Int64 = 0 + let status = getProperty(handle: handle, name: name, format: MPV_FORMAT_INT64, value: &trackCount) + if status >= 0 && trackCount > 0 { + Logger.shared.log("Track list updated: \(trackCount) tracks available", type: "Info") + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.delegate?.renderer(self, didBecomeTracksReady: true) + } + } + default: + break + } + } + + private func getStringProperty(handle: OpaquePointer, name: String) -> String? { + var result: String? + name.withCString { pointer in + if let cString = mpv_get_property_string(handle, pointer) { + result = String(cString: cString) + mpv_free(cString) + } + } + return result + } + + @discardableResult + private func getProperty(handle: OpaquePointer, name: String, format: mpv_format, value: inout T) -> Int32 { + return name.withCString { pointer in + return withUnsafeMutablePointer(to: &value) { mutablePointer in + return mpv_get_property(handle, pointer, format, mutablePointer) + } + } + } + + @inline(__always) + private func withCStringArray(_ args: [String], body: (UnsafeMutablePointer?>?) -> R) -> R { + var cStrings = [UnsafeMutablePointer?]() + cStrings.reserveCapacity(args.count + 1) + for s in args { + cStrings.append(strdup(s)) + } + cStrings.append(nil) + defer { + for ptr in cStrings where ptr != nil { + free(ptr) + } + } + + return cStrings.withUnsafeMutableBufferPointer { buffer in + return buffer.baseAddress!.withMemoryRebound(to: UnsafePointer?.self, capacity: buffer.count) { rebound in + return body(UnsafeMutablePointer(mutating: rebound)) + } + } + } + + // MARK: - Playback Controls + func play() { + setProperty(name: "pause", value: "no") + } + + func pausePlayback() { + setProperty(name: "pause", value: "yes") + } + + func togglePause() { + if isPaused { play() } else { pausePlayback() } + } + + func seek(to seconds: Double) { + guard let handle = mpv else { return } + let clamped = max(0, seconds) + let wasPaused = isPaused + // Prevent time-pos updates from overwriting during seek + isSeeking = true + // Update cached position BEFORE seek so new frames get correct timestamp + cachedPosition = clamped + positionUpdateTime = CACurrentMediaTime() // Reset interpolation base + lastPTS = clamped // Reset monotonic PTS to new position + // Update timebase to match new position (sets rate to 1 for frame display) + syncTimebase(to: clamped) + // Sync seek for accurate positioning + commandSync(handle, ["seek", String(clamped), "absolute"]) + isSeeking = false + // Restore paused rate after seek completes + if wasPaused { + restoreTimebaseRate() + } + } + + func seek(by seconds: Double) { + guard let handle = mpv else { return } + let wasPaused = isPaused + // Prevent time-pos updates from overwriting during seek + isSeeking = true + // Update cached position BEFORE seek + let newPosition = max(0, cachedPosition + seconds) + cachedPosition = newPosition + positionUpdateTime = CACurrentMediaTime() // Reset interpolation base + lastPTS = newPosition // Reset monotonic PTS to new position + // Update timebase to match new position (sets rate to 1 for frame display) + syncTimebase(to: newPosition) + // Sync seek for accurate positioning + commandSync(handle, ["seek", String(seconds), "relative"]) + isSeeking = false + // Restore paused rate after seek completes + if wasPaused { + restoreTimebaseRate() + } + } + + private func restoreTimebaseRate() { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { [weak self] in + guard let self = self, self.isPaused else { return } + if let timebase = self.displayLayer.controlTimebase { + CMTimebaseSetRate(timebase, rate: 0) + } + } + } + + private func syncTimebase(to position: Double) { + let speed = playbackSpeed + let doWork = { [weak self] in + guard let self = self else { return } + // Flush old frames to avoid "old frames with new clock" mismatches + if #available(iOS 17.0, *) { + self.displayLayer.sampleBufferRenderer.flush(removingDisplayedImage: false, completionHandler: nil) + } else { + self.displayLayer.flush() + } + if let timebase = self.displayLayer.controlTimebase { + // Update timebase to new position + CMTimebaseSetTime(timebase, time: CMTime(seconds: position, preferredTimescale: 1000)) + // Set rate to playback speed during seek to ensure frame displays + // restoreTimebaseRate() will set it back to 0 if paused + CMTimebaseSetRate(timebase, rate: speed) + } + } + + if Thread.isMainThread { + doWork() + } else { + DispatchQueue.main.sync { doWork() } + } + } + + /// Sync timebase with current position without flushing (for smooth PiP transitions) + func syncTimebase() { + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + if let timebase = self.displayLayer.controlTimebase { + CMTimebaseSetTime(timebase, time: CMTime(seconds: self.cachedPosition, preferredTimescale: 1000)) + CMTimebaseSetRate(timebase, rate: self.isPaused ? 0 : self.playbackSpeed) + } + } + } + + func setSpeed(_ speed: Double) { + playbackSpeed = speed + setProperty(name: "speed", value: String(speed)) + // Sync timebase rate with playback speed + DispatchQueue.main.async { [weak self] in + guard let self = self, + let timebase = self.displayLayer.controlTimebase else { return } + let rate = self.isPaused ? 0.0 : speed + CMTimebaseSetRate(timebase, rate: rate) + } + } + + func getSpeed() -> Double { + guard let handle = mpv else { return 1.0 } + var speed: Double = 1.0 + getProperty(handle: handle, name: "speed", format: MPV_FORMAT_DOUBLE, value: &speed) + return speed + } + + // MARK: - Subtitle Controls + + func getSubtitleTracks() -> [[String: Any]] { + guard let handle = mpv else { + Logger.shared.log("getSubtitleTracks: mpv handle is nil", type: "Warn") + return [] + } + var tracks: [[String: Any]] = [] + + var trackCount: Int64 = 0 + getProperty(handle: handle, name: "track-list/count", format: MPV_FORMAT_INT64, value: &trackCount) + + for i in 0.. Int { + guard let handle = mpv else { return 0 } + var sid: Int64 = 0 + getProperty(handle: handle, name: "sid", format: MPV_FORMAT_INT64, value: &sid) + return Int(sid) + } + + func addSubtitleFile(url: String, select: Bool = true) { + guard let handle = mpv else { return } + // "cached" adds without selecting, "select" adds and selects + let flag = select ? "select" : "cached" + commandSync(handle, ["sub-add", url, flag]) + } + + // MARK: - Subtitle Positioning + + func setSubtitlePosition(_ position: Int) { + setProperty(name: "sub-pos", value: String(position)) + } + + func setSubtitleScale(_ scale: Double) { + setProperty(name: "sub-scale", value: String(scale)) + } + + func setSubtitleMarginY(_ margin: Int) { + setProperty(name: "sub-margin-y", value: String(margin)) + } + + func setSubtitleAlignX(_ alignment: String) { + setProperty(name: "sub-align-x", value: alignment) + } + + func setSubtitleAlignY(_ alignment: String) { + setProperty(name: "sub-align-y", value: alignment) + } + + func setSubtitleFontSize(_ size: Int) { + setProperty(name: "sub-font-size", value: String(size)) + } + + // MARK: - Audio Track Controls + + func getAudioTracks() -> [[String: Any]] { + guard let handle = mpv else { + Logger.shared.log("getAudioTracks: mpv handle is nil", type: "Warn") + return [] + } + var tracks: [[String: Any]] = [] + + var trackCount: Int64 = 0 + getProperty(handle: handle, name: "track-list/count", format: MPV_FORMAT_INT64, value: &trackCount) + + for i in 0.. 0 { + track["channels"] = Int(channels) + } + + var selected: Int32 = 0 + getProperty(handle: handle, name: "track-list/\(i)/selected", format: MPV_FORMAT_FLAG, value: &selected) + track["selected"] = selected != 0 + + Logger.shared.log("getAudioTracks: found audio track id=\(trackId), title=\(track["title"] ?? "none"), lang=\(track["lang"] ?? "none")", type: "Info") + tracks.append(track) + } + + Logger.shared.log("getAudioTracks: returning \(tracks.count) audio tracks", type: "Info") + return tracks + } + + func setAudioTrack(_ trackId: Int) { + guard let handle = mpv else { + Logger.shared.log("setAudioTrack: mpv handle is nil", type: "Warn") + return + } + Logger.shared.log("setAudioTrack: setting aid to \(trackId)", type: "Info") + + // Use setProperty for synchronous behavior + setProperty(name: "aid", value: String(trackId)) + } + + func getCurrentAudioTrack() -> Int { + guard let handle = mpv else { return 0 } + var aid: Int64 = 0 + getProperty(handle: handle, name: "aid", format: MPV_FORMAT_INT64, value: &aid) + return Int(aid) + } +} diff --git a/modules/mpv-player/ios/MpvPlayer.podspec b/modules/mpv-player/ios/MpvPlayer.podspec new file mode 100644 index 00000000..e0960aa6 --- /dev/null +++ b/modules/mpv-player/ios/MpvPlayer.podspec @@ -0,0 +1,28 @@ +Pod::Spec.new do |s| + s.name = 'MpvPlayer' + s.version = '1.0.0' + s.summary = 'MPVKit for Expo' + s.description = 'MPVKit for Expo' + s.author = 'mpvkit' + s.homepage = 'https://github.com/mpvkit/MPVKit' + s.platforms = { + :ios => '15.1', + :tvos => '15.1' + } + s.source = { git: 'https://github.com/mpvkit/MPVKit.git' } + s.static_framework = true + + s.dependency 'ExpoModulesCore' + s.dependency 'MPVKit', '~> 0.40.0' + + # Swift/Objective-C compatibility + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + # Strip debug symbols to avoid DWARF errors from MPVKit + 'DEBUG_INFORMATION_FORMAT' => 'dwarf', + 'STRIP_INSTALLED_PRODUCT' => 'YES', + 'DEPLOYMENT_POSTPROCESSING' => 'YES', + } + + s.source_files = "**/*.{h,m,mm,swift,hpp,cpp}" +end diff --git a/modules/mpv-player/ios/MpvPlayerModule.swift b/modules/mpv-player/ios/MpvPlayerModule.swift new file mode 100644 index 00000000..87c60400 --- /dev/null +++ b/modules/mpv-player/ios/MpvPlayerModule.swift @@ -0,0 +1,171 @@ +import ExpoModulesCore + +public class MpvPlayerModule: Module { + public func definition() -> ModuleDefinition { + Name("MpvPlayer") + + // Defines event names that the module can send to JavaScript. + Events("onChange") + + // Defines a JavaScript synchronous function that runs the native code on the JavaScript thread. + Function("hello") { + return "Hello from MPV Player! 👋" + } + + // Defines a JavaScript function that always returns a Promise and whose native code + // is by default dispatched on the different thread than the JavaScript runtime runs on. + AsyncFunction("setValueAsync") { (value: String) in + // Send an event to JavaScript. + self.sendEvent("onChange", [ + "value": value + ]) + } + + // Enables the module to be used as a native view. Definition components that are accepted as part of the + // view definition: Prop, Events. + View(MpvPlayerView.self) { + // All video load options are passed via a single "source" prop + Prop("source") { (view: MpvPlayerView, source: [String: Any]?) in + guard let source = source, + let urlString = source["url"] as? String, + let videoURL = URL(string: urlString) else { return } + + let config = VideoLoadConfig( + url: videoURL, + headers: source["headers"] as? [String: String], + externalSubtitles: source["externalSubtitles"] as? [String], + startPosition: source["startPosition"] as? Double, + autoplay: (source["autoplay"] as? Bool) ?? true, + initialSubtitleId: source["initialSubtitleId"] as? Int, + initialAudioId: source["initialAudioId"] as? Int + ) + + view.loadVideo(config: config) + } + + // Async function to play video + AsyncFunction("play") { (view: MpvPlayerView) in + view.play() + } + + // Async function to pause video + AsyncFunction("pause") { (view: MpvPlayerView) in + view.pause() + } + + // Async function to seek to position + AsyncFunction("seekTo") { (view: MpvPlayerView, position: Double) in + view.seekTo(position: position) + } + + // Async function to seek by offset + AsyncFunction("seekBy") { (view: MpvPlayerView, offset: Double) in + view.seekBy(offset: offset) + } + + // Async function to set playback speed + AsyncFunction("setSpeed") { (view: MpvPlayerView, speed: Double) in + view.setSpeed(speed: speed) + } + + // Function to get current speed + AsyncFunction("getSpeed") { (view: MpvPlayerView) -> Double in + return view.getSpeed() + } + + // Function to check if paused + AsyncFunction("isPaused") { (view: MpvPlayerView) -> Bool in + return view.isPaused() + } + + // Function to get current position + AsyncFunction("getCurrentPosition") { (view: MpvPlayerView) -> Double in + return view.getCurrentPosition() + } + + // Function to get duration + AsyncFunction("getDuration") { (view: MpvPlayerView) -> Double in + return view.getDuration() + } + + // Picture in Picture functions + AsyncFunction("startPictureInPicture") { (view: MpvPlayerView) in + view.startPictureInPicture() + } + + AsyncFunction("stopPictureInPicture") { (view: MpvPlayerView) in + view.stopPictureInPicture() + } + + AsyncFunction("isPictureInPictureSupported") { (view: MpvPlayerView) -> Bool in + return view.isPictureInPictureSupported() + } + + AsyncFunction("isPictureInPictureActive") { (view: MpvPlayerView) -> Bool in + return view.isPictureInPictureActive() + } + + // Subtitle functions + AsyncFunction("getSubtitleTracks") { (view: MpvPlayerView) -> [[String: Any]] in + return view.getSubtitleTracks() + } + + AsyncFunction("setSubtitleTrack") { (view: MpvPlayerView, trackId: Int) in + view.setSubtitleTrack(trackId) + } + + AsyncFunction("disableSubtitles") { (view: MpvPlayerView) in + view.disableSubtitles() + } + + AsyncFunction("getCurrentSubtitleTrack") { (view: MpvPlayerView) -> Int in + return view.getCurrentSubtitleTrack() + } + + AsyncFunction("addSubtitleFile") { (view: MpvPlayerView, url: String, select: Bool) in + view.addSubtitleFile(url: url, select: select) + } + + // Subtitle positioning functions + AsyncFunction("setSubtitlePosition") { (view: MpvPlayerView, position: Int) in + view.setSubtitlePosition(position) + } + + AsyncFunction("setSubtitleScale") { (view: MpvPlayerView, scale: Double) in + view.setSubtitleScale(scale) + } + + AsyncFunction("setSubtitleMarginY") { (view: MpvPlayerView, margin: Int) in + view.setSubtitleMarginY(margin) + } + + AsyncFunction("setSubtitleAlignX") { (view: MpvPlayerView, alignment: String) in + view.setSubtitleAlignX(alignment) + } + + AsyncFunction("setSubtitleAlignY") { (view: MpvPlayerView, alignment: String) in + view.setSubtitleAlignY(alignment) + } + + AsyncFunction("setSubtitleFontSize") { (view: MpvPlayerView, size: Int) in + view.setSubtitleFontSize(size) + } + + // Audio track functions + AsyncFunction("getAudioTracks") { (view: MpvPlayerView) -> [[String: Any]] in + return view.getAudioTracks() + } + + AsyncFunction("setAudioTrack") { (view: MpvPlayerView, trackId: Int) in + view.setAudioTrack(trackId) + } + + AsyncFunction("getCurrentAudioTrack") { (view: MpvPlayerView) -> Int in + return view.getCurrentAudioTrack() + } + + // Defines events that the view can send to JavaScript + Events("onLoad", "onPlaybackStateChange", "onProgress", "onError", "onTracksReady") + } + } +} diff --git a/modules/mpv-player/ios/MpvPlayerView.swift b/modules/mpv-player/ios/MpvPlayerView.swift new file mode 100644 index 00000000..a6348a7b --- /dev/null +++ b/modules/mpv-player/ios/MpvPlayerView.swift @@ -0,0 +1,397 @@ +import AVFoundation +import CoreMedia +import ExpoModulesCore +import UIKit + +/// Configuration for loading a video +struct VideoLoadConfig { + let url: URL + var headers: [String: String]? + var externalSubtitles: [String]? + var startPosition: Double? + var autoplay: Bool + /// MPV subtitle track ID to select on start (1-based, -1 to disable, nil to use default) + var initialSubtitleId: Int? + /// MPV audio track ID to select on start (1-based, nil to use default) + var initialAudioId: Int? + + init( + url: URL, + headers: [String: String]? = nil, + externalSubtitles: [String]? = nil, + startPosition: Double? = nil, + autoplay: Bool = true, + initialSubtitleId: Int? = nil, + initialAudioId: Int? = nil + ) { + self.url = url + self.headers = headers + self.externalSubtitles = externalSubtitles + self.startPosition = startPosition + self.autoplay = autoplay + self.initialSubtitleId = initialSubtitleId + self.initialAudioId = initialAudioId + } +} + +// This view will be used as a native component. Make sure to inherit from `ExpoView` +// to apply the proper styling (e.g. border radius and shadows). +class MpvPlayerView: ExpoView { + private let displayLayer = AVSampleBufferDisplayLayer() + private var renderer: MPVSoftwareRenderer? + private var videoContainer: UIView! + private var pipController: PiPController? + + let onLoad = EventDispatcher() + let onPlaybackStateChange = EventDispatcher() + let onProgress = EventDispatcher() + let onError = EventDispatcher() + let onTracksReady = EventDispatcher() + + private var currentURL: URL? + private var cachedPosition: Double = 0 + private var cachedDuration: Double = 0 + private var intendedPlayState: Bool = false // For PiP - ignores transient states during seek + + required init(appContext: AppContext? = nil) { + super.init(appContext: appContext) + setupView() + } + + private func setupView() { + clipsToBounds = true + backgroundColor = .black + + videoContainer = UIView() + videoContainer.translatesAutoresizingMaskIntoConstraints = false + videoContainer.backgroundColor = .black + videoContainer.clipsToBounds = true + addSubview(videoContainer) + + displayLayer.frame = bounds + displayLayer.videoGravity = .resizeAspect + if #available(iOS 17.0, *) { + displayLayer.wantsExtendedDynamicRangeContent = true + } + displayLayer.backgroundColor = UIColor.black.cgColor + videoContainer.layer.addSublayer(displayLayer) + + NSLayoutConstraint.activate([ + videoContainer.topAnchor.constraint(equalTo: topAnchor), + videoContainer.leadingAnchor.constraint(equalTo: leadingAnchor), + videoContainer.trailingAnchor.constraint(equalTo: trailingAnchor), + videoContainer.bottomAnchor.constraint(equalTo: bottomAnchor) + ]) + + renderer = MPVSoftwareRenderer(displayLayer: displayLayer) + renderer?.delegate = self + + // Setup PiP + pipController = PiPController(sampleBufferDisplayLayer: displayLayer) + pipController?.delegate = self + + do { + try renderer?.start() + } catch { + onError(["error": "Failed to start renderer: \(error.localizedDescription)"]) + } + } + + override func layoutSubviews() { + super.layoutSubviews() + CATransaction.begin() + CATransaction.setDisableActions(true) + displayLayer.frame = videoContainer.bounds + displayLayer.isHidden = false + displayLayer.opacity = 1.0 + CATransaction.commit() + } + + func loadVideo(config: VideoLoadConfig) { + // Skip reload if same URL is already playing + if currentURL == config.url { + return + } + currentURL = config.url + + let preset = PlayerPreset( + id: .sdrRec709, + title: "Default", + summary: "Default playback preset", + stream: nil, + commands: [] + ) + + // Pass everything to the renderer - it handles start position and external subs + renderer?.load( + url: config.url, + with: preset, + headers: config.headers, + startPosition: config.startPosition, + externalSubtitles: config.externalSubtitles, + initialSubtitleId: config.initialSubtitleId, + initialAudioId: config.initialAudioId + ) + + if config.autoplay { + play() + } + + onLoad(["url": config.url.absoluteString]) + } + + // Convenience method for simple loads + func loadVideo(url: URL, headers: [String: String]? = nil) { + loadVideo(config: VideoLoadConfig(url: url, headers: headers)) + } + + func play() { + intendedPlayState = true + renderer?.play() + pipController?.updatePlaybackState() + } + + func pause() { + intendedPlayState = false + renderer?.pausePlayback() + pipController?.updatePlaybackState() + } + + func seekTo(position: Double) { + renderer?.seek(to: position) + } + + func seekBy(offset: Double) { + renderer?.seek(by: offset) + } + + func setSpeed(speed: Double) { + renderer?.setSpeed(speed) + } + + func getSpeed() -> Double { + return renderer?.getSpeed() ?? 1.0 + } + + func isPaused() -> Bool { + return renderer?.isPausedState ?? true + } + + func getCurrentPosition() -> Double { + return cachedPosition + } + + func getDuration() -> Double { + return cachedDuration + } + + // MARK: - Picture in Picture + + func startPictureInPicture() { + print("🎬 MpvPlayerView: startPictureInPicture called") + print("🎬 Duration: \(getDuration()), IsPlaying: \(!isPaused())") + pipController?.startPictureInPicture() + } + + func stopPictureInPicture() { + pipController?.stopPictureInPicture() + } + + func isPictureInPictureSupported() -> Bool { + return pipController?.isPictureInPictureSupported ?? false + } + + func isPictureInPictureActive() -> Bool { + return pipController?.isPictureInPictureActive ?? false + } + + // MARK: - Subtitle Controls + + func getSubtitleTracks() -> [[String: Any]] { + return renderer?.getSubtitleTracks() ?? [] + } + + func setSubtitleTrack(_ trackId: Int) { + renderer?.setSubtitleTrack(trackId) + } + + func disableSubtitles() { + renderer?.disableSubtitles() + } + + func getCurrentSubtitleTrack() -> Int { + return renderer?.getCurrentSubtitleTrack() ?? 0 + } + + func addSubtitleFile(url: String, select: Bool = true) { + renderer?.addSubtitleFile(url: url, select: select) + } + + // MARK: - Audio Track Controls + + func getAudioTracks() -> [[String: Any]] { + return renderer?.getAudioTracks() ?? [] + } + + func setAudioTrack(_ trackId: Int) { + renderer?.setAudioTrack(trackId) + } + + func getCurrentAudioTrack() -> Int { + return renderer?.getCurrentAudioTrack() ?? 0 + } + + // MARK: - Subtitle Positioning + + func setSubtitlePosition(_ position: Int) { + renderer?.setSubtitlePosition(position) + } + + func setSubtitleScale(_ scale: Double) { + renderer?.setSubtitleScale(scale) + } + + func setSubtitleMarginY(_ margin: Int) { + renderer?.setSubtitleMarginY(margin) + } + + func setSubtitleAlignX(_ alignment: String) { + renderer?.setSubtitleAlignX(alignment) + } + + func setSubtitleAlignY(_ alignment: String) { + renderer?.setSubtitleAlignY(alignment) + } + + func setSubtitleFontSize(_ size: Int) { + renderer?.setSubtitleFontSize(size) + } + + deinit { + pipController?.stopPictureInPicture() + renderer?.stop() + displayLayer.removeFromSuperlayer() + } +} + +// MARK: - MPVSoftwareRendererDelegate + +extension MpvPlayerView: MPVSoftwareRendererDelegate { + func renderer(_: MPVSoftwareRenderer, didUpdatePosition position: Double, duration: Double) { + cachedPosition = position + cachedDuration = duration + + DispatchQueue.main.async { [weak self] in + guard let self else { return } + // Only update PiP state when PiP is active + if self.pipController?.isPictureInPictureActive == true { + self.pipController?.updatePlaybackState() + } + + self.onProgress([ + "position": position, + "duration": duration, + "progress": duration > 0 ? position / duration : 0, + ]) + } + } + + func renderer(_: MPVSoftwareRenderer, didChangePause isPaused: Bool) { + DispatchQueue.main.async { [weak self] in + guard let self else { return } + // Don't update intendedPlayState here - it's only set by user actions (play/pause) + // This prevents PiP UI flicker during seeking + self.onPlaybackStateChange([ + "isPaused": isPaused, + "isPlaying": !isPaused, + ]) + // Note: Don't call updatePlaybackState() here to avoid flicker + // PiP queries pipControllerIsPlaying when it needs the state + } + } + + func renderer(_: MPVSoftwareRenderer, didChangeLoading isLoading: Bool) { + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.onPlaybackStateChange([ + "isLoading": isLoading, + ]) + } + } + + func renderer(_: MPVSoftwareRenderer, didBecomeReadyToSeek: Bool) { + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.onPlaybackStateChange([ + "isReadyToSeek": didBecomeReadyToSeek, + ]) + } + } + + func renderer(_: MPVSoftwareRenderer, didBecomeTracksReady: Bool) { + DispatchQueue.main.async { [weak self] in + guard let self else { return } + self.onTracksReady([:]) + } + } +} + +// MARK: - PiPControllerDelegate + +extension MpvPlayerView: PiPControllerDelegate { + func pipController(_ controller: PiPController, willStartPictureInPicture: Bool) { + print("PiP will start") + // Sync timebase before PiP starts for smooth transition + renderer?.syncTimebase() + pipController?.updatePlaybackState() + } + + func pipController(_ controller: PiPController, didStartPictureInPicture: Bool) { + print("PiP did start: \(didStartPictureInPicture)") + pipController?.updatePlaybackState() + } + + func pipController(_ controller: PiPController, willStopPictureInPicture: Bool) { + print("PiP will stop") + // Sync timebase before returning from PiP + renderer?.syncTimebase() + } + + func pipController(_ controller: PiPController, didStopPictureInPicture: Bool) { + print("PiP did stop") + // Ensure timebase is synced after PiP ends + renderer?.syncTimebase() + pipController?.updatePlaybackState() + } + + func pipController(_ controller: PiPController, restoreUserInterfaceForPictureInPictureStop completionHandler: @escaping (Bool) -> Void) { + print("PiP restore user interface") + completionHandler(true) + } + + func pipControllerPlay(_ controller: PiPController) { + print("PiP play requested") + play() + } + + func pipControllerPause(_ controller: PiPController) { + print("PiP pause requested") + pause() + } + + func pipController(_ controller: PiPController, skipByInterval interval: CMTime) { + let seconds = CMTimeGetSeconds(interval) + print("PiP skip by interval: \(seconds)") + let target = max(0, cachedPosition + seconds) + seekTo(position: target) + } + + func pipControllerIsPlaying(_ controller: PiPController) -> Bool { + // Use intended state to ignore transient pauses during seeking + return intendedPlayState + } + + func pipControllerDuration(_ controller: PiPController) -> Double { + return getDuration() + } +} diff --git a/modules/mpv-player/ios/PiPController.swift b/modules/mpv-player/ios/PiPController.swift new file mode 100644 index 00000000..80680896 --- /dev/null +++ b/modules/mpv-player/ios/PiPController.swift @@ -0,0 +1,172 @@ +import AVKit +import AVFoundation + +protocol PiPControllerDelegate: AnyObject { + func pipController(_ controller: PiPController, willStartPictureInPicture: Bool) + func pipController(_ controller: PiPController, didStartPictureInPicture: Bool) + func pipController(_ controller: PiPController, willStopPictureInPicture: Bool) + func pipController(_ controller: PiPController, didStopPictureInPicture: Bool) + func pipController(_ controller: PiPController, restoreUserInterfaceForPictureInPictureStop completionHandler: @escaping (Bool) -> Void) + func pipControllerPlay(_ controller: PiPController) + func pipControllerPause(_ controller: PiPController) + func pipController(_ controller: PiPController, skipByInterval interval: CMTime) + func pipControllerIsPlaying(_ controller: PiPController) -> Bool + func pipControllerDuration(_ controller: PiPController) -> Double +} + +final class PiPController: NSObject { + private var pipController: AVPictureInPictureController? + private weak var sampleBufferDisplayLayer: AVSampleBufferDisplayLayer? + + weak var delegate: PiPControllerDelegate? + + var isPictureInPictureSupported: Bool { + return AVPictureInPictureController.isPictureInPictureSupported() + } + + var isPictureInPictureActive: Bool { + return pipController?.isPictureInPictureActive ?? false + } + + var isPictureInPicturePossible: Bool { + return pipController?.isPictureInPicturePossible ?? false + } + + init(sampleBufferDisplayLayer: AVSampleBufferDisplayLayer) { + self.sampleBufferDisplayLayer = sampleBufferDisplayLayer + super.init() + setupPictureInPicture() + } + + private func setupPictureInPicture() { + guard isPictureInPictureSupported, + let displayLayer = sampleBufferDisplayLayer else { + return + } + + let contentSource = AVPictureInPictureController.ContentSource( + sampleBufferDisplayLayer: displayLayer, + playbackDelegate: self + ) + + pipController = AVPictureInPictureController(contentSource: contentSource) + pipController?.delegate = self + pipController?.requiresLinearPlayback = false + #if !os(tvOS) + pipController?.canStartPictureInPictureAutomaticallyFromInline = true + #endif + } + + func startPictureInPicture() { + guard let pipController = pipController, + pipController.isPictureInPicturePossible else { + return + } + + pipController.startPictureInPicture() + } + + func stopPictureInPicture() { + pipController?.stopPictureInPicture() + } + + func invalidate() { + if Thread.isMainThread { + pipController?.invalidatePlaybackState() + } else { + DispatchQueue.main.async { [weak self] in + self?.pipController?.invalidatePlaybackState() + } + } + } + + func updatePlaybackState() { + if Thread.isMainThread { + pipController?.invalidatePlaybackState() + } else { + DispatchQueue.main.async { [weak self] in + self?.pipController?.invalidatePlaybackState() + } + } + } +} + +// MARK: - AVPictureInPictureControllerDelegate + +extension PiPController: AVPictureInPictureControllerDelegate { + func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + delegate?.pipController(self, willStartPictureInPicture: true) + } + + func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + delegate?.pipController(self, didStartPictureInPicture: true) + } + + func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) { + print("Failed to start PiP: \(error)") + delegate?.pipController(self, didStartPictureInPicture: false) + } + + func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + delegate?.pipController(self, willStopPictureInPicture: true) + } + + func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + delegate?.pipController(self, didStopPictureInPicture: true) + } + + func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, restoreUserInterfaceForPictureInPictureStopWithCompletionHandler completionHandler: @escaping (Bool) -> Void) { + delegate?.pipController(self, restoreUserInterfaceForPictureInPictureStop: completionHandler) + } +} + +// MARK: - AVPictureInPictureSampleBufferPlaybackDelegate + +extension PiPController: AVPictureInPictureSampleBufferPlaybackDelegate { + + func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, setPlaying playing: Bool) { + if playing { + delegate?.pipControllerPlay(self) + } else { + delegate?.pipControllerPause(self) + } + } + + func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, didTransitionToRenderSize newRenderSize: CMVideoDimensions) { + } + + func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, skipByInterval skipInterval: CMTime, completion completionHandler: @escaping () -> Void) { + delegate?.pipController(self, skipByInterval: skipInterval) + completionHandler() + } + + var isPlaying: Bool { + return delegate?.pipControllerIsPlaying(self) ?? false + } + + var timeRangeForPlayback: CMTimeRange { + let duration = delegate?.pipControllerDuration(self) ?? 0 + if duration > 0 { + let cmDuration = CMTime(seconds: duration, preferredTimescale: 1000) + return CMTimeRange(start: .zero, duration: cmDuration) + } + return CMTimeRange(start: .zero, duration: .positiveInfinity) + } + + func pictureInPictureControllerTimeRangeForPlayback(_ pictureInPictureController: AVPictureInPictureController) -> CMTimeRange { + return timeRangeForPlayback + } + + func pictureInPictureControllerIsPlaybackPaused(_ pictureInPictureController: AVPictureInPictureController) -> Bool { + return !isPlaying + } + + func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, setPlaying playing: Bool, completion: @escaping () -> Void) { + if playing { + delegate?.pipControllerPlay(self) + } else { + delegate?.pipControllerPause(self) + } + completion() + } +} \ No newline at end of file diff --git a/modules/mpv-player/ios/PlayerPreset.swift b/modules/mpv-player/ios/PlayerPreset.swift new file mode 100644 index 00000000..38112f05 --- /dev/null +++ b/modules/mpv-player/ios/PlayerPreset.swift @@ -0,0 +1,40 @@ +import Foundation + +struct PlayerPreset: Identifiable, Hashable { + enum Identifier: String, CaseIterable { + case sdrRec709 + case hdr10 + case dolbyVisionP5 + case dolbyVisionP8 + } + + struct Stream: Hashable { + enum Source: Hashable { + case remote(URL) + case bundled(resource: String, withExtension: String) + } + + let source: Source + let note: String + + func resolveURL() -> URL? { + switch source { + case .remote(let url): + return url + case .bundled(let resource, let ext): + return Bundle.main.url(forResource: resource, withExtension: ext) + } + } + } + + let id: Identifier + let title: String + let summary: String + let stream: Stream? + let commands: [[String]] + + static var presets: [PlayerPreset] { + let list: [PlayerPreset] = [] + return list + } +} \ No newline at end of file diff --git a/modules/mpv-player/ios/SampleBufferDisplayView.swift b/modules/mpv-player/ios/SampleBufferDisplayView.swift new file mode 100644 index 00000000..8e432c33 --- /dev/null +++ b/modules/mpv-player/ios/SampleBufferDisplayView.swift @@ -0,0 +1,72 @@ +import UIKit +import AVFoundation + +final class SampleBufferDisplayView: UIView { + override class var layerClass: AnyClass { AVSampleBufferDisplayLayer.self } + + var displayLayer: AVSampleBufferDisplayLayer { + return layer as! AVSampleBufferDisplayLayer + } + + private(set) var pipController: PiPController? + + weak var pipDelegate: PiPControllerDelegate? { + didSet { + pipController?.delegate = pipDelegate + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + commonInit() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + commonInit() + } + + private func commonInit() { + backgroundColor = .black + displayLayer.videoGravity = .resizeAspect + #if !os(tvOS) + #if compiler(>=6.0) + if #available(iOS 26.0, *) { + displayLayer.preferredDynamicRange = .automatic + } else if #available(iOS 17.0, *) { + displayLayer.wantsExtendedDynamicRangeContent = true + } + #endif + if #available(iOS 17.0, *) { + displayLayer.wantsExtendedDynamicRangeContent = true + } + #endif + setupPictureInPicture() + } + + private func setupPictureInPicture() { + pipController = PiPController(sampleBufferDisplayLayer: displayLayer) + } + + // MARK: - PiP Control Methods + + func startPictureInPicture() { + pipController?.startPictureInPicture() + } + + func stopPictureInPicture() { + pipController?.stopPictureInPicture() + } + + var isPictureInPictureSupported: Bool { + return pipController?.isPictureInPictureSupported ?? false + } + + var isPictureInPictureActive: Bool { + return pipController?.isPictureInPictureActive ?? false + } + + var isPictureInPicturePossible: Bool { + return pipController?.isPictureInPicturePossible ?? false + } +} diff --git a/modules/mpv-player/src/MpvPlayer.types.ts b/modules/mpv-player/src/MpvPlayer.types.ts new file mode 100644 index 00000000..4359f515 --- /dev/null +++ b/modules/mpv-player/src/MpvPlayer.types.ts @@ -0,0 +1,105 @@ +import type { StyleProp, ViewStyle } from "react-native"; + +export type OnLoadEventPayload = { + url: string; +}; + +export type OnPlaybackStateChangePayload = { + isPaused?: boolean; + isPlaying?: boolean; + isLoading?: boolean; + isReadyToSeek?: boolean; +}; + +export type OnProgressEventPayload = { + position: number; + duration: number; + progress: number; +}; + +export type OnErrorEventPayload = { + error: string; +}; + +export type OnTracksReadyEventPayload = Record; + +export type MpvPlayerModuleEvents = { + onChange: (params: ChangeEventPayload) => void; +}; + +export type ChangeEventPayload = { + value: string; +}; + +export type VideoSource = { + url: string; + headers?: Record; + externalSubtitles?: string[]; + startPosition?: number; + autoplay?: boolean; + /** MPV subtitle track ID to select on start (1-based, -1 to disable) */ + initialSubtitleId?: number; + /** MPV audio track ID to select on start (1-based) */ + initialAudioId?: number; +}; + +export type MpvPlayerViewProps = { + source?: VideoSource; + style?: StyleProp; + onLoad?: (event: { nativeEvent: OnLoadEventPayload }) => void; + onPlaybackStateChange?: (event: { + nativeEvent: OnPlaybackStateChangePayload; + }) => void; + onProgress?: (event: { nativeEvent: OnProgressEventPayload }) => void; + onError?: (event: { nativeEvent: OnErrorEventPayload }) => void; + onTracksReady?: (event: { nativeEvent: OnTracksReadyEventPayload }) => void; +}; + +export interface MpvPlayerViewRef { + play: () => Promise; + pause: () => Promise; + seekTo: (position: number) => Promise; + seekBy: (offset: number) => Promise; + setSpeed: (speed: number) => Promise; + getSpeed: () => Promise; + isPaused: () => Promise; + getCurrentPosition: () => Promise; + getDuration: () => Promise; + startPictureInPicture: () => Promise; + stopPictureInPicture: () => Promise; + isPictureInPictureSupported: () => Promise; + isPictureInPictureActive: () => Promise; + // Subtitle controls + getSubtitleTracks: () => Promise; + setSubtitleTrack: (trackId: number) => Promise; + disableSubtitles: () => Promise; + getCurrentSubtitleTrack: () => Promise; + addSubtitleFile: (url: string, select?: boolean) => Promise; + // Subtitle positioning + setSubtitlePosition: (position: number) => Promise; + setSubtitleScale: (scale: number) => Promise; + setSubtitleMarginY: (margin: number) => Promise; + setSubtitleAlignX: (alignment: "left" | "center" | "right") => Promise; + setSubtitleAlignY: (alignment: "top" | "center" | "bottom") => Promise; + setSubtitleFontSize: (size: number) => Promise; + // Audio controls + getAudioTracks: () => Promise; + setAudioTrack: (trackId: number) => Promise; + getCurrentAudioTrack: () => Promise; +} + +export type SubtitleTrack = { + id: number; + title?: string; + lang?: string; + selected?: boolean; +}; + +export type AudioTrack = { + id: number; + title?: string; + lang?: string; + codec?: string; + channels?: number; + selected?: boolean; +}; diff --git a/modules/mpv-player/src/MpvPlayerModule.ts b/modules/mpv-player/src/MpvPlayerModule.ts new file mode 100644 index 00000000..a1b72af8 --- /dev/null +++ b/modules/mpv-player/src/MpvPlayerModule.ts @@ -0,0 +1,11 @@ +import { NativeModule, requireNativeModule } from "expo"; + +import { MpvPlayerModuleEvents } from "./MpvPlayer.types"; + +declare class MpvPlayerModule extends NativeModule { + hello(): string; + setValueAsync(value: string): Promise; +} + +// This call loads the native module object from the JSI. +export default requireNativeModule("MpvPlayer"); diff --git a/modules/mpv-player/src/MpvPlayerModule.web.ts b/modules/mpv-player/src/MpvPlayerModule.web.ts new file mode 100644 index 00000000..47e29e15 --- /dev/null +++ b/modules/mpv-player/src/MpvPlayerModule.web.ts @@ -0,0 +1,19 @@ +import { NativeModule, registerWebModule } from "expo"; + +import { ChangeEventPayload } from "./MpvPlayer.types"; + +type MpvPlayerModuleEvents = { + onChange: (params: ChangeEventPayload) => void; +}; + +class MpvPlayerModule extends NativeModule { + PI = Math.PI; + async setValueAsync(value: string): Promise { + this.emit("onChange", { value }); + } + hello() { + return "Hello world! 👋"; + } +} + +export default registerWebModule(MpvPlayerModule, "MpvPlayerModule"); diff --git a/modules/mpv-player/src/MpvPlayerView.tsx b/modules/mpv-player/src/MpvPlayerView.tsx new file mode 100644 index 00000000..e29d3ad5 --- /dev/null +++ b/modules/mpv-player/src/MpvPlayerView.tsx @@ -0,0 +1,101 @@ +import { requireNativeView } from "expo"; +import * as React from "react"; +import { useImperativeHandle, useRef } from "react"; + +import { MpvPlayerViewProps, MpvPlayerViewRef } from "./MpvPlayer.types"; + +const NativeView: React.ComponentType = + requireNativeView("MpvPlayer"); + +export default React.forwardRef( + function MpvPlayerView(props, ref) { + const nativeRef = useRef(null); + + useImperativeHandle(ref, () => ({ + play: async () => { + await nativeRef.current?.play(); + }, + pause: async () => { + await nativeRef.current?.pause(); + }, + seekTo: async (position: number) => { + await nativeRef.current?.seekTo(position); + }, + seekBy: async (offset: number) => { + await nativeRef.current?.seekBy(offset); + }, + setSpeed: async (speed: number) => { + await nativeRef.current?.setSpeed(speed); + }, + getSpeed: async () => { + return await nativeRef.current?.getSpeed(); + }, + isPaused: async () => { + return await nativeRef.current?.isPaused(); + }, + getCurrentPosition: async () => { + return await nativeRef.current?.getCurrentPosition(); + }, + getDuration: async () => { + return await nativeRef.current?.getDuration(); + }, + startPictureInPicture: async () => { + await nativeRef.current?.startPictureInPicture(); + }, + stopPictureInPicture: async () => { + await nativeRef.current?.stopPictureInPicture(); + }, + isPictureInPictureSupported: async () => { + return await nativeRef.current?.isPictureInPictureSupported(); + }, + isPictureInPictureActive: async () => { + return await nativeRef.current?.isPictureInPictureActive(); + }, + getSubtitleTracks: async () => { + return await nativeRef.current?.getSubtitleTracks(); + }, + setSubtitleTrack: async (trackId: number) => { + await nativeRef.current?.setSubtitleTrack(trackId); + }, + disableSubtitles: async () => { + await nativeRef.current?.disableSubtitles(); + }, + getCurrentSubtitleTrack: async () => { + return await nativeRef.current?.getCurrentSubtitleTrack(); + }, + addSubtitleFile: async (url: string, select = true) => { + await nativeRef.current?.addSubtitleFile(url, select); + }, + setSubtitlePosition: async (position: number) => { + await nativeRef.current?.setSubtitlePosition(position); + }, + setSubtitleScale: async (scale: number) => { + await nativeRef.current?.setSubtitleScale(scale); + }, + setSubtitleMarginY: async (margin: number) => { + await nativeRef.current?.setSubtitleMarginY(margin); + }, + setSubtitleAlignX: async (alignment: "left" | "center" | "right") => { + await nativeRef.current?.setSubtitleAlignX(alignment); + }, + setSubtitleAlignY: async (alignment: "top" | "center" | "bottom") => { + await nativeRef.current?.setSubtitleAlignY(alignment); + }, + setSubtitleFontSize: async (size: number) => { + await nativeRef.current?.setSubtitleFontSize(size); + }, + // Audio controls + getAudioTracks: async () => { + return await nativeRef.current?.getAudioTracks(); + }, + setAudioTrack: async (trackId: number) => { + await nativeRef.current?.setAudioTrack(trackId); + }, + getCurrentAudioTrack: async () => { + return await nativeRef.current?.getCurrentAudioTrack(); + }, + })); + + return ; + }, +); diff --git a/modules/mpv-player/src/MpvPlayerView.web.tsx b/modules/mpv-player/src/MpvPlayerView.web.tsx new file mode 100644 index 00000000..323f4341 --- /dev/null +++ b/modules/mpv-player/src/MpvPlayerView.web.tsx @@ -0,0 +1,15 @@ +import { MpvPlayerViewProps } from "./MpvPlayer.types"; + +export default function MpvPlayerView(props: MpvPlayerViewProps) { + const url = props.source?.url; + return ( +
+