added base support for mpv

This commit is contained in:
DYefremov
2021-03-23 22:20:49 +03:00
parent 55a21fbc18
commit 3ef587841e
7 changed files with 2104 additions and 48 deletions

View File

@@ -33,8 +33,8 @@ class Defaults(Enum):
TOOLTIP_LOGO_SIZE = 96
LIST_PICON_SIZE = 32
FAV_CLICK_MODE = 0
PLAY_STREAMS_MODE = 0
STREAM_LIB = "gst"
PLAY_STREAMS_MODE = 1 if IS_DARWIN else 0
STREAM_LIB = "vlc"
PROFILE_FOLDER_DEFAULT = False
RECORDS_PATH = DATA_PATH + "records/"
ACTIVATE_TRANSCODING = False

View File

@@ -41,6 +41,48 @@ class Player(ABC):
def get_instance(self, mode, widget, buf_cb, position_cb, error_cb, playing_cb):
pass
def get_window_handle(self, widget):
""" Returns the identifier [pointer] for the window.
Based on gtkvlc.py[get_window_pointer] example from here:
https://github.com/oaubert/python-vlc/tree/master/examples
"""
if sys.platform == "linux":
return widget.get_window().get_xid()
else:
is_darwin = sys.platform == "darwin"
try:
import ctypes
libgdk = ctypes.CDLL("libgdk-3.0.dylib" if is_darwin else "libgdk-3-0.dll")
except OSError as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
else:
# 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]
gpointer = ctypes.pythonapi.PyCapsule_GetPointer(widget.get_window().__gpointer__, None)
get_pointer = libgdk.gdk_quartz_window_get_nsview if is_darwin else libgdk.gdk_win32_window_get_handle
get_pointer.restype = ctypes.c_void_p
get_pointer.argtypes = [ctypes.c_void_p]
return get_pointer(gpointer)
def get_video_widget(self, widget):
from gi.repository import Gtk, Gdk
area = Gtk.DrawingArea(visible=True)
area.connect("draw", self.on_drawing_area_draw)
area.set_events(Gdk.ModifierType.BUTTON1_MASK)
widget.add(area)
return area
def on_drawing_area_draw(self, widget, cr):
""" Used for black background drawing in the player drawing area. """
cr.set_source_rgb(0, 0, 0)
cr.paint()
@staticmethod
def make(name, mode, widget, buf_cb=None, position_cb=None, error_cb=None, playing_cb=None):
""" Factory method. We will not use a separate factory to return a specific implementation.
@@ -55,7 +97,9 @@ class Player(ABC):
Throws a NameError if there is no implementation for the given name.
"""
if name == "gst":
if name == "mpv":
return MpvPlayer.get_instance(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
elif name == "gst":
return GstPlayer.get_instance(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
elif name == "vlc":
return VlcPlayer.get_instance(mode, widget, buf_cb, position_cb, error_cb, playing_cb)
@@ -63,6 +107,74 @@ class Player(ABC):
raise NameError("There is no such [{}] implementation.".format(name))
class MpvPlayer(Player):
""" Simple wrapper for MPV media player.
Uses python-mvp [https://github.com/jaseg/python-mpv].
"""
__INSTANCE = None
def __init__(self, mode, widget, buf_cb, position_cb, error_cb, playing_cb):
try:
from app.tools import mpv
self._player = mpv.MPV(wid=str(self.get_window_handle(self.get_video_widget(widget))))
except OSError as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
raise ImportError("No libmpv is found. Check that it is installed!")
else:
self._mode = mode
self._is_playing = False
@self._player.event_callback(mpv.MpvEventID.FILE_LOADED)
def on_open(event):
log("Starting playback...")
playing_cb()
@self._player.event_callback(mpv.MpvEventID.END_FILE)
def on_end(event):
event = event.get("event", {})
if event.get("reason", mpv.MpvEventEndFile.ERROR) == mpv.MpvEventEndFile.ERROR:
log("Stream playback error: {}".format(event.get("error", mpv.ErrorCode.GENERIC)))
error_cb()
@classmethod
def get_instance(cls, mode, widget, buf_cb, position_cb, error_cb, playing_cb):
if not cls.__INSTANCE:
cls.__INSTANCE = MpvPlayer(mode, widget, buf_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 not mrl:
return
self._player.play(mrl)
self._is_playing = True
@run_task
def stop(self):
self._player.stop()
self._is_playing = True
def pause(self):
pass
def set_time(self, time):
pass
@run_task
def release(self):
self._player.terminate()
self.__INSTANCE = None
def is_playing(self):
return self._is_playing
class GstPlayer(Player):
""" Simple wrapper for GStreamer playbin. """
@@ -82,7 +194,7 @@ class GstPlayer(Player):
msg = "GStreamer error: gtksink plugin not installed!"
log(msg)
raise ImportError(msg)
except OSError as e:
except (OSError, ValueError) as e:
log("{}: Load library error: {}".format(__class__.__name__, e))
raise ImportError("No GStreamer is found. Check that it is installed!")
else:
@@ -195,7 +307,10 @@ class GstPlayer(Player):
class VlcPlayer(Player):
""" Simple wrapper for VLC media player. """
""" Simple wrapper for VLC media player.
Uses python-vlc [https://github.com/oaubert/python-vlc].
"""
__VLC_INSTANCE = None
@@ -279,47 +394,14 @@ class VlcPlayer(Player):
return self._is_playing
def init_video_widget(self, widget):
from gi.repository import Gtk, Gdk
area = Gtk.DrawingArea(visible=True)
area.connect("draw", self.on_drawing_area_draw)
area.set_events(Gdk.ModifierType.BUTTON1_MASK)
widget.add(area)
video_widget = self.get_video_widget(widget)
if sys.platform == "linux":
self._player.set_xwindow(area.get_window().get_xid())
self._player.set_xwindow(video_widget.get_window().get_xid())
elif sys.platform == "darwin":
self.set_nso(area)
self._player.set_nsobject(self.get_window_handle(video_widget))
else:
log("Video widget initialization error: platform '{}' is not supported. ".format(sys.platform))
def set_nso(self, widget):
""" Used on MacOS to set NSObject.
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")
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]
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))
def on_drawing_area_draw(self, widget, cr):
""" Used for black background drawing in the player drawing area. """
allocation = widget.get_allocation()
cr.set_source_rgb(0, 0, 0)
cr.rectangle(0, 0, allocation.width, allocation.height)
cr.fill()
return False
class Recorder:
__VLC_REC_INSTANCE = None

1941
app/tools/mpv.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -193,7 +193,7 @@ class Application(Gtk.Application):
self._select_enabled = True # Multiple selection
# Current satellite positions in the services list
self._sat_positions = []
self._marker_types = {BqServiceType.MARKER.name, BqServiceType.SPACE.name}
self._marker_types = {BqServiceType.MARKER.name, BqServiceType.SPACE.name, BqServiceType.ALT.name}
# Player
self._player = None
self._full_screen = False
@@ -310,6 +310,7 @@ class Application(Gtk.Application):
self._fav_bouquets_paned = builder.get_object("fav_bouquets_paned")
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._player_event_box, "visible")
self._fav_view.bind_property("sensitive", self._player_prev_button, "sensitive")
self._fav_view.bind_property("sensitive", self._player_next_button, "sensitive")
# Record
@@ -2602,6 +2603,7 @@ class Application(Gtk.Application):
self.show_error_dialog(str(e))
return True
else:
self._main_window.connect("key-press-event", self.on_player_key_press)
self._player.play(self._current_mrl)
finally:
if self._settings.play_streams_mode is PlayStreamsMode.BUILT_IN:
@@ -2621,6 +2623,12 @@ class Application(Gtk.Application):
if event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS:
self.on_full_screen()
def on_player_key_press(self, widget, event):
if self._player and self._player_event_box.get_visible():
key = event.keyval
if any((key == Gdk.KEY_F11, key == Gdk.KEY_f, self._full_screen and key == Gdk.KEY_Escape)):
self.on_full_screen()
def on_full_screen(self, item=None):
self._full_screen = not self._full_screen
if self._settings.play_streams_mode is PlayStreamsMode.BUILT_IN:
@@ -2648,11 +2656,13 @@ class Application(Gtk.Application):
icon_name="demon-editor")
self._playback_window.resize(width, height)
self._playback_window.connect("delete-event", self.on_player_close)
self._playback_window.connect("key-press-event", self.on_player_key_press)
box = Gtk.HBox(visible=True, orientation="vertical")
self._player_event_box.reparent(box)
self._playback_window.bind_property("visible", self._player_event_box, "visible")
if not self._settings.is_darwin or self._settings.stream_lib == "gst":
if not self._settings.is_darwin:
self._player_prev_button.set_visible(False)
self._player_next_button.set_visible(False)
self._player_box.remove(self._player_tool_bar)

View File

@@ -1442,7 +1442,6 @@ Author: Dmitriy Yefremov
<signal name="show" handler="on_player_box_visibility" swapped="no"/>
<child>
<object class="GtkEventBox" id="player_event_box">
<property name="visible">True</property>
<property name="can_focus">False</property>
<signal name="button-press-event" handler="on_player_press" swapped="no"/>
<signal name="realize" handler="on_player_box_realize" swapped="no"/>

View File

@@ -1995,8 +1995,9 @@ Author: Dmitriy Yefremov
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">EXPERIMENTAL!</property>
<property name="draw_indicator">True</property>
<property name="group">vlc_lib_button</property>
<property name="group">mpv_lib_button</property>
<signal name="toggled" handler="on_play_mode_changed" swapped="no"/>
</object>
<packing>
@@ -2012,7 +2013,7 @@ Author: Dmitriy Yefremov
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="draw_indicator">True</property>
<property name="group">gst_lib_button</property>
<property name="group">mpv_lib_button</property>
<signal name="toggled" handler="on_play_mode_changed" swapped="no"/>
</object>
<packing>
@@ -2021,6 +2022,23 @@ Author: Dmitriy Yefremov
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkRadioButton" id="mpv_lib_button">
<property name="label" translatable="yes">MPV</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">False</property>
<property name="tooltip_text" translatable="yes">EXPERIMENTAL!</property>
<property name="draw_indicator">True</property>
<property name="group">gst_lib_button</property>
<signal name="toggled" handler="on_play_mode_changed" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
<child type="label">

View File

@@ -125,6 +125,7 @@ class SettingsDialog:
self._get_m3u_radio_button = builder.get_object("get_m3u_radio_button")
self._gst_lib_button = builder.get_object("gst_lib_button")
self._vlc_lib_button = builder.get_object("vlc_lib_button")
self._mpv_lib_button = builder.get_object("mpv_lib_button")
# Program
self._before_save_switch = builder.get_object("before_save_switch")
self._before_downloading_switch = builder.get_object("before_downloading_switch")
@@ -610,7 +611,7 @@ class SettingsDialog:
return FavClickMode.DISABLED
def on_play_mode_changed(self, button):
if self._main_stack.get_visible_child_name() != "streaming":
if self._main_stack.get_visible_child_name() != "streaming" or not button.get_active():
return
if self._settings.is_darwin:
@@ -643,9 +644,14 @@ class SettingsDialog:
def set_stream_lib(self, mode):
self._vlc_lib_button.set_active(mode == "vlc")
self._gst_lib_button.set_active(mode == "gst")
self._mpv_lib_button.set_active(mode == "mpv")
def get_stream_lib(self):
return "gst" if self._gst_lib_button.get_active() else "vlc"
if self._gst_lib_button.get_active():
return "gst"
elif self._vlc_lib_button.get_active():
return "vlc"
return "mpv"
def on_transcoding_preset_changed(self, button):
presets = self._settings.transcoding_presets