一.前言

首先做一个项目我们最好先分析我们要做哪些功能,按功能模块一个个划分好结构。每个功能模块都有相对应的职责。切入正题,我做的这款音乐播放器,实现的是播放本地音乐。有以下几个要点:
1.如何实现播放音乐?
2.如何切换当前正在播放的音乐资源?
3.如何监听音乐播放器的各种状态(播放器状态,播放的进度,缓冲的进度,播放是否完成)?
4.如何手动监控播放进度?
5.如何在后台模式下或者锁屏模式下播放音乐、显示音乐播放信息和远程操控音乐?

二.网络音乐播放器的核心技术点

iOS自带的AVFoundation框架的AVPlayer类,KVO和KVC,通知机制,远程控制,SDWebImage

三.开始工作

1.创建应用程序

导入图片资源并设置icon图标,command+r运行程序后,再按command + shift + h 可以看到icon图片,如下所示

2.使用storyboard搭建主界面

storyboard是主流的开发方式,开发速度快,降低理解难度。如下所示:

3.界面的专辑图片的旋转效果

我们容易想到用计时器,但是你是否遇到过,self 强引用NStimer,NStimer对self强引用。这里互相强引用不释放。如果用__weak typeof(self) wself = self;解决,这是有bu g的,因为你不知道self何时会释放,加入timer在运行时,self就释放了会引起野指针错误。因此最有效的解决办法如下:

#pragma mark 专辑图片的旋转
//实现图片的旋转效果    弧度 = 度数 / 180 * M_PI
-(void)rotate {_musicIcon.transform = CGAffineTransformRotate(_musicIcon.transform, 0.5/180 * M_PI );
}-(void)reloadUI:(MusicModel*)model
{self.musicName.text = model.name;self.artist.text = model.artist;[self.musicIcon sd_setImageWithURL:[NSURL URLWithString:model.cover]];//创建一个定时器,每隔一段时间去访问rotate方法self.rotatingTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(rotate) userInfo:nil repeats:YES];[[NSRunLoop currentRunLoop] addTimer:_rotatingTimer forMode:NSRunLoopCommonModes];self.duration.text = model.duration;self.playBtn.selected = YES;self.playSlider.value = 0;   self.loadTimeProgress.progress = 0;
}//**在视图消失时,要释放计时器**
- (void)viewWillDisappear:(BOOL)animated{[self.rotatingTimer invalidate];self.rotatingTimer = nil;
}

4.导入AVFoundation框架,创建AVPlayer播放器

- (void)viewDidLoad {[super viewDidLoad];[self loadData];  //加载数据,把数据转成模型,存放在dataSource数组中self.currentIndex = 0;[self playBtnAction:self.playBtn];#pragma mark - 加载数据,存在数组里
-(void)loadData
{NSString *path = [[NSBundle mainBundle] pathForResource:@"music" ofType:@"json"];NSData *data = [NSData dataWithContentsOfFile:path];//反序列化NSArray *datas =  [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];//字典转模型for (NSDictionary *dic in datas) {MusicModel *model = [[MusicModel alloc] initWithDic:dic];[self.dataSource addObject:model];   //把模型存到数组里}
}//播放,播放音乐时要监听音乐的状态,进而调用playWithUrl:
- (IBAction)playBtnAction:(UIButton *)sender
{if (!sender.selected) {[self playWithUrl:self.dataSource[self.currentIndex]];sender.selected = YES;}else{[self.player pause];[self removePlayStatus];[self removePlayLoadTime];self.currentModel = nil;sender.selected = NO;}}#pragma mark- 音乐播放相关
//播放音乐
-(void)playWithUrl:(MusicModel*)model
{AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:model.url]];//替换当前音乐资源[self.player replaceCurrentItemWithPlayerItem:item];//刷新界面UI[self reloadUI:model];//监听音乐播放完成通知[self addNSNotificationForPlayMusicFinish];//开始播放[self.player play];//监听播放器状态[self addPlayStatus];//监听音乐缓冲进度[self addPlayLoadTime];//监听音乐播放的进度[self addMusicProgressWithItem:item];//记录当前播放音乐的索引self.currentIndex = [model.Id integerValue];self.currentModel = model;//音乐锁屏信息展示[self setupLockScreenInfo];}

我们来一个个分析playWithUrl:里的方法

1)替换当前音乐

[self.player replaceCurrentItemWithPlayerItem:item];

2)刷新界面UI

上面讲过的设置定时器的那一部分,重新设置歌名,歌手,专辑图片,音乐播放的当前时间和总时间,音乐播放进度和缓冲进度,按钮的状态

-(void)reloadUI:(MusicModel*)model
{self.musicName.text = model.name;self.artist.text = model.artist;[self.musicIcon sd_setImageWithURL:[NSURL URLWithString:model.cover]];//创建一个定时器,每隔一段时间去访问rotate方法self.rotatingTimer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:@selector(rotate) userInfo:nil repeats:YES];[[NSRunLoop currentRunLoop] addTimer:_rotatingTimer forMode:NSRunLoopCommonModes];self.duration.text = model.duration;self.playBtn.selected = YES;self.playSlider.value = 0;self.loadTimeProgress.progress = 0;}

3)监听音乐播放完成通知

