Skip to content

Fix default audio stream selection#980

Open
fezster wants to merge 1 commit intojellyfin:masterfrom
fezster:fix-default-audiostream-selection
Open

Fix default audio stream selection#980
fezster wants to merge 1 commit intojellyfin:masterfrom
fezster:fix-default-audiostream-selection

Conversation

@fezster
Copy link
Contributor

@fezster fezster commented Feb 26, 2025

Discussion thread here: https://forum.jellyfin.org/t-wrong-audio-track-selected-by-default-jellyfin-for-kodi?pid=52729#pid52729

The current implementation does not work on Fire TV devices (Firestick 4k Max, FireCube V3). The issue seems to stem from a race condition in player.py.

If report_playback() -> detect_audio_subs() is called first, the current audio track is defaulted and this is then stored. Later, set_audio_subs() is called and this same audio track is applied. This is what should happen and, I guess, is expected to happen.

On the FireStick, though, this never happens. The set_audio_subs() is called first - which means the DefaultAudioStreamIndex returned by Jellyfin is used. The logic is then not applied correctly and the wrong audio track is applied. Later, report_playback is called, but by then the audio track has already been set incorrectly and so it never corrects itself.

By applying the correct logic to DefaultAudioStreamIndex, this then works as well.

The correct logic, I believe, should be to subtract 1 from the index returned by Jellyfin.

Summary by CodeRabbit

  • Bug Fixes
    • Corrected audio stream selection and reporting to ensure accurate playback behavior and synchronization with the server.

@codecov
Copy link

codecov bot commented Feb 26, 2025

Codecov Report

❌ Patch coverage is 0% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 21.84%. Comparing base (1acddcc) to head (affda25).
⚠️ Report is 251 commits behind head on master.

Files with missing lines Patch % Lines
jellyfin_kodi/helper/playutils.py 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #980      +/-   ##
==========================================
- Coverage   21.87%   21.84%   -0.03%     
==========================================
  Files          63       63              
  Lines        8686     8696      +10     
  Branches     1411     1414       +3     
==========================================
  Hits         1900     1900              
- Misses       6767     6777      +10     
  Partials       19       19              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@fezster fezster force-pushed the fix-default-audiostream-selection branch from a96157b to db0e4e9 Compare February 26, 2025 22:01
@sonarqubecloud
Copy link

@oddstr13 oddstr13 added the needs-testing Some testing is requested/required before merging label May 26, 2025
@oddstr13
Copy link
Member

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented May 26, 2025

Walkthrough

The changes adjust how the "AudioStreamIndex" is handled in both playback initialization and reporting. The index is now decremented by 1 when set during playback setup and incremented by 1 when reported back to the server, ensuring consistent mapping between internal and server-side representations. No public interfaces were changed.

Changes

File(s) Change Summary
jellyfin_kodi/helper/playutils.py Modified assignment of "AudioStreamIndex" to subtract 1 from source's "DefaultAudioStreamIndex".
jellyfin_kodi/player.py Incremented "AudioStreamIndex" by 1 before sending it in playback progress reporting.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant PlayUtils
    participant Player
    participant JellyfinServer

    User->>PlayUtils: Start playback
    PlayUtils->>PlayUtils: Set AudioStreamIndex = DefaultAudioStreamIndex - 1
    PlayUtils->>Player: Pass playback info (AudioStreamIndex)
    Player->>JellyfinServer: Report playback (AudioStreamIndex + 1)
Loading
✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be4207b and db0e4e9.

📒 Files selected for processing (2)
  • jellyfin_kodi/helper/playutils.py (1 hunks)
  • jellyfin_kodi/player.py (1 hunks)
🧰 Additional context used
🪛 GitHub Actions: Test Jellyfin-Kodi
jellyfin_kodi/helper/playutils.py

[error] 211-217: Prettier formatting check failed. File would be reformatted by Black. Run 'black --write' to fix code style issues.

