forked from Ninjalama/streamyfin_mirror
Compare commits
1 Commits
fix/loadin
...
feat/on-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ceb9969007 |
@@ -7,7 +7,7 @@ import { Ionicons } from "@expo/vector-icons";
|
|||||||
import { BlurView } from "expo-blur";
|
import { BlurView } from "expo-blur";
|
||||||
import { useRouter, useSegments } from "expo-router";
|
import { useRouter, useSegments } from "expo-router";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { Alert, Platform, TouchableOpacity, View } from "react-native";
|
import { Alert, Platform, TouchableOpacity, View } from "react-native";
|
||||||
import Animated, {
|
import Animated, {
|
||||||
useAnimatedStyle,
|
useAnimatedStyle,
|
||||||
@@ -17,6 +17,14 @@ import Animated, {
|
|||||||
import Video from "react-native-video";
|
import Video from "react-native-video";
|
||||||
import { Text } from "./common/Text";
|
import { Text } from "./common/Text";
|
||||||
import { Loader } from "./Loader";
|
import { Loader } from "./Loader";
|
||||||
|
import * as FileSystem from "expo-file-system";
|
||||||
|
import {
|
||||||
|
FFmpegKit,
|
||||||
|
FFmpegKitConfig,
|
||||||
|
FFmpegSession,
|
||||||
|
ReturnCode,
|
||||||
|
} from "ffmpeg-kit-react-native";
|
||||||
|
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||||
|
|
||||||
export const CurrentlyPlayingBar: React.FC = () => {
|
export const CurrentlyPlayingBar: React.FC = () => {
|
||||||
const segments = useSegments();
|
const segments = useSegments();
|
||||||
@@ -63,6 +71,106 @@ export const CurrentlyPlayingBar: React.FC = () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const [streamUrl, setStreamUrl] = useState<string | null>(null);
|
||||||
|
const [ffmpegSession, setFfmpegSession] = useState<FFmpegSession | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
const startStreamingTranscode = async (inputUrl: string) => {
|
||||||
|
const outputDir = `${FileSystem.cacheDirectory}stream_${Date.now()}`;
|
||||||
|
const manifestPath = `${outputDir}/stream.m3u8`;
|
||||||
|
|
||||||
|
// Ensure the output directory exists
|
||||||
|
await FileSystem.makeDirectoryAsync(outputDir, { intermediates: true });
|
||||||
|
|
||||||
|
// Base FFmpeg command
|
||||||
|
let ffmpegCommand = `-i "${inputUrl}" `;
|
||||||
|
|
||||||
|
// Add hardware acceleration based on platform
|
||||||
|
if (Platform.OS === "android") {
|
||||||
|
ffmpegCommand += "-c:v h264_mediacodec "; // Hardware acceleration for Android
|
||||||
|
} else if (Platform.OS === "ios") {
|
||||||
|
ffmpegCommand += "-c:v h264_videotoolbox "; // Hardware acceleration for iOS
|
||||||
|
} else {
|
||||||
|
ffmpegCommand += "-c:v libx264 "; // Fallback to software encoding
|
||||||
|
}
|
||||||
|
|
||||||
|
// Complete the command
|
||||||
|
ffmpegCommand += `-c:a aac -f hls -hls_time 4 -hls_list_size 5 -hls_flags delete_segments "${manifestPath}"`;
|
||||||
|
|
||||||
|
console.log("FFmpeg command:", ffmpegCommand);
|
||||||
|
|
||||||
|
// Start FFmpeg process and return the session
|
||||||
|
return FFmpegKit.executeAsync(ffmpegCommand);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const prepareStream = async () => {
|
||||||
|
if (currentlyPlaying?.url) {
|
||||||
|
try {
|
||||||
|
// Check if we already have a stream for this URL
|
||||||
|
const existingStream = await AsyncStorage.getItem(
|
||||||
|
currentlyPlaying.url
|
||||||
|
);
|
||||||
|
if (existingStream) {
|
||||||
|
setStreamUrl(existingStream);
|
||||||
|
} else {
|
||||||
|
const session = await startStreamingTranscode(currentlyPlaying.url);
|
||||||
|
setFfmpegSession(session);
|
||||||
|
|
||||||
|
const returnCode = await session.getReturnCode();
|
||||||
|
|
||||||
|
if (ReturnCode.isSuccess(returnCode)) {
|
||||||
|
console.log("Transcoding completed successfully");
|
||||||
|
const outputDir = `${
|
||||||
|
FileSystem.cacheDirectory
|
||||||
|
}stream_${Date.now()}`;
|
||||||
|
const manifestPath = `${outputDir}/stream.m3u8`;
|
||||||
|
setStreamUrl(manifestPath);
|
||||||
|
// Store the stream URL
|
||||||
|
await AsyncStorage.setItem(currentlyPlaying.url, manifestPath);
|
||||||
|
} else {
|
||||||
|
console.error("Transcoding failed");
|
||||||
|
// Handle failure (e.g., retry or show error message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error preparing stream:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
prepareStream();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
// Cleanup: cancel FFmpeg session when component unmounts
|
||||||
|
if (ffmpegSession) {
|
||||||
|
ffmpegSession.cancel();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [currentlyPlaying?.url]);
|
||||||
|
|
||||||
|
// Cleanup function
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
const cleanup = async () => {
|
||||||
|
if (streamUrl) {
|
||||||
|
try {
|
||||||
|
// Remove the stream URL from AsyncStorage
|
||||||
|
await AsyncStorage.removeItem(currentlyPlaying?.url || "");
|
||||||
|
// Delete the stream files
|
||||||
|
await FileSystem.deleteAsync(streamUrl.replace("file://", ""), {
|
||||||
|
idempotent: true,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error cleaning up stream:", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
cleanup();
|
||||||
|
};
|
||||||
|
}, [streamUrl, currentlyPlaying?.url]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (segments.find((s) => s.includes("tabs"))) {
|
if (segments.find((s) => s.includes("tabs"))) {
|
||||||
// Tab screen - i.e. home
|
// Tab screen - i.e. home
|
||||||
@@ -136,7 +244,7 @@ export const CurrentlyPlayingBar: React.FC = () => {
|
|||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
{currentlyPlaying?.url && (
|
{streamUrl && (
|
||||||
<Video
|
<Video
|
||||||
ref={videoRef}
|
ref={videoRef}
|
||||||
allowsExternalPlayback
|
allowsExternalPlayback
|
||||||
@@ -162,7 +270,7 @@ export const CurrentlyPlayingBar: React.FC = () => {
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
}}
|
}}
|
||||||
source={{
|
source={{
|
||||||
uri: currentlyPlaying.url,
|
uri: streamUrl,
|
||||||
isNetwork: true,
|
isNetwork: true,
|
||||||
startPosition,
|
startPosition,
|
||||||
headers: getAuthHeaders(api),
|
headers: getAuthHeaders(api),
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
|||||||
import { runningProcesses } from "@/utils/atoms/downloads";
|
import { runningProcesses } from "@/utils/atoms/downloads";
|
||||||
import { writeToLog } from "@/utils/log";
|
import { writeToLog } from "@/utils/log";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { Platform } from "react-native";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Custom hook for remuxing HLS to MP4 using FFmpeg.
|
* Custom hook for remuxing HLS to MP4 using FFmpeg.
|
||||||
@@ -30,6 +31,16 @@ export const useRemuxHlsToMp4 = (item: BaseItemDto) => {
|
|||||||
async (url: string) => {
|
async (url: string) => {
|
||||||
const command = `-y -loglevel quiet -thread_queue_size 512 -protocol_whitelist file,http,https,tcp,tls,crypto -multiple_requests 1 -tcp_nodelay 1 -fflags +genpts -i ${url} -c copy -bufsize 50M -max_muxing_queue_size 4096 ${output}`;
|
const command = `-y -loglevel quiet -thread_queue_size 512 -protocol_whitelist file,http,https,tcp,tls,crypto -multiple_requests 1 -tcp_nodelay 1 -fflags +genpts -i ${url} -c copy -bufsize 50M -max_muxing_queue_size 4096 ${output}`;
|
||||||
|
|
||||||
|
// let command: string | null = null;
|
||||||
|
|
||||||
|
// if (Platform.OS === "android") {
|
||||||
|
// command = `-y -loglevel quiet -thread_queue_size 512 -protocol_whitelist file,http,https,tcp,tls,crypto -multiple_requests 1 -tcp_nodelay 1 -fflags +genpts -i ${url} -c:v h264_mediacodec -c:a copy -bufsize 50M -max_muxing_queue_size 4096 ${output}`;
|
||||||
|
// } else if (Platform.OS === "ios") {
|
||||||
|
// command = `-y -loglevel quiet -thread_queue_size 512 -protocol_whitelist file,http,https,tcp,tls,crypto -multiple_requests 1 -tcp_nodelay 1 -fflags +genpts -i ${url} -c:v h264_videotoolbox -c:a copy -bufsize 50M -max_muxing_queue_size 4096 ${output}`;
|
||||||
|
// } else {
|
||||||
|
// throw new Error("Unsupported platform");
|
||||||
|
// }
|
||||||
|
|
||||||
writeToLog(
|
writeToLog(
|
||||||
"INFO",
|
"INFO",
|
||||||
`useRemuxHlsToMp4 ~ startRemuxing for item ${item.Name}`
|
`useRemuxHlsToMp4 ~ startRemuxing for item ${item.Name}`
|
||||||
|
|||||||
Reference in New Issue
Block a user