单击播放按钮时,AVAudioPlayer 的问题会出错

Posted

技术标签:

【中文标题】单击播放按钮时,AVAudioPlayer 的问题会出错【英文标题】:Issue with AVAudioPlayer gives error when click at Play button 【发布时间】:2015-12-03 10:45:32 【问题描述】:

我正在使用“AVFoundation.h”对音频播放器进行编程。我有更新进度条的问题,因此当我点击播放按钮时,我的应用程序出错。我附上了代码示例和错误报告。谁能解决这个问题?

-(void)updateProgress 
NSInteger durationMinutes = [self.audioPlayer duration] / 60;
NSInteger durationSeconds = [self.audioPlayer duration]  - durationMinutes * 60;

NSInteger currentTimeMinutes = [self.audioPlayer currentTime] / 60;
NSInteger currentTimeSeconds = [self.audioPlayer currentTime]  - currentTimeMinutes * 60;
NSString *progressString = [NSString stringWithFormat:@"%d:%02d / %d:%02d", currentTimeMinutes, currentTimeSeconds, durationMinutes, durationSeconds];
self.timeLabel.text = progressString;

self.progressBar.progress = [self.audioPlayer currentTime] / [self.audioPlayer duration];

NSNumber *numCurrentTimeSeconds = [NSNumber numberWithInt:currentTimeSeconds];
NSNumber *numDurationSeconds = [NSNumber numberWithInt:durationSeconds];

NSString *songTitle = [self.selectedFilePath lastPathComponent];
NSString *artistName = @"MyPlayer";
MPMediaItemArtwork *albumArt = [[MPMediaItemArtwork alloc] initWithImage:[UIImage imageNamed:@"placeholder"]];


MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter];
NSDictionary *infoDict = [NSDictionary dictionaryWithObjects:@[songTitle, artistName, albumArt, numDurationSeconds, numCurrentTimeSeconds] forKeys:@[MPMediaItemPropertyTitle, MPMediaItemPropertyAlbumArtist, MPMediaItemPropertyArtwork, MPMediaItemPropertyPlaybackDuration, MPNowPlayingInfoPropertyElapsedPlaybackTime]];
[infoCenter setNowPlayingInfo:infoDict];        

当按下 Build & Run 应用程序在模拟器中成功启动时,我拍摄了 2 张活动控制台的图像 1. 在点击播放按钮之前。

    点击播放按钮后。当应用崩溃时。

enter image description here

现在请建议我此时应该做什么?以便我的应用程序开始顺利运行... 谢谢 法伊兹。


按照 Losiowaty 的说明进行了最后一天的回答。这些黄色问题已被删除,但当我点击播放按钮时,我的编程仍然会出现同样的错误。 enter image description here

这次我上传了完整的代码并突出显示了一些我认为正在发生错误的事情。 请看一下我的 mainwviewcontroller.m 类代码。

    @interface MainViewController ()
    @end
    @implementation MainViewController
    @synthesize audioPlayer;
    - (void)viewDidLoad 
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib
    NSError *error = nil;
[[AVAudiosession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:&error];
    if (error == nil) 
        NSLog(@"audio session initialized successfully");
     else 
        NSLog(@"error initializing audio session: %@", [error description]);
    


    [audioPlayer setDelegate:self];
    MPVolumeView *volumeView = [ [MPVolumeView alloc] init] ;

    [volumeView setFrame:self.airPlayView.bounds];
    [self.airPlayView addSubview:volumeView];

    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];

    [self becomeFirstResponder];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(caughtInterruption:) name:AVAudioSessionInterruptionNotification object:nil];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChanged:) name:AVAudioSessionRouteChangeNotification object:nil];

- (void)didReceiveMemoryWarning 
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.


-(BOOL)canBecomeFirstResponder

    return YES;


-(void)dealloc

    [self resignFirstResponder];

    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionInterruptionNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil];


- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

    if ([[segue identifier] isEqualToString:@"showFilePicker"]) 
        UINavigationController *navigationController = (UINavigationController *)segue.destinationViewController;
        FileViewController *fileViewController = (FileViewController *)navigationController.topViewController;
        fileViewController.delegate = self;
    


#pragma mark - file picker delegate methods

-(void)cancel

    [self dismissViewControllerAnimated:YES completion:nil];


-(void)didFinishWithFile:(NSString *)filePath

    NSError *error = nil;
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];


    self.selectedFilePath = filePath;

    NSString *relativeFilePath = [documentsDirectory stringByAppendingPathComponent:filePath];

    NSURL *fileURL = [NSURL fileURLWithPath:relativeFilePath];

    self.audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:&error];
    self.audioPlayer.delegate = self;

    if (error == nil) 
        NSLog(@"audio player initialized successfully");

        self.titleLabel.text = self.selectedFilePath;

        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(updateProgress) userInfo:nil repeats:YES];

        NSString *songTitle = [filePath lastPathComponent];
        NSString *artistName = @"MyPlayer";
        MPMediaItemArtwork *albumArt = [[MPMediaItemArtwork alloc] initWithImage:[UIImage imageNamed:@"placeholder"]];

        MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter];
        NSDictionary *infoDict = [NSDictionary dictionaryWithObjects:@[songTitle, artistName, albumArt] forKeys:@[MPMediaItemPropertyTitle, MPMediaItemPropertyAlbumArtist, MPMediaItemPropertyArtwork]];
        [infoCenter setNowPlayingInfo:infoDict];

        [self play:nil];


     else 
        NSLog(@"error initializing audio player: %@", [error description]);
    


    //dismiss the file picker
    [self dismissViewControllerAnimated:YES completion:nil];


-(IBAction)play:(id)sender

    if ([self.audioPlayer isPlaying]) 
        [self.audioPlayer pause];
        [self.playButton setImage:[UIImage imageNamed:@"play"] forState:UIControlStateNormal];
        [self.timer invalidate];
        [animation stopAnimating];

     else 
        [self.audioPlayer play];
        [self.playButton setImage:[UIImage imageNamed:@"pause"] forState:UIControlStateNormal];
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(updateProgress) userInfo:nil repeats:YES];

        animation.animationImages = [NSArray arrayWithObjects:
                                     [UIImage imageNamed:@"animation1.png"],
                                     [UIImage imageNamed:@"animation2.png"],
                                     [UIImage imageNamed:@"animation3.png"],nil];

        [animation setAnimationRepeatCount:2000];
        animation.animationDuration = 0.5;
        [animation startAnimating];

    
    self.playbackInterrupted = NO;


-(IBAction)skipForward:(id)sender

    if ([self.audioPlayer isPlaying]) 
        NSTimeInterval desiredTime = self.audioPlayer.currentTime + 15.0f;
        if (desiredTime < self.audioPlayer.duration) 
            self.audioPlayer.currentTime = desiredTime;
        
    


-(IBAction)skipBackward:(id)sender

    if ([self.audioPlayer isPlaying]) 
        NSTimeInterval desiredTime = self.audioPlayer.currentTime - 15.0f;
        if (desiredTime < 0) 
            self.audioPlayer.currentTime = 0.0f;
         else 
            self.audioPlayer.currentTime = desiredTime;
        

    


#pragma mark - Timer delegate

-(void)updateProgress

    NSInteger durationMinutes = [self.audioPlayer duration] / 60;
    NSInteger durationSeconds = [self.audioPlayer duration]  - durationMinutes * 60;

    NSInteger currentTimeMinutes = [self.audioPlayer currentTime] / 60;
    NSInteger currentTimeSeconds = [self.audioPlayer currentTime]  - currentTimeMinutes * 60;
    NSString *progressString = [NSString stringWithFormat:@"%ld:%02ld / %ld:%02ld", currentTimeMinutes,currentTimeSeconds, durationMinutes, durationSeconds];
    self.timeLabel.text = progressString;

    self.progressBar.progress = [self.audioPlayer currentTime] / [self.audioPlayer duration];

    NSNumber *numCurrentTimeSeconds = [NSNumber numberWithInteger:currentTimeSeconds];
    NSNumber *numDurationSeconds = [NSNumber numberWithInteger:durationSeconds];

    NSString *songTitle = [self.selectedFilePath lastPathComponent];
    NSString *artistName = @"MyPlayer";
    MPMediaItemArtwork *albumArt = [[MPMediaItemArtwork alloc] initWithImage:[UIImage imageNamed:@"placeholder"]];


    MPNowPlayingInfoCenter *infoCenter = [MPNowPlayingInfoCenter defaultCenter];
    NSDictionary *infoDict = [NSDictionary dictionaryWithObjects:@[songTitle, artistName, albumArt, numDurationSeconds, numCurrentTimeSeconds] forKeys:@[MPMediaItemPropertyTitle, MPMediaItemPropertyAlbumArtist, MPMediaItemPropertyArtwork, MPMediaItemPropertyPlaybackDuration, MPNowPlayingInfoPropertyElapsedPlaybackTime]];
        [infoCenter setNowPlayingInfo:infoDict];



#pragma mark - AVAudioPlayer delegate methods

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

    if (flag) 
        [self.playButton setImage:[UIImage imageNamed:@"play"] forState:UIControlStateNormal];
        [self.timer invalidate];
        [animation stopAnimating];
    


#pragma mark - Remote control

-(void)remoteControlReceivedWithEvent:(UIEvent *)event

    switch (event.subtype) 
        case UIEventSubtypeRemoteControlPlay:
        case UIEventSubtypeRemoteControlPause:
        case UIEventSubtypeRemoteControlTogglePlayPause:
            [self play:nil];
            break;
        case UIEventSubtypeRemoteControlNextTrack:
            [self skipForward:nil];
            break;
        case UIEventSubtypeRemoteControlPreviousTrack:
            [self skipBackward:nil];
            break;
        default:
            break;
    


#pragma mark - audio interruption

-(void)caughtInterruption:(NSNotification *)notification

    NSDictionary *userInfo = notification.userInfo;

    NSNumber *type =[userInfo objectForKey:AVAudioSessionInterruptionTypeKey];
    if ([type integerValue] == AVAudioSessionInterruptionTypeBegan) 
        if (self.audioPlayer.playing) 
            [self.audioPlayer pause];
            [animation stopAnimating];
            self.playbackInterrupted = YES;
        
     else 
        if (self.audioPlayer.playing == NO && self.playbackInterrupted == YES) 
            [self.audioPlayer play];
            [animation startAnimating];
            self.playbackInterrupted = NO;
        
    


#pragma mark - route changed

-(void)routeChanged:(NSNotification *)notification

    NSDictionary *userInfo = notification.userInfo;

    NSNumber *reason =[userInfo objectForKey:AVAudioSessionRouteChangeReasonKey];
    switch ([reason integerValue]) 
        case AVAudioSessionRouteChangeReasonNoSuitableRouteForCategory:
            [self.audioPlayer stop];
            [animation stopAnimating];
            break;
        case AVAudioSessionRouteChangeReasonNewDeviceAvailable:
        case AVAudioSessionRouteChangeReasonOldDeviceUnavailable:
        case AVAudioSessionRouteChangeReasonWakeFromSleep:
            [self.audioPlayer pause];
            [animation stopAnimating];
            break;
        default:
            break;
    




@end

上面的代码没有错误而且很干净,所有的东西都清楚地提到了,我使用了 4 个按钮,

    播放和暂停

    向前搜索

    向后搜索

    进入文件目录进行音频文件挑选

当我按下第四个按钮时,它准备进入另一个视图以选择音频文件。

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender

    if ([[segue identifier] isEqualToString:@"showFilePicker"]) 
        UINavigationController *navigationController = (UINavigationController *)segue.destinationViewController;
        FileViewController *fileViewController = (FileViewController *)navigationController.topViewController;
        fileViewController.delegate = self;
    

我需要完成两件事

我需要完成的第一件事是,

    我不想进入下一个视图,因为我正在将我的应用程序测试到模拟器中,其中没有我可以在模拟器中放置或定位的物理音频文件,因此我需要避免这件事只是为了我自己的测试目的. 因此,我愿意将音频 mp3 文件添加到 NSBundle 中,并希望在按下播放按钮文件开始播放时播放该文件,然后在再次按下时暂停。支付和暂停的代码非常干净,运行良好。但是对于初始化文件路径,我认为我必须通过以下代码替换上面的视图来初始化 viewDidload 方法中的文件路径。

    - (void)viewDidLoad 
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    NSString *audioFilePath = [[NSBundle mainBundle] pathForResource:@"Nameofflie" ofType:@"mp3"];
    NSURL *pathAsURL = [[NSURL alloc] initFileURLWithPath:audioFilePath];
    
    
    NSError *error = nil;
    audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:pathAsURL error:&error];
       if (error == nil) 
        NSLog(@"audio session initialized successfully");
     else 
        NSLog(@"error initializing audio session: %@", [error description]);
    
    
    
    [audioPlayer setDelegate:self];
    MPVolumeView *volumeView = [ [MPVolumeView alloc] init] ;
    
    [volumeView setFrame:self.airPlayView.bounds];
    [self.airPlayView addSubview:volumeView];
    
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    
    [self becomeFirstResponder];
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(caughtInterruption:) name:AVAudioSessionInterruptionNotification object:nil];
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChanged:) name:AVAudioSessionRouteChangeNotification object:nil];
    

此代码也可以很好地运行和编译,但是在按下播放按钮时会出现同样的错误。所以请建议我在哪里放置以下几行来播放从 NSBudle 放置的 MP3 音乐文件。

NSString *audioFilePath = [[NSBundle mainBundle] pathForResource:@"rabne" ofType:@"mp3"];
NSURL *pathAsURL = [[NSURL alloc] initFileURLWithPath:audioFilePath];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:pathAsURL error:&error];
    这一点与我愿意完成的第一点完全不同。测试成功后,播放该 NSBundle 音频 MP3 文件。 我想再次使用我之前的代码,因为最终用户没有使用我的模拟器,因此对于最终用户,我希望拥有我已经在上面 Mainviewcontroller.m 类中完成的相同选项,即用户必须按第四个数字按钮才能访问其设备的文档目录文件路径。这在我的代码中运行良好。但是我想指出或需要解决的问题是,如果有人直接按第一个播放按钮而不按第四个按钮来选择音频文件,则应该出现一个警报视图,并显示一条消息,首先选择一个文件按第四个按钮,然后单击播放按钮。这就是我愿意在我的代码中包含的内容。

【问题讨论】:

你能点击黄色三角形并在那里显示警告吗?此外,当您的应用程序崩溃时,在右侧代码下方的窗口中会出现一个堆栈跟踪,其中可以更好地描述异常。能否展开并粘贴异常和堆栈跟踪? 感谢您的回复(建议),我已经尝试了很多次,它提供了在第一个问题中进行细微更改的选项,如下所示。 NSString *progressString = [NSString stringWithFormat:@"%ld:02d / %ld:02d", currentTimeMinutes,(long)currentTimeSeconds, (long)durationMinutes, durationSeconds];在同一行发生的另一个问题是“格式字符串未使用数据参数”。 &没有什么可以解决这个问题,&程序仍然给出同样的错误。 这个警告仅仅意味着你传递了 4 个数字来格式化字符串,但只使用了其中的两个。这似乎与崩溃无关,因为警告很少这样做,这就是为什么我还从控制台窗口询问堆栈跟踪和异常描述。 当我按下播放按钮应用程序停止工作并显示这些错误时,让我添加更多错误报告图像。当我跳过这个过程时,附上所有的图像。 如果你看清楚那一行,我会使用所有四个数字。 【参考方案1】:

根据屏幕截图 #2 中的异常,您似乎正在尝试将 nil 对象插入到数组中。将一些对象插入数组的唯一地方是这一行:

NSDictionary *infoDict = [NSDictionary dictionaryWithObjects:@[songTitle, artistName, albumArt, numDurationSeconds, numCurrentTimeSeconds] forKeys:@[MPMediaItemPropertyTitle, MPMediaItemPropertyAlbumArtist, MPMediaItemPropertyArtwork, MPMediaItemPropertyPlaybackDuration, MPNowPlayingInfoPropertyElapsedPlaybackTime]];

第二个数组,即带有键的数组,看起来不错,因为它只包含系统提供的常量。另一方面,第一个对象有两个可能为零的对象:songTitlealbumArt

