mirror of
https://github.com/streamyfin/streamyfin.git
synced 2025-08-20 18:37:18 +02:00
wip: subtitles and onVideoLoad stuff
This commit is contained in:
@@ -303,6 +303,12 @@ export default function page() {
|
||||
onVideoProgress={onProgress}
|
||||
progressUpdateInterval={1000}
|
||||
onVideoStateChange={onPlaybackStateChanged}
|
||||
onVideoLoadStart={() => {
|
||||
console.log("onVideoLoadStart");
|
||||
}}
|
||||
onVideoLoadEnd={() => {
|
||||
console.log("onVideoLoadEnd");
|
||||
}}
|
||||
/>
|
||||
</Pressable>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user