学习笔记----音频开发基础

参考链接

一、AVAudioPlayer播放本地音乐

简介:AVAudioPlayer是AVFoundation.framework中的一个类,它支持多种音频格式,而且能够进行进度、音量、播放速度等控制,详细属性可以看
原文 介绍非常详细。

注意点:AVAudioPlayer只能播放本地文件,并且是一次性加载所有音频数据,初始化AVAudioPlayer时指定的URL也只能是File URL而不能是HTTP URL,AVAudioPlayer一次只能播放一个音频文件,如果想实现上一曲、下一曲的功能可以通过创建多个播放器对象来完成.

AVAudioPlayer简单使用

1.初始化一个AVAudioPlayer

  //获取本地音乐的路径
    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"235319" ofType:@"mp3"];    

    NSURL *url2 = [NSURL URLWithString:filePath];

    NSError *error = nil;
      //创建音乐播放器,此URL不能是HTTP URL
    _audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url2 error:&error];

    //设置为0不循环,可以设置循环次数
    _audioPlayer.numberOfLoops=3;

    //根据URL地址来读取音乐文件(写在ViewDidLoad中会自动播放)
    [_audioPlayer prepareToPlay];
    
    //设置代理,监听音乐是否播放完成
    _audioPlayer.delegate = self;

    //error存在,初始化失败
    if (error) {
        NSLog(@"初始化错误");
    }

2.在需要播放的地方开始播放就行了

-(void)playClick:(UIButton*)button{
    
    if (!button.selected) {
        [self play];
    }else{
        [self pause];
    }
    button.selected = !button.selected;
}

-(void)play{
    if (![_audioPlayer isPlaying]) {
        [_audioPlayer play];
        self.timer.fireDate=[NSDate distantPast];//恢复定时器
    }
}

/**
 *  暂停播放
 */
-(void)pause{
    if ([_audioPlayer isPlaying]) {
        [_audioPlayer pause];
        self.timer.fireDate=[NSDate distantFuture];//暂停定时器,注意不能调用invalidate方法,此方法会取消,之后无法恢复
    }
}

3.滑动UISlider设置当前播放
音乐播放进度的实现主要依靠一个定时器实时计算当前播放时长和音频总时长的比例

//开始播放时,开始定时器,暂停播放时暂停定时器
-(NSTimer *)timer{
    if (!_timer) {
        _timer=[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateProgress) userInfo:nil repeats:true];
    }
    return _timer;
}

-(void)updateProgress{
 //当前播放音乐的进度时间/音乐总时间,算出值赋值给Slider
    float progress= _audioPlayer.currentTime /_audioPlayer.duration;
    if (!self.isTap) {
        [self.slider setValue:progress animated:true];
    }
}
-(void)playToTime:(float)value{
    if (![_audioPlayer isPlaying]) {
        _audioPlayer.currentTime = value*_audioPlayer.duration;

        [_audioPlayer play];
        self.timer.fireDate=[NSDate distantPast];//恢复定时器
    }else{
        _audioPlayer.currentTime = value*_audioPlayer.duration;
    }
}

4.完成播放回掉代理

#pragma mark - 播放器代理方法
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
    NSLog(@"音乐播放完成...");
//切换歌曲可以在此操作
    self.timer.fireDate=[NSDate distantFuture];//恢复定时器
}

二、AVPlayer播放网络,本地音乐

参考文章链接:http://www.cnblogs.com/kenshincui/p/4186022.html

简介:上面说了AVAudioPlayer 不支持网络音频在线播放,你可以使用一些第三方框架来实现在线播放例如AudioStreamer、FreeStreamer。如果不想使用第三方的框架,AVPlayer可以帮你实现在线播放的功能,AVPlayer也是存在于AVFoundation中,但是它功能要更加强大,可以播放本地音视频和网络音视频,有时当你需要自定义一些播放样式时,苹果提供的一些封装好的API例如MPMoviePlayerController可能并不能满足你的需求,这时使用AVPlayer可以帮你完成这些需求。