这些是nil的原因是:

    songTitle 可能是 nil 如果 self.selectedFilePathnil albumArt - 我不完全确定,但如果找不到您的图片,它最终可能会是 nil

请确保这两个不是nil,并且一切都应该正常工作。


至于你的警告,这两个:

NSNumber *numCurrentTimeSeconds = [NSNumber numberWithInt:currentTimeSeconds];
NSNumber *numDurationSeconds = [NSNumber numberWithInt:durationSeconds];

可以通过更改为[NSNumber numberWithInteger:] 来修复,这是因为NSIntegerlongtypedef 而不是int

警告

NSString *progressString = [NSString stringWithFormat:@"%d:%02d / %d:%02d", currentTimeMinutes, currentTimeSeconds, durationMinutes, durationSeconds];

实际上是由同一件事引起的。 %d 需要 int,而 NSIntegerlong。将%d 更改为%ld 将解决此问题。

我仍然坚信这些并没有导致崩溃,尤其是基于抛出的异常,它非常简单地说明了发生了什么。


提供的代码证实了我的假设 - 发生崩溃是因为 self.selectedFilePathupdateProgress 方法中是 nil 导致 songTitle 也是 nil。为什么会发生这种情况?在提供的代码中设置self.selectedFilePath 的唯一位置是didFinishWithFile: 方法,我假设它是FileViewController 的委托方法。如果您不显示它并在那里选择了某些内容,则不会调用该方法。

现在,如果您想设置它以进行测试,最简单的方法是将其添加到您的 viewDidLoad 中:

NSError *error;
NSString *audioFilePath = [[NSBundle mainBundle] pathForResource:@"rabne" ofType:@"mp3"];
NSURL *pathAsURL = [[NSURL alloc] initFileURLWithPath:audioFilePath];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:pathAsURL error:&error];
if (error == nil) 
    self.selectedFilePath = @"test file"; // <<-- IMPORTANT
    self.titleLabel.text = self.selectedFilePath;
  else 
    NSLog(@"error initializing audio player: %@", [error description]);
 

就在[audioPlayer setDelegate:self];上方。这应该让一切顺利。

附带说明:我还要从 didFinishWithFile: 方法中删除这一行 self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(updateProgress) userInfo:nil repeats:YES]; - 您还在 play: 方法中设置了一个计时器,这样做似乎更安全。


至于第 2 点 - 我可以给你一个提示,你知道在self.selectedFilePath != nil 时是否选择了一个文件并查看UIAlertViewController class。剩下的工作留给你,因为它不是原始问题的一部分,与解决崩溃无关。另外,那样你也学不到任何东西:)

【讨论】:

首先感谢您的时间和关注,让我按照您的指示来看看现在发生了什么。很快就会返回结果。 最近我在上面的问题中添加了一个截图,这表明问题。在代码中。我想那个时候肯定会出问题。不在歌曲标题或专辑艺术中。那你说什么? 感谢 Losiowaty,按照您的指示解决了一件事(黄色问题的行现在已解决),因为这些问题存在完全相同的问题,当我将 [NSNumber numberWithInt:] 替换为 [NSNumber numberWithInteger :] 和“%d to %ld”,代码中没有更多问题。但我的应用程序仍然给出了同样的错误,从一开始它就给出了。让我添加 mainviewcontroller.m 类的完整代码,并在上面的问题中突出显示更多内容。更新我的问题部分后,我会在此通知。 如果有,请不要截图,而是复制粘贴代码。 请看我编辑的问题版本。如果还需要更多细节,请告诉我。或者还有什么遗漏...?

以上是关于单击播放按钮时,AVAudioPlayer 的问题会出错的主要内容,如果未能解决你的问题,请参考以下文章

iPhone AVAudioPlayer 应用程序在第一次播放时冻结

AVAudioPlayer 在播放视频时无法识别设备静音按钮

iphone-AVAudioPlayer 重叠

当 AVAudioPlayer 完成播放时切换 tableview 单元格图像

如何安全地停止 AVAudioPlayer

使用 AVAudioPlayer 一次播放多个声音