iOS 7 SDK不支持后台音频播放

24

我已经进行了大量的研究,既在Google上也在StackOverflow上。我找到的所有答案都无法在iOS 7中起作用。我开始使用Xcode 5在iOS 7 SDK中编写全新的应用程序。

我想做的就是从应用程序包中的文件播放音频(而不是来自音乐库)。我希望在后台播放音频并在屏幕锁定时进行控制(除了控制中心之外)。

我将APPNAME-Info.plistUIBackgroundModes设置为音频。一切都在ViewController内完成,而不是在应用程序委托中处理。

@interface ViewController : UIViewController <AVAudioPlayerDelegate>

在实现中的viewDidAppear:方法中,我调用了super,然后是以下代码:

// Once the view has loaded then we can register to begin receiving controls and we can become the first responder
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];

在我的实现中的viewWillDisappear:方法中,我有以下代码:

// End receiving events
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];

我还实现了canBecomeFirstResponder方法,返回值为YES。接下来,我实现了remoteControlReceivedWithEvent:方法:

- (void)remoteControlReceivedWithEvent:(UIEvent *)event {
    // If it is a remote control event handle it correctly
    if (event.type == UIEventTypeRemoteControl) {
        if (event.subtype == UIEventSubtypeRemoteControlPlay) {
            [self playPauseAudio:self];
        } else if (event.subtype == UIEventSubtypeRemoteControlPause) {
            [self playPauseAudio:self];
        } else if (event.subtype == UIEventSubtypeRemoteControlTogglePlayPause) {
            [self playPauseAudio:self];
        }
    }
}

让我感到困惑的是,这个完全相同的设置在iOS 6上运作良好。但在iOS 7上却不行。在iOS 6中,它曾经是如此简单。在iOS 7 SDK中有些根本性的改变。我错过了什么?

5个回答

16
我已经解决了这个问题,并且为了避免其他可怜的灵魂也头发揪出来,以下是解决方法:
首先,请确保你的 Info.plist 文件正确地将音频列为后台模式。
(如果你不知道我在说什么,请选择 YOURAPPNAME-Info.plist,然后点击加号添加一个名为 UIBackgroundModes 的新键,并展开它。添加一个名为 audio 的值。)
你需要引用创建音频的任何播放对象。由于我只播放音频而 AVplayer 不支持后台音频,因此请在你的视图控制器头文件中使用以下代码:
@property (nonatomic, retain) MPMoviePlayerController *audioPlayer;

在实施过程中,请按以下步骤执行:

 [super viewDidAppear:animated];

    //Once the view has loaded then we can register to begin recieving controls and we can become the first responder
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    [self becomeFirstResponder];

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];

    //End recieving events
    [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
    [self resignFirstResponder];

添加两个方法

//Make sure we can recieve remote control events
- (BOOL)canBecomeFirstResponder {
    return YES;
}

- (void) registerForAudioObjectNotifications {

    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];

    [notificationCenter addObserver: self
                           selector: @selector (handlePlaybackStateChanged:)
                               name: MixerHostAudioObjectPlaybackStateDidChangeNotification
                             object: audioObject];
}

现在,所有重要的代码 - 这将使您的应用程序能够从“控制中心”和锁定屏幕控制音频:

- (void) remoteControlReceivedWithEvent: (UIEvent *) receivedEvent {

    if (receivedEvent.type == UIEventTypeRemoteControl) {

        switch (receivedEvent.subtype) {

            case UIEventSubtypeRemoteControlTogglePlayPause:
                [self playOrStop: nil];
                break;

            default:
                break;
        }
    }
}

在这里,您可以添加许多不同类型的事件,并调用任何方法。

典型的事件包括:

 UIEventSubtypeRemoteControlPlay                 = 100,  //Parent EVENT

// All below are sub events and you can catch them using switch or If /else.
    UIEventSubtypeRemoteControlPause                = 101,
    UIEventSubtypeRemoteControlStop                 = 102,
    UIEventSubtypeRemoteControlTogglePlayPause      = 103,
    UIEventSubtypeRemoteControlNextTrack            = 104,
    UIEventSubtypeRemoteControlPreviousTrack        = 105,
    UIEventSubtypeRemoteControlBeginSeekingBackward = 106,
    UIEventSubtypeRemoteControlEndSeekingBackward   = 107,
    UIEventSubtypeRemoteControlBeginSeekingForward  = 108,
    UIEventSubtypeRemoteControlEndSeekingForward    = 109,

