Python结合PyQt5实现视频播放器

在当今数字化时代,视频播放器已成为人们日常生活中不可或缺的工具之一。从家庭娱乐到教育培训,从个人消遣到专业视频编辑,一款功能强大且易于使用的视频播放器能够极大地方便我们的生活和工作。Python作为一种简洁而强大的编程语言,结合PyQt5图形用户界面库,为我们提供了一个理想的平台,来构建一个功能丰富、界面友好的视频播放器。


效果如下:
在这里插入图片描述

但是在这之前我们可能会缺少视频解码器
因为PyQt5 本身不包含视频解码器,它依赖系统安装的解码器

解决方案:
Windows:安装 K-Lite Codec Pack(建议选择"标准版"或"完整版")
Linux:安装 GStreamer 和必要插件:

sudo apt install gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-plugins-good gstreamer1.0-libav

macOS:安装 FFmpeg:

brew install ffmpeg

代码如下:
import sys
import os
from PyQt5.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
    QPushButton, QSlider, QFileDialog, QLabel, QStyle,
    QGraphicsDropShadowEffect, QSizePolicy, QStatusBar, QMessageBox
)
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
from PyQt5.QtMultimediaWidgets import QVideoWidget
from PyQt5.QtCore import Qt, QUrl, QSize, QTimer
from PyQt5.QtGui import QIcon, QColor, QFont, QPalette, QPixmap


