From 3b748d82b0641fc22e8517b5b0a0813c28343c73 Mon Sep 17 00:00:00 2001 From: Jakob Meier Date: Sat, 16 Mar 2024 18:31:41 +0100 Subject: [PATCH] add resolution picker --- melon/widgets/player.py | 106 +++++++++++++++++++++------------------- 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/melon/widgets/player.py b/melon/widgets/player.py index da5ad01..ea68170 100644 --- a/melon/widgets/player.py +++ b/melon/widgets/player.py @@ -3,11 +3,10 @@ gi.require_version("WebKit", "6.0") gi.require_version('Gtk', '4.0') gi.require_version('Adw', '1') gi.require_version('Gst', '1.0') -from gi.repository import Gtk, Adw, WebKit, GLib, Gst +from gi.repository import Gtk, Adw, WebKit, GLib, Gst, Gio from unidecode import unidecode from gettext import gettext as _ from datetime import datetime - from enum import Enum, auto from melon.servers import Stream @@ -15,6 +14,19 @@ from melon.widgets.iconbutton import IconButton from melon.widgets.preferencerow import PreferenceRow, PreferenceType, Preference from melon.utils import pass_me +def format_seconds(secs:float) -> str: + secs = int(secs) + seconds = secs % 60 + mins_hours = secs // 60 + minutes = mins_hours % 60 + hours = mins_hours // 60 + h = "" + if hours != 0: + h = f"{hours:02}:" + m = f"{minutes:02}" + s = f"{seconds:02}" + return f"{h}{m}:{s}" + def clamp(lower, value, upper): return max(lower, min(value, upper)) @@ -211,17 +223,16 @@ class VideoPlayerBase(Gtk.Overlay): self.controls.append(self.duration_display) self._update_playhead() - # add quality selector - # TODO: consider merging this into a 2d menu - # which also controls playback speed - self.quality_menu = Gtk.MenuButton() - self.quality_menu.set_tooltip_text(_("Video quality")) - self.quality_menu.set_icon_name("network-cellular-signal-good-symbolic") - self.controls.append(self.quality_menu) - self.quality_popover = Gtk.Popover() - # show popover above toolbar - self.quality_menu.set_direction(Gtk.ArrowType.UP) - self.quality_menu.set_popover(self.quality_popover) + # options menu + # generated by _build_menu + # includes quality selector + self.options_menu = Gtk.MenuButton() + self.options_menu.set_tooltip_text(_("Stream options")) + self.options_menu.set_icon_name("applications-system-symbolic") + self.controls.append(self.options_menu) + self.options_menu.set_direction(Gtk.ArrowType.UP) + self.reg_action("quality", self._find_stream, "s") + self._build_menu() # select_stream also call update_quality_list # unreachable if len(streams) == 0 # which is why we don't need an additinal check @@ -274,6 +285,9 @@ class VideoPlayerBase(Gtk.Overlay): self.change_brightness(self.brightness) self.change_volume(self.volume) + def reg_action(self, name, func, variant=None): + self.install_action(name, variant, func) + def connect_update(self, callback=None): self.update_callback = callback def connect_ended(self, callback=None): @@ -416,27 +430,37 @@ class VideoPlayerBase(Gtk.Overlay): def seek_backwards(self, delta=30): self.seek(-delta) - def _update_quality_list(self): - # TODO consider making this scrollable - ls = Gtk.Box.new(Gtk.Orientation.VERTICAL, 0) + def _build_menu(self): + model = Gio.Menu() + quality_model = Gio.Menu() # TODO: figure out if we can extract the qualities from m3u8 playlists # maybe using playbin3? for stream in self.streams: - selected = stream == self.stream - row = Adw.ActionRow() - # TODO: consider giving streams a name - # and use the name as title - # and the quality as subtitle - row.set_title(stream.quality) - row.set_activatable(True) - row.connect("activated", pass_me(lambda _, s: self.select_stream_wrapper(s), stream)) - # TODO: add suffix that indicates if this resolution is selected - ls.append(row) - self.quality_popover.set_child(ls) - - def _select_stream_wrapper(self, stream): - self.quality_menu.popdown() - self.select_stream(stream) + item = Gio.MenuItem.new(stream.quality, None) + item.set_action_and_target_value("quality", GLib.Variant("s", stream.quality)) + quality_model.append_item(item) + model.append_submenu(_("Resolution"), quality_model) + self.options_menu.set_menu_model(model) + + def select_stream(self, stream): + self.stream = stream + self.source.set_state(Gst.State.NULL) + self.source.set_property("uri", stream.url) + if not self.position is None: + self.target_position = self.position + self.play() + self._build_menu() + + def _find_stream(self, widg, action:str, variant): + if action != "quality": + return + # get resolution id + resolution = variant[:] + # find stream associated with resolution + for stream in self.streams: + if stream.quality == resolution: + self.select_stream(stream) + break def _start_loop(self): if not self.stopped: @@ -554,26 +578,6 @@ class VideoPlayerBase(Gtk.Overlay): else: self.pause() - def select_stream(self, stream): - # TODO: confirm that this is acctually working - self.stream = stream - self.source.set_property("uri", stream.url) - self.play() - self._update_quality_list() - -def format_seconds(secs:float) -> str: - secs = int(secs) - seconds = secs % 60 - mins_hours = secs // 60 - minutes = mins_hours % 60 - hours = mins_hours // 60 - h = "" - if hours != 0: - h = f"{hours:02}:" - m = f"{minutes:02}" - s = f"{seconds:02}" - return f"{h}{m}:{s}" - class VideoPlayer(Gtk.Stack): def __init__(self, streams: list[Stream], *args, **kwargs): super().__init__(*args, **kwargs) -- 2.38.5