本文主要是介绍Python+PyQt5实现多屏幕协同播放功能,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
《Python+PyQt5实现多屏幕协同播放功能》在现代会议展示、数字广告、展览展示等场景中,多屏幕协同播放已成为刚需,下面我们就来看看如何利用Python和PyQt5开发一套功能强大的跨屏播控系统吧...
一、项目概述:突破传统播放限制
在现代会议展示、数字广告、展览展示等场景中,多屏幕协同播放已成为刚需。传统播放软件往往存在扩展屏支持不足、操作复杂、功能单一等问题。本项目基于python生态的PyQt5和VLC库,开发了一套功能强大的跨屏播控系统,实现了以下核心突破:
- 多屏融合控制:支持主屏操作+扩展屏播放的双屏模式
- 智能媒体识别:自动区分视频/图片格式并适配最佳播放方案
- 专业级过渡效果:内置淡入淡出等专业转场动画
- 低代码高扩展:采用面向对象设计,模块化程度高
系统架构图如下:
[主控制界面] ←PyQt5→ [VLC引擎] → {主屏预览/扩展屏输出}
二、核心技术解析
2.1 多屏管理机制
def init_screens(self): """创新性的多屏检测方案""" try: self.screens = screeninfo.get_monitors() if len(self.screens) > 1: self.ext_screen = self.screens[1] self._create_video_window() self._hide_taskbar() # 自动隐藏扩展屏任务栏 except Exception as e: self._create_fallback_window() # 优雅降级处理
关键技术点:
- 使用screeninfo库动态获取显示器配置
- HWND窗口绑定实现精确到像素的跨屏控制
- 异常情况下的单屏兼容模式
2.2 播放引擎设计
系统采用双VLC实例架构:
主播放器:带音频输出的完整渲染
预览播放器:静音状态的实时同步
self.instance = vlc.Instance("--aout=directsound") self.main_player = self.instance.media_player_new() self.preview_player = self.instance.media_player_new() self.preview_player.audio_set_mute(True) # 预览静音
2.3 专业级转场动画
通过Qt动画框架实现广播级效果:
def start_fade_in_animation(self): """音量淡入曲线动画""" self.fade_animation = QPropertyAnimation(self, b"volume") self.fade_animation.setEasingCurve(QEasingCurve.InOutCirc) self.fade_animation.start()
三、功能使用详解
3.1 基础操作流程
1.添加媒体文件:
- 支持拖拽添加/文件对话框多选
- 自动识别视频(jpg/png等)和图片格式
2.播放模式选择:
- 连续播放:列表循环
- 单次播放:适合重要内容展示
3.多屏输出切换:
- 扩展模式:主控+扩展屏输出
- 主屏模式:仅主界面播放
- 双屏模式:镜像输出
3.2 高级功能
定时截图预览:
def update_preview(self): if self.main_player.video_take_snapshot(0, temp_file, 0, 0) == 0: # 异步处理截图文件 QTimer.singleShot(100, self._process_snapshot)
智能记忆播放:
- 记录上次退出时的播放位置
- 异常中断后自动恢复现场
四、性能优化方案
4.1 资源管理
采用懒加载策略初始化VLC实例
动态释放已完成播放的媒体资源
4.2 线程安全
pythoncom.CoInitialize() # COM组件初始化 try: # VLC多线程操作 finally: pythoncom.CoUninitialize()
4.3 渲染优化
视频:硬件加速解码
图片:Qt原生渲染引擎
五、扩展开发方向
1.网络推流功能:
":sout=#transcode{vcodec=h264}:rtp{dst=192.168.1.100,port=1234}"
2.定时任务模块:
- 基于cron的自动化播放计划
- 节假日特殊排期支持
- API接口扩展:
- RESTful控制接口
- WebSocket实时状态推送
六、效果展示
七、相关源码
import sys import os import json import screeninfo import win32gui import win32con import pythoncom # 修正:使用pythoncom替代win32com.client from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QvboxLayout, QHBoxLayout, QListWidget, QPushButton, QFileDialog, QLabel, QSlider, QComboBox, QGroupBox, QSizePolicy) from PyQt5.QtCore import Qt, QPropertyAnimation, QEasingCurve, QTimer, pyqtProperty from PyQt5.QtGui import QImage, QPixmap, QIcon, QColor, QLinearGradient, QPainter, QFont import vlc from vlc import State class StyledGroupBox(QGroupBox): def __init__(self, title="Python+PyQt5实现多屏幕协同播放功能", parent=None): super().__init__(title, parent) self.setStyleSheet(""" QGroupBox { border: 2px solid #2a82da; border-radius: 8px; margin-top: 10px; padding-top: 15px; background-color: rgba(20, 30, 50, 180); color: #ffffff; font-weight: bold; } QGroupBox::title { subcontrol-origin: margin; left: 10px; padding: 0 5px; } """) class StyledButton(QPushButton): def __init__(self, text="", parent=None): super().__init__(text, parent) self.setStyleSheet(""" QPushButton { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #3a7bd5, stop:1 #00d2ff); border: 1px solid #2a82da; border-radius: 5px; color: white; padding: 5px; font-weight: bold; min-width: 80px; } QPushButton:hover { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #4a8be5, stop:1 #10e2ff); border: 1px solid #3a92ea; } QPushButton:pressed { background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #2a6bc5, stop:1 #00c2ef); padding-top: 6px; padding-bottom: 4px; } """) class StyledListWidget(QListWidget): def __init__(self, parent=None): super().__init__(parent) self.setStyleSheet(""" QListWidget { background-color: rgba(30, 40, 60, 200); border: 1px solid #2a82da; border-radius: 5px; color: #ffffff; font-size: 12px; padding: 5px; } QListWidget::item { border-bottom: 1px solid rgba(42, 130, 218, 50); padding: 5px; } QListWidget::item:selected { background-color: rgba(42, 130, 218, 150); color: white; } QScrollBar:vertical { border: none; background: rgba(30, 40, 60, 200); width: 10px; margin: 0px; } QScrollBar::handle:vertical { background: #2a82da; min-height: 20px; border-radius: 4px; } QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; } """) class StyledSlider(QSlider): def __init__(self, orientation=Qphpt.Horizontal, parent=None): super().__init__(orientation, parent) if orientation == Qt.Horizontal: self.setStyleSheet(""" QSlider::groove:horizontal { height: 6px; background: rgba(30, 40, 60, 200); border-radius: 3px; } QSlider::sub-page:horizontal { background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #3a7bd5, stop:1 #00d2ff); border-radius: 3px; } QSlider::add-page:horizontal { background: rgba(42, 130, 218, 50); border-radius: 3px; } QSlider::handle:horizontal { width: 14px; margin: -4px 0; background: qradialgradient(cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0 #ffffff, stop:1 #2a82da); border-radius: 7px; } """) else: self.setStyleSheet(""" QSlider::groove:vertical { width: 6px; background: rgba(30, 40, 60, 200); border-radius: 3px; } QSlider::sub-page:vertical { background: qlineargradient(x1:0, y1:1, x2:0, y2:0, stop:0 #3a7bd5, stop:1 #00d2ff); border-radius: 3px; } QSlider::add-page:vertical { background: rgba(42, 130, 218, 50); border-radius: 3px; } QSlider::handle:vertical { height: 14px; margin: 0 -4px; background: qradialgradient(cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0 #ffffff, stop:1 #2a82da); border-radius: 7px; } """) class StyledComboBox(QComboBox): def __init__(self, parent=None): super().__init__(parent) self.setStyleSheet(""" QComboBox { background-color: rgba(30, 40, 60, 200); border: 1px solid #2a82da; border-radius: 5px; color: white; padding: 5px; padding-left: 10px; min-width: 100px; } QComboBox:hover { border: 1px solid #3a92ea; } QComboBox::drop-down { subcontrol-origin: padding; subcontrol-position: top right; width: 20px; border-left: 1px solid #2a82da; border-top-right-radius: 5px; border-bottom-right-radius: 5px; } QComboBox::down-arrow { image: url(none); width: 10px; height: 10px; } QComboBox QAbstractItemView { background-color: rgba(30, 40, 60, 200); border: 1px solid #2a82da; selection-background-color: rgba(42, 130, 218, 150); color: white; } """) class ExtendedScreenPlayer(QMainWindow): def __init__(self): super().__init__() pythoncom.CoInitialize() # 修正:使用pythoncom进行COM初始化 # 初始化变量 self.playlist = [] self.current_index = -1 self.instance = vlc.Instance("--aout=directsound") self.main_player = self.instance.media_player_new("--aout=directsound") self.preview_player = self.instance.media_player_new("--aout=directsound") self.mode = "扩展模式" self.screen_modes = ["扩展模式", "主屏模式", "双屏模式"] self.play_mode = True self.current_volume = 100 self._volume = 100 # 初始化UI self.setup_ui_style() self.init_ui() self.init_screens() # 初始化定时器 self.media_timer = QTimer(self) self.media_timer.timeout.connect(self.update_media_status) self.media_timer.start(200) # 初始化动画相关 self.fade_timer = QTimer(self) self.fade_timer.timeout.connect(self.fade_process) self.fade_duration = 8000 self.fade_steps = 30 self.fade_step_interval = self.fade_duration // self.fade_steps self.fade_animation = None self.fading_out = False self.fading_in = False # 显示初始界面 self.show() self.show_home_screen() self.setAcceptDrops(True) self.playback_paused = False # 新增暂停状态标记 self.current_media_position = 0 # 记录当前播放位置 def setup_ui_style(self): """设置全局UI样式""" self.setStyleSheet(""" QMainWindow { background-color: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #0f2027, stop:1 #2c5364); color: #ffffff; } QLabel { color: #ffffff; font-size: 12px; } QLabel#status_label { font-size: 14px; font-weight: bold; padding: 5px; background-color: rgba(20, 30, 50, 180); border-radius: 5px; border: 1px solid #2a82da; } """) # 设置全局字体 font = QFont() font.setFamily("Arial") font.setPointSize(10) QApplication.setFont(font) def init_ui(self): """初始化用户界面""" self.setWindowTitle('大屏播控系统') self.setWindowIcon(QIcon('icon.png')) if os.path.exists('icon.png') else None self.setGeometry(100, 100, 1200, 800) # 主布局 main_widget = QWidget() self.setCentralWidget(main_widget) main_layout = QHBoxLayout() main_widget.setLayout(main_layout) # 左侧控制面板 control_panel = StyledGroupBox("控制面板") control_layout = QVBoxLayout() control_panel.setLayout(control_layout) control_panel.setFixedwidth(450) # 播放列表 self.playlist_widget = StyledListWidget() self.playlist_widget.itemDoubleClicked.connect(self.play_selected_item) control_layout.addWidget(QLabel("播放列表:")) control_layout.addWidget(self.playlist_widget) # 播放控制按钮 btn_layout = QHBoxLayout() controls = [ ('', self.show_home_screen, '返回首页画面'), ('⏮', self.prev_item, '播放上一项'), ('⏯', self.toggle_play, '播放/暂停'), ('⏹', self.stop, '停止播放'), ('⏭', self.next_item, '播放下一项') ] for text, callback, tip in controls: btn = StyledButton(text) btn.clicked.connect(callback) btn.setFixedSize(70, 50) btn.setToolTip(tip) btn.setStyleSheet(""" QPushButton { font-size: 20px; min-width: 30px; } """) btn_layout.addWidget(btn) control_layout.addLayout(btn_layout) # 进度条 self.position_slider = StyledSlider(Qt.Horizontal) self.position_slider.setRange(0, 1000) self.position_slider.sliderMoved.connect(self.set_position) control_layout.addWidget(self.position_slider) # 音量控制 volume_layout = QHBoxLayout() volume_layout.addWidget(QLabel("音量:")) self.volume_slider = StyledSlider(Qt.Horizontal) self.volume_slider.setRange(0, 100) self.volume_slider.setValue(100) self.volume_slider.valueChanged.connect(self.set_volume) volume_layout.addWidget(self.volume_slider) control_layout.addLayout(volume_layout) # 文件操作按钮 file_btn_layout = QHBoxLayout() file_controls = [ ('添加文件', self.add_files), ('删除选中', self.remove_selected), ('清空列表', self.clear_playlist) ] for text, callback in file_controls: btn = StyledButton(text) btn.clicked.connect(callback) file_btn_layout.addWidget(btn) control_layout.addLayout(file_btn_layout) # 播放模式选择 self.mode_combo = StyledComboBox() self.mode_combo.addItems(self.screen_modes) self.mode_combo.currentTextChanged.connect(self.change_mode) control_layout.addWidget(QLabel("播放模式:")) control_layout.addWidget(self.mode_combo) # 播放模式切换按钮 self.play_mode_btn = StyledButton('连续播放') self.play_mode_btn.clicked.connect(self.toggle_play_mode) control_layout.addWidget(self.play_mode_btn) # 列表管理按钮 list_btn_layout = QHBoxLayout() list_controls = [ ('保存列表', self.save_playlist), ('加载列表', self.load_playlist) ] for text, callback in list_controls: btn = StyledButton(text) btn.clicked.connect(callback) list_btn_layout.addWidget(btn) control_layout.addLayout(list_btn_layout) # 右侧预览区域 preview_panel = StyledGroupBox("预览") preview_layout = QVBoxLayout() preview_panel.setLayout(preview_layout) # 视频预览窗口 self.preview_window = QLabel() self.preview_window.setAlignment(Qt.AlignCenter) self.preview_window.setStyleSheet(""" QLabel { background-color: black; border: 2px solid #2a82da; border-radius: 5px; } """) self.preview_window.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.Ignored) preview_layout.addWidget(self.preview_window) # 状态栏 self.status_bar = QLabel('大屏准备就绪') self.status_bar.setObjectName("status_label") preview_layout.addWidget(self.status_bar) # 主布局添加组件 main_layout.addWidget(control_panel) main_layout.addWidget(preview_panel) def init_screens(self): """初始化屏幕配置""" try: self.screens = screeninfo.get_monitors() if len(self.screens) > 1: self.ext_screen = self.screens[1] self._create_video_window() self._hide_taskbar() else: self.status_bar.setText('警告:未检测到扩展屏幕,将使用主屏幕播放!') self._create_fallback_window() except Exception as e: self.status_bar.setText(f'屏幕检测失败: {str(e)}') self._create_fallback_window() def _create_video_window(self): """创建扩展屏播放窗口""" self.video_window = QWidget() self.video_window.setWindowTitle('扩展屏幕播放器') self.video_window.setGeometry( self.ext_screen.x, self.ext_screen.y, self.ext_screen.width, self.ext_screen.height ) self.video_window.setWindowFlags(Qt.FramelessWindowHint | Qt.Tool) self.video_window.setStyleSheet("background-color: black;") self.video_window.showFullScreen() def _create_fallback_window(self): """创建集成到主界面右侧的监看窗口""" self.video_window = self.preview_window self.preview_player.set_hwnd(0) def change_mode(self, mode): """切换播放模式""" self.mode = mode if mode == "扩展模式" and hasattr(self, 'ext_screen'): self._create_video_window() else: if hasattr(self, 'video_window') and self.video_window != self.preview_window: self.video_window.close() self.video_window = self.preview_window def toggle_play_mode(self): """切换播放模式""" self.play_mode = not self.play_mode self.play_mode_btn.setText('连续播放' if self.play_mode else '单个播放') def play_selected_item(self, item): """处理双击播放列表项事件""" row = self.playlist_widget.row(item) self.play_item(row) def play_item(self, index): """播放指定索引的媒体""" if 0 <= index < len(self.playlist): self.current_index = index file_path = self.playlist[index] is_image = file_path.lower().endswith(('.jpg', '.jpeg', '.png')) if not self.play_mode and is_image: self._setup_single_image_playback() if hasattr(self, 'video_window') and self.video_window != self.preview_window: for child in self.video_window.findChildren(QLabel): child.deleteLater() try: pixmap = QPixmap(file_path) if pixmap.isNull(): raise ValueError("图片加载失败") # 在主预览窗口显示 scaled_pixmap = pixmap.scaled( QSize(800, 600), Qt.KeepASPectRatio, Qt.SmoothTransformation ) self.preview_window.setPixmap(scaled_pixmap) # 扩展屏显示逻辑 if hasattr(self, 'video_window') and self.video_window != self.preview_window: ext_label = QLabel(self.video_window) ext_pixmap = pixmap.scaled( QSize(800, 600), Qt.KeepAspectRatio, Qt.SmoothTransformation ) ext_label.setPixmap(ext_pixmap) ext_label.setAlignment(Qt.AlignCenter) ext_label.show() self.status_bar.setText(f'正在显示: {os.path.basename(file_path)}') return except Exception as e: self.status_bar.setText(f'错误: {str(e)}') self.show_home_screen() return else: # 如果是单个播放模式且不是图片,先显示首页 if not self.play_mode and not is_image: self.show_home_screen() if not self.play_mode: self.main_player.event_manager().event_attach( vlc.EventType.MediaPlayerEndReached, self._on_single_play_end ) media = self.instance.media_new(self.playlist[index]) # 主播放器设置 self.main_player.stop() self.main_player.set_media(media) # 预览播放器设置(静音且独立) self.preview_player.stop() self.previephpw_player.set_media(media) self.preview_player.audio_set_mute(True) # 窗口绑定 self.main_player.set_hwnd(0) self.preview_player.set_hwnd(0) if self.video_window and self.video_window != self.preview_window: # 双屏模式:主输出到扩展屏,预览输出到主界面 self.main_player.set_hwnd(self.video_window.winId()) self.preview_player.set_hwnd(self.preview_window.winId()) elhttp://www.chinasem.cnse: # 单屏模式:主播放器输出到预览窗口 self.main_player.set_hwnd(self.preview_window.winId()) self.preview_player.set_hwnd(0) # 同步启动播放 self.main_player.play() if self.video_window != self.preview_window: self.preview_player.play() self.fading_in = True self.fade_timer.start(self.fade_step_interval) self.start_fade_in_animation() # 更新状态和列表选择 self.status_bar.setText(f'正在播放: {os.path.basename(self.playlist[index])}') self.playlist_widget.setCurrentRow(index) def fade_process(self): """处理音量渐变过程""" if self.fading_in: progress = self.fade_timer.remainingTime() / self.fade_duration new_volume = int(100 * (1 - progress) ** 3) self.set_volume(new_volume) if progress <= 0: self.fading_in = False self.fade_timer.stop() elif self.fading_out: progress = self.fade_timer.remainingTime() / self.fade_duration new_volume = int(100 * progress ** 3) self.set_volume(new_volume) if progress <= 0: self.fading_out = False self.fade_timer.stop() QTimer.singleShot(200, lambda: [self.main_player.stop(), self.preview_player.stop()]) def start_fade_in_animation(self): """启动淡入动画""" self.fade_animation = QPropertyAnimation(self, b"volume") self.fade_animation.setDuration(self.fade_duration) self.fade_animation.setStartValue(0) self.fade_animation.setEndValue(100) self.fade_animation.setEasingCurve(QEasingCurve.InOutCirc) self.fade_animation.start() def update_preview(self): """更新预览画面""" if hasattr(self, 'video_window') and self.video_window != self.preview_window: if self.main_player.is_playing(): try: if self.main_player.video_get_size()[0] > 0: temp_file = f"preview_{id(self)}.jpg" if self.main_player.video_take_snapshot(0, temp_file, 0, 0) == 0: retry = 3 while retry > 0 and not os.path.exists(temp_file): QApplication.processEvents() retry -= 1 if os.path.exists(temp_file): pixmap = QPixmap(temp_file) if not pixmap.isNull(): target_size = QSize(800, 600) scaled_pixmap = pixmap.scaled( target_size, Qt.KeepAspectRatio, Qt.SmoothTransformation ) self.preview_window.setPixmap(scaled_pixmap) os.remove(temp_file) except Exception as e: print(f"预览更新失败: {str(e)}") else: grad = QLinearGradient(0, 0, self.preview_window.width(), 0) grad.setColorAt(0, QColor(42, 130, 218)) grad.setColorAt(1, QColor(0, 210, 255)) placeholder = QPixmap(self.preview_window.size()) placeholder.fill(Qt.transparent) painter = QPainter(placeholder) painter.setPen(Qt.NoPen) painter.setBrush(grad) painter.drawRoundedRect(placeholder.rect(), 10, 10) painter.setFont(QFont("微软雅黑", 14)) painter.drawText(placeholder.rect(), Qt.AlignCenter, "主画面播放中") painter.end() self.preview_window.setPixmap(placeholder) QTimer.singleShot(500, self.update_preview) def set_position(self, position): if self.main_player.is_playing(): self.current_media_position = position / 1000.0 self.main_player.set_position(self.current_media_position) def _ensure_media_loaded(self): if not self.main_player.get_media(): media = self.instance.media_new(self.playlist[self.current_index]) self.main_player.set_media(media) self.preview_player.set_media(media) def update_media_status(self): """更新媒体状态""" if self.main_player.is_playing(): position = self.main_player.get_position() * 1000 self.position_slider.setValue(int(position)) if abs(self.preview_player.get_position() - self.main_player.get_position()) > 0.01: self.preview_player.set_position(self.main_player.get_position()) if self.mode != "扩展模式" or not hasattr(self, 'ext_screen'): self.update_preview() else: if self.main_player.get_state() == vlc.State.Ended and self.playlist: if self.play_mode: self.next_item() else: self.stop() self.show_home_screen() def toggle_play(self): if self.main_player.is_playing(): self.main_player.pause() self.playback_paused = True self.status_bar.setText('已暂停') else: if self.playlist: if self.playback_paused: # 恢复播放时保持当前位置 self.main_player.set_pause(0) self.playback_paused = False else: # 新增播放时保持位置 self._ensure_media_loaded() self.main_player.play() self.status_bar.setText('正在播放') #selected = self.playlist_widget.currentRow() #self.play_item(selected if selected != -1 else 0) def stop(self): self.main_player.stop() self.preview_player.stop() self.current_index = -1 self.show_home_screen() def show_home_screen(self): """显示首页画面""" self.main_player.stop() self.preview_player.stop() if os.path.exists('index.jpg'): pixmap = QPixmap('index.jpg') if not pixmap.isNull(): if len(self.screens) > 1: scaled_pixmap = pixmap.scaled(QSize(800, 600), Qt.KeepAspectRatio, Qt.SmoothTransformation) else: scaled_pixmap = pixmap.scaled(self.preview_window.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) self.preview_window.setPixmap(scaled_pixmap) if hasattr(self, 'video_window') and self.video_window != self.preview_window: if len(self.screens) > 1: ext_pixmap = pixmap.scaled(QSize(800, 600), Qt.KeepAspectRatio, Qt.SmoothTransformation) else: ext_pixmap = pixmap.scaled(self.video_window.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation) if hasattr(self.video_window, 'setPixmap'): self.video_window.setPixmap(ext_pixmap) else: for child in self.video_window.children(): if isinstance(child, QLabel): child.setPixmap(ext_pixmap) label = QLabel(self.video_window) label.setPixmap(pixmap.scaled( self.video_window.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation )) label.setAlignment(Qt.AlignCenter) label.show() def _hide_taskbar(self): """隐藏扩展屏任务栏""" try: def callback(hwnd, extra): class_name = win32gui.GetClassName(hwnd) rect = win32gui.GetWindowRect(hwnd) if class_name == "Shell_TrayWnd" and self.ext_screen.x <= rect[0] < self.ext_screen.x + self.ext_screen.width: win32gui.ShowWindow(hwnd, win32con.SW_HIDE) win32gui.EnumWindows(callback, None) except Exception as e: print(f"隐藏任务栏失败: {str(e)}") def closeEvent(self, event): """窗口关闭事件""" def restore_callback(hwnd, extra): if win32gui.GetClassName(hwnd) == "Shell_TrayWnd": win32gui.ShowWindow(hwnd, win32con.SW_SHOW) win32gui.EnumWindows(restore_callback, None) self.main_player.stop() if hasattr(self, 'video_window') and self.video_window != self.preview_window: self.video_window.close() event.accept() def _setup_single_image_playback(self): """配置单张图片播放""" self.main_player.stop() self.preview_player.stop() def _on_single_play_end(self, event): try: self.stop() self.show_home_screen() finally: self.main_player.event_manager().event_detach( vlc.EventType.MediaPlayerEndReached ) def prev_item(self): """播放上一项""" if self.playlist: new_index = (self.current_index - 1) % len(self.playlist) self.play_item(new_index) def next_item(self): """播放下一项""" if self.playlist: new_index = (self.current_index + 1) % len(self.playlist) self.play_item(new_index) def remove_selected(self): """删除选中项""" selected = self.playlist_widget.currentRow() if selected != -1: self.playlist.pop(selected) self.playlist_widget.takeItem(selected) if not self.playlist: self.current_index = -1 def clear_playlist(self): """清空播放列表""" self.playlist.clear() self.playlist_widget.clear() self.current_index = -1 def save_playlist(self): """保存播放列表""" file_name, _ = QFileDialog.getSaveFileName(self, "保存播放列表", os.getcwd(), "列表文件 (*.list)") if file_name: if not file_name.endswith('.list'): file_name += '.list' with open(file_name, 'w', encoding='utf-8') as f: json.dump(self.playlist, f, ensure_ascii=False) def load_playlist(self): """加载播放列表""" file_name, _ = QFileDialog.getOpenFileName(self, "加载播放列表", os.getcwd(), "列表文件 (*.list)") if file_name: try: with open(file_name, 'r', encoding='utf-8') as f: self.playlist = json.load(f) self.playlist_widget.clear() self.playlist_widget.addItems([os.path.basename(f) for f in self.playlist]) if self.playlist: self.current_inpythondex = 0 except FileNotFoundError: self.status_bar.setText('播放列表文件不存在') def get_volume(self): return self.main_player.audio_get_volume() def set_volume(self, volume): """设置音量""" self.current_volume = volume self.main_player.audio_set_volume(volume) volume = pyqtProperty(int, get_volume, set_volume) def add_files(self): files, _ = QFileDialog.getOpenFileNames( self, '选择媒体文件', '', '媒体文件 (*.mp4 *.avi *.mov *.mkv *.mp3 *.wav *.jpg *.jpeg *.png)') if files: self.playlist.extend(files) self.playlist_widget.addItems([os.path.basename(f) for f in files]) ipythonf self.current_index == -1: self.current_index = 0 if __name__ == '__main__': app = QApplication(sys.argv) player = ExtendedScreenPlayer() player.show() sys.exit(app.exec_())
八、项目总结
本系统通过创新的技术架构解决了多屏播控领域的三大痛点:
✅ 操作复杂性:直观的GUI界面降低使用门槛
✅ 功能单一性:融合播放控制、转场特效、多屏管理
✅ 稳定性不足:完善的异常处理机制
实际应用场景:
企业展厅的自动导览系统
会议中心的数字会标管理
零售门店的广告轮播系统
项目完整代码已开源,开发者可基于此进行二次开发。未来计划增加AI内容分析模块,实现智能播控。
到此这篇关于Python+PyQt5实现多屏幕协同播放功能的文章就介绍到这了,更多相关Python多屏幕协同播放内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!
这篇关于Python+PyQt5实现多屏幕协同播放功能的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!