mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-04 09:19:06 +00:00
continue watching improvements. testflight link added to readme
This commit is contained in:
parent
8a34bf6678
commit
3c35b99759
6 changed files with 664 additions and 511 deletions
|
|
@ -66,6 +66,9 @@ Download the latest APK from [GitHub Releases](https://github.com/tapframe/Nuvio
|
||||||
|
|
||||||
### iOS
|
### iOS
|
||||||
|
|
||||||
|
#### TestFlight (Recommended)
|
||||||
|
<img src="https://upload.wikimedia.org/wikipedia/fr/b/bc/TestFlight-icon.png" width="24" height="24" align="left"> [](https://testflight.apple.com/join/QkKMGRqp)
|
||||||
|
|
||||||
#### AltStore
|
#### AltStore
|
||||||
<img src="https://upload.wikimedia.org/wikipedia/commons/2/20/AltStore_logo.png" width="24" height="24" align="left"> [](https://tinyurl.com/NuvioAltstore)
|
<img src="https://upload.wikimedia.org/wikipedia/commons/2/20/AltStore_logo.png" width="24" height="24" align="left"> [](https://tinyurl.com/NuvioAltstore)
|
||||||
|
|
||||||
|
|
|
||||||
284
index.html
284
index.html
|
|
@ -1,10 +1,12 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Nuvio - Media Hub</title>
|
<title>Nuvio - Media Hub</title>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap"
|
||||||
|
rel="stylesheet">
|
||||||
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
|
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
* {
|
* {
|
||||||
|
|
@ -231,7 +233,8 @@
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
html, body {
|
html,
|
||||||
|
body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
|
@ -302,14 +305,29 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes float {
|
@keyframes float {
|
||||||
0%, 100% { transform: translateY(0px) rotate(0deg); }
|
|
||||||
33% { transform: translateY(-20px) rotate(1deg); }
|
0%,
|
||||||
66% { transform: translateY(10px) rotate(-1deg); }
|
100% {
|
||||||
|
transform: translateY(0px) rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
33% {
|
||||||
|
transform: translateY(-20px) rotate(1deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
66% {
|
||||||
|
transform: translateY(10px) rotate(-1deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes rotate {
|
@keyframes rotate {
|
||||||
0% { transform: rotate(0deg); }
|
0% {
|
||||||
100% { transform: rotate(360deg); }
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero-content {
|
.hero-content {
|
||||||
|
|
@ -388,17 +406,26 @@
|
||||||
0% {
|
0% {
|
||||||
background-position: 0% 50%;
|
background-position: 0% 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
50% {
|
50% {
|
||||||
background-position: 100% 50%;
|
background-position: 100% 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
background-position: 0% 50%;
|
background-position: 0% 50%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes titleFloat {
|
@keyframes titleFloat {
|
||||||
0%, 100% { transform: translateY(0px) rotateX(0deg); }
|
|
||||||
50% { transform: translateY(-10px) rotateX(2deg); }
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translateY(0px) rotateX(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translateY(-10px) rotateX(2deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero p {
|
.hero p {
|
||||||
|
|
@ -428,6 +455,7 @@
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateY(30px);
|
transform: translateY(30px);
|
||||||
}
|
}
|
||||||
|
|
||||||
100% {
|
100% {
|
||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
|
|
@ -458,8 +486,17 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes pulse {
|
@keyframes pulse {
|
||||||
0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.3; }
|
|
||||||
50% { transform: translate(-50%, -50%) scale(1.2); opacity: 0.1; }
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
opacity: 0.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: translate(-50%, -50%) scale(1.2);
|
||||||
|
opacity: 0.1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.download-btn {
|
.download-btn {
|
||||||
|
|
@ -550,12 +587,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes bounce {
|
@keyframes bounce {
|
||||||
0%, 20%, 50%, 80%, 100% {
|
|
||||||
|
0%,
|
||||||
|
20%,
|
||||||
|
50%,
|
||||||
|
80%,
|
||||||
|
100% {
|
||||||
transform: translateY(0);
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
40% {
|
40% {
|
||||||
transform: translateY(-8px);
|
transform: translateY(-8px);
|
||||||
}
|
}
|
||||||
|
|
||||||
60% {
|
60% {
|
||||||
transform: translateY(-4px);
|
transform: translateY(-4px);
|
||||||
}
|
}
|
||||||
|
|
@ -601,8 +645,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes floatParticles {
|
@keyframes floatParticles {
|
||||||
0% { transform: translateY(0) rotate(0deg); }
|
0% {
|
||||||
100% { transform: translateY(-20px) rotate(360deg); }
|
transform: translateY(0) rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateY(-20px) rotate(360deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.features h2 {
|
.features h2 {
|
||||||
|
|
@ -715,8 +764,13 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes shimmerSweep {
|
@keyframes shimmerSweep {
|
||||||
0% { transform: translateX(-100%) rotate(45deg); }
|
0% {
|
||||||
100% { transform: translateX(100%) rotate(45deg); }
|
transform: translateX(-100%) rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: translateX(100%) rotate(45deg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.feature-card:hover {
|
.feature-card:hover {
|
||||||
|
|
@ -1523,15 +1577,30 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mobile focus highlight removal */
|
/* Mobile focus highlight removal */
|
||||||
a, button, .download-btn, .privacy-back-btn, .ios-install-option, .ios-modal-close, .scroll-down {
|
a,
|
||||||
|
button,
|
||||||
|
.download-btn,
|
||||||
|
.privacy-back-btn,
|
||||||
|
.ios-install-option,
|
||||||
|
.ios-modal-close,
|
||||||
|
.scroll-down {
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
a:focus, a:active, button:focus, button:active,
|
|
||||||
.download-btn:focus, .download-btn:active,
|
a:focus,
|
||||||
.privacy-back-btn:focus, .privacy-back-btn:active,
|
a:active,
|
||||||
.ios-install-option:focus, .ios-install-option:active,
|
button:focus,
|
||||||
.ios-modal-close:focus, .ios-modal-close:active,
|
button:active,
|
||||||
.scroll-down:focus, .scroll-down:active {
|
.download-btn:focus,
|
||||||
|
.download-btn:active,
|
||||||
|
.privacy-back-btn:focus,
|
||||||
|
.privacy-back-btn:active,
|
||||||
|
.ios-install-option:focus,
|
||||||
|
.ios-install-option:active,
|
||||||
|
.ios-modal-close:focus,
|
||||||
|
.ios-modal-close:active,
|
||||||
|
.scroll-down:focus,
|
||||||
|
.scroll-down:active {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
@ -1540,12 +1609,15 @@
|
||||||
* {
|
* {
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
*:focus, *:active {
|
|
||||||
|
*:focus,
|
||||||
|
*:active {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<section class="hero">
|
<section class="hero">
|
||||||
<div class="hero-content">
|
<div class="hero-content">
|
||||||
|
|
@ -1567,7 +1639,8 @@
|
||||||
<a href="#features" class="scroll-down">
|
<a href="#features" class="scroll-down">
|
||||||
<div class="scroll-down-arrow">
|
<div class="scroll-down-arrow">
|
||||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M7 13L12 18L17 13" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M7 13L12 18L17 13" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<span class="scroll-down-text">Explore</span>
|
<span class="scroll-down-text">Explore</span>
|
||||||
|
|
@ -1584,9 +1657,21 @@
|
||||||
<p class="ios-modal-subtitle">Choose your preferred installation method</p>
|
<p class="ios-modal-subtitle">Choose your preferred installation method</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="ios-install-options">
|
<div class="ios-install-options">
|
||||||
<a href="https://github.com/tapframe/NuvioStreaming/releases" class="ios-install-option" id="direct-download">
|
<a href="https://testflight.apple.com/join/QkKMGRqp" class="ios-install-option" id="testflight-install">
|
||||||
<div class="ios-option-icon">
|
<div class="ios-option-icon">
|
||||||
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDJMMTMuMDkgOC4yNkwyMCA5TDEzLjA5IDE1Ljc0TDEyIDIyTDEwLjkxIDE1Ljc0TDQgOUwxMC45MSA4LjI2TDEyIDJaIiBmaWxsPSIjNjY2NjY2Ii8+Cjwvc3ZnPgo=" alt="Direct Download">
|
<img src="https://upload.wikimedia.org/wikipedia/fr/b/bc/TestFlight-icon.png" alt="TestFlight">
|
||||||
|
</div>
|
||||||
|
<div class="ios-option-content">
|
||||||
|
<div class="ios-option-title">TestFlight (Recommended)</div>
|
||||||
|
<div class="ios-option-description">Install via Apple's official beta testing platform</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<a href="https://github.com/tapframe/NuvioStreaming/releases" class="ios-install-option"
|
||||||
|
id="direct-download">
|
||||||
|
<div class="ios-option-icon">
|
||||||
|
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDJMMTMuMDkgOC4yNkwyMCA5TDEzLjA5IDE1Ljc0TDEyIDIyTDEwLjkxIDE1Ljc0TDQgOUwxMC45MSA4LjI2TDEyIDJaIiBmaWxsPSIjNjY2NjY2Ii8+Cjwvc3ZnPgo="
|
||||||
|
alt="Direct Download">
|
||||||
</div>
|
</div>
|
||||||
<div class="ios-option-content">
|
<div class="ios-option-content">
|
||||||
<div class="ios-option-title">Direct Download</div>
|
<div class="ios-option-title">Direct Download</div>
|
||||||
|
|
@ -1616,7 +1701,8 @@
|
||||||
|
|
||||||
<div class="ios-install-option" id="copy-url">
|
<div class="ios-install-option" id="copy-url">
|
||||||
<div class="ios-option-icon">
|
<div class="ios-option-icon">
|
||||||
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTggNUMzIDUgMyA1IDMgMTBWMTRDMyAxOSAzIDE5IDggMTlIMTZDMjEgMTkgMjEgMTkgMjEgMTRWMTBDMjEgNSAyMSA1IDE2IDVIOFoiIHN0cm9rZT0iIzY2NjY2NiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTEwIDEySDEwLjAxIiBzdHJva2U9IiM2NjY2NjYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0xNCAxMkgxNC4wMSIgc3Ryb2tlPSIjNjY2NjY2IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L3N2Zz4K" alt="Copy URL">
|
<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzIiIGhlaWdodD0iMzIiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTggNUMzIDUgMyA1IDMgMTBWMTRDMyAxOSAzIDE5IDggMTlIMTZDMjEgMTkgMjEgMTkgMjEgMTRWMTBDMjEgNSAyMSA1IDE2IDVIOFoiIHN0cm9rZT0iIzY2NjY2NiIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPHBhdGggZD0iTTEwIDEySDEwLjAxIiBzdHJva2U9IiM2NjY2NjYiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0xNCAxMkgxNC4wMSIgc3Ryb2tlPSIjNjY2NjY2IiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPgo8L3N2Zz4K"
|
||||||
|
alt="Copy URL">
|
||||||
</div>
|
</div>
|
||||||
<div class="ios-option-content">
|
<div class="ios-option-content">
|
||||||
<div class="ios-option-title">Copy Source URL</div>
|
<div class="ios-option-title">Copy Source URL</div>
|
||||||
|
|
@ -1639,56 +1725,70 @@
|
||||||
<div class="feature-card" data-aos="fade-up" data-aos-delay="100">
|
<div class="feature-card" data-aos="fade-up" data-aos-delay="100">
|
||||||
<div class="feature-icon">
|
<div class="feature-icon">
|
||||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M12 2L3 7L12 12L21 7L12 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M12 2L3 7L12 12L21 7L12 2Z" stroke="currentColor" stroke-width="2"
|
||||||
<path d="M3 17L12 22L21 17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
stroke-linecap="round" stroke-linejoin="round" />
|
||||||
<path d="M3 12L12 17L21 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M3 17L12 22L21 17" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
|
<path d="M3 12L12 17L21 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3>Stremio Addon Support</h3>
|
<h3>Stremio Addon Support</h3>
|
||||||
<p>Full compatibility with Stremio addons, allowing you to access your favorite content providers seamlessly.</p>
|
<p>Full compatibility with Stremio addons, allowing you to access your favorite content providers
|
||||||
|
seamlessly.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="feature-card" data-aos="fade-up" data-aos-delay="200">
|
<div class="feature-card" data-aos="fade-up" data-aos-delay="200">
|
||||||
<div class="feature-icon">
|
<div class="feature-icon">
|
||||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M12 2L13.09 8.26L22 9L17 14L18.18 23L12 19.77L5.82 23L7 14L2 9L10.91 8.26L12 2Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M12 2L13.09 8.26L22 9L17 14L18.18 23L12 19.77L5.82 23L7 14L2 9L10.91 8.26L12 2Z"
|
||||||
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3>Advanced Rating System</h3>
|
<h3>Advanced Rating System</h3>
|
||||||
<p>Comprehensive rating screens with IMDB, TMDB, Rotten Tomatoes, and Metacritic scores for informed viewing decisions.</p>
|
<p>Comprehensive rating screens with IMDB, TMDB, Rotten Tomatoes, and Metacritic scores for informed
|
||||||
|
viewing decisions.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="feature-card" data-aos="fade-up" data-aos-delay="300">
|
<div class="feature-card" data-aos="fade-up" data-aos-delay="300">
|
||||||
<div class="feature-icon">
|
<div class="feature-icon">
|
||||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M12 1L3 5V11C3 16.55 6.84 21.74 12 23C17.16 21.74 21 16.55 21 11V5L12 1Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M12 1L3 5V11C3 16.55 6.84 21.74 12 23C17.16 21.74 21 16.55 21 11V5L12 1Z"
|
||||||
<path d="M9 12L11 14L15 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||||
|
<path d="M9 12L11 14L15 10" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3>Deep Customization</h3>
|
<h3>Deep Customization</h3>
|
||||||
<p>Extensive customization options including themes, player settings, notification preferences, and personalized content discovery.</p>
|
<p>Extensive customization options including themes, player settings, notification preferences, and
|
||||||
|
personalized content discovery.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="feature-card" data-aos="fade-up" data-aos-delay="400">
|
<div class="feature-card" data-aos="fade-up" data-aos-delay="400">
|
||||||
<div class="feature-icon">
|
<div class="feature-icon">
|
||||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M9 12L11 14L15 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M9 12L11 14L15 10" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2"/>
|
stroke-linejoin="round" />
|
||||||
|
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3>Watch Progress Tracking</h3>
|
<h3>Watch Progress Tracking</h3>
|
||||||
<p>Seamless progress synchronization across devices with Trakt.tv integration and local watch history management.</p>
|
<p>Seamless progress synchronization across devices with Trakt.tv integration and local watch
|
||||||
|
history management.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="feature-card" data-aos="fade-up" data-aos-delay="500">
|
<div class="feature-card" data-aos="fade-up" data-aos-delay="500">
|
||||||
<div class="feature-icon">
|
<div class="feature-icon">
|
||||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="M21 16V8A2 2 0 0 0 19 6H5A2 2 0 0 0 3 8V16A2 2 0 0 0 5 18H19A2 2 0 0 0 21 16Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M21 16V8A2 2 0 0 0 19 6H5A2 2 0 0 0 3 8V16A2 2 0 0 0 5 18H19A2 2 0 0 0 21 16Z"
|
||||||
<polygon points="7 10 12 15 17 10" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||||
|
<polygon points="7 10 12 15 17 10" stroke="currentColor" stroke-width="2"
|
||||||
|
stroke-linecap="round" stroke-linejoin="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<h3>Multi-Platform Support</h3>
|
<h3>Multi-Platform Support</h3>
|
||||||
<p>Available on iOS and Android platforms with consistent experience and cross-device synchronization</p>
|
<p>Available on iOS and Android platforms with consistent experience and cross-device
|
||||||
|
synchronization</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1699,27 +1799,33 @@
|
||||||
<h2>SEE IT IN ACTION</h2>
|
<h2>SEE IT IN ACTION</h2>
|
||||||
<div class="screenshots-grid">
|
<div class="screenshots-grid">
|
||||||
<div class="screenshot">
|
<div class="screenshot">
|
||||||
<img src="screenshots/Simulator Screenshot - iPhone 16 Pro - 2025-08-27 at 21.08.32-portrait.png" alt="Home Screen" loading="lazy">
|
<img src="screenshots/Simulator Screenshot - iPhone 16 Pro - 2025-08-27 at 21.08.32-portrait.png"
|
||||||
|
alt="Home Screen" loading="lazy">
|
||||||
<h4>Home Screen</h4>
|
<h4>Home Screen</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="screenshot">
|
<div class="screenshot">
|
||||||
<img src="screenshots/WhatsApp Image 2025-09-02 at 00.24.31-portrait.png" alt="App Interface" loading="lazy">
|
<img src="screenshots/WhatsApp Image 2025-09-02 at 00.24.31-portrait.png" alt="App Interface"
|
||||||
|
loading="lazy">
|
||||||
<h4>Details Page</h4>
|
<h4>Details Page</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="screenshot">
|
<div class="screenshot">
|
||||||
<img src="screenshots/Simulator Screenshot - iPhone 16 Pro - 2025-08-27 at 21.09.43-portrait.png" alt="Home Screen 2" loading="lazy">
|
<img src="screenshots/Simulator Screenshot - iPhone 16 Pro - 2025-08-27 at 21.09.43-portrait.png"
|
||||||
|
alt="Home Screen 2" loading="lazy">
|
||||||
<h4>Home Screen 2</h4>
|
<h4>Home Screen 2</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="screenshot">
|
<div class="screenshot">
|
||||||
<img src="screenshots/Simulator Screenshot - iPhone 16 Pro - 2025-08-27 at 21.10.14-portrait.png" alt="Library" loading="lazy">
|
<img src="screenshots/Simulator Screenshot - iPhone 16 Pro - 2025-08-27 at 21.10.14-portrait.png"
|
||||||
|
alt="Library" loading="lazy">
|
||||||
<h4>Library</h4>
|
<h4>Library</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="screenshot">
|
<div class="screenshot">
|
||||||
<img src="screenshots/Simulator Screenshot - iPhone 16 Pro - 2025-08-27 at 21.12.41-landscape.png" alt="Player Loading" loading="lazy">
|
<img src="screenshots/Simulator Screenshot - iPhone 16 Pro - 2025-08-27 at 21.12.41-landscape.png"
|
||||||
|
alt="Player Loading" loading="lazy">
|
||||||
<h4>Player Loading</h4>
|
<h4>Player Loading</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="screenshot">
|
<div class="screenshot">
|
||||||
<img src="screenshots/Simulator Screenshot - iPhone 16 Pro - 2025-08-27 at 21.13.36-landscape.png" alt="Video Player" loading="lazy">
|
<img src="screenshots/Simulator Screenshot - iPhone 16 Pro - 2025-08-27 at 21.13.36-landscape.png"
|
||||||
|
alt="Video Player" loading="lazy">
|
||||||
<h4>Video Player</h4>
|
<h4>Video Player</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="screenshot">
|
<div class="screenshot">
|
||||||
|
|
@ -1745,7 +1851,7 @@
|
||||||
<div class="privacy-content">
|
<div class="privacy-content">
|
||||||
<button class="privacy-back-btn" onclick="hidePrivacyPolicy()">
|
<button class="privacy-back-btn" onclick="hidePrivacyPolicy()">
|
||||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||||
<path d="M19 12H5M12 19l-7-7 7-7"/>
|
<path d="M19 12H5M12 19l-7-7 7-7" />
|
||||||
</svg>
|
</svg>
|
||||||
Back
|
Back
|
||||||
</button>
|
</button>
|
||||||
|
|
@ -1754,7 +1860,9 @@
|
||||||
|
|
||||||
<div class="privacy-section">
|
<div class="privacy-section">
|
||||||
<h2>No Account Sync</h2>
|
<h2>No Account Sync</h2>
|
||||||
<p>Nuvio operates entirely offline regarding user data. We <strong>do not</strong> have servers to store your account, preferences, or viewing history. All data is stored locally on your device.</p>
|
<p>Nuvio operates entirely offline regarding user data. We <strong>do not</strong> have servers to
|
||||||
|
store your account, preferences, or viewing history. All data is stored locally on your device.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="privacy-section">
|
<div class="privacy-section">
|
||||||
|
|
@ -1765,32 +1873,43 @@
|
||||||
<li>Watch history and progress</li>
|
<li>Watch history and progress</li>
|
||||||
<li>App settings and customization</li>
|
<li>App settings and customization</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p><strong>Important:</strong> Since data is stored only on your device, you are responsible for backing it up. If you uninstall the app or clear its data without a backup, your personalized data will be lost permanently.</p>
|
<p><strong>Important:</strong> Since data is stored only on your device, you are responsible for
|
||||||
|
backing it up. If you uninstall the app or clear its data without a backup, your personalized
|
||||||
|
data will be lost permanently.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="privacy-section">
|
<div class="privacy-section">
|
||||||
<h2>Third-Party Services</h2>
|
<h2>Third-Party Services</h2>
|
||||||
<p>Nuvio integrates with external services to provide content and features:</p>
|
<p>Nuvio integrates with external services to provide content and features:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>TMDB (The Movie Database):</strong> Used to fetch metadata like posters, plot summaries, and cast info.</li>
|
<li><strong>TMDB (The Movie Database):</strong> Used to fetch metadata like posters, plot
|
||||||
<li><strong>Trakt.tv (Optional):</strong> If you choose to connect your account, your watch history will be synced with Trakt.tv subject to their privacy policy.</li>
|
summaries, and cast info.</li>
|
||||||
<li><strong>Sentry:</strong> We use Sentry for anonymous crash reporting to help us identify and fix bugs. No personal identifiable information (PII) is sent.</li>
|
<li><strong>Trakt.tv (Optional):</strong> If you choose to connect your account, your watch
|
||||||
|
history will be synced with Trakt.tv subject to their privacy policy.</li>
|
||||||
|
<li><strong>Sentry:</strong> We use Sentry for anonymous crash reporting to help us identify and
|
||||||
|
fix bugs. No personal identifiable information (PII) is sent.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="privacy-section">
|
<div class="privacy-section">
|
||||||
<h2>Content Disclaimer</h2>
|
<h2>Content Disclaimer</h2>
|
||||||
<p>Nuvio is a media player and aggregator. We <strong>do not host</strong> any content. All video content is provided by user-installed addons. Nuvio has no control over and assumes no responsibility for the content provided by third-party addons.</p>
|
<p>Nuvio is a media player and aggregator. We <strong>do not host</strong> any content. All video
|
||||||
|
content is provided by user-installed addons. Nuvio has no control over and assumes no
|
||||||
|
responsibility for the content provided by third-party addons.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="privacy-section">
|
<div class="privacy-section">
|
||||||
<h2>Open Source</h2>
|
<h2>Open Source</h2>
|
||||||
<p>Nuvio is open-source software. You can review our source code to verify our data handling practices on our <a href="https://github.com/tapframe/NuvioStreaming" target="_blank">GitHub repository</a>.</p>
|
<p>Nuvio is open-source software. You can review our source code to verify our data handling
|
||||||
|
practices on our <a href="https://github.com/tapframe/NuvioStreaming" target="_blank">GitHub
|
||||||
|
repository</a>.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="privacy-section">
|
<div class="privacy-section">
|
||||||
<h2>Contact</h2>
|
<h2>Contact</h2>
|
||||||
<p>Questions or concerns? Please reach out via our <a href="https://github.com/tapframe/NuvioStreaming/issues" target="_blank">GitHub Issues</a>.</p>
|
<p>Questions or concerns? Please reach out via our <a
|
||||||
|
href="https://github.com/tapframe/NuvioStreaming/issues" target="_blank">GitHub Issues</a>.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -1802,16 +1921,20 @@
|
||||||
<h3>Special Thanks</h3>
|
<h3>Special Thanks</h3>
|
||||||
<div class="credits-grid">
|
<div class="credits-grid">
|
||||||
<div class="credit-item">
|
<div class="credit-item">
|
||||||
<img src="https://www.themoviedb.org/assets/2/v4/logos/v2/blue_square_2-d537fb228cf3ded904ef09b136fe3fec72548ebc1fea3fbbd1ad9e36364db38b.svg" alt="TMDB" class="credit-logo">
|
<img src="https://www.themoviedb.org/assets/2/v4/logos/v2/blue_square_2-d537fb228cf3ded904ef09b136fe3fec72548ebc1fea3fbbd1ad9e36364db38b.svg"
|
||||||
|
alt="TMDB" class="credit-logo">
|
||||||
</div>
|
</div>
|
||||||
<div class="credit-item">
|
<div class="credit-item">
|
||||||
<div class="stremio-logos">
|
<div class="stremio-logos">
|
||||||
<img src="https://www.stremio.com/website/stremio-logo-small.png" alt="Stremio" class="credit-logo">
|
<img src="https://www.stremio.com/website/stremio-logo-small.png" alt="Stremio"
|
||||||
<img src="https://www.stremio.com/website/stremio-txt-logo-small.png" alt="Stremio" class="credit-logo">
|
class="credit-logo">
|
||||||
|
<img src="https://www.stremio.com/website/stremio-txt-logo-small.png" alt="Stremio"
|
||||||
|
class="credit-logo">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="credit-item">
|
<div class="credit-item">
|
||||||
<img src="https://upload.wikimedia.org/wikipedia/commons/6/69/IMDB_Logo_2016.svg" alt="IMDb" class="credit-logo">
|
<img src="https://upload.wikimedia.org/wikipedia/commons/6/69/IMDB_Logo_2016.svg" alt="IMDb"
|
||||||
|
class="credit-logo">
|
||||||
</div>
|
</div>
|
||||||
<div class="credit-item">
|
<div class="credit-item">
|
||||||
<img src="https://mdblist.com/static/mdblist_logo.png" alt="MDBList" class="credit-logo">
|
<img src="https://mdblist.com/static/mdblist_logo.png" alt="MDBList" class="credit-logo">
|
||||||
|
|
@ -1821,11 +1944,13 @@
|
||||||
<p>Built with ❤️ using React Native & Expo</p>
|
<p>Built with ❤️ using React Native & Expo</p>
|
||||||
<div class="footer-links">
|
<div class="footer-links">
|
||||||
<a href="https://discord.gg/6w8dr3TSDN" class="github-link" target="_blank">
|
<a href="https://discord.gg/6w8dr3TSDN" class="github-link" target="_blank">
|
||||||
<img src="https://cdn.prod.website-files.com/6257adef93867e50d84d30e2/66e3d7f4ef6498ac018f2c55_Symbol.svg" alt="Discord" class="github-logo">
|
<img src="https://cdn.prod.website-files.com/6257adef93867e50d84d30e2/66e3d7f4ef6498ac018f2c55_Symbol.svg"
|
||||||
|
alt="Discord" class="github-logo">
|
||||||
Discord
|
Discord
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/tapframe/NuvioStreaming" class="github-link">
|
<a href="https://github.com/tapframe/NuvioStreaming" class="github-link">
|
||||||
<img src="https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png" alt="GitHub" class="github-logo">
|
<img src="https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png" alt="GitHub"
|
||||||
|
class="github-logo">
|
||||||
GitHub
|
GitHub
|
||||||
</a>
|
</a>
|
||||||
<a href="#privacy-policy" class="privacy-link" onclick="showPrivacyPolicy()">
|
<a href="#privacy-policy" class="privacy-link" onclick="showPrivacyPolicy()">
|
||||||
|
|
@ -1881,11 +2006,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add click event listeners to all screenshots
|
// Add click event listeners to all screenshots
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
const screenshots = document.querySelectorAll('.screenshot');
|
const screenshots = document.querySelectorAll('.screenshot');
|
||||||
|
|
||||||
screenshots.forEach(screenshot => {
|
screenshots.forEach(screenshot => {
|
||||||
screenshot.addEventListener('click', function() {
|
screenshot.addEventListener('click', function () {
|
||||||
const img = this.querySelector('img');
|
const img = this.querySelector('img');
|
||||||
const title = this.querySelector('h4');
|
const title = this.querySelector('h4');
|
||||||
|
|
||||||
|
|
@ -1896,14 +2021,14 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close modal when clicking outside the image
|
// Close modal when clicking outside the image
|
||||||
document.getElementById('screenshotModal').addEventListener('click', function(e) {
|
document.getElementById('screenshotModal').addEventListener('click', function (e) {
|
||||||
if (e.target === this) {
|
if (e.target === this) {
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close modal with Escape key
|
// Close modal with Escape key
|
||||||
document.addEventListener('keydown', function(e) {
|
document.addEventListener('keydown', function (e) {
|
||||||
if (e.key === 'Escape') {
|
if (e.key === 'Escape') {
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
|
|
@ -1912,7 +2037,7 @@
|
||||||
</script>
|
</script>
|
||||||
<script>
|
<script>
|
||||||
// iOS Installation Modal functionality
|
// iOS Installation Modal functionality
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
const iosInstallBtn = document.getElementById('ios-install-btn');
|
const iosInstallBtn = document.getElementById('ios-install-btn');
|
||||||
const iosModal = document.getElementById('ios-modal');
|
const iosModal = document.getElementById('ios-modal');
|
||||||
const iosModalClose = document.getElementById('ios-modal-close');
|
const iosModalClose = document.getElementById('ios-modal-close');
|
||||||
|
|
@ -1923,7 +2048,7 @@
|
||||||
const sourceUrl = 'https://raw.githubusercontent.com/tapframe/NuvioStreaming/main/nuvio-source.json';
|
const sourceUrl = 'https://raw.githubusercontent.com/tapframe/NuvioStreaming/main/nuvio-source.json';
|
||||||
|
|
||||||
// Open modal
|
// Open modal
|
||||||
iosInstallBtn.addEventListener('click', function(e) {
|
iosInstallBtn.addEventListener('click', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
iosModal.classList.add('active');
|
iosModal.classList.add('active');
|
||||||
document.body.style.overflow = 'hidden';
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
@ -1938,28 +2063,28 @@
|
||||||
iosModalClose.addEventListener('click', closeModal);
|
iosModalClose.addEventListener('click', closeModal);
|
||||||
|
|
||||||
// Close modal when clicking outside
|
// Close modal when clicking outside
|
||||||
iosModal.addEventListener('click', function(e) {
|
iosModal.addEventListener('click', function (e) {
|
||||||
if (e.target === iosModal) {
|
if (e.target === iosModal) {
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close modal with Escape key
|
// Close modal with Escape key
|
||||||
document.addEventListener('keydown', function(e) {
|
document.addEventListener('keydown', function (e) {
|
||||||
if (e.key === 'Escape' && iosModal.classList.contains('active')) {
|
if (e.key === 'Escape' && iosModal.classList.contains('active')) {
|
||||||
closeModal();
|
closeModal();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Copy URL functionality
|
// Copy URL functionality
|
||||||
copyUrlBtn.addEventListener('click', function(e) {
|
copyUrlBtn.addEventListener('click', function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
|
||||||
// Try to copy to clipboard
|
// Try to copy to clipboard
|
||||||
if (navigator.clipboard && window.isSecureContext) {
|
if (navigator.clipboard && window.isSecureContext) {
|
||||||
navigator.clipboard.writeText(sourceUrl).then(function() {
|
navigator.clipboard.writeText(sourceUrl).then(function () {
|
||||||
showCopyFeedback();
|
showCopyFeedback();
|
||||||
}).catch(function() {
|
}).catch(function () {
|
||||||
fallbackCopyTextToClipboard(sourceUrl);
|
fallbackCopyTextToClipboard(sourceUrl);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -1993,7 +2118,7 @@
|
||||||
// Show copy feedback
|
// Show copy feedback
|
||||||
function showCopyFeedback() {
|
function showCopyFeedback() {
|
||||||
copyFeedback.classList.add('show');
|
copyFeedback.classList.add('show');
|
||||||
setTimeout(function() {
|
setTimeout(function () {
|
||||||
copyFeedback.classList.remove('show');
|
copyFeedback.classList.remove('show');
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
@ -2046,7 +2171,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle browser back button
|
// Handle browser back button
|
||||||
window.addEventListener('popstate', function(event) {
|
window.addEventListener('popstate', function (event) {
|
||||||
if (window.location.hash === '#privacy-policy') {
|
if (window.location.hash === '#privacy-policy') {
|
||||||
showPrivacyPolicy();
|
showPrivacyPolicy();
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2055,11 +2180,12 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if privacy policy should be shown on page load
|
// Check if privacy policy should be shown on page load
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
if (window.location.hash === '#privacy-policy') {
|
if (window.location.hash === '#privacy-policy') {
|
||||||
showPrivacyPolicy();
|
showPrivacyPolicy();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -477,7 +477,7 @@
|
||||||
);
|
);
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_DEBUG";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.app;
|
||||||
PRODUCT_NAME = "Nuvio";
|
PRODUCT_NAME = Nuvio;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
|
|
@ -494,7 +494,7 @@
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = Nuvio/NuvioRelease.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Nuvio/NuvioRelease.entitlements;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_TEAM = NLXTHANK2N;
|
DEVELOPMENT_TEAM = 8QBDZ766S3;
|
||||||
INFOPLIST_FILE = Nuvio/Info.plist;
|
INFOPLIST_FILE = Nuvio/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
IPHONEOS_DEPLOYMENT_TARGET = 15.1;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
|
@ -508,8 +508,8 @@
|
||||||
"-lc++",
|
"-lc++",
|
||||||
);
|
);
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
OTHER_SWIFT_FLAGS = "$(inherited) -D EXPO_CONFIGURATION_RELEASE";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "com.nuvio.app";
|
PRODUCT_BUNDLE_IDENTIFIER = com.nuvio.hub;
|
||||||
PRODUCT_NAME = "Nuvio";
|
PRODUCT_NAME = Nuvio;
|
||||||
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
SWIFT_OBJC_BRIDGING_HEADER = "Nuvio/Nuvio-Bridging-Header.h";
|
||||||
SWIFT_VERSION = 5.0;
|
SWIFT_VERSION = 5.0;
|
||||||
TARGETED_DEVICE_FAMILY = "1,2";
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>CFBundleDevelopmentRegion</key>
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
|
@ -57,10 +57,6 @@
|
||||||
<string>_googlecast._tcp</string>
|
<string>_googlecast._tcp</string>
|
||||||
<string>_CC1AD845._googlecast._tcp</string>
|
<string>_CC1AD845._googlecast._tcp</string>
|
||||||
</array>
|
</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>
|
<key>RCTNewArchEnabled</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>RCTRootViewBackgroundColor</key>
|
<key>RCTRootViewBackgroundColor</key>
|
||||||
|
|
@ -99,5 +95,5 @@
|
||||||
<string>Dark</string>
|
<string>Dark</string>
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||||
<false/>
|
<false/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?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">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
<key>aps-environment</key>
|
<key>aps-environment</key>
|
||||||
<string>development</string>
|
<string>development</string>
|
||||||
<key>com.apple.developer.associated-domains</key>
|
</dict>
|
||||||
<array/>
|
|
||||||
</dict>
|
|
||||||
</plist>
|
</plist>
|
||||||
|
|
@ -240,6 +240,44 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Helper function to find the next episode
|
||||||
|
const findNextEpisode = useCallback((currentSeason: number, currentEpisode: number, videos: any[]) => {
|
||||||
|
if (!videos || !Array.isArray(videos)) return null;
|
||||||
|
|
||||||
|
// Sort videos to ensure correct order
|
||||||
|
const sortedVideos = [...videos].sort((a, b) => {
|
||||||
|
if (a.season !== b.season) return a.season - b.season;
|
||||||
|
return a.episode - b.episode;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Strategy 1: Look for next episode in the same season
|
||||||
|
let nextEp = sortedVideos.find(v => v.season === currentSeason && v.episode === currentEpisode + 1);
|
||||||
|
|
||||||
|
// Strategy 2: If not found, look for the first episode of the next season
|
||||||
|
if (!nextEp) {
|
||||||
|
nextEp = sortedVideos.find(v => v.season === currentSeason + 1 && v.episode === 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strategy 3: Just find the very next video in the list after the current one
|
||||||
|
// This handles cases where episode numbering isn't sequential or S+1 E1 isn't the standard start
|
||||||
|
if (!nextEp) {
|
||||||
|
const currentIndex = sortedVideos.findIndex(v => v.season === currentSeason && v.episode === currentEpisode);
|
||||||
|
if (currentIndex !== -1 && currentIndex + 1 < sortedVideos.length) {
|
||||||
|
const candidate = sortedVideos[currentIndex + 1];
|
||||||
|
// Ensure we didn't just jump to a random special; check reasonable bounds if needed,
|
||||||
|
// but generally taking the next sorted item is correct for sequential viewing.
|
||||||
|
nextEp = candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the found episode is released
|
||||||
|
if (nextEp && isEpisodeReleased(nextEp)) {
|
||||||
|
return nextEp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Modified loadContinueWatching to render incrementally
|
// Modified loadContinueWatching to render incrementally
|
||||||
const loadContinueWatching = useCallback(async (isBackgroundRefresh = false) => {
|
const loadContinueWatching = useCallback(async (isBackgroundRefresh = false) => {
|
||||||
if (isRefreshingRef.current) {
|
if (isRefreshingRef.current) {
|
||||||
|
|
@ -432,44 +470,44 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
||||||
const { episodeId, progress, progressPercent } = episode;
|
const { episodeId, progress, progressPercent } = episode;
|
||||||
|
|
||||||
if (group.type === 'series' && progressPercent >= 85) {
|
if (group.type === 'series' && progressPercent >= 85) {
|
||||||
let nextSeason: number | undefined;
|
// Local progress completion check
|
||||||
let nextEpisode: number | undefined;
|
|
||||||
if (episodeId) {
|
if (episodeId) {
|
||||||
|
let currentSeason: number | undefined;
|
||||||
|
let currentEpisode: number | undefined;
|
||||||
|
|
||||||
const match = episodeId.match(/s(\d+)e(\d+)/i);
|
const match = episodeId.match(/s(\d+)e(\d+)/i);
|
||||||
if (match) {
|
if (match) {
|
||||||
const currentSeason = parseInt(match[1], 10);
|
currentSeason = parseInt(match[1], 10);
|
||||||
const currentEpisode = parseInt(match[2], 10);
|
currentEpisode = parseInt(match[2], 10);
|
||||||
nextSeason = currentSeason;
|
|
||||||
nextEpisode = currentEpisode + 1;
|
|
||||||
} else {
|
} else {
|
||||||
const parts = episodeId.split(':');
|
const parts = episodeId.split(':');
|
||||||
if (parts.length >= 2) {
|
if (parts.length >= 2) {
|
||||||
const seasonNum = parseInt(parts[parts.length - 2], 10);
|
const seasonNum = parseInt(parts[parts.length - 2], 10);
|
||||||
const episodeNum = parseInt(parts[parts.length - 1], 10);
|
const episodeNum = parseInt(parts[parts.length - 1], 10);
|
||||||
if (!isNaN(seasonNum) && !isNaN(episodeNum)) {
|
if (!isNaN(seasonNum) && !isNaN(episodeNum)) {
|
||||||
nextSeason = seasonNum;
|
currentSeason = seasonNum;
|
||||||
nextEpisode = episodeNum + 1;
|
currentEpisode = episodeNum;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (nextSeason !== undefined && nextEpisode !== undefined && metadata?.videos && Array.isArray(metadata.videos)) {
|
if (currentSeason !== undefined && currentEpisode !== undefined && metadata?.videos) {
|
||||||
const nextEpisodeVideo = metadata.videos.find((video: any) =>
|
const nextEpisodeVideo = findNextEpisode(currentSeason, currentEpisode, metadata.videos);
|
||||||
video.season === nextSeason && video.episode === nextEpisode
|
|
||||||
);
|
if (nextEpisodeVideo) {
|
||||||
if (nextEpisodeVideo && isEpisodeReleased(nextEpisodeVideo)) {
|
|
||||||
batch.push({
|
batch.push({
|
||||||
...basicContent,
|
...basicContent,
|
||||||
id: group.id,
|
id: group.id,
|
||||||
type: group.type,
|
type: group.type,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
lastUpdated: progress.lastUpdated,
|
lastUpdated: progress.lastUpdated,
|
||||||
season: nextSeason,
|
season: nextEpisodeVideo.season,
|
||||||
episode: nextEpisode,
|
episode: nextEpisodeVideo.episode,
|
||||||
episodeTitle: `Episode ${nextEpisode}`,
|
episodeTitle: `Episode ${nextEpisodeVideo.episode}`,
|
||||||
} as ContinueWatchingItem);
|
} as ContinueWatchingItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -532,23 +570,18 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
||||||
|
|
||||||
// If watched on Trakt, treat it as completed (try to find next episode)
|
// If watched on Trakt, treat it as completed (try to find next episode)
|
||||||
if (isWatchedOnTrakt) {
|
if (isWatchedOnTrakt) {
|
||||||
let nextSeason = season;
|
if (season !== undefined && episodeNumber !== undefined && metadata?.videos) {
|
||||||
let nextEpisode = (episodeNumber || 0) + 1;
|
const nextEpisodeVideo = findNextEpisode(season, episodeNumber, metadata.videos);
|
||||||
|
if (nextEpisodeVideo) {
|
||||||
if (nextSeason !== undefined && nextEpisode !== undefined && metadata?.videos && Array.isArray(metadata.videos)) {
|
|
||||||
const nextEpisodeVideo = metadata.videos.find((video: any) =>
|
|
||||||
video.season === nextSeason && video.episode === nextEpisode
|
|
||||||
);
|
|
||||||
if (nextEpisodeVideo && isEpisodeReleased(nextEpisodeVideo)) {
|
|
||||||
batch.push({
|
batch.push({
|
||||||
...basicContent,
|
...basicContent,
|
||||||
id: group.id,
|
id: group.id,
|
||||||
type: group.type,
|
type: group.type,
|
||||||
progress: 0,
|
progress: 0,
|
||||||
lastUpdated: progress.lastUpdated,
|
lastUpdated: progress.lastUpdated,
|
||||||
season: nextSeason,
|
season: nextEpisodeVideo.season,
|
||||||
episode: nextEpisode,
|
episode: nextEpisodeVideo.episode,
|
||||||
episodeTitle: `Episode ${nextEpisode}`,
|
episodeTitle: `Episode ${nextEpisodeVideo.episode}`,
|
||||||
} as ContinueWatchingItem);
|
} as ContinueWatchingItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -614,29 +647,26 @@ const ContinueWatchingSection = React.forwardRef<ContinueWatchingRef>((props, re
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextEpisode = info.episode + 1;
|
|
||||||
const cachedData = await getCachedMetadata('series', showId);
|
const cachedData = await getCachedMetadata('series', showId);
|
||||||
if (!cachedData?.basicContent) continue;
|
if (!cachedData?.basicContent) continue;
|
||||||
const { metadata, basicContent } = cachedData;
|
const { metadata, basicContent } = cachedData;
|
||||||
let nextEpisodeVideo = null;
|
|
||||||
if (metadata?.videos && Array.isArray(metadata.videos)) {
|
if (metadata?.videos) {
|
||||||
nextEpisodeVideo = metadata.videos.find((video: any) =>
|
const nextEpisodeVideo = findNextEpisode(info.season, info.episode, metadata.videos);
|
||||||
video.season === info.season && video.episode === nextEpisode
|
if (nextEpisodeVideo) {
|
||||||
);
|
logger.log(`➕ [TraktSync] Adding next episode for ${showId}: S${nextEpisodeVideo.season}E${nextEpisodeVideo.episode}`);
|
||||||
}
|
|
||||||
if (nextEpisodeVideo && isEpisodeReleased(nextEpisodeVideo)) {
|
|
||||||
logger.log(`➕ [TraktSync] Adding next episode for ${showId}: S${info.season}E${nextEpisode}`);
|
|
||||||
traktBatch.push({
|
traktBatch.push({
|
||||||
...basicContent,
|
...basicContent,
|
||||||
id: showId,
|
id: showId,
|
||||||
type: 'series',
|
type: 'series',
|
||||||
progress: 0,
|
progress: 0,
|
||||||
lastUpdated: info.watchedAt,
|
lastUpdated: info.watchedAt,
|
||||||
season: info.season,
|
season: nextEpisodeVideo.season,
|
||||||
episode: nextEpisode,
|
episode: nextEpisodeVideo.episode,
|
||||||
episodeTitle: `Episode ${nextEpisode}`,
|
episodeTitle: `Episode ${nextEpisodeVideo.episode}`,
|
||||||
} as ContinueWatchingItem);
|
} as ContinueWatchingItem);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Persist "watched" progress for the episode that Trakt reported (only if not recently removed)
|
// Persist "watched" progress for the episode that Trakt reported (only if not recently removed)
|
||||||
if (!recentlyRemovedRef.current.has(showKey)) {
|
if (!recentlyRemovedRef.current.has(showKey)) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue