some ui changes

This commit is contained in:
tapframe 2025-10-05 17:44:31 +05:30
parent 4f1b8103d0
commit ca52a81141
7 changed files with 192 additions and 30 deletions

View file

@ -124,8 +124,9 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
}
try {
const shouldFetchMeta = stremioService.isValidContentId(type, id);
const [metadata, basicContent] = await Promise.all([
stremioService.getMetaDetails(type, id),
shouldFetchMeta ? stremioService.getMetaDetails(type, id) : Promise.resolve(null),
catalogService.getBasicContentDetails(type, id)
]);

View file

@ -656,7 +656,7 @@ const WatchProgressDisplay = memo(({
<Text style={[isTablet ? styles.tabletWatchProgressSubText : styles.watchProgressSubText, {
color: isCompleted ? 'rgba(0,255,136,0.7)' : currentTheme.colors.textMuted,
}]}>
{progressData.episodeInfo} {progressData.formattedTime}
{progressData.episodeInfo}
</Text>
{/* Trakt sync status with enhanced styling */}

View file

@ -521,6 +521,9 @@ const AndroidVideoPlayer: React.FC = () => {
// Volume and brightness controls
const [volume, setVolume] = useState(1.0);
const [brightness, setBrightness] = useState(1.0);
// Store Android system brightness state to restore on exit/unmount
const originalSystemBrightnessRef = useRef<number | null>(null);
const originalSystemBrightnessModeRef = useRef<number | null>(null);
const [showVolumeOverlay, setShowVolumeOverlay] = useState(false);
const [showBrightnessOverlay, setShowBrightnessOverlay] = useState(false);
const [subtitleSettingsLoaded, setSubtitleSettingsLoaded] = useState(false);
@ -848,6 +851,22 @@ const AndroidVideoPlayer: React.FC = () => {
}
try {
// Capture Android system brightness and mode to restore later
if (Platform.OS === 'android') {
try {
const [sysBright, sysMode] = await Promise.all([
(Brightness as any).getSystemBrightnessAsync?.(),
(Brightness as any).getSystemBrightnessModeAsync?.()
]);
originalSystemBrightnessRef.current = typeof sysBright === 'number' ? sysBright : null;
originalSystemBrightnessModeRef.current = typeof sysMode === 'number' ? sysMode : null;
if (DEBUG_MODE) {
logger.log(`[AndroidVideoPlayer] Captured system brightness=${originalSystemBrightnessRef.current}, mode=${originalSystemBrightnessModeRef.current}`);
}
} catch (e) {
if (__DEV__) logger.warn('[AndroidVideoPlayer] Failed to capture system brightness state:', e);
}
}
const currentBrightness = await Brightness.getBrightnessAsync();
setBrightness(currentBrightness);
if (DEBUG_MODE) {
@ -1694,6 +1713,27 @@ const AndroidVideoPlayer: React.FC = () => {
logger.log(`[AndroidVideoPlayer] Current progress: ${actualCurrentTime}/${duration} (${progressPercent.toFixed(1)}%)`);
// Restore Android system brightness state so app does not lock brightness
const restoreSystemBrightness = async () => {
if (Platform.OS !== 'android') return;
try {
// Restore mode first (if available), then brightness value
if (originalSystemBrightnessModeRef.current !== null && typeof (Brightness as any).setSystemBrightnessModeAsync === 'function') {
await (Brightness as any).setSystemBrightnessModeAsync(originalSystemBrightnessModeRef.current);
}
if (originalSystemBrightnessRef.current !== null && typeof (Brightness as any).setSystemBrightnessAsync === 'function') {
await (Brightness as any).setSystemBrightnessAsync(originalSystemBrightnessRef.current);
}
if (DEBUG_MODE) {
logger.log('[AndroidVideoPlayer] Restored Android system brightness and mode');
}
} catch (e) {
logger.warn('[AndroidVideoPlayer] Failed to restore system brightness state:', e);
}
};
await restoreSystemBrightness();
// Navigate immediately without delay
ScreenOrientation.unlockAsync().then(() => {
// On tablets keep rotation unlocked; on phones, return to portrait
@ -2226,7 +2266,17 @@ const AndroidVideoPlayer: React.FC = () => {
}
}
}, [useVLC, selectVlcSubtitleTrack]);
const disableCustomSubtitles = useCallback(() => {
setUseCustomSubtitles(false);
setCustomSubtitles([]);
// Reset to first available built-in track or disable all tracks
if (useVLC) {
selectVlcSubtitleTrack(ksTextTracks.length > 0 ? 0 : null);
}
setSelectedTextTrack(ksTextTracks.length > 0 ? 0 : -1);
}, [useVLC, selectVlcSubtitleTrack, ksTextTracks.length]);
const loadSubtitleSize = async () => {
try {
// Prefer scoped subtitle settings
@ -2760,6 +2810,19 @@ const AndroidVideoPlayer: React.FC = () => {
clearInterval(progressSaveInterval);
setProgressSaveInterval(null);
}
// Best-effort restore of Android system brightness state on unmount
if (Platform.OS === 'android') {
try {
if (originalSystemBrightnessModeRef.current !== null && typeof (Brightness as any).setSystemBrightnessModeAsync === 'function') {
(Brightness as any).setSystemBrightnessModeAsync(originalSystemBrightnessModeRef.current);
}
if (originalSystemBrightnessRef.current !== null && typeof (Brightness as any).setSystemBrightnessAsync === 'function') {
(Brightness as any).setSystemBrightnessAsync(originalSystemBrightnessRef.current);
}
} catch (e) {
logger.warn('[AndroidVideoPlayer] Failed to restore system brightness on unmount:', e);
}
}
};
}, []);
@ -4018,6 +4081,7 @@ const AndroidVideoPlayer: React.FC = () => {
fetchAvailableSubtitles={fetchAvailableSubtitles}
loadWyzieSubtitle={loadWyzieSubtitle}
selectTextTrack={selectTextTrack}
disableCustomSubtitles={disableCustomSubtitles}
increaseSubtitleSize={increaseSubtitleSize}
decreaseSubtitleSize={decreaseSubtitleSize}
toggleSubtitleBackground={toggleSubtitleBackground}

View file

@ -178,6 +178,14 @@ const KSPlayerCore: React.FC = () => {
const [isLoadingSubtitleList, setIsLoadingSubtitleList] = useState<boolean>(false);
const [showSourcesModal, setShowSourcesModal] = useState<boolean>(false);
const [availableStreams, setAvailableStreams] = useState<{ [providerId: string]: { streams: any[]; addonName: string } }>(passedAvailableStreams || {});
// Playback speed controls required by PlayerControls
const speedOptions = [0.5, 1.0, 1.25, 1.5, 2.0, 2.5, 3.0];
const [playbackSpeed, setPlaybackSpeed] = useState<number>(1.0);
const cyclePlaybackSpeed = useCallback(() => {
const idx = speedOptions.indexOf(playbackSpeed);
const nextIdx = (idx + 1) % speedOptions.length;
setPlaybackSpeed(speedOptions[nextIdx]);
}, [playbackSpeed, speedOptions]);
// Smart URL processing for KSPlayer compatibility
const processUrlForKsPlayer = (url: string): string => {
try {
@ -1640,6 +1648,13 @@ const KSPlayerCore: React.FC = () => {
}
};
const disableCustomSubtitles = () => {
setUseCustomSubtitles(false);
setCustomSubtitles([]);
// Reset to first available built-in track or disable all tracks
setSelectedTextTrack(ksTextTracks.length > 0 ? 0 : -1);
};
// Ensure native KSPlayer text tracks are disabled when using custom (addon) subtitles
// and re-applied when switching back to built-in tracks. This prevents double-rendering.
useEffect(() => {
@ -2687,6 +2702,8 @@ const KSPlayerCore: React.FC = () => {
onSlidingComplete={handleSlidingComplete}
buffered={buffered}
formatTime={formatTime}
cyclePlaybackSpeed={cyclePlaybackSpeed}
currentPlaybackSpeed={playbackSpeed}
/>
{showPauseOverlay && (
@ -3290,6 +3307,7 @@ const KSPlayerCore: React.FC = () => {
fetchAvailableSubtitles={fetchAvailableSubtitles}
loadWyzieSubtitle={loadWyzieSubtitle}
selectTextTrack={selectTextTrack}
disableCustomSubtitles={disableCustomSubtitles}
increaseSubtitleSize={increaseSubtitleSize}
decreaseSubtitleSize={decreaseSubtitleSize}
toggleSubtitleBackground={toggleSubtitleBackground}

View file

@ -31,6 +31,7 @@ interface SubtitleModalsProps {
fetchAvailableSubtitles: () => void;
loadWyzieSubtitle: (subtitle: WyzieSubtitle) => void;
selectTextTrack: (trackId: number) => void;
disableCustomSubtitles: () => void;
increaseSubtitleSize: () => void;
decreaseSubtitleSize: () => void;
toggleSubtitleBackground: () => void;
@ -79,6 +80,7 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
fetchAvailableSubtitles,
loadWyzieSubtitle,
selectTextTrack,
disableCustomSubtitles,
increaseSubtitleSize,
decreaseSubtitleSize,
toggleSubtitleBackground,
@ -162,6 +164,22 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
loadWyzieSubtitle(subtitle);
};
const getFileNameFromUrl = (url?: string): string | null => {
if (!url || typeof url !== 'string') return null;
try {
// Prefer URL parsing to safely strip query/hash
const u = new URL(url);
const raw = u.pathname.split('/').pop() || '';
const decoded = decodeURIComponent(raw);
return decoded || null;
} catch {
// Fallback for non-standard URLs
const path = url.split('?')[0].split('#')[0];
const raw = path.split('/').pop() || '';
try { return decodeURIComponent(raw) || null; } catch { return raw || null; }
}
};
// Main subtitle menu
const renderSubtitleMenu = () => {
if (!showSubtitleModal) return null;
@ -383,32 +401,61 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
}}>
Addon Subtitles
</Text>
<TouchableOpacity
style={{
backgroundColor: 'rgba(34, 197, 94, 0.15)',
borderRadius: 12,
paddingHorizontal: chipPadH,
paddingVertical: chipPadV-2,
flexDirection: 'row',
alignItems: 'center',
}}
onPress={() => fetchAvailableSubtitles()}
disabled={isLoadingSubtitleList}
>
{isLoadingSubtitleList ? (
<ActivityIndicator size="small" color="#22C55E" />
) : (
<MaterialIcons name="refresh" size={16} color="#22C55E" />
<View style={{ flexDirection: 'row', gap: 8 }}>
{useCustomSubtitles && (
<TouchableOpacity
style={{
backgroundColor: 'rgba(239, 68, 68, 0.15)',
borderRadius: 12,
paddingHorizontal: chipPadH,
paddingVertical: chipPadV-2,
flexDirection: 'row',
alignItems: 'center',
}}
onPress={() => {
disableCustomSubtitles();
setSelectedOnlineSubtitleId(null);
}}
activeOpacity={0.7}
>
<MaterialIcons name="close" size={16} color="#EF4444" />
<Text style={{
color: '#EF4444',
fontSize: isCompact ? 11 : 12,
fontWeight: '600',
marginLeft: 6,
}}>
Disable
</Text>
</TouchableOpacity>
)}
<Text style={{
color: '#22C55E',
fontSize: isCompact ? 11 : 12,
fontWeight: '600',
marginLeft: 6,
}}>
{isLoadingSubtitleList ? 'Searching' : 'Refresh'}
</Text>
</TouchableOpacity>
<TouchableOpacity
style={{
backgroundColor: 'rgba(34, 197, 94, 0.15)',
borderRadius: 12,
paddingHorizontal: chipPadH,
paddingVertical: chipPadV-2,
flexDirection: 'row',
alignItems: 'center',
}}
onPress={() => fetchAvailableSubtitles()}
disabled={isLoadingSubtitleList}
>
{isLoadingSubtitleList ? (
<ActivityIndicator size="small" color="#22C55E" />
) : (
<MaterialIcons name="refresh" size={16} color="#22C55E" />
)}
<Text style={{
color: '#22C55E',
fontSize: isCompact ? 11 : 12,
fontWeight: '600',
marginLeft: 6,
}}>
{isLoadingSubtitleList ? 'Searching' : 'Refresh'}
</Text>
</TouchableOpacity>
</View>
</View>
{(availableSubtitles.length === 0) && !isLoadingSubtitleList ? (
@ -473,10 +520,19 @@ export const SubtitleModals: React.FC<SubtitleModalsProps> = ({
>
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
<View style={{ flex: 1 }}>
<Text style={{ color: '#FFFFFF', fontSize: 15, fontWeight: '500', marginBottom: 4 }}>
<Text style={{ color: '#FFFFFF', fontSize: 15, fontWeight: '500' }}>
{sub.display}
</Text>
<Text style={{ color: 'rgba(255, 255, 255, 0.6)', fontSize: 13 }}>
{(() => {
const filename = getFileNameFromUrl(sub.url);
if (!filename) return null;
return (
<Text style={{ color: 'rgba(255,255,255,0.75)', fontSize: 12, marginTop: 2 }} numberOfLines={1}>
{filename}
</Text>
);
})()}
<Text style={{ color: 'rgba(255, 255, 255, 0.6)', fontSize: 13, marginTop: 2 }}>
{formatLanguage(sub.language)}{sub.source ? ` · ${sub.source}` : ''}
</Text>
</View>

View file

@ -489,6 +489,10 @@ class CatalogService {
for (let i = 0; i < 2; i++) {
try {
// Skip meta requests for non-content ids (e.g., provider slugs)
if (!stremioService.isValidContentId(type, id)) {
break;
}
meta = await stremioService.getMetaDetails(type, id, preferredAddonId);
if (meta) break;
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));
@ -536,6 +540,10 @@ class CatalogService {
for (let i = 0; i < 3; i++) {
try {
// Skip meta requests for non-content ids (e.g., provider slugs)
if (!stremioService.isValidContentId(type, id)) {
break;
}
meta = await stremioService.getMetaDetails(type, id, preferredAddonId);
if (meta) break;
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)));

View file

@ -188,6 +188,21 @@ class StremioService {
this.initializationPromise = this.initialize();
}
// Shared validator for content IDs eligible for metadata requests
public isValidContentId(type: string, id: string | null | undefined): boolean {
const isValidType = type === 'movie' || type === 'series';
const lowerId = (id || '').toLowerCase();
const looksLikeImdb = /^tt\d+/.test(lowerId);
const looksLikeKitsu = lowerId.startsWith('kitsu:') || lowerId === 'kitsu';
const looksLikeSeriesId = lowerId.startsWith('series:');
const isNullishId = !id || lowerId === 'null' || lowerId === 'undefined';
const providerLikeIds = new Set<string>(['moviebox', 'torbox']);
const isProviderSlug = providerLikeIds.has(lowerId);
if (!isValidType || isNullishId || isProviderSlug) return false;
return looksLikeImdb || looksLikeKitsu || looksLikeSeriesId;
}
static getInstance(): StremioService {
if (!StremioService.instance) {
StremioService.instance = new StremioService();