AVPlayer播放网络音乐

下载地址:AVPlayer播放网络音乐

音乐播放并展示到界面上需要我们实现的功能如下:

1、(核心)播放器通过一个网络链接播放音乐
2、(基本)播放器的常用操作:暂停、播放、上一首、下一首等等
3、(基本)监听该音乐的播放进度、获取音乐的总时间、当前播放时间
4、(基本)监听改播放器状态:
     (1)媒体加载状态
     (2)数据缓冲状态
     (3)播放完毕状态
5、(可选)Remote Control控制音乐的播放
6、(可选)Now Playing Center展示正在播放的音乐

功能实现

我自己写了一个管理类 (里面封装了大部分播放逻辑)
已完成功能:播放、暂停、上下一首、单曲循环、随机播放、后台播放、锁屏信息。
第一次写这种管理类,可能会有点复杂,不太容易看懂 。 我把歌曲的信息也存在里面了。

.h 文件

#import <Foundation/Foundation.h>
#import <AVFoundation/AVFoundation.h>
#import "SongsMessageModel.h"

typedef NS_ENUM(NSInteger, playerType) {
    AVPlayerTypeNormal = 0,           //正常循环播放状态
    AVPlayerTypeRandom,                 //随机播放
    AVPlayerTypeSingle,                 //单曲循环
};

/**返回缓冲进度*/
typedef void(^loadTime)(NSString *str);
/**返回 当前进度、总时长、slider 最大值和当前值*/
typedef void(^returnTime)(NSString *current,NSString *total,float max,float value);
/**返回 AVPlayerItem的序号(换背景图、歌手、歌曲)*/
typedef void(^returnImage)(NSInteger index);

@interface AVPlayerManager : NSObject

/**存放AVplayerItem的数组*/
@property(nonatomic,strong)NSMutableArray *musicArray;

//**歌曲地址数组*/
@property(nonatomic,strong)NSMutableArray *songArray;// 是否进入的已存在的歌单!
/**海报数组*/
@property(nonatomic,strong)NSMutableArray *imageArray;
/**歌手数组*/
@property(nonatomic,strong)NSMutableArray *singerArray;
/**歌曲名数组*/
@property(nonatomic,strong)NSMutableArray *songNameArray;

/**AVPlayer播放器*/
@property (nonatomic, strong) AVPlayer *player;

/**播放模式*/
@property (nonatomic,assign) NSInteger playerType;

/**当前播放时间*/
@property(nonatomic,assign) float currentPlayTime;
/**当前缓冲时间*/
@property(nonatomic,assign)NSTimeInterval currentLoadTime;

/**slider最大值*/
@property(nonatomic,assign) float silderMaxValue;
/**slider当前值*/
@property(nonatomic,assign) float silderValue;
/***/
@property(nonatomic,strong) NSString *playTime;
/**总时长*/
@property(nonatomic,strong) NSString *totalTime;
/**缓冲提示*/
@property(nonatomic,strong) NSString *loadTime;

@property(nonatomic,strong)loadTime block;
@property(nonatomic,strong)returnTime block1;
@property(nonatomic,strong) returnImage block2;

/**通知 获取当前播放状态 控制按钮状态*/
@property(nonatomic,strong) NSNotificationCenter * center;
/**判断是否是从歌单列表进入播放器 还是 直接在进入播放器*/
@property(nonatomic,assign) BOOL listInto;


+ (instancetype)shareManager;
//**播放*/
- (void)musicPlayerWithArray:(NSArray *)musicArray andIndex:(NSInteger )index;
//**正在播放时   播放指定歌曲, */
- (void)musicPlayerWithIndex:(NSInteger )index;
//*播放
-(void)playMusic;
//*暂停
-(void)pasueMusic;
//**下一首*/
- (void)nextSong;
//**上一首*/
- (void)lastSong;
//**进度条 调节 */
- (void)playerProgressWithProgressFloat:(CGFloat)progressFloat;

//**移除观察者*/
-(void)removeObserver;

/**获取当前播放序号*/
-(NSInteger )getcurrentItem;

.m 文件

#import "AVPlayerManager.h"
#import <MediaPlayer/MediaPlayer.h>

@implementation AVPlayerManager
{
    id timeObserve;// 为 当前AVPlayerItem 添加观察者获取各种播放状态
}

+(instancetype)shareManager{
    static AVPlayerManager *manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [AVPlayerManager new];
    });
    return manager;
}

