Experiment with reactivity

This commit is contained in:
Jan Hamal Dvořák 2025-03-11 16:11:47 +01:00
parent d845d3c3a9
commit 5503e48db8
2 changed files with 31 additions and 21 deletions

View file

@ -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")

View file

@ -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