forked from Ninjalama/streamyfin_mirror
Compare commits
1 Commits
v0.15.0
...
feat/on-de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ceb9969007 |
@@ -7,7 +7,7 @@ import { Ionicons } from "@expo/vector-icons";
|
||||
import { BlurView } from "expo-blur";
|
||||
import { useRouter, useSegments } from "expo-router";
|
||||
import { useAtom } from "jotai";
|
||||
import { useEffect, useMemo } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { Alert, Platform, TouchableOpacity, View } from "react-native";
|
||||
import Animated, {
|
||||
useAnimatedStyle,
|
||||
@@ -17,6 +17,14 @@ import Animated, {
|
||||
import Video from "react-native-video";
|
||||
import { Text } from "./common/Text";
|
||||
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 = () => {
|
||||
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(() => {
|
||||
if (segments.find((s) => s.includes("tabs"))) {
|
||||
// Tab screen - i.e. home
|
||||
@@ -136,7 +244,7 @@ export const CurrentlyPlayingBar: React.FC = () => {
|
||||
}
|
||||
`}
|
||||
>
|
||||
{currentlyPlaying?.url && (
|
||||
{streamUrl && (
|
||||
<Video
|
||||
ref={videoRef}
|
||||
allowsExternalPlayback
|
||||
@@ -162,7 +270,7 @@ export const CurrentlyPlayingBar: React.FC = () => {
|
||||
fontSize: 16,
|
||||
}}
|
||||
source={{
|
||||
uri: currentlyPlaying.url,
|
||||
uri: streamUrl,
|
||||
isNetwork: true,
|
||||
startPosition,
|
||||
headers: getAuthHeaders(api),
|
||||
|
||||
@@ -7,6 +7,7 @@ import { BaseItemDto } from "@jellyfin/sdk/lib/generated-client/models";
|
||||
import { runningProcesses } from "@/utils/atoms/downloads";
|
||||
import { writeToLog } from "@/utils/log";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { Platform } from "react-native";
|
||||
|
||||
/**
|
||||
* Custom hook for remuxing HLS to MP4 using FFmpeg.
|
||||
@@ -30,6 +31,16 @@ export const useRemuxHlsToMp4 = (item: BaseItemDto) => {
|
||||
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}`;
|
||||
|
||||
// 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(
|
||||
"INFO",
|
||||
`useRemuxHlsToMp4 ~ startRemuxing for item ${item.Name}`
|
||||
|
||||
Reference in New Issue
Block a user