iOS从零开始学习直播之音频3.歌曲切换
上周迟到了,周末去参加OSC源创会了,还是有点启发的。但这不是重点,重点是 上一篇我只是实现了一首歌曲的在线播放,这肯定是不够的。这一篇博客主要是实现了多首歌曲的顺序播放以及上一首和下一首切换。
先看一下效果图
1.准备工作
(1)数据源
我把歌曲列表存在本地songList.json文件里。用FHAlbumModel管理歌曲。
FHAlbumModel.h
#import <Foundation/Foundation.h>
@interface FHAlbumModel : NSObject
@property (nonatomic, copy) NSString *lrclink; // 歌词
@property (nonatomic, copy) NSString *pic_big; // 背景图
@property (nonatomic, copy) NSString *artist_name; // 歌手
@property (nonatomic, copy) NSString *title; // 歌名
@property (nonatomic, copy) NSString *song_id; // 歌曲地址
- (instancetype)initWithInfo: (NSDictionary *)InfoDic;
@end
FHAlbumModel.m
#import "FHAlbumModel.h"
@implementation FHAlbumModel
- (instancetype)initWithInfo: (NSDictionary *)InfoDic {
FHAlbumModel *model = [[FHAlbumModel alloc] init];
// 通过kvo为属性赋值
[model setValuesForKeysWithDictionary:InfoDic];
return model;
}
- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
}
@end
(2)声明的变量
#import "FHMusicPlayerViewController.h"
#import <AVFoundation/AVFoundation.h>
#import "UIColor+RGBHelper.h"
#import "FHCustomButton.h"
#import "Masonry.h"
#import "FHAlbumModel.h"
#import "FHLrcModel.h"
@interface FHMusicPlayerViewController ()<UITableViewDelegate, UITableViewDataSource>{
UIImageView *_backImageView; // 背景图
UILabel *_album_titleLabel; // 标题
UILabel *_artist_nameLabel; // 副标题
UILabel *_currentLabel; // 当前时间
UILabel *_durationLabel; // 总时间
UIProgressView *_progressView; // 进度条
UISlider *_playerSlider; // 播放控制器
FHCustomButton *_playButton; // 播放暂停
FHCustomButton *_prevButton; // 上一首
FHCustomButton *_nextButton; // 下一首
BOOL _isPlay; // 记录播放暂停状态
NSInteger _index; // 记录播放到了第几首歌
FHAlbumModel *_currentModel;
UITableView *_lrcTableView; // 用于显示歌词
int _row; //记录歌词第几行
}
@property (nonatomic, strong)NSMutableArray *albumArr; //歌曲
@property (nonatomic, strong)NSMutableArray *lrcArr; // 歌词
@property (nonatomic, strong)AVPlayer *avPlayer;
@property (nonatomic, strong)id timePlayProgerssObserver;// 播放器进度观察者
@end
UI的具体实现我就不一一介绍了,可以去我的GitUp下载源码。只要记住每个变量的含义就好了,方便下面的观看。
(3)懒加载变量
#pragma - mark 懒加载歌曲
- (NSMutableArray *)albumArr {
if (!_albumArr) {
_albumArr = [NSMutableArray new];
// 从本地获取json数据
NSData *jsonData = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"songList" ofType:@"json"]];
// 把json数据转换成字典
NSDictionary *rootDic = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:nil];
NSArray *albumArr = [NSArray arrayWithArray:rootDic[@"song_list"]];
for (NSDictionary *dic in albumArr) {
FHAlbumModel *albumModel = [[FHAlbumModel alloc] initWithInfo:dic];
[_albumArr addObject:albumModel];
}
}
return _albumArr;
}
#pragma - mark 懒加载歌词
- (NSMutableArray *)lrcArr{
if (!_lrcArr) {
_lrcArr = [NSMutableArray new];
}
return _lrcArr;
}
#pragma - mark 懒加载AVPlayer
- (AVPlayer *)avPlayer {
if (!_avPlayer) {
AVPlayerItem *item = [AVPlayerItem new];
_avPlayer = [[AVPlayer alloc] initWithPlayerItem:item];
}
return _avPlayer;
}
2.歌曲轮播
#pragma mark - 播放暂停
- (void)playAction:(UIButton *)button {
_isPlay = !_isPlay;
if (_isPlay) {
_playButton.imageView.image = [UIImage imageNamed:@"play"];
if (_currentModel) {
[self.avPlayer play];
}else {
[self playMusic];
}
}else {
_playButton.imageView.image = [UIImage imageNamed:@"stop"];
[self.avPlayer pause];
}
}
当没有歌曲播放时候,添加歌曲。当有歌曲播放时,不添加歌曲。这样可以保证暂停之后继续播放。
- (void)playMusic {
// 1.移除观察者
[self removeObserver];
// 2.修改播放按钮的图片
_playButton.imageView.image = [UIImage imageNamed:@"play"];
// 3.获取歌曲
FHAlbumModel *albumModel = self.albumArr[_index];
// 4.修改标题
_album_titleLabel.text = albumModel.title;
// 5.修改副标题
_artist_nameLabel.text = [NSString stringWithFormat:@"%@ - 经典老歌榜",albumModel.artist_name];
// 6. 实例化新的playerItem
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:albumModel.song_id]];
// 7.取代旧的playerItem
[self.avPlayer replaceCurrentItemWithPlayerItem:playerItem];
// 8.开始播放
[self.avPlayer play];
// 9.添加缓存状态的观察者
[self addObserverOfLoadedTimeRanges];
// 10.添加播放进度的观察者
[self addTimePlayProgerssObserver];
// 11.记录当前播放的歌曲
_currentModel = self.albumArr[_index];
// 12.获取歌词
[self getAlbumLrc];
}
分析:1.添加观察者之前需要把以前的观察者移除。如果不移除self.avPlayer.currentItem 的观察者,就会报“An instance 0x174009380 of class AVPlayerItem was deallocated while key value observers were still registered with it”。意思是观察的对象已经释放,还对它进行观察。我们切换歌曲时,原来的歌曲对象已经释放了,所以对原来歌曲对象添加的观察者也应该移除;虽然self.avPlayer一直存在,但是如果对它一直添加观察者,会耗费大量内存,为了防止内存溢出所以也应该移除。
#pragma mark - 移除观察者
- (void)removeObserver {
// 没添加之前不能移除否则会崩溃
if (!_currentModel) {
return;
}else {
[self.avPlayer.currentItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
[self.avPlayer removeTimeObserver:self.timePlayProgerssObserver];
}
}
#pragma mark - 监听缓存状态
- (void)addObserverOfLoadedTimeRanges {
[self.avPlayer.currentItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context {
if ([keyPath isEqualToString:@"loadedTimeRanges"]) {
NSArray * timeRanges = self.avPlayer.currentItem.loadedTimeRanges;
//本次缓冲的时间范围
CMTimeRange timeRange = [timeRanges.firstObject CMTimeRangeValue];
//缓冲总长度
NSTimeInterval totalLoadTime = CMTimeGetSeconds(timeRange.start) + CMTimeGetSeconds(timeRange.duration);
//音乐的总时间
NSTimeInterval duration = CMTimeGetSeconds(self.avPlayer.currentItem.duration);
//计算缓冲百分比例
NSTimeInterval scale = totalLoadTime/duration;
//更新缓冲进度条
_progressView.progress = scale;
_durationLabel.text = [NSString stringWithFormat:@"%d:%@",(int)duration/60,[self FormatTime:(int)duration%60]];
}
}
#pragma mark - 添加播放进度的观察者
- (void)addTimePlayProgerssObserver {
__block UISlider *weakPregressSlider = _playerSlider;
__weak UILabel *waekCurrentLabel = _currentLabel;
__block int weakRow = _row;
__weak typeof(self) weakSelf = self;
self.timePlayProgerssObserver = [self.avPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
// 当前播放的时间
float current = CMTimeGetSeconds(time);
// 更新歌词
if (weakRow < weakSelf.lrcArr.count) {
FHLrcModel *model = weakSelf.lrcArr[weakRow];
if (model.presenTime == (int)current) {
[weakSelf reloadTabelViewWithRow:weakRow];
weakRow++;
}
}
// 总时间
float total = CMTimeGetSeconds(weakSelf.avPlayer.currentItem.duration);
// 更改当前播放时间
NSString *currentSStr = [weakSelf FormatTime: (int)current % 60];
waekCurrentLabel.text = [NSString stringWithFormat:@"%d:%@",(int)current / 60,currentSStr];
// 更新播放进度条
weakPregressSlider.value = current / total;
}];
}
// 播放完成通知
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(nextButtonClick:) name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
写在viewDidLoad里,因为添加一次就可以。播放完成直接播放下一首。
#pragma mark - 上一首
- (void)prevButtonClick :(UIButton *)button {
_index--;
if (_index < 0) {
_index = self.albumArr.count - 1;
}
[self playMusic];
}
#pragma mark - 下一首
- (void)nextButtonClick :(UIButton *)button {
_index++;
if (_index >= self.albumArr.count) {
_index = 0;
}
[self playMusic];
}
当播放第一首歌曲时,点击上一首播放最后一首歌曲。当播放最后一首歌曲时,点击下一首播放第一首歌曲。
由于篇幅的原因,下一篇博客再介绍歌词的实现。重要的事情说三遍:项目地址GitUp ,欢迎下载。
iOS从零开始学习直播之音频3.歌曲切换的更多相关文章
- iOS从零开始学习直播之音频1.播放本地音频文件
现在直播越来越火,俨然已经成为了下一个红海.作为一个资深码农(我只喜欢这样称呼自己,不喜欢别人这样称呼我),我必须赶上时代的潮流,开始研究视频直播.发现视屏直播类的文章上来就讲拉流.推流.采集.美 ...
- iOS从零开始学习直播之音频4.歌词
上一篇讲了歌曲的切换,这一篇主要讲歌词部分的实现. 先看效果图.当歌手唱到这句歌词时候,我们要标记出来,这里显示字体为黄色. 1.获取歌词 一般歌词都是一个链接.类似于"http ...
- iOS从零开始学习直播之音频2.后台播放和在线播放
本篇主要讲音频的后台播放和在线播放. 后台播放 上一篇写的工程运行之后程序退至后台,发现运行不了,歌停止了,这显然不行,音乐后台播放是标配啊.今天就来讲一下后台播放. 1.在plist文件里,告诉 ...
- iOS从零开始学习直播之2.采集
直播的采集由采集的设备(摄像头.话筒)不同分为视频采集和音频采集,本篇文章会分别介绍. 1.采集步骤 1.创建捕捉会话(AVCaptureSession),iOS调用相机和话筒之前都需要创建捕 ...
- iOS从零开始学习直播之1.播放
对于直播来说,客户端主要做两件事情,推流和播放.今天先讲播放. 播放流程 1.拉流:服务器已有直播内容,从指定地址进行拉取的过程.其实就是向服务器请求数据. 2.解码:对视屏数据进行解压缩. 3. ...
- iOS从零开始学习直播之3.美颜
任何一款直播软件都必须进行美颜,不然哪来的那么多美女,所以技术改变世界,不只是说说而已.美颜在采集的时候就得就行,让主播实时看到直播的效果. 1.美颜原理 其实美颜的本质就是美白和磨皮,分别通 ...
- iOS 直播-获取音频(视频)数据
iOS 直播-获取音频(视频)数据 // // ViewController.m // capture-test // // Created by caoxu on 16/6/3. // Copyri ...
- ios网络学习------6 json格式数据的请求处理
ios网络学习------6 json格式数据的请求处理 分类: IOS2014-06-30 20:33 471人阅读 评论(3) 收藏 举报 #import "MainViewContro ...
- 从零开始学习CocoaPods安装和使用
从零开始学习CocoaPods安装和使用 转载: Code4App原创:http://code4app.com/article/cocoapods-install-usage http://m.i ...
随机推荐
- 在Mac OS X上安装ASP.NET 5(译文)
ASP.NET 5 运行在包括OS X的可用于多个平台的.NET Execution Environment(DNX)上.本文介绍如何在OS X上通过HomeBrew安装DNX和ASP.NET 5. ...
- 小小改动帮你减少bundle.js文件体积(翻译)
我已经从事过好多年的SPA开发工作,我发现很多的程序猿都从来不往 bundle.js 文件的体积上动脑筋,这让我有点懵逼. “安心洗路,等俺把代码混淆压缩后就一切666了”,若是有人这么说,我会翻白眼 ...
- web音乐播放器总结
前言 项目暂时告一段落,胸中有股炽热之气望喷涌而出!忍不住吐槽,为什么程序员要加班啊,为什么产品下达deadline,就得把这生死剑架在程序员的脖子上.卧槽,听说程序员在国外是叫工程师的.最近看了很多 ...
- CSS currentColor 变量的使用
CSS中存在一个神秘的变量,少有人知自然也不怎么为人所用.它就是crrentColor变量(或者说是CSS关键字,但我觉得称为变量好理解些). 初识 它是何物?具有怎样的功效?它从哪里来?带着这些疑问 ...
- 拉格朗日插值法——用Python进行数值计算
插值法的伟大作用我就不说了.... 那么贴代码? 首先说一下下面几点: 1. 已有的数据样本被称之为 "插值节点" 2. 对于特定插值节点,它所对应的插值函数是必定存在且唯一的(关 ...
- Owin:“System.Reflection.TargetInvocationException”类型的未经处理的异常在 mscorlib.dll 中发生
异常汇总:http://www.cnblogs.com/dunitian/p/4523006.html#signalR 这个异常我遇到两种情况,供园友参考: 第一种,权限不够,在项目运行的时候弹出== ...
- 《PDF.NE数据框架常见问题及解决方案-初》
<PDF.NE数据框架常见问题及解决方案-初> 1.新增数据库后,获取标识列的值: 解决方案: PDF.NET数据框架,已经为我们考略了很多,因为用PDF.NET进行数据的添加操作时 ...
- docker对数据卷进行还原操作
转载请注明出处 数据卷容器备份数据后,备份数据查看 http://www.cnblogs.com/zhuxiaojie/p/5947138.html 我们可能要把这个备份的数据,还原到另一台的do ...
- javascript权威指南笔记
最近每天工作之余看下js的细节部分,时间不是很多,所以看的进度也不会太快,写个博客监督自己每天都看下. 以前不知道的细节或者以前知道但是没注意过的地方都会记录下来,所以适合有一定基础的,不适合零基础新 ...
- 外边距塌陷之clearance
在一个BFC中,垂直方向上相邻的块级盒子产生外边距塌陷,本文要说一个特殊的外边距塌陷情况,即当垂直方向上,两个块级盒子之间有个浮动元素相隔时,这个时候会产生什么样的效果呢? .outer{ overf ...