Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

PyQt5 Video Playback: Minimal Player, Custom UI, and Progress Controls

Tech 1

On Windows, QMediaPlayer relies on system codecs. If MP4 fails to play while AVI works (e.g., DirectShowPlayerService errors), installl appropriate H.264/AAC codecs or enable the Windows Media Feature Pack.

Minimal player

A bare-bones player that opens a file dialog, renders video in a QVideoWidget, and starts playback.

import sys
from PyQt5.QtWidgets import QApplication, QFileDialog
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
from PyQt5.QtMultimediaWidgets import QVideoWidget

if __name__ == "__main__":
    app = QApplication(sys.argv)

    video_surface = QVideoWidget()
    video_surface.resize(800, 450)
    video_surface.show()

    media_player = QMediaPlayer()
    media_player.setVideoOutput(video_surface)

    url, _ = QFileDialog.getOpenFileUrl(caption="Open video")
    if url.isValid():
        media_player.setMedia(QMediaContent(url))
        media_player.play()

    sys.exit(app.exec_())

To hardcode a local file, replace the file dialog with a file URL:

from PyQt5.QtCore import QUrl

# media_player.setMedia(QMediaContent(QFileDialog.getOpenFileUrl()[0]))
media_player.setMedia(QMediaContent(QUrl.fromLocalFile(r"C:\Users\user\Videos\sample.mp4")))

UI designed with Qt Desigenr

Build a simple window using Qt Designer and wire it to QMediaPlayer. In Designer:

  • Add a placeholder QWidget where the video should render, then promote it to QVideoWidget (class name: QVideoWidget).
  • Add a button (objectName e.g., btnOpen).
  • Save as video_basic.ui.
import sys
from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QFileDialog
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent

class BasicPlayer:
    def __init__(self):
        self.ui = uic.loadUi("video_basic.ui")
        self.player = QMediaPlayer()
        # In Designer, the promoted widget objectName should be videoSurface
        self.player.setVideoOutput(self.ui.videoSurface)
        self.ui.btnOpen.clicked.connect(self._open_video)

    def _open_video(self):
        url, _ = QFileDialog.getOpenFileUrl(self.ui, "Open video")
        if url.isValid():
            self.player.setMedia(QMediaContent(url))
            self.player.play()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = BasicPlayer()
    window.ui.show()
    sys.exit(app.exec_())

Playback controls: play/pause, seek bar, remaining time

Extend the UI to include:

  • A play/pause button (btnToggle)
  • A horizontal slider for progress (sldPosition)
  • A label for remaining time (lblRemaining)
  • The promoted video widget (videoSurface)

Save this layout as player_controls.ui.

import sys
from PyQt5 import uic
from PyQt5.QtWidgets import QApplication, QFileDialog
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent

class PlayerWithControls:
    def __init__(self):
        self.ui = uic.loadUi("player_controls.ui")

        self.media = QMediaPlayer()
        self.media.setVideoOutput(self.ui.videoSurface)

        self._duration = 0
        self._seeking = False

        # Buttons
        self.ui.btnOpen.clicked.connect(self._open)
        self.ui.btnToggle.clicked.connect(self._toggle)

        # Timeline and time updates
        self.media.durationChanged.connect(self._on_duration)
        self.media.positionChanged.connect(self._on_position)
        self.ui.sldPosition.sliderPressed.connect(self._on_seek_start)
        self.ui.sldPosition.sliderReleased.connect(self._on_seek_end)
        self.ui.sldPosition.sliderMoved.connect(self._on_seek_preview)

    def _open(self):
        url, _ = QFileDialog.getOpenFileUrl(self.ui, "Open video")
        if url.isValid():
            self.media.setMedia(QMediaContent(url))
            self.media.play()
            self.ui.btnToggle.setText("Pause")

    def _toggle(self):
        if self.media.state() == QMediaPlayer.PlayingState:
            self.media.pause()
            self.ui.btnToggle.setText("Play")
        else:
            self.media.play()
            self.ui.btnToggle.setText("Pause")

    def _on_duration(self, ms):
        self._duration = max(0, ms)
        self.ui.sldPosition.setEnabled(self._duration > 0)
        self.ui.sldPosition.setRange(0, self._duration)
        self._update_remaining(self._duration)

    def _on_position(self, ms):
        if not self._seeking:
            self.ui.sldPosition.setValue(ms)
        self._update_remaining(self._duration - ms)

    def _on_seek_start(self):
        self._seeking = True

    def _on_seek_end(self):
        self._seeking = False
        self.media.setPosition(self.ui.sldPosition.value())
        self._update_remaining(self._duration - self.ui.sldPosition.value())

    def _on_seek_preview(self, value):
        # Show remaining time while dragging
        self._update_remaining(self._duration - value)

    def _update_remaining(self, ms):
        if ms < 0:
            ms = 0
        total_seconds = ms // 1000
        minutes, seconds = divmod(int(total_seconds), 60)
        self.ui.lblRemaining.setText(f"{minutes:02d}:{seconds:02d}")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = PlayerWithControls()
    w.ui.show()
    sys.exit(app.exec_())

Appearance tweaks

  • Window chrome:
from PyQt5.QtGui import QIcon

self.ui.setWindowTitle("PyQt5 Video Player")
self.ui.setWindowIcon(QIcon("icons/app.png"))
  • Basic styling:
self.ui.setStyleSheet(
    """
    QWidget { background-color: #121212; color: #e0e0e0; }
    QSlider::groove:horizontal { height: 6px; background: #333; }
    QSlider::handle:horizontal { width: 12px; background: #00bcd4; margin: -4px 0; border-radius: 6px; }
    QPushButton { padding: 6px 10px; }
    """
)

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.