为了帮助调试,您可以使用:

 MPMoviePlayerController *mp1= (MPMoviePlayerController *)[notification object];
    NSLog(@"Movie State is: %d",[mp1 playbackState]);

    switch ([mp1 playbackState]) {
        case 0:
            NSLog(@"******* video has stopped");
            break;
        case 1:
            NSLog(@"******* video is playing after being paused or moved.");
                       break;
        case 2:
            NSLog(@"******* video is paused");
                break;
        case 3:
            NSLog(@"******* video was interrupted");
            break;
        case 4:
            NSLog(@"******* video is seeking forward");
                     break;
        case 5:
            NSLog(@"******* video is seeking Backwards");
            break;
        default:
            break;

就是这样 - 希望能对某些人有所帮助! - 在iOS 7和iOS 6上通过Storyboard应用程序以及使用耳机和所有新的控制中心进行控制都可以完美运行。


1
很遗憾,从控制中心仍然无法为我工作。这是我在iOS6上成功使用的完全相同的代码,但在iOS7上出了问题。实际上,我还看到其他应用程序(如StereoMood和8Tracks)也有同样的问题。 - super
@codejunkie: 我和super一样的经历,iOS 7.0.3还是一样的。我执行了你列出的大多数步骤,唯一不同的是我的响应代码在AppDelegate中,而且我没有音频对象。控制中心根本就不会调用remoteControlReceivedWithEvent:方法。请注意,我的代码适用于所有iOS7之前的情况,并且仍然适用于耳塞遥控。请参见我的问题:http://stackoverflow.com/questions/19686704/how-to-block-ios-7-control-centre-from-controlling-music-app - kakyo
实际上,在我重置了iPhone之后,它又开始工作了。现在在iOS 7.0.3上保持正常运行,没有任何问题。这确实是iOS7中的一些奇怪问题。 - super
1
MixerHostAudioObjectPlaybackStateDidChangeNotification与接收删除事件有什么关系? - ambientlight

7
显然,问题出在苹果方面,因为iOS更新7.0.3修复了这个问题。此外,除了Alex提到的UIEventSubtype变化之外,适用于iOS6的代码现在也适用于iOS7。
为了完整起见,这是我的相关代码,在更新为7.0.3后在iOS6和iOS7上都可以工作。还将AVFoundation.framework和MediaPlayer.framework包含在项目Build Phases->链接二进制文件中。在应用程序委托中没有代码。
在视图控制器.h文件中:
#import <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>

@interface NewsDetailViewController : UIViewController <UIWebViewDelegate, AVAudioSessionDelegate>
@property (nonatomic) MPMoviePlayerController *audioPlayer;

在视图控制器.m文件中:

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.audioPlayer = [[MPMoviePlayerController alloc] initWithContentURL:audioUrl];
    [self.audioPlayer prepareToPlay];
    [self.audioPlayer.view setFrame:CGRectMake(0, 0, self.audioView.frame.size.width,     42)];
    self.audioPlayer.view.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    [self.audioView addSubview:self.audioPlayer.view];
    [self.audioPlayer play];

    NSError *setCategoryError = nil;
    NSError *activationError = nil;
    [[AVAudioSession sharedInstance] setActive:YES error:&activationError];
    [[AVAudioSession sharedInstance] setDelegate:self];
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&setCategoryError];
}

- (BOOL)canBecomeFirstResponder {
    return YES;
}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    [self becomeFirstResponder];
}