常用的类:

  • AVAsset:主要用于获取多媒体信息,是一个抽象类,不能直接使用。
  • AVURLAsset:AVAsset的子类,可以根据一个URL路径创建一个包含媒体信息的AVURLAsset对象。
  • AVPlayerItem:一个媒体资源管理对象,管理者视频的一些基本信息和状态,一个AVPlayerItem对应着一个视频资源。

暂停、播放功能

首先说一下音频的播放、暂停功能,这也是最基本的功能,AVPlayer对应着两个方法play、pause来实现。但是关键问题是如何判断当前音频是否在播放,在前面的内容中无论是音频播放器还是音频播放器都有对应的状态来判断,但是AVPlayer却没有这样的状态属性,通常情况下可以通过判断播放器的播放速度来获得播放状态。如果rate为0说明是停止状态,1是则是正常播放状态。

///判断是否正在播放
-(BOOL)isPlaying{
    if (self.rate==1) {
        return YES;
    }else{
        return NO;
    }
}

播放进度的控制和音频的切换

其次要展示播放进度就没有其他播放器那么简单了。在前面的播放器中通常是使用通知来获得播放器的状态,媒体加载状态等,但是无论是AVPlayer还是AVPlayerItem(AVPlayer有一个属性currentItem是AVPlayerItem类型,表示当前播放的音频对象)都无法获得这些信息。当然AVPlayerItem是有通知的,但是对于获得播放状态和加载状态有用的通知只有一个(其他真的没啥用):播放完成通知AVPlayerItemDidPlayToEndTimeNotification。在播放音频时,特别是播放网络音频往往需要知道音频加载情况、缓冲情况、播放情况,这些信息可以通过KVO监控AVPlayerItem的status、loadedTimeRanges属性来获得。当AVPlayerItem的status属性为AVPlayerStatusReadyToPlay是说明正在播放,只有处于这个状态时才能获得音频时长等信息;当loadedTimeRanges的改变时(每缓冲一部分数据就会更新此属性)可以获得本次缓冲加载的音频范围(包含起始时间、本次加载时长),这样一来就可以实时获得缓冲情况。然后就是依靠AVPlayer的方法获得播放进度,这个方法会在设定的时间间隔内定时更新播放进度,通过time参数通知客户端。相信有了这些视频信息播放进度就不成问题了,事实上通过这些信息就算是平时看到的其他播放器的缓冲进度显示以及拖动播放的功能也可以顺利的实现。

最后就是音频切换的功能,在前面介绍的所有播放器中每个播放器对象一次只能播放一个音频,如果要切换音频只能重新创建一个对象,但是AVPlayer却提供了- (void)replaceCurrentItemWithPlayerItem:(AVPlayerItem *)item方法用于在不同的音频之间切换(事实上在AVFoundation内部还有一个AVQueuePlayer专门处理播放列表切换,有兴趣的朋友可以自行研究,这里不再赘述)。

添加通知和观察者

///添加通知和观察者
-(void)addNotificationAndObserver{
    [self addNotification];
    [self addProgressObserver];
    [self addObserverToPlayerItem:self.currentItem];
}

///移除所有的通知和观察者
-(void)removeNotificationAndObserver{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:self.currentItem];
    [self removeObserverFromPlayerItem:self.currentItem];
    [self removeTimeObserver:self.obeserObject];
}


-(void)addProgressObserver{
    __weak typeof(self) weakSelf = self;
    //这里设置每秒执行一次,这个方法会在设定的时间间隔内定时更新播放进度,通过time参数通知客户端。
    self.obeserObject = [self addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
        if ([weakSelf.delegate respondsToSelector:@selector(msPlyaer:cmTime:)]) {
            [weakSelf.delegate msPlyaer:weakSelf cmTime:time];
        }
    }];
}

-(void)addNotification{
    //给AVPlayerItem添加播放完成通知(音乐播放完成后会回调通知方法)
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.currentItem];
}


