mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-04-21 00:32:04 +00:00
UI changes
This commit is contained in:
parent
d55143e6fb
commit
e435a68aea
4 changed files with 83 additions and 28 deletions
1
enginefs
Submodule
1
enginefs
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 3a70b36f873307cd83fb3178bb891f73cf73aa87
|
||||||
|
|
@ -672,7 +672,7 @@ const MainTabs = () => {
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
height: 85,
|
height: 85 + insets.bottom,
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
}}>
|
}}>
|
||||||
|
|
@ -722,7 +722,7 @@ const MainTabs = () => {
|
||||||
<View
|
<View
|
||||||
style={{
|
style={{
|
||||||
height: '100%',
|
height: '100%',
|
||||||
paddingBottom: 20,
|
paddingBottom: 20 + insets.bottom,
|
||||||
paddingTop: 12,
|
paddingTop: 12,
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
}}
|
}}
|
||||||
|
|
@ -819,6 +819,7 @@ const MainTabs = () => {
|
||||||
// Dynamically require to avoid impacting Android bundle
|
// Dynamically require to avoid impacting Android bundle
|
||||||
const { createNativeBottomTabNavigator } = require('@bottom-tabs/react-navigation');
|
const { createNativeBottomTabNavigator } = require('@bottom-tabs/react-navigation');
|
||||||
const IOSTab = createNativeBottomTabNavigator();
|
const IOSTab = createNativeBottomTabNavigator();
|
||||||
|
const downloadsEnabled = appSettings?.enableDownloads !== false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1, backgroundColor: currentTheme.colors.darkBackground }}>
|
<View style={{ flex: 1, backgroundColor: currentTheme.colors.darkBackground }}>
|
||||||
|
|
@ -828,6 +829,8 @@ const MainTabs = () => {
|
||||||
backgroundColor="transparent"
|
backgroundColor="transparent"
|
||||||
/>
|
/>
|
||||||
<IOSTab.Navigator
|
<IOSTab.Navigator
|
||||||
|
key={`ios-tabs-${downloadsEnabled ? 'with-dl' : 'no-dl'}`}
|
||||||
|
initialRouteName="Home"
|
||||||
// Native tab bar handles its own visuals; keep options minimal
|
// Native tab bar handles its own visuals; keep options minimal
|
||||||
screenOptions={{
|
screenOptions={{
|
||||||
headerShown: false,
|
headerShown: false,
|
||||||
|
|
@ -863,7 +866,7 @@ const MainTabs = () => {
|
||||||
tabBarIcon: () => ({ sfSymbol: 'magnifyingglass' }),
|
tabBarIcon: () => ({ sfSymbol: 'magnifyingglass' }),
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{appSettings?.enableDownloads !== false && (
|
{downloadsEnabled && (
|
||||||
<IOSTab.Screen
|
<IOSTab.Screen
|
||||||
name="Downloads"
|
name="Downloads"
|
||||||
component={DownloadsScreen}
|
component={DownloadsScreen}
|
||||||
|
|
|
||||||
|
|
@ -409,17 +409,7 @@ const StreamCard = memo(({ stream, onPress, index, isLoading, statusMessage, the
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<TouchableOpacity
|
|
||||||
style={styles.streamAction}
|
|
||||||
onPress={() => onPress()}
|
|
||||||
activeOpacity={0.7}
|
|
||||||
>
|
|
||||||
<MaterialIcons
|
|
||||||
name="play-arrow"
|
|
||||||
size={22}
|
|
||||||
color={theme.colors.white}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
{settings?.enableDownloads !== false && (
|
{settings?.enableDownloads !== false && (
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.streamAction, { marginLeft: 8, backgroundColor: theme.colors.elevation2 }]}
|
style={[styles.streamAction, { marginLeft: 8, backgroundColor: theme.colors.elevation2 }]}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,16 @@ export interface TrailerData {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TrailerService {
|
export class TrailerService {
|
||||||
private static readonly XPRIME_URL = 'https://db.xprime.tv/trailers';
|
// Environment-configurable values (Expo public env)
|
||||||
private static readonly LOCAL_SERVER_URL = 'http://192.168.1.11:3001/trailer';
|
private static readonly ENV_LOCAL_BASE = process.env.EXPO_PUBLIC_TRAILER_LOCAL_BASE || 'http://46.62.173.157:3001';
|
||||||
private static readonly AUTO_SEARCH_URL = 'http://192.168.1.11:3001/search-trailer';
|
private static readonly ENV_LOCAL_TRAILER_PATH = process.env.EXPO_PUBLIC_TRAILER_LOCAL_TRAILER_PATH || '/trailer';
|
||||||
private static readonly TIMEOUT = 10000; // 10 seconds
|
private static readonly ENV_LOCAL_SEARCH_PATH = process.env.EXPO_PUBLIC_TRAILER_LOCAL_SEARCH_PATH || '/search-trailer';
|
||||||
|
private static readonly ENV_XPRIME_URL = process.env.EXPO_PUBLIC_XPRIME_URL || 'https://db.xprime.tv/trailers';
|
||||||
|
|
||||||
|
private static readonly XPRIME_URL = TrailerService.ENV_XPRIME_URL;
|
||||||
|
private static readonly LOCAL_SERVER_URL = `${TrailerService.ENV_LOCAL_BASE}${TrailerService.ENV_LOCAL_TRAILER_PATH}`;
|
||||||
|
private static readonly AUTO_SEARCH_URL = `${TrailerService.ENV_LOCAL_BASE}${TrailerService.ENV_LOCAL_SEARCH_PATH}`;
|
||||||
|
private static readonly TIMEOUT = 20000; // 20 seconds
|
||||||
private static readonly USE_LOCAL_SERVER = true; // Toggle between local and XPrime
|
private static readonly USE_LOCAL_SERVER = true; // Toggle between local and XPrime
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -22,10 +28,12 @@ export class TrailerService {
|
||||||
* @returns Promise<string | null> - The trailer URL or null if not found
|
* @returns Promise<string | null> - The trailer URL or null if not found
|
||||||
*/
|
*/
|
||||||
static async getTrailerUrl(title: string, year: number, tmdbId?: string, type?: 'movie' | 'tv'): Promise<string | null> {
|
static async getTrailerUrl(title: string, year: number, tmdbId?: string, type?: 'movie' | 'tv'): Promise<string | null> {
|
||||||
|
logger.info('TrailerService', `getTrailerUrl requested: title="${title}", year=${year}, tmdbId=${tmdbId || 'n/a'}, type=${type || 'n/a'}, useLocal=${this.USE_LOCAL_SERVER}`);
|
||||||
if (this.USE_LOCAL_SERVER) {
|
if (this.USE_LOCAL_SERVER) {
|
||||||
// Try local server first, fallback to XPrime if it fails
|
// Try local server first, fallback to XPrime if it fails
|
||||||
const localResult = await this.getTrailerFromLocalServer(title, year, tmdbId, type);
|
const localResult = await this.getTrailerFromLocalServer(title, year, tmdbId, type);
|
||||||
if (localResult) {
|
if (localResult) {
|
||||||
|
logger.info('TrailerService', 'Returning trailer URL from local server');
|
||||||
return localResult;
|
return localResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,6 +54,7 @@ export class TrailerService {
|
||||||
*/
|
*/
|
||||||
private static async getTrailerFromLocalServer(title: string, year: number, tmdbId?: string, type?: 'movie' | 'tv'): Promise<string | null> {
|
private static async getTrailerFromLocalServer(title: string, year: number, tmdbId?: string, type?: 'movie' | 'tv'): Promise<string | null> {
|
||||||
try {
|
try {
|
||||||
|
const startTime = Date.now();
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
const timeoutId = setTimeout(() => controller.abort(), this.TIMEOUT);
|
const timeoutId = setTimeout(() => controller.abort(), this.TIMEOUT);
|
||||||
|
|
||||||
|
|
@ -65,6 +74,8 @@ export class TrailerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
const url = `${this.AUTO_SEARCH_URL}?${params.toString()}`;
|
const url = `${this.AUTO_SEARCH_URL}?${params.toString()}`;
|
||||||
|
logger.info('TrailerService', `Local server request URL: ${url}`);
|
||||||
|
logger.info('TrailerService', `Local server timeout set to ${this.TIMEOUT}ms`);
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|
@ -77,25 +88,55 @@ export class TrailerService {
|
||||||
|
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
const elapsed = Date.now() - startTime;
|
||||||
|
const contentType = response.headers.get('content-type') || 'unknown';
|
||||||
|
logger.info('TrailerService', `Local server response: status=${response.status} ok=${response.ok} content-type=${contentType} elapsedMs=${elapsed}`);
|
||||||
|
|
||||||
|
// Read body as text first so we can log it even on non-200s
|
||||||
|
let rawText = '';
|
||||||
|
try {
|
||||||
|
rawText = await response.text();
|
||||||
|
if (rawText) {
|
||||||
|
const preview = rawText.length > 200 ? `${rawText.slice(0, 200)}...` : rawText;
|
||||||
|
logger.info('TrailerService', `Local server body preview: ${preview}`);
|
||||||
|
} else {
|
||||||
|
logger.info('TrailerService', 'Local server body is empty');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
const msg = e instanceof Error ? `${e.name}: ${e.message}` : String(e);
|
||||||
|
logger.warn('TrailerService', `Failed reading local server body text: ${msg}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
logger.warn('TrailerService', `Auto-search failed: ${response.status} ${response.statusText}`);
|
logger.warn('TrailerService', `Auto-search failed: ${response.status} ${response.statusText}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json();
|
// Attempt to parse JSON from the raw text
|
||||||
|
let data: any = null;
|
||||||
|
try {
|
||||||
|
data = rawText ? JSON.parse(rawText) : null;
|
||||||
|
const keys = typeof data === 'object' && data !== null ? Object.keys(data).join(',') : typeof data;
|
||||||
|
logger.info('TrailerService', `Local server JSON parsed. Keys/Type: ${keys}`);
|
||||||
|
} catch (e) {
|
||||||
|
const msg = e instanceof Error ? `${e.name}: ${e.message}` : String(e);
|
||||||
|
logger.warn('TrailerService', `Failed to parse local server JSON: ${msg}`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (!data.url || !this.isValidTrailerUrl(data.url)) {
|
if (!data.url || !this.isValidTrailerUrl(data.url)) {
|
||||||
logger.warn('TrailerService', `Invalid trailer URL from auto-search: ${data.url}`);
|
logger.warn('TrailerService', `Invalid trailer URL from auto-search: ${data.url}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info('TrailerService', `Successfully found trailer: ${data.url.substring(0, 50)}...`);
|
logger.info('TrailerService', `Successfully found trailer: ${String(data.url).substring(0, 80)}...`);
|
||||||
return data.url;
|
return data.url;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error && error.name === 'AbortError') {
|
if (error instanceof Error && error.name === 'AbortError') {
|
||||||
logger.warn('TrailerService', 'Auto-search request timed out');
|
logger.warn('TrailerService', `Auto-search request timed out after ${this.TIMEOUT}ms`);
|
||||||
} else {
|
} else {
|
||||||
logger.error('TrailerService', 'Error in auto-search:', error);
|
const msg = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
||||||
|
logger.error('TrailerService', `Error in auto-search: ${msg}`);
|
||||||
}
|
}
|
||||||
return null; // Return null to trigger XPrime fallback
|
return null; // Return null to trigger XPrime fallback
|
||||||
}
|
}
|
||||||
|
|
@ -115,6 +156,8 @@ export class TrailerService {
|
||||||
const url = `${this.XPRIME_URL}?title=${encodeURIComponent(title)}&year=${year}`;
|
const url = `${this.XPRIME_URL}?title=${encodeURIComponent(title)}&year=${year}`;
|
||||||
|
|
||||||
logger.info('TrailerService', `Fetching trailer from XPrime for: ${title} (${year})`);
|
logger.info('TrailerService', `Fetching trailer from XPrime for: ${title} (${year})`);
|
||||||
|
logger.info('TrailerService', `XPrime request URL: ${url}`);
|
||||||
|
logger.info('TrailerService', `XPrime timeout set to ${this.TIMEOUT}ms`);
|
||||||
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|
@ -127,12 +170,14 @@ export class TrailerService {
|
||||||
|
|
||||||
clearTimeout(timeoutId);
|
clearTimeout(timeoutId);
|
||||||
|
|
||||||
|
logger.info('TrailerService', `XPrime response: status=${response.status} ok=${response.ok}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
logger.warn('TrailerService', `XPrime failed: ${response.status} ${response.statusText}`);
|
logger.warn('TrailerService', `XPrime failed: ${response.status} ${response.statusText}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const trailerUrl = await response.text();
|
const trailerUrl = await response.text();
|
||||||
|
logger.info('TrailerService', `XPrime raw URL length: ${trailerUrl ? trailerUrl.length : 0}`);
|
||||||
|
|
||||||
if (!trailerUrl || !this.isValidTrailerUrl(trailerUrl.trim())) {
|
if (!trailerUrl || !this.isValidTrailerUrl(trailerUrl.trim())) {
|
||||||
logger.warn('TrailerService', `Invalid trailer URL from XPrime: ${trailerUrl}`);
|
logger.warn('TrailerService', `Invalid trailer URL from XPrime: ${trailerUrl}`);
|
||||||
|
|
@ -145,9 +190,10 @@ export class TrailerService {
|
||||||
return cleanUrl;
|
return cleanUrl;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error && error.name === 'AbortError') {
|
if (error instanceof Error && error.name === 'AbortError') {
|
||||||
logger.warn('TrailerService', 'XPrime request timed out');
|
logger.warn('TrailerService', `XPrime request timed out after ${this.TIMEOUT}ms`);
|
||||||
} else {
|
} else {
|
||||||
logger.error('TrailerService', 'Error fetching from XPrime:', error);
|
const msg = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
||||||
|
logger.error('TrailerService', `Error fetching from XPrime: ${msg}`);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -218,16 +264,21 @@ export class TrailerService {
|
||||||
if (url.includes('M3U')) {
|
if (url.includes('M3U')) {
|
||||||
// Try to get M3U without encryption first, then with encryption
|
// Try to get M3U without encryption first, then with encryption
|
||||||
const baseUrl = url.split('?')[0];
|
const baseUrl = url.split('?')[0];
|
||||||
return `${baseUrl}?formats=M3U+none,M3U+appleHlsEncryption`;
|
const best = `${baseUrl}?formats=M3U+none,M3U+appleHlsEncryption`;
|
||||||
|
logger.info('TrailerService', `Optimized format URL from M3U: ${best.substring(0, 80)}...`);
|
||||||
|
return best;
|
||||||
}
|
}
|
||||||
// Fallback to MP4 if available
|
// Fallback to MP4 if available
|
||||||
if (url.includes('MPEG4')) {
|
if (url.includes('MPEG4')) {
|
||||||
const baseUrl = url.split('?')[0];
|
const baseUrl = url.split('?')[0];
|
||||||
return `${baseUrl}?formats=MPEG4`;
|
const best = `${baseUrl}?formats=MPEG4`;
|
||||||
|
logger.info('TrailerService', `Optimized format URL from MPEG4: ${best.substring(0, 80)}...`);
|
||||||
|
return best;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the original URL if no format optimization is needed
|
// Return the original URL if no format optimization is needed
|
||||||
|
logger.info('TrailerService', 'No format optimization applied');
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,7 +289,9 @@ export class TrailerService {
|
||||||
* @returns Promise<boolean> - True if trailer is available
|
* @returns Promise<boolean> - True if trailer is available
|
||||||
*/
|
*/
|
||||||
static async isTrailerAvailable(title: string, year: number): Promise<boolean> {
|
static async isTrailerAvailable(title: string, year: number): Promise<boolean> {
|
||||||
|
logger.info('TrailerService', `Checking trailer availability for: ${title} (${year})`);
|
||||||
const trailerUrl = await this.getTrailerUrl(title, year);
|
const trailerUrl = await this.getTrailerUrl(title, year);
|
||||||
|
logger.info('TrailerService', `Trailer availability for ${title} (${year}): ${trailerUrl ? 'available' : 'not available'}`);
|
||||||
return trailerUrl !== null;
|
return trailerUrl !== null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -249,9 +302,11 @@ export class TrailerService {
|
||||||
* @returns Promise<TrailerData | null> - Trailer data or null if not found
|
* @returns Promise<TrailerData | null> - Trailer data or null if not found
|
||||||
*/
|
*/
|
||||||
static async getTrailerData(title: string, year: number): Promise<TrailerData | null> {
|
static async getTrailerData(title: string, year: number): Promise<TrailerData | null> {
|
||||||
|
logger.info('TrailerService', `getTrailerData for: ${title} (${year})`);
|
||||||
const url = await this.getTrailerUrl(title, year);
|
const url = await this.getTrailerUrl(title, year);
|
||||||
|
|
||||||
if (!url) {
|
if (!url) {
|
||||||
|
logger.info('TrailerService', 'No trailer URL found for getTrailerData');
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -292,6 +347,7 @@ export class TrailerService {
|
||||||
localServer: { status: 'online' | 'offline'; responseTime?: number };
|
localServer: { status: 'online' | 'offline'; responseTime?: number };
|
||||||
xprimeServer: { status: 'online' | 'offline'; responseTime?: number };
|
xprimeServer: { status: 'online' | 'offline'; responseTime?: number };
|
||||||
}> {
|
}> {
|
||||||
|
logger.info('TrailerService', 'Testing servers (local and XPrime)');
|
||||||
const results: {
|
const results: {
|
||||||
localServer: { status: 'online' | 'offline'; responseTime?: number };
|
localServer: { status: 'online' | 'offline'; responseTime?: number };
|
||||||
xprimeServer: { status: 'online' | 'offline'; responseTime?: number };
|
xprimeServer: { status: 'online' | 'offline'; responseTime?: number };
|
||||||
|
|
@ -312,9 +368,11 @@ export class TrailerService {
|
||||||
status: 'online',
|
status: 'online',
|
||||||
responseTime: Date.now() - startTime
|
responseTime: Date.now() - startTime
|
||||||
};
|
};
|
||||||
|
logger.info('TrailerService', `Local server online. Response time: ${results.localServer.responseTime}ms`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn('TrailerService', 'Local server test failed:', error);
|
const msg = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
||||||
|
logger.warn('TrailerService', `Local server test failed: ${msg}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test XPrime server
|
// Test XPrime server
|
||||||
|
|
@ -329,11 +387,14 @@ export class TrailerService {
|
||||||
status: 'online',
|
status: 'online',
|
||||||
responseTime: Date.now() - startTime
|
responseTime: Date.now() - startTime
|
||||||
};
|
};
|
||||||
|
logger.info('TrailerService', `XPrime server online. Response time: ${results.xprimeServer.responseTime}ms`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.warn('TrailerService', 'XPrime server test failed:', error);
|
const msg = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
||||||
|
logger.warn('TrailerService', `XPrime server test failed: ${msg}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.info('TrailerService', `Server test results -> local: ${results.localServer.status}, xprime: ${results.xprimeServer.status}`);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue