iOS音频播放 (八):NowPlayingCenter和RemoteControl

转载自:iOS音频播放 (八):NowPlayingCenter和RemoteControl


NowPlayingCenter

NowPlayingCenter能够显示当前正在播放的歌曲信息,它可以控制的范围包括:

  • 锁频界面上所显示的歌曲播放信息和图片
  • iOS7之后控制中心上显示的歌曲播放信息
  • iOS7之前双击home键后出现的进程中向左滑动出现的歌曲播放信息
  • AppleTV,AirPlay中显示的播放信息
  • 车载系统中显示的播放信息

这些信息的显示都由MPNowPlayingInfoCenter类来控制,这个类的定义非常简单:

1
2
3
4
5
6
7
8
9
10
11
MP_EXTERN_CLASS_AVAILABLE(5_0) @interface MPNowPlayingInfoCenter : NSObject  // Returns the default now playing info center. // The default center holds now playing info about the current application. + (MPNowPlayingInfoCenter *)defaultCenter;  // The current now playing info for the center. // Setting the info to nil will clear it. @property (copy) NSDictionary *nowPlayingInfo;  @end 

使用也同样简单,首先#import <MediaPlayer/MPNowPlayingInfoCenter.h>然后调用MPNowPlayingInfoCenter的单例方法获取实例,再把需要显示的信息组织成Dictionary并赋值给nowPlayingInfo属性就完成了。

nowPlayingInfo中一些常用属性被定义在<MediaPlayer/MPMediaItem.h>

1
2
3
4
5
6
7
8
9
10
11
12
MPMediaItemPropertyAlbumTitle //NSString MPMediaItemPropertyAlbumTrackCount //NSNumber of NSUInteger MPMediaItemPropertyAlbumTrackNumber //NSNumber of NSUInteger MPMediaItemPropertyArtist //NSString MPMediaItemPropertyArtwork //MPMediaItemArtwork MPMediaItemPropertyComposer //NSString MPMediaItemPropertyDiscCount //NSNumber of NSUInteger MPMediaItemPropertyDiscNumber //NSNumber of NSUInteger MPMediaItemPropertyGenre //NSString MPMediaItemPropertyPersistentID //NSNumber of uint64_t MPMediaItemPropertyPlaybackDuration //NSNumber of NSTimeInterval MPMediaItemPropertyTitle //NSString 

上面这些属性大多比较浅显易懂,基本上按照字面上的意思去理解就可以了,需要稍微解释以下的是MPMediaItemPropertyArtwork。这个属性表示的是锁屏界面或者AirPlay中显示的歌曲封面图,MPMediaItemArtwork类可以由UIImage类进行初始化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MP_EXTERN_CLASS_AVAILABLE(3_0) @interface MPMediaItemArtwork : NSObject  // Initializes an MPMediaItemArtwork instance with the given full-size image. // The crop rect of the image is assumed to be equal to the bounds of the  // image as defined by the image's size in points, i.e. tightly cropped. - (instancetype)initWithImage:(UIImage *)image NS_DESIGNATED_INITIALIZER NS_AVAILABLE_IOS(5_0);  // Returns the artwork image for an item at a given size (in points). - (UIImage *)imageWithSize:(CGSize)size;  @property (nonatomic, readonly) CGRect bounds; // The bounds of the full size image (in points). @property (nonatomic, readonly) CGRect imageCropRect; // The actual content area of the artwork, in the bounds of the full size image (in points).  @end 

