chore: bump version to 0.7.1

This commit is contained in:
ethan.chen
2025-05-27 14:16:48 +08:00
commit 63eda91fd6
103 changed files with 15868 additions and 0 deletions

View 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()