Skip to content

fix(player): restore embedded mpv playback - #64

Closed
ModerRAS wants to merge 1 commit into
masterfrom
codex/fix-hk1-experimental-players
Closed

fix(player): restore embedded mpv playback#64
ModerRAS wants to merge 1 commit into
masterfrom
codex/fix-hk1-experimental-players

Conversation

@ModerRAS

@ModerRAS ModerRAS commented Aug 11, 2026

Copy link
Copy Markdown
Owner

Summary

  • use mediacodec_embed on GLES 2 devices and keep the cached VO synchronized across Surface lifecycle changes
  • fix mpv firequalizer gain_entry syntax so audio DSP initializes correctly
  • remove the unusable experimental GL runtime, dedicated surface, second Exo instance, and TV/Web entry points
  • retain the legacy EXPERIMENTAL_GL serialization token and migrate existing preferences to standard Exo
  • preserve the standard Exo libass subtitle integration from current master

Verification

  • ./gradlew test :app:assembleDebug
  • git diff --check
  • SDR MKV playback verified on a GLES 2 Android TV device through HDMI capture with advancing frames
  • HDR HEVC playback verified through HDMI capture with MediaCodec hardware decode, VO: [mediacodec_embed], and audio/video playing
  • confirmed no GL runtime/UI/Web references remain outside legacy preference migration

Versioning

Patch-level player fix. The branch is rebased onto the current 2.6.x master baseline; no major/minor version bump is needed.

@coderabbitai

coderabbitai Bot commented Aug 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR removes the experimental GL playback pipeline, standardizes ExoPlayer wiring, migrates legacy backend values, adds GLES-aware MPV output selection, updates FFmpeg filter syntax, and simplifies TV player surface handling.

Changes

Playback backend migration

Layer / File(s) Summary
Backend migration contracts
core/model/..., data/src/test/..., app/src/test/..., web-control/src/test/..., ui-tv/src/main/...
Legacy EXPERIMENTAL_GL values normalize to STANDARD_EXO. The legacy backend is removed from supported choices and debug aliases. Labels, icons, launch tests, and preference migration tests use standard ExoPlayer.
Standard ExoPlayer wiring
player-core/src/main/.../DiModule.kt, ExoPlaybackController.kt, PlayerFactoryImpl.kt, player-core/src/test/.../ExoPlaybackControllerLazyInitTest.kt, player-core/src/main/.../Experimental*
Experimental ExoPlayer injection, lifecycle handling, video-effects dispatch, renderers, pipeline helpers, and related tests are removed. Exo playback now uses only the standard player.
GLES-aware MPV output
player-core/src/main/.../DeviceGlCapabilities.kt, EmbeddedMpvSessionOptions.kt, player-mpv-android/src/main/..., player-core/src/test/...
GLES capability resolution is added. GLES2 selects mediacodec_embed. MPV output state updates use a shared helper.
Audio and TV rendering cleanup
player-core/src/main/.../AudioDspMpvOptions.kt, ui-tv/src/main/.../PlayerScreen.kt, ui-tv/src/main/.../GLVideoSurfaceView.kt, ui-tv/src/test/...
FFmpeg FIR entries use entry(frequency,magnitude). Dedicated GL surface rendering and its tests are removed. PlayerScreen uses native VLC or standard PlayerView hosts.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PlayerScreen
  participant ExoPlaybackController
  participant StandardExoPlayer
  PlayerScreen->>ExoPlaybackController: start playback
  ExoPlaybackController->>StandardExoPlayer: prepare and play
  ExoPlaybackController->>StandardExoPlayer: release standard player
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary change: restoring embedded MPV playback on HK1 devices.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/fix-hk1-experimental-players

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ui-tv/src/main/kotlin/com/miruplay/tv/ui/player/PlayerScreen.kt (1)

448-481: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

currentPosition as an effect key can cancel the capture delay forever.

currentPosition changes on every position tick. Each change restarts this LaunchedEffect and cancels delay(250). The code writes lastStandardDebugCaptureAttempt before the delay, so restarts return early until the position advances by STANDARD_DEBUG_CAPTURE_RETRY_INTERVAL_MS, and then the same cancellation repeats. If the position tick interval is below 250 ms, captureCurrentFrame never runs.

Key the effect on the pending label and the boolean gate instead of the raw position.

