Compare commits
288 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4daab74e27 | ||
|
|
a7fbd567fd | ||
|
|
f90752bdb7 | ||
|
|
c5590639b1 | ||
|
|
098ab73ba1 | ||
|
|
060b0b927b | ||
|
|
786e06b27f | ||
|
|
ef1c34a9c0 | ||
|
|
b97481f2d9 | ||
|
|
8d74b7e7ce | ||
|
|
635c97b1ad | ||
|
|
673c96c917 | ||
|
|
15fc49d84d | ||
|
|
54cfd194f1 | ||
|
|
be561c6d9f | ||
|
|
dc8c27dfc4 | ||
|
|
ce7f92b540 | ||
|
|
f0271cd395 | ||
|
|
2a4c076854 | ||
|
|
c852c56231 | ||
|
|
614ffc12c0 | ||
|
|
d9b2545cdd | ||
|
|
1ae6b4f108 | ||
|
|
373efa0564 | ||
|
|
6c464abdd4 | ||
|
|
5d5d77ae1b | ||
|
|
b2cfc19e96 | ||
|
|
e40e8bb7c5 | ||
|
|
a3158be2bd | ||
|
|
e305dee777 | ||
|
|
415efd4e03 | ||
|
|
f027788266 | ||
|
|
23acda3167 | ||
|
|
a8b4dc5a01 | ||
|
|
fd4efe6c7f | ||
|
|
68340eac9e | ||
|
|
175d47f71f | ||
|
|
0b764412b2 | ||
|
|
f7c0c670d7 | ||
|
|
18bd6ff3ca | ||
|
|
ac5326ba3f | ||
|
|
c5af56537b | ||
|
|
17cdd503e9 | ||
|
|
688950d0c2 | ||
|
|
eb3082cddb | ||
|
|
32bec08f30 | ||
|
|
a7f850d577 | ||
|
|
08f356cfa4 | ||
|
|
6e975ffe26 | ||
|
|
64981dd110 | ||
|
|
b5156bcc69 | ||
|
|
9ab99a1225 | ||
|
|
d5edec025c | ||
|
|
ef43463b99 | ||
|
|
ca2e95e6f4 | ||
|
|
559c50fa87 | ||
|
|
2ca0a05636 | ||
|
|
363de47313 | ||
|
|
bdb2803371 | ||
|
|
6eae438300 | ||
|
|
707ceb711a | ||
|
|
024646579e | ||
|
|
f895428e3d | ||
|
|
698456c205 | ||
|
|
43cf907a2e | ||
|
|
0a04ba5743 | ||
|
|
8b1a40d2e2 | ||
|
|
51ae0784cf | ||
|
|
efa5d3f629 | ||
|
|
fd5861026d | ||
|
|
1535ef9aac | ||
|
|
bb6f1f32a0 | ||
|
|
3effdee5c0 | ||
|
|
bf15c5fb45 | ||
|
|
5c3ba9e0d8 | ||
|
|
ce0b39d48b | ||
|
|
71e3498876 | ||
|
|
e8ec05bd51 | ||
|
|
2303c32940 | ||
|
|
e9d54bf0d6 | ||
|
|
e435a68aea | ||
|
|
d55143e6fb | ||
|
|
e2719c373d | ||
|
|
b1e9f9b3f8 | ||
|
|
2d512053a8 | ||
|
|
6cfddb68bb | ||
|
|
12d04e84d8 | ||
|
|
829e569ccd | ||
|
|
7e300e8789 | ||
|
|
6a6a93aec4 | ||
|
|
13cba764cc | ||
|
|
5263005e92 | ||
|
|
1a12a6c10c | ||
|
|
0b134f9266 | ||
|
|
07233ba9ae | ||
|
|
92aaae40f6 | ||
|
|
faeeaf5ecf | ||
|
|
edfbc2d937 | ||
|
|
ab8f870e73 | ||
|
|
2a5798c107 | ||
|
|
92441110bf | ||
|
|
1a9d59e804 | ||
|
|
9e7b9c5fe4 | ||
|
|
615172d29c | ||
|
|
5671323bc1 | ||
|
|
c0263eb3c3 | ||
|
|
a5a5358f7b | ||
|
|
2599fd85d7 | ||
|
|
84a308e5dc | ||
|
|
569d50f25b | ||
|
|
93221b9760 | ||
|
|
81a7f63782 | ||
|
|
1c7fd533c7 | ||
|
|
a0d9420be2 | ||
|
|
544dc8b639 | ||
|
|
68f02bbc80 | ||
|
|
1660b0a75b | ||
|
|
d691189973 | ||
|
|
03da6c9a0c | ||
|
|
46b0ed44bd | ||
|
|
362000d6df | ||
|
|
fb8b65e61b | ||
|
|
03bded3775 | ||
|
|
f605dd3d49 | ||
|
|
303c4c909e | ||
|
|
8178dfc215 | ||
|
|
714226b6a5 | ||
|
|
383ac95e90 | ||
|
|
67232f5a8e | ||
|
|
d399c8f774 | ||
|
|
4e3c9c208f | ||
|
|
fb316d9f37 | ||
|
|
0c14d8641d | ||
|
|
7dceb23e3d | ||
|
|
704c642a8f | ||
|
|
230afd7414 | ||
|
|
f146f6a312 | ||
|
|
69d9885e30 | ||
|
|
ea3fe35790 | ||
|
|
a67c34f8f6 | ||
|
|
b5b61d05f8 | ||
|
|
581e912d4c | ||
|
|
f31942efdf | ||
|
|
238f08192f | ||
|
|
42c236e235 | ||
|
|
14163513e3 | ||
|
|
563208689b | ||
|
|
106461b2b2 | ||
|
|
0f5fbdf296 | ||
|
|
cfa6bb8689 | ||
|
|
ec23dcc3cb | ||
|
|
13d72338a5 | ||
|
|
25988bdb7b | ||
|
|
0a840b1cb0 | ||
|
|
ce292ce9d3 | ||
|
|
90ab5e6577 | ||
|
|
b15b569fce | ||
|
|
15ab70b524 | ||
|
|
6db159e944 | ||
|
|
35d3096ce4 | ||
|
|
cc2e0308d7 | ||
|
|
ca52a81141 | ||
|
|
4f1b8103d0 | ||
|
|
71487fce59 | ||
|
|
ff1b406c48 | ||
|
|
61418186b2 | ||
|
|
9d54ec44e4 | ||
|
|
60e27da57d | ||
|
|
5668d40bc1 | ||
|
|
33720c5079 | ||
|
|
88313e6d06 | ||
|
|
6d4edabb46 | ||
|
|
bfacc4a1ee | ||
|
|
f72404e22a | ||
|
|
340d109e72 | ||
|
|
87aa913f5f | ||
|
|
5a22ab54fb | ||
|
|
90f99985a0 | ||
|
|
6c8502d8fe | ||
|
|
64c2129b1d | ||
|
|
7a64851256 | ||
|
|
c358c794ec | ||
|
|
028c6d2823 | ||
|
|
96da369f62 | ||
|
|
43d4db022c | ||
|
|
221794026e | ||
|
|
16c460cdc2 | ||
|
|
89416bd714 | ||
|
|
56654e1ced | ||
|
|
7de8a86869 | ||
|
|
3220e91f1c | ||
|
|
2da663ecd1 | ||
|
|
2a89695b0b | ||
|
|
7ca74b3b0d | ||
|
|
b0b309b0f1 | ||
|
|
5e22f3b0da | ||
|
|
9d32c483eb | ||
|
|
78130a54aa | ||
|
|
1f49de9b27 | ||
|
|
1f085604ee | ||
|
|
4f593459b1 | ||
|
|
b8d3d68b65 | ||
|
|
004ee178a4 | ||
|
|
07eab50848 | ||
|
|
b3b6bdee4e | ||
|
|
eea003c170 | ||
|
|
1ca8813e58 | ||
|
|
64c9919e28 | ||
|
|
04f6a0b6be | ||
|
|
35ace0214a | ||
|
|
bbbc22f30f | ||
|
|
9ae0a7010c | ||
|
|
7fa4d20da0 | ||
|
|
b49514f52b | ||
|
|
92973c1c7b | ||
|
|
271126b665 | ||
|
|
ebb7d4cec6 | ||
|
|
f86e6256a7 | ||
|
|
ebf2ea50ed | ||
|
|
a877f5ac13 | ||
|
|
cbd2283bf8 | ||
|
|
fca3c46372 | ||
|
|
b43957e6f9 | ||
|
|
57036aaffb | ||
|
|
999a33f82b | ||
|
|
09256f89e5 | ||
|
|
198828af16 | ||
|
|
a7d3a8acc7 | ||
|
|
838f74caa2 | ||
|
|
88a1399c3b | ||
|
|
8bb6657c6b | ||
|
|
a663f58502 | ||
|
|
e9659ee305 | ||
|
|
0b7bcb52da | ||
|
|
a25ede1284 | ||
|
|
3d9d0f297c | ||
|
|
fcdd13a307 | ||
|
|
d1fbb7e90b | ||
|
|
97c400130c | ||
|
|
534823843b | ||
|
|
1e9c8b07ac | ||
|
|
1673dfaa7a | ||
|
|
8453619067 | ||
|
|
cdec19db1f | ||
|
|
c91546dc1e | ||
|
|
4afadccb24 | ||
|
|
04d22e0eca | ||
|
|
c067b3f7fb | ||
|
|
e66bd2df83 | ||
|
|
00937f37ec | ||
|
|
8461d7f8b7 | ||
|
|
5764825c1d | ||
|
|
37ece44b9b | ||
|
|
89669f4016 | ||
|
|
83c3bdbdde | ||
|
|
5d2fdbdde1 | ||
|
|
30b148d783 | ||
|
|
46b8173b41 | ||
|
|
8d60bff989 | ||
|
|
53b439f1fd | ||
|
|
0b3a36c76f | ||
|
|
c1503f0614 | ||
|
|
52740c26de | ||
|
|
f961e5ac3f | ||
|
|
47dfaa26c0 | ||
|
|
a0d33be096 | ||
|
|
4367be97f8 | ||
|
|
11b4e7889e | ||
|
|
f1ba70db89 | ||
|
|
8159cfeadb | ||
|
|
c619484657 | ||
|
|
d1fafe64da | ||
|
|
3e55dff542 | ||
|
|
718868f7b5 | ||
|
|
32591b2710 | ||
|
|
045e37a0d3 | ||
|
|
02ef82a804 | ||
|
|
c6a2c52365 | ||
|
|
0011079199 | ||
|
|
eb3615acc6 | ||
|
|
f76df498f2 | ||
|
|
de6c74bee9 | ||
|
|
7379a81f01 | ||
|
|
5a6d5a66b0 | ||
|
|
c710a173f2 | ||
|
|
877a4c5dc6 | ||
|
|
0fcc4edde1 | ||
|
|
7ed39d3926 |
4
.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# These are supported funding model platforms
|
||||
|
||||
github: [tapframe]
|
||||
ko_fi: tapframe
|
||||
29
.gitignore
vendored
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
# dependencies
|
||||
node_modules/
|
||||
!node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
|
||||
|
||||
# Expo
|
||||
.expo/
|
||||
|
|
@ -40,11 +41,14 @@ release_announcement.md
|
|||
ALPHA_BUILD_2_ANNOUNCEMENT.md
|
||||
CHANGELOG.md
|
||||
.env.local
|
||||
android/
|
||||
# Android build artifacts (but keep source files)
|
||||
android/app/build/
|
||||
android/build/
|
||||
android/.gradle/
|
||||
android/app/libs/*.aar
|
||||
!android/app/libs/lib-decoder-ffmpeg-release.aar
|
||||
HEATING_OPTIMIZATIONS.md
|
||||
|
||||
android
|
||||
sliderreadme.md
|
||||
# sliderreadme.md
|
||||
.cursor/mcp.json
|
||||
local-scrapers-repo
|
||||
worki.json
|
||||
|
|
@ -53,4 +57,19 @@ hackintosh-emulator-fix.sh
|
|||
/ota-builds
|
||||
src/screens/xavio.md
|
||||
/nuvio-providers
|
||||
/KSPlayer
|
||||
/KSPlayer
|
||||
/exobase
|
||||
# ffmpegreadme.md
|
||||
toast.md
|
||||
ffmpegreadme.md
|
||||
sliderreadme.md
|
||||
bottomsheet.md
|
||||
fastimage.md
|
||||
|
||||
# Backup directories
|
||||
backup_sdk54_upgrade/
|
||||
SDK54_UPGRADE_SUMMARY.md
|
||||
SDK54_UPGRADE_SUMMARY.md
|
||||
build-and-publish-app-releases.sh
|
||||
bottomnav.md
|
||||
/TrailerServices
|
||||
|
|
|
|||
1
.vscode/settings.json
vendored
|
|
@ -1,2 +1,3 @@
|
|||
{
|
||||
"java.compile.nullAnalysis.mode": "automatic"
|
||||
}
|
||||
87
App.tsx
|
|
@ -9,7 +9,9 @@ import React, { useState, useEffect } from 'react';
|
|||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
I18nManager
|
||||
I18nManager,
|
||||
Platform,
|
||||
LogBox
|
||||
} from 'react-native';
|
||||
import { NavigationContainer } from '@react-navigation/native';
|
||||
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
||||
|
|
@ -26,14 +28,19 @@ import { GenreProvider } from './src/contexts/GenreContext';
|
|||
import { TraktProvider } from './src/contexts/TraktContext';
|
||||
import { ThemeProvider, useTheme } from './src/contexts/ThemeContext';
|
||||
import { TrailerProvider } from './src/contexts/TrailerContext';
|
||||
import { DownloadsProvider } from './src/contexts/DownloadsContext';
|
||||
import SplashScreen from './src/components/SplashScreen';
|
||||
import UpdatePopup from './src/components/UpdatePopup';
|
||||
import MajorUpdateOverlay from './src/components/MajorUpdateOverlay';
|
||||
import { useGithubMajorUpdate } from './src/hooks/useGithubMajorUpdate';
|
||||
import { useUpdatePopup } from './src/hooks/useUpdatePopup';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
import * as Sentry from '@sentry/react-native';
|
||||
import UpdateService from './src/services/updateService';
|
||||
import { memoryMonitorService } from './src/services/memoryMonitorService';
|
||||
import { aiService } from './src/services/aiService';
|
||||
import { AccountProvider, useAccount } from './src/contexts/AccountContext';
|
||||
import { ToastProvider } from './src/contexts/ToastContext';
|
||||
|
||||
Sentry.init({
|
||||
dsn: 'https://1a58bf436454d346e5852b7bfd3c95e8@o4509536317276160.ingest.de.sentry.io/4509536317734992',
|
||||
|
|
@ -56,6 +63,12 @@ Sentry.init({
|
|||
I18nManager.allowRTL(false);
|
||||
I18nManager.forceRTL(false);
|
||||
|
||||
// Suppress duplicate key warnings app-wide
|
||||
LogBox.ignoreLogs([
|
||||
'Warning: Encountered two children with the same key',
|
||||
'Keys should be unique so that components maintain their identity across updates'
|
||||
]);
|
||||
|
||||
// This fixes many navigation layout issues by using native screen containers
|
||||
enableScreens(true);
|
||||
|
||||
|
|
@ -82,6 +95,9 @@ const ThemedApp = () => {
|
|||
handleUpdateLater,
|
||||
handleDismiss,
|
||||
} = useUpdatePopup();
|
||||
|
||||
// GitHub major/minor release overlay
|
||||
const githubUpdate = useGithubMajorUpdate();
|
||||
|
||||
// Check onboarding status and initialize services
|
||||
useEffect(() => {
|
||||
|
|
@ -91,8 +107,10 @@ const ThemedApp = () => {
|
|||
const onboardingCompleted = await AsyncStorage.getItem('hasCompletedOnboarding');
|
||||
setHasCompletedOnboarding(onboardingCompleted === 'true');
|
||||
|
||||
// Initialize update service
|
||||
await UpdateService.initialize();
|
||||
// Initialize update service (skip on Android to prevent update checks)
|
||||
if (Platform.OS !== 'android') {
|
||||
await UpdateService.initialize();
|
||||
}
|
||||
|
||||
// Initialize memory monitoring service to prevent OutOfMemoryError
|
||||
memoryMonitorService; // Just accessing it starts the monitoring
|
||||
|
|
@ -141,31 +159,40 @@ const ThemedApp = () => {
|
|||
const initialRouteName = hasCompletedOnboarding ? 'MainTabs' : 'Onboarding';
|
||||
|
||||
return (
|
||||
<PaperProvider theme={customDarkTheme}>
|
||||
<NavigationContainer
|
||||
theme={customNavigationTheme}
|
||||
// Disable automatic linking which can cause layout issues
|
||||
linking={undefined}
|
||||
>
|
||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
<StatusBar
|
||||
style="light"
|
||||
/>
|
||||
{!isAppReady && <SplashScreen onFinish={handleSplashComplete} />}
|
||||
{shouldShowApp && <AppNavigator initialRouteName={initialRouteName} />}
|
||||
|
||||
{/* Update Popup */}
|
||||
<UpdatePopup
|
||||
visible={showUpdatePopup}
|
||||
updateInfo={updateInfo}
|
||||
onUpdateNow={handleUpdateNow}
|
||||
onUpdateLater={handleUpdateLater}
|
||||
onDismiss={handleDismiss}
|
||||
isInstalling={isInstalling}
|
||||
/>
|
||||
</View>
|
||||
</NavigationContainer>
|
||||
</PaperProvider>
|
||||
<AccountProvider>
|
||||
<PaperProvider theme={customDarkTheme}>
|
||||
<NavigationContainer
|
||||
theme={customNavigationTheme}
|
||||
linking={undefined}
|
||||
>
|
||||
<DownloadsProvider>
|
||||
<View style={[styles.container, { backgroundColor: currentTheme.colors.darkBackground }]}>
|
||||
<StatusBar style="light" />
|
||||
{!isAppReady && <SplashScreen onFinish={handleSplashComplete} />}
|
||||
{shouldShowApp && <AppNavigator initialRouteName={initialRouteName} />}
|
||||
{Platform.OS === 'ios' && (
|
||||
<UpdatePopup
|
||||
visible={showUpdatePopup}
|
||||
updateInfo={updateInfo}
|
||||
onUpdateNow={handleUpdateNow}
|
||||
onUpdateLater={handleUpdateLater}
|
||||
onDismiss={handleDismiss}
|
||||
isInstalling={isInstalling}
|
||||
/>
|
||||
)}
|
||||
<MajorUpdateOverlay
|
||||
visible={githubUpdate.visible}
|
||||
latestTag={githubUpdate.latestTag}
|
||||
releaseNotes={githubUpdate.releaseNotes}
|
||||
releaseUrl={githubUpdate.releaseUrl}
|
||||
onDismiss={githubUpdate.onDismiss}
|
||||
onLater={githubUpdate.onLater}
|
||||
/>
|
||||
</View>
|
||||
</DownloadsProvider>
|
||||
</NavigationContainer>
|
||||
</PaperProvider>
|
||||
</AccountProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -177,7 +204,9 @@ function App(): React.JSX.Element {
|
|||
<TraktProvider>
|
||||
<ThemeProvider>
|
||||
<TrailerProvider>
|
||||
<ThemedApp />
|
||||
<ToastProvider>
|
||||
<ThemedApp />
|
||||
</ToastProvider>
|
||||
</TrailerProvider>
|
||||
</ThemeProvider>
|
||||
</TraktProvider>
|
||||
|
|
|
|||
275
README.md
|
|
@ -1,153 +1,184 @@
|
|||
# Nuvio Streaming App
|
||||
<!-- Improved compatibility of back to top link -->
|
||||
<a id="readme-top"></a>
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/titlelogo.png" alt="Nuvio Logo" width="300"/>
|
||||
</p>
|
||||
<!-- 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]
|
||||
|
||||
<p align="center">
|
||||
A modern streaming application built with React Native and Expo, featuring comprehensive Stremio addon integration and Trakt.tv synchronization.
|
||||
</p>
|
||||
<!-- PROJECT LOGO -->
|
||||
<br />
|
||||
<div align="center">
|
||||
<img src="assets/titlelogo.png" alt="Nuvio Logo" width="120" />
|
||||
<h1 align="center">🎬 Nuvio Media Hub</h1>
|
||||
<p align="center">
|
||||
A modern media hub built with React Native and Expo
|
||||
<br />
|
||||
Stremio Addon ecosystem • Cross‑platform • Offline metadata & sync
|
||||
<br />
|
||||
<br />
|
||||
<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>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
---
|
||||
<!-- TABLE OF CONTENTS -->
|
||||
<details>
|
||||
<summary>Table of Contents</summary>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="#about-the-project">About The Project</a>
|
||||
</li>
|
||||
<li><a href="#demo">Screenshots</a></li>
|
||||
<li>
|
||||
<a href="#getting-started">Getting Started</a>
|
||||
<ul>
|
||||
<li><a href="#installation">Installation</a></li>
|
||||
<li><a href="#build">Build</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#contributing">Contributing</a></li>
|
||||
<li><a href="#support">Support</a></li>
|
||||
<li><a href="#license">License</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>
|
||||
</ol>
|
||||
</details>
|
||||
|
||||
## Stable Release
|
||||
Nuvio is now available as a stable release! Version 1.0.0 brings all major features from the beta phase, refined and optimized for the best user experience.
|
||||
<!-- ABOUT THE PROJECT -->
|
||||
## About The Project
|
||||
|
||||
[Download Latest Release](https://github.com/tapframe/NuvioStreaming/tags)
|
||||
Nuvio Media Hub is a cross‑platform app for managing, discovering, and streaming your media via a flexible addon ecosystem. Built with React Native + Expo, it integrates providers and sync services while keeping a simple, fast UI.
|
||||
|
||||
---
|
||||
|
||||
|
||||
## Installation
|
||||
<!-- DEMO / SCREENSHOTS -->
|
||||
## Demo
|
||||
<a id="demo"></a>
|
||||
|
||||
### AltStore Installation
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/2/20/AltStore_logo.png" width="32" height="32" align="left"> [](https://tinyurl.com/NuvioAltstore)
|
||||
| Home | Details |
|
||||
|:----:|:-------:|
|
||||
|  |  |
|
||||
|
||||
### SideStore Installation
|
||||
<img src="https://github.com/SideStore/assets/blob/main/icon.png?raw=true" width="32" height="32" align="left"> [](https://tinyurl.com/NuvioSidestore)
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
**Manual URL:** `https://raw.githubusercontent.com/tapframe/NuvioStreaming/main/nuvio-source.json`
|
||||
<!-- GETTING STARTED -->
|
||||
## Getting Started
|
||||
|
||||
---
|
||||
Follow the steps below to run the app locally.
|
||||
|
||||
## Core Features
|
||||
### Installation
|
||||
|
||||
### Content Discovery
|
||||
- **Personalized Recommendations:** Smart home screen with continue watching functionality
|
||||
- **Advanced Search:** Comprehensive filtering and instant search results
|
||||
- **Rich Metadata:** Detailed content information, cast, crew, and ratings
|
||||
- **Calendar Integration:** Track upcoming episodes and releases
|
||||
- **Library Management:** Personal collections with watch history tracking
|
||||
```bash
|
||||
git clone https://github.com/tapframe/NuvioStreaming.git
|
||||
cd NuvioStreaming
|
||||
npm install
|
||||
# If you hit peer dependency conflicts:
|
||||
# npm install --legacy-peer-deps
|
||||
npx expo start
|
||||
```
|
||||
|
||||
### Streaming Experience
|
||||
- **Dual Player Support:** Built-in video player with gesture controls and external player integration
|
||||
- **Intelligent Quality Selection:** Automatic stream optimization and subtitle support
|
||||
- **Trailer Playback:** Seamless trailer integration with preloading
|
||||
- **Continuous Playback:** Auto-play functionality for uninterrupted viewing
|
||||
- **Progress Synchronization:** Resume playback across devices
|
||||
### Build
|
||||
|
||||
### Platform Integrations
|
||||
- **Trakt.tv Sync:** Complete watch history, ratings, and library synchronization
|
||||
- **Stremio Ecosystem:** Full addon compatibility with easy management
|
||||
- **TMDB Integration:** Comprehensive metadata and high-quality imagery
|
||||
- **MDBList Support:** Enhanced ratings and recommendations
|
||||
- **Custom Scrapers:** Local content source integration
|
||||
```bash
|
||||
npx expo prebuild
|
||||
npx expo run:android # Android
|
||||
npx expo run:ios # iOS
|
||||
```
|
||||
|
||||
### User Interface
|
||||
- **Material Design:** Clean, modern interface with smooth animations
|
||||
- **Dynamic Theming:** Content-based color extraction and customization
|
||||
- **Cross-Platform:** Native experience on iOS and Android
|
||||
- **Performance Optimized:** Efficient rendering and image caching
|
||||
<details>
|
||||
<summary>Alternative iOS Installation</summary>
|
||||
|
||||
### Advanced Features
|
||||
- **Push Notifications:** New episode alerts and content reminders
|
||||
- **Background Synchronization:** Automatic data updates and content sync
|
||||
### AltStore
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/2/20/AltStore_logo.png" width="24" height="24" align="left"> [](https://tinyurl.com/NuvioAltstore)
|
||||
|
||||
---
|
||||
### SideStore
|
||||
<img src="https://github.com/SideStore/assets/blob/main/icon.png?raw=true" width="24" height="24" align="left"> [](https://tinyurl.com/NuvioSidestore)
|
||||
|
||||
## Screenshots
|
||||
**Manual URL:** `https://raw.githubusercontent.com/tapframe/NuvioStreaming/main/nuvio-source.json`
|
||||
|
||||
| Home Screen | Details Page | Home Screen 2 |
|
||||
|:-----------:|:------------:|:-------------:|
|
||||
|  |  |  |
|
||||
</details>
|
||||
|
||||
| Library | Player Loading | Video Player |
|
||||
|:-------:|:--------------:|:------------:|
|
||||
|  |  |  |
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
| Ratings | Episodes & Seasons | Search & Details |
|
||||
|:-------:|:------------------:|:----------------:|
|
||||
|  |  |  |
|
||||
|
||||
|
||||
---
|
||||
## Contributing
|
||||
|
||||
## Tools & Technologies
|
||||
Contributions make the open‑source community amazing! Any contributions are greatly appreciated.
|
||||
|
||||
1. Fork the project
|
||||
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
|
||||
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
|
||||
4. Push to the branch (`git push origin feature/AmazingFeature`)
|
||||
5. Open a Pull Request
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
## Support
|
||||
|
||||
If you find Nuvio helpful, consider supporting development:
|
||||
|
||||
* **Ko‑Fi** – `https://ko-fi.com/tapframe`
|
||||
* **GitHub Star** – Star the repo to show support
|
||||
* **Share** – Tell others about the project
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
## License
|
||||
|
||||
Distributed under the GNU GPLv3 License. See `LICENSE` for more information.
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
## Contact
|
||||
|
||||
**Project Links:**
|
||||
* GitHub: `https://github.com/tapframe`
|
||||
* Issues: `https://github.com/tapframe/NuvioStreaming/issues`
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
* [React Native](https://reactnative.dev/)
|
||||
* [Expo](https://expo.dev/)
|
||||
* [TypeScript](https://www.typescriptlang.org/)
|
||||
* Community contributors and testers
|
||||
|
||||
**Disclaimer:** This application functions as a media hub with addon/plugin support. It does not contain any built‑in content or host media content. Content access is only available through user‑installed plugins and addons. Any legal concerns should be directed to the specific websites providing the content.
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
## Built With
|
||||
|
||||
<p align="left">
|
||||
<a href="https://skillicons.dev">
|
||||
<img src="https://skillicons.dev/icons?i=react,typescript,nodejs,expo,github,githubactions&theme=light&perline=6" />
|
||||
</a>
|
||||
</p>
|
||||
<br/>
|
||||
React Native • Expo • TypeScript
|
||||
</p>
|
||||
|
||||
---
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisites
|
||||
- Node.js 18+
|
||||
- npm or yarn
|
||||
- Expo CLI
|
||||
|
||||
### Setup
|
||||
```bash
|
||||
git clone https://github.com/tapframe/NuvioStreaming.git
|
||||
cd NuvioStreaming
|
||||
npm install
|
||||
npx expo start
|
||||
```
|
||||
|
||||
### Build Commands
|
||||
```bash
|
||||
npx expo run:android # Android build
|
||||
npx expo run:ios # iOS build
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
2. Create a feature branch
|
||||
3. Make your changes
|
||||
4. Submit a pull request
|
||||
|
||||
---
|
||||
|
||||
## Issues
|
||||
|
||||
Report bugs and request features via [GitHub Issues](https://github.com/tapframe/NuvioStreaming/issues)
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
## License
|
||||
|
||||
[](http://www.gnu.org/licenses/gpl-3.0.en.html)
|
||||
|
||||
This project is licensed under the GNU General Public License v3.0 - see the [LICENSE](LICENSE) file for details.
|
||||
|
||||
---
|
||||
|
||||
## Disclaimer
|
||||
|
||||
This application functions as a content aggregator, accessing publicly available streams from third-party sources. No media content is hosted by this application. Users are responsible for compliance with applicable laws and regulations.
|
||||
|
||||
---
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Built with support from:
|
||||
- React Native & Expo
|
||||
- TMDB API
|
||||
- Trakt.tv
|
||||
- Stremio
|
||||
<!-- MARKDOWN LINKS & IMAGES -->
|
||||
[contributors-shield]: https://img.shields.io/github/contributors/tapframe/NuvioStreaming.svg?style=for-the-badge
|
||||
[contributors-url]: https://github.com/tapframe/NuvioStreaming/graphs/contributors
|
||||
[forks-shield]: https://img.shields.io/github/forks/tapframe/NuvioStreaming.svg?style=for-the-badge
|
||||
[forks-url]: https://github.com/tapframe/NuvioStreaming/network/members
|
||||
[stars-shield]: https://img.shields.io/github/stars/tapframe/NuvioStreaming.svg?style=for-the-badge
|
||||
[stars-url]: https://github.com/tapframe/NuvioStreaming/stargazers
|
||||
[issues-shield]: https://img.shields.io/github/issues/tapframe/NuvioStreaming.svg?style=for-the-badge
|
||||
[issues-url]: https://github.com/tapframe/NuvioStreaming/issues
|
||||
[license-shield]: https://img.shields.io/github/license/tapframe/NuvioStreaming.svg?style=for-the-badge
|
||||
[license-url]: http://www.gnu.org/licenses/gpl-3.0.en.html
|
||||
|
|
@ -14,6 +14,7 @@ react {
|
|||
hermesCommand = new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim()).getParentFile().getAbsolutePath() + "/sdks/hermesc/%OS-BIN%/hermesc"
|
||||
codegenDir = new File(["node", "--print", "require.resolve('@react-native/codegen/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().getAbsoluteFile()
|
||||
|
||||
enableBundleCompression = (findProperty('android.enableBundleCompression') ?: false).toBoolean()
|
||||
// Use Expo CLI to bundle the app, this ensures the Metro config
|
||||
// works correctly with Expo projects.
|
||||
cliFile = new File(["node", "--print", "require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })"].execute(null, rootDir).text.trim())
|
||||
|
|
@ -63,9 +64,9 @@ react {
|
|||
}
|
||||
|
||||
/**
|
||||
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
|
||||
* Set this to true in release builds to optimize the app using [R8](https://developer.android.com/topic/performance/app-optimization/enable-app-optimization).
|
||||
*/
|
||||
def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInReleaseBuilds') ?: false).toBoolean()
|
||||
def enableMinifyInReleaseBuilds = (findProperty('android.enableMinifyInReleaseBuilds') ?: false).toBoolean()
|
||||
|
||||
/**
|
||||
* The preferred build flavor of JavaScriptCore (JSC)
|
||||
|
|
@ -78,9 +79,9 @@ def enableProguardInReleaseBuilds = (findProperty('android.enableProguardInRelea
|
|||
* give correct results when using with locales other than en-US. Note that
|
||||
* this variant is about 6MiB larger per architecture than default.
|
||||
*/
|
||||
def jscFlavor = 'org.webkit:android-jsc:+'
|
||||
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
|
||||
|
|
@ -93,16 +94,39 @@ android {
|
|||
applicationId 'com.nuvio.app'
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 13
|
||||
versionName "1.1.0"
|
||||
versionCode 21
|
||||
versionName "1.2.6"
|
||||
|
||||
buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
|
||||
}
|
||||
|
||||
|
||||
// Split APKs by architecture only for smaller downloads
|
||||
splits {
|
||||
abi {
|
||||
reset()
|
||||
enable true
|
||||
universalApk false // If true, also generate a universal APK
|
||||
include "arm64-v8a", "armeabi-v7a", "x86", "x86_64"
|
||||
reset()
|
||||
include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
universalApk true
|
||||
}
|
||||
density {
|
||||
enable false
|
||||
}
|
||||
}
|
||||
|
||||
// Generate unique version codes for each split APK
|
||||
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 abiName = output.getFilter(com.android.build.OutputFile.ABI)
|
||||
|
||||
def versionCode = baseVersionCode * 100 // Base multiplier
|
||||
|
||||
if (abiName != null) {
|
||||
versionCode += abiVersionCodes.get(abiName)
|
||||
}
|
||||
|
||||
output.versionCodeOverride = versionCode
|
||||
}
|
||||
}
|
||||
signingConfigs {
|
||||
|
|
@ -121,15 +145,18 @@ android {
|
|||
// Caution! In production, you need to generate your own keystore file.
|
||||
// see https://reactnative.dev/docs/signed-apk-android.
|
||||
signingConfig signingConfigs.debug
|
||||
shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false)
|
||||
minifyEnabled enableProguardInReleaseBuilds
|
||||
def enableShrinkResources = findProperty('android.enableShrinkResourcesInReleaseBuilds') ?: 'false'
|
||||
shrinkResources enableShrinkResources.toBoolean()
|
||||
minifyEnabled enableMinifyInReleaseBuilds
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true)
|
||||
def enablePngCrunchInRelease = findProperty('android.enablePngCrunchInReleaseBuilds') ?: 'true'
|
||||
crunchPngs enablePngCrunchInRelease.toBoolean()
|
||||
}
|
||||
}
|
||||
packagingOptions {
|
||||
jniLibs {
|
||||
useLegacyPackaging (findProperty('expo.useLegacyPackaging')?.toBoolean() ?: false)
|
||||
def enableLegacyPackaging = findProperty('expo.useLegacyPackaging') ?: 'false'
|
||||
useLegacyPackaging enableLegacyPackaging.toBoolean()
|
||||
}
|
||||
}
|
||||
androidResources {
|
||||
|
|
@ -167,15 +194,15 @@ dependencies {
|
|||
|
||||
if (isGifEnabled) {
|
||||
// For animated gif support
|
||||
implementation("com.facebook.fresco:animated-gif:${reactAndroidLibs.versions.fresco.get()}")
|
||||
implementation("com.facebook.fresco:animated-gif:${expoLibs.versions.fresco.get()}")
|
||||
}
|
||||
|
||||
if (isWebpEnabled) {
|
||||
// For webp support
|
||||
implementation("com.facebook.fresco:webpsupport:${reactAndroidLibs.versions.fresco.get()}")
|
||||
implementation("com.facebook.fresco:webpsupport:${expoLibs.versions.fresco.get()}")
|
||||
if (isWebpAnimatedEnabled) {
|
||||
// Animated webp support
|
||||
implementation("com.facebook.fresco:animated-webp:${reactAndroidLibs.versions.fresco.get()}")
|
||||
implementation("com.facebook.fresco:animated-webp:${expoLibs.versions.fresco.get()}")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -184,4 +211,7 @@ dependencies {
|
|||
} else {
|
||||
implementation jscFlavor
|
||||
}
|
||||
|
||||
// Include only FFmpeg decoder AAR to avoid duplicates with Maven Media3
|
||||
implementation files("libs/lib-decoder-ffmpeg-release.aar")
|
||||
}
|
||||
|
|
|
|||
BIN
android/app/libs/lib-decoder-ffmpeg-release.aar
Normal file
14
android/app/proguard-rules.pro
vendored
|
|
@ -12,3 +12,17 @@
|
|||
-keep class com.facebook.react.turbomodule.** { *; }
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# Media3 / ExoPlayer keep (extensions and reflection)
|
||||
-keep class androidx.media3.** { *; }
|
||||
-dontwarn androidx.media3.**
|
||||
|
||||
# FastImage / Glide ProGuard rules
|
||||
-keep public class com.dylanvann.fastimage.* {*;}
|
||||
-keep public class com.dylanvann.fastimage.** {*;}
|
||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
||||
-keep public class * extends com.bumptech.glide.module.AppGlideModule
|
||||
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
|
||||
**[] $VALUES;
|
||||
public *;
|
||||
}
|
||||
|
|
|
|||
7
android/app/src/debugOptimized/AndroidManifest.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
||||
<application android:usesCleartextTraffic="true" tools:targetApi="28" tools:ignore="GoogleAppIndexingWarning" tools:replace="android:usesCleartextTraffic" />
|
||||
</manifest>
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
<data android:scheme="https"/>
|
||||
</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">
|
||||
<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="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"/>
|
||||
|
|
@ -28,8 +28,7 @@
|
|||
<action android:name="android.intent.action.VIEW"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
<category android:name="android.intent.category.BROWSABLE"/>
|
||||
<data android:scheme="stremioexpo"/>
|
||||
<data android:scheme="com.nuvio.app"/>
|
||||
<data android:scheme="nuvio"/>
|
||||
<data android:scheme="exp+nuvio"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ import android.content.res.Configuration
|
|||
|
||||
import com.facebook.react.PackageList
|
||||
import com.facebook.react.ReactApplication
|
||||
import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative
|
||||
import com.facebook.react.ReactNativeHost
|
||||
import com.facebook.react.ReactPackage
|
||||
import com.facebook.react.ReactHost
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
|
||||
import com.facebook.react.common.ReleaseLevel
|
||||
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint
|
||||
import com.facebook.react.defaults.DefaultReactNativeHost
|
||||
import com.facebook.react.soloader.OpenSourceMergedSoMapping
|
||||
import com.facebook.soloader.SoLoader
|
||||
|
||||
import expo.modules.ApplicationLifecycleDispatcher
|
||||
import expo.modules.ReactNativeHostWrapper
|
||||
|
|
@ -19,21 +19,19 @@ import expo.modules.ReactNativeHostWrapper
|
|||
class MainApplication : Application(), ReactApplication {
|
||||
|
||||
override val reactNativeHost: ReactNativeHost = ReactNativeHostWrapper(
|
||||
this,
|
||||
object : DefaultReactNativeHost(this) {
|
||||
override fun getPackages(): List<ReactPackage> {
|
||||
val packages = PackageList(this).packages
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
// packages.add(new MyReactNativePackage());
|
||||
return packages
|
||||
}
|
||||
this,
|
||||
object : DefaultReactNativeHost(this) {
|
||||
override fun getPackages(): List<ReactPackage> =
|
||||
PackageList(this).packages.apply {
|
||||
// Packages that cannot be autolinked yet can be added manually here, for example:
|
||||
// add(MyReactNativePackage())
|
||||
}
|
||||
|
||||
override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry"
|
||||
|
||||
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
|
||||
|
||||
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
|
||||
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
|
||||
}
|
||||
)
|
||||
|
||||
|
|
@ -42,11 +40,12 @@ class MainApplication : Application(), ReactApplication {
|
|||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
SoLoader.init(this, OpenSourceMergedSoMapping)
|
||||
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
|
||||
// If you opted-in for the New Architecture, we load the native entry point for this app.
|
||||
load()
|
||||
DefaultNewArchitectureEntryPoint.releaseLevel = try {
|
||||
ReleaseLevel.valueOf(BuildConfig.REACT_NATIVE_RELEASE_LEVEL.uppercase())
|
||||
} catch (e: IllegalArgumentException) {
|
||||
ReleaseLevel.STABLE
|
||||
}
|
||||
loadReactNative(this)
|
||||
ApplicationLifecycleDispatcher.onApplicationCreate(this)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 154 KiB After Width: | Height: | Size: 173 KiB |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 7 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 7 KiB |
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 28 KiB |
|
|
@ -3,5 +3,5 @@
|
|||
<string name="expo_splash_screen_resize_mode" translatable="false">contain</string>
|
||||
<string name="expo_splash_screen_status_bar_translucent" translatable="false">false</string>
|
||||
<string name="expo_system_ui_user_interface_style" translatable="false">dark</string>
|
||||
<string name="expo_runtime_version">1.1.0</string>
|
||||
<string name="expo_runtime_version">1.2.6</string>
|
||||
</resources>
|
||||
|
|
@ -1,17 +1,11 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
<item name="android:editTextStyle">@style/ResetEditText</item>
|
||||
<style name="AppTheme" parent="Theme.Material3.DayNight.NoActionBar">
|
||||
<item name="android:enforceNavigationBarContrast" tools:targetApi="29">true</item>
|
||||
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="android:statusBarColor">#020404</item>
|
||||
<item name="android:windowBackground">@color/activityBackground</item>
|
||||
</style>
|
||||
<style name="ResetEditText" parent="@android:style/Widget.EditText">
|
||||
<item name="android:padding">0dp</item>
|
||||
<item name="android:textColorHint">#c8c8c8</item>
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
</style>
|
||||
<style name="Theme.App.SplashScreen" parent="AppTheme">
|
||||
<item name="android:windowBackground">@drawable/ic_launcher_background</item>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,41 +1,24 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
ext {
|
||||
buildToolsVersion = findProperty('android.buildToolsVersion') ?: '35.0.0'
|
||||
minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '24')
|
||||
compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '35')
|
||||
targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '34')
|
||||
kotlinVersion = findProperty('android.kotlinVersion') ?: '1.9.25'
|
||||
|
||||
ndkVersion = "26.1.10909125"
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath('com.android.tools.build:gradle')
|
||||
classpath('com.facebook.react:react-native-gradle-plugin')
|
||||
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath('com.android.tools.build:gradle')
|
||||
classpath('com.facebook.react:react-native-gradle-plugin')
|
||||
classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "com.facebook.react.rootproject"
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
maven {
|
||||
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
|
||||
url(new File(['node', '--print', "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), '../android'))
|
||||
}
|
||||
maven {
|
||||
// Android JSC is installed from npm
|
||||
url(new File(['node', '--print', "require.resolve('jsc-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), '../dist'))
|
||||
}
|
||||
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://www.jitpack.io' }
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url 'https://www.jitpack.io' }
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: "expo-root-project"
|
||||
apply plugin: "com.facebook.react.rootproject"
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@
|
|||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
|
||||
org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
|
||||
org.gradle.jvmargs=-Xmx4096m -XX:MaxMetaspaceSize=1024m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
org.gradle.parallel=true
|
||||
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app's APK
|
||||
|
|
@ -41,6 +41,11 @@ newArchEnabled=true
|
|||
# If set to false, you will be using JSC instead.
|
||||
hermesEnabled=true
|
||||
|
||||
# Use this property to enable edge-to-edge display support.
|
||||
# This allows your app to draw behind system bars for an immersive UI.
|
||||
# Note: Only works with ReactActivity and should not be used with custom Activity.
|
||||
edgeToEdgeEnabled=true
|
||||
|
||||
# Enable GIF support in React Native images (~200 B increase)
|
||||
expo.gif.enabled=true
|
||||
# Enable webp support in React Native images (~85 KB increase)
|
||||
|
|
@ -54,3 +59,7 @@ EX_DEV_CLIENT_NETWORK_INSPECTOR=true
|
|||
|
||||
# Use legacy packaging to compress native libraries in the resulting APK.
|
||||
expo.useLegacyPackaging=false
|
||||
|
||||
# Specifies whether the app is configured to use edge-to-edge via the app config or plugin
|
||||
# WARNING: This property has been deprecated and will be removed in Expo SDK 55. Use `edgeToEdgeEnabled` or `react.edgeToEdgeEnabled` to determine whether the project is using edge-to-edge.
|
||||
expo.edgeToEdgeEnabled=true
|
||||
|
|
|
|||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
|
|
@ -1,6 +1,6 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
|
|||
7
android/gradlew
vendored
Normal file → Executable file
|
|
@ -86,8 +86,7 @@ done
|
|||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
|
@ -115,7 +114,7 @@ case "$( uname )" in #(
|
|||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
CLASSPATH="\\\"\\\""
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
|
|
@ -214,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
|
|
|
|||
4
android/gradlew.bat
vendored
|
|
@ -70,11 +70,11 @@ goto fail
|
|||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
set CLASSPATH=
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
|
|
|
|||
|
|
@ -1,38 +1,39 @@
|
|||
pluginManagement {
|
||||
includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile().toString())
|
||||
def reactNativeGradlePlugin = new File(
|
||||
providers.exec {
|
||||
workingDir(rootDir)
|
||||
commandLine("node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })")
|
||||
}.standardOutput.asText.get().trim()
|
||||
).getParentFile().absolutePath
|
||||
includeBuild(reactNativeGradlePlugin)
|
||||
|
||||
def expoPluginsPath = new File(
|
||||
providers.exec {
|
||||
workingDir(rootDir)
|
||||
commandLine("node", "--print", "require.resolve('expo-modules-autolinking/package.json', { paths: [require.resolve('expo/package.json')] })")
|
||||
}.standardOutput.asText.get().trim(),
|
||||
"../android/expo-gradle-plugin"
|
||||
).absolutePath
|
||||
includeBuild(expoPluginsPath)
|
||||
}
|
||||
|
||||
plugins {
|
||||
id("com.facebook.react.settings")
|
||||
id("expo-autolinking-settings")
|
||||
}
|
||||
plugins { id("com.facebook.react.settings") }
|
||||
|
||||
extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
|
||||
if (System.getenv('EXPO_USE_COMMUNITY_AUTOLINKING') == '1') {
|
||||
ex.autolinkLibrariesFromCommand()
|
||||
} else {
|
||||
def command = [
|
||||
'node',
|
||||
'--no-warnings',
|
||||
'--eval',
|
||||
'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))',
|
||||
'react-native-config',
|
||||
'--json',
|
||||
'--platform',
|
||||
'android'
|
||||
].toList()
|
||||
ex.autolinkLibrariesFromCommand(command)
|
||||
ex.autolinkLibrariesFromCommand(expoAutolinking.rnConfigCommand)
|
||||
}
|
||||
}
|
||||
expoAutolinking.useExpoModules()
|
||||
|
||||
rootProject.name = 'Nuvio'
|
||||
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
reactAndroidLibs {
|
||||
from(files(new File(["node", "--print", "require.resolve('react-native/package.json')"].execute(null, rootDir).text.trim(), "../gradle/libs.versions.toml")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle");
|
||||
useExpoModules()
|
||||
expoAutolinking.useExpoVersionCatalog()
|
||||
|
||||
include ':app'
|
||||
includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile())
|
||||
includeBuild(expoAutolinking.reactNativeGradlePlugin)
|
||||
|
|
|
|||
22
app.json
|
|
@ -2,22 +2,22 @@
|
|||
"expo": {
|
||||
"name": "Nuvio",
|
||||
"slug": "nuvio",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.6",
|
||||
"orientation": "default",
|
||||
"backgroundColor": "#020404",
|
||||
"icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png",
|
||||
"userInterfaceStyle": "dark",
|
||||
"scheme": "stremioexpo",
|
||||
"scheme": "nuvio",
|
||||
"newArchEnabled": true,
|
||||
"splash": {
|
||||
"image": "./assets/splash-icon.png",
|
||||
"image": "./src/assets/splash-icon-new.png",
|
||||
"resizeMode": "contain",
|
||||
"backgroundColor": "#020404"
|
||||
},
|
||||
"ios": {
|
||||
"supportsTablet": true,
|
||||
"icon": "./assets/ios/AppIcon.appiconset/Icon-App-60x60@3x.png",
|
||||
"buildNumber": "13",
|
||||
"buildNumber": "21",
|
||||
"infoPlist": {
|
||||
"NSAppTransportSecurity": {
|
||||
"NSAllowsArbitraryLoads": true
|
||||
|
|
@ -48,7 +48,7 @@
|
|||
"WAKE_LOCK"
|
||||
],
|
||||
"package": "com.nuvio.app",
|
||||
"versionCode": 13,
|
||||
"versionCode": 21,
|
||||
"architectures": [
|
||||
"arm64-v8a",
|
||||
"armeabi-v7a",
|
||||
|
|
@ -79,7 +79,15 @@
|
|||
{
|
||||
"username": "nayifleo"
|
||||
}
|
||||
]
|
||||
],
|
||||
[
|
||||
"expo-libvlc-player",
|
||||
{
|
||||
"localNetworkPermission": "Allow $(PRODUCT_NAME) to access your local network",
|
||||
"supportsBackgroundPlayback": true
|
||||
}
|
||||
],
|
||||
"react-native-bottom-tabs"
|
||||
],
|
||||
"updates": {
|
||||
"enabled": true,
|
||||
|
|
@ -87,6 +95,6 @@
|
|||
"fallbackToCacheTimeout": 30000,
|
||||
"url": "https://grim-reyna-tapframe-69970143.koyeb.app/api/manifest"
|
||||
},
|
||||
"runtimeVersion": "1.1.0"
|
||||
"runtimeVersion": "1.2.6"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1
app/Streams.tsx
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
BIN
assets/AppIcons/android/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4 KiB |
BIN
assets/AppIcons/android/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
assets/AppIcons/android/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
BIN
assets/AppIcons/android/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/AppIcons/android/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.9 KiB |
|
Before Width: | Height: | Size: 6.7 KiB After Width: | Height: | Size: 8.8 KiB |
|
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 6.1 KiB |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 8.6 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 7 KiB After Width: | Height: | Size: 8 KiB |
|
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 9.8 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 85 KiB |
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#151515</color>
|
||||
<color name="ic_launcher_background">#2f2f2f</color>
|
||||
</resources>
|
||||
BIN
assets/bootsplash/android/drawable-hdpi/bootsplash_logo.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
assets/bootsplash/android/drawable-mdpi/bootsplash_logo.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
assets/bootsplash/android/drawable-xhdpi/bootsplash_logo.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
assets/bootsplash/android/drawable-xxhdpi/bootsplash_logo.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
BIN
assets/bootsplash/android/drawable-xxxhdpi/bootsplash_logo.png
Normal file
|
After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 6.9 KiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 111 KiB |
BIN
assets/icon.png
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 111 KiB |
|
Before Width: | Height: | Size: 718 B After Width: | Height: | Size: 785 B |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 5.1 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 5.2 KiB After Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 9.6 KiB After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 5 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 280 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 280 KiB |
|
Before Width: | Height: | Size: 423 KiB After Width: | Height: | Size: 583 KiB |
|
|
@ -3,7 +3,8 @@ module.exports = function (api) {
|
|||
return {
|
||||
presets: ['babel-preset-expo'],
|
||||
plugins: [
|
||||
'react-native-reanimated/plugin',
|
||||
'react-native-worklets/plugin',
|
||||
'react-native-boost/plugin',
|
||||
],
|
||||
env: {
|
||||
production: {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ class KSPlayerView: UIView {
|
|||
private var isPaused = false
|
||||
private var currentVolume: Float = 1.0
|
||||
weak var viewManager: KSPlayerViewManager?
|
||||
private var loadTimeoutWorkItem: DispatchWorkItem?
|
||||
|
||||
// Event blocks for Fabric
|
||||
@objc var onLoad: RCTDirectEventBlock?
|
||||
|
|
@ -109,6 +108,14 @@ class KSPlayerView: UIView {
|
|||
|
||||
guard let uri = source["uri"] as? String else {
|
||||
print("KSPlayerView: No URI provided")
|
||||
sendEvent("onError", ["error": "No URI provided in source"])
|
||||
return
|
||||
}
|
||||
|
||||
// Validate URL before proceeding
|
||||
guard let url = URL(string: uri), url.scheme != nil else {
|
||||
print("KSPlayerView: Invalid URL format: \(uri)")
|
||||
sendEvent("onError", ["error": "Invalid URL format: \(uri)"])
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -135,11 +142,12 @@ class KSPlayerView: UIView {
|
|||
}
|
||||
#endif
|
||||
|
||||
// Create KSPlayerResource
|
||||
let url = URL(string: uri)!
|
||||
// Create KSPlayerResource with validated URL
|
||||
let resource = KSPlayerResource(url: url, options: createOptions(with: headers), name: "Video")
|
||||
|
||||
print("KSPlayerView: Setting source: \(uri)")
|
||||
print("KSPlayerView: URL scheme: \(url.scheme ?? "unknown"), host: \(url.host ?? "unknown")")
|
||||
|
||||
playerView.set(resource: resource)
|
||||
|
||||
// Set up delegate after setting the resource
|
||||
|
|
@ -153,18 +161,6 @@ class KSPlayerView: UIView {
|
|||
}
|
||||
|
||||
setVolume(currentVolume)
|
||||
|
||||
// Start a safety timeout to surface errors if never ready
|
||||
loadTimeoutWorkItem?.cancel()
|
||||
let work = DispatchWorkItem { [weak self] in
|
||||
guard let self = self else { return }
|
||||
let dur = self.playerView.playerLayer?.player.duration ?? 0
|
||||
if dur <= 0 {
|
||||
self.sendEvent("onError", ["error": "Playback timeout: stream did not become ready."])
|
||||
}
|
||||
}
|
||||
loadTimeoutWorkItem = work
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 8, execute: work)
|
||||
}
|
||||
|
||||
private func createOptions(with headers: [String: String]) -> KSOptions {
|
||||
|
|
@ -192,9 +188,23 @@ class KSPlayerView: UIView {
|
|||
options.hardwareDecode = KSOptions.hardwareDecode
|
||||
#endif
|
||||
if !headers.isEmpty {
|
||||
options.appendHeader(headers)
|
||||
if let referer = headers["Referer"] ?? headers["referer"] {
|
||||
options.referer = referer
|
||||
// Clean and validate headers before adding
|
||||
var cleanHeaders: [String: String] = [:]
|
||||
for (key, value) in headers {
|
||||
// Remove any null or empty values
|
||||
if !value.isEmpty && value != "null" {
|
||||
cleanHeaders[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
if !cleanHeaders.isEmpty {
|
||||
options.appendHeader(cleanHeaders)
|
||||
print("KSPlayerView: Added headers: \(cleanHeaders.keys.joined(separator: ", "))")
|
||||
|
||||
if let referer = cleanHeaders["Referer"] ?? cleanHeaders["referer"] {
|
||||
options.referer = referer
|
||||
print("KSPlayerView: Set referer: \(referer)")
|
||||
}
|
||||
}
|
||||
}
|
||||
return options
|
||||
|
|
@ -404,8 +414,6 @@ extension KSPlayerView: KSPlayerLayerDelegate {
|
|||
func player(layer: KSPlayerLayer, state: KSPlayerState) {
|
||||
switch state {
|
||||
case .readyToPlay:
|
||||
// Cancel timeout when ready
|
||||
loadTimeoutWorkItem?.cancel()
|
||||
// Send onLoad event to React Native with track information
|
||||
let p = layer.player
|
||||
let tracks = getAvailableTracks()
|
||||
|
|
@ -447,7 +455,22 @@ extension KSPlayerView: KSPlayerLayerDelegate {
|
|||
|
||||
func player(layer: KSPlayerLayer, finish error: Error?) {
|
||||
if let error = error {
|
||||
sendEvent("onError", ["error": error.localizedDescription])
|
||||
let errorMessage = error.localizedDescription
|
||||
print("KSPlayerView: Player finished with error: \(errorMessage)")
|
||||
|
||||
// Provide more specific error messages for common issues
|
||||
var detailedError = errorMessage
|
||||
if errorMessage.contains("avformat can't open input") {
|
||||
detailedError = "Unable to open video stream. This could be due to:\n• Invalid or malformed URL\n• Network connectivity issues\n• Server blocking the request\n• Unsupported video format\n• Missing required headers"
|
||||
} else if errorMessage.contains("timeout") {
|
||||
detailedError = "Stream connection timed out. The server may be slow or unreachable."
|
||||
} else if errorMessage.contains("404") || errorMessage.contains("Not Found") {
|
||||
detailedError = "Video stream not found. The URL may be expired or incorrect."
|
||||
} else if errorMessage.contains("403") || errorMessage.contains("Forbidden") {
|
||||
detailedError = "Access denied. The server may be blocking requests or require authentication."
|
||||
}
|
||||
|
||||
sendEvent("onError", ["error": detailedError])
|
||||
}
|
||||
}
|
||||
|
||||
173
backup_sdk54_upgrade/NATIVE_MODIFICATIONS_GUIDE.md
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
# Native Modifications Guide - SDK 54 Upgrade
|
||||
|
||||
**Created:** October 14, 2025
|
||||
**From SDK:** 52
|
||||
**To SDK:** 54
|
||||
|
||||
## Overview
|
||||
|
||||
This document records all custom native modifications made to the Nuvio app that need to be preserved during the Expo SDK 54 upgrade.
|
||||
|
||||
---
|
||||
|
||||
## iOS Modifications
|
||||
|
||||
### 1. KSPlayer Bridge Integration
|
||||
|
||||
**Purpose:** Custom video player for iOS using KSPlayer library
|
||||
|
||||
**Files Added/Modified:**
|
||||
- `ios/KSPlayerManager.m` - Objective-C bridge header
|
||||
- `ios/KSPlayerModule.swift` - Swift module for KSPlayer
|
||||
- `ios/KSPlayerView.swift` - Main player view implementation
|
||||
- `ios/KSPlayerViewManager.swift` - React Native view manager
|
||||
|
||||
**Location in Xcode Project:**
|
||||
- Files are in `ios/Nuvio/` directory
|
||||
- Referenced in `ios/Nuvio.xcodeproj/project.pbxproj`
|
||||
|
||||
**Podfile Dependencies (lines 52-56):**
|
||||
```ruby
|
||||
# KSPlayer dependencies
|
||||
pod 'KSPlayer',:git => 'https://github.com/kingslay/KSPlayer.git', :branch => 'main', :modular_headers => true
|
||||
pod 'DisplayCriteria',:git => 'https://github.com/kingslay/KSPlayer.git', :branch => 'main', :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', :modular_headers => true
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Custom video player with multi-codec support
|
||||
- Audio track selection
|
||||
- Subtitle track selection
|
||||
- Advanced playback controls
|
||||
- Header injection for streaming
|
||||
- Multi-channel audio downmixing
|
||||
|
||||
**Restoration Steps:**
|
||||
1. Copy KSPlayer bridge files to `ios/` directory after prebuild
|
||||
2. Add Podfile dependencies
|
||||
3. Run `pod install`
|
||||
4. Ensure files are linked in Xcode project
|
||||
|
||||
---
|
||||
|
||||
## Android Modifications
|
||||
|
||||
### 1. FFmpeg Audio Decoder Extension
|
||||
|
||||
**Purpose:** Enable ExoPlayer to play AC3, E-AC3, DTS, TrueHD audio codecs via FFmpeg
|
||||
|
||||
**Files Added:**
|
||||
- `android/app/libs/lib-decoder-ffmpeg-release.aar` - FFmpeg decoder AAR from Media3
|
||||
|
||||
**build.gradle Modifications (line 189):**
|
||||
```gradle
|
||||
// Include only FFmpeg decoder AAR to avoid duplicates with Maven Media3
|
||||
implementation files("libs/lib-decoder-ffmpeg-release.aar")
|
||||
```
|
||||
|
||||
**proguard-rules.pro Additions (lines 16-18):**
|
||||
```proguard
|
||||
# Media3 / ExoPlayer keep (extensions and reflection)
|
||||
-keep class androidx.media3.** { *; }
|
||||
-dontwarn androidx.media3.**
|
||||
```
|
||||
|
||||
**Node Modules Modification:**
|
||||
- File: `node_modules/react-native-video/android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java`
|
||||
- Change: Set extension renderer mode to PREFER to use FFmpeg decoders
|
||||
```java
|
||||
new DefaultRenderersFactory(getContext())
|
||||
.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER)
|
||||
```
|
||||
|
||||
**Important Notes:**
|
||||
- FFmpeg module provides AUDIO decoders only (AC3, E-AC3, DTS, TrueHD)
|
||||
- Does NOT provide video decoders (HEVC/Dolby Vision rely on device hardware or VLC fallback)
|
||||
- The AAR is from Just Player base (`exobase/app/libs`)
|
||||
|
||||
**Restoration Steps:**
|
||||
1. Copy `lib-decoder-ffmpeg-release.aar` to `android/app/libs/`
|
||||
2. Add implementation line to `android/app/build.gradle`
|
||||
3. Add keep rules to `android/app/proguard-rules.pro`
|
||||
4. Modify `ReactExoplayerView.java` in node_modules (after npm install)
|
||||
|
||||
---
|
||||
|
||||
## Application Logic Changes
|
||||
|
||||
### Player Fallback Strategy
|
||||
|
||||
**Modified Files:**
|
||||
- `src/screens/StreamsScreen.tsx` - Removed MKV pre-forcing to VLC
|
||||
- `src/components/player/AndroidVideoPlayer.tsx` - Error handler toggles `forceVlc`
|
||||
|
||||
**Behavior:**
|
||||
- Start with ExoPlayer + FFmpeg audio decoders by default
|
||||
- On decoder errors (codec not supported), automatically switch to VLC
|
||||
- Do not pre-force VLC based on file extension
|
||||
|
||||
---
|
||||
|
||||
## Backup Location
|
||||
|
||||
All backups are stored in: `/Users/nayifnoushad/Documents/Projects/NuvioStreaming/backup_sdk54_upgrade/`
|
||||
|
||||
**Backup Contents:**
|
||||
- `android_original/` - Complete Android directory
|
||||
- `ios_original/` - Complete iOS directory (partial - Pods symlinks failed)
|
||||
- `KSPlayerManager.m` - iOS bridge file
|
||||
- `KSPlayerModule.swift` - iOS module file
|
||||
- `KSPlayerView.swift` - iOS view file
|
||||
- `KSPlayerViewManager.swift` - iOS view manager file
|
||||
- `lib-decoder-ffmpeg-release.aar` - FFmpeg AAR
|
||||
- `build.gradle.backup` - Android build.gradle
|
||||
- `proguard-rules.pro.backup` - ProGuard rules
|
||||
- `Podfile.backup` - iOS Podfile
|
||||
- `package.json.backup` - Original package.json
|
||||
- `ReactExoplayerView.java.backup` - Modified react-native-video file
|
||||
|
||||
---
|
||||
|
||||
## SDK 54 Upgrade Process
|
||||
|
||||
### Pre-Upgrade Checklist
|
||||
- ✅ All native files backed up
|
||||
- ✅ Custom modifications documented
|
||||
- ✅ FFmpeg AAR preserved
|
||||
- ✅ KSPlayer bridge files preserved
|
||||
- ✅ Build configuration files backed up
|
||||
|
||||
### Upgrade Steps
|
||||
1. Update package.json to SDK 54
|
||||
2. Run `npx expo install` to update compatible packages
|
||||
3. Run `npx expo prebuild --clean` to regenerate native projects
|
||||
4. Restore Android FFmpeg integration
|
||||
5. Restore iOS KSPlayer integration
|
||||
6. Test builds on both platforms
|
||||
|
||||
### Post-Upgrade Verification
|
||||
- [ ] Android: FFmpeg audio decoders working (test AC3/DTS stream)
|
||||
- [ ] iOS: KSPlayer bridge working
|
||||
- [ ] Audio track selection functional
|
||||
- [ ] Subtitle track selection functional
|
||||
- [ ] VLC fallback working on decoder errors
|
||||
- [ ] App builds successfully for both platforms
|
||||
|
||||
---
|
||||
|
||||
## Critical Notes
|
||||
|
||||
1. **react-native-video modification:** This must be reapplied after every `npm install` or package update
|
||||
2. **FFmpeg limitations:** Audio codecs only - video codecs require hardware decoder or VLC
|
||||
3. **KSPlayer Podfile:** Uses git branches, may need version pinning for stability
|
||||
4. **Xcode project:** KSPlayer files must be linked in project.pbxproj after prebuild
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- FFmpeg integration guide: `ffmpegreadme.md`
|
||||
- KSPlayer repo: https://github.com/kingslay/KSPlayer
|
||||
- Expo SDK 54 changelog: https://expo.dev/changelog/2025/
|
||||
|
||||