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

参考链接

一、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 不支持网络音频在线播放,你可以使用一些第三方框架来实现在线播放例如AudioStreamerFreeStreamer。如果不想使用第三方的框架,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地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容