-(instancetype)init{
    if (self =[super init]) {
        _playerType = AVPlayerTypeNormal;
    }
    return self;
}

//播放器懒加载
-(AVPlayer *)player{
    if (!_player) {
        _player = [AVPlayer new];
    }
    return _player;
}

-(NSMutableArray *)musicArray{
    if (!_musicArray) {
        _musicArray = [NSMutableArray array];
        
        _center = [NSNotificationCenter defaultCenter];
    }
    return _musicArray;
}

//播放指定url
- (void)musicPlayerWithURL:(NSURL *)playerItemURL{
    
    //创建要播放的资源
    AVPlayerItem *playerItem = [[AVPlayerItem alloc]initWithURL:playerItemURL];
    //添加观察者
    //播放当前资源
    [self.player replaceCurrentItemWithPlayerItem:playerItem];
}


-(void)musicPlayerWithIndex:(NSInteger)index{
     // 播放AVPlayerItem数组中指定序号歌曲 (此操作之前需要将上一个item 的 观察者移除 并将 seekTime 设置为0 确保下次播放从头开始)
    [self.player replaceCurrentItemWithPlayerItem:self.musicArray[index]];
    
    self.block2([self.musicArray indexOfObject:self.player.currentItem]);
    
    [self addObserver];
    
}

//重新添加新的歌单列表进来
- (void)musicPlayerWithArray:(NSArray *)musicArray andIndex:(NSInteger )index{
    //首先移除之前的歌单列表 再 重新添加
    [self.songArray removeAllObjects];
    [self.songArray addObjectsFromArray:musicArray];
    
    for (NSString * obj in musicArray) {
        AVPlayerItem *songItem = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:obj]];
        [self.musicArray addObject:songItem];
    }
    //播放歌单列表序号对应的歌曲
    [self.player replaceCurrentItemWithPlayerItem:self.musicArray[index]];
    //对当前item 添加观察者
    [self addObserver];
}

//播放
-(void)playMusic{
    [self.player play];
}
//暂停
-(void)pasueMusic{
    [self.player pause];
}

//上一首
-(void)lastSong{
    
    [self removeObserver];//先移除当前 观察者
    
    [self.player.currentItem seekToTime:(kCMTimeZero) completionHandler:^(BOOL finished) {
        
        if (self.playerType == AVPlayerTypeNormal) {//顺序循环播放
            //判断当前歌曲是否是第一首歌曲,如果是跳转到最后一首歌
            if ([self.musicArray indexOfObject:self.player.currentItem] ==0) {
                [self.player replaceCurrentItemWithPlayerItem:self.musicArray.lastObject];
            }else{
                [self.player replaceCurrentItemWithPlayerItem:self.musicArray[[self.musicArray  indexOfObject:self.player.currentItem]-1]];
            }
            
        }else if (self.playerType == AVPlayerTypeRandom){//随机播放
            NSInteger random = arc4random_uniform((int)self.musicArray.count);
            NSLog(@"%ld",random);
            
            [self.player replaceCurrentItemWithPlayerItem:self.musicArray[arc4random_uniform((int)self.musicArray.count)]];
        }else if (self.playerType ==AVPlayerTypeSingle){ //单曲循环在自动切换歌曲时进行判断
            //判断当前歌曲是否是第一首歌曲,如果是跳转到最后一首歌
            if ([self.musicArray indexOfObject:self.player.currentItem] ==0) {
                [self.player replaceCurrentItemWithPlayerItem:self.musicArray.lastObject];
            }else{
                [self.player replaceCurrentItemWithPlayerItem:self.musicArray[[self.musicArray  indexOfObject:self.player.currentItem]-1]];
            }
        }
        
        [self playMusic];
        //放回对应歌曲的信息
         self.block2([self.musicArray indexOfObject:self.player.currentItem]);
        [self addObserver];//添加观察者
    }];
}
//下一首
-(void)nextSong{
    [self removeObserver];
    
    [self.player.currentItem seekToTime:(kCMTimeZero) completionHandler:^(BOOL finished) {
       
        if (self.playerType == AVPlayerTypeNormal) {
            //判断当前歌曲是否是最后一首歌曲,如果是跳转到第一首歌
            if ([self.musicArray indexOfObject:self.player.currentItem]+1 == self.musicArray.count) {
                [self.player replaceCurrentItemWithPlayerItem:self.musicArray.firstObject];
            }else{
                [self.player replaceCurrentItemWithPlayerItem:self.musicArray[[self.musicArray  indexOfObject:self.player.currentItem]+1]];
            }
            
        }else if (self.playerType == AVPlayerTypeRandom){
          [self.player replaceCurrentItemWithPlayerItem:self.musicArray[arc4random_uniform((int)self.musicArray.count)]];
        }else if (self.playerType == AVPlayerTypeSingle){
            //判断当前歌曲是否是最后一首歌曲,如果是跳转到第一首歌
            if ([self.musicArray indexOfObject:self.player.currentItem]+1 == self.musicArray.count) {
                [self.player replaceCurrentItemWithPlayerItem:self.musicArray.firstObject];
            }else{
                [self.player replaceCurrentItemWithPlayerItem:self.musicArray[[self.musicArray  indexOfObject:self.player.currentItem]+1]];
            }
        }
        
        [self playMusic];
        self.block2([self.musicArray indexOfObject:self.player.currentItem]);
        [self addObserver];
    }];
    
}

//获取 当前播放歌曲在AVPlayerItem中的 序号
-(NSInteger )getcurrentItem{
    return [self.musicArray indexOfObject:self.player.currentItem];
}

//将slider的值传入使 歌曲进度 前进或后退
-(void)playerProgressWithProgressFloat:(CGFloat)progressFloat{
    if (progressFloat > self.currentLoadTime) {
        NSDictionary *dict = @{@"播放状态":@0};
        [self.center postNotificationName:@"playOrPasue" object:self userInfo:dict];
    }
    [self.player seekToTime:CMTimeMake(progressFloat, 1.0)];
}

//接受播放完成的通知 开始下一首
- (void)playbackFinished:(NSNotification *)notice {
    //判断是否是单曲循环  是的话 移除观察者 将当前进度调为0 重新播放
    if (self.playerType == AVPlayerTypeSingle) {
        
       [self removeObserver];
       [self.player.currentItem seekToTime:kCMTimeZero];
        
       [self musicPlayerWithIndex:[self getcurrentItem]];
       [self playMusic];
    }else{
       [self nextSong];
    }
    
}

//为当前播放的item 添加观察者
-(void)addObserver{
    __weak typeof(self) weekSelf = self;
    __weak AVPlayer *weekPlayer = self.player;
    
    //观察该item 是否能播放
    [self.player.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    //观察该item 加载进度
    [self.player.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
    
    //AVPlayer 自带方法:返回歌曲 播放进度 信息
    timeObserve = [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
        [weekSelf updateLockedScreenMusic:[weekSelf getcurrentItem]];
        
        float current = CMTimeGetSeconds(time);
        float total = CMTimeGetSeconds(weekPlayer.currentItem.duration);
        weekSelf.currentPlayTime = current;
        if (total > 0) {
            weekSelf.silderMaxValue = total;
        }else{
            weekSelf.silderMaxValue = 300;
        };
        weekSelf.silderValue = current;

        if (current) {
            weekSelf.playTime =[weekSelf timetranstation:[NSString stringWithFormat:@"%.f",current]];
            weekSelf.totalTime =[weekSelf timetranstation:[NSString stringWithFormat:@"%.f",total]];
            
            //判断歌曲播完 后发出通知 -132行 接受通知
            if (current == total) { //    total - current <=1  有时会导致不能正常切换歌曲
                [[NSNotificationCenter defaultCenter] addObserver:weekSelf selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:weekPlayer.currentItem];
            }
            
            //返回 播放界面所需要的内容。
            weekSelf.block1(weekSelf.playTime,weekSelf.totalTime,weekSelf.silderMaxValue,weekSelf.silderValue);
        }
    }];
}

//移除对当前item 的观察 通知
-(void)removeObserver{
    if (timeObserve) {
        [[NSNotificationCenter defaultCenter] removeObserver:self];
        [self.player.currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
        [self.player.currentItem removeObserver:self forKeyPath:@"status"];
        [self.player removeTimeObserver:timeObserve];
        
    }
}

//观察者
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    AVPlayerItem * songItem = object;
    NSDictionary *dict = @{@"播放状态":@1};
    if ([keyPath isEqualToString:@"status"]) {
        switch (self.player.status) {
            case AVPlayerStatusUnknown:
                //  BASE_INFO_FUN(@"KVO:未知状态,此时不能播放");
                break;
            case AVPlayerStatusReadyToPlay:
                // self.status = SUPlayStatusReadyToPlay;
                //  BASE_INFO_FUN(@"KVO:准备完毕,可以播放");
                [self.center postNotificationName:@"playOrPasue" object:self userInfo:dict];
                [self.player play];
                break;
            case AVPlayerStatusFailed:
                //  BASE_INFO_FUN(@"KVO:加载失败,网络或者服务器出现问题");
                break;
            default:
                break;
        }
    }else  if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
        NSArray * array = songItem.loadedTimeRanges;
        CMTimeRange timeRange = [array.firstObject CMTimeRangeValue]; //本次缓冲的时间范围
        NSTimeInterval totalBuffer = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration); //缓冲总长度
        self.currentLoadTime = totalBuffer;
        
        float total = CMTimeGetSeconds(songItem.duration);
      //  NSLog(@"共缓冲%.0f%",totalBuffer/total*100); 计算缓冲进度
        self.loadTime =[NSString stringWithFormat:@"正在缓冲%.0f%",totalBuffer/total*100];
        if ([[NSString stringWithFormat:@"%.0f",totalBuffer/total*100] isEqualToString:@"100"]) {
            self.loadTime =@"";
        }else{
            self.loadTime =[NSString stringWithFormat:@"正在缓冲%.0f%",totalBuffer/total*100];
        }
        CGFloat currentPlayTime =  self.currentPlayTime;
       
        //缓冲完成时间大于当前播放时间5s时通知播放器继续播放 否则暂停
        if (totalBuffer > currentPlayTime +5) {
//            NSDictionary *dict = @{@"播放状态":@1};
//            [self.center postNotificationName:@"playOrPasue" object:self userInfo:dict];
        }else{
            NSDictionary *dict = @{@"播放状态":@0};
            [self.center postNotificationName:@"playOrPasue" object:self userInfo:dict];
        }
        
        NSLog(@"%f ----%f",total,totalBuffer);
        if (totalBuffer + 1 > total) {
            NSDictionary *dict = @{@"播放状态":@1};
            [self.center postNotificationName:@"playOrPasue" object:self userInfo:dict];
        }
        self.block(self.loadTime);
    }
}

