Search code examples
pythonmultithreadingtkintervlc

Playing a new video with vlc in tkinter frame after current one ends


I'm currently working on my first python project and am hitting a brick wall with the VLC player right now. What I want to do is have it play a random video in a tkinter frame from a given set of files in a folder via the playRandomVideo() method (this works), play a new random video via the playRandomVideo() method method when I press a button (also works) and upon completely finishing a video, trigger the corresponding event (also works) and play a random video via the playRandomVideo() method (this one's not working as intended)

If I leave the code as seen, it will open a new window for every time a video is started by the event (if I press the button it will play a new random video in the latest window).

If I stop the playRandomVideo() method from creating a new instance by modifying it to

if not hasattr(Window1, 'player'):
    Window1.player = Instance.media_player_new()

it will freeze when doing Window1.player.set_media(Media) (added a print() before and after this line and only the before was printed)

With any of those modifications every way of starting a new video in the same frame works, except having it started by the event.

I think that I need to do something where the pass currently is, but I can't for the life of me figure out what.

Full relevant code:

from tkinter import Tk, ttk, Frame, Button
import os
import threading
from threading import Thread
from glob import glob
import random
import vlc

class MainWindow(Thread):
    def __init__(self):
        Thread.__init__(self)
        print(threading.current_thread())

    def run(self):
        self.mw = Tk()
        self.mw.title('Python Guides')
        self.mw.geometry('1200x720')

        self.display = Frame(self.mw, bd=5)
        self.display.place(relwidth=1, relheight=1)

        Button(self.mw, text='Next', padx=10,
               command=Window1.playRandomVideo).grid(row=1, columnspan=5, pady=5)
        self.mw.mainloop()

    def playRandomVideo(self):
        fl = glob("C:\\BDFR\\downloads\\*\\*.mp4")
        videoPath = ""
        if len(fl) > 0:
            random_index = random.randrange(len(fl))
            videoPath = fl[random_index]
        if os.path.exists(videoPath) is True:
            if hasattr(Window1, 'player'):
                if Window1.player.is_playing() == 1:
                    Window1.player.stop()
                if Window1.player.is_playing() == 0:
                    pass
            Instance = vlc.Instance()
            Window1.player = Instance.media_player_new()
            Media = Instance.media_new(videoPath)

            Window1.player.set_hwnd(Window1.display.winfo_id())
            Window1.player.set_media(Media)
            Window1.player.play()

            # Event Manager for end of media
            Window1.events = Window1.player.event_manager()
            Window1.events.event_attach(vlc.EventType.MediaPlayerEndReached, Window1.EventManager)

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


Window1 = MainWindow()
Window1.start()
Window1.playRandomVideo()

P.S.: Feel free to give me advice when it comes to proper code formating, I'm pretty much winging it based on what I've seen from random code snippets on the internet, and what Atom's ide-python package shouts at me for.


Solution

  • So after a a good bit more research I stumbled upon this post: https://forum.videolan.org/viewtopic.php?t=80305

    Long story short: VLC player can't send a comand to itself through it's own event. Which lead to this beautifully elegant cough piece of code that fixes the problem:

    # >>> class definition of MainWindow from original question here <<<
            # Event Manager for end of media
            if not hasattr(self, "events"):
                self.events = self.player.event_manager()
                self.events.event_attach(vlc.EventType.MediaPlayerEndReached,
                                         EventManager)
    
    
    vidOver = False
    def EventManager(event):
        if event.type == vlc.EventType.MediaPlayerEndReached:
            # print("Event reports - finished, playing next")
            global vidOver
            vidOver = True
    
    
    def newVid(object):
        global vidOver
        vidOver = False
        while 1:
            if vidOver is True:
                vidOver = False
                object.playRandomVideo()
            time.sleep(0.2)
    
    
    Window1 = MainWindow()
    Window1.start()
    time.sleep(0.2)
    threading.Thread(target=newVid, args=(Window1,), daemon=True).start()
    Window1.playRandomVideo()
    

    In short: A permanent daemon thread checks whether vidOver has been changed to True and starts the playRandomVideo() method when it is. The event handler does nothing more than changint vidOver to True

    I'm not terribly happy with the efficiency of this, so if someone comes up with a more elegant solution I would still apprechiate new ideas.