Search code examples
pythonaudiopyqt5vlcpafy

I close vlc media, but after this audio remains and i can't use gui


When I close the video with a cross, the audio remains. I use pafy and vlc. Also i can't close PyQt app after closing video.

I use python 3.8.

import sys
import pafy
import vlc

from PyQt5.QtWidgets import QApplication, QDialog, QLabel, QHBoxLayout, QPushButton

class GoodYoutubeGUI(QDialog):
    def __init__(self):
        super().__init__()
        self.setFixedSize(800, 800)
        self.setWindowTitle("Good Youtube")
        self.buttons = []
        self.generate_content()

    def generate_buttons_info(self):
        buttons_info = {}
        for _ in  range(5):
            button = QPushButton('Открыть видео', self)
            buttons_info[button] = "https://www.youtube.com/watch?v=zPA2Een1EQA"
    
        return buttons_info

    def generate_content(self):
        pos_y = 0
        for button, link in self.generate_buttons_info().items():
            button.clicked.connect(lambda: self.open_video(link))
            button.move(100, pos_y * 100)
            pos_y += 1

    def open_video(self, url):
        video = pafy.new("https://www.youtube.com/watch?v=zPA2Een1EQA")
        best = video.getbest()
        media = vlc.MediaPlayer(best.url)
        media.play()
        while media.get_state() != vlc.State.Ended:
            continue

        media.stop()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    dlg_main = GoodYoutubeGUI()
    dlg_main.show()
    sys.exit(app.exec_())

Solution

  • You have the following problems:

    • You should not use time consuming tasks within the Qt eventloop as it blocks it causing the GUI to freeze, for example the while.loop and pafy's getbest function, the first one should use a callback to know when the video finishes from reproduce and the second must be executed in a secondary thread.

    • You also have to pass arguments in the lambda since otherwise you will have problems with the scope.

    • To detect when the window closes, a possible solution is to embed the videoplayer in a QWidget and then use an eventfilter to know when the window closes and then stops the player.

    from functools import cached_property
    import sys
    import threading
    
    import pafy
    
    import vlc
    
    from PyQt5.QtCore import pyqtSignal, QEvent, QObject
    from PyQt5.QtWidgets import QApplication, QDialog, QPushButton, QWidget
    
    
    class PlayerManager(QObject):
        def __init__(self, parent=None):
            super().__init__(parent)
            self.window = QWidget()
            if sys.platform.startswith("linux"):  # for Linux using the X Server
                self.player.set_xwindow(self.window.winId())
            elif sys.platform == "win32":  # for Windows
                self.player.set_hwnd(self.window.winId())
            elif sys.platform == "darwin":  # for MacOS
                self.player.set_nsobject(self.window.winId())
            self.window.installEventFilter(self)
    
        @cached_property
        def player(self):
            player = vlc.MediaPlayer()
            player.event_manager().event_attach(
                vlc.EventType.MediaPlayerEndReached, self._handle_finished
            )
            return player
    
        def _handle_finished(self, event):
            if event.type == vlc.EventType.MediaPlayerEndReached:
                self.player.stop()
    
        def play(self):
            self.player.play()
            self.window.show()
    
        def set_media(self, url):
            media = vlc.Media(url)
            self.player.set_media(media)
    
        def eventFilter(self, obj, event):
            if obj is self.window and event.type() == QEvent.Close:
                self.player.stop()
    
            return super().eventFilter(obj, event)
    
    
    class UrlProvider(QObject):
        finished = pyqtSignal(str)
    
        def find_url(self, url):
            threading.Thread(target=self._find_url, args=(url,), daemon=True).start()
    
        def _find_url(self, url):
            video = pafy.new(url)
            best = video.getbest()
            self.finished.emit(best.url)
    
    
    class GoodYoutubeGUI(QDialog):
        def __init__(self):
            super().__init__()
            self.setFixedSize(800, 800)
            self.setWindowTitle("Good Youtube")
            self.buttons = []
            self.url_provider.finished.connect(self.handle_url_finished)
            self.generate_content()
    
        @cached_property
        def player_manager(self):
            return PlayerManager()
    
        @cached_property
        def url_provider(self):
            return UrlProvider()
    
        def generate_buttons_info(self):
            buttons_info = {}
            for _ in range(5):
                button = QPushButton("Открыть видео", self)
                buttons_info[button] = "https://www.youtube.com/watch?v=zPA2Een1EQA"
    
            return buttons_info
    
        def generate_content(self):
            pos_y = 0
            for button, link in self.generate_buttons_info().items():
                button.clicked.connect(lambda checked, link=link: self.open_video(link))
                button.move(100, pos_y * 100)
                pos_y += 1
    
        def open_video(self, url):
            self.url_provider.find_url(url)
    
        def handle_url_finished(self, url):
            self.player_manager.set_media(url)
            self.player_manager.play()
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        dlg_main = GoodYoutubeGUI()
        dlg_main.show()
        sys.exit(app.exec_())