220 lines
8.9 KiB
Python
220 lines
8.9 KiB
Python
"""
|
||
后台任务模块
|
||
负责创建和管理定期检查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()
|