好久沒有寫東西了,最近加班太嚴重,今天抽空把用到的音樂播放器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];
}