另外一些附加属性被定义在<MediaPlayer/MPNowPlayingInfoCenter.h>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// The elapsed time of the now playing item, in seconds. // Note the elapsed time will be automatically extrapolated from the previously  // provided elapsed time and playback rate, so updating this property frequently // is not required (or recommended.) MP_EXTERN NSString *const MPNowPlayingInfoPropertyElapsedPlaybackTime NS_AVAILABLE_IOS(5_0); // NSNumber (double)  // The playback rate of the now playing item, with 1.0 representing normal  // playback. For example, 2.0 would represent playback at twice the normal rate. // If not specified, assumed to be 1.0. MP_EXTERN NSString *const MPNowPlayingInfoPropertyPlaybackRate NS_AVAILABLE_IOS(5_0); // NSNumber (double)  // The "default" playback rate of the now playing item. You should set this // property if your app is playing a media item at a rate other than 1.0 in a // default playback state. e.g., if you are playing back content at a rate of // 2.0 and your playback state is not fast-forwarding, then the default // playback rate should also be 2.0. Conversely, if you are playing back content // at a normal rate (1.0) but the user is fast-forwarding your content at a rate // greater than 1.0, then the default playback rate should be set to 1.0. MP_EXTERN NSString *const MPNowPlayingInfoPropertyDefaultPlaybackRate NS_AVAILABLE_IOS(8_0); // NSNumber (double)  // The index of the now playing item in the application's playback queue. // Note that the queue uses zero-based indexing, so the index of the first item  // would be 0 if the item should be displayed as "item 1 of 10". MP_EXTERN NSString *const MPNowPlayingInfoPropertyPlaybackQueueIndex NS_AVAILABLE_IOS(5_0); // NSNumber (NSUInteger)  // The total number of items in the application's playback queue. MP_EXTERN NSString *const MPNowPlayingInfoPropertyPlaybackQueueCount NS_AVAILABLE_IOS(5_0); // NSNumber (NSUInteger)  // The chapter currently being played. Note that this is zero-based. MP_EXTERN NSString *const MPNowPlayingInfoPropertyChapterNumber NS_AVAILABLE_IOS(5_0); // NSNumber (NSUInteger)  // The total number of chapters in the now playing item. MP_EXTERN NSString *const MPNowPlayingInfoPropertyChapterCount NS_AVAILABLE_IOS(5_0); // NSNumber (NSUInteger) 

其中常用的是MPNowPlayingInfoPropertyElapsedPlaybackTimeMPNowPlayingInfoPropertyPlaybackRate

  • MPNowPlayingInfoPropertyElapsedPlaybackTime表示已经播放的时间,用这个属性可以让NowPlayingCenter显示播放进度;
  • MPNowPlayingInfoPropertyPlaybackRate表示播放速率。通常情况下播放速率为1.0,即真是时间的1秒对应播放时间中的1秒;

这里需要解释的是,NowPlayingCenter中的进度刷新并不是由app不停的更新nowPlayingInfo来做的,而是根据app传入的ElapsedPlaybackTimePlaybackRate进行自动刷新。例如传入ElapsedPlaybackTime=120s,PlaybackRate=1.0,那么NowPlayingCenter会显示2:00并且在接下来的时间中每一秒把进度加1秒并刷新显示。如果需要暂停进度,传入PlaybackRate=0.0即可。

所以每次播放暂停和继续都需要更新NowPlayingCenter并正确设置ElapsedPlaybackTimePlaybackRate否则NowPlayingCenter中的播放进度无法正常显示。

NowPlayingCenter的刷新时机

频繁的刷新NowPlayingCenter并不可取,特别是在有Artwork的情况下。所以需要在合适的时候进行刷新。

依照我自己的经验下面几个情况下刷新NowPlayingCenter比较合适:

  • 当前播放歌曲进度被拖动时
  • 当前播放的歌曲变化时
  • 播放暂停或者恢复时
  • 当前播放歌曲的信息发生变化时(例如Artwork,duration等)

在刷新时可以适当的通过判断app是否active来决定是否必须刷新以减少刷新次数。

MPMediaItemPropertyArtwork

这是一个非常有用的属性,我们可以利用歌曲的封面图来合成一些图片借此达到美化锁屏界面或者显示锁屏歌词。

RemoteControl

RemoteComtrol可以用来在不打开app的情况下控制app中的多媒体播放行为,涉及的内容主要包括:

  • 锁屏界面双击Home键后出现的播放操作区域
  • iOS7之后控制中心的播放操作区域
  • iOS7之前双击home键后出现的进程中向左滑动出现的播放操作区域
  • AppleTV,AirPlay中显示的播放操作区域
  • 耳机线控
  • 车载系统的设置

在何处处理RemoteComtrol

