🧑🎤音乐MCP,听歌走起
引言
在当今AI
技术飞速发展的时代呢,如何将传统应用程序与自然语言交互
相结合成为一个非常有趣的技术方向呀。嗯嗯,本文将详细介绍一个基于FastMCP
框架开发的智能音乐播放器呢,它能够通过自然语言指令
实现音乐播放控制,为用户提供全新的交互体验哦。啊,这个项目最初支持在线音乐播放
功能来着,但是呢,出于版权
考虑嘛,开源版本就仅保留了本地音乐播放
功能啦。
项目概述
这个音乐播放器项目采用Python
语言开发呢,核心功能包括:
- 嗯~
本地音乐文件
的扫描与加载 - 多种
播放模式
(单曲循环
呀、列表循环
啦、随机播放
这样子) - 啊~
播放控制
(播放
/暂停
/停止
/上一首
/下一首
) - 嗯嗯,
播放列表管理
功能 - 通过
FastMCP
框架提供自然语言接口
呢
项目采用模块化设计
哦,主要依赖pygame
处理音频播放,FastMCP
提供AI交互接口
,整体架构非常清晰呢,易于扩展和维护的啦。
技术架构解析
1. 核心组件
项目主要包含以下几个关键组件哦:
import os.path
import requests
import re
import json
import pygame
import threading
import queue
import 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 = # 播放线程句柄哦
这种设计实现了播放状态
与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 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的歌真好听。
作者:盏灯
来源:juejin.cn/post/7520960903743963174
来源:juejin.cn/post/7520960903743963174