当前位置: 首页 > news >正文

使用python实现视频播放器(支持拖动播放位置跳转)

使用python实现视频播放器(支持拖动播放位置跳转)

Python实现视频播放器,在我早期的博文中介绍或作为资料记录过

Python实现视频播放器 https://blog.csdn.net/cnds123/article/details/145926189

Python实现本地视频/音频播放器https://blog.csdn.net/cnds123/article/details/137874107

Python简单GUI程序示例 中 “四、视频播放器” https://blog.csdn.net/cnds123/article/details/122903311

但是,一直不尽人意。现在,再介绍一个。

这是一个基于 PyQt6 和 python-vlc 开发的视频播放器,主要实现了我多次试图实现未果的功能

——带有播放进度条,不仅显示播放进度,还支持拖动播放位置跳转。

普通版视频播放器

主要特点

    播放画面随窗口缩放

    支持常见格式(MP4、AVI、MKV 等)

    通过文件对话框加载视频文件

    播放/暂停、停止、播放进度跳转、音量调节

    显示视频文件名、当前播放时间、总时长

播放进度条:显示播放进度并支持拖动播放位置跳转。

音量滑块:音量滑块调整音量大小。

需安装以下 Python第三方库:

python-vlc、 PyQt6

Windows中,还要安装 VLC 播放器,其下载 地址 https://www.videolan.org/vlc/ 。否则,将报错:缺少 libvlc.dll。

运行效果界面如下:

基本使用操作

    打开文件:点击菜单栏 文件 > 打开文件(快捷键 Ctrl+O) 或底部 打开文件 按钮

    播放/暂停:空格键 或 点击 按钮切换

    停止:停止 按钮   

    进度跳转:拖动进度条

    音量调节:拖动底部音量滑块

源码如下:

import sys
import time
import vlc
import os
from PyQt6 import QtWidgets, QtCore, QtGui

