feat: playlist mode with lifecycle hardening, PipeWire safety, and freezing fixes#253
Open
rhafaelcm wants to merge 22 commits into
Open
feat: playlist mode with lifecycle hardening, PipeWire safety, and freezing fixes#253rhafaelcm wants to merge 22 commits into
rhafaelcm wants to merge 22 commits into
Conversation
Allows users to create a video playlist that rotates across all monitors. Each video plays to completion N times (configurable repeat count) before advancing to the next video in a circular loop. Uses VLC's MediaPlayerEndReached event for precise end-of-video detection. Made-with: Cursor
- Fix _checkDefaultSource crashing with None path for non-video modes - Add "Add to Playlist" option to right-click context menu in Local Video tab - Remove separate Add button from Playlist tab, update description - Add PKG_CONFIG_PATH and runtime env vars for lib64 in Flatpak manifest Made-with: Cursor
Add LIBVA_DRIVERS_PATH with Intel, Mesa, and Mesa default driver paths so libva can auto-detect the correct VA-API driver for any GPU. Made-with: Cursor
… button - Add fade-out/fade-in opacity transition between playlist videos - Show thumbnails and video duration in the Playlist tab (TreeView) - Display video duration below each thumbnail in Local Video tab - Add "Add All to Playlist" button in Local Video tab - Add get_video_duration() and get_thumbnail_pixbuf() utility functions - Fix thumbnail loading bug when generate_thumbnail returns True but path is None Made-with: Cursor
- Static wallpaper only set once (first video) instead of every transition, and now runs in a background thread to avoid blocking the GTK main loop - Add _is_transitioning flag to prevent race conditions from overlapping fade animations when MediaPlayerEndReached fires multiple times - Move get_video_duration calls to background threads in playlist store to avoid blocking UI with synchronous ffprobe calls - Use GLib.idle_add for thread-safe ListStore updates in IconView Made-with: Cursor
Wrap generate_thumbnail and get_thumbnail in try/except to catch GLib.GError when the external thumbnailer (totem-video-thumbnailer) fails for unsupported or corrupted videos, preventing thread crashes. Made-with: Cursor
- Add "Clear playlist" button with edit-clear-all-symbolic icon - Show "Unsaved changes" label when playlist is edited (add, remove, move, clear) that disappears after clicking "Apply Playlist" Made-with: Cursor
…d resource leaks - Fix critical race condition in Fade class using cycle_id to prevent orphaned Timer thread accumulation across playlist transitions - Release old VLC Media objects in set_media() to prevent memory leak - Protect _is_transitioning with try/finally to prevent permanent deadlock - Cache ffprobe video dimensions in _setup_playlist to avoid blocking GTK main thread on every transition - Fix Image.open() file descriptor leak in set_static_wallpaper - Add diagnostic logs: health check every 60s, transition tracking, media end counting, thread count monitoring Made-with: Cursor
- Add logging.basicConfig() in player process so diagnostic logs appear - Replace synchronous ffprobe pre-cache of all 43 videos (which blocked GTK main loop for 20-90s) with lazy background probing per video - Fix config save race: save config inside _setup_player() after setting mode and Default, so the player process reads correct values from disk Made-with: Cursor
…repeats The stop()/play() cycle for repeating videos was reinitializing the hardware decoder on every repeat, leaking VA-API contexts and memory (549MB -> 817MB in 7 media ends). VLC eventually entered a dead state. Delegate repeat handling to VLC via :input-repeat media option, which loops internally without destroying the decoder pipeline. Made-with: Cursor
Stop all VLC players explicitly before set_media() during transitions to ensure VA-API decoder resources are fully released before creating new ones. Previously, set_media() on a still-playing player caused overlapping decoder contexts and memory growth. Add a watchdog timer (30s) that detects stuck playback by monitoring player position, forcing advance to next video if VLC enters a dead state. Also switch health check memory metric from ru_maxrss (peak) to VmRSS (current RSS) for accurate diagnostics. Made-with: Cursor
VLC's player.stop() waits synchronously for decoder threads to finish, but those threads need the GTK main thread for X11 video output rendering, creating a deadlock. set_media() handles the swap internally without this issue. The watchdog timer recovers from stuck VLC decoder states. Made-with: Cursor
Transition between playlist videos is now instant instead of fading opacity out/in, simplifying the transition logic. Made-with: Cursor
Replace in-place set_media() with full VLCWidget recreation on each playlist video change. The old widget is cleaned up in a background thread, avoiding the GTK main thread deadlock caused by VLC's internal decoder stop. Made-with: Cursor
Made-with: Cursor
Synchronize VLC widget cleanup to release GPU decoder buffers before allocating a new instance. Previously, cleanup ran in a daemon thread that could fail to complete, causing GPU memory to grow unbounded. - Make replace_vlc_widget() cleanup synchronous (stop, release media, release player/instance, gc.collect) before creating the new widget - Detach MediaPlayerEndReached event from old player before replacement - Guard GLib timers with _timers_active flag to prevent accumulation - Release vlc.Media in VLCWidget.cleanup() before releasing the player - Call gc.collect() after VLC cleanup to force GPU resource deallocation Made-with: Cursor
… scaling on 2K monitors video_set_crop_geometry() was called before start_playback(), so VLC could silently ignore the crop on newly created widget instances. Now schedule_centercrop() uses the MediaPlayerVout event plus a timed retry fallback to ensure the geometry is applied once the video output actually exists. Also fixes _on_size_changed() calling non-existent methods on Gdk.Monitor instead of the player window. Made-with: Cursor
…laylist Introduces playlist media info tracking (config v6) to probe video codec via ffprobe and persist metadata per playlist item. AV1 videos without HW decode are marked as problematic, forcing software decode and showing a visual warning (markup label, orange color, tooltip) in the GUI. The player logs codec info on playback, tracks recovery attempts per item, and can advance past persistently stalling AV1 videos automatically. Made-with: Cursor
The playlist mode was repeatedly opening/closing the libVLC audio output, which caused PipeWire to lock up after a while. Switching to media reuse instead caused the opposite: PipeWire stayed alive but Hidamari itself froze. Since playlist mode never needs audio, we now build the libVLC instance without any audio backend at all and harden the playlist lifecycle to keep long sessions stable. - Build libVLC with --no-audio --aout=none whenever the player starts in MODE_PLAYLIST (VLCWidget.disable_audio propagated from VideoPlayer via PlayerWindow). Recovery preserves the flag automatically. - Always add :no-audio per media in _set_playlist_media (defense in depth, not just on non-primary monitors). - Disable ScaleVolume / ToggleMute in the GUI when mode is playlist, with an explanatory tooltip; refreshed after every mode-changing apply so the user sees it immediately. - Soft-restart proactively recreates the VLCWidget every 25 transitions via PLAYLIST_SOFT_RESTART_EVERY to keep the decoder healthy on long sessions. - Sync 250 ms time.sleep between Instance release and recreation inside recover_vlc_widget gives libVLC time to free the decoder before a fresh Instance starts on the same widget. - Circuit breaker: PLAYLIST_RECOVERY_MAX_IN_WINDOW recoveries within PLAYLIST_RECOVERY_WINDOW_SEC marks the current video as problematic via mark_playlist_media_info_stalled and advances synchronously. - Deterministic sanitization before recreate: bump preload generation and cancel fade / fade_opacity on every window so workers in flight never touch a widget that's about to be destroyed. - window.stop() inline before set_media releases the previous stream synchronously on every transition. - Watchdog less aggressive: trigger recovery on 2 consecutive stalls (3 for AV1 + software decode), raise interval from 30 s to 45 s, and make recover/advance calls synchronous (no GLib.idle_add hop). Made-with: Cursor
Add a small italic note under the playlist tab heading clarifying that audio output is disabled in playlist mode to keep PipeWire stable. The mute toggle and volume slider are already greyed out via set_audio_controls_for_mode(), but a tooltip alone is easy to miss; the inline note makes the rationale visible at a glance. Made-with: Cursor
Setting set_sensitive(False) on the ToggleMute and ScaleVolume widgets greyed them out in the panel but left the underlying Gio.SimpleAction 'mute' enabled, so the hamburger popover and the systray could still flip is_mute. The widgets only ever invoked --no-audio --aout=none libVLC instances, so audio never came back on, but the persisted config still drifted and would resurface the next time the user switched to video mode. - gui: also disable the 'mute' Gio action via lookup_action(...). set_enabled(False), so menu items, accelerators and programmatic app.activate_action calls all respect the playlist gate. - server: add _is_audio_disabled_mode() and short-circuit the is_mute and volume DBus setters when in MODE_PLAYLIST. This covers the systray menu and any other DBus client that talks straight to the server, keeping the persisted config consistent regardless of the entry point. Made-with: Cursor
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
stop()(triggered byset_media()on an active player) blocks waiting for decoder threads, which in turn need the GTK main thread for X11 video output — causing a deadlock. To solve this:set_media()with fullVLCWidgetrecreation on each playlist video change — the old widget is cleaned up synchronously, completely avoiding the GTK main thread deadlockvideo_set_crop_geometry()was being called before VLC created its video output, causing intermittent failure to fill the screen (especially on 2K monitors). Now uses theMediaPlayerVoutevent with a timed retry fallback to apply crop geometry only after the video output is ready. Also fixed_on_size_changed()calling non-existent methods onGdk.Monitor.ffprobeand persist per-item media info (codec, dimensions, duration, problematic flag). AV1 videos without HW decode are marked as problematic, forced to software decode, and shown with an orange tooltip in the playlist UI; the player logs codec info on playback, tracks recovery attempts per item, and advances past videos that keep stalling instead of looping forever.--no-audio --aout=nonewhenever the player starts inMODE_PLAYLIST(VLCWidget.disable_audiopropagated fromVideoPlayerthroughPlayerWindow); recovery preserves the flag automatically.:no-audioper media in_set_playlist_media(defense in depth, not just on non-primary monitors).VLCWidgeteveryPLAYLIST_SOFT_RESTART_EVERY = 25transitions to keep the decoder healthy on long sessions.time.sleepbetween Instance release and recreation insiderecover_vlc_widgetgives libVLC time to free the decoder before a fresh Instance starts on the same widget.PLAYLIST_RECOVERY_MAX_IN_WINDOWrecoveries withinPLAYLIST_RECOVERY_WINDOW_SECmark the current video as problematic and advance synchronously instead of looping.fade/fade_opacityon every window so in-flight workers never touch a widget about to be destroyed.window.stop()inline beforeset_mediareleases the previous stream synchronously on every transition.GLib.idle_addhop).Gio.SimpleActionmuteis alsoset_enabled(False)so the hamburger popover, keyboard accelerators andapp.activate_actionare all blocked — not just clicks on the widget. Server-sideis_muteandvolumeDBus setters short-circuit whenMODE_PLAYLISTis active, covering the systray menu and any other DBus client.Commits
6903541feature: add playlist mode with repeat count for all monitors4c82608fix: resolve TypeError in config load and improve playlist UXc7aa0e1feat: enable VA-API hardware acceleration in Flatpak692ca64feat: smooth playlist transitions, thumbnails/duration in UI, add-all button5b46ad3fix: resolve freezing during playlist transitions60740b2fix: handle thumbnail generation errors gracefully6b8f46bfeat: add clear playlist button and unsaved changes warning186e572fix: resolve playlist freezing caused by Fade timer race condition and resource leaks7398abafix: resolve playlist freeze caused by blocking ffprobe and config race7962ffbfix: use VLC input-repeat to prevent VA-API decoder leak on playlist repeats9d33b14fix: stop players before loading new media and add stuck-video watchdog8462addfix: remove explicit stop() that caused GTK main thread deadlock134880arefactor: remove fade transition effect from playlist video switching2eff7a0fix: recreate VLCWidget per playlist transition to prevent deadlock43b56f8feat: open GUI on the tab matching the active playback mode350e46efix: prevent GPU memory leak during playlist transitions10ef8fffix: apply centercrop after VLC vout is ready to prevent intermittent scaling on 2K monitorsa9a6ae4Stabilize VLC playlist player lifecyclef8d704cfeat: detect and flag AV1 videos without hardware decode support in playlist9ecf902fix: prevent PipeWire crashes and Hidamari freezes in playlist modecb72e87feat: surface audio-disabled notice on the playlist tab3bd3761fix: actually block mute/volume changes while in playlist modeTest plan
wpctl status(orpactl list source-outputs) and confirm there is novlcclient connected to PipeWire[Playlist Health]lines (60 s interval) with stable RSS[Playlist] Soft-restartlog line every 25 transitionsRecovery #Nloopsgdbus call ... org.freedesktop.DBus.Properties.Set ... is_mute) — all attempts should be ignored and~/.config/hidamari/config.jsonshould keep the originalis_mutevalue--no-audio) and the volume / mute controls are sensitive againBuilt and packaged: https://github.com/rhafaelcm/hidamari/releases/tag/fork-v0.1.0-playlist