最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Python+PyQt5實(shí)現(xiàn)多屏幕協(xié)同播放功能

 更新時(shí)間:2025年03月31日 15:40:08   作者:探客白澤  
在現(xiàn)代會(huì)議展示、數(shù)字廣告、展覽展示等場(chǎng)景中,多屏幕協(xié)同播放已成為剛需,下面我們就來看看如何利用Python和PyQt5開發(fā)一套功能強(qiáng)大的跨屏播控系統(tǒng)吧

一、項(xiàng)目概述:突破傳統(tǒng)播放限制

在現(xiàn)代會(huì)議展示、數(shù)字廣告、展覽展示等場(chǎng)景中,多屏幕協(xié)同播放已成為剛需。傳統(tǒng)播放軟件往往存在擴(kuò)展屏支持不足、操作復(fù)雜、功能單一等問題。本項(xiàng)目基于Python生態(tài)的PyQt5和VLC庫,開發(fā)了一套功能強(qiáng)大的跨屏播控系統(tǒng),實(shí)現(xiàn)了以下核心突破:

  • 多屏融合控制:支持主屏操作+擴(kuò)展屏播放的雙屏模式
  • 智能媒體識(shí)別:自動(dòng)區(qū)分視頻/圖片格式并適配最佳播放方案
  • 專業(yè)級(jí)過渡效果:內(nèi)置淡入淡出等專業(yè)轉(zhuǎn)場(chǎng)動(dòng)畫
  • 低代碼高擴(kuò)展:采用面向?qū)ο笤O(shè)計(jì),模塊化程度高

系統(tǒng)架構(gòu)圖如下:

[主控制界面] ←PyQt5→ [VLC引擎] → {主屏預(yù)覽/擴(kuò)展屏輸出}

二、核心技術(shù)解析

2.1 多屏管理機(jī)制

def init_screens(self):
    """創(chuàng)新性的多屏檢測(cè)方案"""
    try:
        self.screens = screeninfo.get_monitors()
        if len(self.screens) > 1:
            self.ext_screen = self.screens[1]
            self._create_video_window()
            self._hide_taskbar()  # 自動(dòng)隱藏?cái)U(kuò)展屏任務(wù)欄
    except Exception as e:
        self._create_fallback_window()  # 優(yōu)雅降級(jí)處理

關(guān)鍵技術(shù)點(diǎn):

  • 使用screeninfo庫動(dòng)態(tài)獲取顯示器配置
  • HWND窗口綁定實(shí)現(xiàn)精確到像素的跨屏控制
  • 異常情況下的單屏兼容模式

2.2 播放引擎設(shè)計(jì)

系統(tǒng)采用雙VLC實(shí)例架構(gòu):

主播放器:帶音頻輸出的完整渲染

預(yù)覽播放器:靜音狀態(tài)的實(shí)時(shí)同步

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)  # 預(yù)覽靜音

2.3 專業(yè)級(jí)轉(zhuǎn)場(chǎng)動(dòng)畫

通過Qt動(dòng)畫框架實(shí)現(xiàn)廣播級(jí)效果:

def start_fade_in_animation(self):
    """音量淡入曲線動(dòng)畫"""
    self.fade_animation = QPropertyAnimation(self, b"volume")
    self.fade_animation.setEasingCurve(QEasingCurve.InOutCirc)
    self.fade_animation.start()

三、功能使用詳解

3.1 基礎(chǔ)操作流程

1.添加媒體文件:

  • 支持拖拽添加/文件對(duì)話框多選
  • 自動(dòng)識(shí)別視頻(jpg/png等)和圖片格式

2.播放模式選擇:

  • 連續(xù)播放:列表循環(huán)
  • 單次播放:適合重要內(nèi)容展示

3.多屏輸出切換:

  • 擴(kuò)展模式:主控+擴(kuò)展屏輸出
  • 主屏模式:僅主界面播放
  • 雙屏模式:鏡像輸出

3.2 高級(jí)功能

定時(shí)截圖預(yù)覽:

def update_preview(self):
    if self.main_player.video_take_snapshot(0, temp_file, 0, 0) == 0:
        # 異步處理截圖文件
        QTimer.singleShot(100, self._process_snapshot)

智能記憶播放:

  • 記錄上次退出時(shí)的播放位置
  • 異常中斷后自動(dòng)恢復(fù)現(xiàn)場(chǎng)

