Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

Problem with sub-classed QVideoWidget on MacOS



  • In my PyQt5 project, I'm sub-classing a QVideoWidget to a class called ResizeableVideoWidget. I started out by adapting code from this page.

    My semi-minimal code (below) works as expected on Windows, but not on a Mac.

    When run, it should prompt you to browse to a video file, then play it in a ResizeableVideoWidget. On Windows, no problem, it works.

    But on Mac (and not Windows), there's a warning thrown:

    QWidget::setLayout: Attempting to set QLayout "" on ResizeableVideoWidget "", which already has a layout
    

    The ResizeableVideoWidget never appears, meaning that the placeholder QLabel saying "Video panel" is visible instead. You can hear the video playing, but not see it!

    Please can somebody have a look at this and explain whether there's a reason why it's not Mac-friendly? Am I doing something wrong? How would I make a Mac-friendly PyQt5 version of this code?

    import platform
    import sys
    
    from PyQt5 import QtCore
    from PyQt5.QtCore import pyqtSignal, QRect, QUrl
    from PyQt5.QtMultimedia import QMediaContent, QMediaPlayer
    from PyQt5.QtMultimedia import QMediaPlaylist
    from PyQt5.QtMultimediaWidgets import QVideoWidget
    from PyQt5.QtWidgets import QApplication, QDialog, QGridLayout, QLabel, QMenu, QVBoxLayout, QFileDialog
    
    
    # Some code adapted from:
    # https://raw.githubusercontent.com/korabelnikov/moveable-and-resize-qt-widget-on-python/master/tcontainer.py
    
    
    class ResizeableVideoWidget(QVideoWidget):
        menu = None
        inFocus = pyqtSignal(bool)
        outFocus = pyqtSignal(bool)
        newGeometry = pyqtSignal(QRect)
        frameStep = pyqtSignal(int)
        customKeyPressed = pyqtSignal(str)
        clicked = pyqtSignal()
    
        def __init__(self, _parent, cWidget):
            super().__init__(parent=_parent)
            self.parent = _parent
    
            self.menu = QMenu(parent=self, title='Video options')
            self.menuActions = []
            self.setAttribute(QtCore.Qt.WA_DeleteOnClose, True)
            self.setVisible(True)
            self.setAutoFillBackground(False)
            self.setFocusPolicy(QtCore.Qt.ClickFocus)
            self.setFocus()
            self.childWidget = None
    
            self.vLayout = QVBoxLayout()
            if cWidget is not None:
                self.setChildWidget(cWidget)
            self.setLayout(self.vLayout)
    
            self.installEventFilter(_parent)
    
        def setChildWidget(self, cWidget):
            if cWidget:
                self.childWidget = cWidget
                self.childWidget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents, True)
                self.childWidget.setParent(self)
                self.childWidget.releaseMouse()
                self.vLayout.addWidget(cWidget)
                self.vLayout.setContentsMargins(0, 0, 0, 0)
    
    
    class VideoWidgetTest(QDialog):
        def __init__(self):
            super().__init__()
            self.title = 'PyQt5 tests'
            self.setMinimumSize(200, 200)
            self.setWindowFlags(QtCore.Qt.Window |
                                QtCore.Qt.WindowTitleHint |
                                QtCore.Qt.WindowCloseButtonHint)
    
            self.layout = QGridLayout(self)
            self.placeholder = QLabel("Video panel")
            self.videoWidget = ResizeableVideoWidget(self, self.placeholder)
            self.layout.addWidget(self.videoWidget, 0, 0)
            self.footerLabel = QLabel("Footer")
            self.layout.addWidget(self.footerLabel, 1, 0)
    
            self.mediaPlayer = QMediaPlayer(None, QMediaPlayer.VideoSurface)
            self.mediaPlayer.setVideoOutput(self.videoWidget)
    
            openDialog = QFileDialog()
            fileName, action = openDialog.getOpenFileName(self, "Please browse to a video file")
    
            thisQurl = QUrl.fromLocalFile(str(fileName))
            fileObject = QMediaContent(thisQurl)
            self.newPlaylist = QMediaPlaylist()
            self.newPlaylist.addMedia(fileObject)
            self.mediaPlayer.setPlaylist(self.newPlaylist)
            self.mediaPlayer.play()
    
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        ex = VideoWidgetTest()
        ex.show()
        sys.exit(app.exec_())
    
    

  • Lifetime Qt Champion

    Hi,

    It's pretty unusual to use a QVideoWidget like that. A QStackedWidget comes to mind when sharing the same space between two widgets.



  • Can the output of a QMediaPlayer be sent directly to a QStackedWidget, rather than a QVideoWidget? Can I swap one in for the other, or is it more complex than that?

    The stacking involved is all part of the code that I have adapted from https://raw.githubusercontent.com/korabelnikov/moveable-and-resize-qt-widget-on-python/master/tcontainer.py.

    I took it out of the stripped-down example I posted, but the full code includes the ability to resize the widget (independently of the form it's on) by dragging at the corners of it. This is a functionality I'm really after, and on Windows it's working great, but not on Mac.


  • Lifetime Qt Champion

    That would likely be involved. You can check the QVideoWidget sources.

    Can you provide a minimal compilable example that shows that behaviour ?



  • My original code example (above) is not fully minimal but it's not too far off, and it should be compilable.

    The result on Windows looks like this:

    Image of working QMediaPlayer in Windows

    The exact same source code and same video file on a Mac gives this:

    Image of not-working QMediaPlayer in MacOS

    (Test video file downloaded for free from Pexels)


  • Lifetime Qt Champion

    After doing some checks I am surprised that your code works on Windows too. QVideoWidget already has a layout so you should at least get a warning about that.

    Are you getting error messages on your macOS ?

    On a side note, layout is a method of QWidget so replacing it like you are doing in your code is not recommended at all.



  • On Mac it is throwing a warning, rather than an error:

    QWidget::setLayout: Attempting to set QLayout "" on ResizeableVideoWidget "", which already has a layout

    Having adapted the code that I found at this page, from another author, I think that maybe I'm misunderstanding what the correct structure should be.

    Please could somebody give me a pointer about how I would properly get an independently sizeable QVideoWidget inside a form? Essentially I need a QVideoWidget where the user can drag the bottom or right sides of the panel to change the size of the video. The existing code is working on Windows, but of course if it should be improved I'll happily try it.



  • After staring at this problem for an excessively long time, I've realised my mistake. As @SGaist said, the approach was unorthodox in my code because it was confusing the videoWidget object and the wrapper around it, conflating it and trying to treat it like a single item. Once I had a fresh go at building it from scratch, I was able to make the appropriate adjustments, making sure the QVideoWidget was always treated as a child of the ResizeableVideoWidget rather than trying to sub-class QVideoWidget into a ResizeableVideoWidget.

    What was throwing me in the wrong direction was that even though my code was wrong, PyQt5 on Windows was tolerant enough of the odd arrangement to allow it to continue working, so I incorrectly assumed that those bits of the code were correct.

    Thanks again for your help.


Log in to reply