引言
在当今AI
技术飞速发展的时代呢,如何将传统应用程序与自然语言交互
相结合成为一个非常有趣的技术方向呀。嗯嗯,本文将详细介绍一个基于FastMCP
框架开发的智能音乐播放器呢,它能够通过自然语言指令
实现音乐播放控制,为用户提供全新的交互体验哦。啊,这个项目最初支持在线音乐播放
功能来着,但是呢,出于版权
考虑嘛,开源版本就仅保留了本地音乐播放
功能啦。
项目概述
这个音乐播放器项目采用Python
语言开发呢,核心功能包括:
- 嗯~
本地音乐文件
的扫描与加载多种播放模式
(单曲循环
呀、列表循环
啦、随机播放
这样子)啊~ 播放控制
(播放
/暂停
/停止
/上一首
/下一首
)嗯嗯,播放列表管理
功能通过FastMCP
框架提供自然语言接口
呢项目采用模块化设计
哦,主要依赖pygame
处理音频播放,FastMCP
提供AI交互接口
,整体架构非常清晰呢,易于扩展和维护的啦。
技术架构解析
1. 核心组件
项目主要包含以下几个关键组件哦:
import os.pathimport requestsimport reimport jsonimport pygameimport threadingimport queueimport random
pygame.mixer
:负责音频文件的加载
和播放
呢threading
:实现后台播放线程
呀,避免阻塞主程序queue
:用于线程间通信
(虽然最终版本没直接使用队列啦)random
:支持随机播放
模式嘛FastMCP
:提供AI工具调用
接口哦2. 全局状态管理
播放器通过一组全局变量
和线程事件
来管理播放状态呢:
current_play_list = [] # 当前播放列表呀current_play_mode = "single" # 播放模式啦current_song_index = -1 # 当前歌曲索引哦# 线程控制事件is_playing = threading.Event() # 播放状态标志呢is_paused = threading.Event() # 暂停状态标志呀should_load_new_song = threading.Event() # 加载新歌曲标志啦playback_thread = None # 播放线程句柄哦
这种设计实现了播放状态
与UI/控制逻辑
的分离呢,使得系统更加健壮
和可维护
呀。
核心实现细节
1. 音乐播放线程
播放器的核心是一个独立的后台线程
呢,负责实际的音乐播放逻辑哦:
def music_playback_thread(): global current_song_index, current_play_list, current_play_mode # 确保mixer在线程中初始化呀 if not pygame.mixer.get_init(): pygame.mixer.init() while True: # 检查是否需要加载新歌曲啦 if should_load_new_song.is_set(): pygame.mixer.music.stop() should_load_new_song.clear() # 处理歌曲加载逻辑哦 if not current_play_list: print("播放列表为空,无法加载新歌曲呢~") is_playing.clear() is_paused.clear() continue # 验证歌曲索引有效性呀 if not (0 <= current_song_index < len(current_play_list)): current_song_index = 0 # 加载并播放歌曲啦 song_file_name = current_play_list[current_song_index] song_to_play_path = os.path.join("music_file", song_file_name) if not os.path.exists(song_to_play_path): print(f"错误: 歌曲文件 '{song_file_name}' 未找到,跳过啦~") continue try: pygame.mixer.music.load(song_to_play_path) if not is_paused.is_set(): pygame.mixer.music.play() print(f"正在播放 (后台): {song_file_name}哦~") is_playing.set() except pygame.error as e: print(f"Pygame加载/播放错误: {e}. 可能音频文件损坏或格式不支持呢。跳过啦~") continue # 播放状态管理呀 if is_playing.is_set(): if pygame.mixer.music.get_busy() and not is_paused.is_set(): pygame.time.Clock().tick(10) elif not pygame.mixer.music.get_busy() and not is_paused.is_set(): # 歌曲自然结束啦,根据模式处理下一首哦 if current_play_list: if current_play_mode == "single": should_load_new_song.set() elif current_play_mode == "list": current_song_index = (current_song_index + 1) % len(current_play_list) should_load_new_song.set() elif current_play_mode == "random": current_song_index = random.randint(0, len(current_play_list) - 1) should_load_new_song.set() else: is_playing.clear() is_paused.clear() pygame.mixer.music.stop() elif is_paused.is_set(): pygame.time.Clock().tick(10) else: pygame.time.Clock().tick(100)
这个线程实现了完整的播放状态机
呢,能够处理各种播放场景哦,包括正常播放
呀、暂停
啦、歌曲切换
等等呢。
2. FastMCP工具函数
项目通过FastMCP
提供了一系列可被AI调用
的工具函数呢:
播放本地音乐
@mcp.tool()def play_musics_local(song_name: str = "", play_mode: str = "single") -> str: """播放本地音乐呀 :param song_name: 要播放的音乐名称呢,可以留空哦,留空表示加载进来的歌曲列表为本地文件夹中的所有音乐啦 :param play_mode: 播放模式呀,可选single(单曲循环),list(列表循环),random(随机播放)哦 :return: 播放结果呢 """ global current_play_list, current_play_mode, current_song_index, playback_thread # 确保音乐文件夹存在哦 if not os.path.exists("music_file"): os.makedirs("music_file") return "本地文件夹中没有音乐文件呢,已创建文件夹 'music_file'啦~" # 扫描音乐文件呀 music_files = [f for f in os.listdir("music_file") if f.endswith(".mp3")] if not music_files: return "本地文件夹中没有音乐文件呢~" # 构建播放列表啦 play_list_temp = [] if not song_name: play_list_temp = music_files else: for music_file in music_files: if song_name.lower() in music_file.lower(): play_list_temp.append(music_file) if not play_list_temp: return f"未找到匹配 '{song_name}' 的本地音乐文件呢~" current_play_list = play_list_temp current_play_mode = play_mode # 设置初始播放索引哦 if play_mode == "random": current_song_index = random.randint(0, len(current_play_list) - 1) else: if song_name: try: current_song_index = next(i for i, f in enumerate(current_play_list) if song_name.lower() in f.lower()) except StopIteration: current_song_index = 0 else: current_song_index = 0 # 确保播放线程运行呀 if playback_thread is None or not playback_thread.is_alive(): playback_thread = threading.Thread(target=music_playback_thread, daemon=True) playback_thread.start() print("后台播放线程已启动啦~") # 触发播放哦 pygame.mixer.music.stop() is_paused.clear() is_playing.set() should_load_new_song.set() return f"已加载 {len(current_play_list)} 首音乐到播放列表呢。当前播放模式:{play_mode}哦。即将播放:{current_play_list[current_song_index]}呀~"
播放控制函数
@mcp.tool()def pause_music(placeholder: str = ""): """暂停当前播放的音乐呀""" global is_paused, is_playing if pygame.mixer.music.get_busy(): pygame.mixer.music.pause() is_paused.set() return "音乐已暂停啦~" elif is_paused.is_set(): return "音乐已处于暂停状态呢" else: return "音乐未在播放中哦,无法暂停呀"@mcp.tool()def unpause_music(placeholder: str = ""): """恢复暂停的音乐呢""" global is_paused, is_playing if not pygame.mixer.music.get_busy() and pygame.mixer.music.get_pos() != -1 and is_paused.is_set(): pygame.mixer.music.unpause() is_paused.clear() is_playing.set() return "音乐已恢复播放啦~" elif pygame.mixer.music.get_busy() and not is_paused.is_set(): return "音乐正在播放中呢,无需恢复哦" else: return "音乐未在暂停中呀,无法恢复呢"@mcp.tool()def stop_music(placeholder: str = ""): """停止音乐播放并清理资源哦""" global is_playing, is_paused, current_song_index, should_load_new_song pygame.mixer.music.stop() is_playing.clear() is_paused.clear() should_load_new_song.clear() current_song_index = -1 return "音乐已停止啦,程序准备好接收新的播放指令哦~"
歌曲导航函数
@mcp.tool()def next_song(placeholder: str = "") -> str: """播放下一首歌曲呀""" global current_song_index, current_play_list, is_playing, is_paused, current_play_mode, should_load_new_song if not current_play_list: return "播放列表为空呢,无法播放下一首哦~" is_playing.set() is_paused.clear() # 从单曲循环切换到列表循环啦 if current_play_mode == "single": current_play_mode = "list" print("已从单曲循环模式切换到列表循环模式啦~") # 计算下一首索引哦 if current_play_mode == "list": current_song_index = (current_song_index + 1) % len(current_play_list) elif current_play_mode == "random": current_song_index = random.randint(0, len(current_play_list) - 1) should_load_new_song.set() return f"正在播放下一首: {current_play_list[current_song_index]}呢~"@mcp.tool()def previous_song(placeholder: str = "") -> str: """播放上一首歌曲呀""" global current_song_index, current_play_list, is_playing, is_paused, current_play_mode, should_load_new_song if not current_play_list: return "播放列表为空呢,无法播放上一首哦~" is_playing.set() is_paused.clear() if current_play_mode == "single": current_play_mode = "list" print("已从单曲循环模式切换到列表循环模式啦~") if current_play_mode == "list": current_song_index = (current_song_index - 1 + len(current_play_list)) % len(current_play_list) elif current_play_mode == "random": current_song_index = random.randint(0, len(current_play_list) - 1) should_load_new_song.set() return f"正在播放上一首: {current_play_list[current_song_index]}呢~"
播放列表查询
@mcp.tool()def get_playlist(placeholder: str = "") -> str: """获取当前播放列表呀""" global current_play_list, current_song_index if not current_play_list: return "播放列表当前为空呢~" response_lines = ["当前播放列表中的歌曲哦:"] for i, song_name in enumerate(current_play_list): prefix = "-> " if i == current_song_index else " " response_lines.append(f"{prefix}{i + 1}. {song_name}") return "\n".join(response_lines)
部署与使用
1. 环境准备
项目依赖较少呢,只需安装以下库哦:
pip install pygame requests fastmcp// 或者 指定阿里云的镜像源去加速下载(阿里源提供的PyPI镜像源地址)pip install pygame requests fastmcp -i https://mirrors.aliyun.com/pypi/simple/
2. 运行程序
python play_music.py
3. 与AI助手集成
- 在支持
AI助手
的客户端中配置SSE MCP
呀添加MCP地址
:http://localhost:4567/sse
哦启用所有工具函数
啦设置工具为自动执行
以获得更好体验呢配置,模型服务我选的是大模型openRouter:
然后去配置mcp服务器,类型一定要选sse
然后保存。
4. 使用示例
- "
播放本地歌曲
呀,使用随机播放模式
哦""下一首
啦""暂停一下
嘛""继续播放
呀""停止播放
呢""播放歌曲xxx
哦,使用单曲循环模式
啦""查看当前音乐播放列表
呀"JJ的歌真好听。