四、性能優(yōu)化方案

4.1 資源管理

采用懶加載策略初始化VLC實(shí)例

動(dòng)態(tài)釋放已完成播放的媒體資源

4.2 線程安全

pythoncom.CoInitialize()  # COM組件初始化
try:
    # VLC多線程操作
finally:
    pythoncom.CoUninitialize()

4.3 渲染優(yōu)化

視頻:硬件加速解碼

圖片:Qt原生渲染引擎

五、擴(kuò)展開發(fā)方向

1.網(wǎng)絡(luò)推流功能:

":sout=#transcode{vcodec=h264}:rtp{dst=192.168.1.100,port=1234}"

2.定時(shí)任務(wù)模塊:

  • 基于cron的自動(dòng)化播放計(jì)劃
  • 節(jié)假日特殊排期支持
  • API接口擴(kuò)展:
  • RESTful控制接口
  • WebSocket實(shí)時(shí)狀態(tài)推送

六、效果展示

七、相關(guān)源碼

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="", 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=Qt.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進(jìn)行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 = "擴(kuò)展模式"
        self.screen_modes = ["擴(kuò)展模式", "主屏模式", "雙屏模式"]
        self.play_mode = True
        self.current_volume = 100
        self._volume = 100
        
        # 初始化UI
        self.setup_ui_style()
        self.init_ui()
        self.init_screens()
        
        # 初始化定時(shí)器
        self.media_timer = QTimer(self)
        self.media_timer.timeout.connect(self.update_media_status)
        self.media_timer.start(200)
        
        # 初始化動(dòng)畫相關(guān)
        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  # 新增暫停狀態(tài)標(biāo)記
        self.current_media_position = 0  # 記錄當(dāng)前播放位置

    def setup_ui_style(self):
        """設(shè)置全局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;
            }
        """)
        
        # 設(shè)置全局字體
        font = QFont()
        font.setFamily("Arial")
        font.setPointSize(10)
        QApplication.setFont(font)

    def init_ui(self):
        """初始化用戶界面"""
        self.setWindowTitle('大屏播控系統(tǒng)')
        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)
        
        # 左側(cè)控制面板
        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, '播放上一項(xiàng)'),
            ('?', self.toggle_play, '播放/暫停'),
            ('?', self.stop, '停止播放'),
            ('?', self.next_item, '播放下一項(xiàng)')
        ]
        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)
        
        # 進(jìn)度條
        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('連續(xù)播放')
        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)
        
        # 右側(cè)預(yù)覽區(qū)域
        preview_panel = StyledGroupBox("預(yù)覽")
        preview_layout = QVBoxLayout()
        preview_panel.setLayout(preview_layout)
        
        # 視頻預(yù)覽窗口
        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)
        
        # 狀態(tài)欄
        self.status_bar = QLabel('大屏準(zhǔn)備就緒')
        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('警告:未檢測(cè)到擴(kuò)展屏幕,將使用主屏幕播放!')
                self._create_fallback_window()
        except Exception as e:
            self.status_bar.setText(f'屏幕檢測(cè)失敗: {str(e)}')
            self._create_fallback_window()

    def _create_video_window(self):
        """創(chuàng)建擴(kuò)展屏播放窗口"""
        self.video_window = QWidget()
        self.video_window.setWindowTitle('擴(kuò)展屏幕播放器')
        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):
        """創(chuàng)建集成到主界面右側(cè)的監(jiān)看窗口"""
        self.video_window = self.preview_window
        self.preview_player.set_hwnd(0)

    def change_mode(self, mode):
        """切換播放模式"""
        self.mode = mode
        if mode == "擴(kuò)展模式" 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('連續(xù)播放' if self.play_mode else '單個(gè)播放')

    def play_selected_item(self, item):
        """處理雙擊播放列表項(xiàng)事件"""
        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("圖片加載失敗")
                        
                    # 在主預(yù)覽窗口顯示
                    scaled_pixmap = pixmap.scaled(
                        QSize(800, 600),
                        Qt.KeepAspectRatio,
                        Qt.SmoothTransformation
                    )
                    self.preview_window.setPixmap(scaled_pixmap)
                    
                    # 擴(kuò)展屏顯示邏輯
                    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'錯(cuò)誤: {str(e)}')
                    self.show_home_screen()
                    return
            else:
                # 如果是單個(gè)播放模式且不是圖片,先顯示首頁
                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])
            # 主播放器設(shè)置
            self.main_player.stop()
            self.main_player.set_media(media)
            
            # 預(yù)覽播放器設(shè)置(靜音且獨(dú)立)
            self.preview_player.stop()
            self.preview_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:
                # 雙屏模式:主輸出到擴(kuò)展屏,預(yù)覽輸出到主界面
                self.main_player.set_hwnd(self.video_window.winId())
                self.preview_player.set_hwnd(self.preview_window.winId())
            else:
                # 單屏模式:主播放器輸出到預(yù)覽窗口
                self.main_player.set_hwnd(self.preview_window.winId())
                self.preview_player.set_hwnd(0)
            
            # 同步啟動(dòng)播放
            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()
            
            # 更新狀態(tài)和列表選擇
            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):
        """啟動(dòng)淡入動(dòng)畫"""
        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):
        """更新預(yù)覽畫面"""
        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"預(yù)覽更新失敗: {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):
        """更新媒體狀態(tài)"""
        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 != "擴(kuò)展模式" 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:
                    # 恢復(fù)播放時(shí)保持當(dāng)前位置
                    self.main_player.set_pause(0)
                    self.playback_paused = False
                else:
                    # 新增播放時(shí)保持位置
                    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):
        """隱藏?cái)U(kuò)展屏任務(wù)欄"""
        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"隱藏任務(wù)欄失敗: {str(e)}")

    def closeEvent(self, event):
        """窗口關(guān)閉事件"""
        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):
        """播放上一項(xiàng)"""
        if self.playlist:
            new_index = (self.current_index - 1) % len(self.playlist)
            self.play_item(new_index)

    def next_item(self):
        """播放下一項(xiàng)"""
        if self.playlist:
            new_index = (self.current_index + 1) % len(self.playlist)
            self.play_item(new_index)

    def remove_selected(self):
        """刪除選中項(xiàng)"""
        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_index = 0
            except FileNotFoundError:
                self.status_bar.setText('播放列表文件不存在')

    def get_volume(self):
        return self.main_player.audio_get_volume()
    
    def set_volume(self, volume):
        """設(shè)置音量"""
        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])
            if self.current_index == -1:
                self.current_index = 0

