Skip to content

refactor(windows): rewrite native player#204

Merged
kdroidFilter merged 4 commits intomasterfrom
refactor/windows-native-player
Apr 17, 2026
Merged

refactor(windows): rewrite native player#204
kdroidFilter merged 4 commits intomasterfrom
refactor/windows-native-player

Conversation

@kdroidFilter
Copy link
Copy Markdown
Owner

@kdroidFilter kdroidFilter commented Apr 16, 2026

Fixes #139.

Summary

Large refactor of the Windows backend (native DLL + Kotlin state). Cleans up a number of long-standing stability issues along the way.

Native

  • Full migration to ComPtr and RAII wrappers (new ComHelpers.h); no more manual Release() / CloseHandle.
  • Responsibilities split across AudioManager, MediaFoundationManager, HLSPlayer, NativeVideoPlayer.
  • Audio is now the timing master. Video compensates via audioLatencyMs; the old wall-clock drift correction / sleep / sample dropping is gone.
  • AcquireNextSample drops late frames in a tight native loop and caches early ones — it never replays stale frames.
  • 300 ms safety timeout on the cached sample so a stalled audio clock cannot freeze the picture indefinitely.
  • Dedicated audio IMFSourceReader — video ReadSample no longer serialises with audio.
  • MFVideoFormat_RGB32 alpha byte forced opaque with AVX2 (scalar fallback) to avoid washed-out colours in Skia compositing.
  • ShutdownMediaFoundation can now be forced on JVM shutdown so MF worker threads exit before the DLL is unloaded.

Kotlin (WindowsVideoPlayerState)

  • JVM shutdown hook calls ShutdownMediaFoundation() — fixes a 0x87A KERNELBASE crash on window close.
  • dispose() runs native cleanup synchronously and joins the producer coroutine before freeing the reader (no more UAF race with pSourceReader.Reset()).
  • Seek coalescing via pendingSeekTarget + seekInFlight. The producer stays alive across seeks (serialised with videoReaderMutex + isSeeking).
  • consumeFrames no longer clobbers _progress while userDragging is true, so dragging the slider commits the seek where the user released.
  • processOneFrame stops closing the double-buffer bitmaps on resolution change — fixes a Skia use-after-free visible as Image.makeFromBitmap null deref.
  • Per-instance volume persistence; cleaner error reporting on loop / seek failures.

Test plan

  • Play / pause / seek (click + drag) / playback-speed change on a local H.264 file
  • Close the window while a video is playing → clean exit, no 0x87A hs_err
  • HLS stream: play, seek inside the live window, EOF handling
  • Resize the window repeatedly during playback → no crash, no stuck frame

… and robust seek

Large refactor of the Windows backend (native DLL + Kotlin state).

Native:
- Migrate all COM usage to ComPtr; wrap HANDLE/CRITICAL_SECTION in RAII helpers (ComHelpers.h, Utils)
- Split responsibilities across AudioManager / MediaFoundationManager / HLSPlayer / NativeVideoPlayer
- Audio becomes the timing master; video compensates via audioLatencyMs (no wall-clock drift correction, no sample dropping)
- AcquireNextSample: drop late frames in a tight native loop, cache early ones, never replay stale frames
- 300 ms safety timeout on the cached sample so a stalled audio clock cannot freeze the picture
- Dedicated audio IMFSourceReader so video ReadSample never serialises with audio
- RGB32 alpha byte forced opaque (SIMD / AVX2 with scalar fallback)
- ShutdownMediaFoundation can now be forced on JVM shutdown so MF worker threads exit before DLL unload

Kotlin (WindowsVideoPlayerState):
- JVM shutdown hook calls ShutdownMediaFoundation to fix exit code 0x87A crash on window close
- dispose() cleans up natively synchronously and joins the producer coroutine before freeing the reader
- Seek coalescing via pendingSeekTarget + seekInFlight; producer keeps running across seeks (videoReaderMutex + isSeeking)
- consumeFrames no longer clobbers _progress while userDragging is true (drag now commits to the right position)
- processOneFrame stops closing the double-buffer bitmaps on resolution change to avoid a Skia use-after-free
- Proper volume persistence per instance; graceful error reporting on loop/seek failures

Fixes #139 (video freezes after seek under GraalVM native-image).
@kdroidFilter kdroidFilter changed the title refactor(windows): rewrite native player, fix seek under GraalVM (#139) refactor(windows): rewrite native player Apr 16, 2026
…ng resolution changes

- Add deferred-close queue to safely manage bitmap lifecycle during adaptive bitrate (HLS) resolution changes.
- Ensure old bitmaps are cleared only after a safe number of frames to prevent Skia use-after-free crashes.
- Implement `drainPendingCloseBitmaps` to manage and clean pending bitmaps effectively.
- Extend JVM buildArgs to enable custom URL protocols (`--enable-url-protocols=http,https`).
Bugs:
- Guard against clock-skew underflow when computing cached-sample heldMs
- Stop audio thread before the presentation clock in StopPlayback
- InitWASAPI no longer takes ownership of pSourceAudioFormat; caller transfers
- Triple-buffer Skia bitmaps to remove producer/Compose read race
- dispose() avoids runBlocking on AWT EDT to prevent potential deadlock

Performance:
- SeekMedia wakes the audio thread via event before taking csAudioFeed
- Underflow-safe UINT32 arithmetic on framesFree

Cleanup:
- HLSPlayer stored via ComPtr<HLSPlayer> instead of raw pointer
- HLSPlayer frame buffer uses std::vector<BYTE> instead of new[]/delete[]
- Replace 0xC00D36C4 magic with MF_E_UNSUPPORTED_BYTESTREAM_TYPE
- Extract NVP_MIN/MAX_PLAYBACK_SPEED constants in NativeVideoPlayer.h
- Extract startVideoPipeline() helper to dedupe producer/consumer launches
- Replace goto cleanup in InitWASAPI with a RAII scope guard
- GetMediaDuration returns S_FALSE when duration is absent (vs hard error)
- Shutdown logs a warning when live instances remain
- Skip redundant 2ch/48kHz audio fallback when already canonical
- Drop the default nullptr argument on InitWASAPI
- Remove dead clearAllResourcesSync path
@kdroidFilter kdroidFilter merged commit 7a47043 into master Apr 17, 2026
10 checks passed
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.

Preview doesn't update after seeking on Windows

1 participant