mirror of
https://github.com/streamyfin/streamyfin.git
synced 2026-04-30 20:36:29 +01:00
feat(player): add mpv cache and buffer configuration
This commit is contained in:
@@ -20,6 +20,7 @@ import { useTVOptionModal } from "@/hooks/useTVOptionModal";
|
|||||||
import { apiAtom, useJellyfin, userAtom } from "@/providers/JellyfinProvider";
|
import { apiAtom, useJellyfin, userAtom } from "@/providers/JellyfinProvider";
|
||||||
import {
|
import {
|
||||||
AudioTranscodeMode,
|
AudioTranscodeMode,
|
||||||
|
type MpvCacheMode,
|
||||||
TVTypographyScale,
|
TVTypographyScale,
|
||||||
useSettings,
|
useSettings,
|
||||||
} from "@/utils/atoms/settings";
|
} from "@/utils/atoms/settings";
|
||||||
@@ -47,6 +48,7 @@ export default function SettingsTV() {
|
|||||||
const currentAlignY = settings.mpvSubtitleAlignY ?? "bottom";
|
const currentAlignY = settings.mpvSubtitleAlignY ?? "bottom";
|
||||||
const currentTypographyScale =
|
const currentTypographyScale =
|
||||||
settings.tvTypographyScale || TVTypographyScale.Default;
|
settings.tvTypographyScale || TVTypographyScale.Default;
|
||||||
|
const currentCacheMode = settings.mpvCacheEnabled ?? "auto";
|
||||||
|
|
||||||
// Audio transcoding options
|
// Audio transcoding options
|
||||||
const audioTranscodeModeOptions: TVOptionItem<AudioTranscodeMode>[] = useMemo(
|
const audioTranscodeModeOptions: TVOptionItem<AudioTranscodeMode>[] = useMemo(
|
||||||
@@ -138,6 +140,28 @@ export default function SettingsTV() {
|
|||||||
[currentAlignY],
|
[currentAlignY],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Cache mode options
|
||||||
|
const cacheModeOptions: TVOptionItem<MpvCacheMode>[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
label: t("home.settings.buffer.cache_auto"),
|
||||||
|
value: "auto",
|
||||||
|
selected: currentCacheMode === "auto",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("home.settings.buffer.cache_yes"),
|
||||||
|
value: "yes",
|
||||||
|
selected: currentCacheMode === "yes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("home.settings.buffer.cache_no"),
|
||||||
|
value: "no",
|
||||||
|
selected: currentCacheMode === "no",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[t, currentCacheMode],
|
||||||
|
);
|
||||||
|
|
||||||
// Typography scale options
|
// Typography scale options
|
||||||
const typographyScaleOptions: TVOptionItem<TVTypographyScale>[] = useMemo(
|
const typographyScaleOptions: TVOptionItem<TVTypographyScale>[] = useMemo(
|
||||||
() => [
|
() => [
|
||||||
@@ -191,6 +215,11 @@ export default function SettingsTV() {
|
|||||||
return option?.label || t("home.settings.appearance.display_size_default");
|
return option?.label || t("home.settings.appearance.display_size_default");
|
||||||
}, [typographyScaleOptions, t]);
|
}, [typographyScaleOptions, t]);
|
||||||
|
|
||||||
|
const cacheModeLabel = useMemo(() => {
|
||||||
|
const option = cacheModeOptions.find((o) => o.selected);
|
||||||
|
return option?.label || t("home.settings.buffer.cache_auto");
|
||||||
|
}, [cacheModeOptions, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, backgroundColor: "#000000" }}>
|
<View style={{ flex: 1, backgroundColor: "#000000" }}>
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
@@ -382,6 +411,77 @@ export default function SettingsTV() {
|
|||||||
"Get your free API key at opensubtitles.com/en/consumers"}
|
"Get your free API key at opensubtitles.com/en/consumers"}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
{/* Buffer Settings Section */}
|
||||||
|
<TVSectionHeader title={t("home.settings.buffer.title")} />
|
||||||
|
<TVSettingsOptionButton
|
||||||
|
label={t("home.settings.buffer.cache_mode")}
|
||||||
|
value={cacheModeLabel}
|
||||||
|
onPress={() =>
|
||||||
|
showOptions({
|
||||||
|
title: t("home.settings.buffer.cache_mode"),
|
||||||
|
options: cacheModeOptions,
|
||||||
|
onSelect: (value) => updateSettings({ mpvCacheEnabled: value }),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<TVSettingsStepper
|
||||||
|
label={t("home.settings.buffer.buffer_duration")}
|
||||||
|
value={settings.mpvCacheSeconds ?? 10}
|
||||||
|
onDecrease={() => {
|
||||||
|
const newValue = Math.max(
|
||||||
|
5,
|
||||||
|
(settings.mpvCacheSeconds ?? 10) - 5,
|
||||||
|
);
|
||||||
|
updateSettings({ mpvCacheSeconds: newValue });
|
||||||
|
}}
|
||||||
|
onIncrease={() => {
|
||||||
|
const newValue = Math.min(
|
||||||
|
120,
|
||||||
|
(settings.mpvCacheSeconds ?? 10) + 5,
|
||||||
|
);
|
||||||
|
updateSettings({ mpvCacheSeconds: newValue });
|
||||||
|
}}
|
||||||
|
formatValue={(v) => `${v}s`}
|
||||||
|
/>
|
||||||
|
<TVSettingsStepper
|
||||||
|
label={t("home.settings.buffer.max_cache_size")}
|
||||||
|
value={settings.mpvDemuxerMaxBytes ?? 150}
|
||||||
|
onDecrease={() => {
|
||||||
|
const newValue = Math.max(
|
||||||
|
50,
|
||||||
|
(settings.mpvDemuxerMaxBytes ?? 150) - 25,
|
||||||
|
);
|
||||||
|
updateSettings({ mpvDemuxerMaxBytes: newValue });
|
||||||
|
}}
|
||||||
|
onIncrease={() => {
|
||||||
|
const newValue = Math.min(
|
||||||
|
500,
|
||||||
|
(settings.mpvDemuxerMaxBytes ?? 150) + 25,
|
||||||
|
);
|
||||||
|
updateSettings({ mpvDemuxerMaxBytes: newValue });
|
||||||
|
}}
|
||||||
|
formatValue={(v) => `${v} MB`}
|
||||||
|
/>
|
||||||
|
<TVSettingsStepper
|
||||||
|
label={t("home.settings.buffer.max_backward_cache")}
|
||||||
|
value={settings.mpvDemuxerMaxBackBytes ?? 50}
|
||||||
|
onDecrease={() => {
|
||||||
|
const newValue = Math.max(
|
||||||
|
25,
|
||||||
|
(settings.mpvDemuxerMaxBackBytes ?? 50) - 25,
|
||||||
|
);
|
||||||
|
updateSettings({ mpvDemuxerMaxBackBytes: newValue });
|
||||||
|
}}
|
||||||
|
onIncrease={() => {
|
||||||
|
const newValue = Math.min(
|
||||||
|
200,
|
||||||
|
(settings.mpvDemuxerMaxBackBytes ?? 50) + 25,
|
||||||
|
);
|
||||||
|
updateSettings({ mpvDemuxerMaxBackBytes: newValue });
|
||||||
|
}}
|
||||||
|
formatValue={(v) => `${v} MB`}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Appearance Section */}
|
{/* Appearance Section */}
|
||||||
<TVSectionHeader title={t("home.settings.appearance.title")} />
|
<TVSectionHeader title={t("home.settings.appearance.title")} />
|
||||||
<TVSettingsOptionButton
|
<TVSettingsOptionButton
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|||||||
import { GestureControls } from "@/components/settings/GestureControls";
|
import { GestureControls } from "@/components/settings/GestureControls";
|
||||||
import { MediaProvider } from "@/components/settings/MediaContext";
|
import { MediaProvider } from "@/components/settings/MediaContext";
|
||||||
import { MediaToggles } from "@/components/settings/MediaToggles";
|
import { MediaToggles } from "@/components/settings/MediaToggles";
|
||||||
|
import { MpvBufferSettings } from "@/components/settings/MpvBufferSettings";
|
||||||
import { PlaybackControlsSettings } from "@/components/settings/PlaybackControlsSettings";
|
import { PlaybackControlsSettings } from "@/components/settings/PlaybackControlsSettings";
|
||||||
import { ChromecastSettings } from "../../../../../../components/settings/ChromecastSettings";
|
import { ChromecastSettings } from "../../../../../../components/settings/ChromecastSettings";
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ export default function PlaybackControlsPage() {
|
|||||||
<MediaToggles className='mb-4' />
|
<MediaToggles className='mb-4' />
|
||||||
<GestureControls className='mb-4' />
|
<GestureControls className='mb-4' />
|
||||||
<PlaybackControlsSettings />
|
<PlaybackControlsSettings />
|
||||||
|
<MpvBufferSettings />
|
||||||
</MediaProvider>
|
</MediaProvider>
|
||||||
</View>
|
</View>
|
||||||
{!Platform.isTV && <ChromecastSettings />}
|
{!Platform.isTV && <ChromecastSettings />}
|
||||||
|
|||||||
@@ -587,6 +587,13 @@ export default function page() {
|
|||||||
autoplay: true,
|
autoplay: true,
|
||||||
initialSubtitleId,
|
initialSubtitleId,
|
||||||
initialAudioId,
|
initialAudioId,
|
||||||
|
// Pass cache/buffer settings from user preferences
|
||||||
|
cacheConfig: {
|
||||||
|
enabled: settings.mpvCacheEnabled,
|
||||||
|
cacheSeconds: settings.mpvCacheSeconds,
|
||||||
|
maxBytes: settings.mpvDemuxerMaxBytes,
|
||||||
|
maxBackBytes: settings.mpvDemuxerMaxBackBytes,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add external subtitles only for online playback
|
// Add external subtitles only for online playback
|
||||||
@@ -612,6 +619,10 @@ export default function page() {
|
|||||||
subtitleIndex,
|
subtitleIndex,
|
||||||
audioIndex,
|
audioIndex,
|
||||||
offline,
|
offline,
|
||||||
|
settings.mpvCacheEnabled,
|
||||||
|
settings.mpvCacheSeconds,
|
||||||
|
settings.mpvDemuxerMaxBytes,
|
||||||
|
settings.mpvDemuxerMaxBackBytes,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const volumeUpCb = useCallback(async () => {
|
const volumeUpCb = useCallback(async () => {
|
||||||
|
|||||||
100
components/settings/MpvBufferSettings.tsx
Normal file
100
components/settings/MpvBufferSettings.tsx
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
|
import type React from "react";
|
||||||
|
import { useMemo } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { View } from "react-native";
|
||||||
|
import { Stepper } from "@/components/inputs/Stepper";
|
||||||
|
import { PlatformDropdown } from "@/components/PlatformDropdown";
|
||||||
|
import { type MpvCacheMode, useSettings } from "@/utils/atoms/settings";
|
||||||
|
import { Text } from "../common/Text";
|
||||||
|
import { ListGroup } from "../list/ListGroup";
|
||||||
|
import { ListItem } from "../list/ListItem";
|
||||||
|
|
||||||
|
const CACHE_MODE_OPTIONS: { key: string; value: MpvCacheMode }[] = [
|
||||||
|
{ key: "home.settings.buffer.cache_auto", value: "auto" },
|
||||||
|
{ key: "home.settings.buffer.cache_yes", value: "yes" },
|
||||||
|
{ key: "home.settings.buffer.cache_no", value: "no" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export const MpvBufferSettings: React.FC = () => {
|
||||||
|
const { settings, updateSettings } = useSettings();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const cacheModeOptions = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
options: CACHE_MODE_OPTIONS.map((option) => ({
|
||||||
|
type: "radio" as const,
|
||||||
|
label: t(option.key),
|
||||||
|
value: option.value,
|
||||||
|
selected: option.value === (settings?.mpvCacheEnabled ?? "auto"),
|
||||||
|
onPress: () => updateSettings({ mpvCacheEnabled: option.value }),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[settings?.mpvCacheEnabled, t, updateSettings],
|
||||||
|
);
|
||||||
|
|
||||||
|
const currentCacheModeLabel = useMemo(() => {
|
||||||
|
const option = CACHE_MODE_OPTIONS.find(
|
||||||
|
(o) => o.value === (settings?.mpvCacheEnabled ?? "auto"),
|
||||||
|
);
|
||||||
|
return option ? t(option.key) : t("home.settings.buffer.cache_auto");
|
||||||
|
}, [settings?.mpvCacheEnabled, t]);
|
||||||
|
|
||||||
|
if (!settings) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ListGroup title={t("home.settings.buffer.title")} className='mb-4'>
|
||||||
|
<ListItem title={t("home.settings.buffer.cache_mode")}>
|
||||||
|
<PlatformDropdown
|
||||||
|
groups={cacheModeOptions}
|
||||||
|
trigger={
|
||||||
|
<View className='flex flex-row items-center justify-between py-1.5 pl-3'>
|
||||||
|
<Text className='mr-1 text-[#8E8D91]'>
|
||||||
|
{currentCacheModeLabel}
|
||||||
|
</Text>
|
||||||
|
<Ionicons name='chevron-expand-sharp' size={18} color='#5A5960' />
|
||||||
|
</View>
|
||||||
|
}
|
||||||
|
title={t("home.settings.buffer.cache_mode")}
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem title={t("home.settings.buffer.buffer_duration")}>
|
||||||
|
<Stepper
|
||||||
|
value={settings.mpvCacheSeconds ?? 10}
|
||||||
|
step={5}
|
||||||
|
min={5}
|
||||||
|
max={120}
|
||||||
|
onUpdate={(value) => updateSettings({ mpvCacheSeconds: value })}
|
||||||
|
appendValue='s'
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem title={t("home.settings.buffer.max_cache_size")}>
|
||||||
|
<Stepper
|
||||||
|
value={settings.mpvDemuxerMaxBytes ?? 150}
|
||||||
|
step={25}
|
||||||
|
min={50}
|
||||||
|
max={500}
|
||||||
|
onUpdate={(value) => updateSettings({ mpvDemuxerMaxBytes: value })}
|
||||||
|
appendValue=' MB'
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
|
||||||
|
<ListItem title={t("home.settings.buffer.max_backward_cache")}>
|
||||||
|
<Stepper
|
||||||
|
value={settings.mpvDemuxerMaxBackBytes ?? 50}
|
||||||
|
step={25}
|
||||||
|
min={25}
|
||||||
|
max={200}
|
||||||
|
onUpdate={(value) =>
|
||||||
|
updateSettings({ mpvDemuxerMaxBackBytes: value })
|
||||||
|
}
|
||||||
|
appendValue=' MB'
|
||||||
|
/>
|
||||||
|
</ListItem>
|
||||||
|
</ListGroup>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -300,7 +300,11 @@ final class MPVLayerRenderer {
|
|||||||
startPosition: Double? = nil,
|
startPosition: Double? = nil,
|
||||||
externalSubtitles: [String]? = nil,
|
externalSubtitles: [String]? = nil,
|
||||||
initialSubtitleId: Int? = nil,
|
initialSubtitleId: Int? = nil,
|
||||||
initialAudioId: Int? = nil
|
initialAudioId: Int? = nil,
|
||||||
|
cacheEnabled: String? = nil,
|
||||||
|
cacheSeconds: Int? = nil,
|
||||||
|
demuxerMaxBytes: Int? = nil,
|
||||||
|
demuxerMaxBackBytes: Int? = nil
|
||||||
) {
|
) {
|
||||||
currentPreset = preset
|
currentPreset = preset
|
||||||
currentURL = url
|
currentURL = url
|
||||||
@@ -323,6 +327,21 @@ final class MPVLayerRenderer {
|
|||||||
// Stop previous playback before loading new file
|
// Stop previous playback before loading new file
|
||||||
self.command(handle, ["stop"])
|
self.command(handle, ["stop"])
|
||||||
self.updateHTTPHeaders(headers)
|
self.updateHTTPHeaders(headers)
|
||||||
|
|
||||||
|
// Apply cache/buffer settings
|
||||||
|
if let cacheMode = cacheEnabled {
|
||||||
|
self.setProperty(name: "cache", value: cacheMode)
|
||||||
|
}
|
||||||
|
if let cacheSecs = cacheSeconds {
|
||||||
|
self.setProperty(name: "cache-secs", value: String(cacheSecs))
|
||||||
|
}
|
||||||
|
if let maxBytes = demuxerMaxBytes {
|
||||||
|
self.setProperty(name: "demuxer-max-bytes", value: "\(maxBytes)MiB")
|
||||||
|
}
|
||||||
|
if let maxBackBytes = demuxerMaxBackBytes {
|
||||||
|
self.setProperty(name: "demuxer-max-back-bytes", value: "\(maxBackBytes)MiB")
|
||||||
|
}
|
||||||
|
|
||||||
// Set start position
|
// Set start position
|
||||||
if let startPos = startPosition, startPos > 0 {
|
if let startPos = startPosition, startPos > 0 {
|
||||||
self.setProperty(name: "start", value: String(format: "%.2f", startPos))
|
self.setProperty(name: "start", value: String(format: "%.2f", startPos))
|
||||||
|
|||||||
@@ -29,7 +29,10 @@ public class MpvPlayerModule: Module {
|
|||||||
guard let source = source,
|
guard let source = source,
|
||||||
let urlString = source["url"] as? String,
|
let urlString = source["url"] as? String,
|
||||||
let videoURL = URL(string: urlString) else { return }
|
let videoURL = URL(string: urlString) else { return }
|
||||||
|
|
||||||
|
// Parse cache config if provided
|
||||||
|
let cacheConfig = source["cacheConfig"] as? [String: Any]
|
||||||
|
|
||||||
let config = VideoLoadConfig(
|
let config = VideoLoadConfig(
|
||||||
url: videoURL,
|
url: videoURL,
|
||||||
headers: source["headers"] as? [String: String],
|
headers: source["headers"] as? [String: String],
|
||||||
@@ -37,9 +40,13 @@ public class MpvPlayerModule: Module {
|
|||||||
startPosition: source["startPosition"] as? Double,
|
startPosition: source["startPosition"] as? Double,
|
||||||
autoplay: (source["autoplay"] as? Bool) ?? true,
|
autoplay: (source["autoplay"] as? Bool) ?? true,
|
||||||
initialSubtitleId: source["initialSubtitleId"] as? Int,
|
initialSubtitleId: source["initialSubtitleId"] as? Int,
|
||||||
initialAudioId: source["initialAudioId"] as? Int
|
initialAudioId: source["initialAudioId"] as? Int,
|
||||||
|
cacheEnabled: cacheConfig?["enabled"] as? String,
|
||||||
|
cacheSeconds: cacheConfig?["cacheSeconds"] as? Int,
|
||||||
|
demuxerMaxBytes: cacheConfig?["maxBytes"] as? Int,
|
||||||
|
demuxerMaxBackBytes: cacheConfig?["maxBackBytes"] as? Int
|
||||||
)
|
)
|
||||||
|
|
||||||
view.loadVideo(config: config)
|
view.loadVideo(config: config)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,12 @@ struct VideoLoadConfig {
|
|||||||
var initialSubtitleId: Int?
|
var initialSubtitleId: Int?
|
||||||
/// MPV audio track ID to select on start (1-based, nil to use default)
|
/// MPV audio track ID to select on start (1-based, nil to use default)
|
||||||
var initialAudioId: Int?
|
var initialAudioId: Int?
|
||||||
|
/// Cache/buffer settings
|
||||||
|
var cacheEnabled: String? // "auto", "yes", or "no"
|
||||||
|
var cacheSeconds: Int? // Seconds of video to buffer
|
||||||
|
var demuxerMaxBytes: Int? // Max cache size in MB
|
||||||
|
var demuxerMaxBackBytes: Int? // Max backward cache size in MB
|
||||||
|
|
||||||
init(
|
init(
|
||||||
url: URL,
|
url: URL,
|
||||||
headers: [String: String]? = nil,
|
headers: [String: String]? = nil,
|
||||||
@@ -23,7 +28,11 @@ struct VideoLoadConfig {
|
|||||||
startPosition: Double? = nil,
|
startPosition: Double? = nil,
|
||||||
autoplay: Bool = true,
|
autoplay: Bool = true,
|
||||||
initialSubtitleId: Int? = nil,
|
initialSubtitleId: Int? = nil,
|
||||||
initialAudioId: Int? = nil
|
initialAudioId: Int? = nil,
|
||||||
|
cacheEnabled: String? = nil,
|
||||||
|
cacheSeconds: Int? = nil,
|
||||||
|
demuxerMaxBytes: Int? = nil,
|
||||||
|
demuxerMaxBackBytes: Int? = nil
|
||||||
) {
|
) {
|
||||||
self.url = url
|
self.url = url
|
||||||
self.headers = headers
|
self.headers = headers
|
||||||
@@ -32,6 +41,10 @@ struct VideoLoadConfig {
|
|||||||
self.autoplay = autoplay
|
self.autoplay = autoplay
|
||||||
self.initialSubtitleId = initialSubtitleId
|
self.initialSubtitleId = initialSubtitleId
|
||||||
self.initialAudioId = initialAudioId
|
self.initialAudioId = initialAudioId
|
||||||
|
self.cacheEnabled = cacheEnabled
|
||||||
|
self.cacheSeconds = cacheSeconds
|
||||||
|
self.demuxerMaxBytes = demuxerMaxBytes
|
||||||
|
self.demuxerMaxBackBytes = demuxerMaxBackBytes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,13 +164,17 @@ class MpvPlayerView: ExpoView {
|
|||||||
startPosition: config.startPosition,
|
startPosition: config.startPosition,
|
||||||
externalSubtitles: config.externalSubtitles,
|
externalSubtitles: config.externalSubtitles,
|
||||||
initialSubtitleId: config.initialSubtitleId,
|
initialSubtitleId: config.initialSubtitleId,
|
||||||
initialAudioId: config.initialAudioId
|
initialAudioId: config.initialAudioId,
|
||||||
|
cacheEnabled: config.cacheEnabled,
|
||||||
|
cacheSeconds: config.cacheSeconds,
|
||||||
|
demuxerMaxBytes: config.demuxerMaxBytes,
|
||||||
|
demuxerMaxBackBytes: config.demuxerMaxBackBytes
|
||||||
)
|
)
|
||||||
|
|
||||||
if config.autoplay {
|
if config.autoplay {
|
||||||
play()
|
play()
|
||||||
}
|
}
|
||||||
|
|
||||||
onLoad(["url": config.url.absoluteString])
|
onLoad(["url": config.url.absoluteString])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,17 @@ export type VideoSource = {
|
|||||||
initialSubtitleId?: number;
|
initialSubtitleId?: number;
|
||||||
/** MPV audio track ID to select on start (1-based) */
|
/** MPV audio track ID to select on start (1-based) */
|
||||||
initialAudioId?: number;
|
initialAudioId?: number;
|
||||||
|
/** MPV cache/buffer configuration */
|
||||||
|
cacheConfig?: {
|
||||||
|
/** Whether caching is enabled: "auto" (default), "yes", or "no" */
|
||||||
|
enabled?: "auto" | "yes" | "no";
|
||||||
|
/** Seconds of video to buffer (default: 10, range: 5-120) */
|
||||||
|
cacheSeconds?: number;
|
||||||
|
/** Maximum cache size in MB (default: 150, range: 50-500) */
|
||||||
|
maxBytes?: number;
|
||||||
|
/** Maximum backward cache size in MB (default: 50, range: 25-200) */
|
||||||
|
maxBackBytes?: number;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type MpvPlayerViewProps = {
|
export type MpvPlayerViewProps = {
|
||||||
|
|||||||
@@ -185,6 +185,16 @@
|
|||||||
"rewind_length": "Rewind Length",
|
"rewind_length": "Rewind Length",
|
||||||
"seconds_unit": "s"
|
"seconds_unit": "s"
|
||||||
},
|
},
|
||||||
|
"buffer": {
|
||||||
|
"title": "Buffer Settings",
|
||||||
|
"cache_mode": "Cache Mode",
|
||||||
|
"cache_auto": "Auto",
|
||||||
|
"cache_yes": "Enabled",
|
||||||
|
"cache_no": "Disabled",
|
||||||
|
"buffer_duration": "Buffer Duration",
|
||||||
|
"max_cache_size": "Max Cache Size",
|
||||||
|
"max_backward_cache": "Max Backward Cache"
|
||||||
|
},
|
||||||
"gesture_controls": {
|
"gesture_controls": {
|
||||||
"gesture_controls_title": "Gesture Controls",
|
"gesture_controls_title": "Gesture Controls",
|
||||||
"horizontal_swipe_skip": "Horizontal Swipe to Skip",
|
"horizontal_swipe_skip": "Horizontal Swipe to Skip",
|
||||||
|
|||||||
@@ -154,6 +154,9 @@ export enum AudioTranscodeMode {
|
|||||||
AllowAll = "passthrough", // Direct play all audio formats
|
AllowAll = "passthrough", // Direct play all audio formats
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MPV cache mode - controls how caching is enabled
|
||||||
|
export type MpvCacheMode = "auto" | "yes" | "no";
|
||||||
|
|
||||||
export type Settings = {
|
export type Settings = {
|
||||||
home?: Home | null;
|
home?: Home | null;
|
||||||
deviceProfile?: "Expo" | "Native" | "Old";
|
deviceProfile?: "Expo" | "Native" | "Old";
|
||||||
@@ -199,6 +202,11 @@ export type Settings = {
|
|||||||
mpvSubtitleAlignX?: "left" | "center" | "right";
|
mpvSubtitleAlignX?: "left" | "center" | "right";
|
||||||
mpvSubtitleAlignY?: "top" | "center" | "bottom";
|
mpvSubtitleAlignY?: "top" | "center" | "bottom";
|
||||||
mpvSubtitleFontSize?: number;
|
mpvSubtitleFontSize?: number;
|
||||||
|
// MPV buffer/cache settings
|
||||||
|
mpvCacheEnabled?: MpvCacheMode;
|
||||||
|
mpvCacheSeconds?: number;
|
||||||
|
mpvDemuxerMaxBytes?: number; // MB
|
||||||
|
mpvDemuxerMaxBackBytes?: number; // MB
|
||||||
// Gesture controls
|
// Gesture controls
|
||||||
enableHorizontalSwipeSkip: boolean;
|
enableHorizontalSwipeSkip: boolean;
|
||||||
enableLeftSideBrightnessSwipe: boolean;
|
enableLeftSideBrightnessSwipe: boolean;
|
||||||
@@ -290,6 +298,11 @@ export const defaultValues: Settings = {
|
|||||||
mpvSubtitleAlignX: undefined,
|
mpvSubtitleAlignX: undefined,
|
||||||
mpvSubtitleAlignY: undefined,
|
mpvSubtitleAlignY: undefined,
|
||||||
mpvSubtitleFontSize: undefined,
|
mpvSubtitleFontSize: undefined,
|
||||||
|
// MPV buffer/cache defaults
|
||||||
|
mpvCacheEnabled: "auto",
|
||||||
|
mpvCacheSeconds: 10,
|
||||||
|
mpvDemuxerMaxBytes: 150, // MB
|
||||||
|
mpvDemuxerMaxBackBytes: 50, // MB
|
||||||
// Gesture controls
|
// Gesture controls
|
||||||
enableHorizontalSwipeSkip: true,
|
enableHorizontalSwipeSkip: true,
|
||||||
enableLeftSideBrightnessSwipe: true,
|
enableLeftSideBrightnessSwipe: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user