From dd330a17863e4e8388173282b893889006d5523f Mon Sep 17 00:00:00 2001 From: paregi12 Date: Sat, 31 Jan 2026 15:24:36 +0530 Subject: [PATCH] feat(intro): add date-based MAL ID resolution for accurate intro skipping Implements robust season and episode mapping using ArmSyncService in introService. Updates SkipIntroButton and player components to pass releaseDate for precise AniSkip lookups, fixing mismatches for multi-season anime. --- src/components/player/AndroidVideoPlayer.tsx | 1 + src/components/player/KSPlayerCore.tsx | 3 ++- .../player/overlays/SkipIntroButton.tsx | 6 +++-- src/services/introService.ts | 25 ++++++++++++++++--- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/src/components/player/AndroidVideoPlayer.tsx b/src/components/player/AndroidVideoPlayer.tsx index af9eb4c0..f5141880 100644 --- a/src/components/player/AndroidVideoPlayer.tsx +++ b/src/components/player/AndroidVideoPlayer.tsx @@ -1022,6 +1022,7 @@ const AndroidVideoPlayer: React.FC = () => { episode={episode} malId={(metadata as any)?.mal_id || (metadata as any)?.external_ids?.mal_id} kitsuId={id?.startsWith('kitsu:') ? id.split(':')[1] : undefined} + releaseDate={releaseDate} currentTime={playerState.currentTime} onSkip={(endTime) => controlsHook.seekToTime(endTime)} controlsVisible={playerState.showControls} diff --git a/src/components/player/KSPlayerCore.tsx b/src/components/player/KSPlayerCore.tsx index f6e69eaa..9f8538d6 100644 --- a/src/components/player/KSPlayerCore.tsx +++ b/src/components/player/KSPlayerCore.tsx @@ -91,7 +91,7 @@ const KSPlayerCore: React.FC = () => { const { uri, title, episodeTitle, season, episode, id, type, quality, year, episodeId, imdbId, backdrop, availableStreams, - headers, streamProvider, streamName, + headers, streamProvider, streamName, releaseDate, initialPosition: routeInitialPosition } = params; @@ -998,6 +998,7 @@ const KSPlayerCore: React.FC = () => { episode={episode} malId={(metadata as any)?.mal_id || (metadata as any)?.external_ids?.mal_id} kitsuId={id?.startsWith('kitsu:') ? id.split(':')[1] : undefined} + releaseDate={releaseDate} currentTime={currentTime} onSkip={(endTime) => controls.seekToTime(endTime)} controlsVisible={showControls} diff --git a/src/components/player/overlays/SkipIntroButton.tsx b/src/components/player/overlays/SkipIntroButton.tsx index b5329c6a..85b865f8 100644 --- a/src/components/player/overlays/SkipIntroButton.tsx +++ b/src/components/player/overlays/SkipIntroButton.tsx @@ -22,6 +22,7 @@ interface SkipIntroButtonProps { episode?: number; malId?: string; kitsuId?: string; + releaseDate?: string; currentTime: number; onSkip: (endTime: number) => void; controlsVisible?: boolean; @@ -35,6 +36,7 @@ export const SkipIntroButton: React.FC = ({ episode, malId, kitsuId, + releaseDate, currentTime, onSkip, controlsVisible = false, @@ -96,7 +98,7 @@ export const SkipIntroButton: React.FC = ({ const fetchSkipData = async () => { logger.log(`[SkipIntroButton] Fetching skip data for S${season}E${episode} (IMDB: ${imdbId}, MAL: ${malId}, Kitsu: ${kitsuId})...`); try { - const intervals = await introService.getSkipTimes(imdbId, season, episode, malId, kitsuId); + const intervals = await introService.getSkipTimes(imdbId, season, episode, malId, kitsuId, releaseDate); setSkipIntervals(intervals); if (intervals.length > 0) { @@ -111,7 +113,7 @@ export const SkipIntroButton: React.FC = ({ }; fetchSkipData(); - }, [imdbId, type, season, episode, malId, kitsuId, skipIntroEnabled]); + }, [imdbId, type, season, episode, malId, kitsuId, releaseDate, skipIntroEnabled]); // Determine active interval based on current playback position useEffect(() => { diff --git a/src/services/introService.ts b/src/services/introService.ts index 9d225903..3f3c84ec 100644 --- a/src/services/introService.ts +++ b/src/services/introService.ts @@ -1,6 +1,7 @@ import axios from 'axios'; import { logger } from '../utils/logger'; import { tmdbService } from './tmdbService'; +import { ArmSyncService } from './mal/ArmSyncService'; /** * IntroDB API service for fetching TV show intro timestamps @@ -195,7 +196,8 @@ export async function getSkipTimes( season: number, episode: number, malId?: string, - kitsuId?: string + kitsuId?: string, + releaseDate?: string ): Promise { // 1. Try IntroDB (TV Shows) first if (imdbId) { @@ -207,7 +209,22 @@ export async function getSkipTimes( // 2. Try AniSkip (Anime) if we have MAL ID or Kitsu ID let finalMalId = malId; + let finalEpisode = episode; + // If we have IMDb ID and Release Date, try ArmSyncService to resolve exact MAL ID and Episode + if (!finalMalId && imdbId && releaseDate) { + try { + const armResult = await ArmSyncService.resolveByDate(imdbId, releaseDate); + if (armResult) { + finalMalId = armResult.malId.toString(); + finalEpisode = armResult.episode; + logger.log(`[IntroService] ArmSync resolved: MAL ${finalMalId} Ep ${finalEpisode}`); + } + } catch (e) { + logger.warn('[IntroService] ArmSync failed', e); + } + } + // If we have Kitsu ID but no MAL ID, try to resolve it if (!finalMalId && kitsuId) { logger.log(`[IntroService] Resolving MAL ID from Kitsu ID: ${kitsuId}`); @@ -228,8 +245,8 @@ export async function getSkipTimes( } if (finalMalId) { - logger.log(`[IntroService] Fetching AniSkip for MAL ID: ${finalMalId} Ep: ${episode}`); - const aniSkipIntervals = await fetchFromAniSkip(finalMalId, episode); + logger.log(`[IntroService] Fetching AniSkip for MAL ID: ${finalMalId} Ep: ${finalEpisode}`); + const aniSkipIntervals = await fetchFromAniSkip(finalMalId, finalEpisode); if (aniSkipIntervals.length > 0) { logger.log(`[IntroService] Found ${aniSkipIntervals.length} skip intervals from AniSkip`); return aniSkipIntervals; @@ -269,4 +286,4 @@ export const introService = { getSkipTimes }; -export default introService; +export default introService; \ No newline at end of file