mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-25 07:10:30 +01:00
Fixed some edge cases with dropdown
This commit is contained in:
@@ -47,8 +47,9 @@ import {
|
|||||||
import { VideoRef } from "react-native-video";
|
import { VideoRef } from "react-native-video";
|
||||||
import { ControlProvider } from "./contexts/ControlContext";
|
import { ControlProvider } from "./contexts/ControlContext";
|
||||||
import { VideoProvider } from "./contexts/VideoContext";
|
import { VideoProvider } from "./contexts/VideoContext";
|
||||||
import DropdownView from "./DropdownView";
|
|
||||||
import * as Haptics from "expo-haptics";
|
import * as Haptics from "expo-haptics";
|
||||||
|
import DropdownViewDirect from "./dropdown/DropdownViewDirect";
|
||||||
|
import DropdownViewTranscoding from "./dropdown/DropdownViewTranscoding";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
item: BaseItemDto;
|
item: BaseItemDto;
|
||||||
@@ -340,7 +341,11 @@ export const Controls: React.FC<Props> = ({
|
|||||||
setSubtitleTrack={setSubtitleTrack}
|
setSubtitleTrack={setSubtitleTrack}
|
||||||
setSubtitleURL={setSubtitleURL}
|
setSubtitleURL={setSubtitleURL}
|
||||||
>
|
>
|
||||||
<DropdownView showControls={showControls} />
|
{!mediaSource?.TranscodingUrl ? (
|
||||||
|
<DropdownViewDirect showControls={showControls} />
|
||||||
|
) : (
|
||||||
|
<DropdownViewTranscoding showControls={showControls} />
|
||||||
|
)}
|
||||||
</VideoProvider>
|
</VideoProvider>
|
||||||
|
|
||||||
<View
|
<View
|
||||||
|
|||||||
181
components/video-player/controls/dropdown/DropdownViewDirect.tsx
Normal file
181
components/video-player/controls/dropdown/DropdownViewDirect.tsx
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
import React, { useMemo, useState } from "react";
|
||||||
|
import { View, TouchableOpacity } from "react-native";
|
||||||
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
|
import * as DropdownMenu from "zeego/dropdown-menu";
|
||||||
|
import { useControlContext } from "../contexts/ControlContext";
|
||||||
|
import { useVideoContext } from "../contexts/VideoContext";
|
||||||
|
import { EmbeddedSubtitle, ExternalSubtitle } from "../types";
|
||||||
|
import { useAtomValue } from "jotai";
|
||||||
|
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||||
|
import { useLocalSearchParams } from "expo-router";
|
||||||
|
|
||||||
|
interface DropdownViewDirectProps {
|
||||||
|
showControls: boolean;
|
||||||
|
offline?: boolean; // used to disable external subs for downloads
|
||||||
|
}
|
||||||
|
|
||||||
|
const DropdownViewDirect: React.FC<DropdownViewDirectProps> = ({
|
||||||
|
showControls,
|
||||||
|
offline = false,
|
||||||
|
}) => {
|
||||||
|
const api = useAtomValue(apiAtom);
|
||||||
|
const ControlContext = useControlContext();
|
||||||
|
const mediaSource = ControlContext?.mediaSource;
|
||||||
|
const item = ControlContext?.item;
|
||||||
|
const isVideoLoaded = ControlContext?.isVideoLoaded;
|
||||||
|
|
||||||
|
const videoContext = useVideoContext();
|
||||||
|
const {
|
||||||
|
subtitleTracks,
|
||||||
|
audioTracks,
|
||||||
|
setSubtitleURL,
|
||||||
|
setSubtitleTrack,
|
||||||
|
setAudioTrack,
|
||||||
|
} = videoContext;
|
||||||
|
|
||||||
|
const allSubtitleTracksForDirectPlay = useMemo(() => {
|
||||||
|
if (mediaSource?.TranscodingUrl) return null;
|
||||||
|
const embeddedSubs =
|
||||||
|
subtitleTracks
|
||||||
|
?.map((s) => ({
|
||||||
|
name: s.name,
|
||||||
|
index: s.index,
|
||||||
|
deliveryUrl: undefined,
|
||||||
|
}))
|
||||||
|
.filter((sub) => !sub.name.endsWith("[External]")) || [];
|
||||||
|
|
||||||
|
const externalSubs =
|
||||||
|
mediaSource?.MediaStreams?.filter(
|
||||||
|
(stream) => stream.Type === "Subtitle" && !!stream.DeliveryUrl
|
||||||
|
).map((s) => ({
|
||||||
|
name: s.DisplayTitle! + " [External]",
|
||||||
|
index: s.Index!,
|
||||||
|
deliveryUrl: s.DeliveryUrl,
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
// Combine embedded subs with external subs only if not offline
|
||||||
|
if (!offline) {
|
||||||
|
return [...embeddedSubs, ...externalSubs] as (
|
||||||
|
| EmbeddedSubtitle
|
||||||
|
| ExternalSubtitle
|
||||||
|
)[];
|
||||||
|
}
|
||||||
|
return embeddedSubs as EmbeddedSubtitle[];
|
||||||
|
}, [item, isVideoLoaded, subtitleTracks, mediaSource?.MediaStreams, offline]);
|
||||||
|
|
||||||
|
const { subtitleIndex, audioIndex } = useLocalSearchParams<{
|
||||||
|
itemId: string;
|
||||||
|
audioIndex: string;
|
||||||
|
subtitleIndex: string;
|
||||||
|
mediaSourceId: string;
|
||||||
|
bitrateValue: string;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const [selectedSubtitleIndex, setSelectedSubtitleIndex] = useState<Number>(
|
||||||
|
parseInt(subtitleIndex)
|
||||||
|
);
|
||||||
|
const [selectedAudioIndex, setSelectedAudioIndex] = useState<Number>(
|
||||||
|
parseInt(audioIndex)
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
zIndex: 1000,
|
||||||
|
opacity: showControls ? 1 : 0,
|
||||||
|
}}
|
||||||
|
className="p-4"
|
||||||
|
>
|
||||||
|
<DropdownMenu.Root>
|
||||||
|
<DropdownMenu.Trigger>
|
||||||
|
<TouchableOpacity className="aspect-square flex flex-col bg-neutral-800/90 rounded-xl items-center justify-center p-2">
|
||||||
|
<Ionicons name="ellipsis-horizontal" size={24} color={"white"} />
|
||||||
|
</TouchableOpacity>
|
||||||
|
</DropdownMenu.Trigger>
|
||||||
|
<DropdownMenu.Content
|
||||||
|
loop={true}
|
||||||
|
side="bottom"
|
||||||
|
align="start"
|
||||||
|
alignOffset={0}
|
||||||
|
avoidCollisions={true}
|
||||||
|
collisionPadding={8}
|
||||||
|
sideOffset={8}
|
||||||
|
>
|
||||||
|
<DropdownMenu.Sub>
|
||||||
|
<DropdownMenu.SubTrigger key="subtitle-trigger">
|
||||||
|
Subtitle
|
||||||
|
</DropdownMenu.SubTrigger>
|
||||||
|
<DropdownMenu.SubContent
|
||||||
|
alignOffset={-10}
|
||||||
|
avoidCollisions={true}
|
||||||
|
collisionPadding={0}
|
||||||
|
loop={true}
|
||||||
|
sideOffset={10}
|
||||||
|
>
|
||||||
|
{allSubtitleTracksForDirectPlay?.map((sub, idx: number) => (
|
||||||
|
<DropdownMenu.CheckboxItem
|
||||||
|
key={`subtitle-item-${idx}`}
|
||||||
|
value={selectedSubtitleIndex === sub.index}
|
||||||
|
onValueChange={() => {
|
||||||
|
if ("deliveryUrl" in sub && sub.deliveryUrl) {
|
||||||
|
setSubtitleURL &&
|
||||||
|
setSubtitleURL(
|
||||||
|
api?.basePath + sub.deliveryUrl,
|
||||||
|
sub.name
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
"Set external subtitle: ",
|
||||||
|
api?.basePath + sub.deliveryUrl
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log("Set sub index: ", sub.index);
|
||||||
|
setSubtitleTrack && setSubtitleTrack(sub.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedSubtitleIndex(sub.index);
|
||||||
|
console.log("Subtitle: ", sub);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DropdownMenu.ItemTitle key={`subtitle-item-title-${idx}`}>
|
||||||
|
{sub.name}
|
||||||
|
</DropdownMenu.ItemTitle>
|
||||||
|
</DropdownMenu.CheckboxItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenu.SubContent>
|
||||||
|
</DropdownMenu.Sub>
|
||||||
|
<DropdownMenu.Sub>
|
||||||
|
<DropdownMenu.SubTrigger key="audio-trigger">
|
||||||
|
Audio
|
||||||
|
</DropdownMenu.SubTrigger>
|
||||||
|
<DropdownMenu.SubContent
|
||||||
|
alignOffset={-10}
|
||||||
|
avoidCollisions={true}
|
||||||
|
collisionPadding={0}
|
||||||
|
loop={true}
|
||||||
|
sideOffset={10}
|
||||||
|
>
|
||||||
|
{audioTracks?.map((track, idx: number) => (
|
||||||
|
<DropdownMenu.CheckboxItem
|
||||||
|
key={`audio-item-${idx}`}
|
||||||
|
value={selectedAudioIndex === track.index}
|
||||||
|
onValueChange={() => {
|
||||||
|
setSelectedAudioIndex(track.index);
|
||||||
|
setAudioTrack && setAudioTrack(track.index);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DropdownMenu.ItemTitle key={`audio-item-title-${idx}`}>
|
||||||
|
{track.name}
|
||||||
|
</DropdownMenu.ItemTitle>
|
||||||
|
</DropdownMenu.CheckboxItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenu.SubContent>
|
||||||
|
</DropdownMenu.Sub>
|
||||||
|
</DropdownMenu.Content>
|
||||||
|
</DropdownMenu.Root>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DropdownViewDirect;
|
||||||
@@ -2,13 +2,9 @@ import React, { useCallback, useMemo, useState } from "react";
|
|||||||
import { View, TouchableOpacity } from "react-native";
|
import { View, TouchableOpacity } from "react-native";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import * as DropdownMenu from "zeego/dropdown-menu";
|
import * as DropdownMenu from "zeego/dropdown-menu";
|
||||||
import { useControlContext } from "./contexts/ControlContext";
|
import { useControlContext } from "../contexts/ControlContext";
|
||||||
import { useVideoContext } from "./contexts/VideoContext";
|
import { useVideoContext } from "../contexts/VideoContext";
|
||||||
import {
|
import { TranscodedSubtitle } from "../types";
|
||||||
EmbeddedSubtitle,
|
|
||||||
ExternalSubtitle,
|
|
||||||
TranscodedSubtitle,
|
|
||||||
} from "./types";
|
|
||||||
import { useAtomValue } from "jotai";
|
import { useAtomValue } from "jotai";
|
||||||
import { apiAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom } from "@/providers/JellyfinProvider";
|
||||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||||
@@ -30,43 +26,7 @@ const DropdownView: React.FC<DropdownViewProps> = ({
|
|||||||
const isVideoLoaded = ControlContext?.isVideoLoaded;
|
const isVideoLoaded = ControlContext?.isVideoLoaded;
|
||||||
|
|
||||||
const videoContext = useVideoContext();
|
const videoContext = useVideoContext();
|
||||||
const {
|
const { subtitleTracks, setSubtitleTrack } = videoContext;
|
||||||
subtitleTracks,
|
|
||||||
audioTracks,
|
|
||||||
setSubtitleURL,
|
|
||||||
setSubtitleTrack,
|
|
||||||
setAudioTrack,
|
|
||||||
} = videoContext;
|
|
||||||
|
|
||||||
const allSubtitleTracksForDirectPlay = useMemo(() => {
|
|
||||||
if (mediaSource?.TranscodingUrl) return null;
|
|
||||||
const embeddedSubs =
|
|
||||||
subtitleTracks
|
|
||||||
?.map((s) => ({
|
|
||||||
name: s.name,
|
|
||||||
index: s.index,
|
|
||||||
deliveryUrl: undefined,
|
|
||||||
}))
|
|
||||||
.filter((sub) => !sub.name.endsWith("[External]")) || [];
|
|
||||||
|
|
||||||
const externalSubs =
|
|
||||||
mediaSource?.MediaStreams?.filter(
|
|
||||||
(stream) => stream.Type === "Subtitle" && !!stream.DeliveryUrl
|
|
||||||
).map((s) => ({
|
|
||||||
name: s.DisplayTitle! + " [External]",
|
|
||||||
index: s.Index!,
|
|
||||||
deliveryUrl: s.DeliveryUrl,
|
|
||||||
})) || [];
|
|
||||||
|
|
||||||
// Combine embedded subs with external subs only if not offline
|
|
||||||
if (!offline) {
|
|
||||||
return [...embeddedSubs, ...externalSubs] as (
|
|
||||||
| EmbeddedSubtitle
|
|
||||||
| ExternalSubtitle
|
|
||||||
)[];
|
|
||||||
}
|
|
||||||
return embeddedSubs as EmbeddedSubtitle[];
|
|
||||||
}, [item, isVideoLoaded, subtitleTracks, mediaSource?.MediaStreams, offline]);
|
|
||||||
|
|
||||||
const { subtitleIndex, audioIndex, bitrateValue } = useLocalSearchParams<{
|
const { subtitleIndex, audioIndex, bitrateValue } = useLocalSearchParams<{
|
||||||
itemId: string;
|
itemId: string;
|
||||||
@@ -91,23 +51,19 @@ const DropdownView: React.FC<DropdownViewProps> = ({
|
|||||||
(x) => x.Index === parseInt(subtitleIndex)
|
(x) => x.Index === parseInt(subtitleIndex)
|
||||||
);
|
);
|
||||||
|
|
||||||
const intialSubtitleIndex =
|
let initialSubtitleIndex = -1;
|
||||||
!bitrateValue || !isOnTextSubtitle
|
if (!isOnTextSubtitle) {
|
||||||
? parseInt(subtitleIndex)
|
initialSubtitleIndex = parseInt(subtitleIndex);
|
||||||
: chosenSubtitle && isOnTextSubtitle
|
} else if (chosenSubtitle) {
|
||||||
? textBasedSubs.indexOf(chosenSubtitle)
|
initialSubtitleIndex = textBasedSubs.indexOf(chosenSubtitle);
|
||||||
: -1;
|
}
|
||||||
|
|
||||||
const [selectedSubtitleIndex, setSelectedSubtitleIndex] =
|
const [selectedSubtitleIndex, setSelectedSubtitleIndex] =
|
||||||
useState<Number>(intialSubtitleIndex);
|
useState<number>(initialSubtitleIndex);
|
||||||
const [selectedAudioIndex, setSelectedAudioIndex] = useState<Number>(
|
const [selectedAudioIndex, setSelectedAudioIndex] = useState<number>(
|
||||||
parseInt(audioIndex)
|
parseInt(audioIndex)
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: Need to account for the fact when user is on text-based subtitle at start.
|
|
||||||
// Then the user swaps to another text based subtitle.
|
|
||||||
// Then changes audio track.
|
|
||||||
// The user will have the first text based subtitle selected still but it should be the second text based subtitle.
|
|
||||||
const allSubtitleTracksForTranscodingStream = useMemo(() => {
|
const allSubtitleTracksForTranscodingStream = useMemo(() => {
|
||||||
const disableSubtitle = {
|
const disableSubtitle = {
|
||||||
name: "Disable",
|
name: "Disable",
|
||||||
@@ -190,11 +146,25 @@ const DropdownView: React.FC<DropdownViewProps> = ({
|
|||||||
index: x.Index!,
|
index: x.Index!,
|
||||||
})) || [];
|
})) || [];
|
||||||
const ChangeTranscodingAudio = useCallback(
|
const ChangeTranscodingAudio = useCallback(
|
||||||
(audioIndex: number) => {
|
(audioIndex: number, currentSelectedSubtitleIndex: number) => {
|
||||||
|
let newSubtitleIndex: number;
|
||||||
|
|
||||||
|
if (!isOnTextSubtitle) {
|
||||||
|
newSubtitleIndex = parseInt(subtitleIndex);
|
||||||
|
} else if (
|
||||||
|
currentSelectedSubtitleIndex >= 0 &&
|
||||||
|
currentSelectedSubtitleIndex < textBasedSubs.length
|
||||||
|
) {
|
||||||
|
console.log("setHere SubtitleIndex", currentSelectedSubtitleIndex);
|
||||||
|
newSubtitleIndex = textBasedSubs[currentSelectedSubtitleIndex].Index!;
|
||||||
|
console.log("newSubtitleIndex", newSubtitleIndex);
|
||||||
|
} else {
|
||||||
|
newSubtitleIndex = -1;
|
||||||
|
}
|
||||||
const queryParams = new URLSearchParams({
|
const queryParams = new URLSearchParams({
|
||||||
itemId: item.Id ?? "", // Ensure itemId is a string
|
itemId: item.Id ?? "", // Ensure itemId is a string
|
||||||
audioIndex: audioIndex?.toString() ?? "",
|
audioIndex: audioIndex?.toString() ?? "",
|
||||||
subtitleIndex: subtitleIndex,
|
subtitleIndex: newSubtitleIndex?.toString() ?? "",
|
||||||
mediaSourceId: mediaSource?.Id ?? "", // Ensure mediaSourceId is a string
|
mediaSourceId: mediaSource?.Id ?? "", // Ensure mediaSourceId is a string
|
||||||
bitrateValue: bitrateValue,
|
bitrateValue: bitrateValue,
|
||||||
}).toString();
|
}).toString();
|
||||||
@@ -240,62 +210,29 @@ const DropdownView: React.FC<DropdownViewProps> = ({
|
|||||||
loop={true}
|
loop={true}
|
||||||
sideOffset={10}
|
sideOffset={10}
|
||||||
>
|
>
|
||||||
{!mediaSource?.TranscodingUrl &&
|
{allSubtitleTracksForTranscodingStream?.map(
|
||||||
allSubtitleTracksForDirectPlay?.map((sub, idx: number) => (
|
(sub, idx: number) => (
|
||||||
<DropdownMenu.CheckboxItem
|
<DropdownMenu.CheckboxItem
|
||||||
key={`subtitle-item-${idx}`}
|
|
||||||
value={selectedSubtitleIndex === sub.index}
|
value={selectedSubtitleIndex === sub.index}
|
||||||
|
key={`subtitle-item-${idx}`}
|
||||||
onValueChange={() => {
|
onValueChange={() => {
|
||||||
if ("deliveryUrl" in sub && sub.deliveryUrl) {
|
console.log("sub", sub);
|
||||||
setSubtitleURL &&
|
if (selectedSubtitleIndex === sub?.index) return;
|
||||||
setSubtitleURL(
|
setSelectedSubtitleIndex(sub.index);
|
||||||
api?.basePath + sub.deliveryUrl,
|
if (sub.IsTextSubtitleStream && isOnTextSubtitle) {
|
||||||
sub.name
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
"Set external subtitle: ",
|
|
||||||
api?.basePath + sub.deliveryUrl
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
console.log("Set sub index: ", sub.index);
|
|
||||||
setSubtitleTrack && setSubtitleTrack(sub.index);
|
setSubtitleTrack && setSubtitleTrack(sub.index);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedSubtitleIndex(sub.index);
|
ChangeTranscodingSubtitle(sub.index);
|
||||||
console.log("Subtitle: ", sub);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropdownMenu.ItemTitle key={`subtitle-item-title-${idx}`}>
|
<DropdownMenu.ItemTitle key={`subtitle-item-title-${idx}`}>
|
||||||
{sub.name}
|
{sub.name}
|
||||||
</DropdownMenu.ItemTitle>
|
</DropdownMenu.ItemTitle>
|
||||||
</DropdownMenu.CheckboxItem>
|
</DropdownMenu.CheckboxItem>
|
||||||
))}
|
)
|
||||||
{mediaSource?.TranscodingUrl &&
|
)}
|
||||||
allSubtitleTracksForTranscodingStream?.map(
|
|
||||||
(sub, idx: number) => (
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
value={selectedSubtitleIndex === sub.index}
|
|
||||||
key={`subtitle-item-${idx}`}
|
|
||||||
onValueChange={() => {
|
|
||||||
if (selectedSubtitleIndex === sub?.index) return;
|
|
||||||
setSelectedSubtitleIndex(sub.index);
|
|
||||||
if (sub.IsTextSubtitleStream && isOnTextSubtitle) {
|
|
||||||
setSubtitleTrack && setSubtitleTrack(sub.index);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ChangeTranscodingSubtitle(sub.index);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DropdownMenu.ItemTitle
|
|
||||||
key={`subtitle-item-title-${idx}`}
|
|
||||||
>
|
|
||||||
{sub.name}
|
|
||||||
</DropdownMenu.ItemTitle>
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</DropdownMenu.SubContent>
|
</DropdownMenu.SubContent>
|
||||||
</DropdownMenu.Sub>
|
</DropdownMenu.Sub>
|
||||||
<DropdownMenu.Sub>
|
<DropdownMenu.Sub>
|
||||||
@@ -309,37 +246,21 @@ const DropdownView: React.FC<DropdownViewProps> = ({
|
|||||||
loop={true}
|
loop={true}
|
||||||
sideOffset={10}
|
sideOffset={10}
|
||||||
>
|
>
|
||||||
{!mediaSource?.TranscodingUrl &&
|
{allAudio?.map((track, idx: number) => (
|
||||||
audioTracks?.map((track, idx: number) => (
|
<DropdownMenu.CheckboxItem
|
||||||
<DropdownMenu.CheckboxItem
|
key={`audio-item-${idx}`}
|
||||||
key={`audio-item-${idx}`}
|
value={selectedAudioIndex === track.index}
|
||||||
value={selectedAudioIndex === track.index}
|
onValueChange={() => {
|
||||||
onValueChange={() => {
|
if (selectedAudioIndex === track.index) return;
|
||||||
setSelectedAudioIndex(track.index);
|
setSelectedAudioIndex(track.index);
|
||||||
setAudioTrack && setAudioTrack(track.index);
|
ChangeTranscodingAudio(track.index, selectedSubtitleIndex);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropdownMenu.ItemTitle key={`audio-item-title-${idx}`}>
|
<DropdownMenu.ItemTitle key={`audio-item-title-${idx}`}>
|
||||||
{track.name}
|
{track.name}
|
||||||
</DropdownMenu.ItemTitle>
|
</DropdownMenu.ItemTitle>
|
||||||
</DropdownMenu.CheckboxItem>
|
</DropdownMenu.CheckboxItem>
|
||||||
))}
|
))}
|
||||||
{mediaSource?.TranscodingUrl &&
|
|
||||||
allAudio?.map((track, idx: number) => (
|
|
||||||
<DropdownMenu.CheckboxItem
|
|
||||||
key={`audio-item-${idx}`}
|
|
||||||
value={selectedAudioIndex === track.index}
|
|
||||||
onValueChange={() => {
|
|
||||||
if (selectedAudioIndex === track.index) return;
|
|
||||||
setSelectedAudioIndex(track.index);
|
|
||||||
ChangeTranscodingAudio(track.index);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DropdownMenu.ItemTitle key={`audio-item-title-${idx}`}>
|
|
||||||
{track.name}
|
|
||||||
</DropdownMenu.ItemTitle>
|
|
||||||
</DropdownMenu.CheckboxItem>
|
|
||||||
))}
|
|
||||||
</DropdownMenu.SubContent>
|
</DropdownMenu.SubContent>
|
||||||
</DropdownMenu.Sub>
|
</DropdownMenu.Sub>
|
||||||
</DropdownMenu.Content>
|
</DropdownMenu.Content>
|
||||||
Reference in New Issue
Block a user