如何在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