🐛 Proposed fix for the capture scheduling
     LaunchedEffect(
-        playbackState,
-        currentPosition,
+        pendingDebugCaptureLabel,
+        shouldCaptureStandardDebugFrame,
         playerViewRef,
     ) {
         val pendingLabel = viewModel.pendingGlFrameCaptureLabel() ?: return@LaunchedEffect

Run the following script to check how often the position state emits:

#!/bin/bash
# Description: Find the player position tick interval that drives currentPosition.
fd -e kt -g '*PlayerViewModel*' | while IFS= read -r file; do
  rg -n -C 6 'currentPosition|_currentPosition|delay\(' "$file"
done
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ui-tv/src/main/kotlin/com/miruplay/tv/ui/player/PlayerScreen.kt` around lines
448 - 481, Update the LaunchedEffect around pendingGlFrameCaptureLabel to key
only on the pending capture label and shouldCaptureStandardDebugFrame, removing
raw currentPosition and playerViewRef from the effect keys. Continue reading the
latest currentPosition and resolving playerViewRef inside the effect so the 250
ms delay can complete without position-tick cancellation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@player-core/src/main/kotlin/com/miruplay/tv/player/DeviceGlCapabilities.kt`:
- Around line 7-12: Update resolveGlEsMajorVersion so the decoded upper-16-bit
major version cannot return zero: treat a zero result from reqGlEsVersion shr as
ES2 while preserving the existing undefined and non-positive fallbacks. Add a
test case to EmbeddedMpvSessionOptionsTest covering a positive packed value
whose decoded major version is zero.

In
`@player-core/src/main/kotlin/com/miruplay/tv/player/EmbeddedMpvSessionOptions.kt`:
- Line 16: Update the call to effectiveEmbeddedMpvVideoOutput to pass the
resolved glEsMajorVersion from EmbeddedMpvSessionOptions, ensuring ES2 devices
select mediacodec_embed instead of gpu-next.

---

Outside diff comments:
In `@ui-tv/src/main/kotlin/com/miruplay/tv/ui/player/PlayerScreen.kt`:
- Around line 448-481: Update the LaunchedEffect around
pendingGlFrameCaptureLabel to key only on the pending capture label and
shouldCaptureStandardDebugFrame, removing raw currentPosition and playerViewRef
from the effect keys. Continue reading the latest currentPosition and resolving
playerViewRef inside the effect so the 250 ms delay can complete without
position-tick cancellation.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 405bd919-5808-4798-8c43-650621431a5e

📥 Commits

Reviewing files that changed from the base of the PR and between 2d3fd42 and f32a82b.

📒 Files selected for processing (29)
  • app/src/test/kotlin/com/miruplay/tv/LaunchTestSourceContentModeTest.kt
  • core/model/src/main/kotlin/com/miruplay/tv/model/PlaybackUiConventions.kt
  • core/model/src/main/kotlin/com/miruplay/tv/model/ToneMappingModels.kt
  • data/src/test/kotlin/com/miruplay/tv/data/preferences/PlaybackPreferencesManagerTest.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/AudioDspMpvOptions.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/DeviceGlCapabilities.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/DiModule.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/EmbeddedMpvSessionOptions.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/ExoPlaybackController.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/ExperimentalHdrSdrVideoGraphFactory.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/ExperimentalHdrSurfaceMediaCodecVideoRenderer.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/ExperimentalRenderersFactory.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/ExperimentalVideoPipelineMode.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/PlaybackPlayerQualifiers.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/PlayerFactoryImpl.kt
  • player-core/src/test/kotlin/com/miruplay/tv/player/AudioDspMpvOptionsTest.kt
  • player-core/src/test/kotlin/com/miruplay/tv/player/EmbeddedMpvSessionOptionsTest.kt
  • player-core/src/test/kotlin/com/miruplay/tv/player/ExoPlaybackControllerLazyInitTest.kt
  • player-core/src/test/kotlin/com/miruplay/tv/player/ExperimentalHdrSdrVideoGraphFactoryTest.kt
  • player-core/src/test/kotlin/com/miruplay/tv/player/ExperimentalHdrSurfaceMediaCodecVideoRendererTest.kt
  • player-core/src/test/kotlin/com/miruplay/tv/player/ExperimentalVideoPipelineModeTest.kt
  • player-mpv-android/src/main/kotlin/is/xyz/mpv/BaseMPVView.kt
  • player-mpv-android/src/main/kotlin/is/xyz/mpv/MiruMpvSurfaceView.kt
  • ui-tv/src/main/kotlin/com/miruplay/tv/ui/player/GLVideoSurfaceView.kt
  • ui-tv/src/main/kotlin/com/miruplay/tv/ui/player/PlayerScreen.kt
  • ui-tv/src/main/kotlin/com/miruplay/tv/ui/settings/AddSourceScreen.kt
  • ui-tv/src/test/kotlin/com/miruplay/tv/ui/player/GLVideoSurfaceViewAttachmentPolicyTest.kt
  • web-control/src/main/kotlin/com/miruplay/tv/webcontrol/PlaybackDebugConfigMapping.kt
  • web-control/src/test/kotlin/com/miruplay/tv/webcontrol/PlaybackDebugConfigMappingTest.kt
💤 Files with no reviewable changes (13)
  • player-core/src/test/kotlin/com/miruplay/tv/player/ExperimentalHdrSdrVideoGraphFactoryTest.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/ExperimentalHdrSdrVideoGraphFactory.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/ExperimentalRenderersFactory.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/ExperimentalHdrSurfaceMediaCodecVideoRenderer.kt
  • player-core/src/test/kotlin/com/miruplay/tv/player/ExperimentalHdrSurfaceMediaCodecVideoRendererTest.kt
  • ui-tv/src/test/kotlin/com/miruplay/tv/ui/player/GLVideoSurfaceViewAttachmentPolicyTest.kt
  • player-core/src/test/kotlin/com/miruplay/tv/player/ExperimentalVideoPipelineModeTest.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/ExperimentalVideoPipelineMode.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/PlaybackPlayerQualifiers.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/PlayerFactoryImpl.kt
  • ui-tv/src/main/kotlin/com/miruplay/tv/ui/player/GLVideoSurfaceView.kt
  • player-core/src/main/kotlin/com/miruplay/tv/player/DiModule.kt
  • web-control/src/main/kotlin/com/miruplay/tv/webcontrol/PlaybackDebugConfigMapping.kt

Comment on lines +7 to +12
fun resolveGlEsMajorVersion(reqGlEsVersion: Int): Int =
when {
reqGlEsVersion == ConfigurationInfo.GL_ES_VERSION_UNDEFINED -> 2
reqGlEsVersion <= 0 -> 2
else -> reqGlEsVersion shr 16
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
file=$(git ls-files | grep -F 'player-core/src/main/kotlin/com/miruplay/tv/player/DeviceGlCapabilities.kt' | head -n1)
printf '%s\n' "$file"
wc -l "$file"
cat -n "$file"
printf '\nTest references:\n'
rg -n -C 4 'resolveGlEsMajorVersion|EmbeddedMpvSessionOptionsTest|reqGlEsVersion|GL_ES_VERSION_UNDEFINED' .

Repository: ModerRAS/MiruPlay

Length of output: 6816


🌐 Web query:

Android SDK ConfigurationInfo reqGlEsVersion "major" "minor" 16 bits GL_ES_VERSION_UNDEFINED

💡 Result:

In the Android SDK, the property reqGlEsVersion represents the OpenGL ES version required by an application, using a 32-bit integer format where the upper 16 bits define the major version and the lower 16 bits define the minor version [1][2][3]. GL_ES_VERSION_UNDEFINED is a constant defined as 0, which serves as the default value for reqGlEsVersion when no specific version has been set [1][2][3]. Key details regarding this configuration include: Bitwise Representation: The value is encoded such that the major and minor versions are separated into the high and low 16-bit segments, respectively [1][3]. For example, a value of 0x00010002 corresponds to OpenGL ES version 1.2 [2][3]. You can extract these values using the getGlEsVersion method, which parses this integer into a readable "major.minor" string [1][2][3]. Usage in Manifest: Developers declare these requirements in their AndroidManifest.xml using the uses-feature element with the android:glEsVersion attribute [4][5]. If an application does not specify this attribute, the system defaults to assuming the app requires OpenGL ES 1.0, which is supported by all Android devices [4]. Context in Data Structures: This field appears in both the ConfigurationInfo and FeatureInfo classes [1][6]. In FeatureInfo, the reqGlEsVersion is only considered valid if the name field of the object is null, indicating that the feature represents an OpenGL ES version requirement [6][3].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' 'Relevant test section:'
cat -n player-core/src/test/kotlin/com/miruplay/tv/player/EmbeddedMpvSessionOptionsTest.kt | sed -n '145,170p'
printf '\nCall sites and related GL-version logic:\n'
rg -n -C 3 'resolveDeviceGlEsMajorVersion|resolveGlEsMajorVersion|glEsMajor|GLES' player-core/src/main player-core/src/test
printf '\nBehavioral probe for packed values:\n'
python3 - <<'PY'
values = [0, -1, 0x0000FFFF, 0x00010002, 0x00030000, 0x00030002]
for value in values:
    signed = value if value < 2**31 else value - 2**32
    major = signed >> 16
    fallback = 2 if signed == 0 or signed <= 0 else major
    corrected = 2 if signed <= 0 else (major if major > 0 else 2)
    print(f"{value:`#010x`}: encoded major={(value & 0xffffffff) >> 16}, current={fallback}, corrected={corrected}")
PY

Repository: ModerRAS/MiruPlay

Length of output: 11017


Fallback to ES2 when the packed major version is zero.

ConfigurationInfo.reqGlEsVersion stores the major version in the upper 16 bits. A positive value such as 0x0000FFFF currently resolves to 0, but invalid values must fall back to ES2. Coerce a zero decoded major version to 2 and add this case to EmbeddedMpvSessionOptionsTest.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@player-core/src/main/kotlin/com/miruplay/tv/player/DeviceGlCapabilities.kt`
around lines 7 - 12, Update resolveGlEsMajorVersion so the decoded upper-16-bit
major version cannot return zero: treat a zero result from reqGlEsVersion shr as
ES2 while preserving the existing undefined and non-positive fallbacks. Add a
test case to EmbeddedMpvSessionOptionsTest covering a positive packed value
whose decoded major version is zero.

shaderPaths: List<String>,
speed: Float = 1.0f,
runtimeAbiIs32Bit: Boolean = isEmbeddedMpvRuntime32Bit(),
glEsMajorVersion: Int = 3,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 8 --glob '*.kt' \
  'buildEmbeddedMpvSessionOptions\s*\(|effectiveEmbeddedMpvVideoOutput\s*\(' \
  player-core player-mpv-android ui-tv

Repository: ModerRAS/MiruPlay

Length of output: 26245


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- all references ---'
rg -n -C 5 --hidden --glob '!build/**' --glob '!node_modules/**' \
  'buildEmbeddedMpvSessionOptions|effectiveEmbeddedMpvVideoOutput|deviceGlEsMajorVersion|resolveGlEsMajorVersion' .

printf '%s\n' '--- candidate source files ---'
git ls-files | rg 'EmbeddedMpvSessionOptions|ExoPlaybackController|\.kt$|\.java$' | head -200

Repository: ModerRAS/MiruPlay

Length of output: 41830


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- WebControlService production call ---'
sed -n '1075,1145p' web-control/src/main/kotlin/com/miruplay/tv/webcontrol/WebControlService.kt

printf '%s\n' '--- Embedded MPV option functions ---'
sed -n '1,45p;125,145p' player-core/src/main/kotlin/com/miruplay/tv/player/EmbeddedMpvSessionOptions.kt

printf '%s\n' '--- GLES capability resolution ---'
cat -n player-core/src/main/kotlin/com/miruplay/tv/player/DeviceGlCapabilities.kt

printf '%s\n' '--- all production uses of the diagnostic value ---'
rg -n -C 4 --glob 'src/main/**/*.kt' \
  'effectiveEmbeddedMpvVo|embeddedMpvVo|effectiveEmbeddedMpvVideoOutput' web-control player-core app

Repository: ModerRAS/MiruPlay

Length of output: 7856


Pass the resolved GLES version to effectiveEmbeddedMpvVideoOutput. The production debug snapshot omits glEsMajorVersion, so ES2 devices report gpu-next instead of mediacodec_embed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@player-core/src/main/kotlin/com/miruplay/tv/player/EmbeddedMpvSessionOptions.kt`
at line 16, Update the call to effectiveEmbeddedMpvVideoOutput to pass the
resolved glEsMajorVersion from EmbeddedMpvSessionOptions, ensuring ES2 devices
select mediacodec_embed instead of gpu-next.

@ModerRAS
ModerRAS force-pushed the codex/fix-hk1-experimental-players branch from f32a82b to 9eb81c4 Compare August 11, 2026 01:10
@ModerRAS ModerRAS closed this Aug 11, 2026
@ModerRAS
ModerRAS deleted the codex/fix-hk1-experimental-players branch August 11, 2026 01:10
@ModerRAS ModerRAS changed the title fix(player): restore HK1 embedded mpv playback fix(player): restore embedded mpv playback Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant