Compare commits

...

433 commits
1.2.7 ... main

Author SHA1 Message Date
tapframe
7bf3a344f3 release: 1.3.5 (33) 2026-01-12 00:13:29 +05:30
Nayif
14e8e90ee3
Merge pull request #391 from saifshaikh1805/patch-issue301
fix: issue #301
2026-01-11 22:48:15 +05:30
Nayif
d52c202518
Merge pull request #394 from AdityasahuX07/patch-20
Implement save and load for discover settings
2026-01-11 22:47:38 +05:30
tapframe
c728f4ea8d updated exoplayer sub behaviour 2026-01-11 22:46:54 +05:30
tapframe
c20c2713d0 updated exosub 2026-01-11 20:45:36 +05:30
tapframe
d398c73214 removed debrid integration 2026-01-11 20:45:32 +05:30
tapframe
9e6b455323 temp disable splash 2026-01-11 20:45:28 +05:30
AdityasahuX07
5a2271c64e
Implement save and load for discover settings
Added functionality to save and load discover settings including type, catalog, and genre.
2026-01-11 18:21:56 +05:30
tapframe
eb6fcf639f ksp sub updates 2026-01-11 00:46:30 +05:30
tapframe
a85cc93026 internal sub offset, bg fix android 2026-01-10 23:43:32 +05:30
tapframe
56fd18a8e9 Merge branch 'main' of https://github.com/tapframe/NuvioStreaming 2026-01-10 23:31:47 +05:30
tapframe
82d0ebb714 Fix ExoPlayer subtitle styling and iOS MPV config
- Fix subtitle track selection (only first track worked)
- Fix subtitle styling (background, outline, bottom offset)
- Update iOS MPV to match Wayve settings (Vulkan, HDR, stability options)
- Add patch-package for react-native-video fixes
2026-01-10 23:31:08 +05:30
Saif Shaikh
df5772d40b fix: issue #301 2026-01-10 08:10:20 -08:00
Nayif
3030d5961d
Merge pull request #388 from saifshaikh1805/patch/bottomSheetIssuesAndRefactor
patch: bottom sheet back button behavior, SettingsScreen.tsx refactor
2026-01-10 13:36:20 +05:30
Saif Shaikh
6974768457 patch: bottom sheet back button behavior, SettingsScreen.tsx refactor 2026-01-09 23:46:31 -08:00
Nayif
d31cd2fcdc
Merge pull request #386 from AdityasahuX07/patch-19
Adjust padding and margin values in LibraryScreen and CatalogSection
2026-01-09 23:17:48 +05:30
AdityasahuX07
b916bdbcca
Adjust item separator height in CatalogScreen 2026-01-09 20:19:48 +05:30
AdityasahuX07
67d53cf5ce
Adjust padding and margin values in LibraryScreen and CatalogSection
make the uniform gap between the posters to look symmetric.
2026-01-09 20:15:56 +05:30
tapframe
175d6a173e update doc 2026-01-09 18:27:19 +05:30
tapframe
b7140e15a5 added an AppState listener to the player 2026-01-09 17:49:39 +05:30
tapframe
76310dae1b updated AI model 2026-01-09 17:02:08 +05:30
tapframe
01a041aebf fix: fixed autoplay stream 2026-01-09 16:46:46 +05:30
tapframe
031c0c8772 added dev options to prod builds 2026-01-09 01:11:29 +05:30
tapframe
fd1e303403 Merge branch 'main' of https://github.com/tapframe/NuvioStreaming 2026-01-09 00:44:40 +05:30
tapframe
45b63cb33f fixed skip bacwards icon render cache issue 2026-01-09 00:44:32 +05:30
Nayif
c9dfecb68c
Merge pull request #381 from AdityasahuX07/patch-18
Move libraryBadge to its correct position.
2026-01-08 23:29:20 +05:30
tapframe
aa6406eae0 added dev docs 2026-01-08 21:38:48 +05:30
tapframe
26e4c6db88 updated docs 2026-01-08 21:37:01 +05:30
tapframe
2439bd1cd8 added deeplink support for plugin installation 2026-01-08 16:50:18 +05:30
tapframe
1fdcdd02bf updated tablet ui for plugin test screen 2026-01-08 16:15:15 +05:30
tapframe
bb94a49662 Fix: Critical Tablet screen crash fix upon app opening 2026-01-08 15:54:40 +05:30
tapframe
2ebec55bbc updated continue watching logic to render only last 30 watch progress 2026-01-08 15:03:59 +05:30
tapframe
5fe23c7ad1 fixed tmdb enrichment logic overrding addons meta while turned off. 2026-01-08 14:10:43 +05:30
tapframe
b6a5c108de All slide_from_right animations for Android have been replaced with 'default' 2026-01-08 13:09:44 +05:30
tapframe
83ce7cf44d fix: updated stremioservice to handle empty meta addon cases 2026-01-08 13:05:36 +05:30
tapframe
28632d192f updated plugin tester localization 2026-01-08 13:00:42 +05:30
tapframe
2a265bf716 update dev tools translation 2026-01-08 12:35:37 +05:30
AdityasahuX07
b06800860c
move libraryBadge to its correct position.
Add styles to libraryBadge in SearchResultItem.

Fixes issue #377
2026-01-08 12:30:55 +05:30
tapframe
75702d823f plugintest: added player testing support 2026-01-08 03:49:38 +05:30
tapframe
f865b737e6 adjusted plugintest screen layout for tablets 2026-01-08 03:44:39 +05:30
tapframe
2169354f0d refactored plugintest screen 2026-01-08 03:41:32 +05:30
tapframe
8dc1217c36 added repo testing 2026-01-08 03:16:37 +05:30
tapframe
0a1511f09f plugintest screen init 2026-01-08 02:41:43 +05:30
tapframe
73030f150a fix: android seekbar to show timestamp as we drag 2026-01-08 01:30:58 +05:30
tapframe
a1f4702647 reanimated warnings: Fixed by removing the direct .value read in
SkipIntroButton
2026-01-08 00:31:33 +05:30
tapframe
2ddfe63fa4 added polyfill to follow redirect "manual" 2026-01-08 00:27:26 +05:30
tapframe
79ffe92864 fixed searchscreen handlers after the refactor. 2026-01-07 22:29:21 +05:30
tapframe
e5178c9414 Added the missing malId and kitsuId props to
KSPlayerCore.tsx
2026-01-07 22:18:27 +05:30
Nayif
f779febc32
Merge pull request #367 from AdityasahuX07/patch-17
[Updated]Added double tap on search button to open keyboard for ready to search feature.
2026-01-07 22:13:23 +05:30
Nayif
5afd3d6b08
Merge pull request #358 from paregi12/feature/ani-skip
feat: implement AniSkip support in video player
2026-01-07 22:12:54 +05:30
tapframe
6005574019 added german and pt-Portugal in localisation 2026-01-07 18:13:37 +05:30
AdityasahuX07
645dcecaca
Merge branch 'main' into patch-17 2026-01-07 17:33:53 +05:30
AdityasahuX07
1686138499
Update AppNavigator.tsx 2026-01-07 17:32:52 +05:30
AdityasahuX07
cd1ed27f1e
Update print statement from 'Hello' to 'Goodbye' 2026-01-07 17:19:19 +05:30
AdityasahuX07
3b210b06d5
Update AppNavigator.tsx 2026-01-07 17:18:37 +05:30
tapframe
0f9c1b03a5 added trakt attribution 2026-01-07 16:38:04 +05:30
tapframe
217244c367 removing unattributed logos 2026-01-07 15:45:24 +05:30
tapframe
852868cf89 updated legalscreen with localization 2026-01-07 14:22:31 +05:30
tapframe
a52a2ccc31 update readme 2026-01-07 14:16:18 +05:30
tapframe
210ae6b0ee updated pluginscreen terminology 2026-01-07 14:01:02 +05:30
tapframe
c6e55429e4 Merge branch 'main' of https://github.com/tapframe/NuvioStreaming 2026-01-07 13:31:20 +05:30
tapframe
07b27dd485 fixed the issue where common.settings was displayed as a raw key 2026-01-07 13:31:05 +05:30
paregi12
5166dbd446
Merge branch 'tapframe:main' into feature/ani-skip 2026-01-07 13:18:43 +05:30
Nayif
0722923a78
Merge pull request #370 from Eazvy/main
fix escape key crashing on macOS
2026-01-07 12:50:16 +05:30
tapframe
a85698b009 added localization to themescreen. 2026-01-07 12:44:42 +05:30
tapframe
9b2b619121 added italian language to UI. 2026-01-07 12:37:22 +05:30
Nayif
ac097f6513
Merge pull request #371 from albyalex96/patch-1
Add Italian Localization
2026-01-07 12:32:14 +05:30
paregi12
a383289457
Merge branch 'tapframe:main' into feature/ani-skip 2026-01-07 07:47:50 +05:30
albyalex96
e76b44cff1
Created it locale 2026-01-07 01:03:24 +01:00
Eazvy
0f9f6bbe5d
fix accidental override of buffering 2026-01-06 18:23:01 -05:00
Eazvy
c48670fa74
fix escape key crashing on macOS
just adds an ignore listener so it doesn't crash, nor do anything
2026-01-06 18:13:04 -05:00
Nayif
c530619039
Merge pull request #368 from saimuelbr/main
Correcting forgotten parameter in json i18n
2026-01-07 01:06:57 +05:30
saimuelbr
5e221e7e97 minor fix 2026-01-06 16:03:52 -03:00
tapframe
65909a5f2e catalogscreen optimization for heavy render list 2026-01-07 00:23:03 +05:30
tapframe
bbdd4c0504 updated remaining contents for localization 2026-01-07 00:05:02 +05:30
tapframe
9924d26ff6 refactor search screen 2026-01-06 23:48:22 +05:30
tapframe
b10aab6057 release: 1.3.4 2026-01-06 19:44:54 +05:30
paregi12
ccad48fbb4
Merge branch 'tapframe:main' into feature/ani-skip 2026-01-06 18:29:04 +05:30
tapframe
91e9549ec6 type fix 2026-01-06 17:43:03 +05:30
AdityasahuX07
066bf6f15d
Enhance Search tab behavior with event emitter
Added DeviceEventEmitter to handle search input focus on tab press for Search tab.
2026-01-06 17:08:42 +05:30
AdityasahuX07
56df30a4da
Implement focus event listener for Search tab
Added a focus event listener to handle when the Search tab is pressed while already on the Search screen, focusing the input and clearing previous results.
2026-01-06 17:02:38 +05:30
tapframe
27ce25f5c5 added french 2026-01-06 16:11:54 +05:30
tapframe
334d0b1863 added arabic 2026-01-06 15:56:27 +05:30
tapframe
437645d5fd updated remaining files 2026-01-06 15:32:23 +05:30
tapframe
280536e93c updated remaining pages for localization. metascreen and player components 2026-01-06 15:26:13 +05:30
tapframe
611b37c847 added to remaining. metascreen 2026-01-06 14:57:08 +05:30
tapframe
5e3198c9c6 metascreen/streamscrean localization init 2026-01-06 14:46:11 +05:30
tapframe
6ef047db3c updated remaining main screens for localization 2026-01-06 14:04:16 +05:30
tapframe
cdab715463 updated tab navigator for localization 2026-01-06 13:18:31 +05:30
tapframe
96ac361c8e completed settingscreen localization 2026-01-06 13:15:07 +05:30
tapframe
ed4950cd1f updated sub pages 2026-01-06 12:07:37 +05:30
tapframe
afddf4bf2d updated settinsgcreen and it's sub-pages to support localization 2026-01-06 11:39:12 +05:30
tapframe
9c37ad8b94 multi-lang init 2026-01-06 11:34:05 +05:30
tapframe
9877f513e2 up next logic improvements 2026-01-06 10:07:58 +05:30
tapframe
f4b5082827 chore: updated continue watching card hold behaviour 2026-01-06 09:40:42 +05:30
tapframe
1627928fb2 added back up next 2026-01-06 09:17:56 +05:30
tapframe
6ff5aa9e02 updated react native video patch file 2026-01-06 00:25:18 +05:30
Nayif
20601cd7ba
Merge pull request #361 from chrisk325/patch-8
Several optimizations for exoplayer for preventing crashes with heavy file sizes
2026-01-06 00:17:12 +05:30
tapframe
2d6b4afa2d fix: added timeout for tabletstreamscreen to prevent blackscreen until backdrop is fetched 2026-01-06 00:12:00 +05:30
tapframe
4ce14ec4cc optimized perf 2026-01-06 00:00:33 +05:30
tapframe
0f1d736716 slight onboarding screen Ui change 2026-01-05 23:42:51 +05:30
tapframe
edeb6ebe3c feat: added new poster like layout for continue watching card 2026-01-05 17:54:17 +05:30
tapframe
ab7f008bbb added toggle to control this week sections 2026-01-05 13:39:02 +05:30
paregi12
1e60af1ffb feat: prioritize IntroDB and implement ARM API for faster MAL ID resolution 2026-01-05 00:33:33 +05:30
tapframe
4dd1fca0a7 increased cache buffer ksplayer 2026-01-05 00:02:03 +05:30
tapframe
81b97da75e chore: trakt update 2026-01-04 20:50:37 +05:30
paregi12
6a7d6a1458 feat: implement robust IMDb to MAL resolution for AniSkip support 2026-01-04 19:23:53 +05:30
tapframe
2835ede747 Changed Trakt Continue watch Sync Behaviour. now fetches directly from api when authenticated and doesn't merges to local storage. 2026-01-04 18:43:44 +05:30
chrisk325
59f77ac831
optimisations for exo 2026-01-04 16:17:16 +05:30
tapframe
3e63efc178 added parallel season fetching 2026-01-04 15:57:23 +05:30
tapframe
4aa22cc1c3 chore: improved tmdb enrichment logic 2026-01-04 15:37:49 +05:30
chrisk325
4fdda9a184
several exoplayer optimizations to prevent crashes with huge file sizes 2026-01-04 15:25:27 +05:30
chrisk325
5bd9f41104
decreasing player refresh time from 4 times per second to 2 times , to prevent crashes with heavy files 2026-01-04 14:36:50 +05:30
chrisk325
486ea63a8a
fixing exo crash and some UI flaws 2026-01-04 14:33:16 +05:30
paregi12
0919a40c75 fix: correct AniSkip API query parameters 2026-01-04 11:58:54 +05:30
paregi12
3de2fb4809 feat: implement AniSkip support in video player 2026-01-04 11:45:05 +05:30
Nayif
3d5a9ebf42
Merge pull request #355 from chrisk325/patch-7 2026-01-04 11:13:38 +05:30
chrisk325
be3e111e63
small fix 2026-01-04 05:44:56 +05:30
chrisk325
8a0bed7238
ironed out a ui flaw + fix 2026-01-04 05:17:40 +05:30
chrisk325
d2556b6c36
rework 2026-01-04 04:46:13 +05:30
chrisk325
506ca4f95c
rework the trakt sync logic 2026-01-04 03:49:06 +05:30
Nayif
5b2c57d5c7
Merge pull request #351 from tapframe/revert-347-feature/improved10secSkipAndRewind
Revert "patch: incremental 10 sec skip/rewind on multiple taps"
2026-01-04 00:11:43 +05:30
Nayif
7c2b1ac73d
Merge pull request #352 from tapframe/revert-345-feature/seekingTimestamp
Revert "add: current timestamp update while sliding the seek bar on Android"
2026-01-04 00:11:31 +05:30
Nayif
a55669d16f
Revert "add: current timestamp update while sliding the seek bar on Android" 2026-01-04 00:11:13 +05:30
Nayif
656062bc25
Revert "patch: incremental 10 sec skip/rewind on multiple taps" 2026-01-04 00:10:45 +05:30
Nayif
b42401a909
Merge pull request #350 from chrisk325/patch-6
Complete fix for trakt up next thanx to @oceanm8 on discord for the idea
2026-01-03 22:05:06 +05:30
chrisk325
2c6c110265
fix 2026-01-03 20:03:03 +05:30
chrisk325
e7b3458f34
small fix 2026-01-03 19:54:46 +05:30
chrisk325
e0ad949141
small fix 2026-01-03 19:47:53 +05:30
chrisk325
28d27128d1
fix local data overriding trakt progress 2026-01-03 19:11:35 +05:30
chrisk325
ebbe715581
small fix 2026-01-03 18:52:53 +05:30
chrisk325
af138944b5
fix trakt sync to local for upnext 2026-01-03 18:45:43 +05:30
chrisk325
4603d1dc2a
redo how trakt marks stuff as watched to local 2026-01-03 18:33:06 +05:30
chrisk325
e323906083
fix up next 2026-01-03 18:11:27 +05:30
Nayif
6cb115ed74
Merge pull request #348 from chrisk325/patch-5 2026-01-03 10:36:11 +05:30
chrisk325
0149068126
fix continue watching metadata 2026-01-03 02:25:15 +05:30
Nayif
7894258a26
Merge pull request #347 from saifshaikh1805/feature/improved10secSkipAndRewind 2026-01-03 01:54:46 +05:30
Nayif
775242255a
Merge pull request #345 from saifshaikh1805/feature/seekingTimestamp 2026-01-03 01:54:37 +05:30
chrisk325
faa4f341e6
fix up next yet again (final fix probably) 2026-01-03 01:50:43 +05:30
chrisk325
a079649563
fix trakt syncing watched shows/movies back to trakt's recent history 2026-01-03 01:23:40 +05:30
Saif Shaikh
63359532a3 patch: incremental 10 sec skip/rewind on multiple taps 2026-01-02 11:44:50 -08:00
Saif Shaikh
5d42a828d2 add: current timestamp update while sliding the seek bar on Android 2026-01-01 23:32:32 -08:00
Nayif
2da03d4931
Merge pull request #334 from chrisk325/patch-3
fixes trakt up next
2026-01-01 16:12:53 +05:30
chrisk325
4235e327fc
fixes trakt up next 2026-01-01 15:45:49 +05:30
tapframe
0d3454cd24 updated source.json 2026-01-01 04:05:23 +05:30
tapframe
5850650713 1.3.3 2026-01-01 03:49:32 +05:30
tapframe
47f3cb4b71 Merge branch 'main' of https://github.com/tapframe/NuvioStreaming 2026-01-01 03:32:38 +05:30
Nayif
2b802079a0
Merge pull request #331 from chrisk325/main
fixed tmdb enrichment logic , fixed trakt not syncing watch progress for old seasons
2026-01-01 03:31:40 +05:30
chrisk325
0d416f724c
small fix to backdrop fallback logic 2026-01-01 02:02:53 +05:30
tapframe
a6a0a8b1b1 removed sandboxed environment 2026-01-01 02:00:54 +05:30
chrisk325
dd1a3ed496
fix tmdb enrichment override for the logo 2026-01-01 01:58:30 +05:30
chrisk325
9f3831e733
fix for trakt only syncing the latest season disregarding the previous seasons 2026-01-01 01:16:18 +05:30
chrisk325
fd1107a5a3
fix backdrop override by tmdb enrichment 2026-01-01 00:09:33 +05:30
chrisk325
a9a78d5565
fix tmdb enrichment overriding metadata even when turned off 2025-12-31 23:48:56 +05:30
Nayif
a794e27235
Merge pull request #328 from AdityasahuX07/patch-15
Minor UI changes and Bug fix.
2025-12-31 20:03:59 +05:30
Nayif
9bb9d6548a
Merge pull request #327 from chrisk325/main
this should fix addon provided metadata for "continue watching" and "trakt this week"
2025-12-31 20:02:54 +05:30
Nayif
3625ca9edc
Merge pull request #330 from tapframe/plugin
multi plugin support
2025-12-31 20:02:28 +05:30
tapframe
3293b57537 multi plugin support 2025-12-31 19:50:08 +05:30
AdityasahuX07
867458b52f
Change controls hide delay from 3s to 2s
Reduce the delay for hiding controls from 3 seconds to 2 seconds.
2025-12-31 18:59:16 +05:30
AdityasahuX07
8daca53be3
Implement auto-hide for video player controls
Added auto-hide functionality for video player controls after 3 seconds of inactivity, with cleanup on unmount.
2025-12-31 18:56:32 +05:30
AdityasahuX07
4174fd2add
Adjust padding and gap in playerStyles 2025-12-31 18:55:44 +05:30
AdityasahuX07
d3041f99cc
Refactor SpeedActivatedOverlay component props and styles 2025-12-31 18:48:10 +05:30
chrisk325
6acfa2971b
fallback to metadata banner if tmdb enrichment banner is disabled by the user 2025-12-31 17:29:23 +05:30
chrisk325
7271ed39a0
fix for trakt this week metadata 2025-12-31 17:14:02 +05:30
chrisk325
639e84bb88
push addonId for trakt up next 2025-12-31 16:58:24 +05:30
chrisk325
36ad45cfbc
push addonId 2025-12-31 16:52:55 +05:30
chrisk325
c0540db282
preserving stream providers's id 2025-12-31 16:47:15 +05:30
chrisk325
7d6008b0a9
adding addonId to player's saved progress 2025-12-31 16:44:07 +05:30
tapframe
af96d30122 ui changes 2025-12-31 03:26:25 +05:30
tapframe
bf75cca438 refactored settingscreen 2025-12-31 03:15:37 +05:30
tapframe
3285ecbe04 Updated Subtitle Sync Modal 2025-12-31 02:41:13 +05:30
tapframe
6906ad99b7 updated to gorhom bottom sheet 2025-12-31 01:15:04 +05:30
tapframe
f3c5289013 icons update 2025-12-31 01:06:40 +05:30
tapframe
be9473adf7 added subtitle/audio track selection menu 2025-12-31 01:05:09 +05:30
Nayif
ec28f73df9
Merge pull request #324 from chrisk325/patch-1
small fix
2025-12-31 00:37:25 +05:30
chrisk325
d19f4713a2
syntax 2025-12-30 20:38:55 +05:30
chrisk325
2e79c34068
small fix 2025-12-30 19:49:19 +05:30
tapframe
c7e5696974 included limpv aar 2025-12-30 19:45:36 +05:30
tapframe
154d034e8f fixed type errors and syntax errors from merged PR. 2025-12-30 18:55:03 +05:30
tapframe
916eeaef4c Merge branch 'main' of https://github.com/tapframe/NuvioStreaming 2025-12-30 17:56:19 +05:30
Nayif
ad18e30de7
Merge pull request #321 from chrisk325/main 2025-12-30 17:46:50 +05:30
chrisk325
d4917fefc9
fetch addon provided logo first if it errors then fallback to tmdb 2025-12-30 17:20:33 +05:30
chrisk325
67b16c27f3
preserve addonId when creating obj thisweekepisode 2025-12-30 16:05:18 +05:30
chrisk325
f15fe80d3a
addon provided metadata for trakt this week 2025-12-30 15:52:30 +05:30
chrisk325
fbb44b14dd
addon provided metadata for appletv style carousel 2025-12-30 15:49:21 +05:30
chrisk325
103bcdd4cc
save addonId in watch progress 2025-12-30 15:34:11 +05:30
chrisk325
5e04ebca18
preserving addonId 2025-12-30 15:22:31 +05:30
chrisk325
b00812333a
added addonId to continue watching 2025-12-30 15:18:16 +05:30
chrisk325
9012bfdea9
addonId for hero section type carousel 2025-12-30 15:07:55 +05:30
chrisk325
9e5877173e
adding addonId for hero and continue watching 2025-12-30 15:02:01 +05:30
tapframe
b165c3223d fixed the implicit any type errors AppNavigator.tsx 2025-12-30 13:18:13 +05:30
tapframe
79213ad573 Merge branch 'main' of https://github.com/tapframe/NuvioStreaming 2025-12-30 13:11:50 +05:30
Nayif
7df42903c6
Merge pull request #319 from chrisk325/main 2025-12-30 11:17:48 +05:30
chrisk325
42d4290acd
modify onpress handler to pass addonId 2025-12-30 04:19:19 +05:30
chrisk325
8c449215a6
attached addonId to search and discover 2025-12-30 04:12:32 +05:30
tapframe
183d30c720 removed config cache 2025-12-30 03:59:24 +05:30
tapframe
53a572ecac update env example 2025-12-30 03:10:17 +05:30
tapframe
4173786b12 more sdui control over settinsgcreen. 2025-12-30 02:49:10 +05:30
tapframe
44abb9f635 added video support to sdui server 2025-12-30 02:15:43 +05:30
tapframe
fd6e29a8ec Added granular control for TMDB Enrichment 2025-12-29 19:48:26 +05:30
tapframe
832e5368be settingscreen refactor 2025-12-29 15:05:50 +05:30
tapframe
e543d72879 ota server change 2025-12-29 13:44:44 +05:30
tapframe
b4b8648e25 embed stream fetch critical bug fix 2025-12-29 01:03:06 +05:30
tapframe
ff2bca18a5 discover screen optimization 2025-12-28 23:57:31 +05:30
tapframe
cf5cc2d8f9 discover screen init 2025-12-28 22:22:18 +05:30
tapframe
a30fa604d7 discover screen init 2025-12-28 22:10:40 +05:30
tapframe
18e90397d9 black spot fix during playback ksplayer 2025-12-28 19:35:01 +05:30
tapframe
97f558faf4 update discord url 2025-12-28 14:54:45 +05:30
tapframe
69dacb0ede update readme 2025-12-28 14:50:14 +05:30
tapframe
95e7d44035 addes scrolltotop by clicking tab navigation buttons 2025-12-28 13:29:33 +05:30
tapframe
d39a485d24 fixed watchprogress not resuming in exoplayer 2025-12-28 13:10:25 +05:30
tapframe
4f0a673f87 moved submodules to gitignore 2025-12-28 12:54:11 +05:30
tapframe
8618dcda74 rm cached submodules 2025-12-28 12:53:52 +05:30
tapframe
cc8be32cac updated index.html 2025-12-28 12:50:10 +05:30
tapframe
f65eb8fe7e removed submodules 2025-12-28 12:05:46 +05:30
tapframe
7c1a69d136 added ipa, updated nuvio-source.json 2025-12-28 12:02:24 +05:30
tapframe
7fdd4c4383 converted from sectionlist to flatflist (streamscreen) 2025-12-28 03:41:33 +05:30
tapframe
43cd14a025 chore(release): 1.3.2 2025-12-28 03:29:36 +05:30
tapframe
5662ee908d moved introdb base url to env 2025-12-28 03:13:17 +05:30
tapframe
de7fcb4d4d streamsceen scrollview changed to sectionlist 2025-12-28 02:59:26 +05:30
tapframe
f6dea03c05 users can now toggle between - auto/mpv exclusively 2025-12-28 02:21:01 +05:30
tapframe
6e2ddd2dda Added ExoPlayer as primary for better hardwre decoder support and MPV as fallback 2025-12-28 02:14:39 +05:30
tapframe
2d97cad1dc skip intro ui changes 2025-12-27 23:04:10 +05:30
tapframe
1d9a3b645b Added SkipIntro button 2025-12-27 22:53:05 +05:30
tapframe
a89c7f5c5c sub changes 2025-12-27 22:29:27 +05:30
tapframe
91af3a4021 improved stream fetching logic 2025-12-27 21:13:11 +05:30
tapframe
7a5ecd3009 addon first result in streamscreen 2025-12-27 19:15:10 +05:30
tapframe
aed4fed56f refactor streamscreen 2025-12-27 19:12:25 +05:30
tapframe
7885df341e changes 2025-12-27 18:42:41 +05:30
tapframe
2921b3eb1f added internal sub customization - mpv 2025-12-27 18:37:03 +05:30
tapframe
579b0a77b3 added hwdec options to choose from 2025-12-27 17:50:09 +05:30
tapframe
063f8a8c1b macos fullscreen fix 2025-12-26 14:22:13 +05:30
tapframe
985d01d5a9 ksplayer sub rendering fix 2025-12-26 12:32:33 +05:30
tapframe
9f461f7091 ksplayer sub rendering fix 2025-12-26 12:32:19 +05:30
tapframe
0b4db84f30 changed mpv hw to try "mediacode" first 2025-12-25 20:31:14 +05:30
tapframe
7e7804b6d4 improved parental guide UI 2025-12-25 14:22:12 +05:30
tapframe
eee6f81fca parental guide overlay init 2025-12-25 14:09:09 +05:30
tapframe
9375fab06c Changed to TraktService.getInstance().isAuthenticated() 2025-12-25 13:03:04 +05:30
Nayif
d2987ce0cc
Merge pull request #292 from AdityasahuX07/patch-14
Video player Ui changes
2025-12-25 12:46:25 +05:30
tapframe
a61c1e6456 Fix: Player orientation now correctly resets when exiting video player 2025-12-25 12:35:07 +05:30
tapframe
0a1e008d5f update readme 2025-12-24 23:22:37 +05:30
tapframe
7f9e9ff5db orientation fix after exiting player ios 2025-12-24 21:46:46 +05:30
tapframe
39498f78b7 removed dead code 2025-12-24 21:38:02 +05:30
tapframe
8588aca948 removed orphaned modules 2025-12-24 20:05:17 +05:30
Nayif
3f63461d45
Merge pull request #297 from tapframe/mpv
update nuvio-source.json
2025-12-24 20:03:10 +05:30
tapframe
f5e9a3977b update nuvio-source.json 2025-12-24 20:01:50 +05:30
Nayif
aa62cc78f0
Merge pull request #296 from tapframe/mpv
Mpv
2025-12-24 19:55:52 +05:30
tapframe
d6bb2869c5 removed orphaned submodules 2025-12-24 19:54:18 +05:30
AdityasahuX07
74764bbbe0
Merge branch 'main' into patch-14 2025-12-24 19:40:07 +05:30
Nayif
441e8d8656
Merge pull request #295 from tapframe/mpv
Mpv
2025-12-24 19:33:47 +05:30
tapframe
52dd075b6a bumped app version 2025-12-24 19:23:53 +05:30
tapframe
1821bf1230 changes 2025-12-24 19:22:00 +05:30
tapframe
ab720ddae7 responsiveness for sdui modal 2025-12-24 19:21:53 +05:30
tapframe
b4cecee191 .. 2025-12-24 19:05:57 +05:30
tapframe
614597d1bd SDUI fix 2025-12-24 19:05:14 +05:30
tapframe
0165b1f987 SDUI prod url init 2025-12-24 18:56:11 +05:30
tapframe
8b3a1b57bf SDUI modal init 2025-12-24 18:28:39 +05:30
tapframe
c421e46724 changes 2025-12-23 22:32:19 +05:30
tapframe
1f3b9413cd added hw/sw toggle for android 2025-12-23 21:55:46 +05:30
tapframe
6855a89792 fixed header issues 2025-12-23 21:31:10 +05:30
tapframe
811701ebae config added mpv files after prebuild 2025-12-23 17:48:52 +05:30
tapframe
18fa11fd88 added header support 2025-12-23 17:42:45 +05:30
tapframe
767fd2ff87 removed vlc lib 2025-12-23 16:42:41 +05:30
AdityasahuX07
68c5b09e3a
Change primary color to a lighter shade 2025-12-23 16:26:12 +05:30
AdityasahuX07
d2f9b7586a
Refactor SourcesModal component styling and layout
Refactor QualityBadge component for improved styling and readability. Update source selection UI for better user experience.
2025-12-23 15:24:02 +05:30
AdityasahuX07
4753b2a57a
Update EpisodeStreamsModal.tsx 2025-12-23 15:22:01 +05:30
AdityasahuX07
5119822c31
Refactor episode progress fetching and UI adjustments 2025-12-23 15:21:27 +05:30
AdityasahuX07
09d0483ee3
Update print statement from 'Hello' to 'Goodbye' 2025-12-23 15:20:23 +05:30
AdityasahuX07
034fd8a9aa
Refactor AudioTrackModal for better layout and animations
Updated the AudioTrackModal component to improve layout and animations.
2025-12-23 15:18:45 +05:30
AdityasahuX07
b3ec4e0c01
Video player Ui changes 2025-12-23 15:17:18 +05:30
tapframe
f0f71afd67 fixed custom subtitle rendering android 2025-12-23 15:16:26 +05:30
tapframe
3cea291901 fixed subtitle rendering, added aspect ratio support 2025-12-23 14:25:52 +05:30
tapframe
9504d48607 seek fix 2025-12-22 21:40:11 +05:30
tapframe
19438ff1d5 mpv init 2025-12-22 21:26:22 +05:30
tapframe
967b90b98e Error modal simplified for videoplayers 2025-12-22 16:06:52 +05:30
tapframe
0d6d69e0a8 refactor ksplayer 2025-12-22 13:58:16 +05:30
tapframe
a50f8de913 reafactor android videoplayer 2025-12-22 11:35:25 +05:30
tapframe
32df7d79ad Contributors update 2025-12-22 10:42:25 +05:30
tapframe
759215da8c onboarding screen UI update 2025-12-22 01:54:36 +05:30
tapframe
894469ae0e added torrentio configuration to debrid integration page 2025-12-22 01:15:01 +05:30
tapframe
79b0cfc990 consistent theming across player modals 2025-12-20 20:59:19 +05:30
tapframe
a8dfe30546 Merge branch 'main' of https://github.com/tapframe/NuvioStreaming 2025-12-20 20:49:24 +05:30
tapframe
dda34b6982 Trakt scrobble big fix 2025-12-20 20:49:20 +05:30
Nayif
599e31c11f
Merge pull request #289 from tapframe/revert-283-patch-9
Revert "Update Ui of Subtitle selection tab made modern popup window"
2025-12-20 20:45:11 +05:30
Nayif
ffc4200b96
Revert "Update Ui of Subtitle selection tab made modern popup window" 2025-12-20 20:44:14 +05:30
Nayif
d23c48cc0c
Merge pull request #283 from AdityasahuX07/patch-9
Update Ui of Subtitle selection tab made modern popup window
2025-12-20 20:42:42 +05:30
Nayif
aaf0b498f8
Merge pull request #284 from AdityasahuX07/patch-10
Ui change of Audio track menu
2025-12-20 20:41:57 +05:30
Nayif
bda3732a83
Merge pull request #285 from AdityasahuX07/patch-11
Episode Modal Ui enhancement
2025-12-20 20:41:20 +05:30
tapframe
b2a9708856 Merge branch 'main' of https://github.com/tapframe/NuvioStreaming 2025-12-20 19:37:05 +05:30
tapframe
6e3f79a231 updated contribution page to show special mentions 2025-12-20 19:36:54 +05:30
Nayif
395b01d22b
Merge pull request #268 from AdityasahuX07/patch-5
It makes Titles name hide and show for Library posters via toggle in setting.
2025-12-20 13:14:40 +05:30
tapframe
d9aaa045fd webhook test 2025-12-20 12:41:37 +05:30
AdityasahuX07
d6f2cb7592
Adjust audio track modal maximum height
Increased the maximum height of the audio track modal to better utilize screen space.
2025-12-20 11:02:48 +05:30
tapframe
5804959ddf alert orientation fix 2025-12-19 23:48:26 +05:30
AdityasahuX07
af572f8b29
Refactor renderItem for cleaner syntax 2025-12-19 23:44:17 +05:30
AdityasahuX07
104d0f4516
Merge branch 'main' into patch-5 2025-12-19 23:39:40 +05:30
AdityasahuX07
b061c1f756
Episode Modal Ui enhancement 2025-12-19 23:28:35 +05:30
AdityasahuX07
89f99dba85
Ui change of Audio track menu 2025-12-19 23:27:04 +05:30
AdityasahuX07
ea25526ded
Update Ui of Subtitle selection tab made modern popup window 2025-12-19 19:45:46 +05:30
tapframe
2d5b1263b5 Merge branch 'main' of https://github.com/tapframe/NuvioStreaming 2025-12-18 00:15:43 +05:30
Nayif
371aacd734
Merge pull request #279 from AdityasahuX07/patch-6
Modern UI for dynamic volume and brightness overlays and fix brightness control does not work on Android
2025-12-18 00:15:26 +05:30
tapframe
af1b0b03d8 Forces reset of the fetch guard when lastUpdate changes 2025-12-18 00:14:49 +05:30
tapframe
baee619d73 herocarousal improvements 2025-12-18 00:10:06 +05:30
tapframe
b3f5ba4260 continue watching crash fix 2025-12-17 23:27:26 +05:30
tapframe
7ec9c3591e brightness android perm fix 2025-12-17 23:21:50 +05:30
tapframe
407514301b trakt sync now bug fox 2025-12-17 21:53:21 +05:30
AdityasahuX07
35abf985a9
Modern UI for dynamic volume and brightness overlays and fix brightness control not work on Android
Added dynamic volume and brightness icons with overlays for user feedback. Updated gesture handling for improved interaction.
2025-12-17 13:27:07 +05:30
tapframe
374bc8e2d3 TS error fix 2025-12-17 00:12:43 +05:30
tapframe
48300bf767 trakt scrobble improvements 2025-12-16 16:00:23 +05:30
tapframe
78553d8323 toggle for tuning off update alerts 2025-12-16 15:47:56 +05:30
tapframe
8c0b47975c toggle for enalbling/disablingposter label in catalogscreen 2025-12-16 15:38:51 +05:30
tapframe
601a4a0f1d landscape poster support 2025-12-16 15:38:29 +05:30
tapframe
59cb902658 revamped alert UI 2025-12-16 15:24:32 +05:30
tapframe
d876b7618c added episode/season mark as watched feature syncing locally/trakt. 2025-12-16 15:17:56 +05:30
tapframe
60cdf9fe86 added filter for catalogs 2025-12-15 15:37:04 +05:30
tapframe
619333c328 1.2.11 26 2025-12-15 03:11:26 +05:30
tapframe
80d75a528f improved catalogfetching logic 2025-12-15 02:10:23 +05:30
tapframe
4ac45a041a imrpoved metadataloadingscreen UI 2025-12-15 02:05:52 +05:30
tapframe
51064a65b2 metascreen abruption fix 2025-12-15 01:55:32 +05:30
tapframe
2e61617f83 render loop fix usewatchprogress 2025-12-15 01:46:31 +05:30
tapframe
dbbee06a55 bug fixes android and ios 2025-12-15 01:36:10 +05:30
tapframe
181cdaecb5 dependancy update 2025-12-15 00:49:15 +05:30
tapframe
c3fbe31fd4 dependency update 2025-12-15 00:49:00 +05:30
tapframe
f05366ae45 some sentry fixes maximum update limti reached 2025-12-14 23:50:00 +05:30
AdityasahuX07
1c53e65b26
Refactor LibraryScreen to include showTitles prop 2025-12-14 15:31:10 +05:30
tapframe
c01528b309 embedded streams fix 2025-12-14 14:10:15 +05:30
AdityasahuX07
53dd480231
Merge branch 'main' into patch-5 2025-12-14 13:47:20 +05:30
tapframe
3c35b99759 continue watching improvements. testflight link added to readme 2025-12-13 21:09:09 +05:30
tapframe
8a34bf6678 update privacy policy 2025-12-12 14:12:01 +05:30
tapframe
8b5a707daa fixed a continue watching bug where removed content won't reappearing even after watching it again 2025-12-11 18:56:51 +05:30
tapframe
bf22e559c5 header padding fix 2025-12-11 16:10:59 +05:30
tapframe
9e7543df02 header refactor 2025-12-11 15:55:52 +05:30
Nayif
52065a1462
Merge pull request #270 from AdityasahuX07/patch-7
Enhance AppleTVHero component functionality by Adding Play and Save to library Button.
2025-12-11 15:02:49 +05:30
tapframe
01953af578 added external player selection for downloads 2025-12-11 15:00:15 +05:30
tapframe
e160bf6fe0 settingscreen UI changes 2025-12-11 14:50:04 +05:30
AdityasahuX07
ff9d2c52be
Resume now working.
Integrate watch progress for playback management
2025-12-11 14:47:53 +05:30
tapframe
3801e80dd9 streamscreen to player refactor 2025-12-11 14:42:05 +05:30
AdityasahuX07
1307a71b4c
Enhance AppleTVHero component functionality by Adding Play and Save to library Button.
Added play and save button.
On tapping logo of media opens detail page added (for Info button)

