wip: subtitles and onVideoLoad stuff

This commit is contained in:
Fredrik Burmester
2024-10-14 11:14:34 +02:00
parent 092f5e73d7
commit 67be97d857
8 changed files with 144 additions and 58 deletions

View File

@@ -303,6 +303,12 @@ export default function page() {
onVideoProgress={onProgress}
progressUpdateInterval={1000}
onVideoStateChange={onPlaybackStateChanged}
onVideoLoadStart={() => {
console.log("onVideoLoadStart");
}}
onVideoLoadEnd={() => {
console.log("onVideoLoadEnd");
}}
/>
</Pressable>

View File

@@ -2,6 +2,10 @@ import { useAdjacentItems } from "@/hooks/useAdjacentEpisodes";
import { useCreditSkipper } from "@/hooks/useCreditSkipper";
import { useIntroSkipper } from "@/hooks/useIntroSkipper";
import { useTrickplay } from "@/hooks/useTrickplay";
import {
TrackInfo,
VlcPlayerViewRef,
} from "@/modules/vlc-player/src/VlcPlayer.types";
import { usePlaySettings } from "@/providers/PlaySettingsProvider";
import { useSettings } from "@/utils/atoms/settings";
import { getDefaultPlaySettings } from "@/utils/jellyfin/getDefaultPlaySettings";
@@ -29,22 +33,16 @@ import {
View,
} from "react-native";
import { Slider } from "react-native-awesome-slider";
import Animated, {
import {
runOnJS,
SharedValue,
useAnimatedReaction,
useAnimatedStyle,
useSharedValue,
withTiming,
} from "react-native-reanimated";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import { VideoRef } from "react-native-video";
import * as DropdownMenu from "zeego/dropdown-menu";
import { Text } from "../common/Text";
import { Loader } from "../Loader";
import { VlcPlayerViewRef } from "@/modules/vlc-player/src/VlcPlayer.types";
import { secondsToTicks } from "@/utils/secondsToTicks";
import { VideoDebugInfo } from "../vlc/VideoDebugInfo";
import * as DropdownMenu from "zeego/dropdown-menu";
interface Props {
item: BaseItemDto;
@@ -249,10 +247,34 @@ export const Controls: React.FC<Props> = ({
MediaStream | undefined
>(undefined);
const subtitleTracks = useMemo(() => {
return item.MediaStreams?.filter((stream) => stream.Type === "Subtitle");
const allSubtitleTracks = useMemo(() => {
const subs = item.MediaStreams?.filter(
(stream) => stream.Type === "Subtitle"
);
console.log("allSubtitleTracks", subs);
return subs;
}, [item]);
const [audioTracks, setAudioTracks] = useState<TrackInfo[] | null>(null);
const [subtitleTracks, setSubtitleTracks] = useState<TrackInfo[] | null>(
null
);
useEffect(() => {
const fetchTracks = async () => {
if (videoRef.current) {
const audio = await videoRef.current.getAudioTracks();
const subtitles = await videoRef.current.getSubtitleTracks();
setAudioTracks(audio);
setSubtitleTracks(subtitles);
console.log("embedded audio", audio);
console.log("embedded sutitles", subtitles);
}
};
fetchTracks();
}, [videoRef]);
return (
<View
style={[
@@ -278,12 +300,8 @@ export const Controls: React.FC<Props> = ({
>
<DropdownMenu.Root>
<DropdownMenu.Trigger>
<View className="aspect-square flex flex-col rounded-xl items-center justify-center p-2">
<Ionicons
name="ellipsis-horizontal-circle-outline"
size={32}
color={"white"}
/>
<View 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"} />
</View>
</DropdownMenu.Trigger>
<DropdownMenu.Content
@@ -296,19 +314,52 @@ export const Controls: React.FC<Props> = ({
sideOffset={8}
>
<DropdownMenu.Label>Subtitle tracks</DropdownMenu.Label>
{subtitleTracks?.map((sub, idx: number) => (
<DropdownMenu.Item
key={idx.toString()}
onSelect={() => {
if (!sub.Index !== undefined && sub.Index !== null)
videoRef.current?.setSubtitleTrack(sub.Index!);
}}
<DropdownMenu.Sub>
<DropdownMenu.SubTrigger key="image-style-trigger">
Subtitle
</DropdownMenu.SubTrigger>
<DropdownMenu.SubContent
alignOffset={-10}
avoidCollisions={true}
collisionPadding={0}
loop={true}
sideOffset={10}
>
<DropdownMenu.ItemTitle>
{sub.DisplayTitle}
</DropdownMenu.ItemTitle>
</DropdownMenu.Item>
))}
<DropdownMenu.CheckboxItem
key="subtitle--1"
value="off"
onValueChange={() => {
videoRef.current?.setSubtitleTrack(-1);
}}
>
<DropdownMenu.ItemIndicator />
<DropdownMenu.ItemTitle key={`subtitle-item--1`}>
None
</DropdownMenu.ItemTitle>
</DropdownMenu.CheckboxItem>
{subtitleTracks?.map((sub, idx: number) => (
<DropdownMenu.CheckboxItem
key={`subtitle-${idx}`}
value="off"
onValueChange={() => {
// if(sub. === 'External') {
// videoRef.current?.setSubtitleURL(
// `https://fredflix.se/Providers/Subtitles/Subtitles/`
// );
// }
videoRef.current?.setSubtitleTrack(-1);
console.log(sub);
}}
>
<DropdownMenu.ItemIndicator />
<DropdownMenu.ItemTitle key={`subtitle-item-${idx}`}>
{sub.name}
</DropdownMenu.ItemTitle>
</DropdownMenu.CheckboxItem>
))}
</DropdownMenu.SubContent>
</DropdownMenu.Sub>
</DropdownMenu.Content>
</DropdownMenu.Root>
</View>

View File

@@ -30,8 +30,9 @@ public class VlcPlayerModule: Module {
Events(
"onPlaybackStateChanged",
"onVideoLoadStart",
"onVideoStateChange",
"onVideoLoadStart",
"onVideoLoadEnd",
"onVideoProgress"
)
@@ -74,6 +75,11 @@ public class VlcPlayerModule: Module {
AsyncFunction("getVideoCropGeometry") { (view: VlcPlayerView) -> String? in
return view.getVideoCropGeometry()
}
AsyncFunction("setSubtitleURL") { (view: VlcPlayerView, url: String) in
view.setSubtitleURL(url)
}
}
}
}

View File

@@ -9,6 +9,8 @@ class VlcPlayerView: ExpoView {
private var isPaused: Bool = false
private var currentGeometryCString: [CChar]?
private var lastReportedState: VLCMediaPlayerState?
private var lastReportedIsPlaying: Bool?
private var isMediaReady: Bool = false
// MARK: - Initialization
@@ -233,6 +235,7 @@ class VlcPlayerView: ExpoView {
// }
// }
// }
@objc func setSubtitleTrack(_ trackIndex: Int) {
print("Debug: Attempting to set subtitle track to index: \(trackIndex)")
DispatchQueue.main.async {
@@ -255,21 +258,39 @@ class VlcPlayerView: ExpoView {
}
}
@objc func setSubtitleURL(_ subtitleURL: String) {
DispatchQueue.main.async { [weak self] in
guard let self = self, let url = URL(string: subtitleURL) else {
print("Error: Invalid subtitle URL")
return
}
let result = self.mediaPlayer?.addPlaybackSlave(url, type: .subtitle, enforce: true)
if let result = result {
print("Subtitle added with result: \(result)")
} else {
print("Failed to add subtitle")
}
}
}
@objc func getSubtitleTracks() -> [[String: Any]]? {
guard let mediaPlayer = self.mediaPlayer else {
return nil
}
let count = mediaPlayer.numberOfSubtitlesTracks
print(
"Debug: Number of subtitle tracks: \(count)"
)
guard count > 0 else {
return nil
}
var tracks: [[String: Any]] = []
// Add the "Disabled" track
tracks.append(["name": "Disabled", "index": -1])
if let names = mediaPlayer.videoSubTitlesNames as? [String],
let indexes = mediaPlayer.videoSubTitlesIndexes as? [NSNumber]
{
@@ -493,6 +514,7 @@ class VlcPlayerView: ExpoView {
@objc var onVideoLoadStart: RCTDirectEventBlock?
@objc var onVideoStateChange: RCTDirectEventBlock?
@objc var onVideoProgress: RCTDirectEventBlock?
@objc var onVideoLoadEnd: RCTDirectEventBlock?
// MARK: - Deinitialization
@@ -534,30 +556,19 @@ extension VlcPlayerView: VLCMediaPlayerDelegate {
stateInfo["state"] = "Buffering"
}
// switch currentState {
// case .opening:
// stateInfo["state"] = "Opening"
// case .buffering:
// stateInfo["state"] = "Buffering"
// stateInfo["isBuffering"] = true
// case .playing:
// stateInfo["state"] = "Playing"
// case .paused:
// stateInfo["state"] = "Paused"
// case .stopped:
// stateInfo["state"] = "Stopped"
// case .ended:
// stateInfo["state"] = "Ended"
// case .error:
// stateInfo["state"] = "Error"
// default:
// stateInfo["state"] = "Unknown"
// }
// Dermine if the media has finished loading
if currentState == .buffering && !self.isMediaReady {
self.isMediaReady = true
self.onVideoLoadEnd?(stateInfo)
}
print("State changed: \(stateInfo)")
self.lastReportedState = currentState
self.onVideoStateChange?(stateInfo)
if self.lastReportedState != currentState
|| self.lastReportedIsPlaying != player.isPlaying
{
self.lastReportedState = currentState
self.lastReportedIsPlaying = player.isPlaying
self.onVideoStateChange?(stateInfo)
}
}
}

View File

@@ -67,6 +67,7 @@ export type VlcPlayerViewProps = {
onVideoProgress?: (event: ProgressUpdatePayload) => void;
onVideoStateChange?: (event: PlaybackStatePayload) => void;
onVideoLoadStart?: (event: VideoLoadStartPayload) => void;
onVideoLoadEnd?: (event: VideoLoadStartPayload) => void;
};
export interface VlcPlayerViewRef {
@@ -87,4 +88,5 @@ export interface VlcPlayerViewRef {
getChapters: () => Promise<ChapterInfo[] | null>;
setVideoCropGeometry: (geometry: string | null) => Promise<void>;
getVideoCropGeometry: () => Promise<string | null>;
setSubtitleURL: (url: string) => Promise<void>;
}

View File

@@ -5,8 +5,6 @@ import {
VlcPlayerViewProps,
VlcPlayerViewRef,
VlcPlayerSource,
TrackInfo,
ChapterInfo,
} from "./VlcPlayer.types";
interface NativeViewRef extends VlcPlayerViewRef {
@@ -80,6 +78,9 @@ const VlcPlayerView = React.forwardRef<VlcPlayerViewRef, VlcPlayerViewProps>(
const geometry = await nativeRef.current?.getVideoCropGeometry();
return geometry ?? null;
},
setSubtitleURL: async (url: string) => {
await nativeRef.current?.setSubtitleURL(url);
},
}));
const {
@@ -93,6 +94,7 @@ const VlcPlayerView = React.forwardRef<VlcPlayerViewRef, VlcPlayerViewProps>(
onVideoLoadStart,
onVideoStateChange,
onVideoProgress,
onVideoLoadEnd,
...otherProps
} = props;
@@ -111,6 +113,7 @@ const VlcPlayerView = React.forwardRef<VlcPlayerViewRef, VlcPlayerViewProps>(
volume={volume}
videoAspectRatio={videoAspectRatio}
onVideoLoadStart={onVideoLoadStart}
onVideoLoadEnd={onVideoLoadEnd}
onVideoStateChange={onVideoStateChange}
onVideoProgress={onVideoProgress}
/>

View File

@@ -7,6 +7,7 @@ import {
} from "@jellyfin/sdk/lib/generated-client/models";
import { getMediaInfoApi } from "@jellyfin/sdk/lib/utils/api";
import { getAuthHeaders } from "../jellyfin";
import native from "@/utils/profiles/native";
export const getStreamUrl = async ({
api,
@@ -83,7 +84,7 @@ export const getStreamUrl = async ({
{
method: "POST",
data: {
deviceProfile,
deviceProfile: forceDirectPlay ? native : deviceProfile,
userId,
maxStreamingBitrate,
startTimeTicks,

View File

@@ -82,6 +82,12 @@ export default {
Type: MediaTypes.Video,
VideoCodec: "hevc,h264,mpeg4",
},
{
AudioCodec: "flac,alac,aac,eac3,ac3,opus",
Container: "mkv",
Type: MediaTypes.Video,
VideoCodec: "hevc,h264,mpeg4",
},
{
AudioCodec: "alac,aac,ac3",
Container: "m4v",