好久没有写东西了,最近加班太严重,今天抽空把用到的音乐播放器DOUAudioStreamer整理一下,由于项目之前用的是AVPlayer,这个也可以,但是就是要先缓存一段时间再播放,老板看了之后要求,要边缓存边播放(有网时,点击播放按钮就立刻播放),怎么不早说!怎么不早说!怎么不早说!还能怎样?只能原谅他,继续敲代码。。。。。。(还是直接上代码吧)
一、导入三方库
pod 'DOUAudioStreamer'
或者GitHup下载地址: https://github.com/douban/DOUAudioStreamer
二、使用
1.从demo中获取NAKPlaybackIndicatorView文件和MusicIndicator.h和MusicIndicator.m 文件,并导入头文件
//音乐播放#import "DOUAudioStreamer.h"#import "NAKPlaybackIndicatorView.h"#import "MusicIndicator.h"#import "Track.h"
如图:
2.创建一个Track类,用于音乐播放的URL存放
3.需要的界面.h中,添加DOUAudioStreamer,并用单利来初始化
+ (instancetype)sharedInstance ;@property (nonatomic, strong) DOUAudioStreamer *streamer;
如图:
在.m中实现:
static void *kStatusKVOKey = &kStatusKVOKey;static void *kDurationKVOKey = &kDurationKVOKey;static void *kBufferingRatioKVOKey = &kBufferingRatioKVOKey;@property (strong, nonatomic) MusicIndicator *musicIndicator;@property (nonatomic, strong) Track *audioTrack;+ (instancetype)sharedInstance { static HYNEntertainmentController *_sharedMusicVC = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _sharedMusicVC = [[HYNEntertainmentController alloc] init]; _sharedMusicVC.streamer = [[DOUAudioStreamer alloc] init]; }); return _sharedMusicVC;}
播放按钮事件
#pragma mark ---音乐播放按钮-(void)playMusicStart:(UIButton *)sender{ //通过按钮获取cell MusicCollectionViewCell *musicCell = (MusicCollectionViewCell *)[[sender superview] superview]; if(_playFirst == 0){//_playFirst == 0首次播放,其他为暂停 NSURL *url = [NSURL URLWithString:HttpImgUrl(musicCell.model.musicUrl)]; _audioTrack.audioFileURL = url; @try { [self removeStreamerObserver]; } @catch(id anException){ } //在DOUAudioStreamer进行播放时,必须先置为nil _streamer = nil; _streamer = [DOUAudioStreamer streamerWithAudioFile:_audioTrack]; [self addStreamerObserver]; [_streamer play]; } if([_streamer status] == DOUAudioStreamerPaused || [_streamer status] == DOUAudioStreamerIdle){ [sender setBackgroundImage:[UIImage imageNamed:@"music_play_icon"] forState:UIControlStateNormal]; [_streamer play]; }else{ [sender setBackgroundImage:[UIImage imageNamed:@"music_stop_icon"] forState:UIControlStateNormal]; [_streamer pause]; } _playFirst++; }
对DOUAudioStreamer添加监听
- (void)addStreamerObserver { [_streamer addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:kStatusKVOKey]; [_streamer addObserver:self forKeyPath:@"duration" options:NSKeyValueObservingOptionNew context:kDurationKVOKey]; [_streamer addObserver:self forKeyPath:@"bufferingRatio" options:NSKeyValueObservingOptionNew context:kBufferingRatioKVOKey]; }/// 播放器销毁- (void)dealloc{ if (_streamer !=nil) { [_streamer pause]; [_streamer removeObserver:self forKeyPath:@"status" context:kStatusKVOKey]; [_streamer removeObserver:self forKeyPath:@"duration" context:kDurationKVOKey]; [_streamer removeObserver:self forKeyPath:@"bufferingRatio" context:kBufferingRatioKVOKey]; _streamer =nil; } }- (void)removeStreamerObserver { [_streamer removeObserver:self forKeyPath:@"status"]; [_streamer removeObserver:self forKeyPath:@"duration"]; [_streamer removeObserver:self forKeyPath:@"bufferingRatio"];}- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == kStatusKVOKey) { [self performSelector:@selector(updateStatus) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO]; } else if (context == kDurationKVOKey) { [self performSelector:@selector(updateSliderValue:) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO]; } else if (context == kBufferingRatioKVOKey) { [self performSelector:@selector(updateBufferingStatus) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO]; } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; }}- (void)updateSliderValue:(id)timer { }-(void)updateBufferingStatus{ }- (void)updateStatus { //self.musicIsPlaying = NO; _musicIndicator.state = NAKPlaybackIndicatorViewStateStopped; switch ([_streamer status]) { case DOUAudioStreamerPlaying: // self.musicIsPlaying = YES; _musicIndicator.state = NAKPlaybackIndicatorViewStatePlaying; break; case DOUAudioStreamerPaused: break; case DOUAudioStreamerIdle: break; case DOUAudioStreamerFinished:播放完成,可以修改播放按钮,也可以进行下一曲播放或重复播放 { UIButton *btn = (UIButton *)[self.view viewWithTag:2000]; [btn setBackgroundImage:[UIImage imageNamed:@"music_stop_icon"] forState:UIControlStateNormal]; } break; case DOUAudioStreamerBuffering: _musicIndicator.state = NAKPlaybackIndicatorViewStatePlaying; break; case DOUAudioStreamerError: break; } }
这样就能播放了。
三、锁屏时的音乐显示、拔出耳机后暂停播放、监听音频打断事件
-(void)viewWillAppear:(BOOL)animated{ [super viewWillAppear:animated];// 接受远程控制 [self becomeFirstResponder]; [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];}//这个不能忘记了-(BOOL)canBecomeFirstResponder{ return YES;}- (void)viewDidLoad { [super viewDidLoad];//音乐播放器 [self initPlayer];}#pragma mark =========================音乐播放==============================//音乐播放器-(void)initPlayer{ _audioTrack = [[Track alloc] init]; AVAudioSession *session = [AVAudioSession sharedInstance]; [session setActive:YES error:nil]; [session setCategory:AVAudioSessionCategoryPlayback error:nil]; //让app支持接受远程控制事件 [[UIApplication sharedApplication] beginReceivingRemoteControlEvents]; //添加通知,拔出耳机后暂停播放 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChange:) name:AVAudioSessionRouteChangeNotification object:nil]; // 监听音频打断事件 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioSessionWasInterrupted:) name:AVAudioSessionInterruptionNotification object:session]; }// 监听音频打断事件- (void)audioSessionWasInterrupted:(NSNotification *)notification{ //播放的音乐被打断时,停止音乐播放 if (AVAudioSessionInterruptionTypeBegan == [notification.userInfo[AVAudioSessionInterruptionTypeKey] intValue]) { [_streamer pause]; UIButton *btn = (UIButton *)[self.view viewWithTag:2000]; [btn setBackgroundImage:[UIImage imageNamed:@"music_stop_icon"] forState:UIControlStateNormal]; } else if (AVAudioSessionInterruptionTypeEnded == [notification.userInfo[AVAudioSessionInterruptionTypeKey] intValue]) { }}// 拔出耳机后暂停播放-(void)routeChange:(NSNotification *)notification{ NSDictionary *dic=notification.userInfo; int changeReason= [dic[AVAudioSessionRouteChangeReasonKey] intValue]; //等于AVAudioSessionRouteChangeReasonOldDeviceUnavailable表示旧输出不可用 if (changeReason==AVAudioSessionRouteChangeReasonOldDeviceUnavailable) { AVAudioSessionRouteDescription *routeDescription=dic[AVAudioSessionRouteChangePreviousRouteKey]; AVAudioSessionPortDescription *portDescription= [routeDescription.outputs firstObject]; //原设备为耳机则暂停 if ([portDescription.portType isEqualToString:@"Headphones"]) { [_streamer pause]; UIButton *btn = (UIButton *)[self.view viewWithTag:2000]; [btn setBackgroundImage:[UIImage imageNamed:@"music_stop_icon"] forState:UIControlStateNormal]; } }}//锁屏时音乐显示(这个方法可以在点击播放时,调用传值)- (void)setupLockScreenInfoWithSing:(NSString *)sign WithSigner:(NSString *)signer WithImage:(UIImage *)image{ // 1.获取锁屏中心 MPNowPlayingInfoCenter *playingInfoCenter = [MPNowPlayingInfoCenter defaultCenter]; //初始化一个存放音乐信息的字典 NSMutableDictionary *playingInfoDict = [NSMutableDictionary dictionary]; // 2、设置歌曲名 if (sign) { [playingInfoDict setObject:sign forKey:MPMediaItemPropertyAlbumTitle]; } // 设置歌手名 if (signer) { [playingInfoDict setObject:signer forKey:MPMediaItemPropertyArtist]; } // 3设置封面的图片 // UIImage *image = [self getMusicImageWithMusicId:self.currentModel]; if (image) { MPMediaItemArtwork *artwork = [[MPMediaItemArtwork alloc] initWithImage:image]; [playingInfoDict setObject:artwork forKey:MPMediaItemPropertyArtwork]; } // 4设置歌曲的总时长 // [playingInfoDict setObject:self.currentModel.detailDuration forKey:MPMediaItemPropertyPlaybackDuration]; //音乐信息赋值给获取锁屏中心的nowPlayingInfo属性 playingInfoCenter.nowPlayingInfo = playingInfoDict; // 5.开启远程交互 [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];}//锁屏时操作- (void)remoteControlReceivedWithEvent:(UIEvent *)receivedEvent { if (receivedEvent.type == UIEventTypeRemoteControl) { UIButton *sender = (UIButton *)[self.view viewWithTag:2000]; switch (receivedEvent.subtype) {//判断是否为远程控制 case UIEventSubtypeRemoteControlPause: // [[HYNEntertainmentController sharedInstance].streamer pause]; [_streamer pause]; [sender setBackgroundImage:[UIImage imageNamed:@"music_stop_icon"] forState:UIControlStateNormal]; break; case UIEventSubtypeRemoteControlStop: break; case UIEventSubtypeRemoteControlPlay: //[[HYNEntertainmentController sharedInstance].streamer play]; [_streamer play]; [sender setBackgroundImage:[UIImage imageNamed:@"music_play_icon"] forState:UIControlStateNormal]; break; case UIEventSubtypeRemoteControlTogglePlayPause: break; case UIEventSubtypeRemoteControlNextTrack: break; case UIEventSubtypeRemoteControlPreviousTrack: break; default: break; } }}
四、对网络进行判断,当没有网络时,停止音乐播放并切换播放按钮图片
使用的是Reachability进行判断
pod 'Reachability'
//网络判断#import "Reachability.h"@property (nonatomic, strong) Reachability *hostReachability;
在viewDidLoad里注册通知,进行判断
- (void)viewDidLoad { [super viewDidLoad]; //网络判断 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appReachabilityChanged:) name:kReachabilityChangedNotification object:nil]; // 检测指定服务器是否可达 NSString *remoteHostName = @"www.baidu.com"; self.hostReachability = [Reachability reachabilityWithHostName:remoteHostName]; [self.hostReachability startNotifier];}
页面消失时,注销网络判断通知
// 页面消失时候- (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [self.playerView resetPlayer]; [[NSNotificationCenter defaultCenter]removeObserver:self name:kReachabilityChangedNotification object:nil ]; }
网络状态判断
/// 当网络状态发生变化时调用- (void)appReachabilityChanged:(NSNotification *)notification{ Reachability *reach = [notification object]; if([reach isKindOfClass:[Reachability class]]){ NetworkStatus status = [reach currentReachabilityStatus]; if (reach == self.hostReachability) { if ([reach currentReachabilityStatus] == NotReachable) { NSLog(@"网络网络不可达"); _playFirst = 0; [_streamer stop]; UIButton *btn = (UIButton *)[self.view viewWithTag:2000]; [btn setBackgroundImage:[UIImage imageNamed:@"music_stop_icon"] forState:UIControlStateNormal]; } else if (status == ReachableViaWiFi) { NSLog(@"WIFI"); [_collectionView reloadData]; } else if (status == ReachableViaWWAN) { [_collectionView reloadData]; } } }}
整体图片:
上图为未播放
上图为播放中
上图为锁屏时状态
应该没有什么要添加的了,暂时告一段落,有不足之处,可以留言,谢谢!
(不是同一个项目)最近在音频播放时,要求断点续播,这个功能DOUAudioStreamer问题里也有提到,要在 kStatusKVOKey 的 Playing 触发之后调整 currentTime 才可以,也就是在代码中updateStatus方法里可以设置
如图
代码:
这个方法中的updateStatus方法里进行设置- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == kStatusKVOKey) { [self performSelector:@selector(updateStatus) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO]; } else if (context == kDurationKVOKey) { [self performSelector:@selector(updateSliderValue:) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO]; } else if (context == kBufferingRatioKVOKey) { [self performSelector:@selector(updateBufferingStatus) onThread:[NSThread mainThread] withObject:nil waitUntilDone:NO]; } else { [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; }}- (void)updateStatus { switch ([_audioPlayer.streamer status]) { case DOUAudioStreamerPlaying: //设置播放按钮 [self playButtonPlayImage]; NSLog(@"33333"); break; case DOUAudioStreamerPaused: //设置暂停按钮 [self stopButtonPlayImage]; NSLog(@"444444"); break; case DOUAudioStreamerIdle: break; //播放完成,可以修改播放按钮,也可以进行下一曲播放或重复播放 case DOUAudioStreamerFinished: { //播放下一个 [self rightPlayClick]; } break; case DOUAudioStreamerBuffering: break; case DOUAudioStreamerError: break; } //断点续播设置 currentTime为秒 CourseModel *currentModel = self.listArr[self.playIndex]; //_audioPlayer.streamer.currentTime = 30;//测试设置30s _audioPlayer.streamer.currentTime = [currentModel.paly_progress_last floatValue];}