Fixes issue #244
2025-12-10 22:47:34 +05:30
AdityasahuX07
2c9072299e
Add Show/Hide Titles for Library.
The Show/Hide Titles for Library because when the name of movie/show is large then it create uneven gap between library which not look good.

And also If anyone want can toggle keep on for show titles in settings.
2025-12-09 23:17:19 +05:30
tapframe
6bdc998496 custom alert orientation bug fix 2025-12-09 14:50:00 +05:30
tapframe
1b990aa6ec metascreen optimization 2025-12-09 14:32:33 +05:30
tapframe
057c709b41 trakt comment fix 2025-12-09 14:24:49 +05:30
tapframe
d457db5053 featured content fix 2025-12-09 12:57:14 +05:30
Nayif
22d8fe311a
Merge pull request #259 from AdityasahuX07/patch-3
Change libraryBadge position to its correct location only in search tab.
2025-12-05 21:38:25 +05:30
Nayif
e9796ee966
Merge pull request #260 from AdityasahuX07/patch-4
Fix for filter tabs in Library UI element cropped off in mobile devices.
2025-12-05 21:38:13 +05:30
AdityasahuX07
6c201e285a
Fix for filter tabs in Library UI element cropped off in mobile devices.
Fixes #135
2025-12-03 15:41:23 +05:30
AdityasahuX07
6c326e1378
Change libraryBadge position to its correct location.
The bug batter explained in #172

