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

Итак, у меня есть приложение, которое играет кучу песен, в то время как пользователь может пролистывать комиксы. Я использую AVAudioPlayer, и я настроил его для воспроизведения песен в заданном порядке. Так что, когда одна песня закончится, заиграет следующая. Это работает, когда приложение открыто. Проблема возникает, когда приложение находится в фоновом режиме. Я настроил приложение для воспроизведения в фоновом режиме, и это отлично работает. Поэтому, когда пользователь нажимает на главный экран, музыка продолжает играть. Проблема возникает, когда песня заканчивается, он должен играть следующую песню, как это делает, когда приложение открыто. Вместо этого ничего не происходит. В соответствии с инструкциями my 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 (player) не вызывается.

также у меня есть правильная настройка в моей информации.plist и у меня есть это в моем viewDidLoad:

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

Спасибо за любую понимание. Ценится.

3 ответов


обсуждения

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

вам нужен этот код в init или методе viewDidLoad вашего первого контроллера вида:

[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

ДЛИННЫЙ ОТВЕТ С ОБРАЗЦОМ:

вот мой пример. Как и вы, я начал с приложения, которое будет воспроизводить музыку в фоновом режиме, но никогда не сможет продолжать играть после окончания первого клипа. Я сделал копию оригинальной музыки.mp3 и назвал его Music2.mp3. Мое намерение было играть Music2.mp3, как только музыка.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 на непрерывном цикле, даже если приложение находится в фоновом режиме.


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

UIApplication.sharedApplication().beginReceivingRemoteControlEvents()

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

эта гибридная функция C++ / 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();
};