mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-06-02 03:58:36 +01:00
wip
This commit is contained in:
@@ -29,11 +29,10 @@ const formatETA = (seconds: number): string => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getETA = (download: DownloadInfo): string | null => {
|
const getETA = (download: DownloadInfo): string | null => {
|
||||||
console.log("getETA", download);
|
|
||||||
if (
|
if (
|
||||||
!download.startTime ||
|
!download.startTime ||
|
||||||
!download.bytesDownloaded ||
|
!download.secondsDownloaded ||
|
||||||
!download.bytesTotal
|
!download.secondsTotal
|
||||||
) {
|
) {
|
||||||
console.log(download);
|
console.log(download);
|
||||||
return null;
|
return null;
|
||||||
@@ -41,12 +40,10 @@ const getETA = (download: DownloadInfo): string | null => {
|
|||||||
|
|
||||||
const elapsed = Date.now() / 1000 - download.startTime; // seconds
|
const elapsed = Date.now() / 1000 - download.startTime; // seconds
|
||||||
|
|
||||||
console.log("Elapsed (s):", Number(download.startTime), Date.now(), elapsed);
|
if (elapsed <= 0 || download.secondsDownloaded <= 0) return null;
|
||||||
|
|
||||||
if (elapsed <= 0 || download.bytesDownloaded <= 0) return null;
|
const speed = download.secondsDownloaded / elapsed; // downloaded seconds per second
|
||||||
|
const remainingBytes = download.secondsTotal - download.secondsDownloaded;
|
||||||
const speed = download.bytesDownloaded / elapsed; // bytes per second
|
|
||||||
const remainingBytes = download.bytesTotal - download.bytesDownloaded;
|
|
||||||
|
|
||||||
if (speed <= 0) return null;
|
if (speed <= 0) return null;
|
||||||
|
|
||||||
@@ -108,8 +105,8 @@ export default function Index() {
|
|||||||
</Text>
|
</Text>
|
||||||
{activeDownloads.map((i) => {
|
{activeDownloads.map((i) => {
|
||||||
const progress =
|
const progress =
|
||||||
i.bytesTotal && i.bytesDownloaded
|
i.secondsTotal && i.secondsDownloaded
|
||||||
? i.bytesDownloaded / i.bytesTotal
|
? i.secondsDownloaded / i.secondsTotal
|
||||||
: 0;
|
: 0;
|
||||||
const eta = getETA(i);
|
const eta = getETA(i);
|
||||||
const item = i.metadata?.item;
|
const item = i.metadata?.item;
|
||||||
@@ -130,11 +127,6 @@ export default function Index() {
|
|||||||
<Text className="text-xs text-neutral-500">
|
<Text className="text-xs text-neutral-500">
|
||||||
{eta ? `${eta} remaining` : "Calculating time..."}
|
{eta ? `${eta} remaining` : "Calculating time..."}
|
||||||
</Text>
|
</Text>
|
||||||
{i.state === "DOWNLOADING" && i.bytesTotal ? (
|
|
||||||
<Text className="text-xs text-neutral-500">
|
|
||||||
{formatBytes(i.bytesTotal * 100000)}
|
|
||||||
</Text>
|
|
||||||
) : null}
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
|||||||
@@ -15,11 +15,16 @@ import {
|
|||||||
BaseItemDto,
|
BaseItemDto,
|
||||||
MediaSourceInfo,
|
MediaSourceInfo,
|
||||||
} from "@jellyfin/sdk/lib/generated-client/models";
|
} from "@jellyfin/sdk/lib/generated-client/models";
|
||||||
import { useFocusEffect } from "expo-router";
|
import { useFocusEffect, useRouter } from "expo-router";
|
||||||
import { t } from "i18next";
|
import { t } from "i18next";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import React, { useCallback, useMemo, useRef, useState } from "react";
|
import React, { useCallback, useMemo, useRef, useState } from "react";
|
||||||
import { ActivityIndicator, View, ViewProps } from "react-native";
|
import {
|
||||||
|
ActivityIndicator,
|
||||||
|
TouchableOpacity,
|
||||||
|
View,
|
||||||
|
ViewProps,
|
||||||
|
} from "react-native";
|
||||||
import { toast } from "sonner-native";
|
import { toast } from "sonner-native";
|
||||||
import { AudioTrackSelector } from "../AudioTrackSelector";
|
import { AudioTrackSelector } from "../AudioTrackSelector";
|
||||||
import { Bitrate, BitrateSelector } from "../BitrateSelector";
|
import { Bitrate, BitrateSelector } from "../BitrateSelector";
|
||||||
@@ -158,6 +163,8 @@ export const NativeDownloadButton: React.FC<NativeDownloadButton> = ({
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const activeDownload = item.Id ? downloads[item.Id] : undefined;
|
const activeDownload = item.Id ? downloads[item.Id] : undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -168,7 +175,11 @@ export const NativeDownloadButton: React.FC<NativeDownloadButton> = ({
|
|||||||
onPress={handlePresentModalPress}
|
onPress={handlePresentModalPress}
|
||||||
>
|
>
|
||||||
{activeDownload ? (
|
{activeDownload ? (
|
||||||
<>
|
<TouchableOpacity
|
||||||
|
onPress={() => {
|
||||||
|
router.push(`/downloads`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
{activeDownload.state === "PENDING" && (
|
{activeDownload.state === "PENDING" && (
|
||||||
<ActivityIndicator size="small" color="white" />
|
<ActivityIndicator size="small" color="white" />
|
||||||
)}
|
)}
|
||||||
@@ -193,7 +204,7 @@ export const NativeDownloadButton: React.FC<NativeDownloadButton> = ({
|
|||||||
{activeDownload.state === "DONE" && (
|
{activeDownload.state === "DONE" && (
|
||||||
<Ionicons name="cloud-done-outline" size={24} color={"white"} />
|
<Ionicons name="cloud-done-outline" size={24} color={"white"} />
|
||||||
)}
|
)}
|
||||||
</>
|
</TouchableOpacity>
|
||||||
) : (
|
) : (
|
||||||
<Ionicons name="cloud-download-outline" size={24} color="white" />
|
<Ionicons name="cloud-download-outline" size={24} color="white" />
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ function downloadHLSAsset(
|
|||||||
/**
|
/**
|
||||||
* Checks for existing downloads.
|
* Checks for existing downloads.
|
||||||
* Returns an array of downloads with additional fields:
|
* Returns an array of downloads with additional fields:
|
||||||
* id, progress, bytesDownloaded, bytesTotal, and state.
|
|
||||||
*/
|
*/
|
||||||
async function checkForExistingDownloads(): Promise<DownloadInfo[]> {
|
async function checkForExistingDownloads(): Promise<DownloadInfo[]> {
|
||||||
return HlsDownloaderModule.checkForExistingDownloads();
|
return HlsDownloaderModule.checkForExistingDownloads();
|
||||||
@@ -99,91 +98,9 @@ function useDownloadError(): string | null {
|
|||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Moves a file from a temporary URI to a permanent location in the document directory.
|
|
||||||
* @param tempFileUri The temporary file URI returned by the native module.
|
|
||||||
* @param newFilename The desired filename (with extension) for the persisted file.
|
|
||||||
* @returns A promise that resolves with the new file URI.
|
|
||||||
*/
|
|
||||||
async function persistDownloadedFile(
|
|
||||||
tempFileUri: string,
|
|
||||||
newFilename: string
|
|
||||||
): Promise<string> {
|
|
||||||
const newUri = FileSystem.documentDirectory + newFilename;
|
|
||||||
try {
|
|
||||||
await FileSystem.moveAsync({
|
|
||||||
from: tempFileUri,
|
|
||||||
to: newUri,
|
|
||||||
});
|
|
||||||
console.log("File persisted to:", newUri);
|
|
||||||
return newUri;
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Error moving file:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* React hook that returns the completion location of the download.
|
|
||||||
* If a destinationFileName is provided, the hook will move the downloaded file
|
|
||||||
* to the document directory under that name, then return the new URI.
|
|
||||||
*
|
|
||||||
* @param destinationFileName Optional filename (with extension) to persist the file.
|
|
||||||
* @returns The final file URI or null if not completed.
|
|
||||||
*/
|
|
||||||
function useDownloadComplete(destinationFileName?: string): string | null {
|
|
||||||
const [location, setLocation] = useState<string | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log("Setting up download complete listener");
|
|
||||||
|
|
||||||
const subscription = addCompleteListener(
|
|
||||||
async (event: OnCompleteEventPayload) => {
|
|
||||||
console.log("Download complete event received:", event);
|
|
||||||
console.log("Original download location:", event.location);
|
|
||||||
|
|
||||||
if (destinationFileName) {
|
|
||||||
console.log(
|
|
||||||
"Attempting to persist file with name:",
|
|
||||||
destinationFileName
|
|
||||||
);
|
|
||||||
try {
|
|
||||||
const newLocation = await persistDownloadedFile(
|
|
||||||
event.location,
|
|
||||||
destinationFileName
|
|
||||||
);
|
|
||||||
console.log("File successfully persisted to:", newLocation);
|
|
||||||
setLocation(newLocation);
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to persist file:", error);
|
|
||||||
console.error("Error details:", {
|
|
||||||
originalLocation: event.location,
|
|
||||||
destinationFileName,
|
|
||||||
error: error instanceof Error ? error.message : error,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
"No destination filename provided, using original location"
|
|
||||||
);
|
|
||||||
setLocation(event.location);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
console.log("Cleaning up download complete listener");
|
|
||||||
subscription.remove();
|
|
||||||
};
|
|
||||||
}, [destinationFileName]);
|
|
||||||
|
|
||||||
return location;
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
export {
|
||||||
downloadHLSAsset,
|
downloadHLSAsset,
|
||||||
checkForExistingDownloads,
|
checkForExistingDownloads,
|
||||||
useDownloadComplete,
|
|
||||||
useDownloadError,
|
useDownloadError,
|
||||||
useDownloadProgress,
|
useDownloadProgress,
|
||||||
addCompleteListener,
|
addCompleteListener,
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ public class HlsDownloaderModule: Module {
|
|||||||
url: assetURL,
|
url: assetURL,
|
||||||
options: [
|
options: [
|
||||||
"AVURLAssetOutOfBandMIMETypeKey": "application/x-mpegURL",
|
"AVURLAssetOutOfBandMIMETypeKey": "application/x-mpegURL",
|
||||||
"AVURLAssetHTTPHeaderFieldsKey": ["User-Agent": "YourAppNameHere/1.0"],
|
"AVURLAssetHTTPHeaderFieldsKey": ["User-Agent": "Streamyfin/1.0"],
|
||||||
"AVURLAssetAllowsCellularAccessKey": true,
|
"AVURLAssetAllowsCellularAccessKey": true,
|
||||||
])
|
])
|
||||||
|
|
||||||
@@ -134,8 +134,8 @@ public class HlsDownloaderModule: Module {
|
|||||||
downloads.append([
|
downloads.append([
|
||||||
"id": delegate.providedId.isEmpty ? String(id) : delegate.providedId,
|
"id": delegate.providedId.isEmpty ? String(id) : delegate.providedId,
|
||||||
"progress": progress,
|
"progress": progress,
|
||||||
"bytesDownloaded": downloaded,
|
"secondsDownloaded": downloaded,
|
||||||
"bytesTotal": total,
|
"secondsTotal": total,
|
||||||
"state": self.mappedState(for: task),
|
"state": self.mappedState(for: task),
|
||||||
"metadata": metadata,
|
"metadata": metadata,
|
||||||
"startTime": startTime,
|
"startTime": startTime,
|
||||||
@@ -243,8 +243,8 @@ class HLSDownloadDelegate: NSObject, AVAssetDownloadDelegate {
|
|||||||
[
|
[
|
||||||
"id": providedId,
|
"id": providedId,
|
||||||
"progress": progress,
|
"progress": progress,
|
||||||
"bytesDownloaded": downloaded,
|
"secondsDownloaded": downloaded,
|
||||||
"bytesTotal": total,
|
"secondsTotal": total,
|
||||||
"state": progress >= 1.0 ? "DONE" : "DOWNLOADING",
|
"state": progress >= 1.0 ? "DONE" : "DOWNLOADING",
|
||||||
"metadata": metadata,
|
"metadata": metadata,
|
||||||
"startTime": startTime,
|
"startTime": startTime,
|
||||||
@@ -263,6 +263,26 @@ class HLSDownloadDelegate: NSObject, AVAssetDownloadDelegate {
|
|||||||
let newLocation = try module.persistDownloadedFolder(
|
let newLocation = try module.persistDownloadedFolder(
|
||||||
originalLocation: location, folderName: folderName)
|
originalLocation: location, folderName: folderName)
|
||||||
|
|
||||||
|
// Calculate download size
|
||||||
|
let fileManager = FileManager.default
|
||||||
|
let enumerator = fileManager.enumerator(
|
||||||
|
at: newLocation,
|
||||||
|
includingPropertiesForKeys: [.totalFileAllocatedSizeKey],
|
||||||
|
options: [.skipsHiddenFiles],
|
||||||
|
errorHandler: nil)!
|
||||||
|
|
||||||
|
var totalSize: Int64 = 0
|
||||||
|
while let filePath = enumerator.nextObject() as? URL {
|
||||||
|
do {
|
||||||
|
let resourceValues = try filePath.resourceValues(forKeys: [.totalFileAllocatedSizeKey])
|
||||||
|
if let size = resourceValues.totalFileAllocatedSize {
|
||||||
|
totalSize += Int64(size)
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
print("Error calculating size: \(error)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !metadata.isEmpty {
|
if !metadata.isEmpty {
|
||||||
let metadataLocation = newLocation.deletingLastPathComponent().appendingPathComponent(
|
let metadataLocation = newLocation.deletingLastPathComponent().appendingPathComponent(
|
||||||
"\(providedId).json")
|
"\(providedId).json")
|
||||||
@@ -278,6 +298,7 @@ class HLSDownloadDelegate: NSObject, AVAssetDownloadDelegate {
|
|||||||
"state": "DONE",
|
"state": "DONE",
|
||||||
"metadata": metadata,
|
"metadata": metadata,
|
||||||
"startTime": startTime,
|
"startTime": startTime,
|
||||||
|
"bytesDownloaded": totalSize,
|
||||||
])
|
])
|
||||||
} catch {
|
} catch {
|
||||||
module?.sendEvent(
|
module?.sendEvent(
|
||||||
|
|||||||
@@ -21,13 +21,13 @@ export type BaseEventPayload = {
|
|||||||
id: string;
|
id: string;
|
||||||
state: DownloadState;
|
state: DownloadState;
|
||||||
metadata: DownloadMetadata;
|
metadata: DownloadMetadata;
|
||||||
|
startTime?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OnProgressEventPayload = BaseEventPayload & {
|
export type OnProgressEventPayload = BaseEventPayload & {
|
||||||
progress: number;
|
progress: number;
|
||||||
bytesDownloaded: number;
|
secondsDownloaded: number;
|
||||||
bytesTotal: number;
|
secondsTotal: number;
|
||||||
startTime?: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type OnErrorEventPayload = BaseEventPayload & {
|
export type OnErrorEventPayload = BaseEventPayload & {
|
||||||
@@ -38,6 +38,7 @@ export type OnErrorEventPayload = BaseEventPayload & {
|
|||||||
|
|
||||||
export type OnCompleteEventPayload = BaseEventPayload & {
|
export type OnCompleteEventPayload = BaseEventPayload & {
|
||||||
location: string;
|
location: string;
|
||||||
|
bytesDownloaded?: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type HlsDownloaderModuleEvents = {
|
export type HlsDownloaderModuleEvents = {
|
||||||
@@ -52,8 +53,8 @@ export interface DownloadInfo {
|
|||||||
startTime?: number;
|
startTime?: number;
|
||||||
progress: number;
|
progress: number;
|
||||||
state: DownloadState;
|
state: DownloadState;
|
||||||
bytesDownloaded?: number;
|
secondsDownloaded?: number;
|
||||||
bytesTotal?: number;
|
secondsTotal?: number;
|
||||||
location?: string;
|
location?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
metadata: DownloadMetadata;
|
metadata: DownloadMetadata;
|
||||||
|
|||||||
@@ -136,6 +136,7 @@ export const NativeDownloadProvider: React.FC<{
|
|||||||
refetchOnWindowFocus: true,
|
refetchOnWindowFocus: true,
|
||||||
refetchOnMount: true,
|
refetchOnMount: true,
|
||||||
refetchOnReconnect: true,
|
refetchOnReconnect: true,
|
||||||
|
staleTime: 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -150,8 +151,8 @@ export const NativeDownloadProvider: React.FC<{
|
|||||||
id: download.id,
|
id: download.id,
|
||||||
progress: download.progress,
|
progress: download.progress,
|
||||||
state: download.state,
|
state: download.state,
|
||||||
bytesDownloaded: download.bytesDownloaded,
|
secondsDownloaded: download.secondsDownloaded,
|
||||||
bytesTotal: download.bytesTotal,
|
secondsTotal: download.secondsTotal,
|
||||||
metadata: download.metadata,
|
metadata: download.metadata,
|
||||||
startTime: download?.startTime,
|
startTime: download?.startTime,
|
||||||
},
|
},
|
||||||
@@ -165,23 +166,25 @@ export const NativeDownloadProvider: React.FC<{
|
|||||||
initializeDownloads();
|
initializeDownloads();
|
||||||
|
|
||||||
const progressListener = addProgressListener((download) => {
|
const progressListener = addProgressListener((download) => {
|
||||||
|
console.log("Attempting to add progress listener");
|
||||||
if (!download.metadata) throw new Error("No metadata found in download");
|
if (!download.metadata) throw new Error("No metadata found in download");
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
"[HLS] Download progress:",
|
"[HLS] Download progress:",
|
||||||
download.bytesTotal,
|
download.secondsTotal,
|
||||||
download.bytesDownloaded,
|
download.secondsDownloaded,
|
||||||
download.progress,
|
download.progress,
|
||||||
download.state
|
download.state
|
||||||
);
|
);
|
||||||
|
|
||||||
setDownloads((prev) => ({
|
setDownloads((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
[download.id]: {
|
[download.id]: {
|
||||||
id: download.id,
|
id: download.id,
|
||||||
progress: download.progress,
|
progress: download.progress,
|
||||||
state: download.state,
|
state: download.state,
|
||||||
bytesDownloaded: download.bytesDownloaded,
|
secondsDownloaded: download.secondsDownloaded,
|
||||||
bytesTotal: download.bytesTotal,
|
secondsTotal: download.secondsTotal,
|
||||||
metadata: download.metadata,
|
metadata: download.metadata,
|
||||||
startTime: download?.startTime,
|
startTime: download?.startTime,
|
||||||
},
|
},
|
||||||
@@ -189,8 +192,6 @@ export const NativeDownloadProvider: React.FC<{
|
|||||||
});
|
});
|
||||||
|
|
||||||
const completeListener = addCompleteListener(async (payload) => {
|
const completeListener = addCompleteListener(async (payload) => {
|
||||||
if (!payload.id) throw new Error("No id found in payload");
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await rewriteM3U8Files(payload.location);
|
await rewriteM3U8Files(payload.location);
|
||||||
await markFileAsDone(payload.id);
|
await markFileAsDone(payload.id);
|
||||||
@@ -205,21 +206,18 @@ export const NativeDownloadProvider: React.FC<{
|
|||||||
|
|
||||||
toast.success("Download complete ✅");
|
toast.success("Download complete ✅");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to persist file:", error);
|
console.error("Failed to download file:", error);
|
||||||
toast.error("Failed to download ❌");
|
toast.error("Failed to download ❌");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const errorListener = addErrorListener((error) => {
|
const errorListener = addErrorListener((error) => {
|
||||||
console.error("Download error:", error);
|
setDownloads((prev) => {
|
||||||
if (error.id) {
|
const newDownloads = { ...prev };
|
||||||
setDownloads((prev) => {
|
delete newDownloads[error.id];
|
||||||
const newDownloads = { ...prev };
|
return newDownloads;
|
||||||
delete newDownloads[error.id];
|
});
|
||||||
return newDownloads;
|
toast.error("Failed to download ❌");
|
||||||
});
|
|
||||||
toast.error("Failed to download ❌");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@@ -249,10 +247,10 @@ export const NativeDownloadProvider: React.FC<{
|
|||||||
console.log("Found unparsed download:", id);
|
console.log("Found unparsed download:", id);
|
||||||
|
|
||||||
const p = async () => {
|
const p = async () => {
|
||||||
await markFileAsDone(id);
|
await rewriteM3U8Files(
|
||||||
rewriteM3U8Files(
|
|
||||||
FileSystem.documentDirectory + "downloads/" + id
|
FileSystem.documentDirectory + "downloads/" + id
|
||||||
);
|
);
|
||||||
|
await markFileAsDone(id);
|
||||||
};
|
};
|
||||||
toast.promise(p(), {
|
toast.promise(p(), {
|
||||||
error: () => "Failed to download ❌",
|
error: () => "Failed to download ❌",
|
||||||
|
|||||||
@@ -38,7 +38,5 @@ export async function parseBootXML(xml: string): Promise<Boot> {
|
|||||||
parseAttributeValue: true,
|
parseAttributeValue: true,
|
||||||
});
|
});
|
||||||
const jsonObj = parser.parse(xml);
|
const jsonObj = parser.parse(xml);
|
||||||
const b = jsonObj.HLSMoviePackage as Boot;
|
|
||||||
console.log(b.Streams);
|
|
||||||
return jsonObj.HLSMoviePackage as Boot;
|
return jsonObj.HLSMoviePackage as Boot;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { parseBootXML } from "./parse/boot";
|
|||||||
import { parseStreamInfoXml, StreamInfo } from "./parse/streamInfoBoot";
|
import { parseStreamInfoXml, StreamInfo } from "./parse/streamInfoBoot";
|
||||||
|
|
||||||
export async function rewriteM3U8Files(baseDir: string): Promise<void> {
|
export async function rewriteM3U8Files(baseDir: string): Promise<void> {
|
||||||
|
console.log(`[1] Rewriting M3U8 files in ${baseDir}`);
|
||||||
const bootData = await loadBootData(baseDir);
|
const bootData = await loadBootData(baseDir);
|
||||||
if (!bootData) return;
|
if (!bootData) return;
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@ export async function rewriteM3U8Files(baseDir: string): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadBootData(baseDir: string): Promise<any | null> {
|
async function loadBootData(baseDir: string): Promise<any | null> {
|
||||||
|
console.log(`[2] Loading boot.xml from ${baseDir}`);
|
||||||
const bootPath = `${baseDir}/boot.xml`;
|
const bootPath = `${baseDir}/boot.xml`;
|
||||||
try {
|
try {
|
||||||
const bootInfo = await FileSystem.getInfoAsync(bootPath);
|
const bootInfo = await FileSystem.getInfoAsync(bootPath);
|
||||||
@@ -31,15 +33,19 @@ async function processAllStreams(
|
|||||||
baseDir: string,
|
baseDir: string,
|
||||||
bootData: any
|
bootData: any
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
|
console.log(`[3] Processing all streams in ${baseDir}`);
|
||||||
const localPaths: string[] = [];
|
const localPaths: string[] = [];
|
||||||
|
const streams = Array.isArray(bootData.Streams.Stream)
|
||||||
|
? bootData.Streams.Stream
|
||||||
|
: [bootData.Streams.Stream];
|
||||||
|
|
||||||
for (const stream of bootData.Streams.Stream) {
|
for (const stream of streams) {
|
||||||
const streamDir = `${baseDir}/${stream.ID}`;
|
const streamDir = `${baseDir}/${stream.ID}`;
|
||||||
try {
|
try {
|
||||||
const streamInfo = await processStream(streamDir);
|
const streamInfo = await processStream(streamDir);
|
||||||
if (streamInfo && streamInfo.MediaPlaylist.PathToLocalCopy) {
|
if (streamInfo && streamInfo.MediaPlaylist.PathToLocalCopy) {
|
||||||
localPaths.push(
|
localPaths.push(
|
||||||
`${streamDir}${streamInfo.MediaPlaylist.PathToLocalCopy}`
|
`${streamDir}/${streamInfo.MediaPlaylist.PathToLocalCopy}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -84,7 +90,9 @@ export function updatePlaylistWithLocalSegments(
|
|||||||
export async function processStream(
|
export async function processStream(
|
||||||
streamDir: string
|
streamDir: string
|
||||||
): Promise<StreamInfo | null> {
|
): Promise<StreamInfo | null> {
|
||||||
|
console.log(`[4] Processing stream at ${streamDir}`);
|
||||||
const streamInfoPath = `${streamDir}/StreamInfoBoot.xml`;
|
const streamInfoPath = `${streamDir}/StreamInfoBoot.xml`;
|
||||||
|
console.log(`Processing stream at ${streamDir}...`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const streamXML = await FileSystem.readAsStringAsync(streamInfoPath);
|
const streamXML = await FileSystem.readAsStringAsync(streamInfoPath);
|
||||||
|
|||||||
Reference in New Issue
Block a user