Fixes issue #172
2025-12-03 13:47:47 +05:30
tapframe
725c8aa9b7 buffer change ksplayer 2025-12-03 01:22:44 +05:30
Nayif
7a2f340c22
Merge pull request #257 from AdityasahuX07/patch-1
ShowRatingScreen page singular Season logic and heading chopped off fix.
2025-12-01 23:09:01 +05:30
AdityasahuX07
c4af2e8eea
ShowRatingScreen page singular Season logic and heading chopped off fix.
logic for 1 Seasons to 1 Season modified.
Heading for grid chopped off because Episodes is too long word changes to EPs for batter UI experience.

Fixes #159
2025-12-01 20:45:00 +05:30
tapframe
63a7051b86 Merge branch 'main' of https://github.com/tapframe/NuvioStreaming 2025-11-26 23:48:24 +05:30
tapframe
9f75bfdeed navbar changes android 2025-11-26 23:48:05 +05:30
Nayif
083da01463
Merge pull request #242 from aayushrautela/android-nav-bar-fix
Fix nav bar being always visible on android
2025-11-26 23:27:44 +05:30
Nayif
14980f2bfd
Merge branch 'main' into android-nav-bar-fix 2025-11-26 23:26:15 +05:30
tapframe
6c08b459bf ui changes 2025-11-26 01:09:30 +05:30
tapframe
6d1ba14ab4 debrid integration. Torbox 2025-11-26 01:01:00 +05:30
tapframe
bbf035ebae changes 2025-11-26 00:15:32 +05:30
tapframe
03aa45a0b0 1.2.10 25 2025-11-25 02:31:48 +05:30
tapframe
8d918cdf5e downloads notif fix 2025-11-25 02:10:29 +05:30
tapframe
2494d45e8f slight Ui changes this week section 2025-11-25 02:05:11 +05:30
tapframe
011f480fc1 simplified thisweeeksection cards 2025-11-25 01:57:50 +05:30
tapframe
771765f32b apple hero drag changes 2025-11-25 01:48:50 +05:30
tapframe
e9a331dbd5 ksplayer pause fix 2025-11-25 01:44:34 +05:30
tapframe
348cbf86d8 android player wordsplitting fix 2025-11-25 01:17:23 +05:30
tapframe
ecaaaa66ed ksplayer word splitting fix 2025-11-25 01:15:41 +05:30
tapframe
0ab85ec870 thisweekimprovements 2025-11-25 00:56:19 +05:30
tapframe
a27ee4ac56 trakt test 2025-11-25 00:50:47 +05:30
tapframe
56234daf82 gradle fix 2025-11-24 23:25:03 +05:30
tapframe
668099b542 hdr issue fix ios 2025-11-21 16:09:54 +05:30
Aayush Rautela
a4725c24bc Add immersive mode support to AndroidVideoPlayer component
(cherry picked from commit b6eaf25e8905bdf66d4396a78bf6485a46f3162a)
2025-11-16 22:22:48 +01:00
tapframe
1a1fdb6fdf 1.2.9 24 2025-11-16 22:04:03 +05:30
tapframe
0bb0df2a60 flashlist to flatlist 2025-11-16 21:06:35 +05:30
tapframe
5163031869 changes 2025-11-16 19:42:27 +05:30
tapframe
13523fbbe4 trakt episode marking bug fix 2025-11-16 17:55:38 +05:30
tapframe
e9b38db8b4 catalog capping fix 2025-11-16 17:26:37 +05:30
tapframe
eeed1c7492 adjust z-index 2025-11-16 17:11:43 +05:30
tapframe
655ddbeb42 changes 2025-11-16 16:53:54 +05:30
tapframe
69ac2d64ad bug fix 2025-11-16 16:07:56 +05:30
tapframe
34f110f16a player UI update 2025-11-16 15:46:46 +05:30
tapframe
09e35d5a0c plugin fix 2025-11-10 23:11:42 +05:30
tapframe
41081118ef fix some type errors 2025-11-10 17:32:13 +05:30
tapframe
1ca4e275de apple hero changes 2025-11-10 15:23:55 +05:30
tapframe
c9c4a80387 1.2.8 23 2025-11-09 21:41:26 +05:30
tapframe
3d0ac0f9f4 1.2.8 23 2025-11-09 21:41:09 +05:30
tapframe
ffaac958c4 fixes 2025-11-09 20:18:35 +05:30
tapframe
f2f503b9ab icon minor change 2025-11-09 19:17:37 +05:30
tapframe
117306bd66 android player fabric detatch fix 2025-11-09 19:02:27 +05:30
tapframe
76b28d2a2c trailer section ui changes 2025-11-09 19:02:13 +05:30
tapframe
41ba4ac12c downloads improvements 2025-11-09 14:09:48 +05:30
tapframe
54f85e9689 trailer section card ui changes 2025-11-09 13:11:40 +05:30
tapframe
aabf3e18ef hero height change 2025-11-09 11:55:14 +05:30
tapframe
daafdeedc2 ui changes 2025-11-08 15:56:22 +05:30
tapframe
426e936740 parallax added 2025-11-08 14:14:33 +05:30
tapframe
aa0c338c05 trailer bug fixes 2025-11-08 13:03:20 +05:30
tapframe
ea7f6bf7d7 hero changes 2025-11-08 11:54:43 +05:30
tapframe
2c524020af changes 2025-11-08 03:05:09 +05:30
tapframe
6331c43f68 updates for trailer 2025-11-08 02:58:01 +05:30
tapframe
a6168a7d64 ui changes 2025-11-08 02:29:04 +05:30
tapframe
1c083f836b changes 2025-11-08 02:07:31 +05:30
tapframe
1756c28ed9 ui changes 2025-11-08 01:27:09 +05:30
tapframe
91a42e6e29 ui fix 2025-11-05 20:08:12 +05:30
tapframe
0e14d257ad carousal now active for tablets 2025-11-05 19:40:48 +05:30
tapframe
ba72d8bca2 player optimization 2025-11-05 18:48:38 +05:30
tapframe
19420e901e revert legacy ui 2025-11-05 14:48:21 +05:30
tapframe
7aa66aff74 improved hero 2025-11-05 14:10:58 +05:30
tapframe
5c3c5717ab changes 2025-11-05 14:00:34 +05:30
tapframe
a5a66a5e8c changes 2025-11-05 13:45:23 +05:30
tapframe
b17b492741 LEGACY hero carousal init 2025-11-05 13:33:12 +05:30
tapframe
5fe1db24c1 sub bg fix 2025-11-04 20:47:03 +05:30
tapframe
2ba7c3c057 catalog parallel fetching and subtitle backgound fix 2025-11-03 22:33:33 +05:30
tapframe
8e34fbb124 crash fix 2025-11-03 15:36:26 +05:30
tapframe
d971d9f71f UI changes 2025-11-02 22:30:15 +05:30
tapframe
e033352752 new tmdb ratings api 2025-11-02 22:11:39 +05:30
tapframe
96f79f7c72 ksplayer optimizations 2025-11-02 18:33:05 +05:30
tapframe
80cdc98902 svg rendering fix 2025-11-02 16:57:21 +05:30
tapframe
d6af98c6d7 orientation lock removed 2025-11-02 16:47:18 +05:30
tapframe
5a3ebe6c01 ui changes 2025-10-30 23:28:24 +05:30
tapframe
84b8cb7817 upnext button UI changes 2025-10-30 23:26:41 +05:30
tapframe
33d13c74d3 playback control optimization 2025-10-30 18:33:57 +05:30
tapframe
6fa53151fb update tmdb metadata handling 2025-10-30 11:44:53 +05:30
tapframe
4261891a35 sub alignment changes 2025-10-28 01:20:06 +05:30
tapframe
a0a138081d users can now choose between what to backup 2025-10-28 01:02:33 +05:30
tapframe
f13266b1fc ui fix 2025-10-28 00:26:40 +05:30
tapframe
1287d7f6a0 loading overlay re renderfix 2025-10-28 00:12:40 +05:30
tapframe
e437a23029 improved hero banner and logo fallback 2025-10-27 20:18:33 +05:30
tapframe
b71314b8f6 subtitle improvements 2025-10-27 19:35:37 +05:30
tapframe
d9fcc085a6 moviecollection fix 2025-10-27 14:05:13 +05:30
tapframe
dd542091e1 ui fix 2025-10-26 21:06:08 +05:30
tapframe
85cb950fa8 episode modal init 2025-10-26 21:02:39 +05:30
tapframe
09e25738a8 bottomnav behaviour fix 2025-10-26 20:25:06 +05:30
tapframe
9452b01e9c improved source modal behaviour 2025-10-26 20:10:17 +05:30
tapframe
f24d889ee7 update source json 2025-10-26 14:44:43 +05:30
tapframe
d8bf8dd2ae 1.2.7 22 2025-10-26 14:12:25 +05:30
338 changed files with 61120 additions and 30158 deletions

