A libVLC Wrapper for H.265 IoT Live Streams on Android
IoT video on Android often means wrestling with H.265 hardware-decoder black screens and Surface lifecycle bugs. This wrapper codifies a working set of VLC flags and a detach/reattach pattern that avoids the most common native crashes, giving teams a starting point that already handles the ugly edge cases.
EasyVlcPlayer is a Kotlin encapsulation of libVLC 3.6.5 built for Android apps that need to play H.265-encoded RTSP, RTMP, or HTTPS live streams from IoT hardware. It forces software decoding for compatibility, sets a 20fps cap and 800ms buffer to balance latency against jitter, and provides explicit `pauseRender`/`resumeRender` methods that detach and re-attach the Surface when the activity moves to the background or returns.
The wrapper also includes a full Activity integration example that polls a device API every second to start a live session, receives the stream URL via an event bus, and wires up play/pause/stop controls. Several sharp edges are documented: the `play()` method forcibly rewrites any URL scheme to HTTPS, the internal VLC event listener is commented out, and the progress-update runnable is defined but never started.
A comparison between frame-by-frame parsing and VLC's stream-based demuxing explains the trade-off: manual NAL-unit splitting can hit sub-100ms latency but collapses under network jitter, while VLC's internal buffer absorbs fluctuation at the cost of 300ms–2s delay. The root-cause breakdown for slow start, stutter, and black screens points to cache sizing, missing keyframes, and the absence of `--drop-late-frames`.
Disabling hardware decoding is a deliberate compatibility play, not a performance oversight — it sidesteps the fragmented H.265 hardware-decoder landscape on Android at the cost of higher CPU usage.
The forced HTTPS scheme rewrite is a surprising design choice that silently breaks RTSP and RTMP, suggesting the wrapper was tuned for a specific backend that proxies all streams over TLS.
Commenting out VLC’s event listener while exposing an `OnPlayListener` interface creates a false sense of observability: the wrapper can’t actually report most playback errors or completion events.
The progress infrastructure exists but is inert, which implies the code was either stripped from a more complete internal version or left as a placeholder that never got wired up.