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 ...
随机推荐
- 理解Storm并发
作者:Jack47 PS:如果喜欢我写的文章,欢迎关注我的微信公众账号程序员杰克,两边的文章会同步,也可以添加我的RSS订阅源. 注:本文主要内容翻译自understanding-the-parall ...
- 按照Enterprise Integration Pattern搭建服务系统
在前一篇文章中,我们已经对Enterprise Integration Pattern中所包含的各个组成进行了简单地介绍.限于篇幅(20页Word以内),我并没有深入地讨论各个组成.但是如果要真正地按 ...
- JQuery插件定义
一:导言 有些WEB开发者,会引用一个JQuery类库,然后在网页上写一写$("#"),$("."),写了几年就对别人说非常熟悉JQuery.我曾经也是这样的人 ...
- (转)linux下和云端通讯的例程, ubuntu和openwrt下实验成功(二)
前言: 上节用纯linux的函数实现了和云端通讯, 本节开始利用传说中的神器libcurl 话说一个网络程序员对书法十分感兴趣,退休后决定在这方面有所建树. 于是花重金购买了上等的文房四宝. 一 ...
- ABP源码分析四十三:ZERO的本地化
ABP Zero模块扩展了ABP基础框架中的本地化功能,实现了通过数据库管理本地化的功能.其通过数据库保存本地化语言及其资源. ApplicationLanguage:代表本地化语言的实体类.一种语言 ...
- DAO层,Service层,Controller层、View层 的分工合作
DAO层:DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此,DAO层的设计首先是设计DAO的接口,然后在Spring的配置文件中定义此接口的实现类,然后就可在模块中调用此接口 ...
- 精通Perl(第2版)
精通Perl(第2版)(通往Perl大师之路必读经典书籍,体现了一种编程思维,能够帮你解决很多实际的问题) [美]brian d foy(布瑞恩·D·福瓦)著 王兴宇 刘宸宇 译 ISBN 978 ...
- 阿里云 SDK python3支持
最近的一个项目需要操作阿里云的RDS,项目使用python3,让人惊讶的是官方的SDK竟然只支持python2 在阿里云现有SDK上改了改,文件的修改只涉及aliyun/api/base.py,详见h ...
- 阿里云服务器的坑=====部署EF+MVC
异常处理汇总 ~ 修正果带着你的Net飞奔吧!http://www.cnblogs.com/dunitian/p/4599258.html 先参考:http://www.cnblogs.com/dun ...
- Bootstrap3插件系列:bootstrap-select2
1.下载插件 https://github.com/select2/select2 http://select2.github.io/ 2.引用插件 <script src="~/Sc ...