lazy-player/lazy_player/video_overlay.py
2025-03-11 20:13:15 +01:00

139 lines
4 KiB
Python

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