Compare commits
433 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bf3a344f3 | ||
|
|
14e8e90ee3 | ||
|
|
d52c202518 | ||
|
|
c728f4ea8d | ||
|
|
c20c2713d0 | ||
|
|
d398c73214 | ||
|
|
9e6b455323 | ||
|
|
5a2271c64e | ||
|
|
eb6fcf639f | ||
|
|
a85cc93026 | ||
|
|
56fd18a8e9 | ||
|
|
82d0ebb714 | ||
|
|
df5772d40b | ||
|
|
3030d5961d | ||
|
|
6974768457 | ||
|
|
d31cd2fcdc | ||
|
|
b916bdbcca | ||
|
|
67d53cf5ce | ||
|
|
175d6a173e | ||
|
|
b7140e15a5 | ||
|
|
76310dae1b | ||
|
|
01a041aebf | ||
|
|
031c0c8772 | ||
|
|
fd1e303403 | ||
|
|
45b63cb33f | ||
|
|
c9dfecb68c | ||
|
|
aa6406eae0 | ||
|
|
26e4c6db88 | ||
|
|
2439bd1cd8 | ||
|
|
1fdcdd02bf | ||
|
|
bb94a49662 | ||
|
|
2ebec55bbc | ||
|
|
5fe23c7ad1 | ||
|
|
b6a5c108de | ||
|
|
83ce7cf44d | ||
|
|
28632d192f | ||
|
|
2a265bf716 | ||
|
|
b06800860c | ||
|
|
75702d823f | ||
|
|
f865b737e6 | ||
|
|
2169354f0d | ||
|
|
8dc1217c36 | ||
|
|
0a1511f09f | ||
|
|
73030f150a | ||
|
|
a1f4702647 | ||
|
|
2ddfe63fa4 | ||
|
|
79ffe92864 | ||
|
|
e5178c9414 | ||
|
|
f779febc32 | ||
|
|
5afd3d6b08 | ||
|
|
6005574019 | ||
|
|
645dcecaca | ||
|
|
1686138499 | ||
|
|
cd1ed27f1e | ||
|
|
3b210b06d5 | ||
|
|
0f9c1b03a5 | ||
|
|
217244c367 | ||
|
|
852868cf89 | ||
|
|
a52a2ccc31 | ||
|
|
210ae6b0ee | ||
|
|
c6e55429e4 | ||
|
|
07b27dd485 | ||
|
|
5166dbd446 | ||
|
|
0722923a78 | ||
|
|
a85698b009 | ||
|
|
9b2b619121 | ||
|
|
ac097f6513 | ||
|
|
a383289457 | ||
|
|
e76b44cff1 | ||
|
|
0f9f6bbe5d | ||
|
|
c48670fa74 | ||
|
|
c530619039 | ||
|
|
5e221e7e97 | ||
|
|
65909a5f2e | ||
|
|
bbdd4c0504 | ||
|
|
9924d26ff6 | ||
|
|
b10aab6057 | ||
|
|
ccad48fbb4 | ||
|
|
91e9549ec6 | ||
|
|
066bf6f15d | ||
|
|
56df30a4da | ||
|
|
27ce25f5c5 | ||
|
|
334d0b1863 | ||
|
|
437645d5fd | ||
|
|
280536e93c | ||
|
|
611b37c847 | ||
|
|
5e3198c9c6 | ||
|
|
6ef047db3c | ||
|
|
cdab715463 | ||
|
|
96ac361c8e | ||
|
|
ed4950cd1f | ||
|
|
afddf4bf2d | ||
|
|
9c37ad8b94 | ||
|
|
9877f513e2 | ||
|
|
f4b5082827 | ||
|
|
1627928fb2 | ||
|
|
6ff5aa9e02 | ||
|
|
20601cd7ba | ||
|
|
2d6b4afa2d | ||
|
|
4ce14ec4cc | ||
|
|
0f1d736716 | ||
|
|
edeb6ebe3c | ||
|
|
ab7f008bbb | ||
|
|
1e60af1ffb | ||
|
|
4dd1fca0a7 | ||
|
|
81b97da75e | ||
|
|
6a7d6a1458 | ||
|
|
2835ede747 | ||
|
|
59f77ac831 | ||
|
|
3e63efc178 | ||
|
|
4aa22cc1c3 | ||
|
|
4fdda9a184 | ||
|
|
5bd9f41104 | ||
|
|
486ea63a8a | ||
|
|
0919a40c75 | ||
|
|
3de2fb4809 | ||
|
|
3d5a9ebf42 | ||
|
|
be3e111e63 | ||
|
|
8a0bed7238 | ||
|
|
d2556b6c36 | ||
|
|
506ca4f95c | ||
|
|
5b2c57d5c7 | ||
|
|
7c2b1ac73d | ||
|
|
a55669d16f | ||
|
|
656062bc25 | ||
|
|
b42401a909 | ||
|
|
2c6c110265 | ||
|
|
e7b3458f34 | ||
|
|
e0ad949141 | ||
|
|
28d27128d1 | ||
|
|
ebbe715581 | ||
|
|
af138944b5 | ||
|
|
4603d1dc2a | ||
|
|
e323906083 | ||
|
|
6cb115ed74 | ||
|
|
0149068126 | ||
|
|
7894258a26 | ||
|
|
775242255a | ||
|
|
faa4f341e6 | ||
|
|
a079649563 | ||
|
|
63359532a3 | ||
|
|
5d42a828d2 | ||
|
|
2da03d4931 | ||
|
|
4235e327fc | ||
|
|
0d3454cd24 | ||
|
|
5850650713 | ||
|
|
47f3cb4b71 | ||
|
|
2b802079a0 | ||
|
|
0d416f724c | ||
|
|
a6a0a8b1b1 | ||
|
|
dd1a3ed496 | ||
|
|
9f3831e733 | ||
|
|
fd1107a5a3 | ||
|
|
a9a78d5565 | ||
|
|
a794e27235 | ||
|
|
9bb9d6548a | ||
|
|
3625ca9edc | ||
|
|
3293b57537 | ||
|
|
867458b52f | ||
|
|
8daca53be3 | ||
|
|
4174fd2add | ||
|
|
d3041f99cc | ||
|
|
6acfa2971b | ||
|
|
7271ed39a0 | ||
|
|
639e84bb88 | ||
|
|
36ad45cfbc | ||
|
|
c0540db282 | ||
|
|
7d6008b0a9 | ||
|
|
af96d30122 | ||
|
|
bf75cca438 | ||
|
|
3285ecbe04 | ||
|
|
6906ad99b7 | ||
|
|
f3c5289013 | ||
|
|
be9473adf7 | ||
|
|
ec28f73df9 | ||
|
|
d19f4713a2 | ||
|
|
2e79c34068 | ||
|
|
c7e5696974 | ||
|
|
154d034e8f | ||
|
|
916eeaef4c | ||
|
|
ad18e30de7 | ||
|
|
d4917fefc9 | ||
|
|
67b16c27f3 | ||
|
|
f15fe80d3a | ||
|
|
fbb44b14dd | ||
|
|
103bcdd4cc | ||
|
|
5e04ebca18 | ||
|
|
b00812333a | ||
|
|
9012bfdea9 | ||
|
|
9e5877173e | ||
|
|
b165c3223d | ||
|
|
79213ad573 | ||
|
|
7df42903c6 | ||
|
|
42d4290acd | ||
|
|
8c449215a6 | ||
|
|
183d30c720 | ||
|
|
53a572ecac | ||
|
|
4173786b12 | ||
|
|
44abb9f635 | ||
|
|
fd6e29a8ec | ||
|
|
832e5368be | ||
|
|
e543d72879 | ||
|
|
b4b8648e25 | ||
|
|
ff2bca18a5 | ||
|
|
cf5cc2d8f9 | ||
|
|
a30fa604d7 | ||
|
|
18e90397d9 | ||
|
|
97f558faf4 | ||
|
|
69dacb0ede | ||
|
|
95e7d44035 | ||
|
|
d39a485d24 | ||
|
|
4f0a673f87 | ||
|
|
8618dcda74 | ||
|
|
cc8be32cac | ||
|
|
f65eb8fe7e | ||
|
|
7c1a69d136 | ||
|
|
7fdd4c4383 | ||
|
|
43cd14a025 | ||
|
|
5662ee908d | ||
|
|
de7fcb4d4d | ||
|
|
f6dea03c05 | ||
|
|
6e2ddd2dda | ||
|
|
2d97cad1dc | ||
|
|
1d9a3b645b | ||
|
|
a89c7f5c5c | ||
|
|
91af3a4021 | ||
|
|
7a5ecd3009 | ||
|
|
aed4fed56f | ||
|
|
7885df341e | ||
|
|
2921b3eb1f | ||
|
|
579b0a77b3 | ||
|
|
063f8a8c1b | ||
|
|
985d01d5a9 | ||
|
|
9f461f7091 | ||
|
|
0b4db84f30 | ||
|
|
7e7804b6d4 | ||
|
|
eee6f81fca | ||
|
|
9375fab06c | ||
|
|
d2987ce0cc | ||
|
|
a61c1e6456 | ||
|
|
0a1e008d5f | ||
|
|
7f9e9ff5db | ||
|
|
39498f78b7 | ||
|
|
8588aca948 | ||
|
|
3f63461d45 | ||
|
|
f5e9a3977b | ||
|
|
aa62cc78f0 | ||
|
|
d6bb2869c5 | ||
|
|
74764bbbe0 | ||
|
|
441e8d8656 | ||
|
|
52dd075b6a | ||
|
|
1821bf1230 | ||
|
|
ab720ddae7 | ||
|
|
b4cecee191 | ||
|
|
614597d1bd | ||
|
|
0165b1f987 | ||
|
|
8b3a1b57bf | ||
|
|
c421e46724 | ||
|
|
1f3b9413cd | ||
|
|
6855a89792 | ||
|
|
811701ebae | ||
|
|
18fa11fd88 | ||
|
|
767fd2ff87 | ||
|
|
68c5b09e3a | ||
|
|
d2f9b7586a | ||
|
|
4753b2a57a | ||
|
|
5119822c31 | ||
|
|
09d0483ee3 | ||
|
|
034fd8a9aa | ||
|
|
b3ec4e0c01 | ||
|
|
f0f71afd67 | ||
|
|
3cea291901 | ||
|
|
9504d48607 | ||
|
|
19438ff1d5 | ||
|
|
967b90b98e | ||
|
|
0d6d69e0a8 | ||
|
|
a50f8de913 | ||
|
|
32df7d79ad | ||
|
|
759215da8c | ||
|
|
894469ae0e | ||
|
|
79b0cfc990 | ||
|
|
a8dfe30546 | ||
|
|
dda34b6982 | ||
|
|
599e31c11f | ||
|
|
ffc4200b96 | ||
|
|
d23c48cc0c | ||
|
|
aaf0b498f8 | ||
|
|
bda3732a83 | ||
|
|
b2a9708856 | ||
|
|
6e3f79a231 | ||
|
|
395b01d22b | ||
|
|
d9aaa045fd | ||
|
|
d6f2cb7592 | ||
|
|
5804959ddf | ||
|
|
af572f8b29 | ||
|
|
104d0f4516 | ||
|
|
b061c1f756 | ||
|
|
89f99dba85 | ||
|
|
ea25526ded | ||
|
|
2d5b1263b5 | ||
|
|
371aacd734 | ||
|
|
af1b0b03d8 | ||
|
|
baee619d73 | ||
|
|
b3f5ba4260 | ||
|
|
7ec9c3591e | ||
|
|
407514301b | ||
|
|
35abf985a9 | ||
|
|
374bc8e2d3 | ||
|
|
48300bf767 | ||
|
|
78553d8323 | ||
|
|
8c0b47975c | ||
|
|
601a4a0f1d | ||
|
|
59cb902658 | ||
|
|
d876b7618c | ||
|
|
60cdf9fe86 | ||
|
|
619333c328 | ||
|
|
80d75a528f | ||
|
|
4ac45a041a | ||
|
|
51064a65b2 | ||
|
|
2e61617f83 | ||
|
|
dbbee06a55 | ||
|
|
181cdaecb5 | ||
|
|
c3fbe31fd4 | ||
|
|
f05366ae45 | ||
|
|
1c53e65b26 | ||
|
|
c01528b309 | ||
|
|
53dd480231 | ||
|
|
3c35b99759 | ||
|
|
8a34bf6678 | ||
|
|
8b5a707daa | ||
|
|
bf22e559c5 | ||
|
|
9e7543df02 | ||
|
|
52065a1462 | ||
|
|
01953af578 | ||
|
|
e160bf6fe0 | ||
|
|
ff9d2c52be | ||
|
|
3801e80dd9 | ||
|
|
1307a71b4c | ||
|
|
2c9072299e | ||
|
|
6bdc998496 | ||
|
|
1b990aa6ec | ||
|
|
057c709b41 | ||
|
|
d457db5053 | ||
|
|
22d8fe311a | ||
|
|
e9796ee966 | ||
|
|
6c201e285a | ||
|
|
6c326e1378 | ||
|
|
725c8aa9b7 | ||
|
|
7a2f340c22 | ||
|
|
c4af2e8eea | ||
|
|
63a7051b86 | ||
|
|
9f75bfdeed | ||
|
|
083da01463 | ||
|
|
14980f2bfd | ||
|
|
6c08b459bf | ||
|
|
6d1ba14ab4 | ||
|
|
bbf035ebae | ||
|
|
03aa45a0b0 | ||
|
|
8d918cdf5e | ||
|
|
2494d45e8f | ||
|
|
011f480fc1 | ||
|
|
771765f32b | ||
|
|
e9a331dbd5 | ||
|
|
348cbf86d8 | ||
|
|
ecaaaa66ed | ||
|
|
0ab85ec870 | ||
|
|
a27ee4ac56 | ||
|
|
56234daf82 | ||
|
|
668099b542 | ||
|
|
a4725c24bc | ||
|
|
1a1fdb6fdf | ||
|
|
0bb0df2a60 | ||
|
|
5163031869 | ||
|
|
13523fbbe4 | ||
|
|
e9b38db8b4 | ||
|
|
eeed1c7492 | ||
|
|
655ddbeb42 | ||
|
|
69ac2d64ad | ||
|
|
34f110f16a | ||
|
|
09e35d5a0c | ||
|
|
41081118ef | ||
|
|
1ca4e275de | ||
|
|
c9c4a80387 | ||
|
|
3d0ac0f9f4 | ||
|
|
ffaac958c4 | ||
|
|
f2f503b9ab | ||
|
|
117306bd66 | ||
|
|
76b28d2a2c | ||
|
|
41ba4ac12c | ||
|
|
54f85e9689 | ||
|
|
aabf3e18ef | ||
|
|
daafdeedc2 | ||
|
|
426e936740 | ||
|
|
aa0c338c05 | ||
|
|
ea7f6bf7d7 | ||
|
|
2c524020af | ||
|
|
6331c43f68 | ||
|
|
a6168a7d64 | ||
|
|
1c083f836b | ||
|
|
1756c28ed9 | ||
|
|
91a42e6e29 | ||
|
|
0e14d257ad | ||
|
|
ba72d8bca2 | ||
|
|
19420e901e | ||
|
|
7aa66aff74 | ||
|
|
5c3c5717ab | ||
|
|
a5a66a5e8c | ||
|
|
b17b492741 | ||
|
|
5fe1db24c1 | ||
|
|
2ba7c3c057 | ||
|
|
8e34fbb124 | ||
|
|
d971d9f71f | ||
|
|
e033352752 | ||
|
|
96f79f7c72 | ||
|
|
80cdc98902 | ||
|
|
d6af98c6d7 | ||
|
|
5a3ebe6c01 | ||
|
|
84b8cb7817 | ||
|
|
33d13c74d3 | ||
|
|
6fa53151fb | ||
|
|
4261891a35 | ||
|
|
a0a138081d | ||
|
|
f13266b1fc | ||
|
|
1287d7f6a0 | ||
|
|
e437a23029 | ||
|
|
b71314b8f6 | ||
|
|
d9fcc085a6 | ||
|
|
dd542091e1 | ||
|
|
85cb950fa8 | ||
|
|
09e25738a8 | ||
|
|
9452b01e9c | ||
|
|
f24d889ee7 | ||
|
|
d8bf8dd2ae |
11
.env.example
|
|
@ -3,6 +3,12 @@
|
|||
EXPO_PUBLIC_SUPABASE_URL=your_supabase_project_url
|
||||
EXPO_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
|
||||
|
||||
# Remote cache for TMDB (optional)
|
||||
# Set to true to use local/remote cache server, and provide URL
|
||||
EXPO_PUBLIC_USE_REMOTE_CACHE=false
|
||||
EXPO_PUBLIC_CACHE_SERVER_URL=http://localhost:5173
|
||||
EXPO_PUBLIC_DISABLE_LOCAL_CACHE=false
|
||||
|
||||
# MovieBox (MoviesMod) Keys
|
||||
EXPO_PUBLIC_MOVIEBOX_PRIMARY_KEY=your_moviebox_primary_key
|
||||
EXPO_PUBLIC_MOVIEBOX_TMDB_API_KEY=your_tmdb_api_key_for_moviebox
|
||||
|
|
@ -11,3 +17,8 @@ EXPO_PUBLIC_MOVIEBOX_TMDB_API_KEY=your_tmdb_api_key_for_moviebox
|
|||
EXPO_PUBLIC_TRAKT_CLIENT_ID=your_trakt_client_id
|
||||
EXPO_PUBLIC_TRAKT_CLIENT_SECRET=your_trakt_client_secret
|
||||
EXPO_PUBLIC_TRAKT_REDIRECT_URI=stremioexpo://auth/trakt
|
||||
|
||||
# Skip Intro API (IntroDB)
|
||||
# Fetches intro timestamps for TV shows to enable skip intro functionality
|
||||
EXPO_PUBLIC_INTRODB_API_URL=https://api.introdb.app
|
||||
EXPO_PUBLIC_DISCORD_USER_API=
|
||||
28
.gitignore
vendored
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
# dependencies
|
||||
node_modules/
|
||||
# Un-ignore specific react-native-video source files we patch
|
||||
!node_modules/react-native-video/android/src/main/java/com/brentvatne/common/api/SubtitleStyle.kt
|
||||
!node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.kt
|
||||
!node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
|
||||
|
||||
# Expo
|
||||
|
|
@ -31,8 +34,12 @@ yarn-error.*
|
|||
*.pem
|
||||
|
||||
# local env files
|
||||
.env
|
||||
|
||||
.env*.local
|
||||
.env
|
||||
# Sentry
|
||||
ios/sentry.properties
|
||||
android/sentry.properties
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
|
|
@ -47,6 +54,7 @@ android/build/
|
|||
android/.gradle/
|
||||
android/app/libs/*.aar
|
||||
!android/app/libs/lib-decoder-ffmpeg-release.aar
|
||||
!android/app/libs/libmpv-release.aar
|
||||
HEATING_OPTIMIZATIONS.md
|
||||
# sliderreadme.md
|
||||
.cursor/mcp.json
|
||||
|
|
@ -66,7 +74,7 @@ sliderreadme.md
|
|||
bottomsheet.md
|
||||
fastimage.md
|
||||
|
||||
# Backup directories
|
||||
## Backup directories
|
||||
backup_sdk54_upgrade/
|
||||
SDK54_UPGRADE_SUMMARY.md
|
||||
SDK54_UPGRADE_SUMMARY.md
|
||||
|
|
@ -74,3 +82,19 @@ build-and-publish-app-releases.sh
|
|||
bottomnav.md
|
||||
/TrailerServices
|
||||
mmkv.md
|
||||
fix-android-scroll-lag-summary.md
|
||||
server/cache-server
|
||||
server/campaign-manager
|
||||
carousal.md
|
||||
node_modules
|
||||
expofs.md
|
||||
ios/sentry.properties
|
||||
android/sentry.properties
|
||||
Stremio addons refer
|
||||
trakt-docs
|
||||
trakt-docss
|
||||
|
||||
# Removed submodules (kept locally)
|
||||
libmpv-android/
|
||||
mpv-android/
|
||||
mpvKt/
|
||||
79
App.tsx
|
|
@ -13,15 +13,17 @@ import {
|
|||
Platform,
|
||||
LogBox
|
||||
} from 'react-native';
|
||||
import './src/i18n'; // Initialize i18n
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { Provider as PaperProvider } from 'react-native-paper';
|
||||
import { enableScreens } from 'react-native-screens';
|
||||
import AppNavigator, {
|
||||
import { enableScreens, enableFreeze } from 'react-native-screens';
|
||||
import AppNavigator, {
|
||||
CustomNavigationDarkTheme,
|
||||
CustomDarkTheme
|
||||
} from './src/navigation/AppNavigator';
|
||||
import { BottomSheetModalProvider } from '@gorhom/bottom-sheet';
|
||||
import 'react-native-reanimated';
|
||||
import { CatalogProvider } from './src/contexts/CatalogContext';
|
||||
import { GenreProvider } from './src/contexts/GenreContext';
|
||||
|
|
@ -41,6 +43,7 @@ import { aiService } from './src/services/aiService';
|
|||
import { AccountProvider, useAccount } from './src/contexts/AccountContext';
|
||||
import { ToastProvider } from './src/contexts/ToastContext';
|
||||
import { mmkvStorage } from './src/services/mmkvStorage';
|
||||
import { CampaignManager } from './src/components/promotions/CampaignManager';
|
||||
|
||||
Sentry.init({
|
||||
dsn: 'https://1a58bf436454d346e5852b7bfd3c95e8@o4509536317276160.ingest.de.sentry.io/4509536317734992',
|
||||
|
|
@ -71,6 +74,8 @@ LogBox.ignoreLogs([
|
|||
|
||||
// This fixes many navigation layout issues by using native screen containers
|
||||
enableScreens(true);
|
||||
// Freeze non-focused screens to stop background re-renders
|
||||
enableFreeze(true);
|
||||
|
||||
// Inner app component that uses the theme context
|
||||
const ThemedApp = () => {
|
||||
|
|
@ -80,12 +85,12 @@ const ThemedApp = () => {
|
|||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const engine = (global as any).HermesInternal ? 'Hermes' : 'JSC';
|
||||
console.log('JS Engine:', engine);
|
||||
} catch {}
|
||||
} catch { }
|
||||
}, []);
|
||||
const { currentTheme } = useTheme();
|
||||
const [isAppReady, setIsAppReady] = useState(false);
|
||||
const [hasCompletedOnboarding, setHasCompletedOnboarding] = useState<boolean | null>(null);
|
||||
|
||||
|
||||
// Update popup functionality
|
||||
const {
|
||||
showUpdatePopup,
|
||||
|
|
@ -98,7 +103,7 @@ const ThemedApp = () => {
|
|||
|
||||
// GitHub major/minor release overlay
|
||||
const githubUpdate = useGithubMajorUpdate();
|
||||
|
||||
|
||||
// Check onboarding status and initialize services
|
||||
useEffect(() => {
|
||||
const initializeApp = async () => {
|
||||
|
|
@ -106,28 +111,28 @@ const ThemedApp = () => {
|
|||
// Check onboarding status
|
||||
const onboardingCompleted = await mmkvStorage.getItem('hasCompletedOnboarding');
|
||||
setHasCompletedOnboarding(onboardingCompleted === 'true');
|
||||
|
||||
|
||||
// Initialize update service
|
||||
await UpdateService.initialize();
|
||||
|
||||
|
||||
// Initialize memory monitoring service to prevent OutOfMemoryError
|
||||
memoryMonitorService; // Just accessing it starts the monitoring
|
||||
console.log('Memory monitoring service initialized');
|
||||
|
||||
|
||||
// Initialize AI service
|
||||
await aiService.initialize();
|
||||
console.log('AI service initialized');
|
||||
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error initializing app:', error);
|
||||
// Default to showing onboarding if we can't check
|
||||
setHasCompletedOnboarding(false);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
initializeApp();
|
||||
}, []);
|
||||
|
||||
|
||||
// Create custom themes based on current theme
|
||||
const customDarkTheme = {
|
||||
...CustomDarkTheme,
|
||||
|
|
@ -136,7 +141,7 @@ const ThemedApp = () => {
|
|||
primary: currentTheme.colors.primary,
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const customNavigationTheme = {
|
||||
...CustomNavigationDarkTheme,
|
||||
colors: {
|
||||
|
|
@ -151,17 +156,30 @@ const ThemedApp = () => {
|
|||
const handleSplashComplete = () => {
|
||||
setIsAppReady(true);
|
||||
};
|
||||
|
||||
|
||||
// Navigation reference
|
||||
const navigationRef = React.useRef<any>(null);
|
||||
|
||||
// Don't render anything until we know the onboarding status
|
||||
const shouldShowApp = isAppReady && hasCompletedOnboarding !== null;
|
||||
const initialRouteName = hasCompletedOnboarding ? 'MainTabs' : 'Onboarding';
|
||||
|
||||
|
||||
return (
|
||||
<AccountProvider>
|
||||
<PaperProvider theme={customDarkTheme}>
|
||||
<NavigationContainer
|
||||
<NavigationContainer
|
||||
ref={navigationRef}
|
||||
theme={customNavigationTheme}
|
||||
linking={undefined}
|
||||
linking={{
|
||||
prefixes: ['nuvio://'],
|
||||
config: {
|
||||
screens: {
|
||||
ScraperSettings: {
|
||||
path: 'repo',
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<DownloadsProvider>
|
||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
|
|
@ -184,6 +202,7 @@ const ThemedApp = () => {
|
|||
onDismiss={githubUpdate.onDismiss}
|
||||
onLater={githubUpdate.onLater}
|
||||
/>
|
||||
<CampaignManager />
|
||||
</View>
|
||||
</DownloadsProvider>
|
||||
</NavigationContainer>
|
||||
|
|
@ -195,19 +214,21 @@ const ThemedApp = () => {
|
|||
function App(): React.JSX.Element {
|
||||
return (
|
||||
<GestureHandlerRootView style={{ flex: 1 }}>
|
||||
<GenreProvider>
|
||||
<CatalogProvider>
|
||||
<TraktProvider>
|
||||
<ThemeProvider>
|
||||
<TrailerProvider>
|
||||
<ToastProvider>
|
||||
<ThemedApp />
|
||||
</ToastProvider>
|
||||
</TrailerProvider>
|
||||
</ThemeProvider>
|
||||
</TraktProvider>
|
||||
</CatalogProvider>
|
||||
</GenreProvider>
|
||||
<BottomSheetModalProvider>
|
||||
<GenreProvider>
|
||||
<CatalogProvider>
|
||||
<TraktProvider>
|
||||
<ThemeProvider>
|
||||
<TrailerProvider>
|
||||
<ToastProvider>
|
||||
<ThemedApp />
|
||||
</ToastProvider>
|
||||
</TrailerProvider>
|
||||
</ThemeProvider>
|
||||
</TraktProvider>
|
||||
</CatalogProvider>
|
||||
</GenreProvider>
|
||||
</BottomSheetModalProvider>
|
||||
</GestureHandlerRootView>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
65
README.md
|
|
@ -1,16 +1,15 @@
|
|||
<!-- Improved compatibility of back to top link -->
|
||||
<a id="readme-top"></a>
|
||||
|
||||
<!-- PROJECT SHIELDS -->
|
||||
[![Contributors][contributors-shield]][contributors-url]
|
||||
[![Forks][forks-shield]][forks-url]
|
||||
[![Stargazers][stars-shield]][stars-url]
|
||||
[![Issues][issues-shield]][issues-url]
|
||||
[![License][license-shield]][license-url]
|
||||
|
||||
<!-- PROJECT LOGO -->
|
||||
<br />
|
||||
<div align="center">
|
||||
<a id="readme-top"></a>
|
||||
|
||||
[![Contributors][contributors-shield]][contributors-url]
|
||||
[![Forks][forks-shield]][forks-url]
|
||||
[![Stargazers][stars-shield]][stars-url]
|
||||
[![Issues][issues-shield]][issues-url]
|
||||
[![License][license-shield]][license-url]
|
||||
|
||||
<br />
|
||||
<br />
|
||||
<img src="assets/titlelogo.png" alt="Nuvio Logo" width="120" />
|
||||
<h1 align="center">🎬 Nuvio Media Hub</h1>
|
||||
<p align="center">
|
||||
|
|
@ -22,11 +21,10 @@
|
|||
<a href="#getting-started"><strong>Get Started »</strong></a>
|
||||
<br />
|
||||
<br />
|
||||
<a href="#demo">View Screenshots</a>
|
||||
·
|
||||
<a href="https://github.com/tapframe/NuvioStreaming/issues/new?labels=bug&template=bug_report.md">Report Bug</a>
|
||||
·
|
||||
<a href="https://github.com/tapframe/NuvioStreaming/issues/new?labels=enhancement&template=feature_request.md">Request Feature</a>
|
||||
|
||||
<a href="https://github.com/tapframe/NuvioStreaming/issues/new?labels=bug&template=bug_report.md">Report Bug</a>
|
||||
·
|
||||
<a href="https://github.com/tapframe/NuvioStreaming/issues/new?labels=enhancement&template=feature_request.md">Request Feature</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
|
@ -38,11 +36,13 @@
|
|||
<a href="#about-the-project">About The Project</a>
|
||||
</li>
|
||||
<li><a href="#installation">Installation</a></li>
|
||||
<li><a href="#demo">Screenshots</a></li>
|
||||
|
||||
<li><a href="#getting-started">Getting Started</a></li>
|
||||
<li><a href="#contributing">Contributing</a></li>
|
||||
<li><a href="#support">Support</a></li>
|
||||
<li><a href="#support">Support</a></li>
|
||||
<li><a href="#license">License</a></li>
|
||||
<li><a href="#legal">Legal</a></li>
|
||||
<li><a href="#contact">Contact</a></li>
|
||||
<li><a href="#acknowledgments">Acknowledgments</a></li>
|
||||
<li><a href="#built-with">Built With</a></li>
|
||||
|
|
@ -66,6 +66,9 @@ Download the latest APK from [GitHub Releases](https://github.com/tapframe/Nuvio
|
|||
|
||||
### iOS
|
||||
|
||||
#### TestFlight (Recommended)
|
||||
<img src="https://upload.wikimedia.org/wikipedia/fr/b/bc/TestFlight-icon.png" width="24" height="24" align="left"> [](https://testflight.apple.com/join/QkKMGRqp)
|
||||
|
||||
#### AltStore
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/2/20/AltStore_logo.png" width="24" height="24" align="left"> [](https://tinyurl.com/NuvioAltstore)
|
||||
|
||||
|
|
@ -76,20 +79,12 @@ Download the latest APK from [GitHub Releases](https://github.com/tapframe/Nuvio
|
|||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
<!-- DEMO / SCREENSHOTS -->
|
||||
## Demo
|
||||
<a id="demo"></a>
|
||||
|
||||
| Home | Details |
|
||||
|:----:|:-------:|
|
||||
|  |  |
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
<!-- GETTING STARTED -->
|
||||
## Getting Started
|
||||
|
||||
Follow the steps below to run the app locally for development.
|
||||
Follow the steps below to run the app locally for development. For detailed setup and troubleshooting, see [Project Documentation](docs/DOCUMENTATION.md).
|
||||
|
||||
### Development Build
|
||||
|
||||
|
|
@ -145,6 +140,14 @@ Distributed under the GNU GPLv3 License. See `LICENSE` for more information.
|
|||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
## Legal
|
||||
|
||||
For comprehensive legal information, including our full disclaimer, third-party extension policy, and DMCA/Copyright information, please visit our **[Legal & Disclaimer Page](https://tapframe.github.io/NuvioStreaming/#legal)**.
|
||||
|
||||
**Disclaimer:** Nuvio functions solely as a client-side interface for browsing metadata and playing media files provided by user-installed extensions. It does not host, store, or distribute any media content.
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
## Contact
|
||||
|
||||
**Project Links:**
|
||||
|
|
@ -174,6 +177,16 @@ Distributed under the GNU GPLv3 License. See `LICENSE` for more information.
|
|||
React Native • Expo • TypeScript
|
||||
</p>
|
||||
|
||||
## Star History
|
||||
|
||||
<a href="https://www.star-history.com/#tapframe/NuvioStreaming&type=date&legend=top-left">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=tapframe/NuvioStreaming&type=date&theme=dark&legend=top-left" />
|
||||
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=tapframe/NuvioStreaming&type=date&legend=top-left" />
|
||||
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=tapframe/NuvioStreaming&type=date&legend=top-left" />
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
<!-- MARKDOWN LINKS & IMAGES -->
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
apply plugin: "com.android.application"
|
||||
apply plugin: "org.jetbrains.kotlin.android"
|
||||
apply plugin: "com.facebook.react"
|
||||
apply plugin: "io.sentry.android.gradle"
|
||||
|
||||
def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()
|
||||
|
||||
|
|
@ -81,7 +82,7 @@ def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBu
|
|||
*/
|
||||
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
|
||||
|
||||
// apply from: new File(["node", "--print", "require('path').dirname(require.resolve('@sentry/react-native/package.json'))"].execute().text.trim(), "sentry.gradle")
|
||||
apply from: new File(["node", "--print", "require('path').dirname(require.resolve('@sentry/react-native/package.json'))"].execute().text.trim(), "sentry.gradle")
|
||||
|
||||
android {
|
||||
ndkVersion rootProject.ext.ndkVersion
|
||||
|
|
@ -94,8 +95,8 @@ android {
|
|||
applicationId 'com.nuvio.app'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 21
|
||||
versionName "1.2.6"
|
||||
versionCode 33
|
||||
versionName "1.3.5"
|
||||
|
||||
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
||||
}
|
||||
|
|
@ -117,7 +118,7 @@ android {
|
|||
def abiVersionCodes = ['armeabi-v7a': 1, 'arm64-v8a': 2, 'x86': 3, 'x86_64': 4]
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
def baseVersionCode = 21 // Current versionCode 21 from defaultConfig
|
||||
def baseVersionCode = 33 // Current versionCode 33 from defaultConfig
|
||||
def abiName = output.getFilter(com.android.build.OutputFile.ABI)
|
||||
|
||||
def versionCode = baseVersionCode * 100 // Base multiplier
|
||||
|
|
@ -184,7 +185,38 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
sentry {
|
||||
// Enables or disables the automatic configuration of Native Symbols
|
||||
// for Sentry. This executes sentry-cli automatically so
|
||||
// you don't need to do it manually.
|
||||
// Default is disabled.
|
||||
uploadNativeSymbols = true
|
||||
|
||||
// Enables or disables the automatic upload of the app's native source code to Sentry.
|
||||
// This executes sentry-cli with the --include-sources param automatically so
|
||||
// you don't need to do it manually.
|
||||
// This option has an effect only when [uploadNativeSymbols] is enabled.
|
||||
// Default is disabled.
|
||||
includeNativeSources = true
|
||||
|
||||
// `@sentry/react-native` ships with compatible `sentry-android`
|
||||
// This option would install the latest version that ships with the SDK or SAGP (Sentry Android Gradle Plugin)
|
||||
// which might be incompatible with the React Native SDK
|
||||
// Enable auto-installation of Sentry components (sentry-android SDK and okhttp, timber and fragment integrations).
|
||||
// Default is enabled.
|
||||
autoInstallation {
|
||||
enabled = false
|
||||
}
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
exclude group: 'com.caverock', module: 'androidsvg'
|
||||
}
|
||||
|
||||
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 +246,14 @@ dependencies {
|
|||
|
||||
// Include only FFmpeg decoder AAR to avoid duplicates with Maven Media3
|
||||
implementation files("libs/lib-decoder-ffmpeg-release.aar")
|
||||
|
||||
// MPV Player library
|
||||
implementation files("libs/libmpv-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
|
||||
}
|
||||
|
|
|
|||
BIN
android/app/libs/libmpv-release.aar
Normal file
|
|
@ -1,4 +1,5 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-sdk tools:overrideLibrary="dev.jdtech.mpv"/>
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
|
@ -14,11 +15,13 @@
|
|||
</intent>
|
||||
</queries>
|
||||
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:supportsRtl="true" android:enableOnBackInvokedCallback="false">
|
||||
<meta-data android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME" android:value="com.reactnative.googlecast.GoogleCastOptionsProvider"/>
|
||||
<meta-data android:name="com.reactnative.googlecast.RECEIVER_APPLICATION_ID" android:value="CC1AD845"/>
|
||||
<meta-data android:name="expo.modules.updates.ENABLED" android:value="true"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_RUNTIME_VERSION" android:value="@string/expo_runtime_version"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ERROR_RECOVERY_ONLY"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="30000"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest"/>
|
||||
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://ota.nuvioapp.space/api/manifest"/>
|
||||
<activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode|locale|layoutDirection" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true" android:screenOrientation="unspecified">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.facebook.react.defaults.DefaultReactNativeHost
|
|||
|
||||
import expo.modules.ApplicationLifecycleDispatcher
|
||||
import expo.modules.ReactNativeHostWrapper
|
||||
import com.nuvio.app.mpv.MpvPackage
|
||||
|
||||
class MainApplication : Application(), ReactApplication {
|
||||
|
||||
|
|
@ -24,7 +25,7 @@ class MainApplication : Application(), ReactApplication {
|
|||
override fun getPackages(): List<ReactPackage> =
|
||||
PackageList(this).packages.apply {
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
// add(MyReactNativePackage())
|
||||
add(com.nuvio.app.mpv.MpvPackage())
|
||||
}
|
||||
|
||||
override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry"
|
||||
|
|
|
|||
615
android/app/src/main/java/com/nuvio/app/mpv/MPVView.kt
Normal file
|
|
@ -0,0 +1,615 @@
|
|||
package com.nuvio.app.mpv
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.SurfaceTexture
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.Surface
|
||||
import android.view.TextureView
|
||||
import dev.jdtech.mpv.MPVLib
|
||||
|
||||
import com.facebook.react.bridge.LifecycleEventListener
|
||||
import com.facebook.react.bridge.ReactContext
|
||||
|
||||
class MPVView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : TextureView(context, attrs, defStyleAttr), TextureView.SurfaceTextureListener, MPVLib.EventObserver {
|
||||
|
||||
companion object {
|
||||
private const val TAG = "MPVView"
|
||||
}
|
||||
|
||||
private var isMpvInitialized = false
|
||||
private var pendingDataSource: String? = null
|
||||
private var isPaused: Boolean = true
|
||||
private var surface: Surface? = null
|
||||
private var httpHeaders: Map<String, String>? = null
|
||||
|
||||
// Decoder mode setting: 'auto', 'sw', 'hw', 'hw+' (default: auto)
|
||||
var decoderMode: String = "auto"
|
||||
|
||||
// GPU mode setting: 'gpu', 'gpu-next' (default: gpu)
|
||||
var gpuMode: String = "gpu"
|
||||
|
||||
// Flag to track if onLoad has been fired (prevents multiple fires for HLS streams)
|
||||
private var hasLoadEventFired: Boolean = false
|
||||
|
||||
// Event listener for React Native
|
||||
var onLoadCallback: ((duration: Double, width: Int, height: Int) -> Unit)? = null
|
||||
var onProgressCallback: ((position: Double, duration: Double) -> Unit)? = null
|
||||
var onEndCallback: (() -> Unit)? = null
|
||||
var onErrorCallback: ((message: String) -> Unit)? = null
|
||||
var onTracksChangedCallback: ((audioTracks: List<Map<String, Any>>, subtitleTracks: List<Map<String, Any>>) -> Unit)? = null
|
||||
|
||||
private var resumeOnForeground = false
|
||||
private val lifeCycleListener = object : LifecycleEventListener {
|
||||
override fun onHostPause() {
|
||||
resumeOnForeground = !isPaused;
|
||||
if(resumeOnForeground) {
|
||||
Log.d(TAG, "App backgrounded — pausing MPV")
|
||||
setPaused(true)
|
||||
}
|
||||
}
|
||||
override fun onHostResume() {
|
||||
if(resumeOnForeground) {
|
||||
setPaused(false)
|
||||
resumeOnForeground = false
|
||||
}
|
||||
}
|
||||
override fun onHostDestroy() {}
|
||||
}
|
||||
init {
|
||||
surfaceTextureListener = this
|
||||
isOpaque = false
|
||||
(context as? ReactContext)?.addLifecycleEventListener(lifeCycleListener)
|
||||
}
|
||||
|
||||
override fun onSurfaceTextureAvailable(surfaceTexture: SurfaceTexture, width: Int, height: Int) {
|
||||
Log.d(TAG, "Surface texture available: ${width}x${height}")
|
||||
try {
|
||||
surface = Surface(surfaceTexture)
|
||||
|
||||
MPVLib.create(context.applicationContext)
|
||||
initOptions()
|
||||
MPVLib.init()
|
||||
MPVLib.attachSurface(surface!!)
|
||||
MPVLib.addObserver(this)
|
||||
MPVLib.setPropertyString("android-surface-size", "${width}x${height}")
|
||||
observeProperties()
|
||||
isMpvInitialized = true
|
||||
|
||||
// If a data source was set before surface was ready, load it now
|
||||
// Headers are already applied in initOptions() before init()
|
||||
pendingDataSource?.let { url ->
|
||||
loadFile(url)
|
||||
pendingDataSource = null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Failed to initialize MPV", e)
|
||||
onErrorCallback?.invoke("MPV initialization failed: ${e.message}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSurfaceTextureSizeChanged(surfaceTexture: SurfaceTexture, width: Int, height: Int) {
|
||||
Log.d(TAG, "Surface texture size changed: ${width}x${height}")
|
||||
if (isMpvInitialized) {
|
||||
MPVLib.setPropertyString("android-surface-size", "${width}x${height}")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture): Boolean {
|
||||
Log.d(TAG, "Surface texture destroyed")
|
||||
(context as? ReactContext)?.removeLifecycleEventListener(lifeCycleListener)
|
||||
if (isMpvInitialized) {
|
||||
MPVLib.removeObserver(this)
|
||||
MPVLib.detachSurface()
|
||||
MPVLib.destroy()
|
||||
isMpvInitialized = false
|
||||
}
|
||||
surface?.release()
|
||||
surface = null
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onSurfaceTextureUpdated(surfaceTexture: SurfaceTexture) {
|
||||
// Called when the SurfaceTexture is updated via updateTexImage()
|
||||
}
|
||||
|
||||
private fun initOptions() {
|
||||
MPVLib.setOptionString("profile", "fast")
|
||||
|
||||
// GPU rendering mode (gpu or gpu-next)
|
||||
MPVLib.setOptionString("vo", gpuMode)
|
||||
MPVLib.setOptionString("gpu-context", "android")
|
||||
MPVLib.setOptionString("opengl-es", "yes")
|
||||
|
||||
// Decoder mode mapping (same as mpvKt)
|
||||
val hwdecValue = when (decoderMode) {
|
||||
"auto" -> "auto-copy" // Best balance: HW decode, copy to CPU for filters
|
||||
"sw" -> "no" // Software decoding only
|
||||
"hw" -> "mediacodec-copy" // HW decode with copy (safer)
|
||||
"hw+" -> "mediacodec" // Full HW decode (fastest, may have issues)
|
||||
else -> "auto-copy"
|
||||
}
|
||||
Log.d(TAG, "Decoder mode: $decoderMode, hwdec value: $hwdecValue, GPU mode: $gpuMode")
|
||||
MPVLib.setOptionString("hwdec", hwdecValue)
|
||||
// Note: Not setting hwdec-codecs explicitly - let mpv use defaults
|
||||
|
||||
MPVLib.setOptionString("target-colorspace-hint", "yes")
|
||||
|
||||
// HDR and Dolby Vision support
|
||||
// target-prim: Signal target display primaries (auto = passthrough when display supports)
|
||||
MPVLib.setOptionString("target-prim", "auto")
|
||||
// target-trc: Signal target transfer characteristics (auto = passthrough when display supports)
|
||||
MPVLib.setOptionString("target-trc", "auto")
|
||||
// tone-mapping: How to handle HDR/DV content on SDR displays (auto = best automatic choice)
|
||||
MPVLib.setOptionString("tone-mapping", "auto")
|
||||
// hdr-compute-peak: Compute peak brightness for better tone mapping
|
||||
MPVLib.setOptionString("hdr-compute-peak", "auto")
|
||||
// Allow DV Profile 5 (HEVC with RPU) to be decoded by hardware decoder
|
||||
MPVLib.setOptionString("vd-lavc-o", "strict=-2")
|
||||
|
||||
// Workaround for https://github.com/mpv-player/mpv/issues/14651
|
||||
MPVLib.setOptionString("vd-lavc-film-grain", "cpu")
|
||||
|
||||
MPVLib.setOptionString("ao", "audiotrack,opensles")
|
||||
|
||||
// Limit demuxer cache based on Android version (like mpvKt)
|
||||
val cacheMegs = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O_MR1) 64 else 32
|
||||
MPVLib.setOptionString("demuxer-max-bytes", "${cacheMegs * 1024 * 1024}")
|
||||
MPVLib.setOptionString("demuxer-max-back-bytes", "${cacheMegs * 1024 * 1024}")
|
||||
MPVLib.setOptionString("cache", "yes")
|
||||
MPVLib.setOptionString("cache-secs", "30")
|
||||
|
||||
MPVLib.setOptionString("network-timeout", "60")
|
||||
MPVLib.setOptionString("ytdl", "no")
|
||||
|
||||
applyHttpHeadersAsOptions()
|
||||
|
||||
MPVLib.setOptionString("tls-verify", "no")
|
||||
MPVLib.setOptionString("http-reconnect", "yes")
|
||||
MPVLib.setOptionString("stream-reconnect", "yes")
|
||||
|
||||
MPVLib.setOptionString("demuxer-lavf-o", "live_start_index=0,prefer_x_start=1,http_persistent=0")
|
||||
MPVLib.setOptionString("demuxer-seekable-cache", "yes")
|
||||
MPVLib.setOptionString("force-seekable", "yes")
|
||||
|
||||
MPVLib.setOptionString("sub-auto", "fuzzy")
|
||||
MPVLib.setOptionString("sub-visibility", "yes")
|
||||
MPVLib.setOptionString("sub-font-size", "48")
|
||||
MPVLib.setOptionString("sub-pos", "100")
|
||||
MPVLib.setOptionString("sub-color", "#FFFFFFFF")
|
||||
MPVLib.setOptionString("sub-border-size", "3")
|
||||
MPVLib.setOptionString("sub-border-color", "#FF000000")
|
||||
MPVLib.setOptionString("sub-shadow-offset", "2")
|
||||
MPVLib.setOptionString("sub-shadow-color", "#80000000")
|
||||
|
||||
MPVLib.setOptionString("osd-fonts-dir", "/system/fonts")
|
||||
MPVLib.setOptionString("sub-fonts-dir", "/system/fonts")
|
||||
MPVLib.setOptionString("sub-font", "Roboto")
|
||||
MPVLib.setOptionString("embeddedfonts", "yes")
|
||||
|
||||
MPVLib.setOptionString("sub-codepage", "auto")
|
||||
|
||||
MPVLib.setOptionString("blend-subtitles", "no")
|
||||
MPVLib.setOptionString("sub-use-margins", "yes")
|
||||
MPVLib.setOptionString("sub-ass-override", "force")
|
||||
MPVLib.setOptionString("sub-scale", "1.0")
|
||||
MPVLib.setOptionString("sub-fix-timing", "yes")
|
||||
|
||||
MPVLib.setOptionString("osc", "no")
|
||||
MPVLib.setOptionString("osd-level", "1")
|
||||
|
||||
MPVLib.setOptionString("sid", "auto")
|
||||
|
||||
MPVLib.setOptionString("terminal", "no")
|
||||
MPVLib.setOptionString("input-default-bindings", "no")
|
||||
}
|
||||
|
||||
private fun observeProperties() {
|
||||
// MPV format constants (from MPVLib source)
|
||||
val MPV_FORMAT_NONE = 0
|
||||
val MPV_FORMAT_FLAG = 3
|
||||
val MPV_FORMAT_INT64 = 4
|
||||
val MPV_FORMAT_DOUBLE = 5
|
||||
|
||||
MPVLib.observeProperty("time-pos", MPV_FORMAT_DOUBLE)
|
||||
MPVLib.observeProperty("duration/full", MPV_FORMAT_DOUBLE) // Use /full for complete HLS duration
|
||||
MPVLib.observeProperty("pause", MPV_FORMAT_FLAG)
|
||||
MPVLib.observeProperty("paused-for-cache", MPV_FORMAT_FLAG)
|
||||
MPVLib.observeProperty("eof-reached", MPV_FORMAT_FLAG)
|
||||
MPVLib.observeProperty("video-params/aspect", MPV_FORMAT_DOUBLE)
|
||||
MPVLib.observeProperty("width", MPV_FORMAT_INT64)
|
||||
MPVLib.observeProperty("height", MPV_FORMAT_INT64)
|
||||
MPVLib.observeProperty("track-list", MPV_FORMAT_NONE)
|
||||
|
||||
// Observe subtitle properties for debugging
|
||||
MPVLib.observeProperty("sid", MPV_FORMAT_INT64)
|
||||
MPVLib.observeProperty("sub-visibility", MPV_FORMAT_FLAG)
|
||||
MPVLib.observeProperty("sub-text", MPV_FORMAT_NONE)
|
||||
}
|
||||
|
||||
private fun loadFile(url: String) {
|
||||
Log.d(TAG, "Loading file: $url")
|
||||
// Reset load event flag for new file
|
||||
hasLoadEventFired = false
|
||||
MPVLib.command(arrayOf("loadfile", url))
|
||||
}
|
||||
|
||||
// Public API
|
||||
|
||||
fun setDataSource(url: String) {
|
||||
if (isMpvInitialized) {
|
||||
// Headers were already set during initialization in initOptions()
|
||||
loadFile(url)
|
||||
} else {
|
||||
pendingDataSource = url
|
||||
}
|
||||
}
|
||||
|
||||
fun setHeaders(headers: Map<String, String>?) {
|
||||
httpHeaders = headers
|
||||
Log.d(TAG, "Headers set: $headers")
|
||||
}
|
||||
|
||||
private fun applyHttpHeadersAsOptions() {
|
||||
// Always set user-agent (this works reliably)
|
||||
val userAgent = httpHeaders?.get("User-Agent")
|
||||
?: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
|
||||
Log.d(TAG, "Setting User-Agent: $userAgent")
|
||||
MPVLib.setOptionString("user-agent", userAgent)
|
||||
|
||||
// Additionally, set other headers via http-header-fields if present
|
||||
// This is needed for streams that require Referer, Origin, Cookie, etc.
|
||||
httpHeaders?.let { headers ->
|
||||
val otherHeaders = headers.filterKeys { it != "User-Agent" }
|
||||
if (otherHeaders.isNotEmpty()) {
|
||||
// Format as comma-separated "Key: Value" pairs
|
||||
val headerString = otherHeaders.map { (key, value) -> "$key: $value" }.joinToString(",")
|
||||
Log.d(TAG, "Setting additional headers: $headerString")
|
||||
MPVLib.setOptionString("http-header-fields", headerString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setPaused(paused: Boolean) {
|
||||
isPaused = paused
|
||||
if (isMpvInitialized) {
|
||||
MPVLib.setPropertyBoolean("pause", paused)
|
||||
}
|
||||
}
|
||||
|
||||
fun seekTo(positionSeconds: Double) {
|
||||
Log.d(TAG, "seekTo called: positionSeconds=$positionSeconds, isMpvInitialized=$isMpvInitialized")
|
||||
if (isMpvInitialized) {
|
||||
Log.d(TAG, "Executing MPV seek command: seek $positionSeconds absolute")
|
||||
MPVLib.command(arrayOf("seek", positionSeconds.toString(), "absolute"))
|
||||
}
|
||||
}
|
||||
|
||||
fun setSpeed(speed: Double) {
|
||||
if (isMpvInitialized) {
|
||||
MPVLib.setPropertyDouble("speed", speed)
|
||||
}
|
||||
}
|
||||
|
||||
fun setVolume(volume: Double) {
|
||||
if (isMpvInitialized) {
|
||||
// MPV volume is 0-100
|
||||
MPVLib.setPropertyDouble("volume", volume * 100.0)
|
||||
}
|
||||
}
|
||||
|
||||
fun setAudioTrack(trackId: Int) {
|
||||
if (isMpvInitialized) {
|
||||
if (trackId == -1) {
|
||||
MPVLib.setPropertyString("aid", "no")
|
||||
} else {
|
||||
MPVLib.setPropertyInt("aid", trackId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleTrack(trackId: Int) {
|
||||
Log.d(TAG, "setSubtitleTrack called: trackId=$trackId, isMpvInitialized=$isMpvInitialized")
|
||||
if (isMpvInitialized) {
|
||||
if (trackId == -1) {
|
||||
Log.d(TAG, "Disabling subtitles (sid=no)")
|
||||
MPVLib.setPropertyString("sid", "no")
|
||||
MPVLib.setPropertyString("sub-visibility", "no")
|
||||
} else {
|
||||
Log.d(TAG, "Setting subtitle track to: $trackId")
|
||||
MPVLib.setPropertyInt("sid", trackId)
|
||||
// Ensure subtitles are visible
|
||||
MPVLib.setPropertyString("sub-visibility", "yes")
|
||||
|
||||
// Debug: Verify the subtitle was set correctly
|
||||
val currentSid = MPVLib.getPropertyInt("sid")
|
||||
val subVisibility = MPVLib.getPropertyString("sub-visibility")
|
||||
val subDelay = MPVLib.getPropertyDouble("sub-delay")
|
||||
val subScale = MPVLib.getPropertyDouble("sub-scale")
|
||||
Log.d(TAG, "After setting - sid=$currentSid, sub-visibility=$subVisibility, sub-delay=$subDelay, sub-scale=$subScale")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setResizeMode(mode: String) {
|
||||
Log.d(TAG, "setResizeMode called: mode=$mode, isMpvInitialized=$isMpvInitialized")
|
||||
if (isMpvInitialized) {
|
||||
when (mode) {
|
||||
"contain" -> {
|
||||
// Letterbox - show entire video with black bars
|
||||
MPVLib.setPropertyDouble("panscan", 0.0)
|
||||
MPVLib.setPropertyString("keepaspect", "yes")
|
||||
}
|
||||
"cover" -> {
|
||||
// Fill/crop - zoom to fill, cropping edges
|
||||
MPVLib.setPropertyDouble("panscan", 1.0)
|
||||
MPVLib.setPropertyString("keepaspect", "yes")
|
||||
}
|
||||
"stretch" -> {
|
||||
// Stretch - disable aspect ratio
|
||||
MPVLib.setPropertyDouble("panscan", 0.0)
|
||||
MPVLib.setPropertyString("keepaspect", "no")
|
||||
}
|
||||
else -> {
|
||||
// Default to contain
|
||||
MPVLib.setPropertyDouble("panscan", 0.0)
|
||||
MPVLib.setPropertyString("keepaspect", "yes")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Subtitle Styling Methods
|
||||
|
||||
fun setSubtitleSize(size: Int) {
|
||||
if (isMpvInitialized) {
|
||||
Log.d(TAG, "Setting subtitle size: $size")
|
||||
MPVLib.setPropertyInt("sub-font-size", size)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleColor(color: String) {
|
||||
if (isMpvInitialized) {
|
||||
// MPV expects color in #AARRGGBB format, but we receive #RRGGBB
|
||||
// Convert to MPV format with full opacity
|
||||
val mpvColor = if (color.length == 7) "#FF${color.substring(1)}" else color
|
||||
Log.d(TAG, "Setting subtitle color: $mpvColor")
|
||||
MPVLib.setPropertyString("sub-color", mpvColor)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleBackgroundColor(color: String, opacity: Float) {
|
||||
if (isMpvInitialized) {
|
||||
// Convert opacity (0-1) to hex (00-FF)
|
||||
val alphaHex = (opacity * 255).toInt().coerceIn(0, 255).let {
|
||||
String.format("%02X", it)
|
||||
}
|
||||
// MPV format: #AARRGGBB
|
||||
val baseColor = if (color.startsWith("#")) color.substring(1) else color
|
||||
val mpvColor = "#${alphaHex}${baseColor.takeLast(6)}"
|
||||
Log.d(TAG, "Setting subtitle background: $mpvColor (opacity: $opacity)")
|
||||
MPVLib.setPropertyString("sub-back-color", mpvColor)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleBorderSize(size: Int) {
|
||||
if (isMpvInitialized) {
|
||||
Log.d(TAG, "Setting subtitle border size: $size")
|
||||
MPVLib.setPropertyInt("sub-border-size", size)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleBorderColor(color: String) {
|
||||
if (isMpvInitialized) {
|
||||
val mpvColor = if (color.length == 7) "#FF${color.substring(1)}" else color
|
||||
Log.d(TAG, "Setting subtitle border color: $mpvColor")
|
||||
MPVLib.setPropertyString("sub-border-color", mpvColor)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleShadow(enabled: Boolean, offset: Int) {
|
||||
if (isMpvInitialized) {
|
||||
Log.d(TAG, "Setting subtitle shadow: enabled=$enabled, offset=$offset")
|
||||
if (enabled) {
|
||||
MPVLib.setPropertyInt("sub-shadow-offset", offset)
|
||||
MPVLib.setPropertyString("sub-shadow-color", "#80000000")
|
||||
} else {
|
||||
MPVLib.setPropertyInt("sub-shadow-offset", 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitlePosition(pos: Int) {
|
||||
if (isMpvInitialized) {
|
||||
// sub-pos: 0=top, 100=bottom, can go beyond 100 for more offset
|
||||
// UI sends bottomOffset (0=at bottom, higher=more up from bottom)
|
||||
// Convert: MPV pos = 100 - (bottomOffset / screenHeightFactor)
|
||||
// Simplified: just pass pos directly, UI should convert
|
||||
Log.d(TAG, "Setting subtitle position: $pos")
|
||||
MPVLib.setPropertyInt("sub-pos", pos)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleDelay(delaySec: Double) {
|
||||
if (isMpvInitialized) {
|
||||
Log.d(TAG, "Setting subtitle delay: $delaySec seconds")
|
||||
MPVLib.setPropertyDouble("sub-delay", delaySec)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleScale(scale: Double) {
|
||||
if (isMpvInitialized) {
|
||||
Log.d(TAG, "Setting subtitle scale: $scale")
|
||||
MPVLib.setPropertyDouble("sub-scale", scale)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleAlignment(align: String) {
|
||||
if (isMpvInitialized) {
|
||||
// MPV sub-justify values: left, center, right, auto
|
||||
val mpvAlign = when (align) {
|
||||
"left" -> "left"
|
||||
"right" -> "right"
|
||||
"center" -> "center"
|
||||
else -> "center"
|
||||
}
|
||||
Log.d(TAG, "Setting subtitle alignment: $mpvAlign")
|
||||
MPVLib.setPropertyString("sub-justify", mpvAlign)
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleBold(bold: Boolean) {
|
||||
if (isMpvInitialized) {
|
||||
Log.d(TAG, "Setting subtitle bold: $bold")
|
||||
MPVLib.setPropertyString("sub-bold", if (bold) "yes" else "no")
|
||||
}
|
||||
}
|
||||
|
||||
fun setSubtitleItalic(italic: Boolean) {
|
||||
if (isMpvInitialized) {
|
||||
Log.d(TAG, "Setting subtitle italic: $italic")
|
||||
MPVLib.setPropertyString("sub-italic", if (italic) "yes" else "no")
|
||||
}
|
||||
}
|
||||
|
||||
// MPVLib.EventObserver implementation
|
||||
|
||||
override fun eventProperty(property: String) {
|
||||
Log.d(TAG, "Property changed: $property")
|
||||
when (property) {
|
||||
"track-list" -> {
|
||||
// Parse track list and notify React Native
|
||||
parseAndSendTracks()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseAndSendTracks() {
|
||||
try {
|
||||
val trackCount = MPVLib.getPropertyInt("track-list/count") ?: 0
|
||||
Log.d(TAG, "Track count: $trackCount")
|
||||
|
||||
val audioTracks = mutableListOf<Map<String, Any>>()
|
||||
val subtitleTracks = mutableListOf<Map<String, Any>>()
|
||||
|
||||
for (i in 0 until trackCount) {
|
||||
val type = MPVLib.getPropertyString("track-list/$i/type") ?: continue
|
||||
val id = MPVLib.getPropertyInt("track-list/$i/id") ?: continue
|
||||
val title = MPVLib.getPropertyString("track-list/$i/title") ?: ""
|
||||
val lang = MPVLib.getPropertyString("track-list/$i/lang") ?: ""
|
||||
val codec = MPVLib.getPropertyString("track-list/$i/codec") ?: ""
|
||||
|
||||
val trackName = when {
|
||||
title.isNotEmpty() -> title
|
||||
lang.isNotEmpty() -> lang.uppercase()
|
||||
else -> "Track $id"
|
||||
}
|
||||
|
||||
val track = mapOf(
|
||||
"id" to id,
|
||||
"name" to trackName,
|
||||
"language" to lang,
|
||||
"codec" to codec
|
||||
)
|
||||
|
||||
when (type) {
|
||||
"audio" -> {
|
||||
Log.d(TAG, "Found audio track: $track")
|
||||
audioTracks.add(track)
|
||||
}
|
||||
"sub" -> {
|
||||
Log.d(TAG, "Found subtitle track: $track")
|
||||
subtitleTracks.add(track)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(TAG, "Sending tracks - Audio: ${audioTracks.size}, Subtitles: ${subtitleTracks.size}")
|
||||
onTracksChangedCallback?.invoke(audioTracks, subtitleTracks)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Error parsing tracks", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun eventProperty(property: String, value: Long) {
|
||||
Log.d(TAG, "Property $property = $value (Long)")
|
||||
}
|
||||
|
||||
override fun eventProperty(property: String, value: Double) {
|
||||
Log.d(TAG, "Property $property = $value (Double)")
|
||||
when (property) {
|
||||
"time-pos" -> {
|
||||
val duration = MPVLib.getPropertyDouble("duration/full") ?: MPVLib.getPropertyDouble("duration") ?: 0.0
|
||||
onProgressCallback?.invoke(value, duration)
|
||||
}
|
||||
"duration/full", "duration" -> {
|
||||
// Only fire onLoad once when video dimensions are available
|
||||
// For HLS streams, duration updates incrementally as segments are fetched
|
||||
if (!hasLoadEventFired) {
|
||||
val width = MPVLib.getPropertyInt("width") ?: 0
|
||||
val height = MPVLib.getPropertyInt("height") ?: 0
|
||||
// Wait until we have valid dimensions before firing onLoad
|
||||
if (width > 0 && height > 0 && value > 0) {
|
||||
hasLoadEventFired = true
|
||||
Log.d(TAG, "Firing onLoad event: duration=$value, width=$width, height=$height")
|
||||
onLoadCallback?.invoke(value, width, height)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun eventProperty(property: String, value: Boolean) {
|
||||
Log.d(TAG, "Property $property = $value (Boolean)")
|
||||
when (property) {
|
||||
"eof-reached" -> {
|
||||
if (value) {
|
||||
onEndCallback?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun eventProperty(property: String, value: String) {
|
||||
Log.d(TAG, "Property $property = $value (String)")
|
||||
}
|
||||
|
||||
override fun event(eventId: Int) {
|
||||
Log.d(TAG, "Event: $eventId")
|
||||
// MPV event constants (from MPVLib source)
|
||||
val MPV_EVENT_FILE_LOADED = 8
|
||||
val MPV_EVENT_END_FILE = 7
|
||||
|
||||
when (eventId) {
|
||||
MPV_EVENT_FILE_LOADED -> {
|
||||
// File is loaded, start playback if not paused
|
||||
if (!isPaused) {
|
||||
MPVLib.setPropertyBoolean("pause", false)
|
||||
}
|
||||
}
|
||||
MPV_EVENT_END_FILE -> {
|
||||
Log.d(TAG, "MPV_EVENT_END_FILE")
|
||||
|
||||
// Heuristic: If duration is effectively 0 at end of file, it's a load error
|
||||
val duration = MPVLib.getPropertyDouble("duration/full") ?: MPVLib.getPropertyDouble("duration") ?: 0.0
|
||||
val timePos = MPVLib.getPropertyDouble("time-pos") ?: 0.0
|
||||
val eofReached = MPVLib.getPropertyBoolean("eof-reached") ?: false
|
||||
|
||||
Log.d(TAG, "End stats - Duration: $duration, Time: $timePos, EOF: $eofReached")
|
||||
|
||||
if (duration < 1.0 && !eofReached) {
|
||||
val customError = "Unable to play media. Source may be unreachable."
|
||||
Log.e(TAG, "Playback error detected (heuristic): $customError")
|
||||
onErrorCallback?.invoke(customError)
|
||||
} else {
|
||||
onEndCallback?.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
android/app/src/main/java/com/nuvio/app/mpv/MpvPackage.kt
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
package com.nuvio.app.mpv
|
||||
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.bridge.NativeModule
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.uimanager.ViewManager
|
||||
|
||||
class MpvPackage : ReactPackage {
|
||||
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
||||
return listOf(MpvPlayerViewManager(reactContext))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,241 @@
|
|||
package com.nuvio.app.mpv
|
||||
|
||||
import android.graphics.Color
|
||||
import com.facebook.react.bridge.Arguments
|
||||
import com.facebook.react.bridge.ReactApplicationContext
|
||||
import com.facebook.react.bridge.ReadableArray
|
||||
import com.facebook.react.common.MapBuilder
|
||||
import com.facebook.react.uimanager.SimpleViewManager
|
||||
import com.facebook.react.uimanager.ThemedReactContext
|
||||
import com.facebook.react.uimanager.annotations.ReactProp
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter
|
||||
|
||||
class MpvPlayerViewManager(
|
||||
private val reactContext: ReactApplicationContext
|
||||
) : SimpleViewManager<MPVView>() {
|
||||
|
||||
companion object {
|
||||
const val REACT_CLASS = "MpvPlayer"
|
||||
|
||||
// Commands
|
||||
const val COMMAND_SEEK = 1
|
||||
const val COMMAND_SET_AUDIO_TRACK = 2
|
||||
const val COMMAND_SET_SUBTITLE_TRACK = 3
|
||||
}
|
||||
|
||||
override fun getName(): String = REACT_CLASS
|
||||
|
||||
override fun createViewInstance(context: ThemedReactContext): MPVView {
|
||||
val view = MPVView(context)
|
||||
// Note: Do NOT set background color - it will block the SurfaceView content
|
||||
|
||||
// Set up event callbacks
|
||||
view.onLoadCallback = { duration, width, height ->
|
||||
val event = Arguments.createMap().apply {
|
||||
putDouble("duration", duration)
|
||||
putInt("width", width)
|
||||
putInt("height", height)
|
||||
}
|
||||
sendEvent(context, view.id, "onLoad", event)
|
||||
}
|
||||
|
||||
view.onProgressCallback = { position, duration ->
|
||||
val event = Arguments.createMap().apply {
|
||||
putDouble("currentTime", position)
|
||||
putDouble("duration", duration)
|
||||
}
|
||||
sendEvent(context, view.id, "onProgress", event)
|
||||
}
|
||||
|
||||
view.onEndCallback = {
|
||||
sendEvent(context, view.id, "onEnd", Arguments.createMap())
|
||||
}
|
||||
|
||||
view.onErrorCallback = { message ->
|
||||
val event = Arguments.createMap().apply {
|
||||
putString("error", message)
|
||||
}
|
||||
sendEvent(context, view.id, "onError", event)
|
||||
}
|
||||
|
||||
view.onTracksChangedCallback = { audioTracks, subtitleTracks ->
|
||||
val event = Arguments.createMap().apply {
|
||||
val audioArray = Arguments.createArray()
|
||||
audioTracks.forEach { track ->
|
||||
val trackMap = Arguments.createMap().apply {
|
||||
putInt("id", track["id"] as Int)
|
||||
putString("name", track["name"] as String)
|
||||
putString("language", track["language"] as String)
|
||||
putString("codec", track["codec"] as String)
|
||||
}
|
||||
audioArray.pushMap(trackMap)
|
||||
}
|
||||
putArray("audioTracks", audioArray)
|
||||
|
||||
val subtitleArray = Arguments.createArray()
|
||||
subtitleTracks.forEach { track ->
|
||||
val trackMap = Arguments.createMap().apply {
|
||||
putInt("id", track["id"] as Int)
|
||||
putString("name", track["name"] as String)
|
||||
putString("language", track["language"] as String)
|
||||
putString("codec", track["codec"] as String)
|
||||
}
|
||||
subtitleArray.pushMap(trackMap)
|
||||
}
|
||||
putArray("subtitleTracks", subtitleArray)
|
||||
}
|
||||
sendEvent(context, view.id, "onTracksChanged", event)
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private fun sendEvent(context: ThemedReactContext, viewId: Int, eventName: String, params: com.facebook.react.bridge.WritableMap) {
|
||||
context.getJSModule(RCTEventEmitter::class.java)
|
||||
.receiveEvent(viewId, eventName, params)
|
||||
}
|
||||
|
||||
override fun getExportedCustomBubblingEventTypeConstants(): Map<String, Any> {
|
||||
return MapBuilder.builder<String, Any>()
|
||||
.put("onLoad", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onLoad")))
|
||||
.put("onProgress", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onProgress")))
|
||||
.put("onEnd", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onEnd")))
|
||||
.put("onError", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onError")))
|
||||
.put("onTracksChanged", MapBuilder.of("phasedRegistrationNames", MapBuilder.of("bubbled", "onTracksChanged")))
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun getCommandsMap(): Map<String, Int> {
|
||||
return MapBuilder.of(
|
||||
"seek", COMMAND_SEEK,
|
||||
"setAudioTrack", COMMAND_SET_AUDIO_TRACK,
|
||||
"setSubtitleTrack", COMMAND_SET_SUBTITLE_TRACK
|
||||
)
|
||||
}
|
||||
|
||||
override fun receiveCommand(view: MPVView, commandId: String?, args: ReadableArray?) {
|
||||
android.util.Log.d("MpvPlayerViewManager", "receiveCommand: $commandId, args: $args")
|
||||
when (commandId) {
|
||||
"seek" -> {
|
||||
val position = args?.getDouble(0)
|
||||
android.util.Log.d("MpvPlayerViewManager", "Seek command received: position=$position")
|
||||
position?.let { view.seekTo(it) }
|
||||
}
|
||||
"setAudioTrack" -> {
|
||||
args?.getInt(0)?.let { view.setAudioTrack(it) }
|
||||
}
|
||||
"setSubtitleTrack" -> {
|
||||
args?.getInt(0)?.let { view.setSubtitleTrack(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// React Props
|
||||
|
||||
@ReactProp(name = "source")
|
||||
fun setSource(view: MPVView, source: String?) {
|
||||
source?.let { view.setDataSource(it) }
|
||||
}
|
||||
|
||||
@ReactProp(name = "paused")
|
||||
fun setPaused(view: MPVView, paused: Boolean) {
|
||||
view.setPaused(paused)
|
||||
}
|
||||
|
||||
@ReactProp(name = "volume", defaultFloat = 1.0f)
|
||||
fun setVolume(view: MPVView, volume: Float) {
|
||||
view.setVolume(volume.toDouble())
|
||||
}
|
||||
|
||||
@ReactProp(name = "rate", defaultFloat = 1.0f)
|
||||
fun setRate(view: MPVView, rate: Float) {
|
||||
view.setSpeed(rate.toDouble())
|
||||
}
|
||||
|
||||
// Handle backgroundColor prop to prevent crash from React Native style system
|
||||
@ReactProp(name = "backgroundColor", customType = "Color")
|
||||
fun setBackgroundColor(view: MPVView, color: Int?) {
|
||||
// Intentionally ignoring - background color would block the TextureView content
|
||||
// Leave the view transparent
|
||||
}
|
||||
|
||||
@ReactProp(name = "resizeMode")
|
||||
fun setResizeMode(view: MPVView, resizeMode: String?) {
|
||||
view.setResizeMode(resizeMode ?: "contain")
|
||||
}
|
||||
|
||||
@ReactProp(name = "headers")
|
||||
fun setHeaders(view: MPVView, headers: com.facebook.react.bridge.ReadableMap?) {
|
||||
if (headers != null) {
|
||||
val headerMap = mutableMapOf<String, String>()
|
||||
val iterator = headers.keySetIterator()
|
||||
while (iterator.hasNextKey()) {
|
||||
val key = iterator.nextKey()
|
||||
headers.getString(key)?.let { value ->
|
||||
headerMap[key] = value
|
||||
}
|
||||
}
|
||||
view.setHeaders(headerMap)
|
||||
} else {
|
||||
view.setHeaders(null)
|
||||
}
|
||||
}
|
||||
|
||||
@ReactProp(name = "decoderMode")
|
||||
fun setDecoderMode(view: MPVView, decoderMode: String?) {
|
||||
view.decoderMode = decoderMode ?: "auto"
|
||||
}
|
||||
|
||||
@ReactProp(name = "gpuMode")
|
||||
fun setGpuMode(view: MPVView, gpuMode: String?) {
|
||||
view.gpuMode = gpuMode ?: "gpu"
|
||||
}
|
||||
|
||||
// Subtitle Styling Props
|
||||
|
||||
@ReactProp(name = "subtitleSize", defaultInt = 48)
|
||||
fun setSubtitleSize(view: MPVView, size: Int) {
|
||||
view.setSubtitleSize(size)
|
||||
}
|
||||
|
||||
@ReactProp(name = "subtitleColor")
|
||||
fun setSubtitleColor(view: MPVView, color: String?) {
|
||||
view.setSubtitleColor(color ?: "#FFFFFF")
|
||||
}
|
||||
|
||||
@ReactProp(name = "subtitleBackgroundOpacity", defaultFloat = 0.0f)
|
||||
fun setSubtitleBackgroundOpacity(view: MPVView, opacity: Float) {
|
||||
// Black background with user-specified opacity
|
||||
view.setSubtitleBackgroundColor("#000000", opacity)
|
||||
}
|
||||
|
||||
@ReactProp(name = "subtitleBorderSize", defaultInt = 3)
|
||||
fun setSubtitleBorderSize(view: MPVView, size: Int) {
|
||||
view.setSubtitleBorderSize(size)
|
||||
}
|
||||
|
||||
@ReactProp(name = "subtitleBorderColor")
|
||||
fun setSubtitleBorderColor(view: MPVView, color: String?) {
|
||||
view.setSubtitleBorderColor(color ?: "#000000")
|
||||
}
|
||||
|
||||
@ReactProp(name = "subtitleShadowEnabled", defaultBoolean = true)
|
||||
fun setSubtitleShadowEnabled(view: MPVView, enabled: Boolean) {
|
||||
view.setSubtitleShadow(enabled, if (enabled) 2 else 0)
|
||||
}
|
||||
|
||||
@ReactProp(name = "subtitlePosition", defaultInt = 100)
|
||||
fun setSubtitlePosition(view: MPVView, pos: Int) {
|
||||
view.setSubtitlePosition(pos)
|
||||
}
|
||||
|
||||
@ReactProp(name = "subtitleDelay", defaultFloat = 0.0f)
|
||||
fun setSubtitleDelay(view: MPVView, delay: Float) {
|
||||
view.setSubtitleDelay(delay.toDouble())
|
||||
}
|
||||
|
||||
@ReactProp(name = "subtitleAlignment")
|
||||
fun setSubtitleAlignment(view: MPVView, align: String?) {
|
||||
view.setSubtitleAlignment(align ?: "center")
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 7 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 5 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 9.9 KiB |
|
|
@ -3,5 +3,5 @@
|
|||
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
|
||||
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
|
||||
<string name="expo_system_ui_user_interface_style" translatable="false">dark</string>
|
||||
<string name="expo_runtime_version">1.2.6</string>
|
||||
<string name="expo_runtime_version">1.3.5</string>
|
||||
</resources>
|
||||
|
|
@ -1,6 +1,14 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = "35.0.0"
|
||||
minSdkVersion = 24
|
||||
compileSdkVersion = 35
|
||||
targetSdkVersion = 35
|
||||
castFrameworkVersion = "22.1.0"
|
||||
ndkVersion = "29.0.14206865" // Required for libmpv AAR built with NDK r29
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
|
@ -9,6 +17,7 @@ buildscript {
|
|||
classpath('com.android.tools.build:gradle')
|
||||
classpath('com.facebook.react:react-native-gradle-plugin')
|
||||
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
|
||||
classpath("io.sentry:sentry-android-gradle-plugin:5.12.2")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
defaults.url=https://sentry.io/
|
||||
defaults.org=tapframe
|
||||
defaults.project=react-native
|
||||
# Using SENTRY_AUTH_TOKEN environment variable
|
||||
auth.token=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
|
||||
|
|
|
|||
32
app.json
|
|
@ -2,7 +2,7 @@
|
|||
"expo": {
|
||||
"name": "Nuvio",
|
||||
"slug": "nuvio",
|
||||
"version": "1.2.6",
|
||||
"version": "1.3.5",
|
||||
"orientation": "default",
|
||||
"backgroundColor": "#020404",
|
||||
"icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png",
|
||||
|
|
@ -17,15 +17,17 @@
|
|||
"ios": {
|
||||
"supportsTablet": true,
|
||||
"icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png",
|
||||
"buildNumber": "21",
|
||||
"buildNumber": "33",
|
||||
"infoPlist": {
|
||||
"NSAppTransportSecurity": {
|
||||
"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"
|
||||
|
|
@ -45,10 +47,11 @@
|
|||
"icon": "./assets/android/mipmap-xxxhdpi/ic_launcher.png",
|
||||
"permissions": [
|
||||
"INTERNET",
|
||||
"WAKE_LOCK"
|
||||
"WAKE_LOCK",
|
||||
"android.permission.WRITE_SETTINGS"
|
||||
],
|
||||
"package": "com.nuvio.app",
|
||||
"versionCode": 21,
|
||||
"versionCode": 33,
|
||||
"architectures": [
|
||||
"arm64-v8a",
|
||||
"armeabi-v7a",
|
||||
|
|
@ -57,7 +60,6 @@
|
|||
],
|
||||
"jsEngine": "hermes"
|
||||
},
|
||||
|
||||
"extra": {
|
||||
"eas": {
|
||||
"projectId": "909107b8-fe61-45ce-b02f-b02510d306a6"
|
||||
|
|
@ -80,21 +82,21 @@
|
|||
"username": "nayifleo"
|
||||
}
|
||||
],
|
||||
"react-native-bottom-tabs",
|
||||
[
|
||||
"expo-libvlc-player",
|
||||
"react-native-google-cast",
|
||||
{
|
||||
"localNetworkPermission": "Allow $(PRODUCT_NAME) to access your local network",
|
||||
"supportsBackgroundPlayback": true
|
||||
"receiverAppId": "CC1AD845",
|
||||
"iosStartDiscoveryAfterFirstTapOnCastButton": true
|
||||
}
|
||||
],
|
||||
"react-native-bottom-tabs"
|
||||
]
|
||||
],
|
||||
"updates": {
|
||||
"enabled": true,
|
||||
"checkAutomatically": "ON_ERROR_RECOVERY",
|
||||
"fallbackToCacheTimeout": 30000,
|
||||
"url": "https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest"
|
||||
"url": "https://ota.nuvioapp.space/api/manifest"
|
||||
},
|
||||
"runtimeVersion": "1.2.6"
|
||||
"runtimeVersion": "1.3.5"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 162 KiB |
|
Before Width: | Height: | Size: 166 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 76 KiB |
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
|
@ -1,5 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 8.8 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 6.1 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.3 KiB |
BIN
assets/android/mipmap-ldpi/ic_launcher_round.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 8 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 106 KiB |
|
|
@ -1,4 +1,3 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#2f2f2f</color>
|
||||
<color name="ic_launcher_background">#000000</color>
|
||||
</resources>
|
||||
|
|
@ -1,128 +1,128 @@
|
|||
{
|
||||
"images":[
|
||||
"images": [
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"20x20",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-20x20@2x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "20x20",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-20x20@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"20x20",
|
||||
"scale":"3x",
|
||||
"filename":"Icon-App-20x20@3x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "20x20",
|
||||
"scale": "3x",
|
||||
"filename": "Icon-App-20x20@3x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"29x29",
|
||||
"scale":"1x",
|
||||
"filename":"Icon-App-29x29@1x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "29x29",
|
||||
"scale": "1x",
|
||||
"filename": "Icon-App-29x29@1x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"29x29",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-29x29@2x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "29x29",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-29x29@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"29x29",
|
||||
"scale":"3x",
|
||||
"filename":"Icon-App-29x29@3x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "29x29",
|
||||
"scale": "3x",
|
||||
"filename": "Icon-App-29x29@3x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"40x40",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-40x40@2x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "40x40",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-40x40@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"40x40",
|
||||
"scale":"3x",
|
||||
"filename":"Icon-App-40x40@3x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "40x40",
|
||||
"scale": "3x",
|
||||
"filename": "Icon-App-40x40@3x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"60x60",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-60x60@2x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "60x60",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-60x60@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"60x60",
|
||||
"scale":"3x",
|
||||
"filename":"Icon-App-60x60@3x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "60x60",
|
||||
"scale": "3x",
|
||||
"filename": "Icon-App-60x60@3x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"iphone",
|
||||
"size":"76x76",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-76x76@2x.png"
|
||||
"idiom": "iphone",
|
||||
"size": "76x76",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-76x76@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"20x20",
|
||||
"scale":"1x",
|
||||
"filename":"Icon-App-20x20@1x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "20x20",
|
||||
"scale": "1x",
|
||||
"filename": "Icon-App-20x20@1x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"20x20",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-20x20@2x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "20x20",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-20x20@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"29x29",
|
||||
"scale":"1x",
|
||||
"filename":"Icon-App-29x29@1x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "29x29",
|
||||
"scale": "1x",
|
||||
"filename": "Icon-App-29x29@1x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"29x29",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-29x29@2x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "29x29",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-29x29@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"40x40",
|
||||
"scale":"1x",
|
||||
"filename":"Icon-App-40x40@1x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "40x40",
|
||||
"scale": "1x",
|
||||
"filename": "Icon-App-40x40@1x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"40x40",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-40x40@2x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "40x40",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-40x40@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"76x76",
|
||||
"scale":"1x",
|
||||
"filename":"Icon-App-76x76@1x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "76x76",
|
||||
"scale": "1x",
|
||||
"filename": "Icon-App-76x76@1x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"76x76",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-76x76@2x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "76x76",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-76x76@2x.png"
|
||||
},
|
||||
{
|
||||
"idiom":"ipad",
|
||||
"size":"83.5x83.5",
|
||||
"scale":"2x",
|
||||
"filename":"Icon-App-83.5x83.5@2x.png"
|
||||
"idiom": "ipad",
|
||||
"size": "83.5x83.5",
|
||||
"scale": "2x",
|
||||
"filename": "Icon-App-83.5x83.5@2x.png"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"scale" : "1x",
|
||||
"filename" : "ItunesArtwork@2x.png"
|
||||
"size": "1024x1024",
|
||||
"idiom": "ios-marketing",
|
||||
"scale": "1x",
|
||||
"filename": "ItunesArtwork@2x.png"
|
||||
}
|
||||
],
|
||||
"info":{
|
||||
"version":1,
|
||||
"author":"easyappicon"
|
||||
"info": {
|
||||
"version": 1,
|
||||
"author": "easyappicon"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 785 B After Width: | Height: | Size: 760 B |
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5 KiB |
|
Before Width: | Height: | Size: 2 KiB After Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 5 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 398 KiB |
|
Before Width: | Height: | Size: 85 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 280 KiB After Width: | Height: | Size: 398 KiB |
|
Before Width: | Height: | Size: 583 KiB After Width: | Height: | Size: 840 KiB |
BIN
assets/nuviotext.png
Normal file
|
After Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
|
@ -1,6 +1,40 @@
|
|||
import React, { useEffect, useRef, useState, useCallback } from 'react';
|
||||
import React, { useEffect, useRef, useState, useCallback, useMemo } from 'react';
|
||||
import { Platform, Animated, TouchableWithoutFeedback, View } from 'react-native';
|
||||
import Video, { VideoRef, SelectedTrack, BufferingStrategyType, ResizeMode } from 'react-native-video';
|
||||
import RNImmersiveMode from 'react-native-immersive-mode';
|
||||
|
||||
// Subtitle style configuration interface - matches ExoPlayer's SubtitleStyle
|
||||
export interface SubtitleStyleConfig {
|
||||
// Font size in SP (scale-independent pixels) for subtitle text
|
||||
// Default: -1 (uses system default)
|
||||
fontSize?: number;
|
||||
|
||||
// Padding values in pixels
|
||||
paddingTop?: number;
|
||||
paddingBottom?: number;
|
||||
paddingLeft?: number;
|
||||
paddingRight?: number;
|
||||
|
||||
// Opacity of subtitles (0.0 to 1.0)
|
||||
// 0 = hidden, 1 = fully visible
|
||||
opacity?: number;
|
||||
|
||||
// Whether subtitles should follow video position when video is resized
|
||||
// true = subtitles stay within video bounds
|
||||
// false = subtitles can extend beyond video bounds
|
||||
subtitlesFollowVideo?: boolean;
|
||||
}
|
||||
|
||||
// Default subtitle style configuration
|
||||
export const DEFAULT_SUBTITLE_STYLE: SubtitleStyleConfig = {
|
||||
fontSize: 18,
|
||||
paddingTop: 0,
|
||||
paddingBottom: 60,
|
||||
paddingLeft: 16,
|
||||
paddingRight: 16,
|
||||
opacity: 1,
|
||||
subtitlesFollowVideo: true,
|
||||
};
|
||||
|
||||
interface VideoPlayerProps {
|
||||
src: string;
|
||||
|
|
@ -11,6 +45,8 @@ interface VideoPlayerProps {
|
|||
selectedAudioTrack?: SelectedTrack;
|
||||
selectedTextTrack?: SelectedTrack;
|
||||
resizeMode?: ResizeMode;
|
||||
// Subtitle customization - pass custom subtitle styling
|
||||
subtitleStyle?: SubtitleStyleConfig;
|
||||
onProgress?: (data: { currentTime: number; playableDuration: number }) => void;
|
||||
onLoad?: (data: { duration: number }) => void;
|
||||
onError?: (error: any) => void;
|
||||
|
|
@ -28,6 +64,7 @@ export const AndroidVideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||
selectedAudioTrack,
|
||||
selectedTextTrack,
|
||||
resizeMode = 'contain' as ResizeMode,
|
||||
subtitleStyle: customSubtitleStyle,
|
||||
onProgress,
|
||||
onLoad,
|
||||
onError,
|
||||
|
|
@ -40,6 +77,34 @@ export const AndroidVideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||
const [isSeeking, setIsSeeking] = useState(false);
|
||||
const [lastSeekTime, setLastSeekTime] = useState<number>(0);
|
||||
|
||||
// Merge custom subtitle style with defaults
|
||||
const subtitleStyle = useMemo(() => ({
|
||||
...DEFAULT_SUBTITLE_STYLE,
|
||||
...customSubtitleStyle,
|
||||
}), [customSubtitleStyle]);
|
||||
|
||||
// Enable immersive mode when video player mounts, disable when it unmounts
|
||||
useEffect(() => {
|
||||
if (Platform.OS === 'android') {
|
||||
try {
|
||||
RNImmersiveMode.setBarMode('Bottom');
|
||||
RNImmersiveMode.fullLayout(true);
|
||||
} catch (error) {
|
||||
console.log('Immersive mode error:', error);
|
||||
}
|
||||
|
||||
return () => {
|
||||
// Restore navigation bar when video player unmounts
|
||||
try {
|
||||
RNImmersiveMode.setBarMode('Normal');
|
||||
RNImmersiveMode.fullLayout(false);
|
||||
} catch (error) {
|
||||
console.log('Immersive mode cleanup error:', error);
|
||||
}
|
||||
};
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Only render on Android
|
||||
if (Platform.OS !== 'android') {
|
||||
return null;
|
||||
|
|
@ -109,13 +174,21 @@ export const AndroidVideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||
rate={1.0}
|
||||
repeat={false}
|
||||
reportBandwidth={true}
|
||||
textTracks={[]}
|
||||
useTextureView={false}
|
||||
useTextureView={true}
|
||||
disableFocus={false}
|
||||
minLoadRetryCount={3}
|
||||
automaticallyWaitsToMinimizeStalling={true}
|
||||
hideShutterView={false}
|
||||
shutterColor="#000000"
|
||||
subtitleStyle={{
|
||||
fontSize: subtitleStyle.fontSize,
|
||||
paddingTop: subtitleStyle.paddingTop,
|
||||
paddingBottom: subtitleStyle.paddingBottom,
|
||||
paddingLeft: subtitleStyle.paddingLeft,
|
||||
paddingRight: subtitleStyle.paddingRight,
|
||||
opacity: subtitleStyle.opacity,
|
||||
subtitlesFollowVideo: subtitleStyle.subtitlesFollowVideo,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
244
docs/DOCUMENTATION.md
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
# Nuvio Streaming Project Documentation
|
||||
|
||||
This document provides a comprehensive, step-by-step guide on how to build, run, and develop the Nuvio Streaming application for both Android and iOS platforms. It covers prerequisites, initial setup, prebuilding, and native execution.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Prerequisites](#prerequisites)
|
||||
2. [Project Setup](#project-setup)
|
||||
3. [Understanding Prebuild](#understanding-prebuild)
|
||||
4. [Running on Android](#running-on-android)
|
||||
5. [Running on iOS](#running-on-ios)
|
||||
6. [Troubleshooting](#troubleshooting)
|
||||
7. [Useful Commands](#useful-commands)
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before you begin, ensure your development environment is correctly set up.
|
||||
|
||||
### General Tools
|
||||
- **Node.js**: Install the Long Term Support (LTS) version (v18 or newer recommended). [Download Node.js](https://nodejs.org/)
|
||||
- **Git**: For version control. [Download Git](https://git-scm.com/)
|
||||
- **Watchman** (macOS users): Highly recommended for better file watching performance.
|
||||
```bash
|
||||
brew install watchman
|
||||
```
|
||||
|
||||
### Environment Configuration
|
||||
|
||||
**All environment variables are optional for development.**
|
||||
The app is designed to run "out of the box" without a `.env` file. Features requiring API keys (like Trakt syncing) will simply be disabled or use default fallbacks.
|
||||
|
||||
3. **Setup (Optional)**:
|
||||
If you wish to enable specific features, create a `.env` file:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
**Recommended Variables:**
|
||||
* `EXPO_PUBLIC_TRAKT_CLIENT_ID` (etc): Enables Trakt integration.
|
||||
|
||||
### For Android Development
|
||||
|
||||
1. **Java Development Kit (JDK)**: Install JDK 11 or newer (JDK 17 is often recommended for modern React Native).
|
||||
- [OpenJDK](https://openjdk.org/) or [Azul Zulu](https://www.azul.com/downloads/).
|
||||
- Ensure your `JAVA_HOME` environment variable is set.
|
||||
2. **Android Studio**:
|
||||
- Install [Android Studio](https://developer.android.com/studio).
|
||||
- During installation, ensure the **Android SDK**, **Android SDK Platform-Tools**, and **Android Virtual Device** are selected.
|
||||
- Set up your `ANDROID_HOME` (or `ANDROID_SDK_ROOT`) environment variable pointing to your SDK location.
|
||||
|
||||
### For iOS Development (macOS only)
|
||||
|
||||
1. **Xcode**: Install the latest version of Xcode from the Mac App Store.
|
||||
2. **Xcode Command Line Tools**:
|
||||
```bash
|
||||
xcode-select --install
|
||||
```
|
||||
3. **CocoaPods**: Required for managing iOS dependencies.
|
||||
```bash
|
||||
sudo gem install cocoapods
|
||||
```
|
||||
*Note: On Apple Silicon (M1/M2/M3) Macs, you might need to use Homebrew to install Ruby or manage Cocoapods differently if you encounter issues.*
|
||||
|
||||
---
|
||||
|
||||
## Project Setup
|
||||
|
||||
1. **Clone the Repository**
|
||||
```bash
|
||||
git clone https://github.com/tapframe/NuvioStreaming.git
|
||||
cd NuvioStreaming
|
||||
```
|
||||
|
||||
2. **Install Dependencies**
|
||||
Install the project dependencies using `npm`.
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
*Note: If you encounter peer dependency conflicts, you can try `npm install --legacy-peer-deps`, but typically `npm install` should work if the `package.json` is well-maintained.*
|
||||
|
||||
---
|
||||
|
||||
## Understanding Prebuild
|
||||
|
||||
This project is built with **Expo**. Since it may use native modules that are not included in the standard Expo Go client (Custom Dev Client), we often need to "prebuild" the project to generate the native `android` and `ios` directories.
|
||||
|
||||
**What `npx expo prebuild` does:**
|
||||
- It generates the native `android` and `ios` project directories based on your configuration in `app.json` / `app.config.js`.
|
||||
- It applies any Config Plugins specified.
|
||||
- It prepares the project to be built locally using Android Studio or Xcode tools (Gradle/Podfile).
|
||||
|
||||
You typically run this command before compiling the native app if you have made changes to the native configuration (e.g., icons, splash screens, permissions in `app.json`).
|
||||
|
||||
```bash
|
||||
npx expo prebuild
|
||||
```
|
||||
|
||||
> [!WARNING]
|
||||
> **Important:** Running `npx expo prebuild --clean` will delete the `android` and `ios` directories.
|
||||
> If you have manually modified files in these directories (that are not covered by Expo config plugins), they will be lost.
|
||||
> **Recommendation:** Immediately after running prebuild, use `git status` to see what changed. If important files were deleted or reset, use `git checkout <path/to/file>` to revert them to your custom version.
|
||||
> Example:
|
||||
> ```bash
|
||||
> git checkout android/build.gradle
|
||||
> ```
|
||||
|
||||
To prebuild for a specific platform:
|
||||
```bash
|
||||
npx expo prebuild --platform android
|
||||
npx expo prebuild --platform ios
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Running on Android
|
||||
|
||||
Follow these steps to build and run the app on an Android Emulator or connected physical device.
|
||||
|
||||
**Step 1: Start an Emulator or Connect a Device**
|
||||
- **Emulator**: Open Android Studio, go to "Device Manager", and start a virtual device.
|
||||
- **Physical Device**: Connect it via USB, enable **Developer Options** and **USB Debugging**. Verify connection with `adb devices`.
|
||||
|
||||
**Step 2: Generate Native Directories (Prebuild)**
|
||||
If you haven't done so (or if you cleaned the project):
|
||||
```bash
|
||||
npx expo prebuild --platform android
|
||||
```
|
||||
|
||||
**Step 3: Compile and Run**
|
||||
Run the following command to build the Android app and launch it on your device/emulator:
|
||||
```bash
|
||||
npx expo run:android
|
||||
```
|
||||
*This command will start the Metro bundler in a new window/tab and begin the Gradle build process.*
|
||||
|
||||
**Alternative: Open in Android Studio**
|
||||
If you prefer identifying build errors in the IDE:
|
||||
1. Run `npx expo prebuild --platform android`.
|
||||
2. Open Android Studio.
|
||||
3. Select "Open an existing Android Studio Project" and choose the `android` folder inside `NuvioStreaming`.
|
||||
4. Wait for Gradle sync to complete, then press the **Run** (green play) button.
|
||||
|
||||
---
|
||||
|
||||
## Running on iOS
|
||||
|
||||
**Note:** iOS development requires a Mac with Xcode.
|
||||
|
||||
**Step 1: Generate Native Directories (Prebuild)**
|
||||
```bash
|
||||
npx expo prebuild --platform ios
|
||||
```
|
||||
*This will generate the `ios` folder and automatically run `pod install` inside it.*
|
||||
|
||||
**Step 2: Compile and Run**
|
||||
Run the following command to build the iOS app and launch it on the iOS Simulator:
|
||||
```bash
|
||||
npx expo run:ios
|
||||
```
|
||||
*To run on a specific simulator device:*
|
||||
```bash
|
||||
npx expo run:ios --device "iPhone 15 Pro"
|
||||
```
|
||||
|
||||
**Step 3: Running on a Physical iOS Device**
|
||||
1. You need an Apple Developer Account (a free account works for local testing, but requires re-signing every 7 days).
|
||||
2. Open the project in Xcode:
|
||||
```bash
|
||||
xcode-open ios/nuvio.xcworkspace
|
||||
```
|
||||
*(Or simple open `ios/nuvio.xcworkspace` in Xcode manually)*.
|
||||
3. In Xcode, select your project target, go to the **Signing & Capabilities** tab.
|
||||
4. Select your **Team**.
|
||||
5. Connect your device via USB.
|
||||
6. Select your device from the build target dropdown (top bar).
|
||||
7. Press **Cmd + R** to build and run.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "CocoaPods not found" or Pod install errors
|
||||
If `npx expo run:ios` fails during pod installation:
|
||||
```bash
|
||||
cd ios
|
||||
pod install
|
||||
cd ..
|
||||
```
|
||||
If you are on an Apple Silicon Mac and have issues:
|
||||
```bash
|
||||
cd ios
|
||||
arch -x86_64 pod install
|
||||
cd ..
|
||||
```
|
||||
|
||||
### Build Failures after changing dependencies
|
||||
If you install a new library that includes native code, you must rebuild the native app.
|
||||
1. Stop the Metro server.
|
||||
2. Run the platform-specific run command again:
|
||||
```bash
|
||||
npx expo run:android
|
||||
# or
|
||||
npx expo run:ios
|
||||
```
|
||||
|
||||
### General Clean Up
|
||||
If things are acting weird (stale cache, weird build errors), try cleaning the project:
|
||||
|
||||
**1. Clear Metro Cache:**
|
||||
```bash
|
||||
npx expo start -c
|
||||
```
|
||||
|
||||
**2. Clean Native Directories (Drastic Measure):**
|
||||
WARNING: This deletes the `android` and `ios` folders. Only do this if you can regenerate them with `prebuild`.
|
||||
```bash
|
||||
rm -rf android ios
|
||||
npx expo prebuild
|
||||
```
|
||||
*Note: If you have manual changes in `android` or `ios` folders that usually shouldn't be there in a managed workflow, they will be lost. Ensure all native config is configured via Config Plugins in `app.json`.*
|
||||
|
||||
### "SDK location not found" (Android)
|
||||
Create a `local.properties` file in the `android` directory with the path to your SDK:
|
||||
```properties
|
||||
# android/local.properties
|
||||
sdk.dir=/Users/YOUR_USERNAME/Library/Android/sdk
|
||||
```
|
||||
(Replace `YOUR_USERNAME` with your actual username).
|
||||
|
||||
---
|
||||
|
||||
## Useful Commands
|
||||
|
||||
| Command | Description |
|
||||
|---------|-------------|
|
||||
| `npm start` or `npx expo start` | Starts the Metro Bundler (development server). |
|
||||
| `npx expo start --clear` | Starts the bundler with a clear cache. |
|
||||
| `npx expo prebuild` | Generates native `android` and `ios` code. |
|
||||
| `npx expo prebuild --clean` | Deletes existing native folders and regenerates them. |
|
||||
| `npx expo run:android` | Builds and opens the app on Android. |
|
||||
| `npx expo run:ios` | Builds and opens the app on iOS. |
|
||||
| `npx expo install <package>` | Installs a library compatible with your Expo SDK version. |
|
||||
2299
index.html
|
|
@ -1,55 +0,0 @@
|
|||
//
|
||||
// KSPlayerManager.m
|
||||
// Nuvio
|
||||
//
|
||||
// Created by KSPlayer integration
|
||||
//
|
||||
|
||||
#import <React/RCTViewManager.h>
|
||||
#import <React/RCTBridgeModule.h>
|
||||
#import <React/RCTEventEmitter.h>
|
||||
|
||||
@interface RCT_EXTERN_MODULE(KSPlayerViewManager, RCTViewManager)
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(source, NSDictionary)
|
||||
RCT_EXPORT_VIEW_PROPERTY(paused, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(volume, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(rate, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(audioTrack, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(textTrack, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(allowsExternalPlayback, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(usesExternalPlaybackWhileExternalScreenIsActive, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(subtitleBottomOffset, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(subtitleFontSize, NSNumber)
|
||||
RCT_EXPORT_VIEW_PROPERTY(resizeMode, NSString)
|
||||
|
||||
// Event properties
|
||||
RCT_EXPORT_VIEW_PROPERTY(onLoad, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onProgress, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onBuffering, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onEnd, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onError, RCTDirectEventBlock)
|
||||
RCT_EXPORT_VIEW_PROPERTY(onBufferingProgress, RCTDirectEventBlock)
|
||||
|
||||
RCT_EXTERN_METHOD(seek:(nonnull NSNumber *)node toTime:(nonnull NSNumber *)time)
|
||||
RCT_EXTERN_METHOD(setSource:(nonnull NSNumber *)node source:(nonnull NSDictionary *)source)
|
||||
RCT_EXTERN_METHOD(setPaused:(nonnull NSNumber *)node paused:(BOOL)paused)
|
||||
RCT_EXTERN_METHOD(setVolume:(nonnull NSNumber *)node volume:(nonnull NSNumber *)volume)
|
||||
RCT_EXTERN_METHOD(setPlaybackRate:(nonnull NSNumber *)node rate:(nonnull NSNumber *)rate)
|
||||
RCT_EXTERN_METHOD(setAudioTrack:(nonnull NSNumber *)node trackId:(nonnull NSNumber *)trackId)
|
||||
RCT_EXTERN_METHOD(setTextTrack:(nonnull NSNumber *)node trackId:(nonnull NSNumber *)trackId)
|
||||
RCT_EXTERN_METHOD(getTracks:(nonnull NSNumber *)node resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(setAllowsExternalPlayback:(nonnull NSNumber *)node allows:(BOOL)allows)
|
||||
RCT_EXTERN_METHOD(setUsesExternalPlaybackWhileExternalScreenIsActive:(nonnull NSNumber *)node uses:(BOOL)uses)
|
||||
RCT_EXTERN_METHOD(getAirPlayState:(nonnull NSNumber *)node resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(showAirPlayPicker:(nonnull NSNumber *)node)
|
||||
|
||||
@end
|
||||
|
||||
@interface RCT_EXTERN_MODULE(KSPlayerModule, RCTEventEmitter)
|
||||
|
||||
RCT_EXTERN_METHOD(getTracks:(nonnull NSNumber *)nodeTag resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(getAirPlayState:(nonnull NSNumber *)nodeTag resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
|
||||
RCT_EXTERN_METHOD(showAirPlayPicker:(nonnull NSNumber *)nodeTag)
|
||||
|
||||
@end
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
//
|
||||
// KSPlayerModule.swift
|
||||
// Nuvio
|
||||
//
|
||||
// Created by KSPlayer integration
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import KSPlayer
|
||||
import React
|
||||
|
||||
@objc(KSPlayerModule)
|
||||
class KSPlayerModule: RCTEventEmitter {
|
||||
override static func requiresMainQueueSetup() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func supportedEvents() -> [String]! {
|
||||
return [
|
||||
"KSPlayer-onLoad",
|
||||
"KSPlayer-onProgress",
|
||||
"KSPlayer-onBuffering",
|
||||
"KSPlayer-onEnd",
|
||||
"KSPlayer-onError"
|
||||
]
|
||||
}
|
||||
|
||||
@objc func getTracks(_ nodeTag: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
||||
DispatchQueue.main.async {
|
||||
if let viewManager = self.bridge.module(for: KSPlayerViewManager.self) as? KSPlayerViewManager {
|
||||
viewManager.getTracks(nodeTag, resolve: resolve, reject: reject)
|
||||
} else {
|
||||
reject("NO_VIEW_MANAGER", "KSPlayerViewManager not found", nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func getAirPlayState(_ nodeTag: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
||||
DispatchQueue.main.async {
|
||||
if let viewManager = self.bridge.module(for: KSPlayerViewManager.self) as? KSPlayerViewManager {
|
||||
viewManager.getAirPlayState(nodeTag, resolve: resolve, reject: reject)
|
||||
} else {
|
||||
reject("NO_VIEW_MANAGER", "KSPlayerViewManager not found", nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func showAirPlayPicker(_ nodeTag: NSNumber) {
|
||||
print("[KSPlayerModule] showAirPlayPicker called for nodeTag: \(nodeTag)")
|
||||
DispatchQueue.main.async {
|
||||
if let viewManager = self.bridge.module(for: KSPlayerViewManager.self) as? KSPlayerViewManager {
|
||||
print("[KSPlayerModule] Found KSPlayerViewManager, calling showAirPlayPicker")
|
||||
viewManager.showAirPlayPicker(nodeTag)
|
||||
} else {
|
||||
print("[KSPlayerModule] Could not find KSPlayerViewManager")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
//
|
||||
// KSPlayerViewManager.swift
|
||||
// Nuvio
|
||||
//
|
||||
// Created by KSPlayer integration
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import KSPlayer
|
||||
import React
|
||||
|
||||
@objc(KSPlayerViewManager)
|
||||
class KSPlayerViewManager: RCTViewManager {
|
||||
|
||||
// Not needed for RCTViewManager-based views; events are exported via Objective-C externs in KSPlayerManager.m
|
||||
override func view() -> UIView! {
|
||||
let view = KSPlayerView()
|
||||
view.viewManager = self
|
||||
return view
|
||||
}
|
||||
|
||||
override static func requiresMainQueueSetup() -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
override func constantsToExport() -> [AnyHashable : Any]! {
|
||||
return [
|
||||
"EventTypes": [
|
||||
"onLoad": "onLoad",
|
||||
"onProgress": "onProgress",
|
||||
"onBuffering": "onBuffering",
|
||||
"onEnd": "onEnd",
|
||||
"onError": "onError",
|
||||
"onBufferingProgress": "onBufferingProgress"
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
// No-op: events are sent via direct event blocks on the view
|
||||
|
||||
@objc func seek(_ node: NSNumber, toTime time: NSNumber) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.seek(to: TimeInterval(truncating: time))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setSource(_ node: NSNumber, source: NSDictionary) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.setSource(source)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setPaused(_ node: NSNumber, paused: Bool) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.setPaused(paused)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setVolume(_ node: NSNumber, volume: NSNumber) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.setVolume(Float(truncating: volume))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setPlaybackRate(_ node: NSNumber, rate: NSNumber) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.setPlaybackRate(Float(truncating: rate))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setAudioTrack(_ node: NSNumber, trackId: NSNumber) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.setAudioTrack(Int(truncating: trackId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setTextTrack(_ node: NSNumber, trackId: NSNumber) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.setTextTrack(Int(truncating: trackId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func getTracks(_ node: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
let tracks = view.getAvailableTracks()
|
||||
resolve(tracks)
|
||||
} else {
|
||||
reject("NO_VIEW", "KSPlayerView not found", nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AirPlay methods
|
||||
@objc func setAllowsExternalPlayback(_ node: NSNumber, allows: Bool) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.setAllowsExternalPlayback(allows)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func setUsesExternalPlaybackWhileExternalScreenIsActive(_ node: NSNumber, uses: Bool) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
view.setUsesExternalPlaybackWhileExternalScreenIsActive(uses)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func getAirPlayState(_ node: NSNumber, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) {
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
let airPlayState = view.getAirPlayState()
|
||||
resolve(airPlayState)
|
||||
} else {
|
||||
reject("NO_VIEW", "KSPlayerView not found", nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func showAirPlayPicker(_ node: NSNumber) {
|
||||
print("[KSPlayerViewManager] showAirPlayPicker called for node: \(node)")
|
||||
DispatchQueue.main.async {
|
||||
if let view = self.bridge.uiManager.view(forReactTag: node) as? KSPlayerView {
|
||||
print("[KSPlayerViewManager] Found KSPlayerView, calling showAirPlayPicker")
|
||||
view.showAirPlayPicker()
|
||||
} else {
|
||||
print("[KSPlayerViewManager] Could not find KSPlayerView for node: \(node)")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,12 +11,12 @@
|
|||
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
|
||||
2AA769395C1242F225F875AF /* ExpoModulesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E007C0BAC8C453623E81663 /* ExpoModulesProvider.swift */; };
|
||||
3E461D99554A48A4959DE609 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */; };
|
||||
8449DE1C42ADA4CF10BC6D93 /* libPods-Nuvio.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2DDA95A7458A78E817D9496D /* libPods-Nuvio.a */; };
|
||||
9FBA88F42E86ECD700892850 /* KSPlayerViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBA88F32E86ECD700892850 /* KSPlayerViewManager.swift */; };
|
||||
9FBA88F52E86ECD700892850 /* KSPlayerModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBA88F12E86ECD700892850 /* KSPlayerModule.swift */; };
|
||||
9FBA88F62E86ECD700892850 /* KSPlayerManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9FBA88F02E86ECD700892850 /* KSPlayerManager.m */; };
|
||||
9FBA88F72E86ECD700892850 /* KSPlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9FBA88F22E86ECD700892850 /* KSPlayerView.swift */; };
|
||||
BB2F792D24A3F905000567C9 /* Expo.plist in Resources */ = {isa = PBXBuildFile; fileRef = BB2F792C24A3F905000567C9 /* Expo.plist */; };
|
||||
D66ACCC72CB69F1FF14A2585 /* libPods-Nuvio.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 436A6FCA2C83F29076E121BA /* libPods-Nuvio.a */; };
|
||||
F11748422D0307B40044C1D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F11748412D0307B40044C1D9 /* AppDelegate.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
|
|
@ -24,18 +24,18 @@
|
|||
13B07F961A680F5B00A75B9A /* Nuvio.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Nuvio.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = Nuvio/Images.xcassets; sourceTree = "<group>"; };
|
||||
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Nuvio/Info.plist; sourceTree = "<group>"; };
|
||||
2DDA95A7458A78E817D9496D /* libPods-Nuvio.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Nuvio.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
436A6FCA2C83F29076E121BA /* libPods-Nuvio.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Nuvio.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
49055D6E250FAFA21141FE49 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Nuvio/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
|
||||
6E0017E5203955A430ABF21B /* Pods-Nuvio.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nuvio.release.xcconfig"; path = "Target Support Files/Pods-Nuvio/Pods-Nuvio.release.xcconfig"; sourceTree = "<group>"; };
|
||||
5346BAA9EF8C9C8182D4485C /* Pods-Nuvio.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nuvio.release.xcconfig"; path = "Target Support Files/Pods-Nuvio/Pods-Nuvio.release.xcconfig"; sourceTree = "<group>"; };
|
||||
6E007C0BAC8C453623E81663 /* ExpoModulesProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; name = ExpoModulesProvider.swift; path = "Pods/Target Support Files/Pods-Nuvio/ExpoModulesProvider.swift"; sourceTree = "<group>"; };
|
||||
73BB213C2E9EEAC700EC03F8 /* NuvioRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = NuvioRelease.entitlements; path = Nuvio/NuvioRelease.entitlements; sourceTree = "<group>"; };
|
||||
9FBA88F02E86ECD700892850 /* KSPlayerManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = KSPlayerManager.m; sourceTree = "<group>"; };
|
||||
9FBA88F12E86ECD700892850 /* KSPlayerModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KSPlayerModule.swift; sourceTree = "<group>"; };
|
||||
9FBA88F22E86ECD700892850 /* KSPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KSPlayerView.swift; sourceTree = "<group>"; };
|
||||
9FBA88F32E86ECD700892850 /* KSPlayerViewManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KSPlayerViewManager.swift; sourceTree = "<group>"; };
|
||||
904B4A0A0308D3727268BA5E /* Pods-Nuvio.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nuvio.debug.xcconfig"; path = "Target Support Files/Pods-Nuvio/Pods-Nuvio.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
9FBA88F02E86ECD700892850 /* KSPlayerManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ../KSPlayer/RNBridge/KSPlayerManager.m; sourceTree = "<group>"; };
|
||||
9FBA88F12E86ECD700892850 /* KSPlayerModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ../KSPlayer/RNBridge/KSPlayerModule.swift; sourceTree = "<group>"; };
|
||||
9FBA88F22E86ECD700892850 /* KSPlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ../KSPlayer/RNBridge/KSPlayerView.swift; sourceTree = "<group>"; };
|
||||
9FBA88F32E86ECD700892850 /* KSPlayerViewManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ../KSPlayer/RNBridge/KSPlayerViewManager.swift; sourceTree = "<group>"; };
|
||||
AA286B85B6C04FC6940260E9 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = SplashScreen.storyboard; path = Nuvio/SplashScreen.storyboard; sourceTree = "<group>"; };
|
||||
BB2F792C24A3F905000567C9 /* Expo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Expo.plist; sourceTree = "<group>"; };
|
||||
BD6015E69A4861CCBD3C1D39 /* Pods-Nuvio.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Nuvio.debug.xcconfig"; path = "Target Support Files/Pods-Nuvio/Pods-Nuvio.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
|
||||
F11748412D0307B40044C1D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AppDelegate.swift; path = Nuvio/AppDelegate.swift; sourceTree = "<group>"; };
|
||||
F11748442D0722820044C1D9 /* Nuvio-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "Nuvio-Bridging-Header.h"; path = "Nuvio/Nuvio-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
|
|
@ -46,7 +46,7 @@
|
|||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
8449DE1C42ADA4CF10BC6D93 /* libPods-Nuvio.a in Frameworks */,
|
||||
D66ACCC72CB69F1FF14A2585 /* libPods-Nuvio.a in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -76,7 +76,7 @@
|
|||
isa = PBXGroup;
|
||||
children = (
|
||||
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
|
||||
2DDA95A7458A78E817D9496D /* libPods-Nuvio.a */,
|
||||
436A6FCA2C83F29076E121BA /* libPods-Nuvio.a */,
|
||||
);
|
||||
name = Frameworks;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -131,8 +131,8 @@
|
|||
D90A3959C97EE9926C513293 /* Pods */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
BD6015E69A4861CCBD3C1D39 /* Pods-Nuvio.debug.xcconfig */,
|
||||
6E0017E5203955A430ABF21B /* Pods-Nuvio.release.xcconfig */,
|
||||
904B4A0A0308D3727268BA5E /* Pods-Nuvio.debug.xcconfig */,
|
||||
5346BAA9EF8C9C8182D4485C /* Pods-Nuvio.release.xcconfig */,
|
||||
);
|
||||
path = Pods;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -152,15 +152,15 @@
|
|||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "Nuvio" */;
|
||||
buildPhases = (
|
||||
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */,
|
||||
3B2D9C1D63379C2F30AC0F2B /* [CP] Check Pods Manifest.lock */,
|
||||
99A79B70155E84EE1FB7F466 /* [Expo] Configure project */,
|
||||
13B07F871A680F5B00A75B9A /* Sources */,
|
||||
13B07F8C1A680F5B00A75B9A /* Frameworks */,
|
||||
13B07F8E1A680F5B00A75B9A /* Resources */,
|
||||
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
|
||||
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */,
|
||||
9B977D89FE30470F8C59964C /* Upload Debug Symbols to Sentry */,
|
||||
7051E1DA5B27A8E632AD8CB9 /* [CP] Embed Pods Frameworks */,
|
||||
9F740EE07B5F97C85979C145 /* [CP] Embed Pods Frameworks */,
|
||||
550CD54859274FE505BA4957 /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
|
|
@ -234,7 +234,7 @@
|
|||
shellPath = /bin/sh;
|
||||
shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n/bin/sh `\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode.sh'\"` `\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n";
|
||||
};
|
||||
08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = {
|
||||
3B2D9C1D63379C2F30AC0F2B /* [CP] Check Pods Manifest.lock */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
|
|
@ -256,31 +256,7 @@
|
|||
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
7051E1DA5B27A8E632AD8CB9 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Nuvio/Pods-Nuvio-frameworks.sh",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/MobileVLCKit/MobileVLCKit.framework/MobileVLCKit",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/React-Core-prebuilt/React.framework/React",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ReactNativeDependencies/ReactNativeDependencies.framework/ReactNativeDependencies",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/MobileVLCKit.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactNativeDependencies.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Nuvio/Pods-Nuvio-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
800E24972A6A228C8D4807E9 /* [CP] Copy Pods Resources */ = {
|
||||
550CD54859274FE505BA4957 /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
|
|
@ -297,6 +273,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 +301,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 +324,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 +352,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",
|
||||
);
|
||||
|
|
@ -412,6 +406,28 @@
|
|||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh `${NODE_BINARY:-node} --print \"require('path').dirname(require.resolve('@sentry/react-native/package.json')) + '/scripts/sentry-xcode-debug-files.sh'\"`";
|
||||
};
|
||||
9F740EE07B5F97C85979C145 /* [CP] Embed Pods Frameworks */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Nuvio/Pods-Nuvio-frameworks.sh",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/React-Core-prebuilt/React.framework/React",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/ReactNativeDependencies/ReactNativeDependencies.framework/ReactNativeDependencies",
|
||||
"${PODS_XCFRAMEWORKS_BUILD_DIR}/hermes-engine/Pre-built/hermes.framework/hermes",
|
||||
);
|
||||
name = "[CP] Embed Pods Frameworks";
|
||||
outputPaths = (
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/React.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/ReactNativeDependencies.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/hermes.framework",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Nuvio/Pods-Nuvio-frameworks.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
/* End PBXShellScriptBuildPhase section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
|
|
@ -433,13 +449,13 @@
|
|||
/* Begin XCBuildConfiguration section */
|
||||
13B07F941A680F5B00A75B9A /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = BD6015E69A4861CCBD3C1D39 /* Pods-Nuvio.debug.xcconfig */;
|
||||
baseConfigurationReference = 904B4A0A0308D3727268BA5E /* Pods-Nuvio.debug.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Nuvio/Nuvio.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = NLXTHANK2N;
|
||||
DEVELOPMENT_TEAM = 8QBDZ766S3;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||
"$(inherited)",
|
||||
|
|
@ -460,6 +476,7 @@
|
|||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
|
||||
PRODUCT_NAME = Nuvio;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_VERSION = 5.0;
|
||||
|
|
@ -470,13 +487,13 @@
|
|||
};
|
||||
13B07F951A680F5B00A75B9A /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
baseConfigurationReference = 6E0017E5203955A430ABF21B /* Pods-Nuvio.release.xcconfig */;
|
||||
baseConfigurationReference = 5346BAA9EF8C9C8182D4485C /* Pods-Nuvio.release.xcconfig */;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Nuvio/NuvioRelease.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = NLXTHANK2N;
|
||||
DEVELOPMENT_TEAM = 8QBDZ766S3;
|
||||
INFOPLIST_FILE = Nuvio/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
|
@ -490,8 +507,9 @@
|
|||
"-lc++",
|
||||
);
|
||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.hub;
|
||||
PRODUCT_NAME = Nuvio;
|
||||
SUPPORTS_MACCATALYST = YES;
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
||||
SWIFT_VERSION = 5.0;
|
||||
TARGETED_DEVICE_FAMILY = "1,2";
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 237 KiB After Width: | Height: | Size: 166 KiB |
|
|
@ -1,101 +1,110 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Nuvio</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.2.6</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>nuvio</string>
|
||||
<string>com.nuvio.app</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>exp+nuvio</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>21</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_http._tcp</string>
|
||||
</array>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your local network</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>This app does not require microphone access.</string>
|
||||
<key>RCTNewArchEnabled</key>
|
||||
<true/>
|
||||
<key>RCTRootViewBackgroundColor</key>
|
||||
<integer>4278322180</integer>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
</array>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>SplashScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<false/>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleDefault</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Dark</string>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
<dict>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>$(DEVELOPMENT_LANGUAGE)</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>Nuvio</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.2.10</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>nuvio</string>
|
||||
<string>com.nuvio.app</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<string>exp+nuvio</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>29</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
<string>12.0</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>NSBonjourServices</key>
|
||||
<array>
|
||||
<string>_http._tcp</string>
|
||||
<string>_googlecast._tcp</string>
|
||||
<string>_CC1AD845._googlecast._tcp</string>
|
||||
</array>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
<string>Allow $(PRODUCT_NAME) to access your local network</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>This app does not require microphone access.</string>
|
||||
<key>RCTNewArchEnabled</key>
|
||||
<true/>
|
||||
<key>RCTRootViewBackgroundColor</key>
|
||||
<integer>4278322180</integer>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
</array>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>SplashScreen</string>
|
||||
<key>UILaunchScreen</key>
|
||||
<dict>
|
||||
<key>UIColorName</key>
|
||||
<string>SplashScreenBackground</string>
|
||||
<key>UIImageName</key>
|
||||
<string>SplashScreenLegacy</string>
|
||||
</dict>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>arm64</string>
|
||||
</array>
|
||||
<key>UIRequiresFullScreen</key>
|
||||
<false/>
|
||||
<key>UIStatusBarStyle</key>
|
||||
<string>UIStatusBarStyleDefault</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations~ipad</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationPortraitUpsideDown</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Dark</string>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
<key>NSPrivacyAccessedAPITypeReasons</key>
|
||||
<array>
|
||||
<string>CA92.1</string>
|
||||
<string>C56D.1</string>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
<viewController storyboardIdentifier="SplashScreenViewController" id="EXPO-VIEWCONTROLLER-1" sceneMemberID="viewController">
|
||||
<view key="view" userInteractionEnabled="NO" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="EXPO-ContainerView" userLabel="ContainerView">
|
||||
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<imageView id="EXPO-SplashScreen" userLabel="SplashScreenLegacy" image="SplashScreenLegacy" contentMode="scaleAspectFit" clipsSubviews="true" userInteractionEnabled="false" translatesAutoresizingMaskIntoConstraints="false">
|
||||
<rect key="frame" x="0" y="0" width="414" height="736"/>
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@
|
|||
<key>EXUpdatesLaunchWaitMs</key>
|
||||
<integer>30000</integer>
|
||||
<key>EXUpdatesRuntimeVersion</key>
|
||||
<string>1.2.6</string>
|
||||
<string>1.2.11</string>
|
||||
<key>EXUpdatesURL</key>
|
||||
<string>https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest</string>
|
||||
<string>https://ota.nuvioapp.space/api/manifest</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -21,7 +21,7 @@ platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'
|
|||
prepare_react_native_project!
|
||||
|
||||
target 'Nuvio' do
|
||||
use_expo_modules!
|
||||
use_expo_modules!(exclude: ['expo-libvlc-player'])
|
||||
|
||||
if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
|
||||
config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"];
|
||||
|
|
@ -50,8 +50,9 @@ target 'Nuvio' do
|
|||
)
|
||||
|
||||
# KSPlayer dependencies
|
||||
pod 'KSPlayer', :git => 'https://github.com/kingslay/KSPlayer.git', :branch => 'main'
|
||||
pod 'DisplayCriteria', :git => 'https://github.com/kingslay/KSPlayer.git', :branch => 'main', :modular_headers => true
|
||||
# Use the local checkout so we can patch subtitle rendering (and other behaviors) without forking.
|
||||
pod 'KSPlayer', :path => '../KSPlayer'
|
||||
pod 'DisplayCriteria', :path => '../KSPlayer', :modular_headers => true
|
||||
pod 'FFmpegKit', :git => 'https://github.com/kingslay/FFmpegKit.git', :branch => 'main', :modular_headers => true
|
||||
pod 'Libass', :git => 'https://github.com/kingslay/FFmpegKit.git', :branch => 'main'
|
||||
|
||||
|
|
|
|||
357
ios/Podfile.lock
|
|
@ -1,17 +1,17 @@
|
|||
PODS:
|
||||
- DisplayCriteria (1.1.0)
|
||||
- EASClient (1.0.7):
|
||||
- EASClient (1.0.8):
|
||||
- ExpoModulesCore
|
||||
- EXApplication (7.0.7):
|
||||
- EXApplication (7.0.8):
|
||||
- ExpoModulesCore
|
||||
- EXConstants (18.0.9):
|
||||
- EXConstants (18.0.12):
|
||||
- ExpoModulesCore
|
||||
- EXJSONUtils (0.15.0)
|
||||
- EXManifests (1.0.8):
|
||||
- EXManifests (1.0.10):
|
||||
- ExpoModulesCore
|
||||
- EXNotifications (0.32.12):
|
||||
- EXNotifications (0.32.15):
|
||||
- ExpoModulesCore
|
||||
- Expo (54.0.13):
|
||||
- Expo (54.0.29):
|
||||
- ExpoModulesCore
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
|
|
@ -36,15 +36,15 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- expo-dev-client (6.0.15):
|
||||
- expo-dev-client (6.0.20):
|
||||
- EXManifests
|
||||
- expo-dev-launcher
|
||||
- expo-dev-menu
|
||||
- expo-dev-menu-interface
|
||||
- EXUpdatesInterface
|
||||
- expo-dev-launcher (6.0.15):
|
||||
- expo-dev-launcher (6.0.20):
|
||||
- EXManifests
|
||||
- expo-dev-launcher/Main (= 6.0.15)
|
||||
- expo-dev-launcher/Main (= 6.0.20)
|
||||
- expo-dev-menu
|
||||
- expo-dev-menu-interface
|
||||
- ExpoModulesCore
|
||||
|
|
@ -73,7 +73,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- expo-dev-launcher/Main (6.0.15):
|
||||
- expo-dev-launcher/Main (6.0.20):
|
||||
- EXManifests
|
||||
- expo-dev-launcher/Unsafe
|
||||
- expo-dev-menu
|
||||
|
|
@ -104,7 +104,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- expo-dev-launcher/Unsafe (6.0.15):
|
||||
- expo-dev-launcher/Unsafe (6.0.20):
|
||||
- EXManifests
|
||||
- expo-dev-menu
|
||||
- expo-dev-menu-interface
|
||||
|
|
@ -134,9 +134,9 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- expo-dev-menu (7.0.14):
|
||||
- expo-dev-menu/Main (= 7.0.14)
|
||||
- expo-dev-menu/ReactNativeCompatibles (= 7.0.14)
|
||||
- expo-dev-menu (7.0.18):
|
||||
- expo-dev-menu/Main (= 7.0.18)
|
||||
- expo-dev-menu/ReactNativeCompatibles (= 7.0.18)
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -159,7 +159,7 @@ PODS:
|
|||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- expo-dev-menu-interface (2.0.0)
|
||||
- expo-dev-menu/Main (7.0.14):
|
||||
- expo-dev-menu/Main (7.0.18):
|
||||
- EXManifests
|
||||
- expo-dev-menu-interface
|
||||
- ExpoModulesCore
|
||||
|
|
@ -185,7 +185,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- expo-dev-menu/ReactNativeCompatibles (7.0.14):
|
||||
- expo-dev-menu/ReactNativeCompatibles (7.0.18):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -207,38 +207,37 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- ExpoAsset (12.0.9):
|
||||
- ExpoAsset (12.0.11):
|
||||
- ExpoModulesCore
|
||||
- ExpoBlur (15.0.7):
|
||||
- ExpoBlur (15.0.8):
|
||||
- ExpoModulesCore
|
||||
- ExpoBrightness (14.0.7):
|
||||
- ExpoBrightness (14.0.8):
|
||||
- ExpoModulesCore
|
||||
- ExpoCrypto (15.0.7):
|
||||
- ExpoClipboard (8.0.8):
|
||||
- ExpoModulesCore
|
||||
- ExpoDevice (8.0.9):
|
||||
- ExpoCrypto (15.0.8):
|
||||
- ExpoModulesCore
|
||||
- ExpoDocumentPicker (14.0.7):
|
||||
- ExpoDevice (8.0.10):
|
||||
- ExpoModulesCore
|
||||
- ExpoFileSystem (19.0.17):
|
||||
- ExpoDocumentPicker (14.0.8):
|
||||
- ExpoModulesCore
|
||||
- ExpoFont (14.0.9):
|
||||
- ExpoFileSystem (19.0.21):
|
||||
- ExpoModulesCore
|
||||
- ExpoGlassEffect (0.1.4):
|
||||
- ExpoFont (14.0.10):
|
||||
- ExpoModulesCore
|
||||
- ExpoHaptics (15.0.7):
|
||||
- ExpoGlassEffect (0.1.8):
|
||||
- ExpoModulesCore
|
||||
- ExpoKeepAwake (15.0.7):
|
||||
- ExpoHaptics (15.0.8):
|
||||
- ExpoModulesCore
|
||||
- ExpoLibVlcPlayer (2.2.1):
|
||||
- ExpoKeepAwake (15.0.8):
|
||||
- ExpoModulesCore
|
||||
- MobileVLCKit (= 3.6.1b1)
|
||||
- ExpoLinearGradient (15.0.7):
|
||||
- ExpoLinearGradient (15.0.8):
|
||||
- ExpoModulesCore
|
||||
- ExpoLinking (8.0.8):
|
||||
- ExpoLinking (8.0.10):
|
||||
- ExpoModulesCore
|
||||
- ExpoLocalization (17.0.7):
|
||||
- ExpoLocalization (17.0.8):
|
||||
- ExpoModulesCore
|
||||
- ExpoModulesCore (3.0.21):
|
||||
- ExpoModulesCore (3.0.29):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -263,7 +262,7 @@ PODS:
|
|||
- Yoga
|
||||
- ExpoRandom (14.0.1):
|
||||
- ExpoModulesCore
|
||||
- ExpoScreenOrientation (9.0.7):
|
||||
- ExpoScreenOrientation (9.0.8):
|
||||
- ExpoModulesCore
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
|
|
@ -286,14 +285,14 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- ExpoSharing (14.0.7):
|
||||
- ExpoSharing (14.0.8):
|
||||
- ExpoModulesCore
|
||||
- ExpoSystemUI (6.0.7):
|
||||
- ExpoSystemUI (6.0.9):
|
||||
- ExpoModulesCore
|
||||
- ExpoWebBrowser (15.0.8):
|
||||
- ExpoWebBrowser (15.0.10):
|
||||
- ExpoModulesCore
|
||||
- EXStructuredHeaders (5.0.0)
|
||||
- EXUpdates (29.0.12):
|
||||
- EXUpdates (29.0.15):
|
||||
- EASClient
|
||||
- EXManifests
|
||||
- ExpoModulesCore
|
||||
|
|
@ -328,10 +327,11 @@ 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)
|
||||
- ImageColors (2.5.0):
|
||||
- ImageColors (2.5.1):
|
||||
- ExpoModulesCore
|
||||
- KSPlayer (1.1.0):
|
||||
- KSPlayer/Audio (= 1.1.0)
|
||||
|
|
@ -405,8 +405,7 @@ PODS:
|
|||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- MMKVCore (2.2.4)
|
||||
- MobileVLCKit (3.6.1b1)
|
||||
- NitroMmkv (4.0.0):
|
||||
- NitroMmkv (4.1.0):
|
||||
- hermes-engine
|
||||
- MMKVCore (= 2.2.4)
|
||||
- NitroModules
|
||||
|
|
@ -431,7 +430,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- NitroModules (0.31.2):
|
||||
- NitroModules (0.31.10):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -454,6 +453,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- PromisesObjC (2.4.0)
|
||||
- RCTDeprecation (0.81.4)
|
||||
- RCTRequired (0.81.4)
|
||||
- RCTTypeSafety (0.81.4):
|
||||
|
|
@ -1756,7 +1756,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- react-native-bottom-tabs (0.12.2):
|
||||
- react-native-bottom-tabs (1.1.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -1768,7 +1768,7 @@ PODS:
|
|||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-jsi
|
||||
- react-native-bottom-tabs/common (= 0.12.2)
|
||||
- react-native-bottom-tabs/common (= 1.1.0)
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-renderercss
|
||||
|
|
@ -1780,7 +1780,7 @@ PODS:
|
|||
- ReactNativeDependencies
|
||||
- SwiftUIIntrospect (~> 1.0)
|
||||
- Yoga
|
||||
- react-native-bottom-tabs/common (0.12.2):
|
||||
- react-native-bottom-tabs/common (1.1.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -1805,11 +1805,35 @@ PODS:
|
|||
- Yoga
|
||||
- react-native-device-brightness (1.2.7):
|
||||
- React
|
||||
- react-native-get-random-values (1.11.0):
|
||||
- react-native-get-random-values (2.0.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
- 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-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):
|
||||
- react-native-safe-area-context (5.6.2):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -1821,8 +1845,8 @@ PODS:
|
|||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-jsi
|
||||
- react-native-safe-area-context/common (= 5.6.1)
|
||||
- react-native-safe-area-context/fabric (= 5.6.1)
|
||||
- react-native-safe-area-context/common (= 5.6.2)
|
||||
- react-native-safe-area-context/fabric (= 5.6.2)
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-renderercss
|
||||
|
|
@ -1833,7 +1857,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- react-native-safe-area-context/common (5.6.1):
|
||||
- react-native-safe-area-context/common (5.6.2):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -1855,7 +1879,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- react-native-safe-area-context/fabric (5.6.1):
|
||||
- react-native-safe-area-context/fabric (5.6.2):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -1878,7 +1902,31 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- react-native-slider (5.0.1):
|
||||
- react-native-skia (2.4.14):
|
||||
- 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.1.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -1890,7 +1938,7 @@ PODS:
|
|||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-jsi
|
||||
- react-native-slider/common (= 5.0.1)
|
||||
- react-native-slider/common (= 5.1.1)
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-renderercss
|
||||
|
|
@ -1901,7 +1949,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- react-native-slider/common (5.0.1):
|
||||
- react-native-slider/common (5.1.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -1923,7 +1971,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- react-native-video (6.17.0):
|
||||
- react-native-video (6.18.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -1935,7 +1983,7 @@ PODS:
|
|||
- React-graphics
|
||||
- React-ImageManager
|
||||
- React-jsi
|
||||
- react-native-video/Video (= 6.17.0)
|
||||
- react-native-video/Video (= 6.18.0)
|
||||
- React-NativeModulesApple
|
||||
- React-RCTFabric
|
||||
- React-renderercss
|
||||
|
|
@ -1946,7 +1994,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- react-native-video/Fabric (6.17.0):
|
||||
- react-native-video/Fabric (6.18.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -1968,7 +2016,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- react-native-video/Video (6.17.0):
|
||||
- react-native-video/Video (6.18.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2341,7 +2389,7 @@ PODS:
|
|||
- React-utils (= 0.81.4)
|
||||
- ReactNativeDependencies
|
||||
- ReactNativeDependencies (0.81.4)
|
||||
- RNCPicker (2.11.1):
|
||||
- RNCPicker (2.11.4):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2363,7 +2411,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- RNFastImage (8.12.0):
|
||||
- RNFastImage (8.13.0):
|
||||
- hermes-engine
|
||||
- libavif/core (~> 0.11.1)
|
||||
- libavif/libdav1d (~> 0.11.1)
|
||||
|
|
@ -2388,9 +2436,10 @@ PODS:
|
|||
- ReactNativeDependencies
|
||||
- SDWebImage (>= 5.19.1)
|
||||
- SDWebImageAVIFCoder (~> 0.11.0)
|
||||
- SDWebImageSVGCoder (~> 1.7.0)
|
||||
- SDWebImageWebPCoder (~> 0.14)
|
||||
- Yoga
|
||||
- RNGestureHandler (2.28.0):
|
||||
- RNGestureHandler (2.29.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2412,7 +2461,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- RNReanimated (4.1.3):
|
||||
- RNReanimated (4.2.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2434,10 +2483,10 @@ PODS:
|
|||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- RNReanimated/reanimated (= 4.1.3)
|
||||
- RNReanimated/reanimated (= 4.2.0)
|
||||
- RNWorklets
|
||||
- Yoga
|
||||
- RNReanimated/reanimated (4.1.3):
|
||||
- RNReanimated/reanimated (4.2.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2459,10 +2508,10 @@ PODS:
|
|||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- RNReanimated/reanimated/apple (= 4.1.3)
|
||||
- RNReanimated/reanimated/apple (= 4.2.0)
|
||||
- RNWorklets
|
||||
- Yoga
|
||||
- RNReanimated/reanimated/apple (4.1.3):
|
||||
- RNReanimated/reanimated/apple (4.2.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2486,7 +2535,7 @@ PODS:
|
|||
- ReactNativeDependencies
|
||||
- RNWorklets
|
||||
- Yoga
|
||||
- RNScreens (4.16.0):
|
||||
- RNScreens (4.18.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2508,9 +2557,9 @@ PODS:
|
|||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- RNScreens/common (= 4.16.0)
|
||||
- RNScreens/common (= 4.18.0)
|
||||
- Yoga
|
||||
- RNScreens/common (4.16.0):
|
||||
- RNScreens/common (4.18.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2533,7 +2582,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- RNSentry (7.3.0):
|
||||
- RNSentry (7.7.0):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2555,9 +2604,9 @@ PODS:
|
|||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Sentry/HybridSDK (= 8.56.2)
|
||||
- Sentry/HybridSDK (= 8.57.3)
|
||||
- Yoga
|
||||
- RNSVG (15.12.1):
|
||||
- RNSVG (15.15.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2578,9 +2627,9 @@ PODS:
|
|||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- RNSVG/common (= 15.12.1)
|
||||
- RNSVG/common (= 15.15.1)
|
||||
- Yoga
|
||||
- RNSVG/common (15.12.1):
|
||||
- RNSVG/common (15.15.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2624,7 +2673,7 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- RNWorklets (0.6.1):
|
||||
- RNWorklets (0.7.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2646,9 +2695,9 @@ PODS:
|
|||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- RNWorklets/worklets (= 0.6.1)
|
||||
- RNWorklets/worklets (= 0.7.1)
|
||||
- Yoga
|
||||
- RNWorklets/worklets (0.6.1):
|
||||
- RNWorklets/worklets (0.7.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2670,9 +2719,9 @@ PODS:
|
|||
- ReactCommon/turbomodule/bridging
|
||||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- RNWorklets/worklets/apple (= 0.6.1)
|
||||
- RNWorklets/worklets/apple (= 0.7.1)
|
||||
- Yoga
|
||||
- RNWorklets/worklets/apple (0.6.1):
|
||||
- RNWorklets/worklets/apple (0.7.1):
|
||||
- hermes-engine
|
||||
- RCTRequired
|
||||
- RCTTypeSafety
|
||||
|
|
@ -2695,21 +2744,23 @@ PODS:
|
|||
- ReactCommon/turbomodule/core
|
||||
- ReactNativeDependencies
|
||||
- Yoga
|
||||
- SDWebImage (5.21.3):
|
||||
- SDWebImage/Core (= 5.21.3)
|
||||
- SDWebImage/Core (5.21.3)
|
||||
- SDWebImage (5.21.5):
|
||||
- SDWebImage/Core (= 5.21.5)
|
||||
- SDWebImage/Core (5.21.5)
|
||||
- SDWebImageAVIFCoder (0.11.1):
|
||||
- libavif/core (>= 0.11.0)
|
||||
- SDWebImage (~> 5.10)
|
||||
- SDWebImageWebPCoder (0.14.6):
|
||||
- SDWebImageSVGCoder (1.7.0):
|
||||
- SDWebImage/Core (~> 5.6)
|
||||
- SDWebImageWebPCoder (0.15.0):
|
||||
- libwebp (~> 1.0)
|
||||
- SDWebImage/Core (~> 5.17)
|
||||
- Sentry/HybridSDK (8.56.2)
|
||||
- Sentry/HybridSDK (8.57.3)
|
||||
- SwiftUIIntrospect (1.3.0)
|
||||
- Yoga (0.0.0)
|
||||
|
||||
DEPENDENCIES:
|
||||
- DisplayCriteria (from `https://github.com/kingslay/KSPlayer.git`, branch `main`)
|
||||
- DisplayCriteria (from `../KSPlayer`)
|
||||
- EASClient (from `../node_modules/expo-eas-client/ios`)
|
||||
- EXApplication (from `../node_modules/expo-application/ios`)
|
||||
- EXConstants (from `../node_modules/expo-constants/ios`)
|
||||
|
|
@ -2724,6 +2775,7 @@ DEPENDENCIES:
|
|||
- ExpoAsset (from `../node_modules/expo-asset/ios`)
|
||||
- ExpoBlur (from `../node_modules/expo-blur/ios`)
|
||||
- ExpoBrightness (from `../node_modules/expo-brightness/ios`)
|
||||
- ExpoClipboard (from `../node_modules/expo-clipboard/ios`)
|
||||
- ExpoCrypto (from `../node_modules/expo-crypto/ios`)
|
||||
- ExpoDevice (from `../node_modules/expo-device/ios`)
|
||||
- ExpoDocumentPicker (from `../node_modules/expo-document-picker/ios`)
|
||||
|
|
@ -2732,7 +2784,6 @@ DEPENDENCIES:
|
|||
- ExpoGlassEffect (from `../node_modules/expo-glass-effect/ios`)
|
||||
- ExpoHaptics (from `../node_modules/expo-haptics/ios`)
|
||||
- ExpoKeepAwake (from `../node_modules/expo-keep-awake/ios`)
|
||||
- ExpoLibVlcPlayer (from `../node_modules/expo-libvlc-player/ios`)
|
||||
- ExpoLinearGradient (from `../node_modules/expo-linear-gradient/ios`)
|
||||
- ExpoLinking (from `../node_modules/expo-linking/ios`)
|
||||
- ExpoLocalization (from `../node_modules/expo-localization/ios`)
|
||||
|
|
@ -2749,7 +2800,7 @@ DEPENDENCIES:
|
|||
- FFmpegKit (from `https://github.com/kingslay/FFmpegKit.git`, branch `main`)
|
||||
- hermes-engine (from `../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`)
|
||||
- ImageColors (from `../node_modules/react-native-image-colors/ios`)
|
||||
- KSPlayer (from `https://github.com/kingslay/KSPlayer.git`, branch `main`)
|
||||
- KSPlayer (from `../KSPlayer`)
|
||||
- Libass (from `https://github.com/kingslay/FFmpegKit.git`, branch `main`)
|
||||
- lottie-react-native (from `../node_modules/lottie-react-native`)
|
||||
- NitroMmkv (from `../node_modules/react-native-mmkv`)
|
||||
|
|
@ -2792,8 +2843,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,23 +2893,24 @@ DEPENDENCIES:
|
|||
|
||||
SPEC REPOS:
|
||||
trunk:
|
||||
- google-cast-sdk
|
||||
- libavif
|
||||
- libdav1d
|
||||
- libwebp
|
||||
- lottie-ios
|
||||
- MMKVCore
|
||||
- MobileVLCKit
|
||||
- PromisesObjC
|
||||
- ReachabilitySwift
|
||||
- SDWebImage
|
||||
- SDWebImageAVIFCoder
|
||||
- SDWebImageSVGCoder
|
||||
- SDWebImageWebPCoder
|
||||
- Sentry
|
||||
- SwiftUIIntrospect
|
||||
|
||||
EXTERNAL SOURCES:
|
||||
DisplayCriteria:
|
||||
:branch: main
|
||||
:git: https://github.com/kingslay/KSPlayer.git
|
||||
:path: "../KSPlayer"
|
||||
EASClient:
|
||||
:path: "../node_modules/expo-eas-client/ios"
|
||||
EXApplication:
|
||||
|
|
@ -2885,6 +2939,8 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/expo-blur/ios"
|
||||
ExpoBrightness:
|
||||
:path: "../node_modules/expo-brightness/ios"
|
||||
ExpoClipboard:
|
||||
:path: "../node_modules/expo-clipboard/ios"
|
||||
ExpoCrypto:
|
||||
:path: "../node_modules/expo-crypto/ios"
|
||||
ExpoDevice:
|
||||
|
|
@ -2901,8 +2957,6 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/expo-haptics/ios"
|
||||
ExpoKeepAwake:
|
||||
:path: "../node_modules/expo-keep-awake/ios"
|
||||
ExpoLibVlcPlayer:
|
||||
:path: "../node_modules/expo-libvlc-player/ios"
|
||||
ExpoLinearGradient:
|
||||
:path: "../node_modules/expo-linear-gradient/ios"
|
||||
ExpoLinking:
|
||||
|
|
@ -2938,8 +2992,7 @@ EXTERNAL SOURCES:
|
|||
ImageColors:
|
||||
:path: "../node_modules/react-native-image-colors/ios"
|
||||
KSPlayer:
|
||||
:branch: main
|
||||
:git: https://github.com/kingslay/KSPlayer.git
|
||||
:path: "../KSPlayer"
|
||||
Libass:
|
||||
:branch: main
|
||||
:git: https://github.com/kingslay/FFmpegKit.git
|
||||
|
|
@ -3023,10 +3076,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:
|
||||
|
|
@ -3115,60 +3172,55 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/react-native/ReactCommon/yoga"
|
||||
|
||||
CHECKOUT OPTIONS:
|
||||
DisplayCriteria:
|
||||
:commit: f9c3b86dfed683b278a6f5551d4e68ebcdcc3bf2
|
||||
:git: https://github.com/kingslay/KSPlayer.git
|
||||
FFmpegKit:
|
||||
:commit: d7048037a2eb94a3b08113fbf43aa92bdcb332d9
|
||||
:git: https://github.com/kingslay/FFmpegKit.git
|
||||
KSPlayer:
|
||||
:commit: f9c3b86dfed683b278a6f5551d4e68ebcdcc3bf2
|
||||
:git: https://github.com/kingslay/KSPlayer.git
|
||||
Libass:
|
||||
:commit: d7048037a2eb94a3b08113fbf43aa92bdcb332d9
|
||||
:git: https://github.com/kingslay/FFmpegKit.git
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
DisplayCriteria: bb0a90faf14b30848bc50ac0516340ce50164187
|
||||
EASClient: 68127f1248d2b25fdc82dbbfb17be95d1c4700be
|
||||
EXApplication: 296622817d459f46b6c5fe8691f4aac44d2b79e7
|
||||
EXConstants: a95804601ee4a6aa7800645f9b070d753b1142b3
|
||||
EASClient: 40dd9e740684782610c49becab2643782ea1a20c
|
||||
EXApplication: 1e98d4b1dccdf30627f92917f4b2c5a53c330e5f
|
||||
EXConstants: 805f35b1b295c542ca6acce836f21a1f9ee104d5
|
||||
EXJSONUtils: 1d3e4590438c3ee593684186007028a14b3686cd
|
||||
EXManifests: 224345a575fca389073c416297b6348163f28d1a
|
||||
EXNotifications: 7cff475adb5d7a255a9ea46bbd2589cb3b454506
|
||||
Expo: 6580dbf21d94626792b38a95cddb2fb369ec6b0c
|
||||
expo-dev-client: 6da1a574fa7caa33a315d818b43c4e560be91915
|
||||
expo-dev-launcher: ab4604b4cc823ff8c67835a90e7e36834224ea8c
|
||||
expo-dev-menu: 6db57f4d3001fe3992d84a0c2e285de381e4b51b
|
||||
EXManifests: a8d97683e5c7a3b026ffbd58559c64dc655b747b
|
||||
EXNotifications: 983f04ad4ad879b181179e326bf220541e478386
|
||||
Expo: 8fa2204bf8483fe546b4ec87c90d3ca189afc8db
|
||||
expo-dev-client: 425ee077d6754a98cfe3a2e2410d29b440b24c9d
|
||||
expo-dev-launcher: a4f4cdef064ab1fb8621e5b8c7c457cd6e9568c3
|
||||
expo-dev-menu: 05b18812110c175814c6af0d09dd658abcc5e00d
|
||||
expo-dev-menu-interface: 600df12ea01efecdd822daaf13cc0ac091775533
|
||||
ExpoAsset: 9ba6fbd677fb8e241a3899ac00fa735bc911eadf
|
||||
ExpoBlur: 2dd8f64aa31f5d405652c21d3deb2d2588b1852f
|
||||
ExpoBrightness: 32672952bf8b152d0cceaf8ec9f1def3a9a5e0d9
|
||||
ExpoCrypto: c1fbce112d1b6b79652bbe380b4fd4cc91676595
|
||||
ExpoDevice: 148accb4071873d19fba80a2506c58ffa433d620
|
||||
ExpoDocumentPicker: 2200eefc2817f19315fa18f0147e0b80ece86926
|
||||
ExpoFileSystem: b79eadbda7b7f285f378f95f959cc9313a1c9c61
|
||||
ExpoFont: cf9d90ec1d3b97c4f513211905724c8171f82961
|
||||
ExpoGlassEffect: 744bf0c58c26a1b0212dff92856be07b98d01d8c
|
||||
ExpoHaptics: 807476b0c39e9d82b7270349d6487928ce32df84
|
||||
ExpoKeepAwake: 1a2e820692e933c94a565ec3fbbe38ac31658ffe
|
||||
ExpoLibVlcPlayer: dce3d0b5847838cd5f8c5f3c3aa1bc55c92e911d
|
||||
ExpoLinearGradient: a464898cb95153125e3b81894fd479bcb1c7dd27
|
||||
ExpoLinking: f051f28e50ea9269ff539317c166adec81d9342d
|
||||
ExpoLocalization: b852a5d8ec14c5349c1593eca87896b5b3ebfcca
|
||||
ExpoModulesCore: 3a6eb12a5f4d67b2f5fc7d0bc4777b18348f2d7a
|
||||
ExpoAsset: 23a958e97d3d340919fe6774db35d563241e6c03
|
||||
ExpoBlur: b90747a3f22a8b6ceffd9cb0dc41a4184efdc656
|
||||
ExpoBrightness: 46c980463e8a54b9ce77f923c4bff0bb0c9526e0
|
||||
ExpoClipboard: b36b287d8356887844bb08ed5c84b5979bb4dd1e
|
||||
ExpoCrypto: b6105ebaa15d6b38a811e71e43b52cd934945322
|
||||
ExpoDevice: 6327c3c200816795708885adf540d26ecab83d1a
|
||||
ExpoDocumentPicker: 7cd9e71a0f66fb19eb0a586d6f26eee1284692e0
|
||||
ExpoFileSystem: 858a44267a3e6e9057e0888ad7c7cfbf55d52063
|
||||
ExpoFont: 35ac6191ed86bbf56b3ebd2d9154eda9fad5b509
|
||||
ExpoGlassEffect: 8ce45eca31f12e949e23a4ee13e2bfb59e9b0785
|
||||
ExpoHaptics: d3a6375d8dcc3a1083d003bc2298ff654fafb536
|
||||
ExpoKeepAwake: 55f75eca6499bb9e4231ebad6f3e9cb8f99c0296
|
||||
ExpoLinearGradient: 809102bdb979f590083af49f7fa4805cd931bd58
|
||||
ExpoLinking: f4c4a351523da72a6bfa7e1f4ca92aee1043a3ca
|
||||
ExpoLocalization: d9168d5300a5b03e5e78b986124d11fb6ec3ebbd
|
||||
ExpoModulesCore: f3da4f1ab5a8375d0beafab763739dbee8446583
|
||||
ExpoRandom: d1444df65007bdd4070009efd5dab18e20bf0f00
|
||||
ExpoScreenOrientation: ef9ab3fb85c8a8ff57d52aa169b750aca03f0f4c
|
||||
ExpoSharing: 032c01bb034319e2374badf082ae935be866d2e9
|
||||
ExpoSystemUI: 6cd74248a2282adf6dec488a75fa532d69dee314
|
||||
ExpoWebBrowser: d04a0d6247a0bea4519fbc2ea816610019ad83e0
|
||||
ExpoScreenOrientation: c68bd20f210d0616960638c787889e07787e5adb
|
||||
ExpoSharing: 0d983394ed4a80334bab5a0d5384f75710feb7e8
|
||||
ExpoSystemUI: 2ad325f361a2fcd96a464e8574e19935c461c9cc
|
||||
ExpoWebBrowser: 17b064c621789e41d4816c95c93f429b84971f52
|
||||
EXStructuredHeaders: c951e77f2d936f88637421e9588c976da5827368
|
||||
EXUpdates: ef83273afc231a627b170358c90689ac30a4429d
|
||||
EXUpdates: f20abbc8a9f4e150656fe88126d52f52d4e7793f
|
||||
EXUpdatesInterface: 5adf50cb41e079c861da6d9b4b954c3db9a50734
|
||||
FBLazyVector: 9e0cd874afd81d9a4d36679daca991b58b260d42
|
||||
FFmpegKit: 3885085fbbc320745838ee4c8a1f9c5e5953dab2
|
||||
google-cast-sdk: 32f65af50d164e3c475e79ad123db3cc26fbcd37
|
||||
hermes-engine: 35c763d57c9832d0eef764316ca1c4d043581394
|
||||
ImageColors: 51cd79f7a9d2524b7a681c660b0a50574085563b
|
||||
ImageColors: e12eb73e29bc1feaa3c228db8c174a1b25acb59d
|
||||
KSPlayer: f163ac6195f240b6fa5b8225aeb39ec811a70c62
|
||||
Libass: e88af2324e1217e3a4c8bdc675f6f23a9dfc7677
|
||||
libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7
|
||||
|
|
@ -3177,9 +3229,9 @@ SPEC CHECKSUMS:
|
|||
lottie-ios: a881093fab623c467d3bce374367755c272bdd59
|
||||
lottie-react-native: cbe3d931a7c24f7891a8e8032c2bb9b2373c4b9c
|
||||
MMKVCore: f2dd4c9befea04277a55e84e7812f930537993df
|
||||
MobileVLCKit: 2d9c7c373393ae43086aeeff890bf0b1afc15c5c
|
||||
NitroMmkv: 7fe66a61d5acab6516098a64f42af575595e7566
|
||||
NitroModules: 8c4eca403e6f45f474608d24cd11ab664ed2961c
|
||||
NitroMmkv: 4af10c70043b4c3cded3f16547627c7d9d8e3b8b
|
||||
NitroModules: a71a5ab2911caf79e45170e6e12475b5260a12d0
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
RCTDeprecation: 7487d6dda857ccd4cb3dd6ecfccdc3170e85dcbc
|
||||
RCTRequired: 54128b7df8be566881d48c7234724a78cb9b6157
|
||||
RCTTypeSafety: d2b07797a79e45d7b19e1cd2f53c79ab419fe217
|
||||
|
|
@ -3215,13 +3267,15 @@ SPEC CHECKSUMS:
|
|||
React-Mapbuffer: fbe1da882a187e5898bdf125e1cc6e603d27ecae
|
||||
React-microtasksnativemodule: 76905804171d8ccbe69329fc84c57eb7934add7f
|
||||
react-native-blur: 1b00ef07fe0efdc0c40b37139a5268ccad73c72d
|
||||
react-native-bottom-tabs: e37c9d1565b1ee48c4c0e4b4fa4b804775f82dfa
|
||||
react-native-bottom-tabs: bcb70e4fae95fc9da0da875f7414acda26dfc551
|
||||
react-native-device-brightness: 1a997350d060c3df9f303b1df84a4f7c5cbeb924
|
||||
react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba
|
||||
react-native-get-random-values: a603782b2b222a34533c66371614790282dba3f1
|
||||
react-native-google-cast: 7be68a5d0b7eeb95a5924c3ecef8d319ef6c0a44
|
||||
react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187
|
||||
react-native-safe-area-context: 42a1b4f8774b577d03b53de7326e3d5757fe9513
|
||||
react-native-slider: 8c562583722c396a3682f451f0b6e68e351ec3b9
|
||||
react-native-video: 5d9635903e562e0c5eb47c5fa401f1c807d6e068
|
||||
react-native-safe-area-context: 37e680fc4cace3c0030ee46e8987d24f5d3bdab2
|
||||
react-native-skia: 268f183f849742e9da216743ee234bd7ad81c69b
|
||||
react-native-slider: f954578344106f0a732a4358ce3a3e11015eb6e1
|
||||
react-native-video: f5982e21efab0dc356d92541a8a9e19581307f58
|
||||
React-NativeModulesApple: a9464983ccc0f66f45e93558671f60fc7536e438
|
||||
React-oscompat: 73db7dbc80edef36a9d6ed3c6c4e1724ead4236d
|
||||
React-perflogger: 123272debf907cc423962adafcf4513320e43757
|
||||
|
|
@ -3253,22 +3307,23 @@ SPEC CHECKSUMS:
|
|||
ReactCodegen: a15ad48730e9fb2a51a4c9f61fe1ed253dfcf10f
|
||||
ReactCommon: 149b6c05126f2e99f2ed0d3c63539369546f8cae
|
||||
ReactNativeDependencies: ed6d1e64802b150399f04f1d5728ec16b437251e
|
||||
RNCPicker: a7170edbcbf8288de8edb2502e08e7fc757fa755
|
||||
RNFastImage: 42a769cd260a7686b1db32a9f7d754333bad4e77
|
||||
RNGestureHandler: 2914750df066d89bf9d8f48a10ad5f0051108ac3
|
||||
RNReanimated: 3895a29fdf77bbe2a627e1ed599a5e5d1df76c29
|
||||
RNScreens: d8d6f1792f6e7ac12b0190d33d8d390efc0c1845
|
||||
RNSentry: 7726bf35e00ab799f50c4618df6f5417553678a0
|
||||
RNSVG: 31d6639663c249b7d5abc9728dde2041eb2a3c34
|
||||
RNCPicker: c8a3584b74133464ee926224463fcc54dfdaebca
|
||||
RNFastImage: 2d36f4cfed9b2342f94f8591c8be69dd047ac67c
|
||||
RNGestureHandler: 723f29dac55e25f109d263ed65cecc4b9c4bd46a
|
||||
RNReanimated: e1c71e6e693a66b203ae98773347b625d3cc85ee
|
||||
RNScreens: 61c18865ab074f4d995ac8d7cf5060522a649d05
|
||||
RNSentry: 1d7b9fdae7a01ad8f9053335b5d44e75c39a955e
|
||||
RNSVG: cf9ae78f2edf2988242c71a6392d15ff7dd62522
|
||||
RNVectorIcons: 4351544f100d4f12cac156a7c13399e60bab3e26
|
||||
RNWorklets: 54d8dffb7f645873a58484658ddfd4bd1a9a0bc1
|
||||
SDWebImage: 16309af6d214ba3f77a7c6f6fdda888cb313a50a
|
||||
RNWorklets: 9eb6d567fa43984e96b6924a6df504b8a15980cd
|
||||
SDWebImage: e9c98383c7572d713c1a0d7dd2783b10599b9838
|
||||
SDWebImageAVIFCoder: afe194a084e851f70228e4be35ef651df0fc5c57
|
||||
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
|
||||
Sentry: b53951377b78e21a734f5dc8318e333dbfc682d7
|
||||
SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c
|
||||
SDWebImageWebPCoder: 0e06e365080397465cc73a7a9b472d8a3bd0f377
|
||||
Sentry: c643eb180df401dd8c734c5036ddd9dd9218daa6
|
||||
SwiftUIIntrospect: fee9aa07293ee280373a591e1824e8ddc869ba5d
|
||||
Yoga: 051f086b5ccf465ff2ed38a2cf5a558ae01aaaa1
|
||||
|
||||
PODFILE CHECKSUM: 1db7b3713ca6ad8568e4bdf6b72b92b72ee8199d
|
||||
PODFILE CHECKSUM: b884d1ff07ac4a43323bce2e2e1342592513858c
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"expo.jsEngine": "hermes",
|
||||
"EX_DEV_CLIENT_NETWORK_INSPECTOR": "true",
|
||||
"newArchEnabled": "true"
|
||||
}
|
||||
"newArchEnabled": "true",
|
||||
"ios.deploymentTarget": "16.0"
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
defaults.url=https://sentry.io/
|
||||
defaults.org=tapframe
|
||||
defaults.project=react-native
|
||||
# Using SENTRY_AUTH_TOKEN environment variable
|
||||
auth.token=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
|
||||
|
|
|
|||
81
node_modules/react-native-video/android/src/main/java/com/brentvatne/common/api/SubtitleStyle.kt
generated
vendored
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
package com.brentvatne.common.api
|
||||
|
||||
import android.graphics.Color
|
||||
import com.brentvatne.common.toolbox.ReactBridgeUtils
|
||||
import com.facebook.react.bridge.ReadableMap
|
||||
|
||||
/**
|
||||
* Helper file to parse SubtitleStyle prop and build a dedicated class
|
||||
*/
|
||||
class SubtitleStyle public constructor() {
|
||||
var fontSize = -1
|
||||
private set
|
||||
var paddingLeft = 0
|
||||
private set
|
||||
var paddingRight = 0
|
||||
private set
|
||||
var paddingTop = 0
|
||||
private set
|
||||
var paddingBottom = 0
|
||||
private set
|
||||
var opacity = 1f
|
||||
private set
|
||||
var subtitlesFollowVideo = true
|
||||
private set
|
||||
|
||||
// Extended styling (used by ExoPlayerView via Media3 SubtitleView)
|
||||
// Stored as Android color ints to avoid parsing multiple times.
|
||||
var textColor: Int? = null
|
||||
private set
|
||||
var backgroundColor: Int? = null
|
||||
private set
|
||||
var edgeType: String? = null
|
||||
private set
|
||||
var edgeColor: Int? = null
|
||||
private set
|
||||
|
||||
companion object {
|
||||
private const val PROP_FONT_SIZE_TRACK = "fontSize"
|
||||
private const val PROP_PADDING_BOTTOM = "paddingBottom"
|
||||
private const val PROP_PADDING_TOP = "paddingTop"
|
||||
private const val PROP_PADDING_LEFT = "paddingLeft"
|
||||
private const val PROP_PADDING_RIGHT = "paddingRight"
|
||||
private const val PROP_OPACITY = "opacity"
|
||||
private const val PROP_SUBTITLES_FOLLOW_VIDEO = "subtitlesFollowVideo"
|
||||
|
||||
// Extended props (optional)
|
||||
private const val PROP_TEXT_COLOR = "textColor"
|
||||
private const val PROP_BACKGROUND_COLOR = "backgroundColor"
|
||||
private const val PROP_EDGE_TYPE = "edgeType"
|
||||
private const val PROP_EDGE_COLOR = "edgeColor"
|
||||
|
||||
private fun parseColorOrNull(value: String?): Int? {
|
||||
if (value.isNullOrBlank()) return null
|
||||
return try {
|
||||
Color.parseColor(value)
|
||||
} catch (_: IllegalArgumentException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun parse(src: ReadableMap?): SubtitleStyle {
|
||||
val subtitleStyle = SubtitleStyle()
|
||||
subtitleStyle.fontSize = ReactBridgeUtils.safeGetInt(src, PROP_FONT_SIZE_TRACK, -1)
|
||||
subtitleStyle.paddingBottom = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_BOTTOM, 0)
|
||||
subtitleStyle.paddingTop = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_TOP, 0)
|
||||
subtitleStyle.paddingLeft = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_LEFT, 0)
|
||||
subtitleStyle.paddingRight = ReactBridgeUtils.safeGetInt(src, PROP_PADDING_RIGHT, 0)
|
||||
subtitleStyle.opacity = ReactBridgeUtils.safeGetFloat(src, PROP_OPACITY, 1f)
|
||||
subtitleStyle.subtitlesFollowVideo = ReactBridgeUtils.safeGetBool(src, PROP_SUBTITLES_FOLLOW_VIDEO, true)
|
||||
|
||||
// Extended styling
|
||||
subtitleStyle.textColor = parseColorOrNull(ReactBridgeUtils.safeGetString(src, PROP_TEXT_COLOR, null))
|
||||
subtitleStyle.backgroundColor = parseColorOrNull(ReactBridgeUtils.safeGetString(src, PROP_BACKGROUND_COLOR, null))
|
||||
subtitleStyle.edgeType = ReactBridgeUtils.safeGetString(src, PROP_EDGE_TYPE, null)
|
||||
subtitleStyle.edgeColor = parseColorOrNull(ReactBridgeUtils.safeGetString(src, PROP_EDGE_COLOR, null))
|
||||
|
||||
return subtitleStyle
|
||||
}
|
||||
}
|
||||
}
|
||||
441
node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.kt
generated
vendored
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
package com.brentvatne.exoplayer
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.View.MeasureSpec
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.Timeline
|
||||
import androidx.media3.common.text.CueGroup
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.ui.AspectRatioFrameLayout
|
||||
import androidx.media3.ui.CaptionStyleCompat
|
||||
import androidx.media3.ui.DefaultTimeBar
|
||||
import androidx.media3.ui.PlayerView
|
||||
import androidx.media3.ui.SubtitleView
|
||||
import com.brentvatne.common.api.ResizeMode
|
||||
import com.brentvatne.common.api.SubtitleStyle
|
||||
|
||||
@UnstableApi
|
||||
class ExoPlayerView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
|
||||
FrameLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
private var localStyle = SubtitleStyle()
|
||||
private var pendingResizeMode: Int? = null
|
||||
private val liveBadge: TextView = TextView(context).apply {
|
||||
text = "LIVE"
|
||||
setTextColor(Color.WHITE)
|
||||
textSize = 12f
|
||||
val drawable = GradientDrawable()
|
||||
drawable.setColor(Color.RED)
|
||||
drawable.cornerRadius = 6f
|
||||
background = drawable
|
||||
setPadding(12, 4, 12, 4)
|
||||
visibility = View.GONE
|
||||
}
|
||||
|
||||
private val playerView = PlayerView(context).apply {
|
||||
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
setShutterBackgroundColor(Color.TRANSPARENT)
|
||||
useController = true
|
||||
controllerAutoShow = true
|
||||
controllerHideOnTouch = true
|
||||
controllerShowTimeoutMs = 5000
|
||||
// Don't show subtitle button by default - will be enabled when tracks are available
|
||||
setShowSubtitleButton(false)
|
||||
// Enable proper surface view handling to prevent rendering issues
|
||||
setUseArtwork(false)
|
||||
setDefaultArtwork(null)
|
||||
// Ensure proper video scaling - start with FIT mode
|
||||
resizeMode = androidx.media3.ui.AspectRatioFrameLayout.RESIZE_MODE_FIT
|
||||
}
|
||||
|
||||
/**
|
||||
* Subtitles rendered in a full-size overlay (NOT inside PlayerView's content frame).
|
||||
* This keeps subtitles anchored in-place even when the video surface/content frame moves
|
||||
* due to aspect ratio / resizeMode changes.
|
||||
*
|
||||
* Controlled by SubtitleStyle.subtitlesFollowVideo.
|
||||
*/
|
||||
private val overlaySubtitleView = SubtitleView(context).apply {
|
||||
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
visibility = View.GONE
|
||||
// We control styling via SubtitleStyle; don't pull Android system caption defaults.
|
||||
setApplyEmbeddedStyles(true)
|
||||
setApplyEmbeddedFontSizes(true)
|
||||
}
|
||||
|
||||
private fun updateSubtitleRenderingMode() {
|
||||
val internalSubtitleView = playerView.subtitleView
|
||||
val followVideo = localStyle.subtitlesFollowVideo
|
||||
val shouldShow = localStyle.opacity != 0.0f
|
||||
|
||||
if (followVideo) {
|
||||
internalSubtitleView?.visibility = if (shouldShow) View.VISIBLE else View.GONE
|
||||
overlaySubtitleView.visibility = View.GONE
|
||||
} else {
|
||||
// Hard-disable PlayerView's internal subtitle view. PlayerView can recreate/toggle this view
|
||||
// during resize/layout, so we re-assert this in multiple lifecycle points.
|
||||
internalSubtitleView?.visibility = View.GONE
|
||||
internalSubtitleView?.alpha = 0f
|
||||
overlaySubtitleView.visibility = if (shouldShow) View.VISIBLE else View.GONE
|
||||
overlaySubtitleView.alpha = 1f
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
// Add PlayerView with explicit layout parameters
|
||||
val playerViewLayoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
addView(playerView, playerViewLayoutParams)
|
||||
|
||||
// Add overlay subtitles above PlayerView (so it doesn't move with video content frame)
|
||||
val subtitleOverlayLayoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
addView(overlaySubtitleView, subtitleOverlayLayoutParams)
|
||||
|
||||
// Add live badge with its own layout parameters
|
||||
val liveBadgeLayoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
|
||||
liveBadgeLayoutParams.setMargins(16, 16, 16, 16)
|
||||
addView(liveBadge, liveBadgeLayoutParams)
|
||||
|
||||
// PlayerView may internally recreate its subtitle view during relayouts (e.g. resizeMode changes).
|
||||
// Ensure our rendering mode is re-applied whenever PlayerView lays out.
|
||||
playerView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
|
||||
updateSubtitleRenderingMode()
|
||||
}
|
||||
}
|
||||
|
||||
fun setPlayer(player: ExoPlayer?) {
|
||||
val currentPlayer = playerView.player
|
||||
|
||||
if (currentPlayer != null) {
|
||||
currentPlayer.removeListener(playerListener)
|
||||
}
|
||||
|
||||
playerView.player = player
|
||||
|
||||
if (player != null) {
|
||||
player.addListener(playerListener)
|
||||
|
||||
// Apply pending resize mode if we have one
|
||||
pendingResizeMode?.let { resizeMode ->
|
||||
playerView.resizeMode = resizeMode
|
||||
}
|
||||
}
|
||||
|
||||
// Re-assert subtitle rendering mode for the current style.
|
||||
updateSubtitleRenderingMode()
|
||||
applySubtitleStyle(localStyle)
|
||||
}
|
||||
|
||||
fun getPlayerView(): PlayerView = playerView
|
||||
|
||||
fun setResizeMode(@ResizeMode.Mode resizeMode: Int) {
|
||||
val targetResizeMode = when (resizeMode) {
|
||||
ResizeMode.RESIZE_MODE_FILL -> AspectRatioFrameLayout.RESIZE_MODE_FILL
|
||||
ResizeMode.RESIZE_MODE_CENTER_CROP -> AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
||||
ResizeMode.RESIZE_MODE_FIT -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
||||
ResizeMode.RESIZE_MODE_FIXED_WIDTH -> AspectRatioFrameLayout.RESIZE_MODE_FIXED_WIDTH
|
||||
ResizeMode.RESIZE_MODE_FIXED_HEIGHT -> AspectRatioFrameLayout.RESIZE_MODE_FIXED_HEIGHT
|
||||
else -> AspectRatioFrameLayout.RESIZE_MODE_FIT
|
||||
}
|
||||
|
||||
// Apply the resize mode to PlayerView immediately
|
||||
playerView.resizeMode = targetResizeMode
|
||||
|
||||
// Store it for reapplication if needed
|
||||
pendingResizeMode = targetResizeMode
|
||||
|
||||
// Force PlayerView to recalculate its layout
|
||||
playerView.requestLayout()
|
||||
|
||||
// Also request layout on the parent to ensure proper sizing
|
||||
requestLayout()
|
||||
}
|
||||
|
||||
fun setSubtitleStyle(style: SubtitleStyle) {
|
||||
localStyle = style
|
||||
applySubtitleStyle(localStyle)
|
||||
}
|
||||
|
||||
private fun applySubtitleStyle(style: SubtitleStyle) {
|
||||
updateSubtitleRenderingMode()
|
||||
|
||||
playerView.subtitleView?.let { subtitleView ->
|
||||
// Important:
|
||||
// Avoid inheriting Android system caption settings via setUserDefaultStyle(),
|
||||
// because those can force a background/window that the app doesn't want.
|
||||
val resolvedTextColor = style.textColor ?: CaptionStyleCompat.DEFAULT.foregroundColor
|
||||
val resolvedBackgroundColor = style.backgroundColor ?: Color.TRANSPARENT
|
||||
val resolvedEdgeColor = style.edgeColor ?: Color.BLACK
|
||||
|
||||
val resolvedEdgeType = when (style.edgeType?.lowercase()) {
|
||||
"outline" -> CaptionStyleCompat.EDGE_TYPE_OUTLINE
|
||||
"shadow" -> CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW
|
||||
else -> CaptionStyleCompat.EDGE_TYPE_NONE
|
||||
}
|
||||
|
||||
// windowColor MUST be transparent to avoid the "caption window" background.
|
||||
val captionStyle = CaptionStyleCompat(
|
||||
resolvedTextColor,
|
||||
resolvedBackgroundColor,
|
||||
Color.TRANSPARENT,
|
||||
resolvedEdgeType,
|
||||
resolvedEdgeColor,
|
||||
null
|
||||
)
|
||||
subtitleView.setStyle(captionStyle)
|
||||
|
||||
// Text size: if not provided, fall back to user default size.
|
||||
if (style.fontSize > 0) {
|
||||
// Use DIP so the value matches React Native's dp-based fontSize more closely.
|
||||
// SP would multiply by system fontScale and makes "30" look larger than RN "30".
|
||||
subtitleView.setFixedTextSize(android.util.TypedValue.COMPLEX_UNIT_DIP, style.fontSize.toFloat())
|
||||
} else {
|
||||
subtitleView.setUserDefaultTextSize()
|
||||
}
|
||||
|
||||
// Horizontal padding is still useful (safe area); vertical offset is handled via bottomPaddingFraction.
|
||||
subtitleView.setPadding(
|
||||
style.paddingLeft,
|
||||
style.paddingTop,
|
||||
style.paddingRight,
|
||||
0
|
||||
)
|
||||
|
||||
// Bottom offset for *internal* subtitles:
|
||||
// Use Media3 SubtitleView's bottomPaddingFraction (moves cues up) rather than raw view padding.
|
||||
if (style.paddingBottom > 0 && playerView.height > 0) {
|
||||
val fraction = (style.paddingBottom.toFloat() / playerView.height.toFloat())
|
||||
.coerceIn(0f, 0.9f)
|
||||
subtitleView.setBottomPaddingFraction(fraction)
|
||||
}
|
||||
|
||||
if (style.opacity != 0.0f) {
|
||||
subtitleView.alpha = style.opacity
|
||||
subtitleView.visibility = android.view.View.VISIBLE
|
||||
} else {
|
||||
subtitleView.visibility = android.view.View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the same styling to the overlay subtitle view.
|
||||
run {
|
||||
val subtitleView = overlaySubtitleView
|
||||
|
||||
val resolvedTextColor = style.textColor ?: CaptionStyleCompat.DEFAULT.foregroundColor
|
||||
val resolvedBackgroundColor = style.backgroundColor ?: Color.TRANSPARENT
|
||||
val resolvedEdgeColor = style.edgeColor ?: Color.BLACK
|
||||
|
||||
val resolvedEdgeType = when (style.edgeType?.lowercase()) {
|
||||
"outline" -> CaptionStyleCompat.EDGE_TYPE_OUTLINE
|
||||
"shadow" -> CaptionStyleCompat.EDGE_TYPE_DROP_SHADOW
|
||||
else -> CaptionStyleCompat.EDGE_TYPE_NONE
|
||||
}
|
||||
|
||||
val captionStyle = CaptionStyleCompat(
|
||||
resolvedTextColor,
|
||||
resolvedBackgroundColor,
|
||||
Color.TRANSPARENT,
|
||||
resolvedEdgeType,
|
||||
resolvedEdgeColor,
|
||||
null
|
||||
)
|
||||
subtitleView.setStyle(captionStyle)
|
||||
|
||||
if (style.fontSize > 0) {
|
||||
// Use DIP so the value matches React Native's dp-based fontSize more closely.
|
||||
subtitleView.setFixedTextSize(android.util.TypedValue.COMPLEX_UNIT_DIP, style.fontSize.toFloat())
|
||||
} else {
|
||||
subtitleView.setUserDefaultTextSize()
|
||||
}
|
||||
|
||||
subtitleView.setPadding(
|
||||
style.paddingLeft,
|
||||
style.paddingTop,
|
||||
style.paddingRight,
|
||||
0
|
||||
)
|
||||
|
||||
// Bottom offset relative to the full view height (stable even when video content frame moves).
|
||||
val h = height.takeIf { it > 0 } ?: subtitleView.height
|
||||
if (style.paddingBottom > 0 && h > 0) {
|
||||
val fraction = (style.paddingBottom.toFloat() / h.toFloat())
|
||||
.coerceIn(0f, 0.9f)
|
||||
subtitleView.setBottomPaddingFraction(fraction)
|
||||
} else {
|
||||
subtitleView.setBottomPaddingFraction(0f)
|
||||
}
|
||||
|
||||
if (style.opacity != 0.0f) {
|
||||
subtitleView.alpha = style.opacity
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setShutterColor(color: Int) {
|
||||
playerView.setShutterBackgroundColor(color)
|
||||
}
|
||||
|
||||
fun updateSurfaceView(viewType: Int) {
|
||||
// TODO: Implement proper surface type switching if needed
|
||||
}
|
||||
|
||||
val isPlaying: Boolean
|
||||
get() = playerView.player?.isPlaying ?: false
|
||||
|
||||
fun invalidateAspectRatio() {
|
||||
// PlayerView handles aspect ratio automatically through its internal AspectRatioFrameLayout
|
||||
playerView.requestLayout()
|
||||
|
||||
// Reapply the current resize mode to ensure it's properly set
|
||||
pendingResizeMode?.let { resizeMode ->
|
||||
playerView.resizeMode = resizeMode
|
||||
}
|
||||
}
|
||||
|
||||
fun setUseController(useController: Boolean) {
|
||||
playerView.useController = useController
|
||||
if (useController) {
|
||||
// Ensure proper touch handling when controls are enabled
|
||||
playerView.controllerAutoShow = true
|
||||
playerView.controllerHideOnTouch = true
|
||||
// Show controls immediately when enabled
|
||||
playerView.showController()
|
||||
}
|
||||
}
|
||||
|
||||
fun showController() {
|
||||
playerView.showController()
|
||||
}
|
||||
|
||||
fun hideController() {
|
||||
playerView.hideController()
|
||||
}
|
||||
|
||||
fun setControllerShowTimeoutMs(showTimeoutMs: Int) {
|
||||
playerView.controllerShowTimeoutMs = showTimeoutMs
|
||||
}
|
||||
|
||||
fun setControllerAutoShow(autoShow: Boolean) {
|
||||
playerView.controllerAutoShow = autoShow
|
||||
}
|
||||
|
||||
fun setControllerHideOnTouch(hideOnTouch: Boolean) {
|
||||
playerView.controllerHideOnTouch = hideOnTouch
|
||||
}
|
||||
|
||||
fun setFullscreenButtonClickListener(listener: PlayerView.FullscreenButtonClickListener?) {
|
||||
playerView.setFullscreenButtonClickListener(listener)
|
||||
}
|
||||
|
||||
fun setShowSubtitleButton(show: Boolean) {
|
||||
playerView.setShowSubtitleButton(show)
|
||||
}
|
||||
|
||||
fun isControllerVisible(): Boolean = playerView.isControllerFullyVisible
|
||||
|
||||
fun setControllerVisibilityListener(listener: PlayerView.ControllerVisibilityListener?) {
|
||||
playerView.setControllerVisibilityListener(listener)
|
||||
}
|
||||
|
||||
override fun addOnLayoutChangeListener(listener: View.OnLayoutChangeListener) {
|
||||
playerView.addOnLayoutChangeListener(listener)
|
||||
}
|
||||
|
||||
override fun setFocusable(focusable: Boolean) {
|
||||
playerView.isFocusable = focusable
|
||||
}
|
||||
|
||||
private fun updateLiveUi() {
|
||||
val player = playerView.player ?: return
|
||||
val isLive = player.isCurrentMediaItemLive
|
||||
val seekable = player.isCurrentMediaItemSeekable
|
||||
|
||||
// Show/hide badge
|
||||
liveBadge.visibility = if (isLive) View.VISIBLE else View.GONE
|
||||
|
||||
// Disable/enable scrubbing based on seekable
|
||||
val timeBar = playerView.findViewById<DefaultTimeBar?>(androidx.media3.ui.R.id.exo_progress)
|
||||
timeBar?.isEnabled = !isLive || seekable
|
||||
}
|
||||
|
||||
private val playerListener = object : Player.Listener {
|
||||
override fun onCues(cueGroup: CueGroup) {
|
||||
// Keep overlay subtitles in sync. This does NOT interfere with PlayerView's own subtitle rendering.
|
||||
// When subtitlesFollowVideo=false, overlaySubtitleView is the visible one.
|
||||
updateSubtitleRenderingMode()
|
||||
overlaySubtitleView.setCues(cueGroup.cues)
|
||||
}
|
||||
|
||||
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
|
||||
playerView.post {
|
||||
playerView.requestLayout()
|
||||
// Reapply resize mode to ensure it's properly set after timeline changes
|
||||
pendingResizeMode?.let { resizeMode ->
|
||||
playerView.resizeMode = resizeMode
|
||||
}
|
||||
}
|
||||
updateLiveUi()
|
||||
}
|
||||
|
||||
override fun onEvents(player: Player, events: Player.Events) {
|
||||
if (events.contains(Player.EVENT_MEDIA_ITEM_TRANSITION) ||
|
||||
events.contains(Player.EVENT_IS_PLAYING_CHANGED)
|
||||
) {
|
||||
updateLiveUi()
|
||||
}
|
||||
|
||||
// Handle video size changes which affect aspect ratio
|
||||
if (events.contains(Player.EVENT_VIDEO_SIZE_CHANGED)) {
|
||||
pendingResizeMode?.let { resizeMode ->
|
||||
playerView.resizeMode = resizeMode
|
||||
}
|
||||
playerView.requestLayout()
|
||||
requestLayout()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ExoPlayerView"
|
||||
}
|
||||
|
||||
/**
|
||||
* React Native (Yoga) can sometimes defer layout passes that are required by
|
||||
* PlayerView for its child views (controller overlay, surface view, subtitle view, …).
|
||||
* This helper forces a second measure / layout after RN finishes, ensuring the
|
||||
* internal views receive the final size. The same approach is used in the v7
|
||||
* implementation (see VideoView.kt) and in React Native core (Toolbar example [link]).
|
||||
*/
|
||||
private val layoutRunnable = Runnable {
|
||||
measure(
|
||||
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
|
||||
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
|
||||
)
|
||||
layout(left, top, right, bottom)
|
||||
}
|
||||
|
||||
override fun requestLayout() {
|
||||
super.requestLayout()
|
||||
// Post a second layout pass so the ExoPlayer internal views get correct bounds.
|
||||
post(layoutRunnable)
|
||||
}
|
||||
|
||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
|
||||
if (changed) {
|
||||
pendingResizeMode?.let { resizeMode ->
|
||||
playerView.resizeMode = resizeMode
|
||||
}
|
||||
// Re-apply bottomPaddingFraction once we have a concrete height.
|
||||
updateSubtitleRenderingMode()
|
||||
applySubtitleStyle(localStyle)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -161,10 +161,11 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
AdEvent.AdEventListener,
|
||||
AdErrorEvent.AdErrorListener {
|
||||
|
||||
public static final double DEFAULT_MAX_HEAP_ALLOCATION_PERCENT = 1;
|
||||
public static final double DEFAULT_MAX_HEAP_ALLOCATION_PERCENT = 0.5;
|
||||
public static final double DEFAULT_MIN_BUFFER_MEMORY_RESERVE = 0;
|
||||
|
||||
private static final String TAG = "ReactExoplayerView";
|
||||
private static final ExecutorService SHARED_EXECUTOR = Executors.newSingleThreadExecutor();
|
||||
|
||||
private static final CookieManager DEFAULT_COOKIE_MANAGER;
|
||||
private static final int SHOW_PROGRESS = 1;
|
||||
|
|
@ -211,6 +212,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
private float audioVolume = 1f;
|
||||
private int maxBitRate = 0;
|
||||
private boolean hasDrmFailed = false;
|
||||
private int drmRetryCount = 0;
|
||||
private boolean isUsingContentResolution = false;
|
||||
private boolean selectTrackWhenReady = false;
|
||||
private final Handler mainHandler;
|
||||
|
|
@ -227,6 +229,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
*/
|
||||
private boolean isSeeking = false;
|
||||
private long seekPosition = -1;
|
||||
private boolean hasVideoEnded = false;
|
||||
|
||||
// Props from React
|
||||
private Source source = new Source();
|
||||
|
|
@ -242,7 +245,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
private BufferingStrategy.BufferingStrategyEnum bufferingStrategy;
|
||||
private boolean disableDisconnectError;
|
||||
private boolean preventsDisplaySleepDuringVideoPlayback = true;
|
||||
private float mProgressUpdateInterval = 250.0f;
|
||||
private float mProgressUpdateInterval = 1000.0f;
|
||||
protected boolean playInBackground = false;
|
||||
private boolean mReportBandwidth = false;
|
||||
private boolean controls = false;
|
||||
|
|
@ -642,9 +645,12 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
PictureInPictureUtil.applyAutoEnterEnabled(themedReactContext, pictureInPictureParamsBuilder, this.enterPictureInPictureOnLeave);
|
||||
}
|
||||
if (!source.isLocalAssetFile() && !source.isAsset() && source.getBufferConfig().getCacheSize() > 0) {
|
||||
long requestedCacheSize = source.getBufferConfig().getCacheSize();
|
||||
long MAX_SAFE_CACHE_SIZE = 100L * 1024 * 1024;
|
||||
long effectiveCacheSize = Math.min(requestedCacheSize, MAX_SAFE_CACHE_SIZE);
|
||||
RNVSimpleCache.INSTANCE.setSimpleCache(
|
||||
this.getContext(),
|
||||
source.getBufferConfig().getCacheSize()
|
||||
(int) effectiveCacheSize
|
||||
);
|
||||
useCache = true;
|
||||
} else {
|
||||
|
|
@ -653,9 +659,10 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
if (playerNeedsSource) {
|
||||
// Will force display of shutter view if needed
|
||||
exoPlayerView.invalidateAspectRatio();
|
||||
drmRetryCount = 0;
|
||||
hasDrmFailed = false;
|
||||
// DRM session manager creation must be done on a different thread to prevent crashes so we start a new thread
|
||||
ExecutorService es = Executors.newSingleThreadExecutor();
|
||||
es.execute(() -> {
|
||||
SHARED_EXECUTOR.execute(() -> {
|
||||
// DRM initialization must run on a different thread
|
||||
if (viewHasDropped && runningSource == source) {
|
||||
return;
|
||||
|
|
@ -726,7 +733,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
|
||||
DefaultRenderersFactory renderersFactory =
|
||||
new DefaultRenderersFactory(getContext())
|
||||
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF)
|
||||
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER)
|
||||
.setEnableDecoderFallback(true)
|
||||
.forceEnableMediaCodecAsynchronousQueueing();
|
||||
|
||||
|
|
@ -850,13 +857,10 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
MediaSource mediaSource = Objects.requireNonNullElse(mediaSourceWithAds, videoSource);
|
||||
|
||||
// wait for player to be set
|
||||
while (player == null) {
|
||||
try {
|
||||
wait();
|
||||
} catch (InterruptedException ex) {
|
||||
Thread.currentThread().interrupt();
|
||||
DebugLog.e(TAG, ex.toString());
|
||||
}
|
||||
if (player == null) {
|
||||
DebugLog.w(TAG, "Player not ready yet, aborting source initialization");
|
||||
playerNeedsSource = true;
|
||||
return;
|
||||
}
|
||||
|
||||
boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
|
||||
|
|
@ -1411,6 +1415,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
break;
|
||||
case Player.STATE_READY:
|
||||
text += "ready";
|
||||
hasVideoEnded = false;
|
||||
eventEmitter.onReadyForDisplay.invoke();
|
||||
onBuffering(false);
|
||||
clearProgressMessageHandler(); // ensure there is no other message
|
||||
|
|
@ -1429,7 +1434,10 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
case Player.STATE_ENDED:
|
||||
text += "ended";
|
||||
updateProgress();
|
||||
eventEmitter.onVideoEnd.invoke();
|
||||
if (!hasVideoEnded) {
|
||||
hasVideoEnded = true;
|
||||
eventEmitter.onVideoEnd.invoke();
|
||||
}
|
||||
onStopPlayback();
|
||||
setKeepScreenOn(false);
|
||||
break;
|
||||
|
|
@ -1479,8 +1487,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
ArrayList<Track> textTracks = getTextTrackInfo();
|
||||
|
||||
if (source.getContentStartTime() != -1) {
|
||||
ExecutorService es = Executors.newSingleThreadExecutor();
|
||||
es.execute(() -> {
|
||||
SHARED_EXECUTOR.execute(() -> {
|
||||
// To prevent ANRs caused by getVideoTrackInfo we run this on a different thread and notify the player only when we're done
|
||||
ArrayList<VideoTrack> videoTracks = getVideoTrackInfoFromManifest();
|
||||
if (videoTracks != null) {
|
||||
|
|
@ -1591,12 +1598,11 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
// We need retry count to in case where minefest request fails from poor network conditions
|
||||
@WorkerThread
|
||||
private ArrayList<VideoTrack> getVideoTrackInfoFromManifest(int retryCount) {
|
||||
ExecutorService es = Executors.newSingleThreadExecutor();
|
||||
final DataSource dataSource = this.mediaDataSourceFactory.createDataSource();
|
||||
final Uri sourceUri = source.getUri();
|
||||
final long startTime = source.getContentStartTime() * 1000 - 100; // s -> ms with 100ms offset
|
||||
|
||||
Future<ArrayList<VideoTrack>> result = es.submit(new Callable() {
|
||||
Future<ArrayList<VideoTrack>> result = SHARED_EXECUTOR.submit(new Callable<ArrayList<VideoTrack>>() {
|
||||
final DataSource ds = dataSource;
|
||||
final Uri uri = sourceUri;
|
||||
final long startTimeUs = startTime * 1000; // ms -> us
|
||||
|
|
@ -1643,7 +1649,6 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
if (results == null && retryCount < 1) {
|
||||
return this.getVideoTrackInfoFromManifest(++retryCount);
|
||||
}
|
||||
es.shutdown();
|
||||
return results;
|
||||
} catch (Exception e) {
|
||||
DebugLog.w(TAG, "error in getVideoTrackInfoFromManifest handling request:" + e.getMessage());
|
||||
|
|
@ -1819,7 +1824,10 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION
|
||||
&& player.getRepeatMode() == Player.REPEAT_MODE_ONE) {
|
||||
updateProgress();
|
||||
eventEmitter.onVideoEnd.invoke();
|
||||
if (!hasVideoEnded) {
|
||||
hasVideoEnded = true;
|
||||
eventEmitter.onVideoEnd.invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1931,12 +1939,15 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
case PlaybackException.ERROR_CODE_DRM_UNSPECIFIED:
|
||||
if (!hasDrmFailed) {
|
||||
// When DRM fails to reach the app level certificate server it will fail with a source error so we assume that it is DRM related and try one more time
|
||||
hasDrmFailed = true;
|
||||
playerNeedsSource = true;
|
||||
updateResumePosition();
|
||||
initializePlayer();
|
||||
setPlayWhenReady(true);
|
||||
return;
|
||||
if (drmRetryCount < 1) {
|
||||
drmRetryCount++;
|
||||
hasDrmFailed = true;
|
||||
playerNeedsSource = true;
|
||||
updateResumePosition();
|
||||
initializePlayer();
|
||||
setPlayWhenReady(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
|
@ -2030,6 +2041,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
}
|
||||
|
||||
if (!isSourceEqual) {
|
||||
hasVideoEnded = false;
|
||||
playerNeedsSource = true;
|
||||
initializePlayer();
|
||||
}
|
||||
|
|
@ -2114,6 +2126,16 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
if (textRendererIndex != C.INDEX_UNSET) {
|
||||
TrackGroupArray groups = info.getTrackGroups(textRendererIndex);
|
||||
boolean trackFound = false;
|
||||
// NOTE:
|
||||
// RNVideo emits textTracks as a flattened list (Track.index is assigned using textTracks.size()).
|
||||
// However, previous logic compared the requested "index" against the *trackIndex within a group*,
|
||||
// which makes any index > 0 either select the wrong subtitle or keep the first one.
|
||||
// Here we interpret type="index" as the flattened index, matching the JS list order.
|
||||
int targetFlatIndex = -1;
|
||||
if ("index".equals(type)) {
|
||||
targetFlatIndex = ReactBridgeUtils.safeParseInt(value, -1);
|
||||
}
|
||||
int flatIndex = 0;
|
||||
|
||||
for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
|
||||
TrackGroup group = groups.get(groupIndex);
|
||||
|
|
@ -2126,8 +2148,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
} else if ("title".equals(type) && format.label != null && format.label.equals(value)) {
|
||||
isMatch = true;
|
||||
} else if ("index".equals(type)) {
|
||||
int targetIndex = ReactBridgeUtils.safeParseInt(value, -1);
|
||||
if (targetIndex == trackIndex) {
|
||||
if (targetFlatIndex != -1 && targetFlatIndex == flatIndex) {
|
||||
isMatch = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -2139,6 +2160,7 @@ public class ReactExoplayerView extends FrameLayout implements
|
|||
trackFound = true;
|
||||
break;
|
||||
}
|
||||
flatIndex++;
|
||||
}
|
||||
if (trackFound) break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,86 @@
|
|||
"https://github.com/tapframe/NuvioStreaming/blob/main/screenshots/search-portrait.png?raw=true"
|
||||
],
|
||||
"versions": [
|
||||
{
|
||||
"version": "1.3.5",
|
||||
"buildVersion": "33",
|
||||
"date": "2026-01-09",
|
||||
"localizedDescription": "## Update Notes\n\n### ExoPlayer Subtitle Fixes\nThis update mainly focuses on fixing multiple subtitle-related issues in ExoPlayer:\n- Fixed issue where **only the first subtitle index** was being rendered \n- Fixed subtitles **always showing background** due to Android native caption settings \n- Fixed subtitles being **rendered inside the player view** \n- Fixed **subtitle bottom offset** issues \n- Merged PR **#391** by **@saifshaikh1805** \n - Fixes issue **#301**\n\n### KSPlayer Improvements\n- Improved **subtitle behavior** in KSPlayer \n- Merged PR **#394** by **@AdityasahuX07**\n\n### Settings & Persistence\n- Implemented **save and load** functionality for **Discover settings**\n\nThis release improves subtitle rendering accuracy across players and adds better persistence for user preferences.",
|
||||
"downloadURL": "https://github.com/tapframe/NuvioStreaming/releases/download/v1.3.5/app-release.apk",
|
||||
"size": 25700000
|
||||
},
|
||||
{
|
||||
"version": "1.3.4",
|
||||
"buildVersion": "32",
|
||||
"date": "2026-01-06",
|
||||
"localizedDescription": "## Update Notes\n\n### Player & Playback\n- Fixed **Android player crashes with large files** when using ExoPlayer \n - Merged PR **#361** by **@chrisk325**\n\n### Trakt Improvements\n- Improved **Trakt Continue Watching** section for better accuracy and reliability\n\n### Internationalization\n- Added **multi-language support** across the app using **i18n** \n - More languages will be added through **community contributions** \n - ⚠️ **Arabic UI does not use RTL yet**. RTL support will be added in a future update\n\n### Stability & Fixes\n- Crash optimizations and internal stability improvements\n\nThis update focuses on improving playback stability, Trakt experience, and expanding language support.",
|
||||
"downloadURL": "https://github.com/tapframe/NuvioStreaming/releases/download/v1.3.4/app-release.apk",
|
||||
"size": 25700000
|
||||
},
|
||||
{
|
||||
"version": "1.3.3",
|
||||
"buildVersion": "31",
|
||||
"date": "2026-01-01",
|
||||
"localizedDescription": "# Nuvio Media Hub – v1.3.3\n\n## Update Notes\n\n### Playback & Preferences\n- Added **default audio and subtitle track selection**\n\n### Plugins & Repositories\n- Added support for **multiple active repositories**\n- Improved **plugin fetch logic** for better reliability and performance\n- Changed OTA server.\n\n### Trakt & Metadata Fixes\n- Fixed **TMDB enrichment logic**\n- Fixed **Trakt watch progress not syncing** for older seasons \n - Contributed by **@chrisk325** \n - Fixes #331 and closes #233 \n - ⚠️ It is recommended to **log out and log back into Trakt** inside the app to correctly reflect watched status for older seasons\n\n### UI Improvements & Bug Fixes\n- Minor UI refinements and bug fixes \n- Added **YouTube-style press-and-hold playback speed indicator** \n- Refined **gesture indicator pill** \n- Fixed **OSC not auto-hiding on Android** \n - Contributed by **@AdityasahuX07** \n - Fixes #326 and #298 \n\nThis release includes valuable contributions from the community and focuses on improving playback preferences, plugin handling, Trakt syncing, and overall UI polish.",
|
||||
"downloadURL": "https://github.com/tapframe/NuvioStreaming/releases/download/1.3.3/Stable_1-3-3.ipa",
|
||||
"size": 25700000
|
||||
},
|
||||
{
|
||||
"version": "1.3.2",
|
||||
"buildVersion": "30",
|
||||
"date": "2025-12-28",
|
||||
"localizedDescription": "# Nuvio Media Hub – v1.3.2\n\n### New Features\n- Added **Skip Intro** feature powered by **IntroDB** \n- Added support for **Internal Subtitle customization** for both **MPV** and **ExoPlayer** \n - ExoPlayer customization is currently limited \n\n### Stability & Fixes\n- Improved **StreamScreen error handling** to prevent crashes \n- Minor bug fixes and internal improvements",
|
||||
"downloadURL": "https://github.com/tapframe/NuvioStreaming/releases/download/1.3.2/Stable_1-3-2.ipa",
|
||||
"size": 25700000
|
||||
},
|
||||
{
|
||||
"version": "1.3.0",
|
||||
"buildVersion": "28",
|
||||
"date": "2025-12-24",
|
||||
"localizedDescription": "# ⚠️ Important Notice Before Updating to v1.3.0\n\nBefore updating, please read this carefully.\n\nEspecially for **Android users**, this update is **not mandatory**.\nWe have **completely migrated the internal player** from **ExoPlayer + VLC** to **MPV Player**.\nBecause this is a major internal change, **unexpected bugs may occur**.\n\nThis update is **recommended only for users who are willing to test and provide feedback**.\nYour feedback is important to help complete this migration and make it bug-free.\n\n---\n\n# Nuvio Media Hub – v1.3.0\n\n## Android\n- Replaced internal player with **MPV Player** for better codec support\n- Added toggles for **Software (SW) / Hardware (HW) decoding**\n\n## Global\n- Dependency updates\n- Minor bug fixes",
|
||||
"downloadURL": "https://github.com/tapframe/NuvioStreaming/releases/download/1.3.0/Stable_1-3-0.ipa",
|
||||
"size": 25700000
|
||||
},
|
||||
{
|
||||
"version": "1.2.11",
|
||||
"buildVersion": "27",
|
||||
"date": "2025-12-15",
|
||||
"localizedDescription": "# Nuvio Media Hub – v1.2.11 \n\n## Update Notes\n- **Dependency updates** for improved stability \n- **Android animation improvements** for smoother UI interactions \n- Multiple **backend bug fixes** \n\n## Note for iOS Users\n- iOS users can continue using the **TestFlight build** \n- It may show version **1.2.10 (27)**, but the **build is the same as 1.2.11** \n- This is intentional, as bumping the version would require a manual TestFlight review",
|
||||
"downloadURL": "https://github.com/tapframe/NuvioStreaming/releases/download/1.2.11/Stable_1-2-11.ipa",
|
||||
"size": 25700000
|
||||
},
|
||||
{
|
||||
"version": "1.2.10",
|
||||
"buildVersion": "25",
|
||||
"date": "2025-11-25",
|
||||
"localizedDescription": "# Nuvio Media Hub – v1.2.10 \n\n## Update Notes\n- **Dependency updates** for stability and performance \n- **Trakt optimizations** for smoother syncing \n- **Subtitle RTL detection** improvements for better language handling \n- **KSPlayer** pause behavior improvements \n- Fixed incorrect **HDR detection logic** in KSPlayer \n- Simplified **This Week’s section** card UI for a cleaner look \n\n## 📦 Changelog & Download\n[View on GitHub](https://github.com/tapframe/NuvioStreaming/releases/tag/1.2.10)\n\n🌐 **Official Website:** [tapframe.github.io/NuvioStreaming](https://tapframe.github.io/NuvioStreaming)\n\nIf you like **Nuvio Media Hub**, please consider **⭐ starring it on GitHub**. It really helps the project grow \n[⭐ Star on GitHub](https://github.com/tapframe/NuvioStreaming)",
|
||||
"downloadURL": "https://github.com/tapframe/NuvioStreaming/releases/download/1.2.10/Stable_1-2-10.ipa",
|
||||
"size": 25700000
|
||||
},
|
||||
{
|
||||
"version": "1.2.9",
|
||||
"buildVersion": "24",
|
||||
"date": "2025-11-16",
|
||||
"localizedDescription": "# Nuvio Media Hub – v1.2.9 \n\n## Update Notes\n- **Trakt optimizations**\n- **Dependency updates** for better performance \n- **API fixes** to improve data fetching and stability \n- General **bug fixes** across the app ",
|
||||
"downloadURL": "https://github.com/tapframe/NuvioStreaming/releases/download/1.2.9/Stable_1-2-9.ipa",
|
||||
"size": 25700000
|
||||
},
|
||||
{
|
||||
"version": "1.2.8",
|
||||
"buildVersion": "23",
|
||||
"date": "2025-11-09",
|
||||
"localizedDescription": "# Nuvio Media Hub – v1.2.8 \n\n## Update Notes\n- Dependency updates for stability and performance \n- Minor animation tweaks \n- Backend fixes and optimizations ",
|
||||
"downloadURL": "https://github.com/tapframe/NuvioStreaming/releases/download/1.2.8/Stable_1-2-8.ipa",
|
||||
"size": 25700000
|
||||
},
|
||||
{
|
||||
"version": "1.2.7",
|
||||
"buildVersion": "22",
|
||||
"date": "2025-10-26",
|
||||
"localizedDescription": "# ⚠️ Important: Backup Before Updating to v1.2.7 \n\nBefore installing this update, **please back up your data** by going to **Settings → Backup** inside the app. \nThis update replaces the internal storage system. **Old data will not be retrievable** without a backup. \n\n---\n\n# Nuvio Media Hub – v1.2.7 \n\n## Major Changes\n- Replaced **Async Storage** with **React Native MMKV** for **much faster and more reliable local storage** \n > ⚠️ This change resets previous local data unless backed up and restored \n- Added **Playback Speed Controls** to **KSPlayer** (iOS) \n- Repositioned **Details Screen Action Buttons** for a cleaner layout \n- Added **TMDB request caching** for faster performance when revisiting titles \n- Fixed **Library Screen issue** with adding/removing movies and shows \n\n---\n\n## Features Previously Released via OTA\n- **Playback controls for Android** \n- **Library screen layout improvements** \n- Various **backend bug fixes** \n\n---\n\n## Recommended\nAll users are **strongly advised to perform a clean install** after updating \nIf you've created a backup in the old version, restore it after installation using the **Restore** option in settings",
|
||||
"downloadURL": "https://github.com/tapframe/NuvioStreaming/releases/download/1.2.7/Stable_1-2-7.ipa",
|
||||
"size": 25700000
|
||||
},
|
||||
{
|
||||
"version": "1.2.6",
|
||||
"buildVersion": "21",
|
||||
|
|
@ -176,4 +256,4 @@
|
|||
}
|
||||
],
|
||||
"news": []
|
||||
}
|
||||
}
|
||||
3044
package-lock.json
generated
49
package.json
|
|
@ -10,25 +10,26 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@adrianso/react-native-device-brightness": "^1.2.7",
|
||||
"@backpackapp-io/react-native-toast": "^0.14.0",
|
||||
"@bottom-tabs/react-navigation": "^0.12.2",
|
||||
"@d11/react-native-fast-image": "^8.8.0",
|
||||
"@backpackapp-io/react-native-toast": "^0.15.1",
|
||||
"@bottom-tabs/react-navigation": "^1.0.2",
|
||||
"@d11/react-native-fast-image": "^8.13.0",
|
||||
"@expo/env": "^2.0.7",
|
||||
"@expo/metro-runtime": "~6.1.2",
|
||||
"@expo/vector-icons": "^15.0.2",
|
||||
"@gorhom/bottom-sheet": "^5.2.6",
|
||||
"@legendapp/list": "^2.0.13",
|
||||
"@lottiefiles/dotlottie-react": "^0.6.5",
|
||||
"@lottiefiles/dotlottie-react": "^0.17.7",
|
||||
"@react-native-community/blur": "^4.4.1",
|
||||
"@react-native-community/netinfo": "^11.4.1",
|
||||
"@react-native-community/slider": "5.0.1",
|
||||
"@react-native-picker/picker": "2.11.1",
|
||||
"@react-native-community/slider": "^5.1.1",
|
||||
"@react-native-picker/picker": "^2.11.4",
|
||||
"@react-navigation/bottom-tabs": "^7.3.10",
|
||||
"@react-navigation/native": "^7.1.6",
|
||||
"@react-navigation/native-stack": "^7.3.10",
|
||||
"@react-navigation/stack": "^7.2.10",
|
||||
"@sentry/react-native": "~7.3.0",
|
||||
"@shopify/flash-list": "^2.1.0",
|
||||
"@sentry/react-native": "^7.6.0",
|
||||
"@shopify/flash-list": "^2.2.0",
|
||||
"@shopify/react-native-skia": "^2.4.14",
|
||||
"@types/lodash": "^4.17.16",
|
||||
"@types/react-native-video": "^5.0.20",
|
||||
"axios": "^1.12.2",
|
||||
|
|
@ -42,17 +43,19 @@
|
|||
"expo-auth-session": "~7.0.8",
|
||||
"expo-blur": "~15.0.7",
|
||||
"expo-brightness": "~14.0.7",
|
||||
"expo-clipboard": "~8.0.8",
|
||||
"expo-crypto": "~15.0.7",
|
||||
"expo-dev-client": "~6.0.15",
|
||||
"expo-dev-client": "~6.0.20",
|
||||
"expo-device": "~8.0.9",
|
||||
"expo-document-picker": "~14.0.7",
|
||||
"expo-file-system": "~19.0.17",
|
||||
"expo-glass-effect": "~0.1.4",
|
||||
"expo-haptics": "~15.0.7",
|
||||
"expo-intent-launcher": "~13.0.7",
|
||||
"expo-libvlc-player": "^2.1.7",
|
||||
"expo-keep-awake": "~15.0.8",
|
||||
"expo-linear-gradient": "~15.0.7",
|
||||
"expo-localization": "~17.0.7",
|
||||
"expo-navigation-bar": "~5.0.10",
|
||||
"expo-notifications": "~0.32.12",
|
||||
"expo-random": "^14.0.1",
|
||||
"expo-screen-orientation": "~9.0.7",
|
||||
|
|
@ -61,31 +64,37 @@
|
|||
"expo-system-ui": "~6.0.7",
|
||||
"expo-updates": "~29.0.12",
|
||||
"expo-web-browser": "~15.0.8",
|
||||
"i18next": "^25.7.3",
|
||||
"intl-pluralrules": "^2.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lottie-react-native": "~7.3.1",
|
||||
"posthog-react-native": "^4.4.0",
|
||||
"react": "19.1.0",
|
||||
"react-dom": "19.1.0",
|
||||
"react-i18next": "^16.5.1",
|
||||
"react-native": "0.81.4",
|
||||
"react-native-boost": "^0.6.2",
|
||||
"react-native-bottom-tabs": "^0.12.2",
|
||||
"react-native-gesture-handler": "~2.28.0",
|
||||
"react-native-get-random-values": "^1.11.0",
|
||||
"react-native-bottom-tabs": "^1.0.2",
|
||||
"react-native-gesture-handler": "^2.29.1",
|
||||
"react-native-get-random-values": "^2.0.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",
|
||||
"react-native-mmkv": "^4.0.0",
|
||||
"react-native-nitro-modules": "^0.31.2",
|
||||
"react-native-paper": "^5.14.5",
|
||||
"react-native-reanimated": "^4.1.1",
|
||||
"react-native-reanimated": "^4.2.0",
|
||||
"react-native-reanimated-carousel": "^4.0.3",
|
||||
"react-native-safe-area-context": "~5.6.0",
|
||||
"react-native-screens": "~4.16.0",
|
||||
"react-native-svg": "15.12.1",
|
||||
"react-native-url-polyfill": "^2.0.0",
|
||||
"react-native-screens": "^4.18.0",
|
||||
"react-native-svg": "^15.12.1",
|
||||
"react-native-url-polyfill": "^3.0.0",
|
||||
"react-native-vector-icons": "^10.3.0",
|
||||
"react-native-video": "^6.17.0",
|
||||
"react-native-video": "6.18.0",
|
||||
"react-native-web": "^0.21.0",
|
||||
"react-native-wheel-color-picker": "^1.3.1",
|
||||
"react-native-worklets": "^0.6.1"
|
||||
"react-native-worklets": "^0.7.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.25.2",
|
||||
|
|
@ -96,7 +105,7 @@
|
|||
"babel-plugin-transform-remove-console": "^6.9.4",
|
||||
"patch-package": "^8.0.1",
|
||||
"react-native-svg-transformer": "^1.5.0",
|
||||
"typescript": "^5.3.3",
|
||||
"typescript": "^5.9.3",
|
||||
"xcode": "^3.0.1"
|
||||
},
|
||||
"private": true
|
||||
|
|
|
|||