if __name__ == '__main__':
    app = QApplication(sys.argv)
    player = ExtendedScreenPlayer()
    player.show()
    sys.exit(app.exec_())

八、項(xiàng)目總結(jié)

本系統(tǒng)通過創(chuàng)新的技術(shù)架構(gòu)解決了多屏播控領(lǐng)域的三大痛點(diǎn):

? 操作復(fù)雜性:直觀的GUI界面降低使用門檻

? 功能單一性:融合播放控制、轉(zhuǎn)場(chǎng)特效、多屏管理

? 穩(wěn)定性不足:完善的異常處理機(jī)制

實(shí)際應(yīng)用場(chǎng)景:

企業(yè)展廳的自動(dòng)導(dǎo)覽系統(tǒng)

會(huì)議中心的數(shù)字會(huì)標(biāo)管理

零售門店的廣告輪播系統(tǒng)

項(xiàng)目完整代碼已開源,開發(fā)者可基于此進(jìn)行二次開發(fā)。未來計(jì)劃增加AI內(nèi)容分析模塊,實(shí)現(xiàn)智能播控。

到此這篇關(guān)于Python+PyQt5實(shí)現(xiàn)多屏幕協(xié)同播放功能的文章就介紹到這了,更多相關(guān)Python多屏幕協(xié)同播放內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • python之tensorflow手把手實(shí)例講解斑馬線識(shí)別實(shí)現(xiàn)

    python之tensorflow手把手實(shí)例講解斑馬線識(shí)別實(shí)現(xiàn)

    目前智慧城市的發(fā)展,人們生活處處有科技,比如人臉識(shí)別,智慧交通,無人駕駛等前沿的科技產(chǎn)品也都融入了人們生活中;本篇文章帶你從頭開始實(shí)現(xiàn)斑馬線識(shí)別
    2021-09-09
  • 解決Pycharm出現(xiàn)的部分快捷鍵無效問題

    解決Pycharm出現(xiàn)的部分快捷鍵無效問題

    今天小編就為大家分享一篇解決Pycharm出現(xiàn)的部分快捷鍵無效問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-10-10
  • 詳解python中的IO操作方法

    詳解python中的IO操作方法

    這篇文章主要介紹了Python實(shí)現(xiàn)IO操作的示例,是python入門必會(huì)得知識(shí)點(diǎn),將幫助大家更好的理解和使用python,感興趣的朋友可以了解下
    2022-01-01
  • django vue3實(shí)現(xiàn)大文件分段續(xù)傳(斷點(diǎn)續(xù)傳)

    django vue3實(shí)現(xiàn)大文件分段續(xù)傳(斷點(diǎn)續(xù)傳)

    本文介紹了使用Django和Vue3實(shí)現(xiàn)大文件分段上傳(斷點(diǎn)續(xù)傳)的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-01-01
  • Python連接SQLite數(shù)據(jù)庫操作實(shí)戰(zhàn)指南從入門到精通

    Python連接SQLite數(shù)據(jù)庫操作實(shí)戰(zhàn)指南從入門到精通

    在Python中使用SQLite進(jìn)行數(shù)據(jù)庫操作時(shí),我們將深入研究SQLite數(shù)據(jù)庫的創(chuàng)建、表格管理、數(shù)據(jù)插入、查詢、更新和刪除等關(guān)鍵主題,幫助你全面了解如何使用SQLite進(jìn)行數(shù)據(jù)庫操作
    2023-11-11
  • Python實(shí)用工具之實(shí)現(xiàn)PDF轉(zhuǎn)DOCX文檔

    Python實(shí)用工具之實(shí)現(xiàn)PDF轉(zhuǎn)DOCX文檔

    pdf2docx作為第三方包,提供了非常優(yōu)秀的功能,僅僅幾行代碼就可以完成PDF轉(zhuǎn)換為DOCX的工作,所以本文就來利用pdf2docx實(shí)現(xiàn)PDF轉(zhuǎn)DOCX文檔功能吧
    2023-12-12
  • Python+Tkinter實(shí)現(xiàn)RGB數(shù)值轉(zhuǎn)換為16進(jìn)制碼

    Python+Tkinter實(shí)現(xiàn)RGB數(shù)值轉(zhuǎn)換為16進(jìn)制碼

    這篇文章主要為大家詳細(xì)介紹了Python如何利用Tkinter編寫一個(gè)RGB數(shù)值轉(zhuǎn)換為16進(jìn)制碼的小工具,文中的示例代講解詳細(xì),感興趣的小伙伴可以了解一下
    2023-01-01
  • Python結(jié)合jwt實(shí)現(xiàn)登錄權(quán)限校驗(yàn)認(rèn)證

    Python結(jié)合jwt實(shí)現(xiàn)登錄權(quán)限校驗(yàn)認(rèn)證

    本文主要介紹了Python結(jié)合jwt實(shí)現(xiàn)登錄權(quán)限校驗(yàn)認(rèn)證,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-04-04
  • Python中json庫的操作指南

    Python中json庫的操作指南

    JSON是存儲(chǔ)和交換文本信息的語法,類似XML,JSON比XML更小、更快,更易解析,且易于人閱讀和編寫,下面這篇文章主要給大家介紹了關(guān)于Python中json庫的操作指南,需要的朋友可以參考下
    2023-04-04
  • Python函數(shù)基礎(chǔ)

    Python函數(shù)基礎(chǔ)

    這篇文章主要從函數(shù)開始介紹展開Python函數(shù),以最基本的函數(shù)定義方法描述,需要的朋友可以參考下文簡(jiǎn)單的介紹
    2021-08-08

最新評(píng)論

罗定市| 伊金霍洛旗| 德格县| 绥滨县| 罗田县| 吴堡县| 连云港市| 凤冈县| 大庆市| 团风县| 平邑县| 司法| 六盘水市| 尉氏县| 收藏| 潞城市| 肇庆市| 舒兰市| 德昌县| 邵东县| 江北区| 唐山市| 商南县| 休宁县| 互助| 康定县| 徐州市| 石河子市| 泰州市| 墨玉县| 湄潭县| 顺义区| 嘉善县| 兴义市| 仁怀市| 固安县| 通化市| 迁西县| 卓资县| 四会市| 南召县|