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 ...
随机推荐
- 关于Hadoop用户体系的设想(胡思乱想)
关于Hadoop的用户体系设计设想 Hadoop并没有一个完整的用户体系,其权限控制的对象,主要是Linux的其它用户(即非安装Hadoop的用户),控制方式也和Linux的文件权限很像,目前权限控制 ...
- 谁偷了我的热更新?Mono,JIT,iOS
前言 由于匹夫本人是做游戏开发工作的,所以平时也会加一些玩家的群.而一些困扰玩家的问题,同样也困扰着我们这些手机游戏开发者.这不最近匹夫看自己加的一些群,常常会有人问为啥这个游戏一更新就要重新下载,而 ...
- mybatis Generator生成代码及使用方式
本文原创,转载请注明:http://www.cnblogs.com/fengzheng/p/5889312.html 为什么要有mybatis mybatis 是一个 Java 的 ORM 框架,OR ...
- CSharpGL(17)重构CSharpGL
CSharpGL(17)重构CSharpGL CSharpGL用起来我自己都觉得繁琐了,这是到了重构的时候. 下载 CSharpGL已在GitHub开源,欢迎对OpenGL有兴趣的同学加入(https ...
- git 使用
三个状态 文件由 git 维护三中状态 modified:文件被改动,但还没有被提交 staged:标记被改动的文件到下次需要提交的文件快照 committed:文件已经被安全保存到本地 可以使用命令 ...
- xamarin MasterDetailPage点击Master时卡顿现象
在很多项目中经常会使用到MasterDetailPage的布局方式,而且一般做为主页面来开发,在开发中,发现一个并不算Bug的问题,但是却发生了,以此记录下来,方便大家探讨. 现象是这样的,我开发了一 ...
- ListView中item定位
ListView 实现定位特定 item 最近在项目中需要使 ListView 能跳转到特定的 item,查阅文档后,发现 ListView 有以下几种方法可供使用: smoothScrollToPo ...
- JSON(及其在ajax前后端交互的过程)小识
一. json介绍 json是一种轻量级的数据交换格式,规则很简单: 并列的数据之间用逗号(,)分隔: 映射用冒号(:)表示: 并列数据的集合(数组)用方括号([])表示: 映射的集合(对象)用大括号 ...
- ashx中Response.ContentType的常用类型
ashx中Response.ContentType的常用类型: text/plaintext/htmltext/xmlapplication/jsonimage/GIFapplication/x-cd ...
- 推荐13款javascript模板引擎
javaScript 在生成各种页面内容时如果能结合一些模板技术,可以让逻辑和数据之间更加清晰,本文介绍 X 款 JavaScript 的模板引擎.(排名不分先后顺序) 1. Mustache 基于j ...