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
-