#pragma mark - NSNotification
-(void)addNSNotificationForPlayMusicFinish
{[[NSNotificationCenter defaultCenter] removeObserver:self];//给AVPlayerItem添加播放完成通知,播放完成放下一首的通知[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:_player.currentItem];
}-(void)playFinished:(NSNotification*)notification
{//播放下一首[self nextBtnAction:nil];
}

4).开始播放

//开始播放[self.player play];

5).监听播放器状态

#pragma mark - 监听音乐各种状态
//通过KVO监听播放器状态
-(void)addPlayStatus
{[self.player.currentItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
}//移除监听播放器状态1
-(void)removePlayStatus
{if (self.currentModel == nil) {return;}[self.player.currentItem removeObserver:self forKeyPath:@"status"];
}

6).监听音乐缓冲进度

//KVO监听音乐缓冲状态
-(void)addPlayLoadTime
{[self.player.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];}
//移除监听音乐缓冲状态
-(void)removePlayLoadTime
{if (self.currentModel == nil) {return;}[self.player.currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
}

7).监听音乐播放的进度

   //监听音乐播放的进度
-(void)addMusicProgressWithItem:(AVPlayerItem *)item
{//移除监听音乐播放进度[self removeTimeObserver];__weak typeof(self) weakSelf = self;self.timeObserver =  [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {//当前播放的时间float current = CMTimeGetSeconds(time);//总时间float total = CMTimeGetSeconds(item.duration);if (current) {float progress = current / total;//更新播放进度条weakSelf.playSlider.value = progress;weakSelf.currentTime.text = [weakSelf timeFormatted:current];}}];}

8).观察者回调

//观察者回调
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{if ([keyPath isEqualToString:@"status"]) {switch (self.player.status) {case AVPlayerStatusUnknown:{NSLog(@"未知转态");}break;case AVPlayerStatusReadyToPlay:{NSLog(@"准备播放");}break;case AVPlayerStatusFailed:{NSLog(@"加载失败");}break;default:break;}}if ([keyPath isEqualToString:@"loadedTimeRanges"]) {NSArray * timeRanges = self.player.currentItem.loadedTimeRanges;//本次缓冲的时间范围CMTimeRange timeRange = [timeRanges.firstObject CMTimeRangeValue];//缓冲总长度NSTimeInterval totalLoadTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration);//音乐的总时间NSTimeInterval duration = CMTimeGetSeconds(self.player.currentItem.duration);//计算缓冲百分比例NSTimeInterval scale = totalLoadTime/duration;//更新缓冲进度条self.loadTimeProgress.progress = scale;}
}

9).记录当前音乐播放的索引

//记录当前播放音乐的索引self.currentIndex = [model.Id integerValue];self.currentModel = model;

10).音乐锁屏信息 [self setupLockScreenInfo];

#pragma mark - 设置锁屏信息//音乐锁屏信息展示
- (void)setupLockScreenInfo
{// 1.获取锁屏中心MPNowPlayingInfoCenter *playingInfoCenter = [MPNowPlayingInfoCenter defaultCenter];//初始化一个存放音乐信息的字典NSMutableDictionary *playingInfoDict = [NSMutableDictionary dictionary];// 2、设置歌曲名if (self.currentModel.name) {[playingInfoDict setObject:self.currentModel.name forKey:MPMediaItemPropertyAlbumTitle];}// 设置歌手名if (self.currentModel.artist) {[playingInfoDict setObject:self.currentModel.artist 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];
}//获取远程网络图片,如有缓存取缓存,没有缓存,远程加载并缓存
-(UIImage*)getMusicImageWithMusicId:(MusicModel*)model
{UIImage *image;NSString *key = [model.Id stringValue];UIImage *cacheImage = self.musicImageDic[key];if (cacheImage) {image = cacheImage;}else{//这里用了非常规的做法,仅用于demo快速测试,实际开发不推荐,会堵塞主线程//建议加载歌曲时先把网络图片请求下来再设置NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:model.cover]];image =  [UIImage imageWithData:data];if (image) {[self.musicImageDic setObject:image forKey:key];}}return image;
}//监听远程交互方法
- (void)remoteControlReceivedWithEvent:(UIEvent *)event
{switch (event.subtype) {//播放case UIEventSubtypeRemoteControlPlay:{[self.player play];}break;//停止case UIEventSubtypeRemoteControlPause:{[self.player pause];}break;//下一首case UIEventSubtypeRemoteControlNextTrack:[self nextBtnAction:nil];break;//上一首case UIEventSubtypeRemoteControlPreviousTrack:[self lastBtnAction:nil];break;default:break;}
}

附上github地址:(https://github.com/lizichenzi/onlionmusic/tree/master)

非常感谢
http://www.jianshu.com/p/31644a7f581c

iOS 音乐播放器demo讲解相关推荐

  1. iOS 开发 - 播放器Demo开发记录

    iOS 开发 - 播放器Demo开发记录 标签: ios 2014-12-22 14:14 518人阅读 评论(0) 收藏 举报  分类: Mac OS X(50)  版权声明:本文为博主原创文章,未 ...

  2. 【Android-Service】基于MVP的音乐播放器demo实现思路(附源码)

    最近在学习service相关的内容,在该部分的学习过程中,根据学习视频中的内容进行了总结归纳,以下是音乐播放器demo的开发思路,具体步骤及源码: 有关MVP框架的内容可看: link. 实现效果: ...

  3. mplayer音乐软件_MPlayer音乐播放器项目讲解

    MPlayer音乐播放器项目讲解 一.简要介绍MPlyer音乐播放器 MPlayer是一款开源多媒体播放器,以GNU通用公共许可证发布.此款软件可在各主流操作系统使用,例如Linux和其他类Unix系 ...

  4. ios音乐播放器-仿QQ音乐

    这篇文章主要写一个iOS系统下的音乐播放器 , 包括简单的仿QQ音乐播放器界面.音乐播放.歌词解析.后台控制等  ,如果你正好需要 , 希望你看完后能够对你的提升有所帮助 , 当然,阅读中如果发现什么 ...

  5. 卡拉OK效果的实现-iOS音乐播放器

    自己编写的音乐播放器偶然用到这个模块,发现没有思路,而且上网搜了搜,关于这方面的文章不是很多,没找到满意的结果,然后自己也是想了想,最终实现了这种效果,想通了发现其实很简单. 直接上原理: 第一种: ...

  6. 基于IOS音乐播放器在线歌词同步小程序系统(音乐小程序)

    目 录 目 录 1 摘 要 3 Abstract 4 1 导论 6 1.1 背景问题 6 1.2 选题意义 6 1.3 本文内容 7 2 核心功能 9 2.1 功能调研 9 2.2 可行性分析 12 ...

  7. Android 简单的本地音乐播放器Demo

    一个小小的本地音乐播放器,花了几个小时完成的,帮朋友做的毕业作业. 包含基本播放功能.进度条显示和拖拽.时间倒计.后台播放.一键刷新.收藏操作.单独播放收藏页的音乐.... 代码没有一定的模式和注释, ...

  8. android+仿ios+音乐播放器,iOS简单的音乐播放器(仿QQ音乐)

    AVPlayer实现基本的播放,暂停,上一首,下一首,调节音量,调节进度等,正在学习的新人可以看下,有什么不足可以互相学习,谢谢支持 qq音乐.gif 这个是我写的一个简单的低仿QQ音乐, 如果你也喜 ...

  9. iOS音乐播放器小技巧

    在没有网络的情况下,音频的后台播放比较简单,google一下可以搜到很多资料,但是如果每次歌曲的请求都是通过网络,就不成了,有时可以也扛不了几首,这里总结下实现方法,可以实现像电台一样的功能,后台播放 ...

  10. iOS音乐播放器的后台播放和远程控制

    iOS的后台任务一直有很严格的限制,一般情况下只有几类情况能使用后台任务,下面介绍其中的一种 音乐的后台播放. 1.要支持后台任务,首先你得告诉系统你需要后台任务,所以你首先在Info.plist文件 ...

最新文章

  1. c# 元组Tuple
  2. python_day21面向对象的进阶(反射,内置方法,)
  3. pyecharts离线使用说明
  4. jdk只有一个java进程_JDK 10:从Java访问Java应用程序的进程ID
  5. 响应式设计之 —— 视口
  6. Nginx应用场景之反向代理
  7. java标识符和关键字相关概念
  8. python自动化测试框架有哪几种_Python自动化测试-Unittest单元测试框架详解
  9. 11_HTML5_Local_Storage本地存储
  10. 1、css引入的方式有哪些?_低氮燃烧技术都有哪些?
  11. 从四个角度揭密Windows Server 2008技术
  12. 面试题--------5、==与equals的区别
  13. 记住看小电影前一定要检查一下域名是不是 HTTPS 的,不然……
  14. Python使用wxpython制作简单文本编辑器
  15. Spring中过滤器和拦截器
  16. fmincon函数求极值
  17. 苹果手机各种尺寸列表
  18. 51单片机英文缩写全称
  19. 外接显示器如何调整亮度
  20. 昨天去某大厂面试,居然让我做四则运算,还好我够机灵。

热门文章

  1. ORA-39126\ORA-06502\LPX-00225: end-element tag “HIST_GRAM_LIST_ITEM“ does not match start-element ta
  2. 网络体系结构的概念 - 网络协议TCP - 红黑联盟
  3. 使用tf.image.resize() 和tf.image.resize_with_pad()调整图像大小
  4. private111
  5. 【段子来袭(已笑晕)】cv领域会议段子
  6. 《5G无线技术演进白皮书》发布!
  7. 详解Java编码与解码以及常见的编码表,灵活处理乱码问题
  8. 平面设计的核心本质是什么
  9. 根据域名查询IP地址的网站推荐
  10. 51单片机最小系统电路图