mirror of
https://gitee.com/yqsphp/MediaCast.git
synced 2026-05-24 13:45:46 +08:00
优化代码,新增投屏是推送文件名称
This commit is contained in:
@@ -132,12 +132,13 @@ class DLNAController:
|
||||
|
||||
# ================== AVTransport 服务方法 ==================
|
||||
|
||||
def set_av_transport_uri(self, media_url, metadata=""):
|
||||
def set_av_transport_uri(self, media_url, title = "", metadata=""):
|
||||
"""
|
||||
设置播放URI
|
||||
|
||||
Args:
|
||||
media_url: 媒体文件的URL
|
||||
title: 媒体文件名称
|
||||
metadata: 媒体元数据(可选)
|
||||
|
||||
Returns:
|
||||
@@ -146,16 +147,27 @@ class DLNAController:
|
||||
if not self.av_transport_url:
|
||||
return False
|
||||
|
||||
if title and not metadata:
|
||||
# 转义title中的XML特殊字符
|
||||
title_escaped = title.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
|
||||
# 构建简单的DIDL-Lite元数据
|
||||
metadata = f"""<DIDL-Lite xmlns="urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<item id="1" parentID="0" restricted="1">
|
||||
<dc:title>{title_escaped}</dc:title>
|
||||
</item>
|
||||
</DIDL-Lite>"""
|
||||
|
||||
# 转义XML特殊字符
|
||||
media_url_escaped = media_url.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||
media_url_escaped = urllib.parse.quote(media_url_escaped,safe=':/')
|
||||
metadata_escaped = metadata.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||
|
||||
soap_body = f"""<u:SetAVTransportURI xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
|
||||
<InstanceID>0</InstanceID>
|
||||
<CurrentURI>{media_url_escaped}</CurrentURI>
|
||||
<CurrentURIMetaData>{metadata_escaped}</CurrentURIMetaData>
|
||||
</u:SetAVTransportURI>"""
|
||||
<InstanceID>0</InstanceID>
|
||||
<CurrentURI>{media_url_escaped}</CurrentURI>
|
||||
<CurrentURIMetaData>{metadata_escaped}</CurrentURIMetaData>
|
||||
</u:SetAVTransportURI>"""
|
||||
|
||||
action = "urn:schemas-upnp-org:service:AVTransport:1#SetAVTransportURI"
|
||||
success, response = self._send_soap_request(self.av_transport_url, action, soap_body)
|
||||
@@ -178,9 +190,9 @@ class DLNAController:
|
||||
return False
|
||||
|
||||
soap_body = f"""<u:Play xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
|
||||
<InstanceID>0</InstanceID>
|
||||
<Speed>{speed}</Speed>
|
||||
</u:Play>"""
|
||||
<InstanceID>0</InstanceID>
|
||||
<Speed>{speed}</Speed>
|
||||
</u:Play>"""
|
||||
|
||||
action = "urn:schemas-upnp-org:service:AVTransport:1#Play"
|
||||
success, response = self._send_soap_request(self.av_transport_url, action, soap_body)
|
||||
@@ -198,8 +210,8 @@ class DLNAController:
|
||||
return False
|
||||
|
||||
soap_body = """<u:Pause xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
|
||||
<InstanceID>0</InstanceID>
|
||||
</u:Pause>"""
|
||||
<InstanceID>0</InstanceID>
|
||||
</u:Pause>"""
|
||||
|
||||
action = "urn:schemas-upnp-org:service:AVTransport:1#Pause"
|
||||
success, response = self._send_soap_request(self.av_transport_url, action, soap_body)
|
||||
@@ -217,8 +229,8 @@ class DLNAController:
|
||||
return False
|
||||
|
||||
soap_body = """<u:Stop xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
|
||||
<InstanceID>0</InstanceID>
|
||||
</u:Stop>"""
|
||||
<InstanceID>0</InstanceID>
|
||||
</u:Stop>"""
|
||||
|
||||
action = "urn:schemas-upnp-org:service:AVTransport:1#Stop"
|
||||
success, response = self._send_soap_request(self.av_transport_url, action, soap_body)
|
||||
@@ -242,10 +254,10 @@ class DLNAController:
|
||||
target_escaped = target.replace('&', '&').replace('<', '<').replace('>', '>')
|
||||
|
||||
soap_body = f"""<u:Seek xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
|
||||
<InstanceID>0</InstanceID>
|
||||
<Unit>{unit}</Unit>
|
||||
<Target>{target_escaped}</Target>
|
||||
</u:Seek>"""
|
||||
<InstanceID>0</InstanceID>
|
||||
<Unit>{unit}</Unit>
|
||||
<Target>{target_escaped}</Target>
|
||||
</u:Seek>"""
|
||||
|
||||
action = "urn:schemas-upnp-org:service:AVTransport:1#Seek"
|
||||
success, response = self._send_soap_request(self.av_transport_url, action, soap_body)
|
||||
@@ -267,8 +279,8 @@ class DLNAController:
|
||||
return None
|
||||
|
||||
soap_body = """<u:GetTransportInfo xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
|
||||
<InstanceID>0</InstanceID>
|
||||
</u:GetTransportInfo>"""
|
||||
<InstanceID>0</InstanceID>
|
||||
</u:GetTransportInfo>"""
|
||||
|
||||
action = "urn:schemas-upnp-org:service:AVTransport:1#GetTransportInfo"
|
||||
success, response = self._send_soap_request(self.av_transport_url, action, soap_body)
|
||||
@@ -321,8 +333,8 @@ class DLNAController:
|
||||
return None
|
||||
|
||||
soap_body = """<u:GetPositionInfo xmlns:u="urn:schemas-upnp-org:service:AVTransport:1">
|
||||
<InstanceID>0</InstanceID>
|
||||
</u:GetPositionInfo>"""
|
||||
<InstanceID>0</InstanceID>
|
||||
</u:GetPositionInfo>"""
|
||||
|
||||
action = "urn:schemas-upnp-org:service:AVTransport:1#GetPositionInfo"
|
||||
success, response = self._send_soap_request(self.av_transport_url, action, soap_body)
|
||||
@@ -375,10 +387,10 @@ class DLNAController:
|
||||
volume = max(0, min(100, int(volume)))
|
||||
|
||||
soap_body = f"""<u:SetVolume xmlns:u="urn:schemas-upnp-org:service:RenderingControl:1">
|
||||
<InstanceID>0</InstanceID>
|
||||
<Channel>{channel}</Channel>
|
||||
<DesiredVolume>{volume}</DesiredVolume>
|
||||
</u:SetVolume>"""
|
||||
<InstanceID>0</InstanceID>
|
||||
<Channel>{channel}</Channel>
|
||||
<DesiredVolume>{volume}</DesiredVolume>
|
||||
</u:SetVolume>"""
|
||||
|
||||
action = "urn:schemas-upnp-org:service:RenderingControl:1#SetVolume"
|
||||
success, response = self._send_soap_request(self.rendering_control_url, action, soap_body)
|
||||
@@ -399,9 +411,9 @@ class DLNAController:
|
||||
return None
|
||||
|
||||
soap_body = f"""<u:GetVolume xmlns:u="urn:schemas-upnp-org:service:RenderingControl:1">
|
||||
<InstanceID>0</InstanceID>
|
||||
<Channel>{channel}</Channel>
|
||||
</u:GetVolume>"""
|
||||
<InstanceID>0</InstanceID>
|
||||
<Channel>{channel}</Channel>
|
||||
</u:GetVolume>"""
|
||||
|
||||
action = "urn:schemas-upnp-org:service:RenderingControl:1#GetVolume"
|
||||
success, response = self._send_soap_request(self.rendering_control_url, action, soap_body)
|
||||
@@ -444,10 +456,10 @@ class DLNAController:
|
||||
desired_mute = "1" if mute else "0"
|
||||
|
||||
soap_body = f"""<u:SetMute xmlns:u="urn:schemas-upnp-org:service:RenderingControl:1">
|
||||
<InstanceID>0</InstanceID>
|
||||
<Channel>{channel}</Channel>
|
||||
<DesiredMute>{desired_mute}</DesiredMute>
|
||||
</u:SetMute>"""
|
||||
<InstanceID>0</InstanceID>
|
||||
<Channel>{channel}</Channel>
|
||||
<DesiredMute>{desired_mute}</DesiredMute>
|
||||
</u:SetMute>"""
|
||||
|
||||
action = "urn:schemas-upnp-org:service:RenderingControl:1#SetMute"
|
||||
success, response = self._send_soap_request(self.rendering_control_url, action, soap_body)
|
||||
@@ -468,9 +480,9 @@ class DLNAController:
|
||||
return None
|
||||
|
||||
soap_body = f"""<u:GetMute xmlns:u="urn:schemas-upnp-org:service:RenderingControl:1">
|
||||
<InstanceID>0</InstanceID>
|
||||
<Channel>{channel}</Channel>
|
||||
</u:GetMute>"""
|
||||
<InstanceID>0</InstanceID>
|
||||
<Channel>{channel}</Channel>
|
||||
</u:GetMute>"""
|
||||
|
||||
action = "urn:schemas-upnp-org:service:RenderingControl:1#GetMute"
|
||||
success, response = self._send_soap_request(self.rendering_control_url, action, soap_body)
|
||||
@@ -512,7 +524,7 @@ class DLNAController:
|
||||
return None
|
||||
|
||||
soap_body = """<u:GetProtocolInfo xmlns:u="urn:schemas-upnp-org:service:ConnectionManager:1">
|
||||
</u:GetProtocolInfo>"""
|
||||
</u:GetProtocolInfo>"""
|
||||
|
||||
action = "urn:schemas-upnp-org:service:ConnectionManager:1#GetProtocolInfo"
|
||||
success, response = self._send_soap_request(self.connection_manager_url, action, soap_body)
|
||||
@@ -540,119 +552,3 @@ class DLNAController:
|
||||
except ET.ParseError:
|
||||
return None
|
||||
|
||||
# ================== 高级方法 ==================
|
||||
|
||||
def play_media(self, media_url, metadata="", wait=1):
|
||||
"""
|
||||
播放媒体文件(组合操作)
|
||||
|
||||
Args:
|
||||
media_url: 媒体文件URL
|
||||
metadata: 媒体元数据
|
||||
wait: 设置URI后的等待时间(秒)
|
||||
|
||||
Returns:
|
||||
bool: 是否成功
|
||||
"""
|
||||
import time
|
||||
|
||||
if self.set_av_transport_uri(media_url, metadata):
|
||||
time.sleep(wait)
|
||||
return self.play()
|
||||
return False
|
||||
|
||||
def get_device_status(self):
|
||||
"""
|
||||
获取设备完整状态
|
||||
|
||||
Returns:
|
||||
dict: 设备状态信息
|
||||
"""
|
||||
status = {
|
||||
'device_info': {
|
||||
'friendly_name': self.device_info.get('friendly_name', '未知'),
|
||||
'ip': self.device_info.get('ip', '未知'),
|
||||
'location': self.device_info.get('location', '未知')
|
||||
},
|
||||
'transport_info': self.get_transport_info(),
|
||||
'position_info': self.get_position_info(),
|
||||
'volume': self.get_volume(),
|
||||
'mute': self.get_mute(),
|
||||
'protocol_info': self.get_protocol_info(),
|
||||
'services': {
|
||||
'av_transport': self.av_transport_url is not None,
|
||||
'rendering_control': self.rendering_control_url is not None,
|
||||
'connection_manager': self.connection_manager_url is not None
|
||||
}
|
||||
}
|
||||
|
||||
return status
|
||||
|
||||
def get_supported_formats(self):
|
||||
"""
|
||||
获取设备支持的媒体格式
|
||||
|
||||
Returns:
|
||||
list: 支持的格式列表
|
||||
"""
|
||||
protocol_info = self.get_protocol_info()
|
||||
if not protocol_info or 'sink' not in protocol_info:
|
||||
return []
|
||||
|
||||
# 解析sink字段,提取支持的格式
|
||||
sink_info = protocol_info['sink']
|
||||
formats = []
|
||||
|
||||
# DLNA格式通常是逗号分隔的
|
||||
for fmt in sink_info.split(','):
|
||||
fmt = fmt.strip()
|
||||
if fmt:
|
||||
formats.append(fmt)
|
||||
|
||||
return formats
|
||||
|
||||
def can_play_format(self, media_url):
|
||||
"""
|
||||
检查设备是否可能支持指定格式
|
||||
|
||||
Args:
|
||||
media_url: 媒体URL
|
||||
|
||||
Returns:
|
||||
bool: 是否可能支持
|
||||
"""
|
||||
import mimetypes
|
||||
|
||||
# 获取文件扩展名
|
||||
if '.' in media_url:
|
||||
ext = media_url.split('.')[-1].lower()
|
||||
|
||||
# 常见扩展名到MIME类型的映射
|
||||
ext_to_mime = {
|
||||
'mp4': 'video/mp4',
|
||||
'mp3': 'audio/mp3',
|
||||
'm4a': 'audio/mp4',
|
||||
'wav': 'audio/wav',
|
||||
'flac': 'audio/flac',
|
||||
'mkv': 'video/x-matroska',
|
||||
'avi': 'video/x-msvideo',
|
||||
'mov': 'video/quicktime',
|
||||
'wmv': 'video/x-ms-wmv',
|
||||
'flv': 'video/x-flv',
|
||||
'webm': 'video/webm',
|
||||
'ogg': 'audio/ogg',
|
||||
'oga': 'audio/ogg',
|
||||
'ogv': 'video/ogg',
|
||||
'm3u8': 'application/x-mpegURL',
|
||||
'mpd': 'application/dash+xml'
|
||||
}
|
||||
|
||||
mime_type = ext_to_mime.get(ext)
|
||||
if mime_type:
|
||||
# 检查设备是否支持该MIME类型
|
||||
supported_formats = self.get_supported_formats()
|
||||
for fmt in supported_formats:
|
||||
if mime_type in fmt:
|
||||
return True
|
||||
|
||||
return False
|
||||
@@ -43,8 +43,10 @@ class MainWindow(QMainWindow):
|
||||
|
||||
# 获取图标路径(支持打包和开发环境)
|
||||
if getattr(sys, 'frozen', False):
|
||||
# 打包后的exe环境
|
||||
base_path = sys._MEIPASS
|
||||
# pyInstaller打包后的exe环境
|
||||
# base_path = sys._MEIPASS
|
||||
# Nuitka打包后
|
||||
base_path = os.path.dirname(sys.executable)
|
||||
else:
|
||||
# 开发环境
|
||||
base_path = os.path.abspath(".")
|
||||
@@ -707,7 +709,7 @@ class MainWindow(QMainWindow):
|
||||
self.log_message("设置播放地址", file_url)
|
||||
|
||||
# 设置播放URI并开始播放
|
||||
if self.dlna_controller.set_av_transport_uri(file_url):
|
||||
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)
|
||||
@@ -740,7 +742,7 @@ class MainWindow(QMainWindow):
|
||||
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):
|
||||
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)
|
||||
@@ -758,27 +760,27 @@ class MainWindow(QMainWindow):
|
||||
def pause_casting(self):
|
||||
"""暂停投屏"""
|
||||
if self.dlna_controller:
|
||||
if self.dlna_controller.pause():
|
||||
self.is_paused = True # 标记为暂停状态
|
||||
self.dlna_controller.pause()
|
||||
self.is_paused = True # 标记为暂停状态
|
||||
|
||||
self.start_btn.setText("继续播放")
|
||||
self.start_btn.setEnabled(True)
|
||||
self.pause_btn.setEnabled(False)
|
||||
self.start_btn.setText("继续播放")
|
||||
self.start_btn.setEnabled(True)
|
||||
self.pause_btn.setEnabled(False)
|
||||
|
||||
self.log_message("投屏", "已暂停播放")
|
||||
self.log_message("投屏", "已暂停播放")
|
||||
|
||||
def stop_casting(self):
|
||||
"""停止投屏"""
|
||||
if self.dlna_controller:
|
||||
if self.dlna_controller.stop():
|
||||
self.is_paused = False # 重置暂停状态
|
||||
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.start_btn.setText("开始投屏")
|
||||
self.start_btn.setEnabled(True)
|
||||
self.pause_btn.setEnabled(False)
|
||||
self.stop_btn.setEnabled(False)
|
||||
|
||||
self.log_message("投屏", "已停止播放")
|
||||
self.log_message("投屏", "已停止播放")
|
||||
|
||||
def volume_changed(self, value):
|
||||
"""音量改变"""
|
||||
|
||||
Reference in New Issue
Block a user