🔇 Additional comments (1)
jellyfin_kodi/player.py (1)

385-385: LGTM: Correct indexing conversion for server communication.

The change properly converts from internal 0-based indexing to the server's expected 1-based indexing when reporting playback progress. This ensures the Jellyfin server receives the correct AudioStreamIndex value.

self.info["AudioStreamIndex"] = self.info.get("AudioStreamIndex") or source.get(
"DefaultAudioStreamIndex"
)
self.info["AudioStreamIndex"] = self.info.get("AudioStreamIndex") or (source.get("DefaultAudioStreamIndex") - 1)
Copy link

@coderabbitai coderabbitai bot May 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix potential TypeError and address formatting issues.

This change has two issues that need to be addressed:

  1. Critical Issue: If source.get("DefaultAudioStreamIndex") returns None, the subtraction will raise a TypeError.
  2. Formatting Issue: The pipeline indicates this line needs Black formatting.

Apply this diff to fix both issues:

-        self.info["AudioStreamIndex"] = self.info.get("AudioStreamIndex") or (source.get("DefaultAudioStreamIndex") - 1)
+        self.info["AudioStreamIndex"] = self.info.get("AudioStreamIndex") or (
+            source.get("DefaultAudioStreamIndex", 0) - 1
+        )

This ensures:

  • A default value of 0 if DefaultAudioStreamIndex is missing
  • Proper code formatting to pass Black style checks
  • Safe conversion from server's 1-based to internal 0-based indexing
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
self.info["AudioStreamIndex"] = self.info.get("AudioStreamIndex") or (source.get("DefaultAudioStreamIndex") - 1)
self.info["AudioStreamIndex"] = self.info.get("AudioStreamIndex") or (
source.get("DefaultAudioStreamIndex", 0) - 1
)
🤖 Prompt for AI Agents
In jellyfin_kodi/helper/playutils.py at line 216, the current code risks a
TypeError if source.get("DefaultAudioStreamIndex") returns None because it
attempts to subtract 1 from None. To fix this, provide a default value of 1 when
retrieving "DefaultAudioStreamIndex" to ensure subtraction is safe, then
subtract 1 to convert from 1-based to 0-based indexing. Also, reformat the line
according to Black style guidelines to resolve formatting issues.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An index of -1 would likely cause unexpected issues.
If that value truly is 1-index based rather than 0-index based, the default should be 1, not 0.

@fezster could the issue you are seeing be related to what is getting fixed in #996? (just for a different part of the code/stream)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't mind the bot too much, mainly testing it to see if it proves to be a good tool with aiding in PR reviews.

Fix default audio stream selection
@fezster fezster force-pushed the fix-default-audiostream-selection branch from db0e4e9 to affda25 Compare May 27, 2025 07:19
@sonarqubecloud
Copy link

@oddstr13
Copy link
Member

@coderabbitai Can you review this PR and the related issue description (in linked forum post) in light of what has been uncovered for subtitles with PR #996 ?
Fully trace out the code paths if needed, and assess whether the same underlying issue could be relevant also here.

@coderabbitai
Copy link

coderabbitai bot commented May 27, 2025

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

I'll help you analyze this PR in the context of PR #996's subtitle findings. Let me first gather information about PR #996 and trace the relevant code paths.


🏁 Script executed:

#!/bin/bash
# First, let's get information about PR #996 to understand what was uncovered for subtitles
gh pr view 996 --json title,body,comments,url

Length of output: 17528


🏁 Script executed:

#!/bin/bash
# Look for any forum post links in the current PR's description or comments
gh pr view 980 --json body,comments | jq -r '.body, .comments[].body' | grep -E "(forum|jellyfin\.org|discuss\.jellyfin)"

Length of output: 411


🏁 Script executed:

#!/bin/bash
# Let's examine the current audio/subtitle stream handling code more thoroughly
# First, look at the modified files to understand the current changes
cat jellyfin_kodi/helper/playutils.py

Length of output: 24389


