diff --git a/changes/jellyfin-review-audio-track.md b/changes/jellyfin-review-audio-track.md new file mode 100644 index 00000000..f4eeffa4 --- /dev/null +++ b/changes/jellyfin-review-audio-track.md @@ -0,0 +1,4 @@ +type: fixed +area: jellyfin + +- Timing review previews and mined card audio now use the audio track you are playing on Jellyfin direct-play streams, instead of the track Jellyfin marks as default. diff --git a/src/anki-integration/media-source.test.ts b/src/anki-integration/media-source.test.ts index a3c49871..92a6792d 100644 --- a/src/anki-integration/media-source.test.ts +++ b/src/anki-integration/media-source.test.ts @@ -213,6 +213,30 @@ test('resolveMediaGenerationInput reads file-local mpv request options', async ( }); }); +test('resolveMediaGenerationInput keeps stream selection for a directly played remote container', async () => { + const resolver = ( + mediaSource as typeof mediaSource & { + resolveMediaGenerationInput?: StructuredMediaResolver; + } + ).resolveMediaGenerationInput; + assert.equal(typeof resolver, 'function'); + + // Jellyfin direct play: mpv opens the URL as-is, so it can hold several audio streams. + const jellyfinUrl = 'http://jellyfin.local:8096/Videos/abc/stream?static=true'; + const result = await resolver!( + { + currentVideoPath: jellyfinUrl, + requestProperty: async (name: string) => + name === 'stream-open-filename' ? jellyfinUrl : null, + }, + 'audio', + ); + + assert.equal(result?.path, jellyfinUrl); + assert.equal(result?.source, 'stream-open-filename'); + assert.equal(result?.singleResolvedStream, false); +}); + test('resolveMediaGenerationInput prefers a ready cached media file for YouTube extraction', async () => { const resolver = ( mediaSource as typeof mediaSource & { diff --git a/src/anki-integration/media-source.ts b/src/anki-integration/media-source.ts index 8f511bc9..0bb650d2 100644 --- a/src/anki-integration/media-source.ts +++ b/src/anki-integration/media-source.ts @@ -400,12 +400,15 @@ export async function resolveMediaGenerationInput( return result; } if (streamOpenFilename) { + // A URL that yt-dlp resolved (e.g. youtube.com -> googlevideo) is one picked stream. + // When mpv opens the path as-is (e.g. Jellyfin direct play), it is the full container, + // so mpv's audio stream index still applies. const result = await toResolvedMediaGenerationInput( mpvClient, streamOpenFilename, kind, 'stream-open-filename', - isRemoteMediaPath(streamOpenFilename), + isRemoteMediaPath(streamOpenFilename) && streamOpenFilename !== currentVideoPath, ); logResolvedMediaGenerationInput(options, currentVideoPath, result); return result;