IOS - AVAudioPlayer не продолжает следующую песню в фоновом режиме

Итак, у меня есть приложение, которое воспроизводит кучу песен, в то время как пользователь может перелистывать комикс. Я использую AVAudioPlayer, и я настроил его для воспроизведения песен в установленном порядке. Поэтому, когда одна песня заканчивается, следующий будет воспроизводиться. Это работает безупречно, когда приложение открыто. Проблема возникает, когда приложение находится в фоновом режиме. Я настроил приложение, чтобы играть в фоновом режиме, и это работает нормально. Поэтому, когда пользователь нажимает на главный экран, музыка продолжает воспроизводиться. Проблема возникает, когда песня заканчивается. Предполагается, что она будет воспроизводить следующую песню, как при открытии приложения. Вместо этого ничего не происходит. Согласно моим заявлениям NSLog, вызываются правильные методы, но ничего не происходит. Вот мой код:

- (void)audioPlayerDidFinishPlaying: (AVAudioPlayer *)player successfully: (BOOL) flag {

NSLog(@"Song finished");

if ([songSelect isEqualToString: @"01icecapades"]) {
    isPlay = @"yes";
    songSelect = @"02sugarcube";
    imageSelect = @"playbanner02";
    [self performSelector:@selector(triggerSong) withObject:nil afterDelay:0];
    [self performSelector:@selector(triggerBanner) withObject:nil afterDelay:0];
}
else if ([songSelect isEqualToString: @"02sugarcube"]) {
    isPlay = @"yes";
    songSelect = @"03bullets";
    imageSelect = @"playbanner03";
    [self performSelector:@selector(triggerSong) withObject:nil afterDelay:0];
    [self performSelector:@selector(triggerBanner) withObject:nil afterDelay:0];
}
else if ([songSelect isEqualToString: @"03bullets"]) {
    isPlay = @"yes";
    songSelect = @"04satanama";
    imageSelect = @"playbanner04";
    [self performSelector:@selector(triggerSong) withObject:nil afterDelay:0];
    [self performSelector:@selector(triggerBanner) withObject:nil afterDelay:0];
}
else if ([songSelect isEqualToString: @"04satanama"]) {
    isPlay = @"yes";
    songSelect = @"05uglyjoke";
    imageSelect = @"playbanner05";
    [self performSelector:@selector(triggerSong) withObject:nil afterDelay:0];
    [self performSelector:@selector(triggerBanner) withObject:nil afterDelay:0];
}
else if ([songSelect isEqualToString: @"05uglyjoke"]) {
    isPlay = @"yes"; 
    songSelect = @"01icecapades";
    imageSelect = @"playbanner01";
    [self performSelector:@selector(triggerSong) withObject:nil afterDelay:0];
    [self performSelector:@selector(triggerBanner) withObject:nil afterDelay:0];
}}

Выше - это код, который распознает, какая песня играет, и затем устанавливает правильную песню. Затем он запускает другой метод, который устанавливает плеер.

- (void)triggerSong {
NSLog(@"triggerSong called");
NSString       *path;
NSError        *error;
// Path the audio file
path = [[NSBundle mainBundle] pathForResource:songSelect ofType:@"mp3"];
// If we can access the file...
if ([[NSFileManager defaultManager] fileExistsAtPath:path]) 
{    
    // Setup the player
    player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];
    //player = [initWithContentsOfURL:[NSURL fileURLWithPath:path] error:&error];
    [player setDelegate: self];
    // Set the volume (range is 0 to 1)
    player.volume = 1.0f;     
    [player prepareToPlay];
    [player setNumberOfLoops:0];
    [player play];
    NSLog(@"player play");
    [error release];
    player.delegate = self;
    // schedules an action every second for countdown
    [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateTimeLeft) userInfo:nil repeats:YES];
}}

