掘金 人工智能 15小时前
🧑‍🎤音乐MCP,听歌走起
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文介绍了一个基于FastMCP框架开发的智能音乐播放器项目,该项目使用Python语言,并结合了pygame和FastMCP技术。它实现了本地音乐文件的扫描、播放控制、播放列表管理等功能,并通过FastMCP提供了自然语言接口,使用户可以通过语音指令控制音乐播放。由于版权原因,开源版本仅保留了本地音乐播放功能。文章详细介绍了项目的技术架构、核心组件、全局状态管理以及关键的实现细节,包括音乐播放线程和FastMCP工具函数。最后,文章还提供了部署和使用的步骤,以及与AI助手的集成方法,展示了该项目的实际应用。

🎧 项目的核心在于使用Python和Pygame进行音频播放,通过模块化设计,实现了本地音乐文件的扫描与加载、多种播放模式、播放控制以及播放列表管理等功能,为用户提供了基本的音乐播放体验。

⚙️ 技术架构方面,项目采用了全局变量和线程事件来管理播放状态,实现了播放状态与UI/控制逻辑的分离,使得系统更加健壮和可维护。其中,音乐播放线程是项目的核心,负责实际的音乐播放逻辑,包括歌曲加载、播放、暂停、切换等操作。

🤖 FastMCP框架是该项目的亮点,它提供了一系列可被AI调用的工具函数,如播放本地音乐、暂停、恢复、停止、歌曲导航以及播放列表查询等。这些工具函数使得用户可以通过自然语言指令与音乐播放器进行交互,从而实现智能控制。

🚀 部署与使用部分详细介绍了项目的环境准备、运行程序、与AI助手集成等步骤,并提供了使用示例。用户只需安装必要的库,运行程序,并配置AI助手,即可体验通过自然语言控制音乐播放的乐趣。

引言

在当今AI技术飞速发展的时代呢,如何将传统应用程序与自然语言交互相结合成为一个非常有趣的技术方向呀。嗯嗯,本文将详细介绍一个基于FastMCP框架开发的智能音乐播放器呢,它能够通过自然语言指令实现音乐播放控制,为用户提供全新的交互体验哦。啊,这个项目最初支持在线音乐播放功能来着,但是呢,出于版权考虑嘛,开源版本就仅保留了本地音乐播放功能啦。

项目概述

这个音乐播放器项目采用Python语言开发呢,核心功能包括:

    嗯~ 本地音乐文件的扫描与加载多种播放模式单曲循环呀、列表循环啦、随机播放这样子)啊~ 播放控制播放/暂停/停止/上一首/下一首)嗯嗯,播放列表管理功能通过FastMCP框架提供自然语言接口

项目采用模块化设计哦,主要依赖pygame处理音频播放,FastMCP提供AI交互接口,整体架构非常清晰呢,易于扩展和维护的啦。

技术架构解析

1. 核心组件

项目主要包含以下几个关键组件哦:

import os.pathimport requestsimport reimport jsonimport pygameimport threadingimport queueimport random

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. 使用示例

JJ的歌真好听。

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

FastMCP 智能音乐播放器 Python AI交互
相关文章