🏁 Script executed:

#!/bin/bash
# Look at the player.py file to understand the complete context
cat jellyfin_kodi/player.py

Length of output: 15083


🏁 Script executed:

#!/bin/bash
# Search for methods related to audio and subtitle stream handling
ast-grep --pattern 'def detect_audio_subs($$$) {
  $$$
}'

Length of output: 59


🏁 Script executed:

#!/bin/bash
# Search for set_audio_subs method
ast-grep --pattern 'def set_audio_subs($$$) {
  $$$
}'

Length of output: 56


🏁 Script executed:

#!/bin/bash
# Look for any recent changes or related issues with subtitle handling
rg "SubtitleStreamIndex" -A 5 -B 5

Length of output: 11016


🏁 Script executed:

#!/bin/bash
# Search for any comments or documentation about stream indexing issues
rg -i "index.*stream|stream.*index" -A 3 -B 3 --type py

Length of output: 14924


@oddstr13, I've thoroughly analyzed both PRs and traced the complete code paths. Here's my assessment of whether the same underlying issue from PR #996 could be relevant to PR #980:

Analysis Summary

PR #996 Discovery: Fixed subtitle stream indexing where the code was incorrectly using array positions instead of actual stream["Index"] values. This caused exceptions when the MediaStreams array order didn't match the real stream indexes.

PR #980 Issue: Addresses a race condition on Fire TV devices where set_audio_subs() is called before detect_audio_subs(), resulting in wrong default audio track selection.

Key Differences in Stream Index Handling