-(void)playbackFinished:(NSNotification *)notification{
    self.isPlayComplete = YES;
    if ([self.delegate respondsToSelector:@selector(msPlyaerPlayEnd:)]) {
        [self.delegate msPlyaerPlayEnd:self];
    }
}

-(void)addObserverToPlayerItem:(AVPlayerItem *)playerItem{
    //监控状态属性,注意AVPlayer也有一个status属性,通过监控它的status也可以获得播放状态
    
    [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    //监控网络加载情况属性
    [playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
    
}
-(void)removeObserverFromPlayerItem:(AVPlayerItem *)playerItem{
    [playerItem removeObserver:self forKeyPath:@"status"];
    [playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
}


-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    AVPlayerItem *playerItem=object;
    if ([keyPath isEqualToString:@"status"]) {
        AVPlayerStatus status= [[change objectForKey:@"new"] intValue];

        if(status==AVPlayerStatusReadyToPlay){
            ///获取音频长度
            NSLog(@"正在播放...,视频总长度:%.2f",CMTimeGetSeconds(playerItem.duration));
            if ([self.delegate respondsToSelector:@selector(msPlyaer:currentPlayerItem:)]) {
                [self.delegate msPlyaer:self currentPlayerItem:playerItem];
            }
        }else{
            ///更新音频播放时间
            if ([self.delegate respondsToSelector:@selector(msPlyaerPlayError:currentPlayerItem:)]) {
                [self.delegate msPlyaerPlayError:self currentPlayerItem:playerItem];
            }
        }
    }else if([keyPath isEqualToString:@"loadedTimeRanges"]){
        NSArray *array=playerItem.loadedTimeRanges;
        CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次缓冲时间范围
        float startSeconds = CMTimeGetSeconds(timeRange.start);
        float durationSeconds = CMTimeGetSeconds(timeRange.duration);
        NSTimeInterval totalBuffer = startSeconds + durationSeconds;//缓冲总长度
        NSLog(@"共缓冲:%.2f",totalBuffer);
        //
    }
}

音量控制

使用player.volume属性可以控制播放的音量,但是此属性仅改变app的音量,不要用它来制作slider的声音控制开关,可以使用MPVolumeView来制作,这里 就不介绍啦,有兴趣的朋友可以自己去看看。

注意点

1.切换音乐时可以手动判断当前是否在播放,以防止上一曲暂停或者播放完成后处于暂停状态下直接调用下面方法时不播放。

///调用此方法切换播放源
    [self replaceCurrentItemWithPlayerItem:item];

2.切换音频时要清除上一个item的通知和观察者

///移除所有的通知和观察者
-(void)removeNotificationAndObserver{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:self.currentItem];
    [self removeObserverFromPlayerItem:self.currentItem];
    [self removeTimeObserver:self.obeserObject];
}

3.使用全局变量标记进度观察者返回的对象,切换音频时移除观察


@interface MSPlayer ()

@property(nonatomic,strong) id  obeserObject;

@end
-(void)addProgressObserver{
    __weak typeof(self) weakSelf = self;
    //这里设置每秒执行一次,这个方法会在设定的时间间隔内定时更新播放进度,通过time参数通知客户端。
    self.obeserObject = [self addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
        if ([weakSelf.delegate respondsToSelector:@selector(msPlyaer:cmTime:)]) {
            [weakSelf.delegate msPlyaer:weakSelf cmTime:time];
        }
    }];
}
   [self removeTimeObserver:self.obeserObject];

4.音频播放失败后一定要重新处理player和item,具体的好办法我也不清楚,只是通过下面的代码实现了重新播放。

-(void)msPlyaerPlayError:(MSPlayer*)player currentPlayerItem:(AVPlayerItem*)playerItem{
    self.player = nil;
    self.playerItme = nil;
}

还有一些注意项我也忘记了,demo里面都写了很 详细的注释,这只是我们项目中用到音频播放这一块时写的一个小demo,有兴趣的朋友可以看一下,写的不好不喜勿喷。

参考文章链接:http://www.cnblogs.com/kenshincui/p/4186022.html

demo地址

你可能感兴趣的:(学习笔记----音频开发基础)