- (void)viewWillDisappear:(BOOL)animated {
    [self.audioPlayer stop];
    [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
    [self resignFirstResponder];

    [super viewWillDisappear:animated];
}

- (void)remoteControlReceivedWithEvent:(UIEvent *)receivedEvent {

    if (receivedEvent.type == UIEventTypeRemoteControl) {
        switch (receivedEvent.subtype) {
            case UIEventSubtypeRemoteControlPlay:
                [self.audioPlayer play];
                break;
            case UIEventSubtypeRemoteControlPause:
                [self.audioPlayer pause];
                break;
            case UIEventSubtypeRemoteControlTogglePlayPause:
                if (self.audioPlayer.playbackState == MPMoviePlaybackStatePlaying) {
                    [self.audioPlayer pause];
                }
                else {
                    [self.audioPlayer play];
                }
                break;
            default:
                break;
        }
    }
}

6
如果您想在iPhone和模拟器中播放背景音频,那么您需要在plist中编写此代码,并首先确保您的Info.plist将音频正确列为后台模式。
(如果您不知道我在说什么,请选择YOURAPPNAME-Info.plist选择它。单击加号并键入一个键UIBackgroundModes并输入。添加一个名为“App plays audio”(用于模拟器)或“App plays audio or streams audio / video using AirPlay”(用于iPhone)的值。)
在AppDelegate.m中。
- (void)applicationDidEnterBackground:(UIApplication *)application
{
    __block UIBackgroundTaskIdentifier task = 0;
    task=[application beginBackgroundTaskWithExpirationHandler:^{
    NSLog(@"Expiration handler called %f",[application backgroundTimeRemaining]);
    [application endBackgroundTask:task];
    task=UIBackgroundTaskInvalid;
    }];
}

在您的项目中添加这两个框架,并在ViewController.h文件中加入几行代码。
#import <AVFoundation/AVFoundation.h>
#import <MediaPlayer/MediaPlayer.h>

@interface ViewController : UIViewController <UIWebViewDelegate, AVAudioSessionDelegate>
@property (nonatomic) MPMoviePlayerController *audioPlayer;

提醒您,这些框架引用应在您的项目中添加。
然后,在 Viewcontrller.m 文件中。
- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    [self becomeFirstResponder];
}

- (void)viewWillDisappear:(BOOL)animated {
    [self.audioPlayer stop];
    [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
    [self resignFirstResponder];

    [super viewWillDisappear:animated];
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    NSURL *audioUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"songName" ofType:@"mp3"]];
    self.audioPlayer = [[MPMoviePlayerController alloc] initWithContentURL:audioUrl];
    [self.audioPlayer prepareToPlay];
    [self.audioPlayer.view setFrame:CGRectMake(0, 0, self.view.frame.size.width-100,     42)];
    self.audioPlayer.view.autoresizingMask = UIViewAutoresizingFlexibleWidth;
    [self.view addSubview:self.audioPlayer.view];
    [self.audioPlayer play];


    // for run application in background
    NSError *setCategoryError = nil;
    NSError *activationError = nil;
    [[AVAudioSession sharedInstance] setActive:YES error:&activationError];
    [[AVAudioSession sharedInstance] setDelegate:self];
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&setCategoryError];
}

我希望这篇文章能够帮助你在 iPhone 和模拟器上实现后台音频播放。


修改AppDelegate不是良好的代码实践,我们应该尽量避免使用AppDelegate,因为它依赖于操作系统在应用程序首次加载时控制权被夺取,从而剥夺了应用程序控制器的控制权。更多信息请参见:http://www.hollance.com/2012/02/dont-abuse-the-app-delegate/ - codejunkie
在后台完美运行,那么当视图改变时会怎样呢? - Noor
我已经回到另一个视图,但想播放之前视图中相同的音频。 - Noor

4

需要注意的一件事是,在iOS6和iOS7中,远程控制事件与播放/暂停事件不同。在iOS6中,播放/暂停事件作为一个UIEventSubtype出现:
UIEventSubtypeRemoteControlTogglePlayPause
而在iOS7中,它们分别以两个不同的子类型出现:
UIEventSubtypeRemoteControlPause
UIEventSubtypeRemoteControlPlay


0

由于我也对找到这个解决方案感兴趣,所以我会从我的角度添加更多信息。

我遇到了同样的问题,但是苹果文档关于远程控制事件管理方面还没有改变。

我尝试移动一些东西,发生了一些有趣的事情。

我最初在我的TabBar控制器中使用了远程控制事件管理。现在我将所有内容都移到播放器视图控制器中,我可以再次通过我的耳机控制音乐播放,但无法从iOS7新的控制面板(从屏幕底部出现的面板)中的按钮进行控制。

这相当奇怪。

为了解决这个问题,让我们试着用我们的测试来丰富这个线程。


我目前正在测试并寻找使用MPMPlayerViewController的解决方法,注册其通知对象并获取该状态,一旦所有工作都正常,将进行更新。 - codejunkie
我没有使用MPPlayer,所以我无法注册那些通知。这很疯狂,因为我只是将所有东西移动到AppDelegate中解决了问题,但现在它不再起作用了。 - super

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接