from __future__ import annotations from datetime import datetime from typing import Any from gi.repository import Gdk, Gtk, Pango from .reactive import Watcher from .video_player import VideoPlayer class VideoOverlay(Gtk.Overlay, Watcher): player: VideoPlayer message: Gtk.Label message_expiration: float grid: Gtk.Grid grid_expiration: float clock: Gtk.Label progressbar: Gtk.ProgressBar now: float def __init__(self, player: VideoPlayer): super().__init__() self.now = 0.0 self.player = player # Message is appears at the center of the screen, # above everything else. Usually to indicate change # of subtitle or audio track or something similar. self.message = Gtk.Label() self.message.set_name("overlay-message") self.message.set_valign(Gtk.Align.CENTER) self.message.set_halign(Gtk.Align.CENTER) self.message.set_visible(False) self.message.set_wrap(True) self.message.set_wrap_mode(Pango.WrapMode.WORD_CHAR) # Once specific time passes, message disappears. self.message_expiration = 0.0 # Grid overlay is between the video at the bottom and # the message at the top. It is only shown when user # interacts with the player. self.grid = Gtk.Grid() self.grid.set_hexpand(True) self.grid.set_vexpand(True) self.grid.set_column_homogeneous(True) self.grid.set_row_homogeneous(True) # Grid visibility can also expire after a while. self.grid_expiration = 0.0 # Create grid boxes. clock_box = Gtk.Box(hexpand=True, vexpand=True) self.grid.attach(clock_box, 0, 0, 1, 1) self.grid.attach(Gtk.Box(), 1, 0, 2, 1) self.grid.attach(Gtk.Box(), 0, 1, 3, 1) progressbar_box = Gtk.Box( name="progressbar-box", hexpand=True, vexpand=True, ) self.grid.attach(progressbar_box, 0, 2, 3, 1) # Add clock to the top-left grid box. self.clock = Gtk.Label( name="overlay-clock", halign=Gtk.Align.START, valign=Gtk.Align.START, ) clock_box.append(self.clock) # Create progressbar. self.progressbar = Gtk.ProgressBar( name="progressbar", hexpand=True, halign=Gtk.Align.FILL, valign=Gtk.Align.END, focusable=False, ) progressbar_box.append(self.progressbar) # Add children. self.set_child(self.player.picture) self.add_overlay(self.grid) self.add_overlay(self.message) # Consume ticks for the clock and overlay expiration. self.add_tick_callback(self._on_tick, None) # Register all watches. self.watch_all() def _on_tick(self, widget: Gtk.Widget, frame_clock: Gdk.FrameClock, data: Any) -> bool: self.clock.set_text(datetime.now().strftime("%H:%M")) self.now = frame_clock.get_frame_time() / 1_000_000 if self.grid_expiration <= self.now: self.grid.hide() if self.message_expiration <= self.now: self.message.hide() position = self.player.get_position() duration = self.player.get_duration() if position is not None and duration is not None: self.progressbar.set_fraction(position / duration) return True def show_message(self, text: str, timeout: float = 1.0) -> None: """Show text in a centered overlay that disappears after timeout.""" self.message.set_text(text) self.message.show() self.message_expiration = self.now + timeout def _watch_player_state(self): is_playing = self.player.is_playing.value is_paused = self.player.is_paused.value # Just to track other user interactions. self.player.last_user_input.value self.grid.show() if is_playing and is_paused: self.grid_expiration = 1e20 else: self.grid_expiration = self.now + 1.0