diff --git a/android/app/build.gradle b/android/app/build.gradle index e3d62de..611b0c4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -185,6 +185,9 @@ android { } dependencies { +// @generated begin react-native-google-cast-dependencies - expo prebuild (DO NOT MODIFY) sync-3822a3c86222e7aca74039b551612aab7e75365d + implementation "com.google.android.gms:play-services-cast-framework:${safeExtGet('castFrameworkVersion', '+')}" +// @generated end react-native-google-cast-dependencies // The version of react-native is set by the React Native Gradle Plugin implementation("com.facebook.react:react-android") @@ -214,4 +217,11 @@ dependencies { // Include only FFmpeg decoder AAR to avoid duplicates with Maven Media3 implementation files("libs/lib-decoder-ffmpeg-release.aar") + + // Google Cast Framework + implementation "com.google.android.gms:play-services-cast-framework:${safeExtGet('castFrameworkVersion', '+')}" +} + +def safeExtGet(prop, fallback) { + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 667d1f0..63ac1b6 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -14,6 +14,8 @@ + + diff --git a/android/app/src/main/java/com/nuvio/app/MainActivity.kt b/android/app/src/main/java/com/nuvio/app/MainActivity.kt index bdd6bfe..91a9e1e 100644 --- a/android/app/src/main/java/com/nuvio/app/MainActivity.kt +++ b/android/app/src/main/java/com/nuvio/app/MainActivity.kt @@ -9,6 +9,7 @@ import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnable import com.facebook.react.defaults.DefaultReactActivityDelegate import expo.modules.ReactActivityDelegateWrapper +import com.reactnative.googlecast.api.RNGCCastContext class MainActivity : ReactActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -17,6 +18,12 @@ class MainActivity : ReactActivity() { // This is required for expo-splash-screen. setTheme(R.style.AppTheme); super.onCreate(null) +// @generated begin react-native-google-cast-onCreate - expo prebuild (DO NOT MODIFY) sync-489050f2bf9933a98bbd9d93137016ae14c22faa + RNGCCastContext.getSharedInstance(this) +// @generated end react-native-google-cast-onCreate + + // Initialize Google Cast context + RNGCCastContext.getSharedInstance(this) } /** diff --git a/android/build.gradle b/android/build.gradle index 0554dd1..a07c845 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,6 +1,13 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext { + buildToolsVersion = "34.0.0" + minSdkVersion = 24 + compileSdkVersion = 34 + targetSdkVersion = 34 + castFrameworkVersion = "22.1.0" + } repositories { google() mavenCentral() diff --git a/app.json b/app.json index 86eb780..f242b47 100644 --- a/app.json +++ b/app.json @@ -24,9 +24,11 @@ "NSAllowsArbitraryLoads": true }, "NSBonjourServices": [ - "_http._tcp" + "_http._tcp", + "_googlecast._tcp", + "_CC1AD845._googlecast._tcp" ], - "NSLocalNetworkUsageDescription": "App uses the local network to discover and connect to devices.", + "NSLocalNetworkUsageDescription": "Nuvio uses the local network to discover Cast-enabled devices on your WiFi network and to connect to local media servers.", "NSMicrophoneUsageDescription": "This app does not require microphone access.", "UIBackgroundModes": [ "audio" @@ -59,7 +61,6 @@ ], "jsEngine": "hermes" }, - "extra": { "eas": { "projectId": "909107b8-fe61-45ce-b02f-b02510d306a6" @@ -89,7 +90,14 @@ "supportsBackgroundPlayback": true } ], - "react-native-bottom-tabs" + "react-native-bottom-tabs", + [ + "react-native-google-cast", + { + "receiverAppId": "CC1AD845", + "iosStartDiscoveryAfterFirstTapOnCastButton": true + } + ] ], "updates": { "enabled": true, diff --git a/ios/Nuvio.xcodeproj/project.pbxproj b/ios/Nuvio.xcodeproj/project.pbxproj index 3acee97..f21d58a 100644 --- a/ios/Nuvio.xcodeproj/project.pbxproj +++ b/ios/Nuvio.xcodeproj/project.pbxproj @@ -297,6 +297,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/ExpoLocalization/ExpoLocalization_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/ExpoSystemUI/ExpoSystemUI_privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/KSPlayer/KSPlayer_KSPlayer.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/PromisesObjC/FBLPromises_Privacy.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/RNSVG/RNSVGFilters.bundle", "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf", "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf", @@ -324,6 +325,14 @@ "${PODS_CONFIGURATION_BUILD_DIR}/Sentry/Sentry.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/expo-dev-launcher/EXDevLauncher.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/expo-dev-menu/EXDevMenu.bundle", + "${PODS_ROOT}/google-cast-sdk/GoogleCastSDK-ios-4.8.4_static_xcframework/GoogleCast.xcframework/ios-arm64/GoogleCast.framework/GoogleCastCoreResources.bundle", + "${PODS_ROOT}/google-cast-sdk/GoogleCastSDK-ios-4.8.4_static_xcframework/GoogleCast.xcframework/ios-arm64/GoogleCast.framework/GoogleCastUIResources.bundle", + "${PODS_ROOT}/google-cast-sdk/GoogleCastSDK-ios-4.8.4_static_xcframework/GoogleCast.xcframework/ios-arm64/GoogleCast.framework/GoogleCastOptionalUIResources.bundle", + "${PODS_ROOT}/google-cast-sdk/GoogleCastSDK-ios-4.8.4_static_xcframework/GoogleCast.xcframework/ios-arm64/GoogleCast.framework/MaterialDialogs.bundle", + "${PODS_ROOT}/google-cast-sdk/GoogleCastSDK-ios-4.8.4_static_xcframework/GoogleCast.xcframework/ios-arm64/GoogleCast.framework/GoogleSansBold.bundle", + "${PODS_ROOT}/google-cast-sdk/GoogleCastSDK-ios-4.8.4_static_xcframework/GoogleCast.xcframework/ios-arm64/GoogleCast.framework/GoogleSansMedium.bundle", + "${PODS_ROOT}/google-cast-sdk/GoogleCastSDK-ios-4.8.4_static_xcframework/GoogleCast.xcframework/ios-arm64/GoogleCast.framework/GoogleSansRegular.bundle", + "${PODS_CONFIGURATION_BUILD_DIR}/google-cast-sdk/GoogleCast.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/lottie-ios/LottiePrivacyInfo.bundle", "${PODS_CONFIGURATION_BUILD_DIR}/lottie-react-native/Lottie_React_Native_Privacy.bundle", ); @@ -339,6 +348,7 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoLocalization_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/ExpoSystemUI_privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/KSPlayer_KSPlayer.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/FBLPromises_Privacy.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/RNSVGFilters.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/AntDesign.ttf", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Entypo.ttf", @@ -366,6 +376,14 @@ "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Sentry.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevLauncher.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/EXDevMenu.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleCastCoreResources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleCastUIResources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleCastOptionalUIResources.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/MaterialDialogs.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSansBold.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSansMedium.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleSansRegular.bundle", + "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleCast.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/LottiePrivacyInfo.bundle", "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/Lottie_React_Native_Privacy.bundle", ); diff --git a/ios/Nuvio/AppDelegate.swift b/ios/Nuvio/AppDelegate.swift index a7887e1..4364d81 100644 --- a/ios/Nuvio/AppDelegate.swift +++ b/ios/Nuvio/AppDelegate.swift @@ -1,4 +1,9 @@ import Expo +// @generated begin react-native-google-cast-import - expo prebuild (DO NOT MODIFY) sync-4cd300bca26a1d1fcc83f4baf37b0e62afcc1867 +#if canImport(GoogleCast) && os(iOS) +import GoogleCast +#endif +// @generated end react-native-google-cast-import import React import ReactAppDependencyProvider @@ -13,6 +18,18 @@ public class AppDelegate: ExpoAppDelegate { _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { +// @generated begin react-native-google-cast-didFinishLaunchingWithOptions - expo prebuild (DO NOT MODIFY) sync-3f476aa248b3451597781fe1ea72c7d4127ed7f9 +#if canImport(GoogleCast) && os(iOS) + let receiverAppID = "CC1AD845" + let criteria = GCKDiscoveryCriteria(applicationID: receiverAppID) + let options = GCKCastOptions(discoveryCriteria: criteria) + options.disableDiscoveryAutostart = false + options.startDiscoveryAfterFirstTapOnCastButton = true + options.suspendSessionsWhenBackgrounded = true + GCKCastContext.setSharedInstanceWith(options) + GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = true +#endif +// @generated end react-native-google-cast-didFinishLaunchingWithOptions let delegate = ReactNativeDelegate() let factory = ExpoReactNativeFactory(delegate: delegate) delegate.dependencyProvider = RCTAppDependencyProvider() diff --git a/ios/Nuvio/Info.plist b/ios/Nuvio/Info.plist index 2e04327..b224878 100644 --- a/ios/Nuvio/Info.plist +++ b/ios/Nuvio/Info.plist @@ -54,6 +54,8 @@ NSBonjourServices _http._tcp + _googlecast._tcp + _CC1AD845._googlecast._tcp NSLocalNetworkUsageDescription Allow $(PRODUCT_NAME) to access your local network diff --git a/ios/Nuvio/PrivacyInfo.xcprivacy b/ios/Nuvio/PrivacyInfo.xcprivacy index c6b452e..f184652 100644 --- a/ios/Nuvio/PrivacyInfo.xcprivacy +++ b/ios/Nuvio/PrivacyInfo.xcprivacy @@ -20,6 +20,7 @@ NSPrivacyAccessedAPITypeReasons CA92.1 + C56D.1 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 0d1e4b3..7c12a57 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -229,7 +229,7 @@ PODS: - ExpoModulesCore - ExpoKeepAwake (15.0.7): - ExpoModulesCore - - ExpoLibVlcPlayer (2.2.1): + - ExpoLibVlcPlayer (2.2.3): - ExpoModulesCore - MobileVLCKit (= 3.6.1b1) - ExpoLinearGradient (15.0.7): @@ -328,6 +328,7 @@ PODS: - FFmpegKit/FFmpegKit (= 6.1.0) - FFmpegKit/FFmpegKit (6.1.0): - Libass + - google-cast-sdk (4.8.4) - hermes-engine (0.81.4): - hermes-engine/Pre-built (= 0.81.4) - hermes-engine/Pre-built (0.81.4) @@ -454,6 +455,7 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga + - PromisesObjC (2.4.0) - RCTDeprecation (0.81.4) - RCTRequired (0.81.4) - RCTTypeSafety (0.81.4): @@ -1807,6 +1809,10 @@ PODS: - React - react-native-get-random-values (1.11.0): - React-Core + - react-native-google-cast (4.9.1): + - google-cast-sdk + - PromisesObjC + - React - react-native-netinfo (11.4.1): - React-Core - react-native-safe-area-context (5.6.1): @@ -1878,6 +1884,30 @@ PODS: - ReactCommon/turbomodule/core - ReactNativeDependencies - Yoga + - react-native-skia (2.2.12): + - hermes-engine + - RCTRequired + - RCTTypeSafety + - React + - React-callinvoker + - React-Core + - React-Core-prebuilt + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ReactNativeDependencies + - Yoga - react-native-slider (5.0.1): - hermes-engine - RCTRequired @@ -2792,8 +2822,10 @@ DEPENDENCIES: - react-native-bottom-tabs (from `../node_modules/react-native-bottom-tabs`) - "react-native-device-brightness (from `../node_modules/@adrianso/react-native-device-brightness`)" - react-native-get-random-values (from `../node_modules/react-native-get-random-values`) + - react-native-google-cast (from `../node_modules/react-native-google-cast`) - "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)" - react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`) + - "react-native-skia (from `../node_modules/@shopify/react-native-skia`)" - "react-native-slider (from `../node_modules/@react-native-community/slider`)" - react-native-video (from `../node_modules/react-native-video`) - React-NativeModulesApple (from `../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) @@ -2840,12 +2872,14 @@ DEPENDENCIES: SPEC REPOS: trunk: + - google-cast-sdk - libavif - libdav1d - libwebp - lottie-ios - MMKVCore - MobileVLCKit + - PromisesObjC - ReachabilitySwift - SDWebImage - SDWebImageAVIFCoder @@ -3023,10 +3057,14 @@ EXTERNAL SOURCES: :path: "../node_modules/@adrianso/react-native-device-brightness" react-native-get-random-values: :path: "../node_modules/react-native-get-random-values" + react-native-google-cast: + :path: "../node_modules/react-native-google-cast" react-native-netinfo: :path: "../node_modules/@react-native-community/netinfo" react-native-safe-area-context: :path: "../node_modules/react-native-safe-area-context" + react-native-skia: + :path: "../node_modules/@shopify/react-native-skia" react-native-slider: :path: "../node_modules/@react-native-community/slider" react-native-video: @@ -3152,7 +3190,7 @@ SPEC CHECKSUMS: ExpoGlassEffect: 744bf0c58c26a1b0212dff92856be07b98d01d8c ExpoHaptics: 807476b0c39e9d82b7270349d6487928ce32df84 ExpoKeepAwake: 1a2e820692e933c94a565ec3fbbe38ac31658ffe - ExpoLibVlcPlayer: dce3d0b5847838cd5f8c5f3c3aa1bc55c92e911d + ExpoLibVlcPlayer: 6b4a27f54f5300550227cffcf25cc88ab4f6c7c9 ExpoLinearGradient: a464898cb95153125e3b81894fd479bcb1c7dd27 ExpoLinking: f051f28e50ea9269ff539317c166adec81d9342d ExpoLocalization: b852a5d8ec14c5349c1593eca87896b5b3ebfcca @@ -3167,6 +3205,7 @@ SPEC CHECKSUMS: EXUpdatesInterface: 5adf50cb41e079c861da6d9b4b954c3db9a50734 FBLazyVector: 9e0cd874afd81d9a4d36679daca991b58b260d42 FFmpegKit: 3885085fbbc320745838ee4c8a1f9c5e5953dab2 + google-cast-sdk: 32f65af50d164e3c475e79ad123db3cc26fbcd37 hermes-engine: 35c763d57c9832d0eef764316ca1c4d043581394 ImageColors: 51cd79f7a9d2524b7a681c660b0a50574085563b KSPlayer: f163ac6195f240b6fa5b8225aeb39ec811a70c62 @@ -3180,6 +3219,7 @@ SPEC CHECKSUMS: MobileVLCKit: 2d9c7c373393ae43086aeeff890bf0b1afc15c5c NitroMmkv: 7fe66a61d5acab6516098a64f42af575595e7566 NitroModules: 8c4eca403e6f45f474608d24cd11ab664ed2961c + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 RCTDeprecation: 7487d6dda857ccd4cb3dd6ecfccdc3170e85dcbc RCTRequired: 54128b7df8be566881d48c7234724a78cb9b6157 RCTTypeSafety: d2b07797a79e45d7b19e1cd2f53c79ab419fe217 @@ -3218,8 +3258,10 @@ SPEC CHECKSUMS: react-native-bottom-tabs: e37c9d1565b1ee48c4c0e4b4fa4b804775f82dfa react-native-device-brightness: 1a997350d060c3df9f303b1df84a4f7c5cbeb924 react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba + react-native-google-cast: 7be68a5d0b7eeb95a5924c3ecef8d319ef6c0a44 react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187 react-native-safe-area-context: 42a1b4f8774b577d03b53de7326e3d5757fe9513 + react-native-skia: 05327e7acee05e7c27b68a5da0bc80d65f5d790c react-native-slider: 8c562583722c396a3682f451f0b6e68e351ec3b9 react-native-video: 5d9635903e562e0c5eb47c5fa401f1c807d6e068 React-NativeModulesApple: a9464983ccc0f66f45e93558671f60fc7536e438 diff --git a/package-lock.json b/package-lock.json index e426ea9..6d14046 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "@react-navigation/stack": "^7.2.10", "@sentry/react-native": "~7.3.0", "@shopify/flash-list": "^2.1.0", + "@shopify/react-native-skia": "2.2.12", "@types/lodash": "^4.17.16", "@types/react-native-video": "^5.0.20", "axios": "^1.12.2", @@ -50,7 +51,7 @@ "expo-glass-effect": "~0.1.4", "expo-haptics": "~15.0.7", "expo-intent-launcher": "~13.0.7", - "expo-libvlc-player": "^2.1.7", + "expo-libvlc-player": "^2.2.3", "expo-linear-gradient": "~15.0.7", "expo-localization": "~17.0.7", "expo-notifications": "~0.32.12", @@ -70,6 +71,7 @@ "react-native-bottom-tabs": "^0.12.2", "react-native-gesture-handler": "~2.28.0", "react-native-get-random-values": "^1.11.0", + "react-native-google-cast": "^4.9.1", "react-native-image-colors": "^2.5.0", "react-native-immersive-mode": "^2.0.2", "react-native-markdown-display": "^7.0.2", @@ -3624,6 +3626,32 @@ "react-native": "*" } }, + "node_modules/@shopify/react-native-skia": { + "version": "2.2.12", + "resolved": "https://registry.npmjs.org/@shopify/react-native-skia/-/react-native-skia-2.2.12.tgz", + "integrity": "sha512-P5wZSMPTp00hM0do+awNFtb5aPh5hSpodMGwy7NaxK90AV+SmUu7wZe6NGevzQIwgFa89Epn6xK3j4jKWdQi+A==", + "license": "MIT", + "dependencies": { + "canvaskit-wasm": "0.40.0", + "react-reconciler": "0.31.0" + }, + "bin": { + "setup-skia-web": "scripts/setup-canvaskit.js" + }, + "peerDependencies": { + "react": ">=19.0", + "react-native": ">=0.78", + "react-native-reanimated": ">=3.19.1" + }, + "peerDependenciesMeta": { + "react-native": { + "optional": true + }, + "react-native-reanimated": { + "optional": true + } + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -4430,6 +4458,12 @@ "url": "https://github.com/sponsors/crutchcorn" } }, + "node_modules/@webgpu/types": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@webgpu/types/-/types-0.1.21.tgz", + "integrity": "sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow==", + "license": "BSD-3-Clause" + }, "node_modules/@xmldom/xmldom": { "version": "0.8.11", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz", @@ -5308,6 +5342,15 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvaskit-wasm": { + "version": "0.40.0", + "resolved": "https://registry.npmjs.org/canvaskit-wasm/-/canvaskit-wasm-0.40.0.tgz", + "integrity": "sha512-Od2o+ZmoEw9PBdN/yCGvzfu0WVqlufBPEWNG452wY7E9aT8RBE+ChpZF526doOlg7zumO4iCS+RAeht4P0Gbpw==", + "license": "BSD-3-Clause", + "dependencies": { + "@webgpu/types": "0.1.21" + } + }, "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -6666,9 +6709,9 @@ } }, "node_modules/expo-libvlc-player": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/expo-libvlc-player/-/expo-libvlc-player-2.2.1.tgz", - "integrity": "sha512-RYp5t+2B5v8b5h1vfQfKwRYi8WjMaZQ6gWYFJJ+Phrx3tND+moTDIvG+OvPn6Uxn9TslzlbX1n6ad9jv/cypDw==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/expo-libvlc-player/-/expo-libvlc-player-2.2.3.tgz", + "integrity": "sha512-HuTmcawtYACeYfX+Ft0RbWRFOh/Wu2OswS4HjSICEW909UY/ZvtHOqPRLym47VjA1oulLHGB7SGGvSgvPd2/4A==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -10770,6 +10813,16 @@ "react-native": ">=0.56" } }, + "node_modules/react-native-google-cast": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/react-native-google-cast/-/react-native-google-cast-4.9.1.tgz", + "integrity": "sha512-/HvIKAaWHtG6aTNCxrNrqA2ftWGkfH0M/2iN+28pdGUXpKmueb33mgL1m8D4zzwEODQMcmpfoCsym1IwDvugBQ==", + "license": "MIT", + "peerDependencies": { + "react": "*", + "react-native": "*" + } + }, "node_modules/react-native-image-colors": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/react-native-image-colors/-/react-native-image-colors-2.5.0.tgz", @@ -11373,6 +11426,27 @@ "async-limiter": "~1.0.0" } }, + "node_modules/react-reconciler": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.31.0.tgz", + "integrity": "sha512-7Ob7Z+URmesIsIVRjnLoDGwBEG/tVitidU0nMsqX/eeJaLY89RISO/10ERe0MqmzuKUUB1rmY+h1itMbUHg9BQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.25.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "react": "^19.0.0" + } + }, + "node_modules/react-reconciler/node_modules/scheduler": { + "version": "0.25.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", + "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", + "license": "MIT" + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", diff --git a/package.json b/package.json index 9e8a526..b828800 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "@react-navigation/stack": "^7.2.10", "@sentry/react-native": "~7.3.0", "@shopify/flash-list": "^2.1.0", + "@shopify/react-native-skia": "2.2.12", "@types/lodash": "^4.17.16", "@types/react-native-video": "^5.0.20", "axios": "^1.12.2", @@ -50,7 +51,7 @@ "expo-glass-effect": "~0.1.4", "expo-haptics": "~15.0.7", "expo-intent-launcher": "~13.0.7", - "expo-libvlc-player": "^2.1.7", + "expo-libvlc-player": "^2.2.3", "expo-linear-gradient": "~15.0.7", "expo-localization": "~17.0.7", "expo-notifications": "~0.32.12", @@ -70,6 +71,7 @@ "react-native-bottom-tabs": "^0.12.2", "react-native-gesture-handler": "~2.28.0", "react-native-get-random-values": "^1.11.0", + "react-native-google-cast": "^4.9.1", "react-native-image-colors": "^2.5.0", "react-native-immersive-mode": "^2.0.2", "react-native-markdown-display": "^7.0.2", diff --git a/src/components/home/ContentItem.tsx b/src/components/home/ContentItem.tsx index 05fa634..7aa251a 100644 --- a/src/components/home/ContentItem.tsx +++ b/src/components/home/ContentItem.tsx @@ -145,7 +145,10 @@ const ContentItem = ({ item, onPress, shouldLoadImage: shouldLoadImageProp, defe }, []); const handlePress = useCallback(() => { - onPress(item.id, item.type); + // Validate ID before pressing to prevent errors with NaN/undefined IDs + if (item.id && item.id !== 'NaN' && item.id !== 'undefined') { + onPress(item.id, item.type); + } }, [item.id, item.type, onPress]); const handleOptionSelect = useCallback(async (option: string) => { diff --git a/src/components/player/AndroidVideoPlayer.tsx b/src/components/player/AndroidVideoPlayer.tsx index f7ff250..6145558 100644 --- a/src/components/player/AndroidVideoPlayer.tsx +++ b/src/components/player/AndroidVideoPlayer.tsx @@ -3105,9 +3105,6 @@ const AndroidVideoPlayer: React.FC = () => { logo={metadata?.logo} backgroundFadeAnim={backgroundFadeAnim} backdropImageOpacityAnim={backdropImageOpacityAnim} - logoScaleAnim={logoScaleAnim} - logoOpacityAnim={logoOpacityAnim} - pulseAnim={pulseAnim} onClose={handleClose} width={screenDimensions.width} height={screenDimensions.height} diff --git a/src/components/player/KSPlayerCore.tsx b/src/components/player/KSPlayerCore.tsx index 94d8ef2..0fbc2cc 100644 --- a/src/components/player/KSPlayerCore.tsx +++ b/src/components/player/KSPlayerCore.tsx @@ -2486,9 +2486,6 @@ const KSPlayerCore: React.FC = () => { logo={metadata?.logo} backgroundFadeAnim={backgroundFadeAnim} backdropImageOpacityAnim={backdropImageOpacityAnim} - logoScaleAnim={logoScaleAnim} - logoOpacityAnim={logoOpacityAnim} - pulseAnim={pulseAnim} onClose={handleClose} width={shouldUseFullscreen ? effectiveDimensions.width : screenDimensions.width} height={shouldUseFullscreen ? effectiveDimensions.height : screenDimensions.height} diff --git a/src/components/player/modals/LoadingOverlay.tsx b/src/components/player/modals/LoadingOverlay.tsx index 5f9075c..af49eaf 100644 --- a/src/components/player/modals/LoadingOverlay.tsx +++ b/src/components/player/modals/LoadingOverlay.tsx @@ -1,8 +1,17 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { View, TouchableOpacity, Animated, ActivityIndicator, StyleSheet, Image } from 'react-native'; import { MaterialIcons } from '@expo/vector-icons'; import { LinearGradient } from 'expo-linear-gradient'; import FastImage from '@d11/react-native-fast-image'; +import Reanimated, { + useSharedValue, + useAnimatedStyle, + withTiming, + withRepeat, + withSequence, + Easing, + withDelay +} from 'react-native-reanimated'; import { styles } from '../utils/playerStyles'; interface LoadingOverlayProps { @@ -12,9 +21,6 @@ interface LoadingOverlayProps { logo: string | null | undefined; backgroundFadeAnim: Animated.Value; backdropImageOpacityAnim: Animated.Value; - logoScaleAnim: Animated.Value; - logoOpacityAnim: Animated.Value; - pulseAnim: Animated.Value; onClose: () => void; width: number | string; height: number | string; @@ -28,14 +34,54 @@ const LoadingOverlay: React.FC = ({ logo, backgroundFadeAnim, backdropImageOpacityAnim, - logoScaleAnim, - logoOpacityAnim, - pulseAnim, onClose, width, height, useFastImage = false, }) => { + const logoOpacity = useSharedValue(0); + const logoScale = useSharedValue(1); + + useEffect(() => { + if (visible && hasLogo && logo) { + // Reset + logoOpacity.value = 0; + logoScale.value = 1; + + // Start animations after 1 second delay + logoOpacity.value = withDelay( + 1000, + withTiming(1, { + duration: 800, + easing: Easing.out(Easing.cubic), + }) + ); + + logoScale.value = withDelay( + 1000, + withRepeat( + withSequence( + withTiming(1.04, { + duration: 2000, + easing: Easing.inOut(Easing.ease), + }), + withTiming(1, { + duration: 2000, + easing: Easing.inOut(Easing.ease), + }) + ), + -1, + false + ) + ); + } + }, [visible, hasLogo, logo]); + + const logoAnimatedStyle = useAnimatedStyle(() => ({ + opacity: logoOpacity.value, + transform: [{ scale: logoScale.value }], + })); + if (!visible) return null; return ( @@ -93,13 +139,12 @@ const LoadingOverlay: React.FC = ({ {hasLogo && logo ? ( - + = ({ }} resizeMode={FastImage.resizeMode.contain} /> - + ) : ( )} diff --git a/src/components/player/modals/SourcesModal.tsx b/src/components/player/modals/SourcesModal.tsx index 6d99341..95f3544 100644 --- a/src/components/player/modals/SourcesModal.tsx +++ b/src/components/player/modals/SourcesModal.tsx @@ -81,7 +81,7 @@ export const SourcesModal: React.FC = ({ const sortedProviders = Object.entries(availableStreams); const handleStreamSelect = (stream: Stream) => { - if (stream.url !== currentStreamUrl && (!isChangingSource || isChangingSource === false)) { + if (stream.url !== currentStreamUrl && !isChangingSource) { onSelectStream(stream); } }; diff --git a/src/hooks/useFeaturedContent.ts b/src/hooks/useFeaturedContent.ts index 0439221..efef66a 100644 --- a/src/hooks/useFeaturedContent.ts +++ b/src/hooks/useFeaturedContent.ts @@ -494,7 +494,8 @@ export function useFeaturedContent() { } } else { // For carousel items - check if saved and toggle - const isItemSaved = await catalogService.isInLibrary(contentToUse.type, contentToUse.id); + const libraryItems = await catalogService.getLibraryItems(); + const isItemSaved = libraryItems.some(libItem => libItem.id === contentToUse.id && libItem.type === contentToUse.type); await Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); if (isItemSaved) { @@ -514,7 +515,8 @@ export function useFeaturedContent() { const isItemSaved = useCallback(async (item: StreamingContent) => { try { - return await catalogService.isInLibrary(item.type, item.id); + const items = await catalogService.getLibraryItems(); + return items.some(libItem => libItem.id === item.id && libItem.type === item.type); } catch (error) { logger.error('Error checking if item is saved:', error); return false;