How to Build a Python Media Player using LibVLC and GTK+
In this tutorial, we shall learn how to create a multimedia app in Python with the help of LibVLC and GTK+ which plays media when its starts. App users will also be able to control basic playback options.
This tutorial assumes that the readers know how to create and run Python files; understand basic interpreter errors; working with strings, floats and booleans; object orientation; and idea of threads in Python. Here are other neat Python tips worth knowing about.
The tutorial is suited for novice and intermediate programmers. This tutorial covers GTK+ version 3 and LibVLC media framework; and has been created and tested on Linux.
LibVLC
It is the external programming interface of the VLC media player. LibVLC (VLC SDK) is the core engine and interface to the multimedia framework on which VLC media player is based. It can be embedded into applications to get multimedia capabilities. Therefore, the application should have the same features that VLC media player has.
Dependencies
VLC is developed in C language, therefore install it before using it through Python.The Python bindings have complete coverage of the LibVLC API and the generated module is in pure Python. It only depends on ctypes.
Download the vlc.py module from the Git repository and put the module in a place accessible by Python.
GTK+
A toolkit for creating graphical user interfaces written in the C programming language.
You should also know about these things:
-
GDK: the GIMP Drawing Kit lies between the xlib and the GTK+ library, handling basic renderings such as drawing primitives, raster graphics (bitmaps), cursors, fonts, as well as window events and drag-and-drop functionality. It contains back-ends to a couple of windowing systems, like the X11.
-
GdkX11: Also a part of GObject package, for X Window System Interaction, and X backend-specific functions.
-
PyGObject: A Python module that enables developers to access GObject-based libraries such as GTK+ ,Gdk, GdkX11, etc within Python.
Installation
PyGObject and its dependencies are packaged by all major Linux distributions. So, install the package from the official repository of the distribution.
The code block below shows how to import Gtk+ library from the GI (gobject-introspection) package. Since users can have multiple versions of GTK+ on their systems, import GTK that it refers to GTK+ 3 and not any other version of the library, which is the purpose of statement gi.require_version('Gtk', '3.0')
.
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
Application Components
I. Application Window
It is a sub-class of Gtk.Window
to define our own ApplicationWindow
class to create a top-level window that contains other widgets.
In the class’s constructor, call the constructor of the super class and set the value of the property title to Python-Vlc Media Player.
class ApplicationWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Python-Vlc Media Player")
Media control buttons
self.playback_button = Gtk.Button()
self.stop_button = Gtk.Button()
Images to be displayed on buttons
We use built-in images provided by the GTK+ library.
self.play_image = Gtk.Image.new_from_icon_name(
"gtk-media-play",
Gtk.IconSize.MENU
)
self.pause_image = Gtk.Image.new_from_icon_name(
"gtk-media-pause",
Gtk.IconSize.MENU
)
self.stop_image = Gtk.Image.new_from_icon_name(
"gtk-media-stop",
Gtk.IconSize.MENU
)
Set the images on the buttons.
self.playback_button.set_image(self.play_image)
self.stop_button.set_image(self.stop_image)
events and signals in GTK+
- GTK+ is an event-driven system. All GUI applications are event-driven.
- The applications start on a main loop, which continuously checks for newly generated events. If there is no event, the application waits and does nothing.
- In GTK+, an event is a message from the X server. When the event reaches a widget, it may react to this event by emitting a signal.
- The GTK+ programmer can connect a specific callback to the signal. The callback is a handler function that reacts to the signal.
- The handler function takes two parameters :
The first parameter is the object which emitted the signal; in our case, it is the Play/Pause button. The second parameter is optional where one can send events/data generated when the signal is fired.
For example: data type can be a Gdk.EventType.
Here is the link to get to know more about Event-Driven programming.
Connect buttons to clicked signals.
self.playback_button.connect("clicked", self.toggle_player_playback)
self.stop_button.connect("clicked", self.stop_player)
The function toggle_player_playback
handles switching between play/pause options and sets playback_button
image accordingly.
def toggle_player_playback(self, widget, data=None):
"""
Handler for Player's Playback Button (Play/Pause).
"""
if self.is_player_active == False and self.player_paused == False:
self.player.play()
self.playback_button.set_image(self.pause_image)
self.is_player_active = True
elif self.is_player_active == True and self.player_paused == True:
self.player.play()
self.playback_button.set_image(self.pause_image)
self.player_paused = False
elif self.is_player_active == True and self.player_paused == False:
self.player.pause()
self.playback_button.set_image(self.play_image)
self.player_paused = True
else:
pass
The Function stop_player
stops media playback and sets the playback button to play the image.
def stop_player(self, widget, data=None):
self.player.stop()
self.is_player_active = False
self.playback_button.set_image(self.play_image)
Media Rendering Surface
To draw on the screen, we use the DrawingArea widget. A drawing area widget is essentially an X window and nothing more. It's a blank canvas in which we can draw whatever we like. A drawing area is created using the call below.
The default size can be overridden, as is true for all widgets, by calling set_size_request_
, and that, in turn, can also be overridden if the user manually resizes the window containing the drawing area.
self.draw_area = Gtk.DrawingArea()
self.draw_area.set_size_request(300,300)
Note: This tutorial does not cover input signals from users on GtkDrawingArea. To capture events from input devices like mouse, for these widgets, we need to use an EventBox widget.
Connect the rendering area to its realize signal. The "realize" signal is to take any necessary actions when the widget is instantiated on a particular display. I shall talk about the_realize
handler later on in this tutorial.
self.draw_area.connect("realize",self._realized)
Before discussing LibVLC, lets pack widgets in Gtk.Box widgets known as Gtk Containers.
What are boxes? Boxes are invisible containers into which we can pack our widgets. And when packing widgets into a horizontal box, the objects are inserted horizontally from left to right or right to left depending on whether Gtk.Box.pack___start()
or Gtk.Box.pack_end()
is used.
In a vertical box, widgets are packed from top to bottom or vice versa. We can use any combination of boxes inside or beside other boxes to create the desired effect.
Create a horizontally-orientated box container where 6 pixels are placed between children. Here, the children are our two media control buttons.
We pack from the start of the box. The pack start takes four parameters:
- The
GtkWidget
to be added to the box. - Boolean expand: TRUE if the new child is to be given extra space allocated to the box.
- Boolean fill: TRUE if the space given to child by the expand option is actually allocated to child, rather than just padding it.
- padding: extra space in pixels to put between this child and its neighbors, over and above the global amount specified by “spacing” property.
self.hbox = Gtk.Box(spacing=6)
self.hbox.pack_start(self.playback_button, True, True, 0)
self.hbox.pack_start(self.stop_button, True, True, 0)
Similarly, we create a vertically-oriented box and it as the child of the top-level window. Next, we pack the rendering surface first and then the horizontal box.
self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.add(self.vbox)
self.vbox.pack_start(self.draw_area, True, True, 0)
self.vbox.pack_start(self.hbox, False, False, 0)
Show the window recursively and any child widgets.
self.show_all()
Below is the screenshot of our resultant GUI of the application.
II. Playing Media using the LibVLCvlc framework
import vlc
Note:
- The drawing area has to be realized before to get its Gdk.Window.
- We can't get the window property directly.
- We need to import GdkX11 for the xid method because the rendering surface's window is an instance of GdkX11.X11Window.
- GObject.Object -> Gdk.Window -> GdkX11.X11Window
- Hencem import GdkX11.
gi.require_version('GdkX11', '3.0')
from gi.repository import GdkX11
As soon as the rendering surface is realized, its handler function performs the following operations:
-
Create and initialize a LibVLC instance and pass
"--no-xlib"
as parameter. -
Create an empty Media Player object.
self.vlcInstance = vlc.Instance("--no-xlib")
self.player = self.vlcInstance.media_player_new()
Why is the "--no-xlib"
passed to vlcInstance
?
Generally, when multiple threads are using Xlib (also known as libX11) concurrently, it is necessary to call the XInitThreads()
function, as we are not dealing with threads using xlib here. Thus, notifying LibVLC that the Xlib is not initialized for threads.
- Get the window ID of the Media Rendering Surface's X11 window and hook the player to the window for media output.
win_id = widget.get_window().get_xid()
self.player.set_xwindow(win_id)
- Set theMRL(Media Resource Locator — used by LibVLC) to play.
self.player.set_mrl(media_url)
Putting it altogether
import sys
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
gi.require_version('GdkX11', '3.0')
from gi.repository import GdkX11
import vlc
MRL = ""
class ApplicationWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Python-Vlc Media Player")
self.player_paused=False
self.is_player_active = False
self.connect("destroy",Gtk.main_quit)
def show(self):
self.show_all()
def setup_objects_and_events(self):
self.playback_button = Gtk.Button()
self.stop_button = Gtk.Button()
self.play_image = Gtk.Image.new_from_icon_name(
"gtk-media-play",
Gtk.IconSize.MENU
)
self.pause_image = Gtk.Image.new_from_icon_name(
"gtk-media-pause",
Gtk.IconSize.MENU
)
self.stop_image = Gtk.Image.new_from_icon_name(
"gtk-media-stop",
Gtk.IconSize.MENU
)
self.playback_button.set_image(self.play_image)
self.stop_button.set_image(self.stop_image)
self.playback_button.connect("clicked", self.toggle_player_playback)
self.stop_button.connect("clicked", self.stop_player)
self.draw_area = Gtk.DrawingArea()
self.draw_area.set_size_request(300,300)
self.draw_area.connect("realize",self._realized)
self.hbox = Gtk.Box(spacing=6)
self.hbox.pack_start(self.playback_button, True, True, 0)
self.hbox.pack_start(self.stop_button, True, True, 0)
self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
self.add(self.vbox)
self.vbox.pack_start(self.draw_area, True, True, 0)
self.vbox.pack_start(self.hbox, False, False, 0)
def stop_player(self, widget, data=None):
self.player.stop()
self.is_player_active = False
self.playback_button.set_image(self.play_image)
def toggle_player_playback(self, widget, data=None):
"""
Handler for Player's Playback Button (Play/Pause).
"""
if self.is_player_active == False and self.player_paused == False:
self.player.play()
self.playback_button.set_image(self.pause_image)
self.is_player_active = True
elif self.is_player_active == True and self.player_paused == True:
self.player.play()
self.playback_button.set_image(self.pause_image)
self.player_paused = False
elif self.is_player_active == True and self.player_paused == False:
self.player.pause()
self.playback_button.set_image(self.play_image)
self.player_paused = True
else:
pass
def _realized(self, widget, data=None):
self.vlcInstance = vlc.Instance("--no-xlib")
self.player = self.vlcInstance.media_player_new()
win_id = widget.get_window().get_xid()
self.player.set_xwindow(win_id)
self.player.set_mrl(MRL)
self.player.play()
self.playback_button.set_image(self.pause_image)
self.is_player_active = True
if __name__ == '__main__':
if not sys.argv[1:]:
print "Exiting \nMust provide the MRL."
sys.exit(1)
if len(sys.argv[1:]) == 1:
MRL = sys.argv[1]
window = ApplicationWindow()
window.setup_objects_and_events()
window.show()
Gtk.main()
window.player.stop()
window.vlcInstance.release()
Gtk.main()
It is the GTK+ processing loop which is running inside our main application thread.
- When the user is doing nothing, GTK+ sits in the main loop and waits for input. If the user performs some action — say, a mouse click — then the main loop “wakes up” and delivers an event to GTK+.
- GTK+ is “thread aware” but not thread safe — it provides a global lock controlled by
gdk_threads_enter()
andgdk_threads_leave()
which protects all use of GTK+. That is, only one thread can use GTK+ at any given time. - Calling
Gtk.main_quit()
makes the main loop inside ofGtk.main()
return. - Thus, connecting the "destroy" signal to
Gtk.main_quit()
will destroy the window and also terminate our application.
Below is the screenshot showing a media playing in our media player.
Its seems that we have reached end of this tutorial. However, This application can be extended to add more multimedia capabilities and UI features.
Therefore, you may explore both libraries to add more robust features in your app.
Thanks! I hope you enjoyed the tutorial. Please leave your questions in the comment section below.
is it true info about VLC
im i supposed to type something in MRL?
im not seeing any output after running it