mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-03-11 17:45:38 +00:00
simkl optimizations
This commit is contained in:
parent
f008654ac7
commit
effab63a70
3 changed files with 137 additions and 52 deletions
|
|
@ -16,6 +16,7 @@ export const usePlayerSetup = (
|
|||
setBrightness: (bri: number) => void,
|
||||
paused: boolean
|
||||
) => {
|
||||
const originalAppBrightnessRef = useRef<number | null>(null);
|
||||
const originalSystemBrightnessRef = useRef<number | null>(null);
|
||||
const originalSystemBrightnessModeRef = useRef<number | null>(null);
|
||||
const isAppBackgrounded = useRef(false);
|
||||
|
|
@ -100,6 +101,7 @@ export const usePlayerSetup = (
|
|||
}
|
||||
}
|
||||
const currentBrightness = await Brightness.getBrightnessAsync();
|
||||
originalAppBrightnessRef.current = currentBrightness;
|
||||
setBrightness(currentBrightness);
|
||||
} catch (error) {
|
||||
logger.warn('[usePlayerSetup] Error setting brightness', error);
|
||||
|
|
@ -111,12 +113,26 @@ export const usePlayerSetup = (
|
|||
return () => {
|
||||
subscription?.remove();
|
||||
disableImmersiveMode();
|
||||
async function restoreBrightness() {
|
||||
await Brightness.setBrightnessAsync(originalSystemBrightnessRef.current!);
|
||||
setBrightness(originalSystemBrightnessRef.current!);
|
||||
}
|
||||
if (Platform.OS === 'android' && originalSystemBrightnessRef.current !== null)
|
||||
restoreBrightness();
|
||||
const restoreBrightness = async () => {
|
||||
try {
|
||||
if (Platform.OS === 'android') {
|
||||
if (originalSystemBrightnessModeRef.current !== null) {
|
||||
await (Brightness as any).setSystemBrightnessModeAsync?.(originalSystemBrightnessModeRef.current);
|
||||
}
|
||||
if (originalSystemBrightnessRef.current !== null) {
|
||||
await (Brightness as any).setSystemBrightnessAsync?.(originalSystemBrightnessRef.current);
|
||||
}
|
||||
}
|
||||
if (originalAppBrightnessRef.current !== null) {
|
||||
await Brightness.setBrightnessAsync(originalAppBrightnessRef.current);
|
||||
setBrightness(originalAppBrightnessRef.current);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn('[usePlayerSetup] Error restoring brightness', e);
|
||||
}
|
||||
};
|
||||
|
||||
restoreBrightness();
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,59 @@
|
|||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { AppState, AppStateStatus } from 'react-native';
|
||||
import {
|
||||
SimklService,
|
||||
SimklContentData,
|
||||
SimklPlaybackData,
|
||||
SimklUserSettings,
|
||||
SimklStats
|
||||
SimklStats,
|
||||
SimklActivities
|
||||
} from '../services/simklService';
|
||||
import { storageService } from '../services/storageService';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
const simklService = SimklService.getInstance();
|
||||
|
||||
let hasLoadedProfileOnce = false;
|
||||
let cachedUserSettings: SimklUserSettings | null = null;
|
||||
let cachedUserStats: SimklStats | null = null;
|
||||
|
||||
export function useSimklIntegration() {
|
||||
const [isAuthenticated, setIsAuthenticated] = useState<boolean>(false);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
|
||||
// Basic lists
|
||||
const [continueWatching, setContinueWatching] = useState<SimklPlaybackData[]>([]);
|
||||
const [userSettings, setUserSettings] = useState<SimklUserSettings | null>(null);
|
||||
const [userStats, setUserStats] = useState<SimklStats | null>(null);
|
||||
const [userSettings, setUserSettings] = useState<SimklUserSettings | null>(() => cachedUserSettings);
|
||||
const [userStats, setUserStats] = useState<SimklStats | null>(() => cachedUserStats);
|
||||
|
||||
const lastPlaybackFetchAt = useRef(0);
|
||||
const lastActivitiesCheckAt = useRef(0);
|
||||
const lastPlaybackActivityAt = useRef<number | null>(null);
|
||||
|
||||
const parseActivityDate = (value?: string): number | null => {
|
||||
if (!value) return null;
|
||||
const parsed = Date.parse(value);
|
||||
return Number.isNaN(parsed) ? null : parsed;
|
||||
};
|
||||
|
||||
const getLatestPlaybackActivity = (activities: SimklActivities | null): number | null => {
|
||||
if (!activities) return null;
|
||||
|
||||
const candidates: Array<number | null> = [
|
||||
parseActivityDate(activities.playback?.all),
|
||||
parseActivityDate(activities.playback?.movies),
|
||||
parseActivityDate(activities.playback?.episodes),
|
||||
parseActivityDate(activities.playback?.tv),
|
||||
parseActivityDate(activities.playback?.anime),
|
||||
parseActivityDate(activities.all),
|
||||
parseActivityDate((activities as any).last_update),
|
||||
parseActivityDate((activities as any).updated_at)
|
||||
];
|
||||
|
||||
const timestamps = candidates.filter((value): value is number => typeof value === 'number');
|
||||
if (timestamps.length === 0) return null;
|
||||
return Math.max(...timestamps);
|
||||
};
|
||||
|
||||
// Check authentication status
|
||||
const checkAuthStatus = useCallback(async () => {
|
||||
|
|
@ -56,9 +90,17 @@ export function useSimklIntegration() {
|
|||
try {
|
||||
const settings = await simklService.getUserSettings();
|
||||
setUserSettings(settings);
|
||||
cachedUserSettings = settings;
|
||||
|
||||
const stats = await simklService.getUserStats();
|
||||
setUserStats(stats);
|
||||
const accountId = settings?.account?.id;
|
||||
if (accountId) {
|
||||
const stats = await simklService.getUserStats(accountId);
|
||||
setUserStats(stats);
|
||||
cachedUserStats = stats;
|
||||
} else {
|
||||
setUserStats(null);
|
||||
cachedUserStats = null;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('[useSimklIntegration] Error loading user profile:', error);
|
||||
}
|
||||
|
|
@ -170,9 +212,33 @@ export function useSimklIntegration() {
|
|||
if (!isAuthenticated) return false;
|
||||
|
||||
try {
|
||||
const now = Date.now();
|
||||
if (now - lastActivitiesCheckAt.current < 30000) {
|
||||
return true;
|
||||
}
|
||||
lastActivitiesCheckAt.current = now;
|
||||
|
||||
const activities = await simklService.getActivities();
|
||||
const latestPlaybackActivity = getLatestPlaybackActivity(activities);
|
||||
|
||||
if (latestPlaybackActivity && lastPlaybackActivityAt.current === latestPlaybackActivity) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (latestPlaybackActivity) {
|
||||
lastPlaybackActivityAt.current = latestPlaybackActivity;
|
||||
}
|
||||
|
||||
if (now - lastPlaybackFetchAt.current < 60000) {
|
||||
return true;
|
||||
}
|
||||
lastPlaybackFetchAt.current = now;
|
||||
|
||||
const playback = await simklService.getPlaybackStatus();
|
||||
logger.log(`[useSimklIntegration] fetched Simkl playback: ${playback.length}`);
|
||||
|
||||
setContinueWatching(playback);
|
||||
|
||||
for (const item of playback) {
|
||||
let id: string | undefined;
|
||||
let type: string;
|
||||
|
|
@ -215,9 +281,11 @@ export function useSimklIntegration() {
|
|||
|
||||
useEffect(() => {
|
||||
if (isAuthenticated) {
|
||||
loadPlaybackStatus();
|
||||
fetchAndMergeSimklProgress();
|
||||
loadUserProfile();
|
||||
if (!hasLoadedProfileOnce) {
|
||||
hasLoadedProfileOnce = true;
|
||||
loadUserProfile();
|
||||
}
|
||||
}
|
||||
}, [isAuthenticated, loadPlaybackStatus, fetchAndMergeSimklProgress, loadUserProfile]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { mmkvStorage } from './mmkvStorage';
|
||||
import { AppState, AppStateStatus } from 'react-native';
|
||||
import { logger } from '../utils/logger';
|
||||
import Constants from 'expo-constants';
|
||||
|
||||
// Storage keys
|
||||
export const SIMKL_ACCESS_TOKEN_KEY = 'simkl_access_token';
|
||||
|
|
@ -119,6 +120,19 @@ export interface SimklStats {
|
|||
};
|
||||
}
|
||||
|
||||
export interface SimklActivities {
|
||||
all?: string;
|
||||
playback?: {
|
||||
all?: string;
|
||||
movies?: string;
|
||||
episodes?: string;
|
||||
tv?: string;
|
||||
anime?: string;
|
||||
[key: string]: string | undefined;
|
||||
};
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export class SimklService {
|
||||
private static instance: SimklService;
|
||||
private accessToken: string | null = null;
|
||||
|
|
@ -272,10 +286,12 @@ export class SimklService {
|
|||
return null;
|
||||
}
|
||||
|
||||
const appVersion = Constants.expoConfig?.version || (Constants as any).manifest?.version || 'unknown';
|
||||
const headers: HeadersInit = {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.accessToken}`,
|
||||
'simkl-api-key': SIMKL_CLIENT_ID
|
||||
'simkl-api-key': SIMKL_CLIENT_ID,
|
||||
'User-Agent': `Nuvio/${appVersion}`
|
||||
};
|
||||
|
||||
const options: RequestInit = {
|
||||
|
|
@ -518,41 +534,27 @@ export class SimklService {
|
|||
}
|
||||
|
||||
public async getPlaybackStatus(): Promise<SimklPlaybackData[]> {
|
||||
// Docs: GET /sync/playback/{type} with {type} values `movies` or `episodes`.
|
||||
// Some docs also mention appending /movie or /episode; we try both variants for safety.
|
||||
const tryEndpoints = async (endpoints: string[]): Promise<SimklPlaybackData[]> => {
|
||||
for (const endpoint of endpoints) {
|
||||
try {
|
||||
const res = await this.apiRequest<SimklPlaybackData[]>(endpoint);
|
||||
if (Array.isArray(res)) {
|
||||
logger.log(`[SimklService] getPlaybackStatus: ${endpoint} -> ${res.length} items`);
|
||||
return res;
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warn(`[SimklService] getPlaybackStatus: ${endpoint} failed`, e);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
const movies = await tryEndpoints([
|
||||
'/sync/playback/movies',
|
||||
'/sync/playback/movie',
|
||||
'/sync/playback?type=movies'
|
||||
]);
|
||||
|
||||
const episodes = await tryEndpoints([
|
||||
'/sync/playback/episodes',
|
||||
'/sync/playback/episode',
|
||||
'/sync/playback?type=episodes'
|
||||
]);
|
||||
|
||||
const combined = [...episodes, ...movies]
|
||||
const playback = await this.apiRequest<SimklPlaybackData[]>('/sync/playback');
|
||||
const items = Array.isArray(playback) ? playback : [];
|
||||
const sorted = items
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => new Date(b.paused_at).getTime() - new Date(a.paused_at).getTime());
|
||||
|
||||
logger.log(`[SimklService] getPlaybackStatus: combined ${combined.length} items (episodes=${episodes.length}, movies=${movies.length})`);
|
||||
return combined;
|
||||
logger.log(`[SimklService] getPlaybackStatus: ${sorted.length} items`);
|
||||
return sorted;
|
||||
}
|
||||
|
||||
/**
|
||||
* SYNC: Get account activity timestamps
|
||||
*/
|
||||
public async getActivities(): Promise<SimklActivities | null> {
|
||||
try {
|
||||
const response = await this.apiRequest<SimklActivities>('/sync/activities');
|
||||
return response || null;
|
||||
} catch (error) {
|
||||
logger.error('[SimklService] Failed to get activities:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -585,20 +587,19 @@ export class SimklService {
|
|||
/**
|
||||
* Get user stats
|
||||
*/
|
||||
public async getUserStats(): Promise<SimklStats | null> {
|
||||
public async getUserStats(accountId?: number): Promise<SimklStats | null> {
|
||||
try {
|
||||
if (!await this.isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Need account ID from settings first
|
||||
const settings = await this.getUserSettings();
|
||||
if (!settings?.account?.id) {
|
||||
const resolvedAccountId = accountId ?? (await this.getUserSettings())?.account?.id;
|
||||
if (!resolvedAccountId) {
|
||||
logger.warn('[SimklService] Cannot get user stats: no account ID');
|
||||
return null;
|
||||
}
|
||||
|
||||
const response = await this.apiRequest<SimklStats>(`/users/${settings.account.id}/stats`, 'POST');
|
||||
const response = await this.apiRequest<SimklStats>(`/users/${resolvedAccountId}/stats`, 'POST');
|
||||
logger.log('[SimklService] getUserStats:', JSON.stringify(response));
|
||||
return response;
|
||||
} catch (error) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue