diff --git a/app/(auth)/player/_layout.tsx b/app/(auth)/player/_layout.tsx
index dd9d2879..96d08058 100644
--- a/app/(auth)/player/_layout.tsx
+++ b/app/(auth)/player/_layout.tsx
@@ -8,7 +8,16 @@ export default function Layout() {
+ (null);
@@ -141,7 +140,7 @@ export default function page() {
maxStreamingBitrate: bitrateValue,
mediaSourceId: mediaSourceId,
subtitleStreamIndex: subtitleIndex,
- deviceProfile: !bitrateValue ? native : transcoding,
+ deviceProfile: native,
});
if (!res) return null;
@@ -374,18 +373,8 @@ export default function page() {
DeliveryUrl: `${api?.basePath || ""}${chosenSubtitleTrack.DeliveryUrl}`,
};
}
-
if (!chosenAudioTrack) throw new Error("No audio track found");
-
initOptions.push(`--audio-track=${allAudio.indexOf(chosenAudioTrack)}`);
- } else {
- // Transcoded playback CASE
- if (chosenSubtitleTrack?.DeliveryMethod === "Hls") {
- externalTrack = {
- name: `subs ${chosenSubtitleTrack.DisplayTitle}`,
- DeliveryUrl: "",
- };
- }
}
return (
diff --git a/app/(auth)/player/transcoding-player.tsx b/app/(auth)/player/transcoding-player.tsx
new file mode 100644
index 00000000..d568170d
--- /dev/null
+++ b/app/(auth)/player/transcoding-player.tsx
@@ -0,0 +1,546 @@
+import { Text } from "@/components/common/Text";
+import { Loader } from "@/components/Loader";
+import { useOrientation } from "@/hooks/useOrientation";
+import { useOrientationSettings } from "@/hooks/useOrientationSettings";
+import { useWebSocket } from "@/hooks/useWebsockets";
+import { TrackInfo } from "@/modules/vlc-player";
+import { apiAtom, userAtom } from "@/providers/JellyfinProvider";
+import { useSettings } from "@/utils/atoms/settings";
+import { getBackdropUrl } from "@/utils/jellyfin/image/getBackdropUrl";
+import { getAuthHeaders } from "@/utils/jellyfin/jellyfin";
+import { getStreamUrl } from "@/utils/jellyfin/media/getStreamUrl";
+import { secondsToTicks } from "@/utils/secondsToTicks";
+import { Api } from "@jellyfin/sdk";
+import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client";
+import {
+ getPlaystateApi,
+ getUserLibraryApi,
+} from "@jellyfin/sdk/lib/utils/api";
+import { useQuery } from "@tanstack/react-query";
+import * as Haptics from "expo-haptics";
+import { useFocusEffect, useLocalSearchParams } from "expo-router";
+import { useAtomValue } from "jotai";
+import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import { Pressable, useWindowDimensions, View } from "react-native";
+import { useSharedValue } from "react-native-reanimated";
+import Video, {
+ OnProgressData,
+ SelectedTrack,
+ SelectedTrackType,
+ VideoRef,
+} from "react-native-video";
+import { Controls } from "@/components/video-player/controls/Controls";
+import transcoding from "@/utils/profiles/transcoding";
+
+const Player = () => {
+ const api = useAtomValue(apiAtom);
+ const user = useAtomValue(userAtom);
+ const [settings] = useSettings();
+ const videoRef = useRef(null);
+
+ const firstTime = useRef(true);
+ const dimensions = useWindowDimensions();
+
+ const [isPlaybackStopped, setIsPlaybackStopped] = useState(false);
+ const [showControls, setShowControls] = useState(true);
+ const [ignoreSafeAreas, setIgnoreSafeAreas] = useState(false);
+ const [isPlaying, setIsPlaying] = useState(false);
+ const [isBuffering, setIsBuffering] = useState(true);
+
+ const progress = useSharedValue(0);
+ const isSeeking = useSharedValue(false);
+ const cacheProgress = useSharedValue(0);
+
+ const {
+ itemId,
+ audioIndex: audioIndexStr,
+ subtitleIndex: subtitleIndexStr,
+ mediaSourceId,
+ bitrateValue: bitrateValueStr,
+ } = useLocalSearchParams<{
+ itemId: string;
+ audioIndex: string;
+ subtitleIndex: string;
+ mediaSourceId: string;
+ bitrateValue: string;
+ }>();
+
+ const audioIndex = audioIndexStr ? parseInt(audioIndexStr, 10) : undefined;
+ const subtitleIndex = subtitleIndexStr
+ ? parseInt(subtitleIndexStr, 10)
+ : undefined;
+ const bitrateValue = bitrateValueStr
+ ? parseInt(bitrateValueStr, 10)
+ : undefined;
+
+ const {
+ data: item,
+ isLoading: isLoadingItem,
+ isError: isErrorItem,
+ } = useQuery({
+ queryKey: ["item", itemId],
+ queryFn: async () => {
+ if (!api) {
+ throw new Error("No api");
+ }
+
+ if (!itemId) {
+ console.warn("No itemId");
+ return null;
+ }
+
+ const res = await getUserLibraryApi(api).getItem({
+ itemId,
+ userId: user?.Id,
+ });
+
+ return res.data;
+ },
+ staleTime: 0,
+ });
+
+ const {
+ data: stream,
+ isLoading: isLoadingStreamUrl,
+ isError: isErrorStreamUrl,
+ } = useQuery({
+ queryKey: [
+ "stream-url",
+ itemId,
+ audioIndex,
+ subtitleIndex,
+ bitrateValue,
+ user,
+ mediaSourceId,
+ ],
+ queryFn: async () => {
+ if (!api) {
+ throw new Error("No api");
+ }
+
+ if (!item) {
+ console.warn("No item", itemId, item);
+ return null;
+ }
+
+ const res = await getStreamUrl({
+ api,
+ item,
+ startTimeTicks: item?.UserData?.PlaybackPositionTicks!,
+ userId: user?.Id,
+ audioStreamIndex: audioIndex,
+ maxStreamingBitrate: bitrateValue,
+ mediaSourceId: mediaSourceId,
+ subtitleStreamIndex: subtitleIndex,
+ deviceProfile: transcoding,
+ });
+
+ if (!res) return null;
+
+ const { mediaSource, sessionId, url } = res;
+
+ if (!sessionId || !mediaSource || !url) {
+ console.warn("No sessionId or mediaSource or url", url);
+ return null;
+ }
+
+ return {
+ mediaSource,
+ sessionId,
+ url,
+ };
+ },
+ enabled: !!item,
+ staleTime: 0,
+ });
+
+ const poster = usePoster(item, api);
+ const videoSource = useVideoSource(item, api, poster, stream?.url);
+
+ const togglePlay = useCallback(
+ async (ticks: number) => {
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
+ if (isPlaying) {
+ videoRef.current?.pause();
+ await getPlaystateApi(api!).onPlaybackProgress({
+ itemId: item?.Id!,
+ audioStreamIndex: audioIndex ? audioIndex : undefined,
+ subtitleStreamIndex: subtitleIndex ? subtitleIndex : undefined,
+ mediaSourceId: mediaSourceId,
+ positionTicks: Math.floor(ticks),
+ isPaused: true,
+ playMethod: stream?.url.includes("m3u8")
+ ? "Transcode"
+ : "DirectStream",
+ playSessionId: stream?.sessionId,
+ });
+ } else {
+ videoRef.current?.resume();
+ await getPlaystateApi(api!).onPlaybackProgress({
+ itemId: item?.Id!,
+ audioStreamIndex: audioIndex ? audioIndex : undefined,
+ subtitleStreamIndex: subtitleIndex ? subtitleIndex : undefined,
+ mediaSourceId: mediaSourceId,
+ positionTicks: Math.floor(ticks),
+ isPaused: false,
+ playMethod: stream?.url.includes("m3u8")
+ ? "Transcode"
+ : "DirectStream",
+ playSessionId: stream?.sessionId,
+ });
+ }
+ },
+ [
+ isPlaying,
+ api,
+ item,
+ videoRef,
+ settings,
+ stream,
+ audioIndex,
+ subtitleIndex,
+ mediaSourceId,
+ ]
+ );
+
+ const play = useCallback(() => {
+ videoRef.current?.resume();
+ reportPlaybackStart();
+ }, [videoRef]);
+
+ const pause = useCallback(() => {
+ videoRef.current?.pause();
+ }, [videoRef]);
+
+ const stop = useCallback(() => {
+ setIsPlaybackStopped(true);
+ videoRef.current?.pause();
+ reportPlaybackStopped();
+ }, [videoRef]);
+
+ const seek = useCallback(
+ (seconds: number) => {
+ videoRef.current?.seek(seconds);
+ },
+ [videoRef]
+ );
+
+ const reportPlaybackStopped = async () => {
+ if (!item?.Id) return;
+ await getPlaystateApi(api!).onPlaybackStopped({
+ itemId: item.Id,
+ mediaSourceId: mediaSourceId,
+ positionTicks: Math.floor(progress.value),
+ playSessionId: stream?.sessionId,
+ });
+ };
+
+ const reportPlaybackStart = async () => {
+ if (!item?.Id) return;
+ await getPlaystateApi(api!).onPlaybackStart({
+ itemId: item.Id,
+ audioStreamIndex: audioIndex ? audioIndex : undefined,
+ subtitleStreamIndex: subtitleIndex ? subtitleIndex : undefined,
+ mediaSourceId: mediaSourceId,
+ playMethod: stream?.url.includes("m3u8") ? "Transcode" : "DirectStream",
+ playSessionId: stream?.sessionId,
+ });
+ };
+
+ const onProgress = useCallback(
+ async (data: OnProgressData) => {
+ if (isSeeking.value === true) return;
+ if (isPlaybackStopped === true) return;
+
+ const ticks = secondsToTicks(data.currentTime);
+
+ progress.value = ticks;
+ cacheProgress.value = secondsToTicks(data.playableDuration);
+
+ // TODO: Use this when streaming with HLS url, but NOT when direct playing
+ // TODO: since playable duration is always 0 then.
+ // setIsBuffering(data.playableDuration === 0);
+
+ if (!item?.Id || data.currentTime === 0) {
+ return;
+ }
+
+ await getPlaystateApi(api!).onPlaybackProgress({
+ itemId: item.Id,
+ audioStreamIndex: audioIndex ? audioIndex : undefined,
+ subtitleStreamIndex: subtitleIndex ? subtitleIndex : undefined,
+ mediaSourceId: mediaSourceId,
+ positionTicks: Math.round(ticks),
+ isPaused: !isPlaying,
+ playMethod: stream?.url.includes("m3u8") ? "Transcode" : "DirectStream",
+ playSessionId: stream?.sessionId,
+ });
+ },
+ [
+ item,
+ isPlaying,
+ api,
+ isPlaybackStopped,
+ isSeeking,
+ stream,
+ mediaSourceId,
+ audioIndex,
+ subtitleIndex,
+ ]
+ );
+
+ useFocusEffect(
+ useCallback(() => {
+ play();
+
+ return () => {
+ stop();
+ };
+ }, [play, stop])
+ );
+
+ useOrientation();
+ useOrientationSettings();
+
+ useWebSocket({
+ isPlaying: isPlaying,
+ pauseVideo: pause,
+ playVideo: play,
+ stopPlayback: stop,
+ });
+
+ const [selectedTextTrack, setSelectedTextTrack] = useState<
+ SelectedTrack | undefined
+ >();
+
+ const [embededTextTracks, setEmbededTextTracks] = useState<
+ {
+ index: number;
+ language?: string | undefined;
+ selected?: boolean | undefined;
+ title?: string | undefined;
+ type: any;
+ }[]
+ >([]);
+
+ const [audioTracks, setAudioTracks] = useState([]);
+ const [selectedAudioTrack, setSelectedAudioTrack] = useState<
+ SelectedTrack | undefined
+ >(undefined);
+
+
+ // Set intial Subtitle Track.
+ // We will only select external tracks if they are are text based. Else it should be burned in already.
+ const textSubs =
+ stream?.mediaSource.MediaStreams?.filter((sub) => sub.Type === "Subtitle" && sub.IsTextSubtitleStream) || [];
+ const chosenSubtitleTrack = textSubs.find(
+ (sub) => sub.Index === subtitleIndex
+ );
+ useEffect(() => {
+ if (chosenSubtitleTrack && selectedTextTrack === undefined) {
+ setSelectedTextTrack({
+ type: SelectedTrackType.INDEX,
+ value: textSubs.indexOf(chosenSubtitleTrack),
+ });
+ }
+ }, [embededTextTracks]);
+
+ const getAudioTracks = (): TrackInfo[] => {
+ return audioTracks.map((t) => ({
+ name: t.name,
+ index: t.index,
+ }));
+ };
+
+ const getSubtitleTracks = (): TrackInfo[] => {
+ return embededTextTracks.map((t) => ({
+ name: t.title ?? "",
+ index: t.index,
+ language: t.language,
+ }));
+ };
+
+ if (isLoadingItem || isLoadingStreamUrl)
+ return (
+
+
+
+ );
+
+ if (isErrorItem || isErrorStreamUrl)
+ return (
+
+ Error
+
+ );
+
+ return (
+
+ {
+ setShowControls(!showControls);
+ }}
+ style={{
+ flex: 1,
+ height: "100%",
+ width: "100%",
+ position: "absolute",
+ top: 0,
+ left: 0,
+ zIndex: 0,
+ }}
+ >
+ {videoSource ? (
+
+
+ {item && (
+
+ setSelectedTextTrack({
+ type: SelectedTrackType.INDEX,
+ value: i,
+ })
+ }
+ getAudioTracks={getAudioTracks}
+ setAudioTrack={(i) => {
+ console.log("setAudioTrack ~", i);
+ setSelectedAudioTrack({
+ type: SelectedTrackType.INDEX,
+ value: i,
+ });
+ }}
+ />
+ )}
+
+ );
+};
+
+export function usePoster(
+ item: BaseItemDto | null | undefined,
+ api: Api | null
+): string | undefined {
+ const poster = useMemo(() => {
+ if (!item || !api) return undefined;
+ return item.Type === "Audio"
+ ? `${api.basePath}/Items/${item.AlbumId}/Images/Primary?tag=${item.AlbumPrimaryImageTag}&quality=90&maxHeight=200&maxWidth=200`
+ : getBackdropUrl({
+ api,
+ item: item,
+ quality: 70,
+ width: 200,
+ });
+ }, [item, api]);
+
+ return poster ?? undefined;
+}
+
+export function useVideoSource(
+ item: BaseItemDto | null | undefined,
+ api: Api | null,
+ poster: string | undefined,
+ url?: string | null
+) {
+ const videoSource = useMemo(() => {
+ if (!item || !api || !url) {
+ return null;
+ }
+
+ const startPosition = item?.UserData?.PlaybackPositionTicks
+ ? Math.round(item.UserData.PlaybackPositionTicks / 10000)
+ : 0;
+
+ return {
+ uri: url,
+ isNetwork: true,
+ startPosition,
+ headers: getAuthHeaders(api),
+ metadata: {
+ artist: item?.AlbumArtist ?? undefined,
+ title: item?.Name || "Unknown",
+ description: item?.Overview ?? undefined,
+ imageUri: poster,
+ subtitle: item?.Album ?? undefined,
+ },
+ };
+ }, [item, api, poster, url]);
+
+ return videoSource;
+}
+
+export default Player;
diff --git a/components/PlayButton.tsx b/components/PlayButton.tsx
index 2633afde..d41e59fc 100644
--- a/components/PlayButton.tsx
+++ b/components/PlayButton.tsx
@@ -65,9 +65,18 @@ export const PlayButton: React.FC = ({
const colorChangeProgress = useSharedValue(0);
const [settings] = useSettings();
+
+
+ // console.log(bitrateValue);
const goToPlayer = useCallback(
- (q: string) => {
- router.push(`/player/player?${q}`);
+ (q: string, bitrateValue: number | undefined) => {
+ if (!bitrateValue) {
+ // @ts-expect-error
+ router.push(`/player/direct-player?${q}`);
+ return;
+ }
+ // @ts-expect-error
+ router.push(`/player/transcoding-player?${q}`);
},
[router]
);
@@ -86,7 +95,7 @@ export const PlayButton: React.FC = ({
const queryString = queryParams.toString();
if (!client) {
- goToPlayer(queryString);
+ goToPlayer(queryString, selectedOptions.bitrate?.value);
return;
}
@@ -207,7 +216,7 @@ export const PlayButton: React.FC = ({
});
break;
case 1:
- goToPlayer(queryString);
+ goToPlayer(queryString, selectedOptions.bitrate?.value);
break;
case cancelButtonIndex:
break;
diff --git a/components/video-player/controls/Controls.tsx b/components/video-player/controls/Controls.tsx
index f6e47c3d..c0388c51 100644
--- a/components/video-player/controls/Controls.tsx
+++ b/components/video-player/controls/Controls.tsx
@@ -145,8 +145,13 @@ export const Controls: React.FC = ({
bitrateValue: bitrate.toString(),
}).toString();
+ if (!bitrate.value) {
+ // @ts-expect-error
+ router.replace(`player/direct-player?${queryParams}`);
+ return;
+ }
// @ts-expect-error
- router.replace(`player/player?${queryParams}`);
+ router.replace(`player/transcoding-player?${queryParams}`);
}, [previousItem, settings]);
const goToNextItem = useCallback(() => {
@@ -163,8 +168,13 @@ export const Controls: React.FC = ({
bitrateValue: bitrate.toString(),
}).toString();
+ if (!bitrate.value) {
+ // @ts-expect-error
+ router.replace(`player/direct-player?${queryParams}`);
+ return;
+ }
// @ts-expect-error
- router.replace(`player/player?${queryParams}`);
+ router.replace(`player/transcoding-player?${queryParams}`);
}, [nextItem, settings]);
const updateTimes = useCallback(
diff --git a/components/video-player/controls/DropdownView.tsx b/components/video-player/controls/DropdownView.tsx
index 44303e91..ae8b4e74 100644
--- a/components/video-player/controls/DropdownView.tsx
+++ b/components/video-player/controls/DropdownView.tsx
@@ -61,16 +61,9 @@ const DropdownView: React.FC = ({ showControls }) => {
)[];
}, [item, isVideoLoaded, subtitleTracks, mediaSource?.MediaStreams]);
- // const audioForTranscodingStream = mediaSource?.MediaStreams?.filter(
- // (x) => x.Type === "Audio"
- // ).map((x) => ({
- // name: x.DisplayTitle!,
- // index: x.Index!,
- // }));
-
// Only used for transcoding streams.
const {
- subtitleIndex: subtitleIndexStr,
+ subtitleIndex,
audioIndex,
bitrateValue,
} = useLocalSearchParams<{
@@ -84,11 +77,12 @@ const DropdownView: React.FC = ({ showControls }) => {
// Either its on a text subtitle or its on not on any subtitle therefore it should show all the embedded HLS subtitles.
const isOnTextSubtitle =
mediaSource?.MediaStreams?.find(
- (x) => x.Index === parseInt(subtitleIndexStr) && x.IsTextSubtitleStream
- ) || subtitleIndexStr === "-1";
+ (x) => x.Index === parseInt(subtitleIndex) && x.IsTextSubtitleStream
+ ) || subtitleIndex === "-1";
- // TODO: Add support for text sorting subtitles renaming.
+ // Need to sort in the right order when on text mode. So its seamless.
const allSubtitleTracksForTranscodingStream = useMemo(() => {
+ const disableSubtitle = { name: 'Disable', index: -1, IsTextSubtitleStream: true } as TranscodedSubtitle;
const allSubs =
mediaSource?.MediaStreams?.filter((x) => x.Type === "Subtitle") ?? [];
if (isOnTextSubtitle) {
@@ -109,7 +103,11 @@ const DropdownView: React.FC = ({ showControls }) => {
IsTextSubtitleStream: x.IsTextSubtitleStream,
} as TranscodedSubtitle)
);
- return [...textSubtitles, ...imageSubtitles];
+ return [
+ disableSubtitle,
+ ...textSubtitles,
+ ...imageSubtitles
+ ];
}
const transcodedSubtitle: TranscodedSubtitle[] = allSubs.map((x) => ({
@@ -119,11 +117,12 @@ const DropdownView: React.FC = ({ showControls }) => {
}));
return [
- { name: 'Disable', index: -1, IsTextSubtitleStream: true } as TranscodedSubtitle,
+ disableSubtitle,
...transcodedSubtitle
];
}, [item, isVideoLoaded, subtitleTracks, mediaSource?.MediaStreams]);
+
const ChangeTranscodingSubtitle = useCallback(
(subtitleIndex: number) => {
const queryParams = new URLSearchParams({
@@ -135,7 +134,29 @@ const DropdownView: React.FC = ({ showControls }) => {
}).toString();
// @ts-expect-error
- router.replace(`player/player?${queryParams}`);
+ router.replace(`player/transcoding?${queryParams}`);
+ },
+ [mediaSource]
+ );
+
+ // Audio tracks for transcoding streams.
+ const allAudio =
+ mediaSource?.MediaStreams?.filter((x) => x.Type === "Audio").map((x) => ({
+ name: x.DisplayTitle!,
+ index: x.Index!,
+ })) || [];
+ const ChangeTranscodingAudio = useCallback(
+ (audioIndex: number) => {
+ const queryParams = new URLSearchParams({
+ itemId: item.Id ?? "", // Ensure itemId is a string
+ audioIndex: audioIndex?.toString() ?? "",
+ subtitleIndex: subtitleIndex,
+ mediaSourceId: mediaSource?.Id ?? "", // Ensure mediaSourceId is a string
+ bitrateValue: bitrateValue,
+ }).toString();
+
+ // @ts-expect-error
+ router.replace(`player/transcoding?${queryParams}`);
},
[mediaSource]
);
@@ -211,7 +232,7 @@ const DropdownView: React.FC = ({ showControls }) => {
{
- if (subtitleIndexStr === sub.index.toString()) return;
+ if (subtitleIndex === sub.index.toString()) return;
if (sub.IsTextSubtitleStream && isOnTextSubtitle) {
setSubtitleTrack && setSubtitleTrack(sub.index);
@@ -242,7 +263,7 @@ const DropdownView: React.FC = ({ showControls }) => {
loop={true}
sideOffset={10}
>
- {audioTracks?.map((track, idx: number) => (
+ {!mediaSource?.TranscodingUrl && audioTracks?.map((track, idx: number) => (
{
@@ -255,6 +276,21 @@ const DropdownView: React.FC = ({ showControls }) => {
))}
+
+ {mediaSource?.TranscodingUrl && allAudio?.map((track, idx: number) => (
+ {
+ if (audioIndex === track.index.toString()) return;
+ ChangeTranscodingAudio(track.index);
+ }}
+ >
+
+
+ {track.name}
+
+
+ ))}
diff --git a/modules/vlc-player/android/src/main/java/expo/modules/vlcplayer/VlcPlayerView.kt b/modules/vlc-player/android/src/main/java/expo/modules/vlcplayer/VlcPlayerView.kt
index 5c3c8079..03245207 100644
--- a/modules/vlc-player/android/src/main/java/expo/modules/vlcplayer/VlcPlayerView.kt
+++ b/modules/vlc-player/android/src/main/java/expo/modules/vlcplayer/VlcPlayerView.kt
@@ -30,7 +30,6 @@ class VlcPlayerView(context: Context, appContext: AppContext) : ExpoView(context
private val onVideoLoadEnd by EventDispatcher()
private var startPosition: Int? = 0
- private var isTranscodedStream: Boolean = false
private var isMediaReady: Boolean = false
private var externalTrack: Map? = null
@@ -60,9 +59,6 @@ class VlcPlayerView(context: Context, appContext: AppContext) : ExpoView(context
val uri = source["uri"] as? String
- if (uri != null && uri.contains("m3u8")) {
- isTranscodedStream = true
- }
// Handle video load start event
// onVideoLoadStart?.invoke(mapOf("target" to reactTag ?: "null"))
@@ -239,16 +235,6 @@ class VlcPlayerView(context: Context, appContext: AppContext) : ExpoView(context
}
}
- // Only used for HLS transcoded streams
- private fun setSubtitleTrackByName(trackName: String) {
- val track = mediaPlayer?.getSpuTracks()?.firstOrNull { it.name.startsWith(trackName) }
- val trackIndex = track?.id ?: -1
- println("Track Index setting to: $trackIndex")
- if (trackIndex != -1) {
- setSubtitleTrack(trackIndex)
- }
- }
-
private fun updateVideoProgress() {
val player = mediaPlayer ?: return
@@ -257,25 +243,16 @@ class VlcPlayerView(context: Context, appContext: AppContext) : ExpoView(context
val durationMs = player.media?.duration?.toInt() ?: 0
if (currentTimeMs >= 0 && currentTimeMs < durationMs) {
- // Handle when VLC starts at cloest earliest segment skip to the start time, for transcoded streams.
+ // Set subtitle URL if available
if (player.isPlaying && !isMediaReady) {
isMediaReady = true
externalTrack?.let {
val name = it["name"]
val deliveryUrl = it["DeliveryUrl"] ?: ""
- if (!name.isNullOrEmpty()) {
- if (!isTranscodedStream) {
- setSubtitleURL(deliveryUrl, name)
- }
- else {
- setSubtitleTrackByName(name)
- }
+ if (!name.isNullOrEmpty() && !deliveryUrl.isNullOrEmpty()) {
+ setSubtitleURL(deliveryUrl, name)
}
}
-
- if (isTranscodedStream && startPosition != 0) {
- seekTo((startPosition ?: 0) * 1000)
- }
}
onVideoProgress(mapOf(
"currentTime" to currentTimeMs,