http://www.devdiv.com/forum.php?mod=viewthread&tid=125220
9.5 在其他活动声音上面播放音频 问题 你可能想在播放音频时让其他程序静音,或者在其他程序的音频回放之上播放音频。 解决 使用音频会话设置你的应用程序所使用的音频类别。 讨论 AVAudioSession类由AVFoundation框架引入。每个IOS应用都有一个音频会话。这个会话可以被AVAudioSession类的sharedInstance类方法访问,如下: AVAudioSession *audioSession = [AVAudioSession sharedInstance]; 在获得一个AVAudioSession类的实例后,你就能通过调用音频会话对象的setCategory:error:实例方法,来从IOS应用可用的不同类别中作出选择。下面列出了可供使用的音频会话类别: AVAudioSessionCategorySoloAmbient 这个类别非常像AVAudioSessionCategoryAmbient类别,除了会停止其他程序的音频回放,比如iPod程序。当设备被设置为静音模式,你的音频回放将会停止。 AVAudioSessionCategoryRecord 这会停止其他应用的声音(比如iPod)并让你的应用也不能初始化音频回放(比如AVAudioPlayer)。在这种模式下,你只能进行录音。使用这个类别,调用AVAudioPlayer的prepareToPlay会返回YES,但是调用play方法将返回NO。主UI界面会照常工作。这时,即使你的设备屏幕被用户锁定了,应用的录音仍会继续。 AVAudioSessionCategoryPlayback 这个类别会静止其他应用的音频回放(比如iPod应用的音频回放)。你可以使用AVAudioPlayer的prepareToPlay和play方法,在你的应用中播放声音。主UI界面会照常工作。这时,即使屏幕被锁定或者设备为静音模式,音频回放都会继续。 AVAudioSessionCategoryPlayAndRecord 这个类别允许你的应用中同时进行声音的播放和录制。当你的声音录制或播放开始后,其他应用的声音播放将会停止。主UI界面会照常工作。这时,即使屏幕被锁定或者设备为静音模式,音频回放和录制都会继续。 AVAudioSessionCategoryAudioProcessing 这个类别用于应用中进行音频处理的情形,而不是音频回放或录制。设置了这种模式,你在应用中就不能播放和录制任何声音。调用AVAPlayer的prepareToPlay和play方法都将返回NO。其他应用的音频回放,比如iPod,也会在此模式下停止。 AVAudioSessionCategoryAmbient 这个类别不会停止其他应用的声音,相反,它允许你的音频播放于其他应用的声音之上,比如iPod。你的应用的主UI县城会工作正常。调用AVAPlayer的prepareToPlay和play方法都将返回YES。当用户锁屏时,你的应用将停止所有正在回放的音频。仅当你的应用是唯一播放该音频文件的应用时,静音模式将停止你程序的音频回放。如果正当iPod播放一手歌时,你开始播放音频,将设备设为静音模式并不能停止你的音频回放。 |
9.7 为视频文件捕获缩略图
问题
你在使用MPMoviePlayerController类的实例播放一个视频文件时,可能想要在某个时刻从影片捕获一张截图。
解决
使用MPMoviePlayerController的requestThumbnailImagesAtTimes:timeOption:实例方法,如下:
/* Capture the frame at the third second into the movie */ NSNumber *thirdSecondThumbnail = [NSNumber numberWithFloat:3.0f];
/* We can ask to capture as many frames as we
want. But for now, we are just asking to capture one frame */
NSArray *requestedThumbnails =
[NSArray arrayWithObject:thirdSecondThumbnail];
/* Ask the movie player to capture this frame for us */
[self.moviePlayer
requestThumbnailImagesAtTimes:requestedThumbnails timeOption:MPMovieTimeOptionExact];
讨论
MPMoviePlayerController的一个实例能够从最近播放的影片中捕获缩略图,同步和异步都可以。在这里,我们将关注这个类的异步图像捕获。
我们可以使用MPMoviePlayerController的requestThumbnailImagesAtTimes:timeOption:实例方法来异步访问缩略图。当我说“异步”时,我的意思是,在缩略图正在被捕获和被报告给你指派的对象(我们很快能看到)的过程中,视频播放器将继续它的工作,且不会阻塞回放。我们必须观察MPMoviePlayerThumbnailImage RequestDidFinishNotification通知消息,这个消息由视频播放器发送给默认消息中心,以便知道我们的缩略图什么时候可用:
- (void) startPlayingVideo:(id)paramSender{
/* First let's construct the URL of the file in our application bundle
that needs to get played by the movie player */ NSBundle *mainBundle = [NSBundle mainBundle];
NSString *urlAsString = [mainBundle pathForResource:@"Sample"
ofType:@"m4v"];
NSURL *url = [NSURL fileURLWithPath:urlAsString];
/* If we have already created a movie player before, let's try to stop it */
if (self.moviePlayer != nil){
[self stopPlayingVideo:nil]; }
/* Now create a new movie player using the URL */
self.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:url];
if (self.moviePlayer != nil){
/* Listen for the notification that the movie player sends us whenever it finishes playing an audio file */
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(videoHasFinishedPlaying:) name:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayer];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(videoThumbnailIsAvailable:)
name:MPMoviePlayerThumbnailImageRequestDidFinishNotification object:self.moviePlayer];
NSLog(@"Successfully instantiated the movie player.");
/* Scale the movie player to fit the aspect ratio */ self.moviePlayer.scalingMode = MPMovieScalingModeAspectFit;
/* Let's start playing the video in full screen mode */ [self.moviePlayer play];
[self.view addSubview:self.moviePlayer.view];
[self.moviePlayer setFullscreen:YES animated:YES];
/* Capture the frame at the third second into the movie */ NSNumber *thirdSecondThumbnail = [NSNumber numberWithFloat:3.0f];
/* We can ask to capture as many frames as we
want. But for now, we are just asking to capture one frame */
NSArray *requestedThumbnails =
[NSArray arrayWithObject:thirdSecondThumbnail];
/* Ask the movie player to capture this frame for us */ [self.moviePlayer
requestThumbnailImagesAtTimes:requestedThumbnails timeOption:MPMovieTimeOptionExact];
} else {
NSLog(@"Failed to instantiate the movie player."); }
}
你可以看到,我们我们要求视频播放器捕获影片中第三秒的那个帧。一旦这个任务完成,我们的视图控制器的videoThumbnailIsAvailable:实例方法就会被调用,下面的代码说明了我们如何能访问被捕获的图像:
- (void) videoThumbnailIsAvailable:(NSNotification *)paramNotification{ MPMoviePlayerController *controller = [paramNotification object];
if (controller != nil &&
[controller isEqual:self.moviePlayer]){ NSLog(@"Thumbnail is available");
/* Now get the thumbnail out of the user info dictionary */ UIImage *thumbnail =
[paramNotification.userInfo
objectForKey:MPMoviePlayerThumbnailImageKey];
if (thumbnail != nil){
/* We got the thumbnail image. You can now use it here */
} }
}
因为我们在startPlayingVideo:中实例化视频播放器时,开始了监听MPMoviePlayerThumbnailImageRequestDidFinishNotifi cation
通知,所以也必须在我们停止视频播放器时,停止对这个通知的监听(或者任何你在你的应用框架中认为恰当的时间)。
- (void) stopPlayingVideo:(id)paramSender { if (self.moviePlayer != nil){
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayer];
[[NSNotificationCenter defaultCenter]
removeObserver:self name:MPMoviePlayerThumbnailImageRequestDidFinishNotification object:self.moviePlayer];
[self.moviePlayer stop];
if ([self.moviePlayer.view.superview isEqual:self.view]){ [self.moviePlayer.view removeFromSuperview];
} }
}
当在调用MPMoviePlayerController的requestThumbnailImagesAtTimes:timeOption:实例方法时,我们可以为timeOption:指定MPMovie TimeOptionExact 或者MPMovieTimeOptionNearestKeyFrame其中之一。前者捕获视频时间线上精确的点,后者虽然不那么精确,但使用更少的系统资源,并在捕获缩略图时整体上有更好的表现。MPMovieTimeOptionNearestKey Frame通常能胜任精确要求,因为它仅仅有那么一两帧的误差。
9.8 访问音乐库 问题 你想要访问用户从她的音乐库中挑选的物品。 解决 使用MPMediaPickerController类: MPMediaPickerController *mediaPicker = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeAny]; 讨论 MPMediaPickerController是一个iPod程序显示给用户的视图控制器。只要实例化MPMediaPickerController,你就可以为你的用户展现一个标准的视图控制器,让他们任意从音乐库中进行选择,然后控制重新返回到你的程序中。这点对游戏特别有用,例如,用户玩游戏时,可以让你的程序在背景播放它最喜爱的歌曲。 你可以用成为媒体选择控制器的委托(遵守MPMediaPickerControllerDelegate协议)的方式从它那里获得信息: #import <UIKit/UIKit.h> #import <MediaPlayer/MediaPlayer.h> @interface Accessing_the_Music_LibraryViewController : UIViewController <MPMediaPickerControllerDelegate> @end 在你的displayMediaPicker:选择器中,实现所需的代码,来显示媒体选择控制器实例并且向用户展现一个模态视图控制器: - (void) displayMediaPicker{ MPMediaPickerController *mediaPicker = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeAny]; if (mediaPicker != nil){ NSLog(@"Successfully instantiated a media picker."); mediaPicker.delegate = self; mediaPicker.allowsPickingMultipleItems = NO; [self.navigationController presentModalViewController:mediaPicker animated:YES]; } else { NSLog(@"Could not instantiate a media picker."); } } 媒体选择控制器的allowsPickingMultipleItems属性,让你指定用户能否在隐藏控制器之前选择多于一个物品。它接受一个BOOL值,目前我们就把它设置为NO,稍后我们会看到效果。现在,让我们来实现MPMediaPickerControllerDelegate协议的各种委托消息: - (void) mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection{ NSLog(@"Media Picker returned"); for (MPMediaItem *thisItem in mediaItemCollection.items){ NSURL [thisItem NSString [thisItem NSString [thisItem *itemURL = valueForProperty:MPMediaItemPropertyAssetURL]; *itemTitle = valueForProperty:MPMediaItemPropertyTitle]; *itemArtist = valueForProperty:MPMediaItemPropertyArtist]; MPMediaItemArtwork *itemArtwork = [thisItem valueForProperty:MPMediaItemPropertyArtwork]; NSLog(@"Item URL = %@", itemURL); NSLog(@"Item Title = %@", itemTitle); NSLog(@"Item Artist = %@", itemArtist); NSLog(@"Item Artwork = %@", itemArtwork); } [mediaPicker dismissModalViewControllerAnimated:YES]; } 你可以使用MPMediaItem的valueForProperty:实例方法访问不同的属性。这个类的实例通过mediaPicker:didPick MediaItems:委托消息的mediaItemCollection参数返回到你的程序中。 现在让我们写一个带有很简单的GUI的一个程序,它允许我们询问用户,来从iPod库中选择一个音乐。在她选择了音乐文件后,我们尝试使用MPMusicPlayerController实例来播放它。我们的GUI有个简单的按钮:“选择”和“播放”,以及“停止播放”。第一个按钮会要用户从iPod库中选择一段音频给我们播放,第二个按钮会停止音频回放(如果我们已经在播放音频了)。我们会从程序的UI设计开始。让我们以简单的方式创建,就像图9-1那样。 图9-1 一个极简单的UI,展示了媒体选择器和AV音频播放器 现在让我们继续在视图控制器的.h中定义这两个按钮: @interface Accessing_the_Music_LibraryViewController : UIViewController <MPMediaPickerControllerDelegate, AVAudioPlayerDelegate> @property (nonatomic, strong) MPMusicPlayerController *myMusicPlayer; @property (nonatomic, strong) UIButton *buttonPickAndPlay; @property (nonatomic, strong) UIButton *buttonStopPlaying; @end 当我们的视图加载完毕,我们会实例化这两个按钮们将他们放置于视图之上: - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; self.buttonPickAndPlay = [UIButton buttonWithType:UIButtonTypeRoundedRect]; self.buttonPickAndPlay.frame = CGRectMake(0.0f, 0.0f, 200, 37.0f); self.buttonPickAndPlay.center = CGPointMake(self.view.center.x, self.view.center.y - 50); [self.buttonPickAndPlay setTitle:@"Pick and Play" forState:UIControlStateNormal]; [self.buttonPickAndPlay addTarget:self action:@selector(displayMediaPickerAndPlayItem) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:self.buttonPickAndPlay]; self.buttonStopPlaying = [UIButton buttonWithType:UIButtonTypeRoundedRect]; self.buttonStopPlaying.frame = CGRectMake(0.0f, 0.0f, 200, 37.0f); self.buttonStopPlaying.center = CGPointMake(self.view.center.x, self.view.center.y + 50); [self.buttonStopPlaying setTitle:@"Stop Playing" forState:UIControlStateNormal]; [self.buttonStopPlaying addTarget:self action:@selector(stopPlayingAudio) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:self.buttonStopPlaying]; [self.navigationController setNavigationBarHidden:YES animated:NO]; } 视图控制器的两个最重要的方法是displayMediaPicker 和 PlayItem and stopPlayingAudio: - (void) stopPlayingAudio{ if (self.myMusicPlayer != nil){ [[NSNotificationCenter defaultCenter] removeObserver:self name:MPMusicPlayerControllerPlaybackStateDidChangeNotification object:self.myMusicPlayer]; [[NSNotificationCenter defaultCenter] removeObserver:self name:MPMusicPlayerControllerNowPlayingItemDidChangeNotification object:self.myMusicPlayer]; [[NSNotificationCenter defaultCenter] removeObserver:self name:MPMusicPlayerControllerVolumeDidChangeNotification object:self.myMusicPlayer]; [self.myMusicPlayer stop]; } } - (void) displayMediaPickerAndPlayItem{ MPMediaPickerController *mediaPicker = [[MPMediaPickerController alloc] initWithMediaTypes:MPMediaTypeMusic]; if (mediaPicker != nil){ NSLog(@"Successfully instantiated a media picker."); mediaPicker.delegate = self; mediaPicker.allowsPickingMultipleItems = YES; [self.navigationController presentModalViewController:mediaPicker animated:YES]; } else { NSLog(@"Could not instantiate a media picker."); } } 当我们的媒体选择控制器成功完成,mediaPicker:didPickMediaItems消息会在委托对象中被调用(这个例子中就是视图控制器)。另一方面,如果用户取消了媒体播放器,我们会得到mediaPicker:mediaPickerDid Cancel消息。下面的代码实现了每种情况下将会被调用的方法。 - (void) mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection{ NSLog(@"Media Picker returned"); /* First, if we have already created a music player, let's deallocate it */ self.myMusicPlayer = nil; self.myMusicPlayer = [[MPMusicPlayerController alloc] init]; [self.myMusicPlayer beginGeneratingPlaybackNotifications]; /* Get notified when the state of the playback changes */ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(musicPlayerStateChanged:) name:MPMusicPlayerControllerPlaybackStateDidChangeNotification object:self.myMusicPlayer]; /* Get notified when the playback moves from one item to the other. In this recipe, we are only going to allow our user to pick one music file */ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(nowPlayingItemIsChanged:) name:MPMusicPlayerControllerNowPlayingItemDidChangeNotification object:self.myMusicPlayer]; /* And also get notified when the volume of the music player is changed */ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(volumeIsChanged:) name:MPMusicPlayerControllerVolumeDidChangeNotification object:self.myMusicPlayer]; /* Start playing the items in the collection */ [self.myMusicPlayer setQueueWithItemCollection:mediaItemCollection]; [self.myMusicPlayer play]; /* Finally dismiss the media picker controller */ [mediaPicker dismissModalViewControllerAnimated:YES]; } - (void) mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker{ /* The media picker was cancelled */ NSLog(@"Media Picker was cancelled"); [mediaPicker dismissModalViewControllerAnimated:YES]; } 我么在监听音乐播放器通过通知发送的事件。下面是用于处理被监听的音乐播放器通知消息的三个方法: - (void) musicPlayerStateChanged:(NSNotification *)paramNotification{ NSLog(@"Player State Changed"); /* Let's get the state of the player */ NSNumber *stateAsObject = [paramNotification.userInfo objectForKey:@"MPMusicPlayerControllerPlaybackStateKey"]; NSInteger state = [stateAsObject integerValue]; /* Make your decision based on the state of the player */ switch (state){ case MPMusicPlaybackStateStopped:{ /* Here the media player has stopped playing the queue. */ break; } case MPMusicPlaybackStatePlaying:{ /* The media player is playing the queue. Perhaps you can reduce some processing that your application that is using to give more processing power to the media player */ break; } case MPMusicPlaybackStatePaused:{ /* The media playback is paused here. You might want to indicate by showing graphics to the user */ break; } case MPMusicPlaybackStateInterrupted:{ /* An interruption stopped the playback of the media queue */ break; } case MPMusicPlaybackStateSeekingForward:{ /* The user is seeking forward in the queue */ break; } case MPMusicPlaybackStateSeekingBackward:{ /* The user is seeking backward in the queue */ break; } } /* switch (State){ */ } - (void) nowPlayingItemIsChanged:(NSNotification *)paramNotification{ NSLog(@"Playing Item Is Changed"); NSString *persistentID = [paramNotification.userInfo objectForKey:@"MPMusicPlayerControllerNowPlayingItemPersistentIDKey"]; /* Do something with Persistent ID */ NSLog(@"Persistent ID = %@", persistentID); } - (void) volumeIsChanged:(NSNotification *)paramNotification{ NSLog(@"Volume Is Changed"); /* The userInfo dictionary of this notification is normally empty */ } 我们还会实现视图控制器的viewDidUnload方法,来确保不会留下任何内存泄漏: . - (void) viewDidUnload{ [super viewDidUnload]; [self stopPlayingAudio]; self.myMusicPlayer = nil; } 接下来,运行我们的程序,点击视图控制器的“点击”和“播放”按钮,我们会看到媒体选择控制器。一旦控制器出现,和iPod相同的UI将展现给用户。在用户选择了一项之后(或者取消整个对话框),我们会在视图控制器中得到相应的委托消息(当我们的视图控制器是媒体选择器的视图控制器时)。在物品被选择后(在这里我们只允许选一个),我们会开始我们的音乐播放器,并开始播放整个选择集合。 如果你想允许用户一次选择多于一项,只要设置你的媒体选择器的allowsPickingMultipleItems属性为YES: mediaPicker.allowsPickingMultipleItems = YES; 有时,当在使用媒体选择控制器时(MPMedia PickerController),“MPMediaPicker:失去到iPod库的连接”消息将会输出到控制台屏幕。这是因为媒体选择器在显示给用户时,因为诸如iTunes同步这样的事件而被打断。这时,你的mediaPickerDidCancel:委托方法立刻会被调用。 |