View file

@ -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
View file

@ -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
View file

@ -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>
);
}

View file

@ -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"> [![Join TestFlight](https://img.shields.io/badge/Join-TestFlight-blue?style=for-the-badge)](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"> [![Add to AltStore](https://img.shields.io/badge/Add%20to-AltStore-blue?style=for-the-badge)](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 |
|:----:|:-------:|
| ![Home](screenshots/Simulator%20Screenshot%20-%20iPhone%2016%20Pro%20-%202025-08-27%20at%2021.08.32-portrait.png) | ![Details](screenshots/WhatsApp%20Image%202025-09-02%20at%2000.24.31-portrait.png) |
<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 -->

View file

@ -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
}

Binary file not shown.

View 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"/>

View file

@ -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)
}
/**

View file

@ -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"

View 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()
}
}
}
}
}

View 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))
}
}

View file

@ -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")
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

View file

@ -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>

View file

@ -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")
}
}

View file

@ -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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

View file

@ -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"
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 76 KiB

View file

@ -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>

View file

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 106 KiB

View file

@ -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>

View file

@ -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"
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 B

After

Width:  |  Height:  |  Size: 760 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

After

Width:  |  Height:  |  Size: 398 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 583 KiB

After

Width:  |  Height:  |  Size: 840 KiB

BIN
assets/nuviotext.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 206 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -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
View 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

File diff suppressed because it is too large Load diff

View file

@ -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

View file

@ -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")
}
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -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)")
}
}
}
}

View file

@ -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";

View file

@ -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()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 237 KiB

After

Width:  |  Height:  |  Size: 166 KiB

View file

@ -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>

View file

@ -20,6 +20,7 @@
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
<string>C56D.1</string>
</array>
</dict>
<dict>

View file

@ -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"/>

View file

@ -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>

View file

@ -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'

View file

@ -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

View file

@ -1,5 +1,6 @@
{
"expo.jsEngine": "hermes",
"EX_DEV_CLIENT_NETWORK_INSPECTOR": "true",
"newArchEnabled": "true"
}
"newArchEnabled": "true",
"ios.deploymentTarget": "16.0"
}

View file

@ -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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

View 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
}
}
}

View 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)
}
}
}

View file

@ -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;
}

View file

@ -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 Weeks 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

File diff suppressed because it is too large Load diff

View file

@ -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

Some files were not shown because too many files have changed in this diff Show more