import type { MediaSourceInfo, MediaStream, } from "@jellyfin/sdk/lib/generated-client"; abstract class StreamRankerStrategy { abstract streamType: string; abstract rankStream( prevIndex: number, prevSource: MediaSourceInfo, mediaStreams: MediaStream[], trackOptions: any, ): void; protected rank( prevIndex: number, prevSource: MediaSourceInfo, mediaStreams: MediaStream[], trackOptions: any, ): void { if (prevIndex === -1) { console.debug("AutoSet Subtitle - No Stream Set"); trackOptions[`Default${this.streamType}StreamIndex`] = -1; return; } if (!prevSource.MediaStreams || !mediaStreams) { console.debug(`AutoSet ${this.streamType} - No MediaStreams`); return; } let bestStreamIndex = null; let bestStreamScore = 0; const prevStream = prevSource.MediaStreams[prevIndex]; if (!prevStream) { console.debug(`AutoSet ${this.streamType} - No prevStream`); return; } console.debug( `AutoSet ${this.streamType} - Previous was ${prevStream.Index} - ${prevStream.DisplayTitle}`, ); let prevRelIndex = 0; for (const stream of prevSource.MediaStreams) { if (stream.Type !== this.streamType) { continue; } if (stream.Index === prevIndex) { break; } prevRelIndex += 1; } let newRelIndex = 0; for (const stream of mediaStreams) { if (stream.Type !== this.streamType) { continue; } let score = 0; if (prevStream.Codec === stream.Codec) { score += 1; } if (prevRelIndex === newRelIndex) { score += 1; } if ( prevStream.DisplayTitle && prevStream.DisplayTitle === stream.DisplayTitle ) { score += 2; } if ( prevStream.Language && prevStream.Language !== "und" && prevStream.Language === stream.Language ) { score += 2; } console.debug( `AutoSet ${this.streamType} - Score ${score} for ${stream.Index} - ${stream.DisplayTitle}`, ); if (score > bestStreamScore && score >= 3) { bestStreamScore = score; bestStreamIndex = stream.Index; } newRelIndex += 1; } if (bestStreamIndex != null) { console.debug( `AutoSet ${this.streamType} - Using ${bestStreamIndex} score ${bestStreamScore}.`, ); trackOptions[`Default${this.streamType}StreamIndex`] = bestStreamIndex; } else { console.debug( `AutoSet ${this.streamType} - Threshold not met. Using default.`, ); } } } class SubtitleStreamRanker extends StreamRankerStrategy { streamType = "Subtitle"; rankStream( prevIndex: number, prevSource: MediaSourceInfo, mediaStreams: MediaStream[], trackOptions: any, ): void { super.rank(prevIndex, prevSource, mediaStreams, trackOptions); } } class AudioStreamRanker extends StreamRankerStrategy { streamType = "Audio"; rankStream( prevIndex: number, prevSource: MediaSourceInfo, mediaStreams: MediaStream[], trackOptions: any, ): void { super.rank(prevIndex, prevSource, mediaStreams, trackOptions); } } class StreamRanker { private strategy: StreamRankerStrategy; constructor(strategy: StreamRankerStrategy) { this.strategy = strategy; } setStrategy(strategy: StreamRankerStrategy) { this.strategy = strategy; } rankStream( prevIndex: number, prevSource: MediaSourceInfo, mediaStreams: MediaStream[], trackOptions: any, ) { this.strategy.rankStream(prevIndex, prevSource, mediaStreams, trackOptions); } } export { StreamRanker, SubtitleStreamRanker, AudioStreamRanker };