diff --git a/app/(auth)/player/direct-player.tsx b/app/(auth)/player/direct-player.tsx index 7848a597..0ebb3273 100644 --- a/app/(auth)/player/direct-player.tsx +++ b/app/(auth)/player/direct-player.tsx @@ -17,7 +17,7 @@ import { apiAtom, userAtom } from "@/providers/JellyfinProvider"; import { useSettings } from "@/utils/atoms/settings"; import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl"; import { writeToLog } from "@/utils/log"; -import native from "@/utils/profiles/native"; +import generateDeviceProfile from "@/utils/profiles/native"; import { msToTicks, ticksToSeconds } from "@/utils/time"; import { type BaseItemDto, @@ -159,6 +159,7 @@ export default function page() { useEffect(() => { const fetchStreamData = async () => { + const native = await generateDeviceProfile(); try { let result: Stream | null = null; if (offline && !Platform.isTV) { diff --git a/components/PlayButton.tsx b/components/PlayButton.tsx index 8bbbe046..83c3a81c 100644 --- a/components/PlayButton.tsx +++ b/components/PlayButton.tsx @@ -7,7 +7,6 @@ import { getPrimaryImageUrl } from "@/utils/jellyfin/image/getPrimaryImageUrl"; import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl"; import { chromecast } from "@/utils/profiles/chromecast"; import { chromecasth265 } from "@/utils/profiles/chromecasth265"; -import ios from "@/utils/profiles/ios"; import { runtimeTicksToMinutes } from "@/utils/time"; import { useActionSheet } from "@expo/react-native-action-sheet"; import { Feather, Ionicons, MaterialCommunityIcons } from "@expo/vector-icons"; diff --git a/providers/PlaySettingsProvider.tsx b/providers/PlaySettingsProvider.tsx index fad502fb..cf61248d 100644 --- a/providers/PlaySettingsProvider.tsx +++ b/providers/PlaySettingsProvider.tsx @@ -1,7 +1,7 @@ import type { Bitrate } from "@/components/BitrateSelector"; import { settingsAtom } from "@/utils/atoms/settings"; import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl"; -import native from "@/utils/profiles/native"; +import generateDeviceProfile from "@/utils/profiles/native"; import type { BaseItemDto, MediaSourceInfo, @@ -84,6 +84,7 @@ export const PlaySettingsProvider: React.FC<{ children: React.ReactNode }> = ({ } try { + const native = await generateDeviceProfile(); const data = await getStreamUrl({ api, deviceProfile: native, diff --git a/utils/jellyfin/media/getStreamUrl.ts b/utils/jellyfin/media/getStreamUrl.ts index bfe9a90c..253478a0 100644 --- a/utils/jellyfin/media/getStreamUrl.ts +++ b/utils/jellyfin/media/getStreamUrl.ts @@ -1,4 +1,4 @@ -import native from "@/utils/profiles/native"; +import generateDeviceProfile from "@/utils/profiles/native"; import type { Api } from "@jellyfin/sdk"; import type { BaseItemDto, @@ -15,7 +15,7 @@ export const getStreamUrl = async ({ startTimeTicks = 0, maxStreamingBitrate, sessionData, - deviceProfile = native, + deviceProfile = generateDeviceProfile(), audioStreamIndex = 0, subtitleStreamIndex = undefined, mediaSourceId, diff --git a/utils/jellyfin/playstate/reportPlaybackProgress.ts b/utils/jellyfin/playstate/reportPlaybackProgress.ts index 282a9b59..76e27c25 100644 --- a/utils/jellyfin/playstate/reportPlaybackProgress.ts +++ b/utils/jellyfin/playstate/reportPlaybackProgress.ts @@ -1,7 +1,5 @@ import { getOrSetDeviceId } from "@/providers/JellyfinProvider"; import type { Settings } from "@/utils/atoms/settings"; -import ios from "@/utils/profiles/ios"; -import native from "@/utils/profiles/native"; import old from "@/utils/profiles/old"; import type { Api } from "@jellyfin/sdk"; import { DeviceProfile } from "@jellyfin/sdk/lib/generated-client"; diff --git a/utils/jellyfin/session/capabilities.ts b/utils/jellyfin/session/capabilities.ts index 1c2a04af..50e8bcbd 100644 --- a/utils/jellyfin/session/capabilities.ts +++ b/utils/jellyfin/session/capabilities.ts @@ -1,5 +1,5 @@ import type { Settings } from "@/utils/atoms/settings"; -import native from "@/utils/profiles/native"; +import generateDeviceProfile from "@/utils/profiles/native"; import type { Api } from "@jellyfin/sdk"; import type { AxiosResponse } from "axios"; import { getAuthHeaders } from "../jellyfin"; @@ -43,7 +43,7 @@ export const postCapabilities = async ({ ], supportsMediaControl: true, id: sessionId, - DeviceProfile: native, + DeviceProfile: generateDeviceProfile(), }, { headers: getAuthHeaders(api), diff --git a/utils/profiles/android.js b/utils/profiles/android.js deleted file mode 100644 index 19665c7f..00000000 --- a/utils/profiles/android.js +++ /dev/null @@ -1,143 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -import MediaTypes from "../../constants/MediaTypes"; - -/** - * Device profile for Native video player - */ -export default { - Name: "1. Native iOS Video Profile", - MaxStaticBitrate: 100000000, - MaxStreamingBitrate: 120000000, - MusicStreamingTranscodingBitrate: 384000, - CodecProfiles: [ - { - Type: MediaTypes.Video, - Codec: "h264,h265,hevc,mpeg4,divx,xvid,wmv,vc1,vp8,vp9,av1", - }, - { - Type: MediaTypes.Audio, - Codec: "aac,mp3,flac,alac,opus,vorbis,pcm,wma", - }, - ], - DirectPlayProfiles: [ - { - Type: MediaTypes.Video, - Container: "mp4,mkv,avi,mov,flv,ts,m2ts,webm,ogv,3gp", - VideoCodec: "h264,h265,hevc,mpeg4,divx,xvid,wmv,vc1,vp8,vp9,av1", - AudioCodec: "aac,mp3,flac,alac,opus,vorbis,wma", - }, - { - Type: MediaTypes.Audio, - Container: "mp3,aac,flac,alac,wav,ogg,wma", - AudioCodec: "mp3,aac,flac,alac,opus,vorbis,wma,pcm", - }, - ], - TranscodingProfiles: [ - { - Type: MediaTypes.Video, - Context: "Streaming", - Protocol: "hls", - Container: "ts", - VideoCodec: "h264", - AudioCodec: "aac,mp3,ac3", - MaxAudioChannels: "8", - MinSegments: "2", - BreakOnNonKeyFrames: true, - }, - { - Type: MediaTypes.Audio, - Context: "Streaming", - Protocol: "http", - Container: "mp3", - AudioCodec: "mp3", - MaxAudioChannels: "2", - }, - ], - ResponseProfiles: [ - { - Container: "mkv", - MimeType: "video/x-matroska", - Type: MediaTypes.Video, - }, - { - Container: "mp4", - MimeType: "video/mp4", - Type: MediaTypes.Video, - }, - ], - SubtitleProfiles: [ - { Format: "srt", Method: "Embed" }, - { Format: "srt", Method: "External" }, - { Format: "srt", Method: "Encode" }, - { Format: "ass", Method: "Embed" }, - { Format: "ass", Method: "External" }, - { Format: "ass", Method: "Encode" }, - { Format: "ssa", Method: "Embed" }, - { Format: "ssa", Method: "External" }, - { Format: "ssa", Method: "Encode" }, - { Format: "sub", Method: "Embed" }, - { Format: "sub", Method: "External" }, - { Format: "sub", Method: "Encode" }, - { Format: "vtt", Method: "Embed" }, - { Format: "vtt", Method: "External" }, - { Format: "vtt", Method: "Encode" }, - { Format: "ttml", Method: "Embed" }, - { Format: "ttml", Method: "External" }, - { Format: "ttml", Method: "Encode" }, - { Format: "pgs", Method: "Embed" }, - { Format: "pgs", Method: "External" }, - { Format: "pgs", Method: "Encode" }, - { Format: "dvdsub", Method: "Embed" }, - { Format: "dvdsub", Method: "External" }, - { Format: "dvdsub", Method: "Encode" }, - { Format: "dvbsub", Method: "Embed" }, - { Format: "dvbsub", Method: "External" }, - { Format: "dvbsub", Method: "Encode" }, - { Format: "xsub", Method: "Embed" }, - { Format: "xsub", Method: "External" }, - { Format: "xsub", Method: "Encode" }, - { Format: "mov_text", Method: "Embed" }, - { Format: "mov_text", Method: "External" }, - { Format: "mov_text", Method: "Encode" }, - { Format: "scc", Method: "Embed" }, - { Format: "scc", Method: "External" }, - { Format: "scc", Method: "Encode" }, - { Format: "smi", Method: "Embed" }, - { Format: "smi", Method: "External" }, - { Format: "smi", Method: "Encode" }, - { Format: "teletext", Method: "Embed" }, - { Format: "teletext", Method: "External" }, - { Format: "teletext", Method: "Encode" }, - { Format: "microdvd", Method: "Embed" }, - { Format: "microdvd", Method: "External" }, - { Format: "microdvd", Method: "Encode" }, - { Format: "mpl2", Method: "Embed" }, - { Format: "mpl2", Method: "External" }, - { Format: "mpl2", Method: "Encode" }, - { Format: "pjs", Method: "Embed" }, - { Format: "pjs", Method: "External" }, - { Format: "pjs", Method: "Encode" }, - { Format: "realtext", Method: "Embed" }, - { Format: "realtext", Method: "External" }, - { Format: "realtext", Method: "Encode" }, - { Format: "stl", Method: "Embed" }, - { Format: "stl", Method: "External" }, - { Format: "stl", Method: "Encode" }, - { Format: "subrip", Method: "Embed" }, - { Format: "subrip", Method: "External" }, - { Format: "subrip", Method: "Encode" }, - { Format: "subviewer", Method: "Embed" }, - { Format: "subviewer", Method: "External" }, - { Format: "subviewer", Method: "Encode" }, - { Format: "text", Method: "Embed" }, - { Format: "text", Method: "External" }, - { Format: "text", Method: "Encode" }, - { Format: "vplayer", Method: "Embed" }, - { Format: "vplayer", Method: "External" }, - { Format: "vplayer", Method: "Encode" }, - ], -}; diff --git a/utils/profiles/base.js b/utils/profiles/base.js deleted file mode 100644 index 1ff7dc46..00000000 --- a/utils/profiles/base.js +++ /dev/null @@ -1,86 +0,0 @@ -import MediaTypes from "../../constants/MediaTypes"; - -export default { - Name: "Expo Base Video Profile", - MaxStaticBitrate: 100000000, - MaxStreamingBitrate: 120000000, - MusicStreamingTranscodingBitrate: 384000, - CodecProfiles: [ - { - Codec: "h264", - Conditions: [ - { - Condition: "NotEquals", - IsRequired: false, - Property: "IsAnamorphic", - Value: "true", - }, - { - Condition: "EqualsAny", - IsRequired: false, - Property: "VideoProfile", - Value: "high|main|baseline|constrained baseline", - }, - { - Condition: "LessThanEqual", - IsRequired: false, - Property: "VideoLevel", - Value: "51", - }, - { - Condition: "NotEquals", - IsRequired: false, - Property: "IsInterlaced", - Value: "true", - }, - ], - Type: MediaTypes.Video, - }, - { - Codec: "hevc", - Conditions: [ - { - Condition: "NotEquals", - IsRequired: false, - Property: "IsAnamorphic", - Value: "true", - }, - { - Condition: "EqualsAny", - IsRequired: false, - Property: "VideoProfile", - Value: "main|main 10", - }, - { - Condition: "LessThanEqual", - IsRequired: false, - Property: "VideoLevel", - Value: "183", - }, - { - Condition: "NotEquals", - IsRequired: false, - Property: "IsInterlaced", - Value: "true", - }, - ], - Type: MediaTypes.Video, - }, - ], - ContainerProfiles: [], - DirectPlayProfiles: [], - ResponseProfiles: [ - { - Container: "m4v", - MimeType: "video/mp4", - Type: MediaTypes.Video, - }, - ], - SubtitleProfiles: [ - { - Format: "vtt", - Method: "Hls", - }, - ], - TranscodingProfiles: [], -}; diff --git a/utils/profiles/ios.js b/utils/profiles/ios.js deleted file mode 100644 index 7708a94c..00000000 --- a/utils/profiles/ios.js +++ /dev/null @@ -1,149 +0,0 @@ -/** - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -import MediaTypes from "../../constants/MediaTypes"; - -import BaseProfile from "./base"; - -/** - * Device profile for Expo Video player on iOS 13+ - */ -export default { - ...BaseProfile, - Name: "Expo iOS Video Profile", - DirectPlayProfiles: [ - { - AudioCodec: "aac,mp3,ac3,eac3,flac,alac", - Container: "mp4,m4v", - Type: MediaTypes.Video, - VideoCodec: "hevc,h264", - }, - { - AudioCodec: "aac,mp3,ac3,eac3,flac,alac", - Container: "mov", - Type: MediaTypes.Video, - VideoCodec: "hevc,h264", - }, - { - Container: "mp3", - Type: MediaTypes.Audio, - }, - { - Container: "aac", - Type: MediaTypes.Audio, - }, - { - AudioCodec: "aac", - Container: "m4a", - Type: MediaTypes.Audio, - }, - { - AudioCodec: "aac", - Container: "m4b", - Type: MediaTypes.Audio, - }, - { - Container: "flac", - Type: MediaTypes.Audio, - }, - { - Container: "alac", - Type: MediaTypes.Audio, - }, - { - AudioCodec: "alac", - Container: "m4a", - Type: MediaTypes.Audio, - }, - { - AudioCodec: "alac", - Container: "m4b", - Type: MediaTypes.Audio, - }, - { - Container: "wav", - Type: MediaTypes.Audio, - }, - ], - TranscodingProfiles: [ - { - AudioCodec: "aac", - BreakOnNonKeyFrames: true, - Container: "aac", - Context: "Streaming", - MaxAudioChannels: "6", - MinSegments: "2", - Protocol: "hls", - Type: MediaTypes.Audio, - }, - { - AudioCodec: "aac", - Container: "aac", - Context: "Streaming", - MaxAudioChannels: "6", - Protocol: "http", - Type: MediaTypes.Audio, - }, - { - AudioCodec: "mp3", - Container: "mp3", - Context: "Streaming", - MaxAudioChannels: "6", - Protocol: "http", - Type: MediaTypes.Audio, - }, - { - AudioCodec: "wav", - Container: "wav", - Context: "Streaming", - MaxAudioChannels: "6", - Protocol: "http", - Type: MediaTypes.Audio, - }, - { - AudioCodec: "mp3", - Container: "mp3", - Context: "Static", - MaxAudioChannels: "6", - Protocol: "http", - Type: MediaTypes.Audio, - }, - { - AudioCodec: "aac", - Container: "aac", - Context: "Static", - MaxAudioChannels: "6", - Protocol: "http", - Type: MediaTypes.Audio, - }, - { - AudioCodec: "wav", - Container: "wav", - Context: "Static", - MaxAudioChannels: "6", - Protocol: "http", - Type: MediaTypes.Audio, - }, - { - AudioCodec: "aac,mp3", - BreakOnNonKeyFrames: true, - Container: "ts", - Context: "Streaming", - MaxAudioChannels: "6", - MinSegments: "2", - Protocol: "hls", - Type: MediaTypes.Video, - VideoCodec: "h264", - }, - { - AudioCodec: "aac,mp3,ac3,eac3,flac,alac", - Container: "mp4", - Context: "Static", - Protocol: "http", - Type: MediaTypes.Video, - VideoCodec: "h264", - }, - ], -}; diff --git a/utils/profiles/native.js b/utils/profiles/native.js index 92f36b02..b97dc4c8 100644 --- a/utils/profiles/native.js +++ b/utils/profiles/native.js @@ -1,3 +1,5 @@ +import { Platform } from "react-native"; +import DeviceInfo from "react-native-device-info"; /** * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this @@ -5,132 +7,190 @@ */ import MediaTypes from "../../constants/MediaTypes"; -/** - * Device profile for Native video player - */ -export default { - Name: "1. Vlc Player", - MaxStaticBitrate: 999_999_999, - MaxStreamingBitrate: 999_999_999, - CodecProfiles: [ - { - Type: MediaTypes.Video, - Codec: "h264,h265,hevc,mpeg4,divx,xvid,wmv,vc1,vp8,vp9,av1", - }, - { - Type: MediaTypes.Audio, - Codec: "aac,ac3,eac3,mp3,flac,alac,opus,vorbis,pcm,wma", - }, - ], - DirectPlayProfiles: [ - { - Type: MediaTypes.Video, - Container: "mp4,mkv,avi,mov,flv,ts,m2ts,webm,ogv,3gp,hls", - VideoCodec: - "h264,hevc,mpeg4,divx,xvid,wmv,vc1,vp8,vp9,av1,avi,mpeg,mpeg2video", - AudioCodec: "aac,ac3,eac3,mp3,flac,alac,opus,vorbis,wma,dts", - }, - { - Type: MediaTypes.Audio, - Container: "mp3,aac,flac,alac,wav,ogg,wma", - AudioCodec: - "mp3,aac,flac,alac,opus,vorbis,wma,pcm,mpa,wav,ogg,oga,webma,ape", - }, - ], - TranscodingProfiles: [ - { - Type: MediaTypes.Video, - Context: "Streaming", - Protocol: "hls", - Container: "fmp4", - VideoCodec: "h264, hevc", - AudioCodec: "aac,mp3,ac3,dts", - }, - { - Type: MediaTypes.Audio, - Context: "Streaming", - Protocol: "http", - Container: "mp3", - AudioCodec: "mp3", - MaxAudioChannels: "2", - }, - ], - SubtitleProfiles: [ - // Official formats - { Format: "vtt", Method: "Embed" }, - { Format: "vtt", Method: "External" }, +// Helper function to detect Dolby Vision support +const supportsDolbyVision = async () => { + if (Platform.OS === "ios") { + const deviceModel = await DeviceInfo.getModel(); + // iPhone 12 and newer generally support Dolby Vision + const modelNumber = Number.parseInt(deviceModel.replace(/iPhone/, ""), 10); + return !Number.isNaN(modelNumber) && modelNumber >= 12; + } - { Format: "webvtt", Method: "Embed" }, - { Format: "webvtt", Method: "External" }, + if (Platform.OS === "android") { + const apiLevel = await DeviceInfo.getApiLevel(); + const isHighEndDevice = + (await DeviceInfo.getTotalMemory()) > 4 * 1024 * 1024 * 1024; // >4GB RAM + // Very rough approximation - Android 10+ on higher-end devices may support it + return apiLevel >= 29 && isHighEndDevice; + } - { Format: "srt", Method: "Embed" }, - { Format: "srt", Method: "External" }, - - { Format: "subrip", Method: "Embed" }, - { Format: "subrip", Method: "External" }, - - { Format: "ttml", Method: "Embed" }, - { Format: "ttml", Method: "External" }, - - { Format: "dvbsub", Method: "Embed" }, - { Format: "dvdsub", Method: "Encode" }, - - { Format: "ass", Method: "Embed" }, - { Format: "ass", Method: "External" }, - - { Format: "idx", Method: "Embed" }, - { Format: "idx", Method: "Encode" }, - - { Format: "pgs", Method: "Embed" }, - { Format: "pgs", Method: "Encode" }, - - { Format: "pgssub", Method: "Embed" }, - { Format: "pgssub", Method: "Encode" }, - - { Format: "ssa", Method: "Embed" }, - { Format: "ssa", Method: "External" }, - - // Other formats - { Format: "microdvd", Method: "Embed" }, - { Format: "microdvd", Method: "External" }, - - { Format: "mov_text", Method: "Embed" }, - { Format: "mov_text", Method: "External" }, - - { Format: "mpl2", Method: "Embed" }, - { Format: "mpl2", Method: "External" }, - - { Format: "pjs", Method: "Embed" }, - { Format: "pjs", Method: "External" }, - - { Format: "realtext", Method: "Embed" }, - { Format: "realtext", Method: "External" }, - - { Format: "scc", Method: "Embed" }, - { Format: "scc", Method: "External" }, - - { Format: "smi", Method: "Embed" }, - { Format: "smi", Method: "External" }, - - { Format: "stl", Method: "Embed" }, - { Format: "stl", Method: "External" }, - - { Format: "sub", Method: "Embed" }, - { Format: "sub", Method: "External" }, - - { Format: "subviewer", Method: "Embed" }, - { Format: "subviewer", Method: "External" }, - - { Format: "teletext", Method: "Embed" }, - { Format: "teletext", Method: "Encode" }, - - { Format: "text", Method: "Embed" }, - { Format: "text", Method: "External" }, - - { Format: "vplayer", Method: "Embed" }, - { Format: "vplayer", Method: "External" }, - - { Format: "xsub", Method: "Embed" }, - { Format: "xsub", Method: "External" }, - ], + return false; +}; + +export const generateDeviceProfile = async () => { + const dolbyVisionSupported = await supportsDolbyVision(); + /** + * Device profile for Native video player + */ + const profile = { + Name: "1. Vlc Player", + MaxStaticBitrate: 999_999_999, + MaxStreamingBitrate: 999_999_999, + CodecProfiles: [ + { + Type: MediaTypes.Video, + Codec: "h264,mpeg4,divx,xvid,wmv,vc1,vp8,vp9,av1", + }, + { + Type: MediaTypes.Video, + Codec: "hevc,h265", + Conditions: [ + { + Condition: "LessThanEqual", + Property: "VideoLevel", + Value: "153", + IsRequired: false, + }, + // We'll add Dolby Vision condition below if not supported + ], + }, + { + Type: MediaTypes.Audio, + Codec: "aac,ac3,eac3,mp3,flac,alac,opus,vorbis,pcm,wma", + }, + ], + DirectPlayProfiles: [ + { + Type: MediaTypes.Video, + Container: "mp4,mkv,avi,mov,flv,ts,m2ts,webm,ogv,3gp,hls", + VideoCodec: + "h264,hevc,mpeg4,divx,xvid,wmv,vc1,vp8,vp9,av1,avi,mpeg,mpeg2video", + AudioCodec: "aac,ac3,eac3,mp3,flac,alac,opus,vorbis,wma,dts", + }, + { + Type: MediaTypes.Audio, + Container: "mp3,aac,flac,alac,wav,ogg,wma", + AudioCodec: + "mp3,aac,flac,alac,opus,vorbis,wma,pcm,mpa,wav,ogg,oga,webma,ape", + }, + ], + TranscodingProfiles: [ + { + Type: MediaTypes.Video, + Context: "Streaming", + Protocol: "hls", + Container: "fmp4", + VideoCodec: "h264, hevc", + AudioCodec: "aac,mp3,ac3,dts", + }, + { + Type: MediaTypes.Audio, + Context: "Streaming", + Protocol: "http", + Container: "mp3", + AudioCodec: "mp3", + MaxAudioChannels: "2", + }, + ], + SubtitleProfiles: [ + // Official formats + { Format: "vtt", Method: "Embed" }, + { Format: "vtt", Method: "External" }, + + { Format: "webvtt", Method: "Embed" }, + { Format: "webvtt", Method: "External" }, + + { Format: "srt", Method: "Embed" }, + { Format: "srt", Method: "External" }, + + { Format: "subrip", Method: "Embed" }, + { Format: "subrip", Method: "External" }, + + { Format: "ttml", Method: "Embed" }, + { Format: "ttml", Method: "External" }, + + { Format: "dvbsub", Method: "Embed" }, + { Format: "dvdsub", Method: "Encode" }, + + { Format: "ass", Method: "Embed" }, + { Format: "ass", Method: "External" }, + + { Format: "idx", Method: "Embed" }, + { Format: "idx", Method: "Encode" }, + + { Format: "pgs", Method: "Embed" }, + { Format: "pgs", Method: "Encode" }, + + { Format: "pgssub", Method: "Embed" }, + { Format: "pgssub", Method: "Encode" }, + + { Format: "ssa", Method: "Embed" }, + { Format: "ssa", Method: "External" }, + + // Other formats + { Format: "microdvd", Method: "Embed" }, + { Format: "microdvd", Method: "External" }, + + { Format: "mov_text", Method: "Embed" }, + { Format: "mov_text", Method: "External" }, + + { Format: "mpl2", Method: "Embed" }, + { Format: "mpl2", Method: "External" }, + + { Format: "pjs", Method: "Embed" }, + { Format: "pjs", Method: "External" }, + + { Format: "realtext", Method: "Embed" }, + { Format: "realtext", Method: "External" }, + + { Format: "scc", Method: "Embed" }, + { Format: "scc", Method: "External" }, + + { Format: "smi", Method: "Embed" }, + { Format: "smi", Method: "External" }, + + { Format: "stl", Method: "Embed" }, + { Format: "stl", Method: "External" }, + + { Format: "sub", Method: "Embed" }, + { Format: "sub", Method: "External" }, + + { Format: "subviewer", Method: "Embed" }, + { Format: "subviewer", Method: "External" }, + + { Format: "teletext", Method: "Embed" }, + { Format: "teletext", Method: "Encode" }, + + { Format: "text", Method: "Embed" }, + { Format: "text", Method: "External" }, + + { Format: "vplayer", Method: "Embed" }, + { Format: "vplayer", Method: "External" }, + + { Format: "xsub", Method: "Embed" }, + { Format: "xsub", Method: "External" }, + ], + }; + + // Add Dolby Vision restriction if not supported + if (!dolbyVisionSupported) { + const hevcProfile = profile.CodecProfiles.find( + (p) => p.Type === MediaTypes.Video && p.Codec.includes("hevc"), + ); + + if (hevcProfile) { + hevcProfile.Conditions.push({ + Condition: "NotEquals", + Property: "VideoRangeType", + Value: "DOVI", //no dolby vision at all + IsRequired: true, + }); + } + } + + return profile; +}; + +export default async () => { + return await generateDeviceProfile(); };