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_())

Answer

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_())