diff --git a/android/app/build.gradle b/android/app/build.gradle
index f9ed83bf..335494dd 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -94,8 +94,8 @@ android {
applicationId 'com.nuvio.app'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
- versionCode 20
- versionName "1.2.5"
+ versionCode 21
+ versionName "1.2.6"
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
}
@@ -117,7 +117,7 @@ android {
def abiVersionCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
applicationVariants.all { variant ->
variant.outputs.each { output ->
- def baseVersionCode = 20 // Current versionCode from defaultConfig
+ def baseVersionCode = 21 // Current versionCode 21 from defaultConfig
def abiName = output.getFilter(com.android.build.OutputFile.ABI)
def versionCode = baseVersionCode * 100 // Base multiplier
diff --git a/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png b/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png
index 29ed05b1..15b512eb 100644
Binary files a/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png and b/android/app/src/main/res/drawable-hdpi/splashscreen_logo.png differ
diff --git a/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png b/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png
index 1e3a3784..8b78e2f9 100644
Binary files a/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png and b/android/app/src/main/res/drawable-mdpi/splashscreen_logo.png differ
diff --git a/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png b/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png
index 6b7ddf9a..528962ab 100644
Binary files a/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png and b/android/app/src/main/res/drawable-xhdpi/splashscreen_logo.png differ
diff --git a/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png b/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png
index b977b3fe..0969e6a3 100644
Binary files a/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png and b/android/app/src/main/res/drawable-xxhdpi/splashscreen_logo.png differ
diff --git a/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png b/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png
index a79fe8b5..7de47821 100644
Binary files a/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png and b/android/app/src/main/res/drawable-xxxhdpi/splashscreen_logo.png differ
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 08f0025b..78602b85 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -3,5 +3,5 @@
contain
false
dark
- 1.2.5
+ 1.2.6
\ No newline at end of file
diff --git a/app.json b/app.json
index 5a069f45..8ddc14f3 100644
--- a/app.json
+++ b/app.json
@@ -2,7 +2,7 @@
"expo": {
"name": "Nuvio",
"slug": "nuvio",
- "version": "1.2.5",
+ "version": "1.2.6",
"orientation": "default",
"backgroundColor": "#020404",
"icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png",
@@ -17,7 +17,7 @@
"ios": {
"supportsTablet": true,
"icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png",
- "buildNumber": "20",
+ "buildNumber": "21",
"infoPlist": {
"NSAppTransportSecurity": {
"NSAllowsArbitraryLoads": true
@@ -48,7 +48,7 @@
"WAKE_LOCK"
],
"package": "com.nuvio.app",
- "versionCode": 20,
+ "versionCode": 21,
"architectures": [
"arm64-v8a",
"armeabi-v7a",
@@ -95,6 +95,6 @@
"fallbackToCacheTimeout": 30000,
"url": "https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest"
},
- "runtimeVersion": "1.2.5"
+ "runtimeVersion": "1.2.6"
}
}
diff --git a/ios/Nuvio.xcodeproj/project.pbxproj b/ios/Nuvio.xcodeproj/project.pbxproj
index b562e963..4bf304a6 100644
--- a/ios/Nuvio.xcodeproj/project.pbxproj
+++ b/ios/Nuvio.xcodeproj/project.pbxproj
@@ -460,7 +460,7 @@
"-lc++",
);
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
- PRODUCT_BUNDLE_IDENTIFIER = "com.nuvio.app";
+ PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
PRODUCT_NAME = "Nuvio";
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
diff --git a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image.png b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image.png
index efcdf22a..8649d708 100644
Binary files a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image.png and b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image.png differ
diff --git a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png
index efcdf22a..8649d708 100644
Binary files a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png and b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@2x.png differ
diff --git a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png
index efcdf22a..8649d708 100644
Binary files a/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png and b/ios/Nuvio/Images.xcassets/SplashScreenLegacy.imageset/image@3x.png differ
diff --git a/ios/Nuvio/Info.plist b/ios/Nuvio/Info.plist
index d701baa9..c4ebe4b2 100644
--- a/ios/Nuvio/Info.plist
+++ b/ios/Nuvio/Info.plist
@@ -19,7 +19,7 @@
CFBundlePackageType
$(PRODUCT_BUNDLE_PACKAGE_TYPE)
CFBundleShortVersionString
- 1.2.5
+ 1.2.6
CFBundleSignature
????
CFBundleURLTypes
@@ -39,7 +39,7 @@
CFBundleVersion
- 20
+ 21
LSMinimumSystemVersion
12.0
LSRequiresIPhoneOS
diff --git a/ios/Nuvio/Supporting/Expo.plist b/ios/Nuvio/Supporting/Expo.plist
index f325c178..c48927a0 100644
--- a/ios/Nuvio/Supporting/Expo.plist
+++ b/ios/Nuvio/Supporting/Expo.plist
@@ -9,7 +9,7 @@
EXUpdatesLaunchWaitMs
30000
EXUpdatesRuntimeVersion
- 1.2.5
+ 1.2.6
EXUpdatesURL
https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest
diff --git a/src/components/home/ContinueWatchingSection.tsx b/src/components/home/ContinueWatchingSection.tsx
index 648a2c92..a9af8ac2 100644
--- a/src/components/home/ContinueWatchingSection.tsx
+++ b/src/components/home/ContinueWatchingSection.tsx
@@ -25,6 +25,7 @@ import * as Haptics from 'expo-haptics';
import { TraktService } from '../../services/traktService';
import { stremioService } from '../../services/stremioService';
import { streamCacheService } from '../../services/streamCacheService';
+import { useSettings } from '../../hooks/useSettings';
import CustomAlert from '../../components/CustomAlert';
// Define interface for continue watching items
@@ -99,6 +100,7 @@ const isEpisodeReleased = (video: any): boolean => {
const ContinueWatchingSection = React.forwardRef((props, ref) => {
const navigation = useNavigation>();
const { currentTheme } = useTheme();
+ const { settings } = useSettings();
const [continueWatchingItems, setContinueWatchingItems] = useState([]);
const [loading, setLoading] = useState(true);
const appState = useRef(AppState.currentState);
@@ -656,6 +658,45 @@ const ContinueWatchingSection = React.forwardRef((props, re
try {
logger.log(`๐ฌ [ContinueWatching] User clicked on: ${item.name} (${item.type}:${item.id})`);
+ // Check if cached streams are enabled in settings
+ if (!settings.useCachedStreams) {
+ logger.log(`๐บ [ContinueWatching] Cached streams disabled, navigating to ${settings.openMetadataScreenWhenCacheDisabled ? 'MetadataScreen' : 'StreamsScreen'} for ${item.name}`);
+
+ // Navigate based on the second setting
+ if (settings.openMetadataScreenWhenCacheDisabled) {
+ // Navigate to MetadataScreen
+ if (item.type === 'series' && item.season && item.episode) {
+ const episodeId = `${item.id}:${item.season}:${item.episode}`;
+ navigation.navigate('Metadata', {
+ id: item.id,
+ type: item.type,
+ episodeId: episodeId
+ });
+ } else {
+ navigation.navigate('Metadata', {
+ id: item.id,
+ type: item.type
+ });
+ }
+ } else {
+ // Navigate to StreamsScreen
+ if (item.type === 'series' && item.season && item.episode) {
+ const episodeId = `${item.id}:${item.season}:${item.episode}`;
+ navigation.navigate('Streams', {
+ id: item.id,
+ type: item.type,
+ episodeId: episodeId
+ });
+ } else {
+ navigation.navigate('Streams', {
+ id: item.id,
+ type: item.type
+ });
+ }
+ }
+ return;
+ }
+
// Check if we have a cached stream for this content
const episodeId = item.type === 'series' && item.season && item.episode
? `${item.id}:${item.season}:${item.episode}`
@@ -730,7 +771,7 @@ const ContinueWatchingSection = React.forwardRef((props, re
});
}
}
- }, [navigation]);
+ }, [navigation, settings.useCachedStreams, settings.openMetadataScreenWhenCacheDisabled]);
// Handle long press to delete (moved before renderContinueWatchingItem)
const handleLongPress = useCallback((item: ContinueWatchingItem) => {
diff --git a/src/components/metadata/HeroSection.tsx b/src/components/metadata/HeroSection.tsx
index 45a84b69..4de55e5e 100644
--- a/src/components/metadata/HeroSection.tsx
+++ b/src/components/metadata/HeroSection.tsx
@@ -335,7 +335,7 @@ const ActionButtons = memo(({
return isWatched ? 'Play' : playButtonText;
}, [isWatched, playButtonText, type, watchProgress, groupedEpisodes]);
- // Determine if we should show buttons in a single row (Play, Save, and one other button = 3 total)
+ // Determine if we should show buttons in a single row (Play, Save, and optionally one other button)
const hasAiChat = aiChatEnabled;
const hasTraktCollection = isAuthenticated;
const hasRatings = type === 'series';
@@ -343,16 +343,20 @@ const ActionButtons = memo(({
// Count additional buttons (excluding Play and Save)
const additionalButtonCount = (hasAiChat ? 1 : 0) + (hasTraktCollection ? 1 : 0) + (hasRatings ? 1 : 0);
- // Show single row when there's exactly 1 additional button (3 total buttons)
- const shouldShowSingleRow = additionalButtonCount === 1;
+ // Show single row when there are 0 additional buttons (2 total: Play + Save) or 1 additional button (3 total)
+ const shouldShowSingleRow = additionalButtonCount <= 1;
return (
{shouldShowSingleRow ? (
- /* Single Row Layout - Play, Save, and one other button (3 total) */
+ /* Single Row Layout - Play, Save, and optionally one other button (2-3 total) */
@@ -370,7 +374,12 @@ const ActionButtons = memo(({
@@ -396,8 +405,8 @@ const ActionButtons = memo(({
- {/* Third Button - AI Chat, Trakt Collection, or Ratings */}
- {hasAiChat && (
+ {/* Third Button - AI Chat, Trakt Collection, or Ratings (only if available) */}
+ {hasAiChat && additionalButtonCount === 1 && (
{
@@ -444,7 +453,7 @@ const ActionButtons = memo(({
)}
- {hasTraktCollection && !hasAiChat && (
+ {hasTraktCollection && !hasAiChat && additionalButtonCount === 1 && (
)}
- {hasRatings && !hasAiChat && !hasTraktCollection && (
+ {hasRatings && !hasAiChat && !hasTraktCollection && additionalButtonCount === 1 && (
;
@@ -1266,6 +1268,21 @@ const InnerNavigator = ({ initialRouteName }: { initialRouteName?: keyof RootSta
},
}}
/>
+
{
+ const navigation = useNavigation>();
+ const { settings, updateSetting } = useSettings();
+ const { currentTheme } = useTheme();
+ const colors = currentTheme.colors;
+ const [showSavedIndicator, setShowSavedIndicator] = useState(false);
+ const fadeAnim = React.useRef(new Animated.Value(0)).current;
+
+ // Prevent iOS entrance flicker by restoring a non-translucent StatusBar
+ useEffect(() => {
+ try {
+ StatusBar.setTranslucent(false);
+ StatusBar.setBackgroundColor(colors.darkBackground);
+ StatusBar.setBarStyle('light-content');
+ if (Platform.OS === 'ios') {
+ StatusBar.setHidden(false);
+ }
+ } catch {}
+ }, [colors.darkBackground]);
+
+ const handleBack = useCallback(() => {
+ navigation.goBack();
+ }, [navigation]);
+
+ // Fade in/out animation for the "Changes saved" indicator
+ useEffect(() => {
+ if (showSavedIndicator) {
+ Animated.sequence([
+ Animated.timing(fadeAnim, {
+ toValue: 1,
+ duration: 300,
+ useNativeDriver: true
+ }),
+ Animated.delay(1000),
+ Animated.timing(fadeAnim, {
+ toValue: 0,
+ duration: 300,
+ useNativeDriver: true
+ })
+ ]).start(() => setShowSavedIndicator(false));
+ }
+ }, [showSavedIndicator, fadeAnim]);
+
+ const handleUpdateSetting = useCallback((
+ key: K,
+ value: typeof settings[K]
+ ) => {
+ updateSetting(key, value);
+ setShowSavedIndicator(true);
+ }, [updateSetting]);
+
+ const CustomSwitch = ({ value, onValueChange }: { value: boolean; onValueChange: (value: boolean) => void }) => (
+
+ );
+
+ const SettingItem = ({
+ title,
+ description,
+ value,
+ onValueChange,
+ isLast = false
+ }: {
+ title: string;
+ description: string;
+ value: boolean;
+ onValueChange: (value: boolean) => void;
+ isLast?: boolean;
+ }) => (
+
+
+
+ {title}
+
+
+ {description}
+
+
+
+
+ );
+
+ const SectionHeader = ({ title }: { title: string }) => (
+
+
+ {title}
+
+
+ );
+
+ return (
+
+
+
+ {/* Header */}
+
+
+
+ Settings
+
+
+ Continue Watching
+
+
+
+ {/* Content */}
+
+
+
+
+ handleUpdateSetting('useCachedStreams', value)}
+ isLast={!settings.useCachedStreams}
+ />
+ {!settings.useCachedStreams && (
+ handleUpdateSetting('openMetadataScreenWhenCacheDisabled', value)}
+ isLast={true}
+ />
+ )}
+
+
+
+
+
+
+ How it works
+
+
+
+ โข Streams are cached for 1 hour after playing{'\n'}
+ โข Cached streams are validated before use{'\n'}
+ โข If cache is invalid or expired, falls back to content screen{'\n'}
+ โข "Use Cached Streams" controls direct player vs screen navigation{'\n'}
+ โข "Open Metadata Screen" appears only when cached streams are disabled
+
+
+
+
+ {/* Saved indicator */}
+
+
+ Changes saved
+
+
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ },
+ header: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingHorizontal: 16,
+ paddingVertical: 12,
+ paddingTop: Platform.OS === 'ios' ? 0 : 12,
+ },
+ backButton: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginRight: 16,
+ },
+ backText: {
+ fontSize: 16,
+ fontWeight: '600',
+ marginLeft: 4,
+ },
+ headerTitle: {
+ fontSize: 20,
+ fontWeight: '700',
+ flex: 1,
+ },
+ content: {
+ flex: 1,
+ },
+ contentContainer: {
+ paddingBottom: 100,
+ },
+ sectionHeader: {
+ paddingHorizontal: 16,
+ paddingVertical: 12,
+ paddingTop: 24,
+ },
+ sectionTitle: {
+ fontSize: 13,
+ fontWeight: '700',
+ letterSpacing: 0.5,
+ textTransform: 'uppercase',
+ },
+ settingsCard: {
+ marginHorizontal: 16,
+ borderRadius: 12,
+ overflow: 'hidden',
+ },
+ settingItem: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingHorizontal: 16,
+ paddingVertical: 16,
+ borderBottomWidth: 1,
+ },
+ settingContent: {
+ flex: 1,
+ marginRight: 16,
+ },
+ settingTitle: {
+ fontSize: 16,
+ fontWeight: '600',
+ marginBottom: 4,
+ },
+ settingDescription: {
+ fontSize: 14,
+ lineHeight: 20,
+ },
+ infoCard: {
+ marginHorizontal: 16,
+ marginTop: 16,
+ padding: 16,
+ borderRadius: 12,
+ },
+ infoHeader: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ marginBottom: 12,
+ },
+ infoTitle: {
+ fontSize: 16,
+ fontWeight: '600',
+ marginLeft: 8,
+ },
+ infoText: {
+ fontSize: 14,
+ lineHeight: 20,
+ },
+ savedIndicator: {
+ position: 'absolute',
+ bottom: 32,
+ left: 16,
+ right: 16,
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ paddingVertical: 12,
+ paddingHorizontal: 20,
+ borderRadius: 8,
+ },
+ savedText: {
+ color: '#FFFFFF',
+ fontSize: 14,
+ fontWeight: '600',
+ marginLeft: 8,
+ },
+});
+
+export default ContinueWatchingSettingsScreen;
diff --git a/src/screens/SettingsScreen.tsx b/src/screens/SettingsScreen.tsx
index 20b3e9dc..179d0e28 100644
--- a/src/screens/SettingsScreen.tsx
+++ b/src/screens/SettingsScreen.tsx
@@ -483,6 +483,14 @@ const SettingsScreen: React.FC = () => {
icon="home"
renderControl={ChevronRight}
onPress={() => navigation.navigate('HomeScreenSettings')}
+ isTablet={isTablet}
+ />
+ navigation.navigate('ContinueWatchingSettings')}
isLast={true}
isTablet={isTablet}
/>
diff --git a/src/screens/StreamsScreen.tsx b/src/screens/StreamsScreen.tsx
index f73284a1..b8d22b60 100644
--- a/src/screens/StreamsScreen.tsx
+++ b/src/screens/StreamsScreen.tsx
@@ -1556,7 +1556,7 @@ export const StreamsScreen = () => {
];
}, [availableProviders, type, episodeStreams, groupedStreams, settings.streamDisplayMode]);
- const sections = useMemo(() => {
+ const sections: Array<{ title: string; addonId: string; data: Stream[]; isEmptyDueToQualityFilter?: boolean } | null> = useMemo(() => {
const streams = metadata?.videos && metadata.videos.length > 1 && selectedEpisode ? episodeStreams : groupedStreams;
const installedAddons = stremioService.getInstalledAddons();
@@ -1648,12 +1648,7 @@ export const StreamsScreen = () => {
const isEmptyDueToQualityFilter = totalOriginalCount > 0 && totalStreamsCount === 0;
if (isEmptyDueToQualityFilter) {
- return [{
- title: 'Available Streams',
- addonId: 'grouped-all',
- data: [{ isEmptyPlaceholder: true } as any],
- isEmptyDueToQualityFilter: true
- }];
+ return []; // Return empty array instead of showing placeholder
}
// Combine streams: Addons first (unsorted), then sorted plugins
@@ -1780,12 +1775,7 @@ export const StreamsScreen = () => {
}
if (isEmptyDueToQualityFilter) {
- return {
- title: addonName,
- addonId,
- data: [{ isEmptyPlaceholder: true } as any],
- isEmptyDueToQualityFilter
- };
+ return null; // Return null to exclude this section completely
}
let processedStreams = filteredStreams;
@@ -1861,7 +1851,7 @@ export const StreamsScreen = () => {
});
return result;
- });
+ }).filter(Boolean); // Filter out null values
}
}, [selectedProvider, type, episodeStreams, groupedStreams, settings.streamDisplayMode, filterStreamsByQuality, addonResponseOrder, settings.streamSortMode, selectedEpisode, metadata]);
@@ -1869,11 +1859,11 @@ export const StreamsScreen = () => {
React.useEffect(() => {
console.log('๐ [StreamsScreen] Final sections:', {
sectionsCount: sections.length,
- sections: sections.map(s => ({
- title: s.title,
- addonId: s.addonId,
- dataCount: s.data?.length || 0,
- isEmptyDueToQualityFilter: s.isEmptyDueToQualityFilter
+ sections: sections.filter(Boolean).map(s => ({
+ title: s!.title,
+ addonId: s!.addonId,
+ dataCount: s!.data?.length || 0,
+ isEmptyDueToQualityFilter: s!.isEmptyDueToQualityFilter
}))
});
}, [sections]);
@@ -2206,15 +2196,15 @@ export const StreamsScreen = () => {
scrollEventThrottle: 16,
})}
>
- {sections.map((section, sectionIndex) => (
-
+ {sections.filter(Boolean).map((section, sectionIndex) => (
+
{/* Section Header */}
- {renderSectionHeader({ section })}
+ {renderSectionHeader({ section: section! })}
{/* Stream Cards using FlatList */}
- {section.data && section.data.length > 0 ? (
+ {section!.data && section!.data.length > 0 ? (
{
if (item && item.url) {
return `${item.url}-${sectionIndex}-${index}`;
@@ -2257,20 +2247,7 @@ export const StreamsScreen = () => {
index,
})}
/>
- ) : (
- // Empty section placeholder
-
-
-
-
- No streams available
-
-
- All streams were filtered by your quality settings
-
-
-
- )}
+ ) : null}
))}
@@ -2805,28 +2782,6 @@ const createStyles = (colors: any) => StyleSheet.create({
fontSize: 11,
fontWeight: '400',
},
- emptySectionContainer: {
- padding: 16,
- alignItems: 'center',
- justifyContent: 'center',
- minHeight: 80,
- },
- emptySectionContent: {
- alignItems: 'center',
- justifyContent: 'center',
- },
- emptySectionTitle: {
- fontSize: 14,
- fontWeight: '600',
- marginTop: 8,
- textAlign: 'center',
- },
- emptySectionSubtitle: {
- fontSize: 12,
- marginTop: 4,
- textAlign: 'center',
- lineHeight: 16,
- },
});
export default memo(StreamsScreen);
diff --git a/src/services/streamCacheService.ts b/src/services/streamCacheService.ts
index d4487117..be658be3 100644
--- a/src/services/streamCacheService.ts
+++ b/src/services/streamCacheService.ts
@@ -92,14 +92,9 @@ class StreamCacheService {
return null;
}
- // Validate that the stream URL is still accessible (quick HEAD request)
- logger.log(`๐ [StreamCache] Validating stream URL: ${cacheEntry.cachedStream.url}`);
- const isUrlValid = await this.validateStreamUrl(cacheEntry.cachedStream.url);
- if (!isUrlValid) {
- logger.log(`โ [StreamCache] Stream URL invalid for ${type}:${id}${episodeId ? `:${episodeId}` : ''}`);
- await this.removeCachedStream(id, type, episodeId);
- return null;
- }
+ // Skip URL validation for now - many CDNs block HEAD requests
+ // This was causing valid streams to be rejected
+ logger.log(`๐ [StreamCache] Skipping URL validation (CDN compatibility)`);
logger.log(`โ
[StreamCache] Using cached stream for ${type}:${id}${episodeId ? `:${episodeId}` : ''}`);
return cacheEntry.cachedStream;
@@ -131,7 +126,7 @@ class StreamCacheService {
const cacheKeys = allKeys.filter(key => key.startsWith(CACHE_KEY_PREFIX));
for (const key of cacheKeys) {
- await storageService.removeItem(key);
+ await AsyncStorage.removeItem(key);
}
logger.log(`๐งน [StreamCache] Cleared ${cacheKeys.length} cached streams`);
@@ -173,8 +168,8 @@ class StreamCacheService {
*/
async getCacheInfo(): Promise<{ totalCached: number; expiredCount: number; validCount: number }> {
try {
- const allKeys = await storageService.getAllKeys();
- const cacheKeys = allKeys.filter(key => key.startsWith(CACHE_KEY_PREFIX));
+ const allKeys = await AsyncStorage.getAllKeys();
+ const cacheKeys = allKeys.filter((key: string) => key.startsWith(CACHE_KEY_PREFIX));
let expiredCount = 0;
let validCount = 0;
@@ -182,7 +177,7 @@ class StreamCacheService {
for (const key of cacheKeys) {
try {
- const cachedData = await storageService.getItem(key);
+ const cachedData = await AsyncStorage.getItem(key);
if (cachedData) {
const cacheEntry: StreamCacheEntry = JSON.parse(cachedData);
if (now > cacheEntry.expiresAt) {
diff --git a/src/utils/version.ts b/src/utils/version.ts
index f29182b7..df5a2ca8 100644
--- a/src/utils/version.ts
+++ b/src/utils/version.ts
@@ -1,7 +1,7 @@
// Single source of truth for the app version displayed in Settings
// Update this when bumping app version
-export const APP_VERSION = '1.2.5';
+export const APP_VERSION = '1.2.6';
export function getDisplayedAppVersion(): string {
return APP_VERSION;