From ddb3e98f16b630ccf5c4ef29a3ee22454a63af1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B6=B5=E6=9B=A6?= Date: Mon, 19 Jan 2026 15:35:58 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E6=94=AF=E6=8C=81=E4=BB=A3=E7=90=86=20m?= =?UTF-8?q?3u8=20=E6=A0=BC=E5=BC=8F=E9=93=BE=E6=8E=A5=20close=20#711?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- xiaomusic/api/routers/file.py | 36 +++++++++++++++++++++++++++++------ xiaomusic/music_library.py | 9 +++++++-- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/xiaomusic/api/routers/file.py b/xiaomusic/api/routers/file.py index db74cc0..1fe5929 100644 --- a/xiaomusic/api/routers/file.py +++ b/xiaomusic/api/routers/file.py @@ -289,8 +289,13 @@ async def get_picture(request: Request, file_path: str, key: str = "", code: str @router.get("/proxy", summary="基于正常下载逻辑的代理接口") -async def proxy(urlb64: str): - """代理接口""" +async def proxy(urlb64: str, radio: bool = None): + """代理接口 + + Args: + urlb64: Base64编码的URL + radio: 是否为电台直播流。None时自动识别,True强制使用长超时,False强制使用短超时 + """ try: # 将Base64编码的URL解码为字符串 url_bytes = base64.b64decode(urlb64) @@ -310,9 +315,14 @@ async def proxy(urlb64: str): status_code=400, detail="无效的URL格式" ) from invalid_url_exc - # 创建会话并确保关闭 + # 直播流使用更长的超时时间(24小时),普通文件使用10分钟 + timeout_seconds = 86400 if radio else 600 + log.info( + f"代理模式: {'电台直播流' if radio else '普通文件'}, 超时时间: {timeout_seconds}秒" + ) + session = aiohttp.ClientSession( - timeout=aiohttp.ClientTimeout(total=600), + timeout=aiohttp.ClientTimeout(total=timeout_seconds, sock_read=300), connector=aiohttp.TCPConnector(ssl=True), ) @@ -363,8 +373,22 @@ async def proxy(urlb64: str): finally: await close_session() - # 提取文件名 - filename = parsed_url.path.split("/")[-1].split("?")[0] or "output.mp3" + # 提取文件名,根据URL扩展名智能判断 + filename = parsed_url.path.split("/")[-1].split("?")[0] + if not filename: + # 根据URL扩展名或Content-Type设置默认文件名 + path_lower = parsed_url.path.lower() + if path_lower.endswith(".m3u8"): + filename = "stream.m3u8" + elif path_lower.endswith(".m3u"): + filename = "stream.m3u" + else: + # 根据Content-Type推断 + content_type = resp.headers.get("Content-Type", "").lower() + if "mpegurl" in content_type or "m3u8" in content_type: + filename = "stream.m3u8" + else: + filename = "output.mp3" return StreamingResponse( stream_generator(), diff --git a/xiaomusic/music_library.py b/xiaomusic/music_library.py index 9e49141..7bbfa3d 100644 --- a/xiaomusic/music_library.py +++ b/xiaomusic/music_library.py @@ -1023,7 +1023,9 @@ class MusicLibrary: # 是否需要代理 if self.config.web_music_proxy or url.startswith("self://"): - proxy_url = self._get_proxy_url(url) + # 判断是否为电台,传入 radio 参数 + is_radio = self.is_web_radio_music(name) + proxy_url = self._get_proxy_url(url, radio=is_radio) return proxy_url, url return url, None @@ -1044,11 +1046,12 @@ class MusicLibrary: self.log.error(f"_get_url_from_api use api fail. name:{name}, url:{url}") return url - def _get_proxy_url(self, origin_url): + def _get_proxy_url(self, origin_url, radio=None): """获取代理URL Args: origin_url: 原始URL + radio: 是否为电台直播流,None时不传参数 Returns: str: 代理URL @@ -1057,6 +1060,8 @@ class MusicLibrary: proxy_url = ( f"{self.config.hostname}:{self.config.public_port}/proxy?urlb64={urlb64}" ) + if radio is not None: + proxy_url += f"&radio={'true' if radio else 'false'}" self.log.info(f"Using proxy url: {proxy_url}") return proxy_url