欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

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

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

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

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

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

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

2.1 多屏管理機(jī)制

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

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

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

2.2 播放引擎設(shè)計

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

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

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

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è)級轉(zhuǎn)場動畫

通過Qt動畫框架實現(xiàn)廣播級效果:

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 基礎(chǔ)操作流程

1.添加媒體文件:

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

2.播放模式選擇:

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

3.多屏輸出切換:

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

3.2 高級功能

定時截圖預(yù)覽:

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

智能記憶播放:

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

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

4.1 資源管理

采用懶加載策略初始化VLC實例

動態(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.定時任務(wù)模塊:

  • 基于cron的自動化播放計劃
  • 節(jié)假日特殊排期支持
  • API接口擴(kuò)展:
  • RESTful控制接口
  • WebSocket實時狀態(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()
        
        # 初始化定時器
        self.media_timer = QTimer(self)
        self.media_timer.timeout.connect(self.update_media_status)
        self.media_timer.start(200)
        
        # 初始化動畫相關(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, '播放上一項'),
            ('?', 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)
        
        # 進(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('警告:未檢測到擴(kuò)展屏幕,將使用主屏幕播放!')
                self._create_fallback_window()
        except Exception as e:
            self.status_bar.setText(f'屏幕檢測失敗: {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 '單個播放')

    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("圖片加載失敗")
                        
                    # 在主預(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'錯誤: {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])
            # 主播放器設(shè)置
            self.main_player.stop()
            self.main_player.set_media(media)
            
            # 預(yù)覽播放器設(shè)置(靜音且獨立)
            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)
            
            # 同步啟動播放
            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):
        """啟動淡入動畫"""
        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ù)播放時保持當(dāng)前位置
                    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):
        """隱藏擴(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):
        """播放上一項"""
        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_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_())

八、項目總結(jié)

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

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

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

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

實際應(yīng)用場景:

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

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

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

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

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

相關(guān)文章

  • 用Python實現(xiàn)篩選文件腳本的方法

    用Python實現(xiàn)篩選文件腳本的方法

    今天小編就為大家分享一篇用Python實現(xiàn)篩選文件腳本的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-10-10
  • 如何用Python識別車牌的示例代碼

    如何用Python識別車牌的示例代碼

    車牌識別系統(tǒng)計算機(jī)視頻圖像識別技術(shù)在車輛牌照識別中的一種應(yīng)用,本文主要介紹了如何用Python識別車牌的示例代碼,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • Python如何提取公共模塊并避免代碼重復(fù)

    Python如何提取公共模塊并避免代碼重復(fù)

    模塊化編程是提高代碼重用性和可維護(hù)性的關(guān)鍵,這篇文章小編就來為大家詳細(xì)介紹一下Python如何提取公共模塊并避免代碼重復(fù),希望對大家有所幫助
    2025-02-02
  • django中send_mail功能實現(xiàn)詳解

    django中send_mail功能實現(xiàn)詳解

    這篇文章主要給大家介紹了關(guān)于django中send_mail功能實現(xiàn)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。
    2018-02-02
  • 詳解python 3.6 安裝json 模塊(simplejson)

    詳解python 3.6 安裝json 模塊(simplejson)

    這篇文章主要介紹了python 3.6 安裝json 模塊(simplejson),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • Python利用緩存流實現(xiàn)壓縮PDF文件

    Python利用緩存流實現(xiàn)壓縮PDF文件

    在Python中,有許多庫可以用來壓縮PDF文件,其中最常用的是PyPDF2和PDFMiner,本文將為大家介紹一個新的方法,即使用緩存流壓縮PDF文件,感興趣的可以了解下
    2023-08-08
  • Python實現(xiàn)為pdf添加水印功能

    Python實現(xiàn)為pdf添加水印功能

    這篇文章主要介紹了Python實現(xiàn)給普通PDF添加水印的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04
  • Python函數(shù)式編程模塊functools的使用與實踐

    Python函數(shù)式編程模塊functools的使用與實踐

    本文主要介紹了Python函數(shù)式編程模塊functools的使用與實踐,教你如何使用?functools.partial、functools.wraps、functools.lru_cache?和?functools.reduce,感興趣的可以了解一下
    2024-03-03
  • Django中多種重定向方法使用詳解

    Django中多種重定向方法使用詳解

    這篇文章主要介紹了Django中多種重定向方法使用詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2019-07-07
  • Numpy中np.expand_dims的用法

    Numpy中np.expand_dims的用法

    np.expand_dims是Numpy庫中的一個函數(shù),它的主要作用是在數(shù)組的指定位置增加一個新的維度,本文就來介紹一下它的用法,具有一定的參考價值,感興趣的可以了解一下
    2024-03-03

最新評論