#pragma 秒数转为分钟
-(NSString *)timetranstation:(NSString *)time{
    NSString *returnTime =[NSString string];
    NSInteger total = [time integerValue];
    
    returnTime =[NSString stringWithFormat:@"%02ld:%02ld",total/60,total%60];
    return returnTime;
}


#pragma mark - 锁屏时候的设置,效果需要在真机上才可以看到
- (void)updateLockedScreenMusic:(NSInteger )index{
    // 播放信息中心
    MPNowPlayingInfoCenter *center = [MPNowPlayingInfoCenter defaultCenter];
    
    // 初始化播放信息
    NSMutableDictionary *info = [NSMutableDictionary dictionary];
    // 专辑名称
//    info[MPMediaItemPropertyAlbumTitle] = [sel];
    // 歌手
    info[MPMediaItemPropertyArtist] = self.singerArray[index];
    // 歌曲名称
    info[MPMediaItemPropertyTitle] =  self.songNameArray[index];
    // 设置图片
    info[MPMediaItemPropertyArtwork] = [[MPMediaItemArtwork alloc] initWithImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@",self.imageArray[index]]]]]];
    // 设置持续时间(歌曲的总时间)
    [info setObject:[NSNumber numberWithFloat:CMTimeGetSeconds([self.player.currentItem duration])] forKey:MPMediaItemPropertyPlaybackDuration];
    // 设置当前播放进度
    [info setObject:[NSNumber numberWithFloat:CMTimeGetSeconds([self.player.currentItem currentTime])] forKey:MPNowPlayingInfoPropertyElapsedPlaybackTime];
    
    // 切换播放信息
    center.nowPlayingInfo = info;
    
}

第一次写,如果哪位大神耐心看了,希望多给一点指点,不足的地方指出来。我要多学习学习!!!

demo中的网络地址来自于 阿里星球 , 因为是在线播放,所以在真机上面跑会有点卡 ,希望大神指点!需要怎么处理?

参考链接:iOS音频篇:使用AVPlayer播放网络音乐

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

推荐阅读更多精彩内容