You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在OS X系统下通过MusicPlayer判断MIDI文件播放完毕

嘿,我来帮你搞定这两个问题——播放应用资源里的MIDI文件,以及检测它何时播放完毕!

播放应用资源中的MIDI文件

你的代码思路是对的,但有几个地方需要完善,比如正确获取应用内资源的路径,还有清理旧的播放器/序列避免内存泄漏。这里是补全并优化后的代码:

MusicPlayer musicPlayer = NULL;
MusicSequence sequence = NULL;
int MusicPlaying = 0;
char TmpPath[256]; // 假设你已经定义了这个字符数组

void PlayMusic(char *fname) {
    OSStatus res = noErr;

    // 先清理之前的播放器和序列,防止内存泄漏
    if (musicPlayer != NULL) {
        DisposeMusicPlayer(musicPlayer);
        musicPlayer = NULL;
    }
    if (sequence != NULL) {
        DisposeMusicSequence(sequence);
        sequence = NULL;
    }

    // 创建新的播放器和序列
    res = NewMusicPlayer(&musicPlayer);
    if (res != noErr) {
        // 这里可以添加错误处理,比如打印错误码
        return;
    }

    res = NewMusicSequence(&sequence);
    if (res != noErr) {
        // 错误处理
        return;
    }

    // 获取应用内MUSIC目录下的目标MIDI文件路径
    NSString *resourceName = [NSString stringWithUTF8String:fname];
    NSString *midiPath = [[NSBundle mainBundle] pathForResource:resourceName 
                                                        ofType:@"mid" 
                                                   inDirectory:@"MUSIC"];

    if (!midiPath) {
        // 文件不存在,处理错误逻辑
        NSLog(@"找不到指定的MIDI文件:%s", fname);
        return;
    }

    // 将路径转为CFURLRef,加载到序列中
    CFURLRef midiURL = (__bridge CFURLRef)[NSURL fileURLWithPath:midiPath];
    res = MusicSequenceFileLoad(sequence, midiURL, 0, 0);
    if (res != noErr) {
        // 错误处理
        return;
    }

    // 将序列关联到播放器,准备并开始播放
    res = MusicPlayerSetSequence(musicPlayer, sequence);
    res = MusicPlayerPreroll(musicPlayer);
    res = MusicPlayerStart(musicPlayer);

    if (res == noErr) {
        MusicPlaying = 1;
    } else {
        // 播放启动失败,处理错误
        MusicPlaying = 0;
    }
}
判断MIDI播放完毕的方法

有两种可靠的方式来检测播放状态,推荐第一种监听属性的方式,因为它能准确响应播放中途暂停、结束等所有状态变化:

方法1:监听MusicPlayer的播放状态属性

通过MusicPlayerAddPropertyListener监听kMusicPlayerProperty_IsPlaying属性,当播放状态从YES变为NO时,就说明播放完成了:

// 定义状态变化的回调函数
static void MusicPlayerStatusChanged(void *clientData, MusicPlayer player, 
                                     const MusicPropertyID propertyID, 
                                     const MusicTimeStamp timestamp) {
    if (propertyID == kMusicPlayerProperty_IsPlaying) {
        Boolean isPlaying;
        UInt32 size = sizeof(isPlaying);
        OSStatus res = MusicPlayerGetProperty(player, kMusicPlayerProperty_IsPlaying, 
                                             &isPlaying, &size);
        
        if (res == noErr && !isPlaying) {
            // 播放完毕,这里写你的处理逻辑
            MusicPlaying = 0;
            NSLog(@"MIDI播放结束啦!");
        }
    }
}

// 在初始化播放器后添加监听(比如在PlayMusic里或者应用启动时)
void SetupMusicStatusListener() {
    if (musicPlayer == NULL) return;
    OSStatus res = MusicPlayerAddPropertyListener(musicPlayer, 
                                                 kMusicPlayerProperty_IsPlaying, 
                                                 MusicPlayerStatusChanged, 
                                                 NULL);
    if (res != noErr) {
        // 监听添加失败,处理错误
    }
}

注意:在不需要播放器的时候,记得调用MusicPlayerRemovePropertyListener移除监听,避免野指针问题。

方法2:计算序列时长,用定时器检查

如果你不需要监听中途的状态变化,只是想知道播放结束的大致时间,可以先计算MIDI序列的总时长,然后设置一个定时器来检查:

// 获取MIDI序列的总时长(秒)
Float64 GetMidiSequenceTotalDuration(MusicSequence sequence) {
    MusicTrack tempoTrack;
    OSStatus res = MusicSequenceGetTempoTrack(sequence, &tempoTrack);
    if (res != noErr) return 0.0;

    MusicTimeStamp totalBeats;
    UInt32 size = sizeof(totalBeats);
    res = MusicTrackGetProperty(tempoTrack, kSequenceTrackProperty_TrackLength, 
                               &totalBeats, &size);
    if (res != noErr) return 0.0;

    Float64 durationInSeconds;
    res = MusicSequenceGetSecondsForBeats(sequence, totalBeats, &durationInSeconds);
    return durationInSeconds;
}

// 在PlayMusic里调用,设置定时器
void PlayMusic(char *fname) {
    // ... 前面的播放代码 ...

    Float64 totalDuration = GetMidiSequenceTotalDuration(sequence);
    if (totalDuration > 0) {
        // 延迟总时长后检查播放状态
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 
                                    (int64_t)(totalDuration * NSEC_PER_SEC)), 
                      dispatch_get_main_queue(), ^{
            Boolean isPlaying;
            UInt32 size = sizeof(isPlaying);
            OSStatus res = MusicPlayerGetProperty(musicPlayer, kMusicPlayerProperty_IsPlaying, 
                                                 &isPlaying, &size);
            if (res == noErr && !isPlaying) {
                MusicPlaying = 0;
                NSLog(@"MIDI播放完成!");
            }
        });
    }
}

这种方式适合简单场景,但如果MIDI有循环或者中途被暂停,就不够准确了,所以优先推荐第一种监听属性的方法。

内容的提问来源于stack exchange,提问作者Everett Kaser

火山引擎 最新活动