Search code examples
iosobjective-caudioavaudioplayer

How to loop a section of a track?


I have a music file that contains an intro (0:00-0:10) and a looping section (0:10-0:30). There are many libraries and simple solutions to loop a track from end->beginning, but I am having trouble finding a way to loop a track from end->loop start. What is the best way to achieve this?

here is the code I am currently using based off the comments below. It still produces a small gap when looping:

_player = [AVPlayer playerWithURL:url];
Float64 durationSeconds = CMTimeGetSeconds([asset duration]);
CMTime loopStart = CMTimeMakeWithSeconds(19.2, 1);
CMTime loopEnd = CMTimeMakeWithSeconds(durationSeconds, 1);
NSArray *times = @[[NSValue valueWithCMTime:loopEnd]];

__weak typeof(AVPlayer *)weakPlayer = _player;
self.playerObserver = [_player addBoundaryTimeObserverForTimes:times queue:NULL usingBlock:^{
    [weakPlayer seekToTime:loopStart];
}];

[_player play];

Solution

  • In the end, ObjectAL had a demo project that uses two separate audio files to play and intro and then loop a main body of a song without a gap in playback. I've modified it to work with a single audio file:

    +(ALBuffer *)bufferFromFile:(NSString *)filePath startTime:(float)startTime endTime:(float)endTime
    {
        NSURL *url = [OALTools urlForPath:filePath];
        OALAudioFile *file = [[OALAudioFile alloc] initWithUrl:url reduceToMono:NO];
    
        // Convert the start and end times into frames
        NSInteger startFrame = startTime*file.streamDescription->mSampleRate;
        NSInteger endFrame = (endTime-startTime)*file.streamDescription->mSampleRate;///file.totalFrames-(endTime*file.streamDescription->mSampleRate);
    
        ALBuffer* buffer = [file bufferNamed:[url description]
                                  startFrame:startFrame
                                   numFrames:endFrame];
        //as_release(file); // I've commented this out, but I suspect there may be some kind of leak here. Can anyone comment on how to properly release this memory?
        return buffer;
    }
    
    -(void)playBackgroundMusic:(NSString *)file withIntroThatLoopsAt:(CGFloat)seconds
    {
        // Uses OpenAL for both the intro and the main loop.
        // Uses a callback to start the main loop playing.
        // Note: This only works on iOS 5+.
    
        _source = [ALSource source];
        _introBuffer= [JAudioEngine bufferFromFile:file startTime:0 endTime:seconds];
        _mainBuffer = [JAudioEngine bufferFromFile:file startTime:seconds endTime:-1];
    
        [self stop];
    
        __block typeof(self) blockSelf = self;
        [_source registerNotification:AL_BUFFERS_PROCESSED
                                 callback:^(__unused ALSource *source, __unused ALuint notificationID, __unused ALvoid *userData)
                                 {
                                     [blockSelf.source play:blockSelf.mainBuffer loop:YES];
                                     [blockSelf.source unregisterAllNotifications];
                                 }
                                 userData:nil];
    
        [_source play:_introBuffer];
    }
    
    -(void)stop
    {
        [_source unregisterAllNotifications];
        [_source stop];
    }