Теперь я предполагаю, что это не лучший способ сделать это, но он отлично работает, когда приложение находится в состоянии переднего плана. Я просматривал документацию, и я не могу найти причину этой проблемы. Я надеялся, что кто-то сможет увидеть ошибку в моем подходе. Как я уже говорил, два NSLogs в методе triggerSong вызываются, поэтому я не вижу, почему AVAudioPlayer (плеер) не вызывается.

Также у меня есть правильная настройка в моем info.plist, и у меня есть это в моем представленииDidLoad:

//Make sure the system follows our playback status
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];

Спасибо за понимание. Очень признателен.

Ответ 1

Соответствующее обсуждение

КРАТКИЙ ОТВЕТ:

Этот код необходим для вашего первого элемента управления представлением или метода viewDidLoad:

[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

ДОЛГОЙ ОТВЕТ W/SAMPLE:

Вот мой пример. Как и вы, я начал с приложения, которое будет воспроизводить музыку в фоновом режиме, но никогда не сможет продолжить воспроизведение после завершения первого клипа. Я сделал копию оригинального Music.mp3 и назвал его Music2.mp3. Мое намерение состояло в том, чтобы воспроизвести Music2.mp3, как только Music.mp3 закончил (audioPlayerDidFinishPlaying:). Я некоторое время сталкивался с фоновыми задачами, пока не получил эту работу БЕЗ фоновой задачи:

-(id)init{
    self = [super initWithNibName:@"MediaPlayerViewController" bundle:nil];
    if(self){

        //Need this to play background playlist
        [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

        //MUSIC CLIP
        //Sets up the first song...
        NSString *musicPath = [[NSBundle mainBundle] pathForResource:@"Music" ofType:@"mp3"];
        if(musicPath){
            NSURL *musicURL = [NSURL fileURLWithPath:musicPath];
            audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:musicURL error:nil]; 
            [audioPlayer setDelegate:self];
        }
    }
    return self;
}


-(IBAction)playAudioFile:(id)sender{

    if([audioPlayer isPlaying]){
        //Stop playing audio and change test of button
        [audioPlayer stop];
        [sender setTitle:@"Play Audio File" forState:UIControlStateNormal];
    }
    else{
        //Start playing audio and change text of button so
        //user can tap to stop playback
        [audioPlayer play];
        [sender setTitle:@"Stop Audio File" forState:UIControlStateNormal];
    }
}


-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
    [audioButton setTitle:@"Play Audio File" forState:UIControlStateNormal];
    [playRecordingButton setTitle:@"Play Rec File" forState:UIControlStateNormal];

    //PLAY THE SECOND SONG
    NSString *musicPath2 = [[NSBundle mainBundle] pathForResource:@"Music2" ofType:@"mp3"];
    if(musicPath2){

        NSURL *musicURL2 = [NSURL fileURLWithPath:musicPath2];
        audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:musicURL2 error:nil];
        [audioPlayer setDelegate:self];
        NSLog(@"Play it again: \n%@", musicPath2);
        [audioPlayer play];
    } 
}

Конечным результатом является то, что мое приложение теперь воспроизводит Music2.mp3 в непрерывном цикле, даже если приложение находится в фоновом режиме.

Ответ 2

Просто чтобы подтвердить, что сказал Squatch, это также решение в Swift:

UIApplication.sharedApplication().beginReceivingRemoteControlEvents()

Ответ 3

OS X демонстрирует ту же проблему с использованием AVAudioPlayer, однако UIApplication - это только iOS-конструкция. OS X требует использования NSApplication, но NSApplication не возвращается, пока приложение не завершится, поэтому нам нужно использовать потоки. В качестве бонуса существует assert() где-то в глубинах NSApplication, требующий основного потока.

Эта гибридная функция С++/Objective C является одним из способов обхода этой проблемы с OS X:

void do_the_dumb (void real_application(void)) {
    std::thread thread ([real_application]() {
        real_application();
        [[NSApplication sharedApplication] terminate: [NSApplication sharedApplication]];
    });
    [[NSApplication sharedApplication] run];
    thread.join();
};