基于Juce Framework开发音频播放器:M3U读写与MP3标签显示咨询
基于Juce的M3U播放列表与MP3标签处理方案
一、M3U文件读写实现
你看到的VLC生成的是**扩展M3U(EXTM3U)**格式,这是当前主流播放器通用的标准,但并非唯一形式——还有仅包含文件路径的基础M3U格式,不过EXTM3U因能携带元信息,是更实用的选择。
1. 读取M3U文件
直接用Juce的文件IO API配合字符串处理即可实现,无需额外库:
- 先读取文件所有内容并按换行分割成行数组
- 逐行遍历处理:
- 跳过空行和非标准注释行(以
#开头但不是#EXTM3U或#EXTINF的行) - 遇到
#EXTM3U标记,确认是扩展格式,直接跳过 - 遇到
#EXTINF:开头的行,解析冒号后逗号前的时长数值,以及逗号后的元信息(如艺术家+歌曲名)并暂存 - 遇到普通路径行,将暂存的元信息与路径绑定,存入自定义的播放列表数据结构
- 跳过空行和非标准注释行(以
示例代码片段:
juce::File m3uFile("path/to/playlist.m3u"); juce::StringArray lines = m3uFile.readAllLines(); // 自定义结构体存储播放列表条目信息 struct PlaylistItem { juce::String filePath; int duration; juce::String title; }; juce::Array<PlaylistItem> playlist; juce::String currentTitle; int currentDuration = -1; for (const auto& line : lines) { juce::String trimmedLine = line.trim(); if (trimmedLine.isEmpty()) continue; if (trimmedLine.startsWith("#EXTM3U")) { continue; } else if (trimmedLine.startsWith("#EXTINF:")) { auto parts = trimmedLine.fromFirstOccurrenceOf(":", false, false).splitIntoCommas(); if (parts.size() >= 2) { currentDuration = parts[0].getIntValue(); currentTitle = parts[1].trim(); } } else { juce::File audioFile = m3uFile.getSiblingFile(trimmedLine); if (audioFile.existsAsFile()) { playlist.add({audioFile.getFullPathName(), currentDuration, currentTitle}); } // 重置临时变量 currentTitle = ""; currentDuration = -1; } }
2. 写入M3U文件
按EXTM3U格式拼接内容,用Juce的文件IO写入即可:
- 开头固定写入
#EXTM3U标记 - 遍历播放列表条目:
- 先写
#EXTINF:行,格式为#EXTINF:{时长},{艺术家 - 歌曲名},未知时长填-1即可 - 再写入音频文件的相对路径(相对于M3U文件所在目录),保证播放列表的可移植性
- 先写
示例代码片段:
juce::File outputM3U("path/to/save/playlist.m3u"); juce::String m3uContent; m3uContent += "#EXTM3U\n"; for (const auto& item : playlist) { m3uContent += juce::String::formatted("#EXTINF:%d,%s\n", item.duration, item.title); // 生成相对路径 juce::File m3uParent = outputM3U.getParentDirectory(); juce::String relativePath = m3uParent.getRelativePathFrom(item.filePath, juce::File::relativePathStyle::unix); m3uContent += relativePath + "\n"; } outputM3U.replaceWithText(m3uContent);
二、MP3标签解析(歌曲名、艺术家、封面)
Juce内置API仅支持读取部分基础元数据,如需完整解析(包括封面),可选择以下两种方式:
- Juce原生AudioFormatReader:快速读取基础元数据,但不支持封面
juce::File mp3File("path/to/music.mp3"); std::unique_ptr<juce::AudioFormatReader> reader = juce::AudioFormatManager().createReaderFor(mp3File); if (reader) { juce::String title = reader->metadata.getValue("title"); juce::String artist = reader->metadata.getValue("artist"); // 可通过对应key读取专辑名等其他字段 }
- 集成第三方轻量库:推荐
TagLib,兼容ID3v1、ID3v2全版本,可读取封面等完整标签信息,只需引入头文件和编译库即可:
TagLib::FileRef file("path/to/music.mp3"); if (!file.isNull()) { TagLib::Tag* tag = file.tag(); juce::String title = juce::String(tag->title().toCString()); juce::String artist = juce::String(tag->artist().toCString()); // 解析封面 if (TagLib::MPEG::File* mp3File = dynamic_cast<TagLib::MPEG::File*>(file.file())) { TagLib::ID3v2::Tag* id3v2Tag = mp3File->ID3v2Tag(); if (id3v2Tag) { TagLib::ID3v2::FrameList frames = id3v2Tag->frameList("APIC"); if (!frames.isEmpty()) { TagLib::ID3v2::AttachedPictureFrame* picFrame = static_cast<TagLib::ID3v2::AttachedPictureFrame*>(frames.front()); // 转换封面数据为Juce Image juce::MemoryBlock coverData(picFrame->picture().data(), picFrame->picture().size()); juce::Image coverImage = juce::ImageFileFormat::loadFrom(coverData); } } } }
三、关键开发建议
- 固定使用EXTM3U格式:基础M3U仅能存储路径,无法携带元信息,EXTM3U是行业通用标准,兼容性拉满
- 优先存储相对路径:保存播放列表时用相对于M3U文件的路径,避免因文件移动导致播放列表失效
- 做好异常处理:读写M3U时检查文件权限、存在性;解析标签时处理文件损坏、无标签的边界情况
- 兼容多ID3版本:MP3可能使用ID3v1、ID3v2.3或ID3v2.4,第三方库能更好地兼容这些版本
内容的提问来源于stack exchange,提问作者yetkin955
相关产品推荐
相关产品推荐

