206 lines
7.8 KiB
Python
206 lines
7.8 KiB
Python
"""
|
||
路由模块
|
||
包含所有API端点的定义和处理逻辑
|
||
"""
|
||
import asyncio
|
||
import time
|
||
import logging
|
||
from fastapi import WebSocket, WebSocketDisconnect
|
||
from pydantic import BaseModel
|
||
from .apple_music import get_music_status, control_music
|
||
from .lyrics import get_lyrics_data, delete_lyrics, get_lyrics_from_netease, get_lyrics_from_id, save_lyrics, clear_lyrics_cache
|
||
from .websocket_manager import ConnectionManager
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# 音乐控制请求模型
|
||
class ControlRequest(BaseModel):
|
||
action: str # 'playpause', 'previous', 'next', 'seek'
|
||
position: float = None # 仅当 action=='seek' 时有效
|
||
|
||
# 手动搜索歌词请求模型
|
||
class SearchLyricsRequest(BaseModel):
|
||
track_name: str
|
||
artist: str
|
||
|
||
# 获取指定ID的歌词请求模型
|
||
class GetLyricsFromIdRequest(BaseModel):
|
||
id: int
|
||
track_name: str
|
||
artist: str
|
||
|
||
def register_routes(app, manager: ConnectionManager):
|
||
"""注册所有API路由到FastAPI应用"""
|
||
|
||
# 保留向下兼容的HTTP接口
|
||
@app.get("/status")
|
||
def status_endpoint():
|
||
"""获取 Apple Music 播放状态"""
|
||
return get_music_status()
|
||
|
||
@app.get("/lyrics")
|
||
def lyrics_endpoint():
|
||
"""获取当前歌词"""
|
||
status = get_music_status()
|
||
return get_lyrics_data(status)
|
||
|
||
@app.delete("/lyrics")
|
||
def delete_lyrics_endpoint(track_name: str, artist: str):
|
||
"""删除指定歌曲的歌词"""
|
||
success = delete_lyrics(track_name, artist)
|
||
if success:
|
||
return {"status": "success", "message": "歌词已删除"}
|
||
else:
|
||
return {"status": "error", "message": "未找到要删除的歌词"}
|
||
|
||
@app.post("/lyrics/search")
|
||
def search_lyrics_endpoint(request: SearchLyricsRequest):
|
||
"""手动搜索歌词"""
|
||
try:
|
||
# 搜索歌词
|
||
result = get_lyrics_from_netease(request.track_name, request.artist)
|
||
if result['code'] == 200 and result['result']['songCount'] > 0:
|
||
songs = result['result']['songs']
|
||
return {
|
||
"status": "success",
|
||
"songs": songs
|
||
}
|
||
|
||
else:
|
||
return {
|
||
"status": "error",
|
||
"message": "未找到歌词"
|
||
}
|
||
except Exception as e:
|
||
logger.error(f"搜索歌词时出错: {e}", exc_info=True)
|
||
return {
|
||
"status": "error",
|
||
"message": f"搜索歌词时出错: {str(e)}"
|
||
}
|
||
|
||
@app.post("/lyrics/getLyricsFromId")
|
||
def get_lyrics_from_id_endpoint(request: GetLyricsFromIdRequest):
|
||
"""获取指定ID的歌词"""
|
||
result = get_lyrics_from_id(request.id)
|
||
if result['code'] == 200 and 'lrc' in result:
|
||
logger.debug(f"网易云音乐歌词: {result['lrc']['lyric']}")
|
||
lyrics_text = result['lrc']['lyric']
|
||
# 保存到数据库
|
||
save_lyrics(request.track_name, request.artist, lyrics_text)
|
||
clear_lyrics_cache()
|
||
return {
|
||
"status": "success",
|
||
}
|
||
|
||
# 添加播放控制接口
|
||
@app.post("/control")
|
||
async def control_endpoint(request: ControlRequest):
|
||
"""控制Apple Music播放"""
|
||
result = control_music(request.action, request.position)
|
||
|
||
# 如果控制成功,广播更新的状态给所有WebSocket客户端
|
||
if result["status"] == "success":
|
||
# 延迟一下,让Apple Music有时间执行命令
|
||
await asyncio.sleep(0.1)
|
||
status = get_music_status()
|
||
lyrics = get_lyrics_data(status)
|
||
|
||
combined_data = {
|
||
"type": "position_update", # 添加消息类型
|
||
"timestamp": time.time(),
|
||
"status": status,
|
||
"lyrics": lyrics
|
||
}
|
||
|
||
# 异步广播更新
|
||
asyncio.create_task(manager.broadcast(combined_data))
|
||
|
||
return result
|
||
|
||
# WebSocket端点
|
||
@app.websocket("/ws")
|
||
async def websocket_endpoint(websocket: WebSocket):
|
||
"""WebSocket连接处理,合并推送歌词和状态"""
|
||
await manager.connect(websocket)
|
||
|
||
try:
|
||
# 连接建立后立即发送一次数据
|
||
status = get_music_status()
|
||
lyrics = get_lyrics_data(status)
|
||
|
||
logger.info(f"发送初始状态数据: 当前歌曲={status.get('track_name', '无')}, 歌词可用={lyrics is not None}")
|
||
|
||
combined_data = {
|
||
"type": "track_change", # 确保添加消息类型
|
||
"timestamp": time.time(),
|
||
"status": status,
|
||
"lyrics": lyrics
|
||
}
|
||
|
||
await websocket.send_json(combined_data)
|
||
|
||
# 发送欢迎消息
|
||
await websocket.send_json({"type": "info", "message": "WebSocket连接已建立"})
|
||
|
||
# 连接状态标志,用于安全终止循环
|
||
is_connected = True
|
||
|
||
# 持续接收并处理客户端消息
|
||
while is_connected:
|
||
try:
|
||
# 接收客户端消息,设置超时避免无限阻塞
|
||
data = await asyncio.wait_for(websocket.receive_json(), timeout=1.0)
|
||
logger.info(f"收到WebSocket消息: {data}")
|
||
|
||
# 处理不同类型的消息
|
||
if isinstance(data, dict):
|
||
# 处理请求状态消息
|
||
if data.get("type") == "request_status":
|
||
logger.info("客户端请求状态更新")
|
||
status = get_music_status()
|
||
lyrics = get_lyrics_data(status)
|
||
|
||
update_data = {
|
||
"type": "track_change", # 确保添加消息类型
|
||
"timestamp": time.time(),
|
||
"status": status,
|
||
"lyrics": lyrics
|
||
}
|
||
|
||
await websocket.send_json(update_data)
|
||
|
||
# 处理ping消息
|
||
elif data.get("type") == "ping":
|
||
logger.debug("收到客户端ping")
|
||
await websocket.send_json({
|
||
"type": "pong",
|
||
"timestamp": time.time()
|
||
})
|
||
except WebSocketDisconnect:
|
||
# 连接已断开,退出循环
|
||
logger.info("WebSocket连接断开")
|
||
is_connected = False
|
||
break
|
||
except asyncio.TimeoutError:
|
||
# 接收超时,但连接可能仍然有效,继续循环
|
||
continue
|
||
except Exception as e:
|
||
# 其他错误,记录后继续(除非是连接断开)
|
||
if "disconnect" in str(e).lower() or "not connected" in str(e).lower():
|
||
logger.info(f"WebSocket连接可能已断开: {e}")
|
||
is_connected = False
|
||
break
|
||
else:
|
||
logger.error(f"处理WebSocket消息时出错: {e}")
|
||
# 继续监听下一条消息,不断开连接
|
||
continue
|
||
|
||
except WebSocketDisconnect:
|
||
logger.info("WebSocket连接断开 (外层异常)")
|
||
except Exception as e:
|
||
logger.error(f"WebSocket错误: {e}")
|
||
finally:
|
||
# 确保连接从管理器中移除
|
||
await manager.disconnect(websocket)
|
||
logger.info("WebSocket连接已清理")
|