PyQt5 Video Playback: Minimal Player, Custom UI, and Progress Controls
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; }
"""
)