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 ...
随机推荐
- 钉钉开放平台demo调试异常问题解决:hostname in certificate didn't match
今天研究钉钉的开放平台,结果一个demo整了半天,这帮助系统写的也很难懂.遇到两个问题: 1.首先是执行demo时报unable to find valid certification path to ...
- CanvasWebgl项目介绍
CanvasWebgl 介绍 CanvasWebgl 是一个基于webgl 开发的2d绘图框架,使用TypeScript开发 CanvasWebgl的功能,是在屏幕空间 或者 3D空间产生一个画布 ...
- Direct3D Draw函数 异步调用原理解析
概述 在D3D10中,一个基本的渲染流程可分为以下步骤: 清理帧缓存: 执行若干次的绘制: 通过Device API创建所需Buffer: 通过Map/Unmap填充数据到Buffer中: 将Buff ...
- tomcat RMI 停不掉
项目采用jfinal框架,做了一个RMI的服务,对其它程序提供服务.实现上,写了一个RmiPlugin package com.wisdombud.cloudtalk.plugin; import j ...
- redis成长之路——(五)
单例.哨兵.Cluster redis应用广泛,主要体现于实际场景的可用化,但是对于码农来说初步入手很多理念难以理解:码农的想法就是:为什么我要管那么多,我只想用,能用就行!所以必须将三个场景透明化. ...
- BootStrap_04之jQuery插件(导航、轮播)、以及Less
1.列偏移与列排序: ①列偏移:控制列出现的位置,某列偏移后,后序列会随之偏移--只能右偏移: col-lg/md/sm/xs-offset-*; ②列排序:控制某一列的位置,该列可以左(pull)右 ...
- 基于Hexo和Github搭建博客
搭建自己的个人博客. 准备工作 确保电脑需要已下载安装node和npm.查看安装是否成功,windows只需在命令行输入以下两条命令即可. 1 2 $ node -v $ npm -v 安装hexo ...
- 【Win 10 应用开发】加载外部的 srt 字幕
据说系统内置的多媒体功能支持 srt. ssa 等字幕,老周测试过几种格式的字幕均能加载. SRT 字幕是最简单的字幕结构,甚至你用记事本都能做出来,就是分为几行来写. 第一行是字幕的编号,应该是从1 ...
- 3. 解析 struts.xml 文件
1. struts.xml 文件基本配置: 主要放在资源路径下,配置 sturts2相关的 Action , 拦截器等配置 <struts> <!-- 设置常量 --> < ...
- windows 环境下nginx + tomcat群 + redis 实现session共享
nginx作为负载均衡根据定义将不同的用户请求分发到不同的服务器,同时也解决了因单点部署服务器故障导致的整个应用不能访问的问题 在加入nginx之后,如果多个服务器中的一个或多个(不是全部)发生故障, ...