diff --git a/app/tools/media.py b/app/tools/media.py index f455b5a6..718a2293 100644 --- a/app/tools/media.py +++ b/app/tools/media.py @@ -6,103 +6,102 @@ from app.commons import run_task, log, _DATE_FORMAT class Player: - """ Simple wrapper for VLC media player. """ - __VLC_INSTANCE = None + """ Simple wrapper for GStreamer playbin. """ + __INSTANCE = None def __init__(self, mode, rewind_cb, position_cb, error_cb, playing_cb): try: - from app.tools import vlc - from app.tools.vlc import EventType + import gi + + gi.require_version("Gst", "1.0") + gi.require_version("GstVideo", "1.0") + from gi.repository import Gst, GstVideo except OSError as e: log("{}: Load library error: {}".format(__class__.__name__, e)) raise ImportError else: + # initialize GStreamer + Gst.init(sys.argv) + self.STATE = Gst.State + self.STAT_RETURN = Gst.StateChangeReturn + self._mode = mode self._is_playing = False - args = "--quiet {}".format("" if sys.platform == "darwin" else "--no-xlib") - self._player = vlc.Instance(args).media_player_new() - ev_mgr = self._player.event_manager() + self._player = Gst.ElementFactory.make("playbin", "player") - if rewind_cb: - # TODO look other EventType options - ev_mgr.event_attach(EventType.MediaPlayerBuffering, - lambda et, p: rewind_cb(p.get_media().get_duration()), - self._player) - if position_cb: - ev_mgr.event_attach(EventType.MediaPlayerTimeChanged, - lambda et, p: position_cb(p.get_time()), - self._player) - - if error_cb: - ev_mgr.event_attach(EventType.MediaPlayerEncounteredError, - lambda et, p: error_cb(), - self._player) - if playing_cb: - ev_mgr.event_attach(EventType.MediaPlayerPlaying, - lambda et, p: playing_cb(), - self._player) + bus = self._player.get_bus() + bus.add_signal_watch() + bus.connect("message::error", error_cb) + bus.connect("message::state-changed", playing_cb) @classmethod def get_instance(cls, mode, rewind_cb=None, position_cb=None, error_cb=None, playing_cb=None): - if not cls.__VLC_INSTANCE: - cls.__VLC_INSTANCE = Player(mode, rewind_cb, position_cb, error_cb, playing_cb) - return cls.__VLC_INSTANCE + if not cls.__INSTANCE: + cls.__INSTANCE = Player(mode, rewind_cb, position_cb, error_cb, playing_cb) + return cls.__INSTANCE def get_play_mode(self): return self._mode - @run_task def play(self, mrl=None): - if mrl: - self._player.set_mrl(mrl) - self._player.play() - self._is_playing = True + self.stop() + + if mrl: + self._player.set_property("uri", mrl) + + ret = self._player.set_state(self.STATE.PLAYING) + + if ret == self.STAT_RETURN.FAILURE: + log("ERROR: Unable to set the 'PLAYING' state for '{}'.".format(mrl)) + else: + self._is_playing = True - @run_task def stop(self): if self._is_playing: - self._player.stop() + self._player.set_state(self.STATE.READY) self._is_playing = False def pause(self): - self._player.pause() + self._player.set_state(self.STATE.PAUSED) def set_time(self, time): - self._player.set_time(time) + pass - @run_task def release(self): if self._player: self._is_playing = False - self._player.stop() - self._player.release() - self.__VLC_INSTANCE = None + self.stop() + self._player.set_state(self.STATE.NULL) + self.__INSTANCE = None def set_xwindow(self, xid): self._player.set_xwindow(xid) - def set_nso(self, widget): - """ Used on MacOS to set NSObject. + def set_handle(self, widget): + """ Used on Windows to set window pointer. Based on gtkvlc.py[get_window_pointer] example from here: https://github.com/oaubert/python-vlc/tree/master/examples """ try: import ctypes - g_dll = ctypes.CDLL("libgdk-3.0.dylib") + + libgdk = ctypes.CDLL("libgdk-3-0.dll") except OSError as e: log("{}: Load library error: {}".format(__class__.__name__, e)) else: - get_nsview = g_dll.gdk_quartz_window_get_nsview - get_nsview.restype, get_nsview.argtypes = ctypes.c_void_p, [ctypes.c_void_p] + # https://gitlab.gnome.org/GNOME/pygobject/-/issues/112 ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p ctypes.pythonapi.PyCapsule_GetPointer.argtypes = [ctypes.py_object] - # Get the C void* pointer to the window - pointer = ctypes.pythonapi.PyCapsule_GetPointer(widget.get_window().__gpointer__, None) - self._player.set_nsobject(get_nsview(pointer)) + gpointer = ctypes.pythonapi.PyCapsule_GetPointer(widget.get_window().__gpointer__, None) + libgdk.gdk_win32_window_get_handle.restype = ctypes.c_void_p + libgdk.gdk_win32_window_get_handle.argtypes = [ctypes.c_void_p] + handle = libgdk.gdk_win32_window_get_handle(gpointer) + + self._player.set_window_handle(handle) def set_mrl(self, mrl): - self._player.set_mrl(mrl) + self._player.set_property("uri", mrl) def is_playing(self): return self._is_playing diff --git a/app/tools/yt.py b/app/tools/yt.py index 5cdae9eb..e3d3ea55 100644 --- a/app/tools/yt.py +++ b/app/tools/yt.py @@ -11,6 +11,7 @@ from urllib.parse import unquote from urllib.request import Request, urlopen, urlretrieve from app.commons import log +from app.settings import SEP from app.ui.uicommons import show_notification _YT_PATTERN = re.compile(r"https://www.youtube.com/.+(?:v=)([\w-]{11}).*") @@ -169,7 +170,7 @@ class PlayListParser(HTMLParser): try: resp = json.loads(data) - except JSONDecodeError as e: + except YouTubeException as e: log("{}: Parsing data error: {}".format(__class__.__name__, e)) else: sb = resp.get("sidebar", None) @@ -229,7 +230,7 @@ class YouTubeDL: "cookiefile": "cookies.txt"} # File name where cookies should be read from and dumped to. def __init__(self, settings, callback): - self._path = settings.default_data_path + "tools/" + self._path = settings.default_data_path + "tools{}".format(SEP) self._update = settings.enable_yt_dl_update self._supported = {"22", "18"} self._dl = None @@ -246,7 +247,7 @@ class YouTubeDL: return cls._DL_INSTANCE def init(self): - if not os.path.isfile(self._path + "youtube_dl/version.py"): + if not os.path.isfile(self._path + "youtube_dl{}version.py".format(SEP)): self.get_latest_release() if self._path not in sys.path: @@ -313,7 +314,7 @@ class YouTubeDL: os.makedirs(os.path.dirname(self._path), exist_ok=True) for info in arch.infolist(): - pref, sep, f = info.filename.partition("/youtube_dl/") + pref, sep, f = info.filename.partition("{}youtube_dl{}".format(SEP, SEP)) if sep: arch.extract(info.filename) shutil.move(info.filename, "{}{}{}".format(self._path, sep, f)) diff --git a/app/ui/main_app_window.py b/app/ui/main_app_window.py index d22af021..c5f2333a 100644 --- a/app/ui/main_app_window.py +++ b/app/ui/main_app_window.py @@ -17,7 +17,7 @@ from app.eparser.ecommons import CAS, Flag, BouquetService from app.eparser.enigma.bouquets import BqServiceType from app.eparser.iptv import export_to_m3u from app.eparser.neutrino.bouquets import BqType -from app.settings import SettingsType, Settings, SettingsException, PlayStreamsMode, SettingsReadException +from app.settings import SettingsType, Settings, SettingsException, PlayStreamsMode, SettingsReadException, IS_WIN from app.tools.media import Player, Recorder from app.ui.epg_dialog import EpgDialog from app.ui.transmitter import LinksTransmitter @@ -304,12 +304,11 @@ class Application(Gtk.Application): self._player_tool_bar = builder.get_object("player_tool_bar") self._player_prev_button = builder.get_object("player_prev_button") self._player_next_button = builder.get_object("player_next_button") - self._player_box.bind_property("visible", tool_bar, "visible", 4) self._player_box.bind_property("visible", self._services_main_box, "visible", 4) self._player_box.bind_property("visible", self._bouquets_main_box, "visible", 4) self._player_box.bind_property("visible", builder.get_object("fav_pos_column"), "visible", 4) self._player_box.bind_property("visible", builder.get_object("fav_pos_column"), "visible", 4) - self._player_box.bind_property("visible", self._profile_combo_box, "sensitive", 4) + self._player_box.bind_property("visible", tool_bar, "sensitive", 4) self._fav_view.bind_property("sensitive", self._player_prev_button, "sensitive") self._fav_view.bind_property("sensitive", self._player_next_button, "sensitive") # Record @@ -333,21 +332,23 @@ class Application(Gtk.Application): self._status_bar_box.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), style_provider, Gtk.STYLE_PROVIDER_PRIORITY_USER) + # Menu bar + main_box = builder.get_object("main_window_box") + builder.add_from_file(UI_RESOURCES_PATH + "app_menu_bar.ui") + menu_bar = Gtk.MenuBar.new_from_model(builder.get_object("menu_bar")) + menu_bar.set_visible(True) + main_box.pack_start(menu_bar, False, False, 0) + main_box.reorder_child(menu_bar, 0) + self._main_data_box.bind_property("visible", menu_bar, "visible") + self._player_box.bind_property("visible", menu_bar, "sensitive", 4) + if self._settings.get("telnet"): + self.init_telnet(builder) + def do_startup(self): Gtk.Application.do_startup(self) self.init_keys() self.set_accels() - - builder = Gtk.Builder() - builder.set_translation_domain("demon-editor") - builder.add_from_file(UI_RESOURCES_PATH + "app_menu_bar.ui") - self.set_menubar(builder.get_object("menu_bar")) - self.set_app_menu(builder.get_object("app-menu")) - - if self._settings.get("telnet"): - self.init_telnet(builder) - self.update_profile_label() self.init_drag_and_drop() self.init_colors() @@ -2445,7 +2446,7 @@ class Application(Gtk.Application): def on_player_stop(self, item=None): if self._player: - self._player.stop() + GLib.idle_add(self._player.stop) def on_player_previous(self, item): if self._fav_view.do_move_cursor(self._fav_view, Gtk.MovementStep.DISPLAY_LINES, -1): @@ -2498,12 +2499,12 @@ class Application(Gtk.Application): if not self._full_screen and self._player_rewind_box.get_visible(): GLib.idle_add(self._player_current_time_label.set_text, self.get_time_str(t), priority=GLib.PRIORITY_LOW) - def on_player_error(self): + def on_player_error(self, bus=None, msg=None): self.set_playback_elms_active() self.show_error_dialog("Can't Playback!") @run_idle - def set_playback_elms_active(self): + def set_playback_elms_active(self, bus=None, msg=None): self._fav_view.set_sensitive(True) self._fav_view.do_grab_focus(self._fav_view) @@ -2525,8 +2526,8 @@ class Application(Gtk.Application): self.show_error_dialog("No VLC is found. Check that it is installed!") return True else: - if self._settings.is_darwin: - self._player.set_nso(widget) + if IS_WIN: + self._player.set_handle(widget) else: self._player.set_xwindow(widget.get_window().get_xid()) self._player.play(self._current_mrl) @@ -2541,20 +2542,15 @@ class Application(Gtk.Application): widget.set_size_request(w * 0.6, -1) def on_player_drawing_area_draw(self, widget, cr): - """ Used for black background drawing in the player drawing area. + """ Used for black background drawing in the player drawing area. """ + if not self._player.is_playing: + allocation = widget.get_allocation() - Required for Gtk >= 3.20. - More info: https://developer.gnome.org/gtk3/stable/ch32s10.html, - https://developer.gnome.org/gtk3/stable/GtkStyleContext.html#gtk-render-background - """ - context = widget.get_style_context() - width = widget.get_allocated_width() - height = widget.get_allocated_height() - Gtk.render_background(context, cr, 0, 0, width, height) - r, g, b, a = 0, 0, 0, 1 # black color - cr.set_source_rgba(r, g, b, a) - cr.rectangle(0, 0, width, height) - cr.fill() + cr.set_source_rgb(0, 0, 0) + cr.rectangle(0, 0, allocation.width, allocation.height) + cr.fill() + + return False def on_player_press(self, area, event): if event.button == Gdk.BUTTON_PRIMARY: diff --git a/app/ui/main_window.glade b/app/ui/main_window.glade index ad2c5c8d..9240658d 100644 --- a/app/ui/main_window.glade +++ b/app/ui/main_window.glade @@ -1005,305 +1005,22 @@ Author: Dmitriy Yefremov False vertical - + True False + center + 10 + 10 + 10 10 - + True False - center - center - 10 - 10 - 10 - 10 - - - True - False - Profile - 0 - False - - - - False - True - 0 - - - - - True - False - expand - - - True - False - True - Open - app.on_data_open - open_image - True - - - True - True - 0 - - - - - - True - False - True - FTP-transfer - app.on_download - ftp_image - True - - - True - True - 1 - - - - - - True - True - Telnet - app.on_telnet_client_show - telnet_image - True - - - True - True - 2 - - - - - - False - True - Save - app.on_data_save - save_image - True - - - True - True - 3 - - - - - - True - False - True - Backups - app.on_backup_tool_show - backups_image - True - - - True - True - 4 - - - - - False - True - 1 - - - - - False - 0.99000000022351742 - immediate - expand - - - Search - True - True - True - Search - win.search - find_image - True - - - True - True - 0 - - - - - Filter - True - False - True - Filter - win.filter - filter_image - True - - - True - True - 4 - - - - - Lock - True - False - True - Parent lock - app.on_locked - lock_image - True - - - True - True - 5 - - - - - Hide - True - False - True - Hide/Skip - app.on_hide - hide_image - True - - - True - True - 5 - - - - - False - True - 2 - - - - - False - center - expand - - - Satellites - True - False - True - Satellites editor - app.on_satellite_editor_show - sat_editor_image - True - - - True - True - 0 - - - - - Picons - True - False - True - Picons manager - app.on_picons_manager_show - picons_image - True - - - True - True - 1 - - - - - True - False - True - IPTV tools - toolbar_iptv_menu - - - True - False - center - 2 - - - True - False - center - center - network-wired-symbolic - 1 - - - False - True - 0 - - - - - True - False - IPTV - - - False - True - 1 - - - - - - - True - True - 5 - - - - - False - True - 3 - - + Profile + 0 + False + False @@ -1311,11 +1028,279 @@ Author: Dmitriy Yefremov 0 + + + True + False + expand + + + True + False + True + Open + app.on_data_open + open_image + True + + + True + True + 0 + + + + + + True + False + True + FTP-transfer + app.on_download + ftp_image + True + + + True + True + 1 + + + + + + True + True + Telnet + app.on_telnet_client_show + telnet_image + True + + + True + True + 2 + + + + + + False + True + Save + app.on_data_save + save_image + True + + + True + True + 3 + + + + + + True + False + True + Backups + app.on_backup_tool_show + backups_image + True + + + True + True + 4 + + + + + False + True + 1 + + + + + False + 0.99000000022351742 + immediate + expand + + + Search + True + True + True + Search + win.search + find_image + True + + + True + True + 0 + + + + + Filter + True + False + True + Filter + win.filter + filter_image + True + + + True + True + 4 + + + + + Lock + True + False + True + Parent lock + app.on_locked + lock_image + True + + + True + True + 5 + + + + + Hide + True + False + True + Hide/Skip + app.on_hide + hide_image + True + + + True + True + 5 + + + + + False + True + 2 + + + + + False + expand + + + Satellites + True + False + True + Satellites editor + app.on_satellite_editor_show + sat_editor_image + True + + + True + True + 0 + + + + + Picons + True + False + True + Picons manager + app.on_picons_manager_show + picons_image + True + + + True + True + 1 + + + + + True + False + True + IPTV tools + toolbar_iptv_menu + + + True + False + center + 2 + + + True + False + center + center + network-wired-symbolic + 1 + + + False + True + 0 + + + + + True + False + IPTV + + + False + True + 1 + + + + + + + True + True + 5 + + + + + False + True + 3 + + True False - center 10 immediate True @@ -1323,7 +1308,7 @@ Author: Dmitriy Yefremov False - False + True True Create bouquet True @@ -1390,13 +1375,13 @@ Author: Dmitriy Yefremov False True end - 1 + 4 False - False + True 0