chore: bump version to 0.7.1
This commit is contained in:
219
backend/modules/background_tasks.py
Normal file
219
backend/modules/background_tasks.py
Normal file
@@ -0,0 +1,219 @@
|
||||
"""
|
||||
后台任务模块
|
||||
负责创建和管理定期检查Apple Music状态并推送更新的后台任务
|
||||
"""
|
||||
import time
|
||||
import asyncio
|
||||
import logging
|
||||
from .apple_music import get_music_status
|
||||
from .lyrics import get_lyrics_data
|
||||
from .websocket_manager import ConnectionManager
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 存储后台任务引用
|
||||
background_tasks = set()
|
||||
|
||||
async def fetch_and_update_lyrics(status, manager):
|
||||
"""异步获取歌词并发送更新"""
|
||||
try:
|
||||
# 异步获取歌词
|
||||
logger.info(f"正在获取歌词...")
|
||||
lyrics = get_lyrics_data(status)
|
||||
logger.info(f"歌词: {lyrics}")
|
||||
# 获取到歌词后,再发送一个歌词更新消息
|
||||
if lyrics and isinstance(lyrics, dict): # 确保lyrics是一个非空的字典
|
||||
lyrics_update = {
|
||||
"type": "lyric_update", # 使用不同的消息类型
|
||||
"timestamp": time.time(),
|
||||
"lyrics": lyrics
|
||||
}
|
||||
|
||||
await manager.broadcast(lyrics_update)
|
||||
logger.info("歌词获取完成,已发送更新")
|
||||
except Exception as e:
|
||||
logger.error(f"获取歌词并发送更新时出错: {e}")
|
||||
|
||||
async def update_music_info(manager: ConnectionManager):
|
||||
"""定期检查音乐状态并向所有客户端推送更新"""
|
||||
logger.info("后台任务update_music_info开始运行...")
|
||||
|
||||
# 记录上一次状态
|
||||
last_position = -1
|
||||
last_track_name = None
|
||||
last_lyrics = None
|
||||
last_playback_status = None # 记录上一次的播放状态
|
||||
|
||||
# 配置更新间隔
|
||||
check_interval = 0.05 # 内部检查间隔仍保持较短
|
||||
|
||||
# 设定重试次数
|
||||
consecutive_errors = 0
|
||||
max_consecutive_errors = 10
|
||||
|
||||
while True:
|
||||
try:
|
||||
current_time = time.time()
|
||||
|
||||
# 获取当前状态
|
||||
status = get_music_status()
|
||||
if status is None:
|
||||
logger.error("获取音乐状态失败,返回None")
|
||||
await asyncio.sleep(1)
|
||||
continue
|
||||
|
||||
current_status = status.get("status", "unknown")
|
||||
|
||||
# 错误状态处理
|
||||
if current_status == "error":
|
||||
logger.error(f"获取音乐状态返回错误: {status.get('error', 'Unknown error')}")
|
||||
await asyncio.sleep(1)
|
||||
continue
|
||||
|
||||
# 状态变化处理
|
||||
if current_status != last_playback_status and last_playback_status is not None:
|
||||
logger.info(f"播放状态变化: {last_playback_status} -> {current_status}")
|
||||
|
||||
# 确定消息类型
|
||||
message_type = "status_change"
|
||||
if current_status == "playing":
|
||||
message_type = "playback_resumed"
|
||||
elif current_status == "paused":
|
||||
message_type = "playback_paused"
|
||||
elif current_status == "stopped":
|
||||
message_type = "playback_stopped"
|
||||
elif current_status == "notrunning":
|
||||
message_type = "app_not_running"
|
||||
|
||||
# 向客户端通知状态变化
|
||||
status_update = {
|
||||
"type": message_type,
|
||||
"timestamp": current_time,
|
||||
"status": status
|
||||
}
|
||||
|
||||
await manager.broadcast(status_update)
|
||||
last_playback_status = current_status
|
||||
consecutive_errors = 0
|
||||
await asyncio.sleep(check_interval)
|
||||
continue
|
||||
|
||||
# 如果没有状态变化,但是还没有记录过状态,进行初始化
|
||||
if last_playback_status is None:
|
||||
last_playback_status = current_status
|
||||
|
||||
# 如果应用未运行或已停止,无需进一步处理
|
||||
if current_status in ["stopped", "notrunning"]:
|
||||
await asyncio.sleep(check_interval)
|
||||
continue
|
||||
|
||||
# 保存当前播放状态用于下次比较
|
||||
last_playback_status = current_status
|
||||
|
||||
# 如果已停止,无需进一步处理
|
||||
if current_status in ["stopped", "notrunning"]:
|
||||
await asyncio.sleep(check_interval)
|
||||
continue
|
||||
|
||||
# 以下是正常播放状态下的处理
|
||||
position = status.get("position", -1)
|
||||
track_name = status.get("track_name")
|
||||
|
||||
# 安全检查:如果缺少关键数据,跳过本次循环
|
||||
if track_name is None:
|
||||
logger.debug("歌曲名称为空,跳过本次更新")
|
||||
await asyncio.sleep(check_interval)
|
||||
continue
|
||||
|
||||
# 检查是否需要发送更新
|
||||
track_changed = track_name != last_track_name
|
||||
|
||||
# 如果歌曲变化,获取新歌词
|
||||
if track_changed:
|
||||
try:
|
||||
# 立即发送歌曲变化通知(不含歌词)
|
||||
logger.info(f"歌曲变化: {last_track_name} -> {track_name}")
|
||||
|
||||
# 先发送一个不含歌词的track_change消息
|
||||
initial_update = {
|
||||
"type": "track_change",
|
||||
"timestamp": current_time,
|
||||
"status": status,
|
||||
"lyrics": None # 不包含歌词
|
||||
}
|
||||
|
||||
await manager.broadcast(initial_update)
|
||||
|
||||
# 更新歌曲名称记录
|
||||
last_track_name = track_name
|
||||
last_position = position
|
||||
|
||||
# 重置错误计数
|
||||
consecutive_errors = 0
|
||||
|
||||
# 启动一个独立的异步任务来获取歌词并后续发送更新
|
||||
# 这样主循环可以继续运行,不会阻塞
|
||||
asyncio.create_task(fetch_and_update_lyrics(status, manager))
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理歌曲变化时出错: {e}")
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
# 检查歌词是否需要更新(播放位置变化明显)
|
||||
elif abs(last_position - position) > 0.5: # 如果播放位置变化超过0.5秒
|
||||
try:
|
||||
lyrics = get_lyrics_data(status)
|
||||
|
||||
# 只有当歌词内容变化时才发送更新
|
||||
current_lyric = lyrics.get("current_lyric", "") if lyrics and isinstance(lyrics, dict) else ""
|
||||
current_lyric_time = lyrics.get("current_lyric_time", "") if lyrics and isinstance(lyrics, dict) else ""
|
||||
last_current_lyric_time = last_lyrics.get("current_lyric_time", "") if last_lyrics and isinstance(last_lyrics, dict) else ""
|
||||
|
||||
if current_lyric_time != last_current_lyric_time:
|
||||
if current_lyric is None:
|
||||
current_lyric = ""
|
||||
logger.debug(f"歌词更新: {current_lyric[:20]}...")
|
||||
|
||||
combined_data = {
|
||||
"type": "lyric_change",
|
||||
"timestamp": current_time,
|
||||
"status": status,
|
||||
"lyrics": lyrics
|
||||
}
|
||||
|
||||
await manager.broadcast(combined_data)
|
||||
last_lyrics = lyrics
|
||||
last_position = position
|
||||
|
||||
# 重置错误计数
|
||||
consecutive_errors = 0
|
||||
except Exception as e:
|
||||
logger.error(f"处理歌词更新时出错: {e}")
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
# 更新上一次位置
|
||||
if track_changed or (abs(last_position - position) > 0.5):
|
||||
last_position = position
|
||||
|
||||
# 等待下一次检查
|
||||
await asyncio.sleep(check_interval)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"更新音乐信息时出错: {e}")
|
||||
consecutive_errors += 1
|
||||
if consecutive_errors >= max_consecutive_errors:
|
||||
logger.error(f"连续错误次数超过{max_consecutive_errors},退出后台任务")
|
||||
break
|
||||
await asyncio.sleep(1) # 出错时等待1秒再重试
|
||||
|
||||
def start_background_task(manager: ConnectionManager):
|
||||
"""启动后台任务并添加到跟踪集合中"""
|
||||
task = asyncio.create_task(update_music_info(manager))
|
||||
background_tasks.add(task)
|
||||
task.add_done_callback(background_tasks.discard)
|
||||
return task
|
||||
|
||||
def cancel_all_tasks():
|
||||
"""取消所有后台任务"""
|
||||
for task in background_tasks:
|
||||
task.cancel()
|
||||
Reference in New Issue
Block a user