class ModernVideoPlayer(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("Modern MP4 Player")
        self.setGeometry(100, 100, 1000, 700)
        self.setMinimumSize(800, 500)

        # 设置应用样式
        self.setStyleSheet("""
            QMainWindow {
                background-color: #1e1e2e;
            }
            QWidget {
                color: #cdd6f4;
                font-family: 'Segoe UI', Arial, sans-serif;
            }
            QPushButton {
                background-color: #313244;
                border: none;
                border-radius: 5px;
                padding: 8px;
                min-width: 40px;
                min-height: 40px;
            }
            QPushButton:hover {
                background-color: #45475a;
            }
            QPushButton:pressed {
                background-color: #585b70;
            }
            QSlider::groove:horizontal {
                height: 6px;
                background: #313244;
                border-radius: 3px;
            }
            QSlider::handle:horizontal {
                background: #f5c2e7;
                border: 1px solid #cba6f7;
                width: 16px;
                margin: -6px 0;
                border-radius: 8px;
            }
            QSlider::sub-page:horizontal {
                background: #cba6f7;
                border-radius: 3px;
            }
            QStatusBar {
                background-color: #181825;
                color: #a6adc8;
                font-size: 10pt;
            }
            QLabel {
                font-size: 11pt;
            }
        """)

        # 创建媒体播放器
        self.media_player = QMediaPlayer()
        self.media_player.setNotifyInterval(100)

        # 创建视频小部件
        self.video_widget = QVideoWidget()
        self.video_widget.setStyleSheet("background-color: #11111b;")

        # 创建UI组件
        self.create_ui()

        # 连接信号
        self.media_player.positionChanged.connect(self.update_position)
        self.media_player.durationChanged.connect(self.update_duration)
        self.media_player.stateChanged.connect(self.update_buttons)
        self.media_player.volumeChanged.connect(self.update_volume)
        self.media_player.mediaStatusChanged.connect(self.handle_media_status)

        # 状态变量
        self.is_fullscreen = False
        self.controls_visible = True

        # 创建状态栏
        self.status_bar = QStatusBar()
        self.setStatusBar(self.status_bar)
        self.status_bar.showMessage("就绪 - 拖放MP4文件到窗口或点击打开按钮")

        # 设置初始音量
        self.media_player.setVolume(70)

        # 用于控制自动隐藏的计时器
        self.controls_timer = QTimer(self)
        self.controls_timer.setInterval(3000)
        self.controls_timer.timeout.connect(self.hide_controls)

        # 接受拖放
        self.setAcceptDrops(True)

        # 默认封面
        self.cover_label = QLabel(self)
        self.cover_label.setAlignment(Qt.AlignCenter)
        cover_pixmap = QPixmap(":default_cover.png")
        if cover_pixmap.isNull():
            # 创建默认封面
            self.cover_label.setText("MP4 Player\n拖放视频文件到此处")
            self.cover_label.setStyleSheet("""
                QLabel {
                    font-size: 24px;
                    color: #cba6f7;
                    background-color: #11111b;
                    border: 2px dashed #cba6f7;
                    border-radius: 10px;
                    padding: 30px;
                }
            """)
        else:
            self.cover_label.setPixmap(cover_pixmap.scaled(400, 300, Qt.KeepAspectRatio, Qt.SmoothTransformation))
        self.cover_label.setGeometry(300, 150, 400, 300)
        self.cover_label.show()

    def create_ui(self):
        # 主窗口布局
        central_widget = QWidget(self)
        self.setCentralWidget(central_widget)
        main_layout = QVBoxLayout(central_widget)
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(0)

        # 视频显示区域
        main_layout.addWidget(self.video_widget, 1)
        self.media_player.setVideoOutput(self.video_widget)

        # 添加阴影效果
        self.add_shadow_effect(self.video_widget)

        # 控制面板容器
        self.control_container = QWidget()
        self.control_container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.control_container.setStyleSheet("background-color: rgba(24, 24, 37, 220);")

        # 控制面板布局
        control_layout = QVBoxLayout(self.control_container)
        control_layout.setContentsMargins(20, 10, 20, 15)

        # 进度条
        self.position_slider = QSlider(Qt.Horizontal)
        self.position_slider.setRange(0, 0)
        self.position_slider.sliderMoved.connect(self.set_position)
        self.position_slider.setStyleSheet("""
            QSlider::groove:horizontal {
                height: 8px;
                background: #313244;
                border-radius: 4px;
            }
            QSlider::sub-page:horizontal {
                background: #cba6f7;
                border-radius: 4px;
            }
            QSlider::handle:horizontal {
                background: #f5c2e7;
                border: 2px solid #cba6f7;
                width: 20px;
                margin: -8px 0;
                border-radius: 10px;
            }
        """)
        control_layout.addWidget(self.position_slider)

        # 时间标签
        self.time_label = QLabel("00:00 / 00:00")
        self.time_label.setAlignment(Qt.AlignCenter)
        self.time_label.setFont(QFont("Segoe UI", 10))
        control_layout.addWidget(self.time_label)

        # 按钮区域
        button_layout = QHBoxLayout()
        button_layout.setContentsMargins(0, 10, 0, 0)

        # 打开文件按钮
        self.open_button = QPushButton()
        self.open_button.setIcon(self.style().standardIcon(QStyle.SP_DialogOpenButton))
        self.open_button.setToolTip("打开视频文件")
        self.open_button.clicked.connect(self.open_file)
        self.add_button_effect(self.open_button)
        button_layout.addWidget(self.open_button)

        # 播放/暂停按钮
        self.play_button = QPushButton()
        self.play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))
        self.play_button.setToolTip("播放/暂停")
        self.play_button.clicked.connect(self.play_video)
        self.play_button.setFixedSize(50, 50)
        self.add_button_effect(self.play_button)
        button_layout.addWidget(self.play_button)

        # 停止按钮
        self.stop_button = QPushButton()
        self.stop_button.setIcon(self.style().standardIcon(QStyle.SP_MediaStop))
        self.stop_button.setToolTip("停止")
        self.stop_button.clicked.connect(self.stop_video)
        self.add_button_effect(self.stop_button)
        button_layout.addWidget(self.stop_button)

        # 快退按钮
        self.rewind_button = QPushButton()
        self.rewind_button.setIcon(self.style().standardIcon(QStyle.SP_MediaSeekBackward))
        self.rewind_button.setToolTip("快退10秒")
        self.rewind_button.clicked.connect(self.rewind)
        self.add_button_effect(self.rewind_button)
        button_layout.addWidget(self.rewind_button)

        # 快进按钮
        self.forward_button = QPushButton()
        self.forward_button.setIcon(self.style().standardIcon(QStyle.SP_MediaSeekForward))
        self.forward_button.setToolTip("快进10秒")
        self.forward_button.clicked.connect(self.forward)
        self.add_button_effect(self.forward_button)
        button_layout.addWidget(self.forward_button)

        # 音量区域
        volume_layout = QHBoxLayout()
        volume_layout.setSpacing(10)

        # 音量图标
        self.volume_button = QPushButton()
        self.volume_button.setIcon(self.style().standardIcon(QStyle.SP_MediaVolume))
        self.volume_button.setToolTip("静音")
        self.volume_button.clicked.connect(self.toggle_mute)
        self.add_button_effect(self.volume_button)
        volume_layout.addWidget(self.volume_button)

        # 音量滑块
        self.volume_slider = QSlider(Qt.Horizontal)
        self.volume_slider.setRange(0, 100)
        self.volume_slider.setValue(70)
        self.volume_slider.setFixedWidth(100)
        self.volume_slider.valueChanged.connect(self.set_volume)
        volume_layout.addWidget(self.volume_slider)

        button_layout.addLayout(volume_layout)

        # 全屏按钮
        self.fullscreen_button = QPushButton()
        self.fullscreen_button.setIcon(self.style().standardIcon(QStyle.SP_TitleBarMaxButton))
        self.fullscreen_button.setToolTip("全屏")
        self.fullscreen_button.clicked.connect(self.toggle_fullscreen)
        self.add_button_effect(self.fullscreen_button)
        button_layout.addWidget(self.fullscreen_button)

        control_layout.addLayout(button_layout)

        # 添加控制面板到主布局
        main_layout.addWidget(self.control_container)

        # 添加视频标题标签
        self.title_label = QLabel("现代MP4播放器")
        self.title_label.setAlignment(Qt.AlignCenter)
        self.title_label.setFont(QFont("Segoe UI", 16, QFont.Bold))
        self.title_label.setStyleSheet("color: #f5c2e7; background-color: rgba(17, 17, 27, 180); padding: 10px;")
        main_layout.addWidget(self.title_label, 0, Qt.AlignTop)

        # 设置视频部件的事件过滤器
        self.video_widget.installEventFilter(self)

    def add_shadow_effect(self, widget):
        shadow = QGraphicsDropShadowEffect()
        shadow.setBlurRadius(20)
        shadow.setColor(QColor(0, 0, 0, 180))
        shadow.setOffset(0, 5)
        widget.setGraphicsEffect(shadow)

    def add_button_effect(self, button):
        shadow = QGraphicsDropShadowEffect()
        shadow.setBlurRadius(10)
        shadow.setColor(QColor(203, 166, 247, 100))
        shadow.setOffset(0, 2)
        button.setGraphicsEffect(shadow)

    def open_file(self, file_path=None):
        """打开视频文件"""
        if not file_path:
            file_path, _ = QFileDialog.getOpenFileName(
                self, "选择MP4视频文件",
                os.path.expanduser("~/Videos"),
                "MP4视频文件 (*.mp4);;所有文件 (*)"
            )

        if file_path and os.path.exists(file_path):
            # 隐藏封面
            self.cover_label.hide()

            try:
                # 确保使用正确的路径格式
                media_url = QUrl.fromLocalFile(file_path)
                self.media_player.setMedia(QMediaContent(media_url))

                # 启用按钮
                self.play_button.setEnabled(True)
                self.stop_button.setEnabled(True)
                self.rewind_button.setEnabled(True)
                self.forward_button.setEnabled(True)

                # 更新标题
                self.title_label.setText(os.path.basename(file_path))
                self.status_bar.showMessage(f"已加载: {os.path.basename(file_path)}")

                # 自动开始播放
                self.media_player.play()

            except Exception as e:
                self.status_bar.showMessage(f"文件加载错误: {str(e)}")
                QMessageBox.warning(self, "文件错误", f"无法加载文件:\n{str(e)}")

    def dragEnterEvent(self, event):
        """处理拖放进入事件"""
        if event.mimeData().hasUrls():
            urls = event.mimeData().urls()
            if urls and urls[0].isLocalFile() and urls[0].toLocalFile().lower().endswith('.mp4'):
                event.acceptProposedAction()

    def dropEvent(self, event):
        """处理拖放事件"""
        for url in event.mimeData().urls():
            if url.isLocalFile():
                file_path = url.toLocalFile()
                if file_path.lower().endswith('.mp4'):
                    self.open_file(file_path)
                    break

    def play_video(self):
        """播放/暂停视频"""
        if self.media_player.mediaStatus() == QMediaPlayer.NoMedia:
            self.open_file()
            return

        if self.media_player.state() == QMediaPlayer.PlayingState:
            self.media_player.pause()
            self.status_bar.showMessage("已暂停")
        else:
            self.media_player.play()
            self.status_bar.showMessage("正在播放")

    def stop_video(self):
        """停止视频"""
        self.media_player.stop()
        self.status_bar.showMessage("已停止")

    def set_position(self, position):
        """设置播放位置"""
        self.media_player.setPosition(position)

    def set_volume(self, volume):
        """设置音量"""
        self.media_player.setVolume(volume)

        # 更新音量按钮图标
        if volume == 0:
            self.volume_button.setIcon(self.style().standardIcon(QStyle.SP_MediaVolumeMuted))
        else:
            self.volume_button.setIcon(self.style().standardIcon(QStyle.SP_MediaVolume))

    def toggle_mute(self):
        """切换静音状态"""
        if self.media_player.isMuted():
            self.media_player.setMuted(False)
            self.volume_button.setIcon(self.style().standardIcon(QStyle.SP_MediaVolume))
        else:
            self.media_player.setMuted(True)
            self.volume_button.setIcon(self.style().standardIcon(QStyle.SP_MediaVolumeMuted))

    def rewind(self):
        """快退10秒"""
        current_pos = self.media_player.position()
        self.media_player.setPosition(max(0, current_pos - 10000))

    def forward(self):
        """快进10秒"""
        current_pos = self.media_player.position()
        duration = self.media_player.duration()
        self.media_player.setPosition(min(duration, current_pos + 10000))

    def update_position(self, position):
        """更新进度条位置和时间标签"""
        self.position_slider.setValue(position)

        # 更新时间标签
        duration = self.media_player.duration()
        if duration > 0:
            current_time = self.format_time(position)
            total_time = self.format_time(duration)
            self.time_label.setText(f"{current_time} / {total_time}")

    def update_duration(self, duration):
        """更新进度条范围"""
        self.position_slider.setRange(0, duration)

    def update_buttons(self, state):
        """更新按钮图标"""
        if state == QMediaPlayer.PlayingState:
            self.play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaPause))
        else:
            self.play_button.setIcon(self.style().standardIcon(QStyle.SP_MediaPlay))

    def update_volume(self, volume):
        """更新音量滑块位置"""
        self.volume_slider.setValue(volume)

    def format_time(self, milliseconds):
        """将毫秒转换为 MM:SS 格式"""
        seconds = milliseconds // 1000
        minutes = seconds // 60
        seconds = seconds % 60
        return f"{minutes:02d}:{seconds:02d}"

    def toggle_fullscreen(self):
        """切换全屏模式"""
        if self.is_fullscreen:
            self.showNormal()
            self.statusBar().show()
            self.control_container.show()
            self.title_label.show()
            self.is_fullscreen = False
        else:
            self.showFullScreen()
            self.statusBar().hide()
            self.control_container.hide()
            self.title_label.hide()
            self.is_fullscreen = True

    def eventFilter(self, source, event):
        """处理视频区域事件"""
        if source is self.video_widget:
            if event.type() == event.MouseButtonDblClick:
                self.toggle_fullscreen()
                return True
            elif event.type() == event.MouseMove:
                if self.is_fullscreen:
                    self.control_container.show()
                    self.title_label.show()
                    self.controls_timer.start()
                return True
        return super().eventFilter(source, event)

    def hide_controls(self):
        """在全屏模式下隐藏控制面板"""
        if self.is_fullscreen:
            self.control_container.hide()
            self.title_label.hide()
            self.controls_timer.stop()

    def handle_media_status(self, status):
        """处理媒体状态变化"""
        if status == QMediaPlayer.LoadedMedia:
            self.status_bar.showMessage("媒体已加载")
        elif status == QMediaPlayer.BufferingMedia:
            self.status_bar.showMessage("缓冲中...")
        elif status == QMediaPlayer.StalledMedia:
            self.status_bar.showMessage("媒体卡顿,正在缓冲...")
        elif status == QMediaPlayer.EndOfMedia:
            self.status_bar.showMessage("播放完成")
        elif status == QMediaPlayer.InvalidMedia:
            self.status_bar.showMessage("无法播放此媒体文件")
            # 显示错误提示
            QMessageBox.warning(self, "播放错误",
                                "无法播放此MP4文件。\n\n"
                                "请确保:\n"
                                "1. 文件是有效的MP4格式\n"
                                "2. 系统已安装必要的解码器\n\n"
                                "在Windows上,建议安装K-Lite解码器包。")


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

    # 设置应用样式
    app.setStyle("Fusion")

    # 创建并设置调色板
    palette = app.palette()
    palette.setColor(QPalette.Window, QColor(30, 30, 46))
    palette.setColor(QPalette.WindowText, QColor(205, 214, 244))
    palette.setColor(QPalette.Base, QColor(24, 24, 37))
    palette.setColor(QPalette.AlternateBase, QColor(49, 50, 68))
    palette.setColor(QPalette.ToolTipBase, QColor(17, 17, 27))
    palette.setColor(QPalette.ToolTipText, QColor(205, 214, 244))
    palette.setColor(QPalette.Text, QColor(205, 214, 244))
    palette.setColor(QPalette.Button, QColor(49, 50, 68))
    palette.setColor(QPalette.ButtonText, QColor(205, 214, 244))
    palette.setColor(QPalette.BrightText, QColor(245, 194, 231))
    palette.setColor(QPalette.Highlight, QColor(203, 166, 247))
    palette.setColor(QPalette.HighlightedText, QColor(17, 17, 27))
    app.setPalette(palette)

    player = ModernVideoPlayer()
    player.show()
    sys.exit(app.exec_())
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

准时准点睡觉

如果觉得不错可以点点这里哦

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值