根据官方文档的描述:

If your app plays audio or video content, you might want it to respond to remote control events that originate from either transport controls or external accessories. (External accessories must conform to Apple-provided specifications.) iOS converts commands into UIEvent objects and delivers the events to an app. The app sends them to the first responder and, if the first responder doesn’t handle them, they travel up the responder chain.

RemoteComtrol事件产生时,iOS会以UIEvent的形式发送给app,app会首先转发到first responder,如果first responder不处理这个事件的话那么事件就会沿着responder chain继续转发。关于responder chain的相关内容可以查看这里。

从responder chain文档看来如果之前的所有responder全部不响应RemoteComtrol事件的话,最终事件会被转发给Application(如图)。所以我们知道作为responder chain的最末端,在UIApplication中实现RemoteComtrol的处理是最为合理的,而并非在UIWindow中或者AppDelegate中。

iOS音频播放 (八):NowPlayingCenter和RemoteControl_第1张图片

实现自己的UIApplication

首先新建一个UIApplication的子类

1
2
3
4
5
#import <UIKit/UIKit.h>  @interface MyApplication : UIApplication  @end 

然后找到工程中的main.m,可以看到代码如下:

1
2
3
4
5
6
int main(int argc, char * argv[]) {  @autoreleasepool {  return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));  } } 

在main中调用了UIApplicationMain方法

1
2
3
// If nil is specified for principalClassName, the value for NSPrincipalClass from the Info.plist is used. If there is no // NSPrincipalClass key specified, the UIApplication class is used. The delegate class will be instantiated using init. UIKIT_EXTERN int UIApplicationMain(int argc, char *argv[], NSString *principalClassName, NSString *delegateClassName); 

我们需要做的就是给UIApplicationMain方法的第三个参数传入我们的application类名,如下:

1
2
3
4
5
6
7
8
9
10
#import <UIKit/UIKit.h> #import "AppDelegate.h" #import "MyApplication.h"  int main(int argc, char * argv[]) {  @autoreleasepool {  return UIApplicationMain(argc, argv, NSStringFromClass([MyApplication class]), NSStringFromClass([AppDelegate class]));  } } 

这样就成功实现了自己的UIApplication.

处理RemoteComtrol

了解了应该在何处处理RemoteComtrol事件之后,再来看下官方文档中描述的三个必要条件:

  • 接受者必须能够成为first responder
  • 必须显示地声明接收RemoteComtrol事件
  • 你的app必须是Now Playingapp

对于第一条就是要在自己的UIApplication中实现canBecomeFirstResponder方法:

1
2
3
4
5
6
7
8
9
10
#import "MyApplication.h"  @implementation MyApplication  - (BOOL)canBecomeFirstResponder {  return YES; }  @end 

第二条是要求显示地调用[[UIApplication sharedApplication] beginReceivingRemoteControlEvents],调用的实际一般是在播放开始时;

第三条就是要求占据NowPlayingCenter,这个之前已经提到过了。

满足三个条件后可以在UIApplication中实现处理RemoteComtrol事件的方法,根据不同的事件实现不同的操作即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#import "MyApplication.h"  @implementation MyApplication  - (BOOL)canBecomeFirstResponder {  return YES; }  - (void)remoteControlReceivedWithEvent:(UIEvent *)event {  switch (event.subtype)  {  case UIEventSubtypeRemoteControlPlay:  //play  break;  case UIEventSubtypeRemoteControlPause:  //pause  break;  case UIEventSubtypeRemoteControlStop:  //stop  break;  default:  break;  } }  @end 

示例代码

git上有一个关于remotecontrol的小工程供大家参考ios-audio-remote-control

后记

到本篇为止iOS的音频播放话题基本上算是完结了。接下来我会在空余时间去研究一下iOS 8中新加入的AVAudioEngine,其功能涵盖播放、录音、混音、音效处理,看上去十分强大,从接口的定义上看像是对AudioUnit的高层封装,当研究有了一定的成果之后也会以博文的形式分享出来。


你可能感兴趣的:(iOS音频播放 (八):NowPlayingCenter和RemoteControl)