Search code examples
pyqt5qwidgetqmediaplayerqvideowidget

After stopping a video in PyQt5 QMediaPlayer, artifacts occur when the window is resized


After the video has finished playing, or the QMediaPlayer stop() method has been called, artifacts occur in place of the video widget when the window is resized. Below is a screen shot of the artifacts: enter image description here A minimal example of a program:

import sys
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, QFileDialog, QSlider
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
from PyQt5.QtMultimediaWidgets import QVideoWidget

class Player(QMainWindow):
    def __init__(self, parent=None):
        super(Player, self).__init__(parent)
        
        self.media_player = QMediaPlayer(self)
        self.video_widget = QVideoWidget(self)
        self.slider = QSlider(Qt.Horizontal)

        self.open_button = QPushButton("Open Media File")
        self.open_button.clicked.connect(self.open_file)

        layout = QVBoxLayout()
        layout.addWidget(self.video_widget)
        layout.addWidget(self.open_button)
        layout.addWidget(self.slider)

        widget = QWidget(self)
        widget.setLayout(layout)
        self.setCentralWidget(widget)

        self.media_player.setVideoOutput(self.video_widget)
        self.media_player.positionChanged.connect(self.update_slider)
        self.slider.sliderMoved.connect(self.set_position)

    def open_file(self):
        file_dialog = QFileDialog.getOpenFileName(self, "Open Media File")
        if file_dialog[0]:
            self.set_and_play_file(file_dialog[0])

    def set_and_play_file(self, selected_file):
        self.media_player.setMedia(QMediaContent(QUrl.fromLocalFile(selected_file)))
        self.media_player.play()

    def update_slider(self, position):
        self.slider.setMaximum(int(self.media_player.duration() / 1000))
        self.slider.setValue(int(position / 1000))

    def set_position(self, position):
        self.media_player.setPosition(int(position * 1000))

if __name__ == "__main__":
    app = QApplication(sys.argv)
    player = Player()
    player.show()
    sys.exit(app.exec_())

I tried monitoring the QMediaPlayer.EndOfMedia state and replacing the stop() method with the pause() method, and changing the player position to 0. This improved things to some extent, but eventually I ran into artifacts again and decided that there must be a way to get rid of this problem. I also tried setting

self.video_widget.setAttribute(Qt.WA_OpaquePaintEvent, False)
self.video_widget.setAutoFillBackground()
self.video_widget.setAttribute(Qt.WA_NoSystemBackground, True)

but that didn't produce any results. Plus I tried to predefine paintEvent() in the video widget and draw a red line on the widget, but in the end, probably due to my lack of experience with QPainter, I failed and the red line was not drawn on the widget.

For now, I don't want to believe that this is a flaw in QMediaPlayer, and eventually I'll just have to accept it and look for more and more workarounds.

I am using: Windows 10 x64 1809, PyQt5 5.15.10


Solution

  • A conditional and, in my opinion, incomplete solution was a call to

    import os
    os.environ['QT_MULTIMEDIA_PREFERRED_PLUGINS'] = "windowsmediafoundation"
    

    at the beginning of the code. This code changes the default DirectShow to Windows Media Foundation. This only works on Windows and has its drawbacks, but at least it solves my artifact problem.