class VLCPlayer(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.instance = vlc.Instance()
        self.player = self.instance.media_player_new()
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.update_ui)
        self.current_file = None
        self.media_loaded = False
        self.init_ui()

    def init_ui(self):
        # 主窗口设置
        self.setWindowTitle("PyQt6 VLC Player")
        self.resize(800, 600)

        # 创建菜单栏
        menubar = self.menuBar()
        file_menu = menubar.addMenu("文件(&F)")

        # 添加"打开"动作
        open_action = QtGui.QAction("打开文件...", self)
        open_action.setShortcut("Ctrl+O")
        open_action.triggered.connect(self.open_file)
        file_menu.addAction(open_action)

        # 创建主容器和布局
        central_widget = QtWidgets.QWidget(self)
        self.setCentralWidget(central_widget)
        main_layout = QtWidgets.QVBoxLayout(central_widget)
        main_layout.setContentsMargins(0, 0, 0, 0)

        # 视频标题标签 
        self.title_label = QtWidgets.QLabel("当前未选择媒体文件")
        self.title_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        self.title_label.setStyleSheet("font-size: 14px; color: #666; margin: 5px;")
        main_layout.addWidget(self.title_label)
        
        # 视频显示区域
        self.video_widget = QtWidgets.QWidget()
        self.video_widget.setStyleSheet("background-color: black;")
        main_layout.addWidget(self.video_widget, stretch=1)

        # 控制面板
        control_panel = QtWidgets.QWidget()
        control_layout = QtWidgets.QVBoxLayout(control_panel)

        # 进度条
        self.progress_bar = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal)
        self.progress_bar.setMinimum(0)
        self.progress_bar.sliderMoved.connect(self.set_position)
        control_layout.addWidget(self.progress_bar)

        # 时间标签
        self.time_label = QtWidgets.QLabel("00:00:00 / 00:00:00")
        control_layout.addWidget(self.time_label)

        # 控制按钮
        button_layout = QtWidgets.QHBoxLayout()
        self.play_btn = QtWidgets.QPushButton("播放")
        self.play_btn.clicked.connect(self.toggle_play)
        self.stop_btn = QtWidgets.QPushButton("停止")
        self.stop_btn.clicked.connect(self.stop)
        self.open_btn = QtWidgets.QPushButton("打开文件")
        self.open_btn.clicked.connect(self.open_file)
        
        # 音量控制
        self.volume_slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal)
        self.volume_slider.setRange(0, 100)
        self.volume_slider.setValue(100)
        self.volume_slider.valueChanged.connect(self.set_volume)

        # 添加控件
        button_layout.addWidget(self.open_btn)
        button_layout.addWidget(self.play_btn)
        button_layout.addWidget(self.stop_btn)
        button_layout.addWidget(QtWidgets.QLabel("音量:"))
        button_layout.addWidget(self.volume_slider)
        control_layout.addLayout(button_layout)

        main_layout.addWidget(control_panel)

        # 设置VLC渲染
        if sys.platform == "win32":
            self.player.set_hwnd(int(self.video_widget.winId()))
        elif sys.platform == "linux":
            self.player.set_xwindow(self.video_widget.winId())
        elif sys.platform == "darwin":
            from PyQt6.QtGui import QCocoaNativeContext
            self.player.set_nsobject(int(QCocoaNativeContext(self.video_widget.winId()).nsview()))

        self.timer.start(200)

    def open_file(self):
        # 修复1:打开文件前先停止播放
        if self.player.is_playing():
            self.player.stop()
            self.media_loaded = False
            self.play_btn.setText("播放")

        file_dialog = QtWidgets.QFileDialog(self)
        file_dialog.setNameFilter("视频文件 (*.mp4 *.avi *.mkv *.mov *.flv)")
        if file_dialog.exec():
            selected_files = file_dialog.selectedFiles()
            if selected_files:
                self.load_media(selected_files[0])

    def load_media(self, file_path):
        try:
            # 新增:停止定时器避免冲突
            self.timer.stop()
            
            # 修复2:确保彻底释放旧媒体资源
            self.player.stop()
            self.player.set_media(None)  # 清除旧媒体引用

            # 重置状态
            self.media_loaded = False
            self.play_btn.setText("播放")
            self.progress_bar.setValue(0)
            self.time_label.setText("00:00:00 / 00:00:00")

            # 加载新文件
            self.current_file = file_path
            media = self.instance.media_new(file_path)
            self.player.set_media(media)
            
            # 更新标题
            file_name = os.path.basename(file_path)
            self.title_label.setText(f"当前播放: {file_name}")

            # 修复3:异步解析媒体信息(避免阻塞UI)
            media.parse_with_options(vlc.MediaParseFlag.network, 1000)
            
            # 设置进度条最大值
            self.progress_bar.setMaximum(media.get_duration())
            self.media_loaded = True
            
            # 显示总时长
            total_time = time.strftime("%H:%M:%S", time.gmtime(media.get_duration() // 1000))
            self.time_label.setText(f"00:00:00 / {total_time}")

            # 新增:确保媒体加载完成后再启定时器
            self.timer.start(200)

        except Exception as e:
            QtWidgets.QMessageBox.critical(self, "错误", f"无法加载文件:\n{str(e)}")
            self.media_loaded = False
            self.title_label.setText("媒体加载失败")

    def toggle_play(self):
        if not self.media_loaded:
            self.open_file()
            return
            
        # 修复4:强制同步按钮状态
        if self.player.is_playing():
            self.player.pause()
            self.play_btn.setText("播放")
        else:
            self.player.play()
            self.play_btn.setText("暂停")

    def stop(self):
        self.player.stop()
        self.progress_bar.setValue(0)
        self.time_label.setText("00:00:00 / 00:00:00")
        self.play_btn.setText("播放")
        self.title_label.setText("播放已停止")

    def set_volume(self, value):
        self.player.audio_set_volume(value)

    def set_position(self, value):
        if self.player.is_seekable():
            self.player.set_position(value / self.progress_bar.maximum())

    def update_ui(self):
        if not self.media_loaded:
            return  # 新增:防止在无媒体时更新

        media_length = self.player.get_length()
        if media_length > 0:
            current_time = self.player.get_time()
            
            # 新增:检测播放结束
            if current_time >= media_length - 500:  # 留50ms容差
                self.stop()
                return

            self.progress_bar.setMaximum(media_length)
            self.progress_bar.setValue(current_time)

            total_time = time.strftime("%H:%M:%S", time.gmtime(media_length // 1000))
            current_time_str = time.strftime("%H:%M:%S", time.gmtime(current_time // 1000))
            self.time_label.setText(f"{current_time_str} / {total_time}")


    def closeEvent(self, event):
        self.player.stop()
        event.accept()

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    player = VLCPlayer()
    player.show()
    sys.exit(app.exec())

专用版视频播放控制器(视频播控器)

主要添加视频“加密”、“解密”功能

运行条件除了和上面的一样外,还需安装第三方库pycryptodome

pycryptodome 是一个强大的加密库,用于实现加密算法。

界面如下:

1)加密视频

菜单栏点击 安全 → 加密视频

优先当前播放文件路径,即:

    如果当前正在播放视频且未加密 → 弹出确认对话框

    否则 → 弹出文件选择对话框

加密文件的后缀 .vef,放在原文将后缀之后。命名规则为:原文件名.vef(如 video.mp4 → video.mp4.vef)

2)解密视频

菜单栏点击 安全 → 解密视频

文件过滤:仅显示 .vef 文件

解密文件,出现保存对话框,默认放置在原位置,默认用文件名(可改),若存放处有同名文件,提示是否替换。

命名规则:自动去除 .vef 后缀 → video.mp4

解密时,若输入的密码和加密时不一致,提示:密码错误或文件损坏 → 立即终止并提示

源码如下:

import sys
import time
import hashlib
import secrets
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import vlc
import os
from PyQt6 import QtWidgets, QtCore, QtGui

class VLCPlayer(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self.instance = vlc.Instance()
        self.player = self.instance.media_player_new()
        self.timer = QtCore.QTimer(self)
        self.timer.timeout.connect(self.update_ui) # 连接定时器到更新方法
        self.current_file = None
        self.media_loaded = False
        self.temp_files = set() #用于跟踪临时解密文件
        self.init_ui()
        self.init_crypto()

    def init_ui(self):
        # 主窗口设置
        self.setWindowTitle("视频播控器(特别专用版)")
        self.resize(800, 600)

        # 初始化菜单
        menubar = self.menuBar()
        file_menu = menubar.addMenu("文件(&F)")
        crypto_menu = menubar.addMenu("安全(&S)")

        # 文件操作
        open_action = self.create_action("打开文件...", "Ctrl+O", self.open_file)
        # exit_action = self.create_action("退出", "Ctrl+Q", lambda: self.close()) # 播放退出太慢

        # 加密解密操作
        encrypt_action = self.create_action("加密视频...", "Ctrl+E", self.encrypt_video)
        decrypt_action = self.create_action("解密视频...", "Ctrl+D", self.decrypt_video)

        # 添加菜单项
        file_menu.addAction(open_action)
        file_menu.addSeparator()
        # file_menu.addAction(exit_action)
        crypto_menu.addAction(encrypt_action)
        crypto_menu.addAction(decrypt_action)

        # 创建主容器和布局
        central_widget = QtWidgets.QWidget(self)
        self.setCentralWidget(central_widget)
        main_layout = QtWidgets.QVBoxLayout(central_widget)
        main_layout.setContentsMargins(0, 0, 0, 0)

        # 视频标题标签 
        self.title_label = QtWidgets.QLabel("当前未选择媒体文件")
        self.title_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
        self.title_label.setStyleSheet("font-size: 14px; color: #666; margin: 5px;")
        main_layout.addWidget(self.title_label)
        
        # 视频显示区域
        self.video_widget = QtWidgets.QWidget()
        self.video_widget.setStyleSheet("background-color: black;")
        main_layout.addWidget(self.video_widget, stretch=1)

        # 控制面板
        control_panel = QtWidgets.QWidget()
        control_layout = QtWidgets.QVBoxLayout(control_panel)

        # 进度条
        self.progress_bar = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal)
        self.progress_bar.setMinimum(0)
        self.progress_bar.sliderMoved.connect(self.set_position)
        control_layout.addWidget(self.progress_bar)

        # 时间标签
        self.time_label = QtWidgets.QLabel("00:00:00 / 00:00:00")
        control_layout.addWidget(self.time_label)

        # 控制按钮
        button_layout = QtWidgets.QHBoxLayout()
        self.play_btn = QtWidgets.QPushButton("播放")
        self.play_btn.clicked.connect(self.toggle_play)
        self.stop_btn = QtWidgets.QPushButton("停止")
        self.stop_btn.clicked.connect(self.stop)
        self.open_btn = QtWidgets.QPushButton("打开文件")
        self.open_btn.clicked.connect(self.open_file)
        
        # 音量控制
        self.volume_slider = QtWidgets.QSlider(QtCore.Qt.Orientation.Horizontal)
        self.volume_slider.setRange(0, 100)
        self.volume_slider.setValue(100)
        self.volume_slider.valueChanged.connect(self.set_volume)

        # 添加控件
        button_layout.addWidget(self.open_btn)
        button_layout.addWidget(self.play_btn)
        button_layout.addWidget(self.stop_btn)
        button_layout.addWidget(QtWidgets.QLabel("音量:"))
        button_layout.addWidget(self.volume_slider)
        control_layout.addLayout(button_layout)

        main_layout.addWidget(control_panel)

        # 设置VLC渲染
        if sys.platform == "win32":
            self.player.set_hwnd(int(self.video_widget.winId()))
        elif sys.platform == "linux":
            self.player.set_xwindow(self.video_widget.winId())
        elif sys.platform == "darwin":
            from PyQt6.QtGui import QCocoaNativeContext
            self.player.set_nsobject(int(QCocoaNativeContext(self.video_widget.winId()).nsview()))

        self.timer.start(200)

    def init_crypto(self):
        """初始化加密参数"""
        self.key_derivation_iterations = 100000
        self.salt_size = 16
        self.nonce_size = 16
        self.tag_size = 16
        self.chunk_size = 64 * 1024  # 64KB块处理

    def create_action(self, text, shortcut, callback):
        """创建标准化菜单动作"""
        action = QtGui.QAction(text, self)
        action.setShortcut(shortcut)
        action.triggered.connect(callback)
        return action        

    def open_file(self):
        # 修复1:打开文件前先停止播放
        if self.player.is_playing():
            self.player.stop()
            self.media_loaded = False
            self.play_btn.setText("播放")

        file_dialog = QtWidgets.QFileDialog(self)
        file_dialog.setNameFilter("视频文件 (*.mp4 *.avi *.mkv *.mov *.flv)")
        if file_dialog.exec():
            selected_files = file_dialog.selectedFiles()
            if selected_files:
                self.load_media(selected_files[0])

    def load_media(self, file_path):
        try:
            # 如果是加密文件需要特殊处理
            if file_path.lower().endswith('.vef'):
                QtWidgets.QMessageBox.warning(
                    self, "警告", 
                    "请使用菜单中的解密功能打开加密文件"
                )
                return
            
            # 新增:停止定时器避免冲突
            self.timer.stop()
            
            # 修复2:确保彻底释放旧媒体资源
            self.player.stop()
            self.player.set_media(None)  # 清除旧媒体引用

            # 重置状态
            self.media_loaded = False
            self.play_btn.setText("播放")
            self.progress_bar.setValue(0)
            self.time_label.setText("00:00:00 / 00:00:00")

            # 加载新文件
            self.current_file = file_path
            media = self.instance.media_new(file_path)
            self.player.set_media(media)
            
            # 更新标题
            file_name = os.path.basename(file_path)
            self.title_label.setText(f"当前播放: {file_name}")

            # 修复3:异步解析媒体信息(避免阻塞UI)
            media.parse_with_options(vlc.MediaParseFlag.network, 1000)
            
            # 设置进度条最大值
            self.progress_bar.setMaximum(media.get_duration())
            self.media_loaded = True
            
            # 显示总时长
            total_time = time.strftime("%H:%M:%S", time.gmtime(media.get_duration() // 1000))
            self.time_label.setText(f"00:00:00 / {total_time}")

            # 新增:确保媒体加载完成后再启定时器
            self.timer.start(200)

        except Exception as e:
            QtWidgets.QMessageBox.critical(self, "错误", f"无法加载文件:\n{str(e)}")
            self.media_loaded = False
            self.title_label.setText("媒体加载失败")

    def toggle_play(self):
        if not self.media_loaded:
            self.open_file()
            return
            
        # 修复4:强制同步按钮状态
        if self.player.is_playing():
            self.player.pause()
            self.play_btn.setText("播放")
        else:
            self.player.play()
            self.play_btn.setText("暂停")

    # ---------- 加密解密功能 ----------
    def _get_encrypted_filename(self, src_path):
        """生成加密文件名(原文件名+.vef)"""
        return src_path + ".vef"  # 直接在原文件名后追加.vef

    def encrypt_video(self):
        """智能加密方法:优先处理当前播放文件"""
        # 自动检测当前播放文件
        src_path = None
        if self.current_file and os.path.exists(self.current_file):
            # 检查是否已经是加密文件
            if not self.current_file.lower().endswith('.vef'):
                reply = QtWidgets.QMessageBox.question(
                    self, '加密确认', 
                    f"是否加密当前播放的文件?\n{os.path.basename(self.current_file)}",
                    QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No
                )
                if reply == QtWidgets.QMessageBox.StandardButton.Yes:
                    src_path = self.current_file

        # 如果无当前可用文件,则选择文件
        if not src_path:
            src_path, _ = QtWidgets.QFileDialog.getOpenFileName(
                self, "选择要加密的视频文件", "",
                "视频文件 (*.mp4 *.avi *.mkv *.mov *.flv)"
            )
            if not src_path:
                return

        # 处理加密文件特殊情况
        if src_path.lower().endswith('.vef'):
            QtWidgets.QMessageBox.warning(self, "警告", "不能加密已加密文件")
            return

        # 获取保存路径
        default_name = self._get_encrypted_filename(os.path.basename(src_path))
        dest_path, _ = QtWidgets.QFileDialog.getSaveFileName(
            self, "保存加密文件", 
            os.path.join(os.path.dirname(src_path), default_name),  # 默认原目录
            "加密视频 (*.vef)"
        )
        
        if not dest_path:
            return

        # 获取密码
        password, ok = QtWidgets.QInputDialog.getText(
            self, "输入密码", "设置加密密码:", 
            QtWidgets.QLineEdit.EchoMode.Password
        )
        if not ok or not password:
            return

        # 执行加密流程
        try:
            # 如果是当前播放文件,停止播放
            was_playing = False
            if src_path == self.current_file:
                was_playing = self.player.is_playing()
                self.player.stop()
                self.media_loaded = False

            self._encrypt_file(src_path, dest_path, password)
            
            # 成功提示
            msg = f"加密成功!\n原文件: {os.path.basename(src_path)}\n加密文件: {os.path.basename(dest_path)}"
            QtWidgets.QMessageBox.information(self, "完成", msg)

            # 如果加密的是当前文件,询问是否加载加密文件
            if src_path == self.current_file:
                choice = QtWidgets.QMessageBox.question(
                    self, "加载文件", "是否立即加载加密后的文件?",
                    QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No
                )
                if choice == QtWidgets.QMessageBox.StandardButton.Yes:
                    self.load_media(dest_path)
        except Exception as e:
            self.show_error(f"加密失败: {str(e)}")
        finally:
            # 恢复原始状态(如果需要)
            if was_playing and src_path != dest_path:
                self.load_media(src_path)

    def _encrypt_file(self, src_path, dest_path, password):
        """执行文件加密(新增)"""
        try:
            # 生成加密参数
            salt = secrets.token_bytes(self.salt_size)
            key = hashlib.pbkdf2_hmac(
                'sha256',
                password.encode('utf-8'),
                salt,
                self.key_derivation_iterations,
                dklen=32
            )
            cipher = AES.new(key, AES.MODE_GCM)
            cipher.update(salt)

            # 分块加密
            with open(src_path, 'rb') as fin, open(dest_path, 'wb') as fout:
                # 写入加密头
                fout.write(salt)
                fout.write(cipher.nonce)

                while True:
                    chunk = fin.read(self.chunk_size)
                    if not chunk:
                        break
                    encrypted = cipher.encrypt(pad(chunk, AES.block_size))
                    fout.write(encrypted)

                # 写入认证标签
                fout.write(cipher.digest())

        except PermissionError:
            raise RuntimeError("文件被其他程序占用,请关闭后重试")
        except Exception as e:
            raise RuntimeError(f"加密失败: {str(e)}")

    def _get_decrypted_filename(self, src_path):
        """生成解密文件名(去除.vef后缀)"""
        if src_path.lower().endswith('.vef'):
            return src_path[:-4]  # 去除.vef
        return src_path + "_decrypted"

    def decrypt_video(self):
        """增强型解密方法"""
        try:
            # 选择加密文件
            src_file, _ = QtWidgets.QFileDialog.getOpenFileName(
                self, "选择要解密的文件", "", 
                "加密视频 (*.vef)"
            )
            if not src_file:
                return

            # 生成默认保存路径
            default_path = self._get_decrypted_filename(src_file)
            dest_file, _ = QtWidgets.QFileDialog.getSaveFileName(
                self, "保存解密文件",
                default_path,  # 默认原目录+去后缀
                "视频文件 (*.*)"
            )
            if not dest_file:
                return

            # 检查文件是否已存在
            if os.path.exists(dest_file):
                reply = QtWidgets.QMessageBox.question(
                    self, "文件存在",
                    f"目标文件已存在,是否覆盖?\n{dest_file}",
                    QtWidgets.QMessageBox.StandardButton.Yes | QtWidgets.QMessageBox.StandardButton.No
                )
                if reply != QtWidgets.QMessageBox.StandardButton.Yes:
                    return

            # 获取密码
            password, ok = QtWidgets.QInputDialog.getText(
                self, "输入密码", "解密密码:", 
                QtWidgets.QLineEdit.EchoMode.Password
            )
            if not ok or not password:
                return

            # 执行解密
            decrypted_path = self._decrypt_file(src_file, password, dest_file)
            
            if decrypted_path:
                QtWidgets.QMessageBox.information(
                    self, "成功", 
                    f"文件解密成功!\n保存路径: {decrypted_path}"
                )
                self.load_media(decrypted_path)
                
        except Exception as e:
            self.show_error(f"解密失败: {str(e)}")

    def _decrypt_file(self, src_path, password, dest_path):
        """增强型解密方法(保存到指定路径)"""
        try:
            with open(src_path, 'rb') as fin:
                salt = fin.read(self.salt_size)
                nonce = fin.read(self.nonce_size)
                key = self._derive_key(password, salt)
                cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
                cipher.update(salt)

                with open(dest_path, 'wb') as fout:
                    encrypted = fin.read()
                    ciphertext, tag = encrypted[:-self.tag_size], encrypted[-self.tag_size:]
                    
                    # 分块解密写入
                    chunk_size = self.chunk_size + AES.block_size
                    for i in range(0, len(ciphertext), chunk_size):
                        chunk = ciphertext[i:i+chunk_size]
                        decrypted = unpad(cipher.decrypt(chunk), AES.block_size)
                        fout.write(decrypted)
                    
                    # 验证标签
                    cipher.verify(tag)
                
                return dest_path
                
        except ValueError as ve:
            # 清理已写入的部分文件
            if os.path.exists(dest_path):
                try:
                    os.remove(dest_path)
                except:
                    pass
            raise ValueError("解密失败 - 密码错误或文件损坏") from ve
        except Exception as e:
            if os.path.exists(dest_path):
                try:
                    os.remove(dest_path)
                except:
                    pass
            raise RuntimeError(f"解密过程错误: {str(e)}") from e


    def _derive_key(self, password, salt):
        """生成加密密钥"""
        return hashlib.pbkdf2_hmac(
            'sha256',
            password.encode('utf-8'),
            salt,
            self.key_derivation_iterations,
            dklen=32  # AES-256需要32字节密钥
        )

    # ---------- 辅助功能 ----------
    def show_error(self, message):
        """显示错误提示"""
        QtWidgets.QMessageBox.critical(self, "错误", message)

    def closeEvent(self, event):
        """关闭处理,清理临时文件"""
        for temp_file in self.temp_files:
            try:
                if os.path.exists(temp_file):
                    os.remove(temp_file)
            except Exception as e:
                print(f"删除临时文件失败: {str(e)}")
        self.player.stop() # 停止播放器
        event.accept()  # 
        
    def stop(self):
        self.player.stop()
        self.progress_bar.setValue(0)
        self.time_label.setText("00:00:00 / 00:00:00")
        self.play_btn.setText("播放")
        self.title_label.setText("播放已停止")

    def set_volume(self, value):
        self.player.audio_set_volume(value)

    def set_position(self, value):
        if self.player.is_seekable():
            self.player.set_position(value / self.progress_bar.maximum())

    def update_ui(self):
        if not self.media_loaded:
            return  # 新增:防止在无媒体时更新

        media_length = self.player.get_length()
        if media_length > 0:
            current_time = self.player.get_time()
            
            # 新增:检测播放结束
            if current_time >= media_length - 500:  # 留50ms容差
                self.stop()
                return

            self.progress_bar.setMaximum(media_length)
            self.progress_bar.setValue(current_time)

            total_time = time.strftime("%H:%M:%S", time.gmtime(media_length // 1000))
            current_time_str = time.strftime("%H:%M:%S", time.gmtime(current_time // 1000))
            self.time_label.setText(f"{current_time_str} / {total_time}")


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    player = VLCPlayer()
    player.show()
    sys.exit(app.exec())

http://www.dtcms.com/a/109583.html

相关文章:

  • 第二十六章:Seaborn库实现统计数据可视化
  • 2025年机动车授权签字人考试判断题分享
  • 2025年渗透测试面试题总结- 某汽车厂商-安全工程师扩展(题目+回答)
  • 量子计算与经典计算的融合与未来
  • AI赋能——让人工智能助力工作提质增效
  • CVPR2024 | 构建时序动作检测模型对时序干扰的鲁棒性基准
  • 近日八股——计算机网络
  • 使用pycharm社区版调试DIFY后端python代码
  • 破解 N 皇后 II:位运算的高效艺术
  • 4月3日工作日志
  • CVSS-通用漏洞评分系统版本 4.0:规范文档
  • 代码随想录|动态规划|18完全背包理论基础
  • Java Lambda 表达式提升效率
  • 高效深度学习lecture01
  • Flask与 FastAPI 对比:哪个更适合你的 Web 开发?
  • MySQL(三)
  • 二分类交叉熵损失
  • 在内网环境中为 Gogs 配置 HTTPS 访问
  • 常用的元素操作API
  • chromium魔改——navigator.webdriver 检测
  • 【无人机】无人机PX4飞控系统高级软件架构
  • 创新项目实训开发日志1
  • 21.数据链路层协议
  • 如何在本地部署魔搭上千问Qwen2.5-VL-32B-Instruct-AWQ模型在显卡1上面运行推理,并开启api服务
  • QT 中的元对象系统(五):QMetaObject::invokeMethod的使用和实现原理
  • JavaScript基础-移动端常用开发框架
  • 智能多媒体处理流水线——基于虎跃办公API的自动化解决方案
  • Redis 除了数据类型外的核心功能 的详细说明,包含事务、流水线、发布/订阅、Lua 脚本的完整代码示例和表格总结
  • 【数据集】多视图文本数据集
  • Python第七章09:自定义python包.py