139 lines
4 KiB
Python
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
|