787 lines
29 KiB
Python
787 lines
29 KiB
Python
import sys
|
||
import os
|
||
import threading
|
||
import socket
|
||
import resources
|
||
|
||
from PyQt5.QtCore import Qt
|
||
from PyQt5.QtGui import QIcon
|
||
from PyQt5.QtWidgets import QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, QLabel, QGroupBox, QFrame, QPushButton, \
|
||
QListWidget, QSlider, QFileDialog, QMessageBox, QListWidgetItem, QApplication
|
||
from core.device_discovery import DeviceDiscovery
|
||
from core.dlna_controller import DLNAController
|
||
from core.http_file_server import HTTPFileServer
|
||
from core.logger import AppLogger
|
||
|
||
class IconManager:
|
||
"""图标管理器,处理Qt资源系统的图标加载"""
|
||
|
||
@staticmethod
|
||
def get_icon(icon_name="icon"):
|
||
"""
|
||
从Qt资源系统加载图标
|
||
|
||
支持格式优先级:
|
||
1. 根据平台自动选择格式
|
||
2. 使用资源别名
|
||
3. 使用完整路径
|
||
"""
|
||
# 根据平台选择图标格式
|
||
platform = sys.platform
|
||
if platform == "win32":
|
||
extensions = [".ico", ".png"]
|
||
elif platform == "darwin": # macOS
|
||
extensions = [".icns", ".png"]
|
||
else: # Linux和其他
|
||
extensions = [".png", ".svg"]
|
||
|
||
# 尝试不同的路径格式
|
||
icon = QIcon()
|
||
|
||
for ext in extensions:
|
||
resource_path = f":/icons/{icon_name}{ext}"
|
||
icon = QIcon(resource_path)
|
||
if not icon.isNull():
|
||
print(f"✓ 从资源加载: {resource_path}")
|
||
return icon
|
||
|
||
# 如果都没找到,创建空图标
|
||
if icon.isNull():
|
||
print("⚠ 无法从资源加载图标,使用默认图标")
|
||
# 可以使用Qt内置图标作为备选
|
||
icon = QIcon.fromTheme("application-x-executable")
|
||
|
||
return icon
|
||
|
||
class MainWindow(QMainWindow):
|
||
"""智能媒体投屏器主窗口"""
|
||
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.device_discovery = None
|
||
self.dlna_controller = None
|
||
self.http_server = None
|
||
self.selected_device = None
|
||
self.selected_file = None
|
||
self.discovered_devices = []
|
||
self.logger = AppLogger('MediaCastUI')
|
||
self.http_port = 19735
|
||
self.is_paused = False # 新增:跟踪暂停状态
|
||
self.init_ui()
|
||
self.init_connections()
|
||
|
||
def init_ui(self):
|
||
"""初始化UI界面"""
|
||
version = "v1.0.3"
|
||
app_name = "多媒体投屏 by yqsphp"
|
||
|
||
self.setWindowTitle(app_name)
|
||
self.setGeometry(100, 100, 700, 500)
|
||
|
||
# 1. 加载主图标
|
||
icon = IconManager.get_icon()
|
||
|
||
# 2. 设置窗口图标(标题栏)
|
||
self.setWindowIcon(icon)
|
||
|
||
# 3. 设置应用程序图标(任务栏)
|
||
QApplication.instance().setWindowIcon(icon)
|
||
|
||
# Windows专用:设置AppUserModelID(非常重要!)
|
||
if sys.platform == "win32":
|
||
try:
|
||
from ctypes import windll
|
||
# 唯一ID,格式:公司名.程序名.版本
|
||
myappid = app_name + " " + version
|
||
windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
|
||
except ImportError:
|
||
pass
|
||
|
||
# 创建中央部件
|
||
central_widget = QWidget()
|
||
self.setCentralWidget(central_widget)
|
||
# 主布局
|
||
main_layout = QVBoxLayout(central_widget)
|
||
main_layout.setSpacing(10)
|
||
main_layout.setContentsMargins(10, 10, 10, 10)
|
||
# 水平布局用于左右面板
|
||
horizontal_layout = QHBoxLayout()
|
||
horizontal_layout.setSpacing(10)
|
||
# 左侧控制面板
|
||
left_panel = self.create_panel()
|
||
horizontal_layout.addWidget(left_panel, 1)
|
||
|
||
# 将水平布局添加到垂直布局中
|
||
main_layout.addLayout(horizontal_layout)
|
||
# 状态栏
|
||
self.statusBar().showMessage("准备就绪")
|
||
# 添加弹性空间
|
||
main_layout.addStretch()
|
||
# 底部版权信息
|
||
copyright_label = QLabel(f"© {app_name} {version}")
|
||
copyright_label.setAlignment(Qt.AlignCenter)
|
||
copyright_label.setStyleSheet("""
|
||
color: #999;
|
||
font-size: 11px;
|
||
padding: 15px 0 5px 0;
|
||
border-top: 1px solid #eee;
|
||
margin-top: 10px;
|
||
""")
|
||
main_layout.addWidget(copyright_label)
|
||
# 设置扁平化样式
|
||
self.setStyleSheet("""
|
||
/* 主窗口 */
|
||
QMainWindow {
|
||
background-color: #f5f5f5;
|
||
font-family: 'Microsoft YaHei', 'Segoe UI', sans-serif;
|
||
}
|
||
|
||
/* 分组框 - 扁平化 */
|
||
QGroupBox {
|
||
font-size: 13px;
|
||
border: 1px solid #ddd;
|
||
border-radius: 6px;
|
||
margin-top: 8px;
|
||
padding-top: 8px;
|
||
background-color: white;
|
||
}
|
||
|
||
QGroupBox::title {
|
||
subcontrol-origin: margin;
|
||
left: 10px;
|
||
padding: 0 8px 0 8px;
|
||
color: #333;
|
||
}
|
||
|
||
/* 按钮 - 扁平化 */
|
||
QPushButton {
|
||
border: none;
|
||
border-radius: 4px;
|
||
padding: 13px 10px;
|
||
font-size: 15px;
|
||
color: white;
|
||
background-color: #6c757d;
|
||
}
|
||
/* 特殊按钮样式 */
|
||
#browseBtn{
|
||
background:white;
|
||
border: 1px solid #ddd;
|
||
color:blank;
|
||
}
|
||
|
||
#refreshBtn {
|
||
background-color: #28a745;
|
||
color: white;
|
||
border: none;
|
||
}
|
||
|
||
#startCastBtn {
|
||
background-color: #007bff;
|
||
color: white;
|
||
border: none;
|
||
}
|
||
|
||
#pauseCastBtn {
|
||
background-color: #ffc107;
|
||
color: white;
|
||
border: none;
|
||
}
|
||
|
||
#stopCastBtn {
|
||
background-color: #dc3545;
|
||
color: white;
|
||
border: none;
|
||
}
|
||
#startCastBtn:disabled,
|
||
#pauseCastBtn:disabled,
|
||
#stopCastBtn:disabled{
|
||
color: #999 !important;
|
||
background-color: #f5f5f5 !important;
|
||
}
|
||
|
||
|
||
/*音量图标*/
|
||
#muteBtn{
|
||
background-color:none;
|
||
}
|
||
/* 列表控件 */
|
||
QListWidget {
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
background-color: white;
|
||
padding: 4px;
|
||
}
|
||
|
||
QListWidget::item {
|
||
padding: 6px 8px;
|
||
border-radius: 4px;
|
||
border-bottom: 1px solid #f0f0f0;
|
||
}
|
||
|
||
QListWidget::item:selected {
|
||
color: white;
|
||
background-color: #007bff;
|
||
}
|
||
|
||
/* 文本编辑框 */
|
||
QTextEdit {
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
background-color: white;
|
||
font-size: 11.5px;
|
||
padding: 0 4px;
|
||
}
|
||
|
||
/* 标签 */
|
||
QLabel {
|
||
color: #333;
|
||
font-size: 12px;
|
||
}
|
||
|
||
/* 输入框 */
|
||
QLineEdit {
|
||
border: 1px solid #ddd;
|
||
border-radius: 4px;
|
||
padding: 0 8px;
|
||
font-size: 12px;
|
||
}
|
||
""")
|
||
|
||
def create_panel(self):
|
||
"""创建左侧控制面板(文件和设备控制合并)"""
|
||
panel = QWidget()
|
||
layout = QVBoxLayout(panel)
|
||
layout.setSpacing(10)
|
||
|
||
# ==================== 系统信息区域 ====================
|
||
system_group = QGroupBox("系统信息")
|
||
system_layout = QVBoxLayout()
|
||
system_layout.setSpacing(8)
|
||
|
||
# 获取本机IP
|
||
try:
|
||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||
s.connect(("8.8.8.8", 80))
|
||
local_ip = s.getsockname()[0]
|
||
s.close()
|
||
except:
|
||
local_ip = "获取失败"
|
||
|
||
# 服务器状态
|
||
server_frame = QFrame()
|
||
server_layout = QHBoxLayout(server_frame)
|
||
|
||
server_layout.addWidget(QLabel("文件服务:"))
|
||
self.ip_label = QLabel(f"{local_ip}:{self.http_port}")
|
||
server_layout.addWidget(self.ip_label, 1)
|
||
|
||
system_layout.addWidget(server_frame)
|
||
|
||
# 文件服务状态
|
||
self.file_service_label = QLabel("就绪")
|
||
self.file_service_label.setStyleSheet("color: #28a745;")
|
||
server_layout.addWidget(self.file_service_label, 1)
|
||
|
||
system_group.setLayout(system_layout)
|
||
|
||
layout.addWidget(system_group)
|
||
|
||
# ==================== 文件和设备控制区域 ====================
|
||
control_group = QGroupBox("文件与设备控制")
|
||
control_layout = QVBoxLayout()
|
||
control_layout.setSpacing(12)
|
||
|
||
# 文件选择区域
|
||
file_frame = QFrame()
|
||
file_layout = QHBoxLayout(file_frame)
|
||
file_layout.setSpacing(8)
|
||
|
||
self.file_label = QLabel("未选择文件")
|
||
self.file_label.setWordWrap(True)
|
||
self.file_label.setMinimumHeight(25)
|
||
self.file_label.setStyleSheet("""
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 4px;
|
||
padding-left: 5px;
|
||
""")
|
||
file_layout.addWidget(self.file_label, 1)
|
||
|
||
browse_btn = QPushButton("浏览文件")
|
||
browse_btn.setCursor(Qt.PointingHandCursor)
|
||
browse_btn.setFixedWidth(80)
|
||
browse_btn.setObjectName("browseBtn")
|
||
browse_btn.clicked.connect(self.browse_file)
|
||
file_layout.addWidget(browse_btn)
|
||
|
||
control_layout.addWidget(file_frame)
|
||
|
||
# 设备列表区域
|
||
device_frame = QFrame()
|
||
device_layout = QVBoxLayout(device_frame)
|
||
device_layout.setSpacing(8)
|
||
|
||
# 设备列表标题和刷新按钮
|
||
device_header = QHBoxLayout()
|
||
device_header.addWidget(QLabel("设备列表(单击选中):"))
|
||
device_header.addStretch()
|
||
device_layout.addLayout(device_header)
|
||
|
||
# 设备列表
|
||
self.device_list = QListWidget()
|
||
self.device_list.itemClicked.connect(self.select_device)
|
||
self.device_list.setMinimumHeight(100)
|
||
device_layout.addWidget(self.device_list)
|
||
|
||
control_layout.addWidget(device_frame)
|
||
|
||
# 播放控制按钮
|
||
playback_frame = QFrame()
|
||
playback_layout = QHBoxLayout(playback_frame)
|
||
playback_layout.setSpacing(8)
|
||
|
||
self.refresh_btn = QPushButton("刷新设备")
|
||
self.refresh_btn.setObjectName("refreshBtn")
|
||
self.refresh_btn.setCursor(Qt.PointingHandCursor)
|
||
self.refresh_btn.clicked.connect(self.refresh_devices)
|
||
|
||
self.start_btn = QPushButton("开始投屏")
|
||
self.start_btn.setObjectName("startCastBtn")
|
||
self.start_btn.setCursor(Qt.PointingHandCursor)
|
||
self.start_btn.setEnabled(False)
|
||
self.start_btn.clicked.connect(self.start_casting)
|
||
|
||
self.pause_btn = QPushButton("暂停投屏")
|
||
self.pause_btn.setObjectName("pauseCastBtn")
|
||
self.pause_btn.setCursor(Qt.PointingHandCursor)
|
||
self.pause_btn.clicked.connect(self.pause_casting)
|
||
self.pause_btn.setEnabled(False)
|
||
|
||
self.stop_btn = QPushButton("结束投屏")
|
||
self.stop_btn.setObjectName("stopCastBtn")
|
||
self.stop_btn.setCursor(Qt.PointingHandCursor)
|
||
self.stop_btn.clicked.connect(self.stop_casting)
|
||
self.stop_btn.setEnabled(False)
|
||
|
||
playback_layout.addWidget(self.refresh_btn)
|
||
playback_layout.addWidget(self.start_btn)
|
||
playback_layout.addWidget(self.pause_btn)
|
||
playback_layout.addWidget(self.stop_btn)
|
||
|
||
control_layout.addWidget(playback_frame)
|
||
|
||
# 音量控制
|
||
volume_frame = QFrame()
|
||
volume_layout = QHBoxLayout(volume_frame)
|
||
volume_layout.setSpacing(8)
|
||
|
||
volume_layout.addWidget(QLabel("音量:"))
|
||
self.volume_slider = QSlider(Qt.Horizontal)
|
||
self.volume_slider.setRange(0, 100)
|
||
self.volume_slider.setValue(50)
|
||
self.volume_slider.setCursor(Qt.PointingHandCursor)
|
||
self.volume_slider.valueChanged.connect(self.volume_changed)
|
||
volume_layout.addWidget(self.volume_slider, 1)
|
||
|
||
self.mute_btn = QPushButton("🔊")
|
||
self.mute_btn.setCheckable(True)
|
||
self.mute_btn.setFixedWidth(40)
|
||
self.mute_btn.setObjectName("muteBtn")
|
||
self.mute_btn.setCursor(Qt.PointingHandCursor)
|
||
self.mute_btn.clicked.connect(self.toggle_mute)
|
||
volume_layout.addWidget(self.mute_btn)
|
||
|
||
control_layout.addWidget(volume_frame)
|
||
|
||
control_group.setLayout(control_layout)
|
||
layout.addWidget(control_group, 1) # 主要区域占据更多空间
|
||
|
||
layout.addStretch()
|
||
return panel
|
||
|
||
def init_connections(self):
|
||
"""初始化信号连接"""
|
||
# 设备发现
|
||
self.refresh_devices()
|
||
|
||
# 启动HTTP文件服务器
|
||
self.start_http_server()
|
||
|
||
def start_http_server(self):
|
||
"""启动HTTP文件服务器"""
|
||
try:
|
||
self.http_server = HTTPFileServer(self.http_port)
|
||
if self.http_server.start():
|
||
self.log_message("服务器", "HTTP文件服务器启动成功")
|
||
self.file_service_label.setText("运行中")
|
||
self.file_service_label.setStyleSheet("color: #20c997; font-weight: bold;")
|
||
else:
|
||
self.log_message("服务器", "HTTP文件服务器启动失败", "error")
|
||
self.file_service_label.setText("异常")
|
||
self.file_service_label.setStyleSheet("color: #dc3545; font-weight: bold;")
|
||
except Exception as e:
|
||
self.log_message("服务器", f"启动HTTP服务器出错: {e}", "error")
|
||
|
||
def enable_refresh_button(self):
|
||
"""启用刷新按钮"""
|
||
self.refresh_btn.setEnabled(True)
|
||
self.refresh_btn.setText("刷新设备")
|
||
|
||
def on_discovery_error(self, error_message):
|
||
"""设备发现错误"""
|
||
self.log_message("设备发现", f"搜索失败: {error_message}", "error")
|
||
self.enable_refresh_button()
|
||
QMessageBox.warning(self, "设备发现失败", f"搜索设备时出错:\n{error_message}")
|
||
|
||
def refresh_devices(self):
|
||
"""刷新设备列表"""
|
||
self.log_message("设备发现", "开始搜索网络中的投屏设备...", "info")
|
||
self.refresh_btn.setEnabled(False)
|
||
self.refresh_btn.setText("搜索中...")
|
||
|
||
# 在线程中进行,避免阻塞UI
|
||
def do_quick_discovery():
|
||
if not self.device_discovery:
|
||
self.device_discovery = DeviceDiscovery()
|
||
# 获取刷新后设备信息
|
||
new_device = self.device_discovery.discover_media_renderers()
|
||
self.update_device(new_device, self.selected_device)
|
||
|
||
# 确保在UI线程中更新
|
||
thread = threading.Thread(target=do_quick_discovery, daemon=True)
|
||
thread.start()
|
||
|
||
def update_device(self, new_devices, current_selected_device=None):
|
||
"""
|
||
更新设备列表显示,保留选中状态(使用UDN作为唯一标识)
|
||
Args:
|
||
new_devices: 搜索的设备
|
||
current_selected_device:当前选中的设备
|
||
"""
|
||
#当前选中设备
|
||
current_selected_udn = None
|
||
if current_selected_device:
|
||
current_selected_udn = current_selected_device.get('udn')
|
||
|
||
# 创建新设备字典,以UDN为键
|
||
new_device_dict = {}
|
||
for device in new_devices:
|
||
device_udn = device.get('udn')
|
||
if device_udn: # 只有有UDN的设备才处理
|
||
new_device_dict[device_udn] = device
|
||
|
||
# 获取之前列表中所有设备的UDN
|
||
current_device_udn_list = []
|
||
for i in range(self.device_list.count()):
|
||
item = self.device_list.item(i)
|
||
device = item.data(Qt.UserRole)
|
||
device_udn = device.get('udn')
|
||
if device_udn:
|
||
current_device_udn_list.append(device_udn)
|
||
|
||
# 移除不存在的设备
|
||
items_to_remove = []
|
||
for i in range(self.device_list.count()):
|
||
item = self.device_list.item(i)
|
||
device = item.data(Qt.UserRole)
|
||
|
||
# 获取设备的标识(优先使用UDN,没有则用IP)
|
||
device_udn = device.get('udn')
|
||
# 如果设备标识不在新设备列表中,标记为删除
|
||
if device_udn not in new_device_dict:
|
||
items_to_remove.append(i)
|
||
|
||
# 从后往前删除,避免索引变化
|
||
for index in reversed(items_to_remove):
|
||
self.device_list.takeItem(index)
|
||
# 如果删除的是当前选中的设备,清空选中状态
|
||
if item and item == self.device_list.currentItem():
|
||
self.device_list.clearSelection()
|
||
self.selected_device = None
|
||
|
||
# 更新或添加设备
|
||
current_selected_item = None
|
||
|
||
for device in new_devices:
|
||
# 获取设备标识(优先使用UDN,没有则用IP)
|
||
device_identifier = device.get('udn')
|
||
|
||
device_name = device.get('friendly_name', '未知设备')
|
||
device_ip = device.get('ip', '未知IP')
|
||
|
||
# 创建显示文本
|
||
if device.get('udn'):
|
||
# 简化UDN显示,只显示最后一部分
|
||
udn_parts = device_identifier.split(':')
|
||
udn_display = udn_parts[-1][:8] if len(udn_parts) > 1 else device_identifier[:8]
|
||
item_text = f"{device_name} [{device_ip}]"
|
||
else:
|
||
item_text = f"{device_name} [{device_ip}] (无UDN)"
|
||
|
||
# 检查设备是否已存在
|
||
found_item = None
|
||
for i in range(self.device_list.count()):
|
||
item = self.device_list.item(i)
|
||
existing_device = item.data(Qt.UserRole)
|
||
|
||
# 比较设备标识
|
||
existing_identifier = existing_device.get('udn')
|
||
if not existing_identifier:
|
||
existing_identifier = existing_device.get('ip', '')
|
||
|
||
if existing_identifier == device_identifier:
|
||
found_item = item
|
||
break
|
||
|
||
if found_item:
|
||
# 设备已存在,更新显示文本和设备数据
|
||
if found_item.text() != item_text:
|
||
found_item.setText(item_text)
|
||
# 更新设备数据(可能有新信息)
|
||
found_item.setData(Qt.UserRole, device)
|
||
|
||
# 检查是否是之前选中的设备
|
||
if current_selected_udn and device.get('udn') == current_selected_udn:
|
||
current_selected_item = found_item
|
||
else:
|
||
# 设备不存在,添加新项目
|
||
item = QListWidgetItem(item_text)
|
||
item.setData(Qt.UserRole, device)
|
||
self.device_list.addItem(item)
|
||
|
||
# 检查是否是之前选中的设备
|
||
if current_selected_udn and device.get('udn') == current_selected_udn:
|
||
current_selected_item = item
|
||
|
||
# 恢复选中状态
|
||
if current_selected_item:
|
||
self.device_list.setCurrentItem(current_selected_item)
|
||
self.selected_device = current_selected_item.data(Qt.UserRole)
|
||
|
||
device_name = self.selected_device.get('friendly_name', '未知设备')
|
||
self.log_message("设备选择", f"已恢复选择设备: {device_name}")
|
||
elif self.device_list.count() > 0 and self.device_list.currentItem() is None:
|
||
# 如果之前选中的设备不存在了,清空选中
|
||
self.device_list.clearSelection()
|
||
self.selected_device = None
|
||
elif self.device_list.count() == 0:
|
||
# 没有设备
|
||
self.selected_device = None
|
||
|
||
# 更新设备数量统计
|
||
device_count = self.device_list.count()
|
||
if device_count > 0:
|
||
self.log_message("设备发现", f"发现 {device_count} 个投屏设备")
|
||
|
||
# 如果有选中的设备,启用控制按钮
|
||
if self.selected_device:
|
||
device_name = self.selected_device.get('friendly_name', '未知设备')
|
||
self.log_message("设备选择", f"当前选中: {device_name}")
|
||
else:
|
||
self.log_message("设备发现", "未发现任何投屏设备", "warning")
|
||
self.selected_device = None
|
||
|
||
def select_device(self, item):
|
||
"""从列表中选择设备"""
|
||
device = item.data(Qt.UserRole)
|
||
if device:
|
||
self.selected_device = device
|
||
|
||
self.log_message("设备选择", f"已选择设备: {device.get('friendly_name', '未知')}")
|
||
|
||
# 启用控制按钮
|
||
self.start_btn.setText("开始投屏")
|
||
self.enable_control_buttons(True)
|
||
|
||
def browse_file(self):
|
||
"""浏览并选择媒体文件"""
|
||
file_dialog = QFileDialog()
|
||
file_dialog.setFileMode(QFileDialog.ExistingFile)
|
||
|
||
# 设置文件过滤器
|
||
file_dialog.setNameFilter(
|
||
"媒体文件 (*.mp4 *.avi *.mkv *.mov *.mp3 *.wav *.flac *.wmv *.flv *.webm "
|
||
"*.mpeg *.mpg *.m4v *.3gp *.m4a *.aac *.ogg *.wma *.ape *.alac "
|
||
"*.amr *.opus *.ac3 *.dts *.ts *.m2ts *.vob *.ogv *.asf *.rm "
|
||
"*.rmvb *.divx *.xvid *.f4v *.m2v *.mpe *.mp2 *.mp1 *.ra *.ram "
|
||
"*.mid *.midi *.cda *.gsm *.vox *.voc *.dvf *.raw *.mka *.tta "
|
||
"*.spx *.8svx *.aiff *.au *.snd *.paf *.svx *.nist *.ircam "
|
||
"*.voc *.smp *.vox *.sou);;"
|
||
)
|
||
|
||
if file_dialog.exec_():
|
||
files = file_dialog.selectedFiles()
|
||
if files:
|
||
self.selected_file = files[0]
|
||
file_name = os.path.basename(self.selected_file)
|
||
self.file_label.setText(file_name)
|
||
#选择文件后启用播放按钮
|
||
self.enable_control_buttons(True)
|
||
self.log_message("文件选择", f"已选择文件: {file_name}")
|
||
|
||
def start_casting(self):
|
||
"""开始投屏"""
|
||
if not self.selected_device :
|
||
self.log_message("投屏", "请先选择设备", "error")
|
||
return
|
||
if not self.selected_file:
|
||
self.log_message("投屏", "请先选择文件", "error")
|
||
return
|
||
try:
|
||
# 如果是第一次播放,需要创建DLNA控制器并设置URI
|
||
if not self.dlna_controller:
|
||
# 创建DLNA控制器
|
||
self.dlna_controller = DLNAController(self.selected_device)
|
||
|
||
# 启动HTTP服务器并获取文件URL
|
||
file_name = os.path.basename(self.selected_file)
|
||
dir_bool = self.http_server.set_root_directory(self.selected_file)
|
||
self.log_message("设置服务路径", "成功" if dir_bool else "失败")
|
||
|
||
# 获取IP地址(从IP标签中提取)
|
||
ip_text = self.ip_label.text()
|
||
file_url = f"http://{ip_text}/{file_name}"
|
||
self.log_message("设置播放地址", file_url)
|
||
|
||
# 设置播放URI并开始播放
|
||
if self.dlna_controller.set_av_transport_uri(file_url, file_name):
|
||
self.dlna_controller.play()
|
||
self.start_btn.setEnabled(False)
|
||
self.pause_btn.setEnabled(True)
|
||
self.stop_btn.setEnabled(True)
|
||
|
||
self.log_message("投屏", f"开始投屏: {file_name}")
|
||
self.statusBar().showMessage(f"正在投屏: {file_name}")
|
||
self.is_paused = False # 确保不是暂停状态
|
||
else:
|
||
QMessageBox.critical(self, "错误", "投屏失败,请检查设备连接")
|
||
return
|
||
else:
|
||
# 如果已经创建了DLNA控制器,可能是从暂停状态恢复
|
||
if self.is_paused:
|
||
# 从暂停状态恢复播放
|
||
if self.dlna_controller.play():
|
||
self.is_paused = False
|
||
self.start_btn.setEnabled(False)
|
||
self.pause_btn.setEnabled(True)
|
||
self.stop_btn.setEnabled(True)
|
||
self.log_message("投屏", "继续播放")
|
||
else:
|
||
QMessageBox.critical(self, "错误", "继续播放失败")
|
||
else:
|
||
# 如果不是暂停状态,说明是停止后的重新播放
|
||
# 需要重新设置URI
|
||
file_name = os.path.basename(self.selected_file)
|
||
ip_text = self.ip_label.text()
|
||
dir_bool = self.http_server.set_root_directory(self.selected_file)
|
||
self.log_message("设置服务路径", "成功" if dir_bool else "失败")
|
||
file_url = f"http://{ip_text}/{file_name}"
|
||
|
||
if self.dlna_controller.set_av_transport_uri(file_url, file_name):
|
||
self.dlna_controller.play()
|
||
self.start_btn.setEnabled(False)
|
||
self.pause_btn.setEnabled(True)
|
||
self.stop_btn.setEnabled(True)
|
||
|
||
self.log_message("投屏", f"重新开始投屏: {file_name}")
|
||
self.statusBar().showMessage(f"正在投屏: {file_name}")
|
||
else:
|
||
QMessageBox.critical(self, "错误", "重新投屏失败")
|
||
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "错误", f"投屏时出错: {str(e)}")
|
||
self.log_message("投屏", f"投屏失败: {str(e)}", "error")
|
||
|
||
def pause_casting(self):
|
||
"""暂停投屏"""
|
||
if self.dlna_controller:
|
||
self.dlna_controller.pause()
|
||
self.is_paused = True # 标记为暂停状态
|
||
|
||
self.start_btn.setText("继续播放")
|
||
self.start_btn.setEnabled(True)
|
||
self.pause_btn.setEnabled(False)
|
||
|
||
self.log_message("投屏", "已暂停播放")
|
||
|
||
def stop_casting(self):
|
||
"""停止投屏"""
|
||
if self.dlna_controller:
|
||
self.dlna_controller.stop()
|
||
self.is_paused = False # 重置暂停状态
|
||
|
||
self.start_btn.setText("开始投屏")
|
||
self.start_btn.setEnabled(True)
|
||
self.pause_btn.setEnabled(False)
|
||
self.stop_btn.setEnabled(False)
|
||
|
||
self.log_message("投屏", "已停止播放")
|
||
|
||
def volume_changed(self, value):
|
||
"""音量改变"""
|
||
if self.dlna_controller:
|
||
self.dlna_controller.set_volume(value)
|
||
|
||
def toggle_mute(self, checked):
|
||
"""切换静音"""
|
||
if self.dlna_controller:
|
||
self.dlna_controller.set_mute(checked)
|
||
icon = "🔇" if checked else "🔊"
|
||
self.mute_btn.setText(icon)
|
||
|
||
status = "静音" if checked else "取消静音"
|
||
self.log_message("控制", f"{status}")
|
||
|
||
def log_message(self, category, message, level="info"):
|
||
"""记录日志消息"""
|
||
# timestamp = datetime.now().strftime("%H:%M:%S")
|
||
#
|
||
# if level == "error":
|
||
# color = "#dc3545"
|
||
# prefix = "❌"
|
||
# elif level == "warning":
|
||
# color = "#ffc107"
|
||
# prefix = "⚠️"
|
||
# else:
|
||
# color = "#0d6efd"
|
||
# prefix = "ℹ️"
|
||
#
|
||
# log_entry = f'<span style="color:#adb5bd;">[{timestamp}]</span><span style="color:{color};"><b>{category}:</b> {prefix} {message}</span><br>'
|
||
#
|
||
# self.log_text.insertHtml(log_entry)
|
||
#
|
||
# # 自动滚动到底部
|
||
# scrollbar = self.log_text.verticalScrollBar()
|
||
# scrollbar.setValue(scrollbar.maximum())
|
||
self.logger.info(message)
|
||
# 同时输出到状态栏
|
||
if level == "error":
|
||
self.statusBar().showMessage(f"错误: {message}", 1000)
|
||
elif level == "warning":
|
||
self.statusBar().showMessage(f"警告: {message}", 1000)
|
||
else:
|
||
self.statusBar().showMessage(message, 1000)
|
||
|
||
def enable_control_buttons(self, enabled):
|
||
"""启用/禁用控制按钮"""
|
||
if self.selected_device and self.selected_file:
|
||
self.start_btn.setEnabled(True)
|
||
self.pause_btn.setEnabled(False)
|
||
self.stop_btn.setEnabled(False)
|
||
self.volume_slider.setEnabled(enabled)
|
||
self.mute_btn.setEnabled(enabled)
|
||
|
||
def closeEvent(self, event):
|
||
"""窗口关闭事件"""
|
||
# 停止HTTP服务器
|
||
if self.http_server:
|
||
self.http_server.stop()
|
||
|
||
# 停止DLNA控制器
|
||
if self.dlna_controller:
|
||
try:
|
||
self.dlna_controller.stop()
|
||
except:
|
||
pass
|
||
|
||
self.log_message("系统", "应用程序已关闭")
|
||
event.accept() |