You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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仅支持读取部分基础元数据,如需完整解析(包括封面),可选择以下两种方式:

  1. 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读取专辑名等其他字段
}
  1. 集成第三方轻量库:推荐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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 19:05:36