Search code examples
pythoneventsgtkvlc

gtk+ vlc, MediaPlayerEndReached event does not play next video


So, I'm using GTK+/VLC to create a window with a video playback area and a "next" button.

It starts by playing the first video file and clicking next button will skip to the next media file, playing it. Everything working great so far.

My problem is, if I wait for MediaPlayerEndReached to be fired, button_Next() is executed but the video does not change.

Am I missing anything?

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
import time

from os import listdir
from os.path import join

startingPath = './files/'

# ----------------------------------

class MediaWindow(Gtk.Window):

    def __init__(self):
        Gtk.Window.__init__(self, title="Media Player")
        self.set_decorated(False) # removes titlebar
        self.move(300,150)
        self.connect("destroy", Gtk.main_quit)

        self.currFldrIdx = 0
        self.currFileIdx = 0

        self.Fldrs = []
        for fldrs in sorted(listdir(startingPath)):
            self.Fldrs.append(join(startingPath,fldrs))
        print self.Fldrs

        self.num_Fldrs = len(self.Fldrs)

        # vlc
        self.vlcInstance = vlc.Instance('--no-xlib')
        self.vlcPlayer = self.vlcInstance.media_player_new()

    def setup_objects_and_events(self):
        self.pause_nextImg = Gtk.Image.new_from_icon_name(
            "gtk-media-forward",
            Gtk.IconSize.MENU
        )

        # Buttons
        self.button_Next = Gtk.Button()
        self.button_Next.set_image(self.pause_nextImg)
        self.button_Next.connect("clicked", self.on_button_NextImg)

        # Area
        self.draw_area = Gtk.DrawingArea()
        self.draw_area.set_size_request(800,480)
        self.draw_area.connect("realize",self._realized)

        # Grid ------------------------------------
        self.hbox1 = Gtk.Box()
        self.hbox1.pack_start(self.draw_area, True, True, 0)

        self.hbox2 = Gtk.Box()
        self.hbox2.pack_start(self.button_Next, True, True, 0)

        self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
        self.add(self.vbox)
        self.vbox.pack_start(self.hbox1, True, True, 0)
        self.vbox.pack_start(self.hbox2, False, True, 0)

    def on_button_NextImg(self, widget):
        print 'next!'
        num_FldrFiles = len(listdir(self.Fldrs[self.currFldrIdx]))

        self.currFileIdx = self.currFileIdx+1 if self.currFileIdx+1<num_FldrFiles else 0

        fileToShow = join(self.Fldrs[self.currFldrIdx], sorted(listdir(self.Fldrs[self.currFldrIdx]))[self.currFileIdx])
        print 'now showing' + fileToShow

        self.vlcPlayer.set_xwindow(self.win_id)
        self.vlcPlayer.set_mrl(fileToShow)
        self.vlcPlayer.play()

    def _realized(self, widget, data=None):
        fileToShow = join(self.Fldrs[self.currFldrIdx], sorted(listdir(self.Fldrs[self.currFldrIdx]))[self.currFileIdx])

        self.win_id = widget.get_window().get_xid()
        self.vlcPlayer.set_xwindow(self.win_id)
        self.vlcPlayer.set_mrl(fileToShow)

        self.events = self.vlcPlayer.event_manager()
        self.events.event_attach(vlc.EventType.MediaPlayerEndReached, self.EventManager)

        self.vlcPlayer.play()

    def EventManager(self, event):
        if event.type == vlc.EventType.MediaPlayerEndReached:
            print "Event reports - finished, playing next"
            self.button_Next.clicked()

# ----------------------------------
if __name__ == '__main__':
    # Create
    win = MediaWindow()
    # Setup
    win.setup_objects_and_events()
    win.show_all()
    Gtk.main()

Solution

  • Your problem is described here:

    the libvlc API is not reentrant within its callbacks https://forum.videolan.org/viewtopic.php?f=32&t=80305

    and here: https://forum.videolan.org/viewtopic.php?t=82502

    You should typically have a mainloop in your application (gobject.mainloop(), or Qt mainloop), so you should instead register a method to restart the player from there

    This is bit mucked about with for the file names but your code now registers the GObject.idle_add() function that is required.

    import sys
    import gi
    gi.require_version('Gtk', '3.0')
    from gi.repository import Gtk, GObject
    gi.require_version('GdkX11', '3.0')
    from gi.repository import GdkX11
    
    import vlc
    import time
    
    from os import listdir
    from os.path import join
    
    startingPath = './files/'
    
    # ----------------------------------
    
    class MediaWindow(Gtk.Window):
    
        def __init__(self):
            Gtk.Window.__init__(self, title="Media Player")
            self.autoPlay = 0
            self.set_decorated(False) # removes titlebar
            self.move(300,150)
            self.connect("destroy", Gtk.main_quit)
    
            self.currFldrIdx = 0
            self.currFileIdx = 0
    
            self.Fldrs = ['/home/public/2005.mp3','/home/public/happy.mp3','/home/public/vp.mp3']
            #for fldrs in sorted(listdir(startingPath)):
            #    self.Fldrs.append(join(startingPath,fldrs))
    
            self.num_Fldrs = len(self.Fldrs) - 1
    
            # vlc
            self.vlcInstance = vlc.Instance('--no-xlib')
            self.vlcPlayer = self.vlcInstance.media_player_new()
    
        def setup_objects_and_events(self):
            self.pause_nextImg = Gtk.Image.new_from_icon_name(
                "gtk-media-forward",
                Gtk.IconSize.MENU
            )
    
            # Buttons
            self.button_Next = Gtk.Button()
            self.button_Next.set_image(self.pause_nextImg)
            self.button_Next.connect("clicked", self.on_button_NextImg)
    
            # Area
            self.draw_area = Gtk.DrawingArea()
            self.draw_area.set_size_request(800,480)
            self.draw_area.connect("realize",self._realized)
    
            # Grid ------------------------------------
            self.hbox1 = Gtk.Box()
            self.hbox1.pack_start(self.draw_area, True, True, 0)
    
            self.hbox2 = Gtk.Box()
            self.hbox2.pack_start(self.button_Next, True, True, 0)
    
            self.vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
            self.add(self.vbox)
            self.vbox.pack_start(self.hbox1, True, True, 0)
            self.vbox.pack_start(self.hbox2, False, True, 0)
    
        def on_button_NextImg(self, widget=None):
            self.currFileIdx += 1
            if self.currFileIdx > self.num_Fldrs:
                self.currFileIdx = 0
    
            fileToShow = self.Fldrs[self.currFileIdx]
            media = self.vlcInstance.media_new_path(fileToShow)
            self.vlcPlayer.set_media(media)
            if self.vlcPlayer.play() == -1:
                print ("error playing",fileToShow)
            else:
                print("now playing",fileToShow)
    
        def _realized(self, widget, data=None):
            fileToShow = self.Fldrs[self.currFldrIdx]
            self.win_id = widget.get_window().get_xid()
            self.vlcPlayer.set_xwindow(self.win_id)
            self.vlcPlayer.set_mrl(fileToShow)
    
            self.events = self.vlcPlayer.event_manager()
            self.events.event_attach(vlc.EventType.MediaPlayerEndReached, self.EventManager)
            self.vlcPlayer.play()
    
        def EventManager(self, event):
            if event.type == vlc.EventType.MediaPlayerEndReached:
                GObject.idle_add(self.on_button_NextImg)
    
    # ----------------------------------
    if __name__ == '__main__':
        # Create
        win = MediaWindow()
        # Setup
        win.setup_objects_and_events()
        win.show_all()
        Gtk.main()