Mediaplayer mit VLC
| Anke (encarsia) | Auch verfügbar in: English
Inhalt
Mediaplayer mit LibVLC realisieren
VLC ist nicht nur ein Multimediaplayer, sondern auch ein Framework, zu dem Python-Bindings verfügbar sind. In diesem Beispiel wird analog zum GStreamer-Artikel ein einfacher Mediaplayer mittels LibVLC umgesetzt.
LibVLC
Voraussetzung für die Verwendung ist die Installation der Python-Bindings. Diese sind unter der Paketbezeichnung python-vlc
zu finden.
Glade
Darstellungsbereich der Mediendatei: Widget Gtk.DrawingArea
Steuerungselemente: Vor-/Zurückspulen (Gtk.Button), Pause (Gtk.Togglebutton)
Medienauswahl: Buttons, um Video- oder Bilddatei anzuzeigen
Playback manipulieren: Buttons zum Stummschalten und Drehen des Videos
Python
Player einrichten
Der VLC-Player wird initiiert, sobald das dazugehörige Widget, in diesem Fall also Gtk.DrawingArea gezeichnet wird. Dazu wird das Signal realize
genutzt, das grundsätzlich für die Klasse der Widgets verfügbar ist.
vlcOptions = "--no-xlib" win_id = widget.get_window().get_xid() setup_player(vlcOptions) vlcInstance = vlc.Instance(options) player = vlcInstance.media_player_new() player.set_xwindow(win_id)
Als Optionen können Kommandozeilenoptionen von VLC übergeben werden. Im Beispiel wird beim Klick auf den "Rotate"-Button das Bild um 180° gedreht. Der Player wird erneut initiiert und die zusätzliche Option --video-filter=transform{type=180}
übergeben.
Medium abspielen
Wie auch der GStreamer-Player kann der VLC-Player viele Video-/Audio- oder Bild-Formate anzeigen bzw. abspielen.
player.set_mrl(file_url) # Datei abspielen player.play() # Pause/Play-Schalter player.pause()
Positionsanzeige
Die Umsetzung des Fortschrittsbalkens und die Nutzung als Schiebereglers gestaltet sich ziemlich einfach.
# Position abfragen player.get_position() # Position bestimmen player.set_position(val)
Der Wertebereich liegt dabei zwischen 0 und 1. Das Problem bei diesen Funktionen ist, dass sie relativ ressourcenintensiv arbeiten und das Playback mitunter verruckelt ist.
Die Lösung im hiesigen Beispiel besteht darin, get_position
-Abfragen zu umgehen, indem die Regler-Position herangezogen wird.
Möglichkeiten und Limitierungen
Die Nutzung der LibVLC-Python-Bindings erweist sich als einfach und angesichts der GStreamer-Umsetzung als geradezu intuitiv. Auch das "Headerbar-Problem" besteht nicht.
Auf der anderen Seite greift man hier auf großes Projekt zurück, man muss VLC und die Python-Bindings installiert haben anstatt einfach das GStreamer-Modul aus dem GObject Introspection-Repository zu verwenden. Auch ist im Test der Ressourcenverbrauch von VLC gegenüber GStreamer größer.
Listings
Python
20_vlc_simpleplayer.py (Source)
#!/usr/bin/env python # -*- coding: utf-8 -*- import os import subprocess import sys import time import vlc import gi gi.require_version("Gtk", "3.0") from gi.repository import Gtk, Gio, Gdk, GLib class Handler: def on_play_here_realize(self, widget): vlcOptions = "--no-xlib" self.win_id = widget.get_window().get_xid() self.setup_player(vlcOptions) self.player.audio_set_mute(False) self.is_playing = False def on_rotate_toggled(self, widget): pos = self.player.get_position() self.player.stop() self.player.release() self.vlcInstance.release() if widget.get_active(): vlcOptions = "--no-xlib --video-filter=transform{type=180}" else: vlcOptions = "--no-xlib" self.setup_player(vlcOptions) self.player.set_mrl(self.video) self.player.play() self.player.set_position(pos) if not self.is_playing: time.sleep(.05) self.player.pause() def setup_player(self, options): self.vlcInstance = vlc.Instance(options) self.player = self.vlcInstance.media_player_new() self.player.set_xwindow(self.win_id) def on_backward_clicked(self, widget): skip_pos = go.slider.get_value() - 10 if skip_pos < 0: self.player.set_position(0) go.slider.set_value(0) else: self.player.set_position(skip_pos / 100) go.slider.set_value(skip_pos) def on_forward_clicked(self, widget): skip_pos = go.slider.get_value() + 10 if skip_pos > 100: self.player.pause() self.player.set_position(0.99) go.slider.set_value(100) else: self.player.set_position(skip_pos / 100) go.slider.set_value(skip_pos) def on_playpause_togglebutton_toggled(self, widget): if widget.get_active(): img = Gtk.Image.new_from_icon_name(Gtk.STOCK_MEDIA_PLAY, Gtk.IconSize.BUTTON) widget.set_property("image", img) self.is_playing = False else: img = Gtk.Image.new_from_icon_name(Gtk.STOCK_MEDIA_PAUSE, Gtk.IconSize.BUTTON) widget.set_property("image", img) self.is_playing = True self.player.pause() GLib.timeout_add(1000, self.update_slider) def on_vbutton_clicked(self, widget): self.video = "file://" + os.path.abspath("mediaplayer.avi") self.duration = go.get_duration(self.video) self.player.set_mrl(self.video) self.is_playing = True go.slider.set_value(0) go.obj("playpause_togglebutton").set_active(False) go.obj("playpause_togglebutton").set_sensitive(True) go.obj("mute").set_sensitive(True) go.obj("rotate").set_sensitive(True) self.player.play() GLib.timeout_add(1000, self.update_slider) def on_ibutton_clicked(self, widget): image = "file://" + os.path.abspath("mediaplayer.jpg") self.player.set_mrl(image) self.is_playing = False self.player.play() go.obj("playpause_togglebutton").set_sensitive(False) go.obj("mute").set_sensitive(False) go.obj("rotate").set_sensitive(False) def on_mute_toggled(self, widget): if widget.get_active(): widget.set_label("Unmute") else: widget.set_label("Mute") self.player.audio_toggle_mute() def on_progress_change_value(self, widget, scroll, value): self.player.set_position(value / 100) widget.set_value(value) def update_slider(self): if not self.is_playing: return False # cancel timeout else: pos = go.slider.get_value() new_pos = (pos + 100 / self.duration) go.slider.set_value(new_pos) if new_pos > 100: self.is_playing = False return True # continue calling every x milliseconds class VlcPlayer: def __init__(self): self.app = Gtk.Application.new("org.media.player", Gio.ApplicationFlags(0)) self.app.connect("activate", self.on_app_activate) def on_app_activate(self, app): # setting up builder builder = Gtk.Builder() builder.add_from_file("20_vlc_player.glade") builder.connect_signals(Handler()) self.obj = builder.get_object # slider position is float between 0..100 self.slider = self.obj("progress") window = self.obj("window") window.set_application(app) window.show_all() def get_duration(self,video): command = ["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", video, ] ffprobe_cmd = subprocess.run(command, stdout=subprocess.PIPE) # stdout of subprocess is byte variable, convert into float then into integer return int(float(ffprobe_cmd.stdout.decode())) def run(self, argv): self.app.run(argv) go = VlcPlayer() go.run(None)
Glade
<?xml version="1.0" encoding="UTF-8"?> <!-- Generated with glade 3.20.0 --> <interface> <requires lib="gtk+" version="3.16"/> <object class="GtkAdjustment" id="adjustment"> <property name="upper">100</property> <property name="step_increment">1</property> <property name="page_increment">10</property> </object> <object class="GtkImage" id="image1"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="stock">gtk-media-rewind</property> </object> <object class="GtkImage" id="image2"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="stock">gtk-media-forward</property> </object> <object class="GtkImage" id="image3"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="stock">gtk-media-pause</property> </object> <object class="GtkApplicationWindow" id="window"> <property name="width_request">600</property> <property name="height_request">500</property> <property name="can_focus">False</property> <property name="default_width">440</property> <property name="default_height">250</property> <child> <object class="GtkBox" id="box1"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="orientation">vertical</property> <child> <object class="GtkDrawingArea" id="play_here"> <property name="visible">True</property> <property name="can_focus">False</property> <signal name="realize" handler="on_play_here_realize" swapped="no"/> </object> <packing> <property name="expand">True</property> <property name="fill">True</property> <property name="position">0</property> </packing> </child> <child> <object class="GtkSeparator" id="separator1"> <property name="visible">True</property> <property name="can_focus">False</property> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">1</property> </packing> </child> <child> <object class="GtkBox" id="box3"> <property name="visible">True</property> <property name="can_focus">False</property> <child> <object class="GtkButtonBox" id="buttonbox1"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="layout_style">start</property> <child> <object class="GtkButton" id="backward"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <property name="image">image1</property> <property name="always_show_image">True</property> <signal name="clicked" handler="on_backward_clicked" swapped="no"/> </object> <packing> <property name="expand">True</property> <property name="fill">True</property> <property name="position">0</property> </packing> </child> <child> <object class="GtkButton" id="forward"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <property name="image">image2</property> <property name="always_show_image">True</property> <signal name="clicked" handler="on_forward_clicked" swapped="no"/> </object> <packing> <property name="expand">True</property> <property name="fill">True</property> <property name="position">1</property> </packing> </child> <child> <object class="GtkToggleButton" id="playpause_togglebutton"> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <property name="image">image3</property> <property name="always_show_image">True</property> <signal name="toggled" handler="on_playpause_togglebutton_toggled" swapped="no"/> </object> <packing> <property name="expand">True</property> <property name="fill">True</property> <property name="position">2</property> </packing> </child> </object> <packing> <property name="expand">False</property> <property name="fill">False</property> <property name="position">0</property> </packing> </child> <child> <object class="GtkScale" id="progress"> <property name="width_request">300</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="halign">center</property> <property name="margin_left">5</property> <property name="margin_right">5</property> <property name="adjustment">adjustment</property> <property name="fill_level">100</property> <property name="round_digits">1</property> <property name="draw_value">False</property> <signal name="change-value" handler="on_progress_change_value" swapped="no"/> </object> <packing> <property name="expand">False</property> <property name="fill">False</property> <property name="position">1</property> </packing> </child> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">2</property> </packing> </child> <child> <object class="GtkButtonBox" id="buttonbox2"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="homogeneous">True</property> <property name="layout_style">expand</property> <child> <object class="GtkButton" id="vbutton"> <property name="label" translatable="yes">Video</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <signal name="clicked" handler="on_vbutton_clicked" swapped="no"/> </object> <packing> <property name="expand">True</property> <property name="fill">True</property> <property name="position">0</property> </packing> </child> <child> <object class="GtkButton" id="ibutton"> <property name="label" translatable="yes">Image</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <signal name="clicked" handler="on_ibutton_clicked" swapped="no"/> </object> <packing> <property name="expand">True</property> <property name="fill">True</property> <property name="position">1</property> </packing> </child> <child> <object class="GtkToggleButton" id="mute"> <property name="label" translatable="yes">Mute</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <signal name="toggled" handler="on_mute_toggled" swapped="no"/> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">2</property> </packing> </child> <child> <object class="GtkToggleButton" id="rotate"> <property name="label" translatable="yes">Rotate</property> <property name="visible">True</property> <property name="can_focus">True</property> <property name="receives_default">True</property> <signal name="toggled" handler="on_rotate_toggled" swapped="no"/> </object> <packing> <property name="expand">True</property> <property name="fill">True</property> <property name="position">3</property> </packing> </child> </object> <packing> <property name="expand">False</property> <property name="fill">True</property> <property name="position">4</property> </packing> </child> </object> </child> <child type="titlebar"> <object class="GtkHeaderBar"> <property name="visible">True</property> <property name="can_focus">False</property> <property name="title">VLC based media player</property> <property name="show_close_button">True</property> <child> <placeholder/> </child> </object> </child> </object> </interface>
Kommentare
Comments powered by Disqus