diff --git a/lazy_player/main_window.py b/lazy_player/main_window.py index febe553..f39aae7 100644 --- a/lazy_player/main_window.py +++ b/lazy_player/main_window.py @@ -5,14 +5,15 @@ from datetime import datetime from pathlib import Path from typing import Any, cast -from gi.repository import Gdk, GLib, GObject, Gtk, Pango +from gi.repository import Gdk, GLib, Gtk, Pango from .file_model import FileItem, FileListModel, FileType +from .reactive import Watcher, update_all_computed from .thumbnailer import Thumbnailer from .video_player import VideoPlayer -class MainWindow(Gtk.ApplicationWindow): +class MainWindow(Gtk.ApplicationWindow, Watcher): stack: Gtk.Stack list_view: Gtk.ListView list_model: FileListModel @@ -128,8 +129,7 @@ class MainWindow(Gtk.ApplicationWindow): # Setup video player self.video_player = VideoPlayer(self.video_widget) - self.video_player.connect("notify::is-playing", self._on_player_state_changed) - self.video_player.connect("notify::is-paused", self._on_player_state_changed) + self.watch(self._sync_overlay_grid) # Add both main menu and overlay to stack self.stack.add_named(main_box, "menu") @@ -493,16 +493,17 @@ class MainWindow(Gtk.ApplicationWindow): # Set absolute time when overlay should hide self.overlay_hide_time = self.now + timeout_seconds - def _on_player_state_changed(self, player: VideoPlayer, param: GObject.ParamSpec) -> None: - """Update grid visibility based on player state""" - is_playing = player.get_property("is-playing") - is_paused = player.get_property("is-paused") + def _sync_overlay_grid(self): + """Update grid visibility based on player state.""" - # Show grid only when paused and playing - if self.grid_overlay: - self.grid_overlay.set_visible(is_playing and is_paused) + is_playing = self.video_player.is_playing.value + is_paused = self.video_player.is_paused.value + self.grid_overlay.set_visible(is_playing and is_paused) def _on_tick(self, widget: Gtk.Widget, frame_clock: Gdk.FrameClock, data: Any) -> bool: + # Update all reactive values. + update_all_computed() + current_time = frame_clock.get_frame_time() / 1_000_000 current_time_str = datetime.now().strftime("%H:%M") diff --git a/lazy_player/video_player.py b/lazy_player/video_player.py index 4f759a6..194ff1e 100644 --- a/lazy_player/video_player.py +++ b/lazy_player/video_player.py @@ -4,6 +4,8 @@ from pathlib import Path from gi.repository import GObject, Gst, Gtk +from .reactive import Ref + DEFAULT_SEEK_FLAGS = Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT @@ -13,12 +15,15 @@ class VideoPlayer(GObject.Object): __gtype_name__ = "VideoPlayer" - is_playing = GObject.Property(type=bool, default=False) - is_paused = GObject.Property(type=bool, default=True) + is_playing: Ref[bool] + is_paused: Ref[bool] def __init__(self, picture: Gtk.Picture): super().__init__() + self.is_playing = Ref(False) + self.is_paused = Ref(True) + self.pipeline = Gst.Pipeline.new("video-player") playbin = Gst.ElementFactory.make("playbin", "playbin") @@ -71,24 +76,26 @@ class VideoPlayer(GObject.Object): # Start playing self.pipeline.set_state(Gst.State.PLAYING) - self.set_property("is-playing", True) - self.set_property("is-paused", False) + self.is_playing.value = True + self.is_paused.value = False def stop(self) -> None: """Stop playback and release resources""" + self.pipeline.set_state(Gst.State.NULL) - self.set_property("is-paused", True) - self.set_property("is-playing", False) + + self.is_playing.value = True + self.is_paused.value = False def toggle_play_pause(self) -> None: """Toggle between play and pause states""" _, state, _ = self.pipeline.get_state(0) if state == Gst.State.PLAYING: self.pipeline.set_state(Gst.State.PAUSED) - self.set_property("is-paused", True) + self.is_paused.value = True else: self.pipeline.set_state(Gst.State.PLAYING) - self.set_property("is-paused", False) + self.is_paused.value = False def seek_relative(self, offset: float) -> None: """Seek relative to current position by offset seconds""" @@ -108,17 +115,19 @@ class VideoPlayer(GObject.Object): def seek_end(self): """Seek to the end of the video.""" - duration = self.get_duration() - if duration: + + if duration := self.get_duration(): self.pipeline.seek_simple(Gst.Format.TIME, DEFAULT_SEEK_FLAGS, duration) def get_position(self) -> int | None: """Get current playback position in nanoseconds""" + success, position = self.pipeline.query_position(Gst.Format.TIME) return position if success else None def get_duration(self) -> int | None: """Get total duration in nanoseconds""" + success, duration = self.pipeline.query_duration(Gst.Format.TIME) return duration if success else None