1
0
mirror of https://github.com/hanxi/xiaomusic.git synced 2025-12-06 14:52:50 +08:00

feat: 新增代理播放链接功能 see: #525

This commit is contained in:
涵曦
2025-08-15 20:52:39 +08:00
parent 8246ebf74d
commit 6a37e1ec70
3 changed files with 101 additions and 1 deletions

View File

@@ -1,4 +1,5 @@
import asyncio
import base64
import hashlib
import json
import os
@@ -9,6 +10,7 @@ import urllib.parse
from contextlib import asynccontextmanager
from dataclasses import asdict
from typing import TYPE_CHECKING, Annotated
from urllib.parse import urlparse
import socketio
@@ -19,6 +21,7 @@ if TYPE_CHECKING:
from xiaomusic.xiaomusic import XiaoMusic
import aiofiles
import aiohttp
from fastapi import (
Depends,
FastAPI,
@@ -32,7 +35,7 @@ from fastapi import (
from fastapi.middleware.cors import CORSMiddleware
from fastapi.openapi.docs import get_redoc_html, get_swagger_ui_html
from fastapi.openapi.utils import get_openapi
from fastapi.responses import RedirectResponse
from fastapi.responses import RedirectResponse, StreamingResponse
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
@@ -859,3 +862,84 @@ async def get_redoc_documentation(Verifcation=Depends(verification)):
@app.get("/openapi.json", include_in_schema=False)
async def openapi(Verifcation=Depends(verification)):
return get_openapi(title=app.title, version=app.version, routes=app.routes)
@app.get("/proxy", summary="基于正常下载逻辑的代理接口")
async def proxy(urlb64: str):
try:
# 将Base64编码的URL解码为字符串
url_bytes = base64.b64decode(urlb64)
url = url_bytes.decode("utf-8")
print(f"解码后的代理请求: {url}")
except Exception as e:
raise HTTPException(status_code=400, detail=f"Base64解码失败: {str(e)}") from e
log.info(f"代理请求: {url}")
parsed_url = urlparse(url)
if not parsed_url.scheme or not parsed_url.netloc:
# Fixed: Use a new exception instance since 'e' from previous block is out of scope
invalid_url_exc = ValueError("URL缺少协议或域名")
raise HTTPException(
status_code=400, detail="无效的URL格式"
) from invalid_url_exc
# 创建会话并确保关闭
session = aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=600),
connector=aiohttp.TCPConnector(ssl=False),
)
# 复用经过验证的请求头配置
def get_wget_headers(parsed_url):
return {
"User-Agent": "Wget/1.21.3",
"Accept": "*/*",
"Accept-Encoding": "identity",
"Host": parsed_url.netloc,
"Connection": "Keep-Alive",
}
async def close_session():
if not session.closed:
await session.close()
try:
# 复用download_file中的请求逻辑
headers = get_wget_headers(parsed_url)
resp = await session.get(url, headers=headers, allow_redirects=True)
if resp.status not in (200, 206):
await close_session()
status_exc = ValueError(f"服务器返回状态码: {resp.status}")
raise HTTPException(
status_code=resp.status, detail=f"下载失败,状态码: {resp.status}"
) from status_exc
# 流式生成器与download_file的分块逻辑一致
async def stream_generator():
try:
async for data in resp.content.iter_chunked(4096):
yield data
finally:
await close_session()
# 提取文件名
filename = parsed_url.path.split("/")[-1].split("?")[0] or "output.mp3"
return StreamingResponse(
stream_generator(),
media_type=resp.headers.get("Content-Type", "audio/mpeg"),
headers={"Content-Disposition": f'inline; filename="{filename}"'},
background=BackgroundTask(close_session),
)
except aiohttp.ClientConnectionError as e:
await close_session()
raise HTTPException(status_code=502, detail=f"连接错误: {str(e)}") from e
except asyncio.TimeoutError as e:
await close_session()
raise HTTPException(status_code=504, detail="下载超时") from e
except Exception as e:
await close_session()
raise HTTPException(status_code=500, detail=f"发生错误: {str(e)}") from e

View File

@@ -153,6 +153,9 @@
<div class="component-button-group">
<button onclick="playUrl()">播放链接</button>
<button onclick="playProxyUrl()">代理播放链接</button>
</div>
<div class="component-button-group">
<button onclick="playTts()">播放文字</button>
<button onclick="togglePlayLink()">关闭</button>
</div>

View File

@@ -382,6 +382,19 @@ function playUrl() {
});
}
function playProxyUrl() {
const origin_url = $("#music-url").val();
const protocol = window.location.protocol;
const host= window.location.host;
const baseUrl = `${protocol}//${host}`;
const urlb64 = btoa(origin_url);
const url = `${baseUrl}/proxy?urlb64=${urlb64}`;
const encoded_url = encodeURIComponent(url);
$.get(`/playurl?url=${encoded_url}&did=${did}`, function (data, status) {
console.log(data);
});
}
function playTts() {
var value = $("#text-tts").val();
$.get(`/playtts?text=${value}&did=${did}`, function (data, status) {