Audio Streams (PR #980):

# In playutils.py - subtracts 1 from server default
self.info["AudioStreamIndex"] = source.get("DefaultAudioStreamIndex", 0) - 1

# In player.py - adds 1 back when reporting to server  
"AudioStreamIndex": (item["AudioStreamIndex"] + 1)

Subtitle Streams (no adjustment):

# In playutils.py - uses server default directly
self.info["SubtitleStreamIndex"] = source.get("DefaultSubtitleStreamIndex")

Assessment of Relationship

Different Root Causes:

  1. PR Fix indexing of media streams in subtitle dialog #996: Array position ≠ actual stream["Index"] mismatch in dialog selection
  2. PR Fix default audio stream selection #980: Timing issue where default selection bypasses proper offset logic

Potential Shared Vulnerability:
The same indexing mismatch that PR #996 addressed for subtitles could theoretically affect audio streams, but PR #980's specific fix suggests the audio issue is more about timing/offset handling rather than array-vs-index confusion.

Code Path Analysis

flowchart TB
    A[Playback Start] --> B{Platform Type}
    B -->|Normal| C[report_playback]
    B -->|Fire TV| D[set_audio_subs called early]
    
    C --> E[detect_audio_subs]
    E --> F[Audio index + 1]
    F --> G[set_audio_subs]
    G --> H[Kodi setAudioStream with index - 1]
    
    D --> I[Uses DefaultAudioStreamIndex directly]
    I --> J[Wrong track selected]
    
    K[PR #980 Fix] --> L[DefaultAudioStreamIndex - 1]
    L --> M[Later +1 when reporting back]
Loading

Conclusion

While both PRs address stream indexing issues, they target different specific problems:

The underlying complexity is similar (stream indexing is tricky), but PR #980's approach with -1/+1 adjustments suggests the audio path already had different handling than subtitles. The fix appears targeted and appropriate for the specific Fire TV race condition rather than a broader indexing mismatch like PR #996 addressed.

Recommendation: The fix looks sound for the specific issue described. However, it would be worth verifying that the same array-vs-index issues from PR #996 don't affect other audio stream handling paths beyond this race condition scenario.


🏁 Script executed:

#!/bin/bash
# Let's look at the forum post mentioned to get more context about the original issue
curl -s "https://forum.jellyfin.org/t-wrong-audio-track-selected-by-default-jellyfin-for-kodi?pid=52729#pid52729" | grep -A 10 -B 10 "wrong audio track\|Fire TV\|audio stream\|default audio" || echo "Unable to fetch forum content"

Length of output: 2906


Perfect! Now I have the complete context from the forum post. This provides crucial details about the specific race condition issue. Let me update my analysis:

Updated Analysis with Forum Context

The forum post provides critical details that clarify the relationship between these PRs:

The Specific Race Condition (PR #980)

Normal Sequence (works correctly):

  1. report_playback()detect_audio_subs() (sets correct audio track)
  2. set_audio_subs() (applies the already-correct track)

Fire TV Sequence (broken):

  1. set_audio_subs() called first → uses raw DefaultAudioStreamIndex without offset logic
  2. report_playback() called later → can't fix the already-incorrectly-set track

Key Insight from Forum Analysis

The user's logs show:

  • Line 28: AudioStreamIndex set to '3' (correct TrueHD track)
  • Later: Commentary track (ac-3) loads instead
  • Manual switch: TrueHD works fine when manually selected

This confirms it's a timing issue where the wrong default is applied before the correct logic can run.

Relationship to PR #996's Indexing Fix

Different but Related Issues:

  1. PR Fix indexing of media streams in subtitle dialog #996: Fixed subtitle code using array position instead of stream["Index"]
  2. PR Fix default audio stream selection #980: Fixed audio race condition with proper offset handling

Shared Indexing Complexity: Both PRs deal with the fundamental challenge that Jellyfin stream indexes ≠ Kodi internal indexes

Code Path Verification

Looking at the audio stream handling in get_audio_subs():

# This code already uses actual stream["Index"] values (like PR #996's fix)
for stream in streams:
    index = stream["Index"]  # ✅ Uses actual index, not array position
    if stream_type == "Audio":
        audio_streams.append(index)

Good news: The audio stream selection code already uses proper stream["Index"] values, so it doesn't have the same array-vs-index vulnerability that PR #996 fixed for subtitles.

Assessment of PR #980's Fix

The -1/+1 offset approach is appropriate and targeted for this race condition:

# playutils.py: Prepare for Kodi (0-based)
source.get("DefaultAudioStreamIndex", 0) - 1

# player.py: Report back to Jellyfin (1-based)  
item["AudioStreamIndex"] + 1

This ensures consistent offset handling regardless of whether detect_audio_subs() runs first or not.

Final Conclusion

The PRs address related but distinct issues:

No additional vulnerability: The audio code already uses proper stream["Index"] values like PR #996's fix, so the same indexing bug doesn't appear to affect the audio paths.

PR #980's fix is sound: The offset adjustment resolves the Fire TV race condition by ensuring consistent index handling regardless of call sequence.

The underlying stream indexing complexity is shared between both issues, but PR #980's targeted fix appropriately addresses the specific race condition without introducing the broader indexing problems that PR #996 solved for subtitles.

@TheOverpassArsonist
Copy link

TheOverpassArsonist commented Oct 31, 2025

What is the requested change by the bot? I read through it's report and it just seems to say that the pull request is valid, it doesn't seem to request any change, but Github is saying a change was requested. Have I misunderstood something or is the bot acting up? There are earlier replies that note potential issues, but the latest May 27th reply suggests that the PR is valid so I'm not following what the requested change is.

Asking because it seems like it's been a while without any activity and I'm running into an issue that I think may be related. (not on a Fire TV device, LibreElec on Pi4, but I'm getting similarly inconsistent audio streams with no obvious pattern, and this being a race condition with that exact effect seems like it'd check out as the root cause.)

@oddstr13
Copy link
Member

I'm not familiar enough with these parts to properly review it, and my spider senses are tingling saying there is something strange going on somewhere here.

I have not dedicated the required hours to get to the bottom of what's going on here, mainly because I haven't personally noticed any issues with it.

Feel free to apply these changes on top of latest release/master, and report back your findings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-testing Some testing is requested/required before merging

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants