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.
This commit is contained in:
paregi12 2026-01-31 15:24:36 +05:30
parent 02da43e21c
commit dd330a1786
4 changed files with 28 additions and 7 deletions

View file

@ -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}

View file

@ -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}

View file

@ -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<SkipIntroButtonProps> = ({
episode,
malId,
kitsuId,
releaseDate,
currentTime,
onSkip,
controlsVisible = false,
@ -96,7 +98,7 @@ export const SkipIntroButton: React.FC<SkipIntroButtonProps> = ({
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<SkipIntroButtonProps> = ({
};
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(() => {

View file

@ -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<SkipInterval[]> {
// 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;