在iOS中音频播放从形式上可以分为音效播放和音乐播放。前者主要指的是一些短音频播放,通常作为点缀音频,对于这类音频不需要进行进度、循环等控制。后者指的是一些较长的音频,通常是主音频,对于这些音频的播放通常需要进行精确的控制。
在iOS中播放两类音频分别使用AudioToolbox.framework和AVFoundation.framework来完成音效和音乐播放。
1.音效播放
AudioToolbox.framework是一套基于C语言的框架,使用它来播放音效其本质是将短音频注册到系统声音服务(System Sound Service)中。System Sound Service是一种简单、底层的声音播放服务,但是它本身也存在着一些限制:
1)音频播放时间不能超过30s
2)数据必须是PCM或者IMA4格式
3)音频文件必须打包成caf、aif、wav中的一种(注意这是官方文档的说法,实际测试发现一些.mp3也可以播放)
下面是结合重力感应实现的相关代码
#import "ViewController.h"
//短效音频
#import
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
//重力感应/摇一摇功能实现
-(void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
NSLog(@"摇动开始");
//短效音频播放
//创建一个系统声音id
SystemSoundID soundID;
//创建短效音频播放器并且设置播放资源
AudioServicesCreateSystemSoundID((__bridge CFURLRef _Nonnull)([NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"shake" ofType:@"wav"]]), &soundID);
//播放短效音频的同时加入震动效果,kSystemSoundID_Vibrate指的就是震动效果
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
//开始播放
AudioServicesPlaySystemSound(soundID);
}
-(void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
NSLog(@"摇动结束");
//一般写页面的跳转
}
-(void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
NSLog(@"取消摇动");
}
2.音乐播放
如果播放较大的音频或者对音频有精确的控制则System Sound Service很难满足实际需求了,通常这种情况会选择使用AVFoundation.framework中的AVAudioPlayer来实现,我们可以把AVAudioPlayer看做一个高级的播放器,它能够支持广泛的音频格式。
AVAudioPlayer可以播放任意长度的音频文件、支持循环播放、可以同步播放多个音频文件、控制播放的进度以及从音频文件的任意一点开始播放等。要使用AVAudioPlayer的对象播放音乐文件,你只需要为其指定一个音频文件并设定一个实现了AVAudioPlayerDelegate协议的delegate对象。
AVAudioPlayer类封装了播放单个声音的能力,播放器可以用NSURL或者NSData来初始化,要注意的是NSURL不可以是网络url而必须是本地文件url,因为AVAudioPlayer本身不具有网络播放音频的能力。因为AVAudioPlayer只能播放一个完整的文件,并不支持流式播放,所以必须是缓冲完才能播放。一个AVAudioPlayer对象只能播放一个音频,如果想混音可以创建多个AVAudioPlayer对象。
下面是音乐播放的简单实现代码:
#import "ViewController.h"
#import
@interface ViewController ()
{
UISlider * _progressSlider;
UISlider * _volumeSlider;
int _currentIndex;//用来记录当前播放的是第几首歌
AVAudioPlayer * _audioPlayer;
NSTimer * _timer;
}
//本地音乐文件
@property(nonatomic,strong) NSArray * localMusicArray;
//网络音乐地址
@property(nonatomic,strong) NSArray * urlArray;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//1.创建UI
[self createUI];
//2.设置播放资源
[self initData];
//3.创建音乐播放器
[self createAudioPlayer];
//4.细节处理
[self dealWithDetail];
//创建定时器
_timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(sliderChange) userInfo:nil repeats:YES];
}
#pragma mark - 细节处理
-(void)dealWithDetail{
//设置后台播放,结合修改plist,两个步骤一个都不能少
//初始化音频会话
AVAudioSession * session = [[AVAudioSession alloc]init];
//设置类型为后台播放类型
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
//开启后台播放
[session setActive:YES error:nil];
//监听输出设备的状态
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(routeChange:) name:AVAudioSessionRouteChangeNotification object:nil];
}
-(void)routeChange:(NSNotification *)noti
{
//获取监听内容
NSDictionary * dic = noti.userInfo;
//获取状态改变的原因
int changeReason = [dic[AVAudioSessionRouteChangeReasonKey] intValue];
//当状态改变原因为检测不到输出设备
if (changeReason == AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
//获取状态描述
AVAudioSessionRouteDescription * description = dic[AVAudioSessionRouteChangePreviousRouteKey];
//获取端口描述
AVAudioSessionPortDescription * portDescription = [description.outputs firstObject];
//如果端口是耳机并且音频正在播放的话暂停播放
if ([portDescription.portType isEqualToString:@"HeadPhones"]) {
if (_audioPlayer.isPlaying) {
//暂停播放器
[_audioPlayer pause];
//暂停定时器
[_timer setFireDate:[NSDate distantFuture]];
}
}
}
}
#pragma mark - 定时器响应方法
-(void)sliderChange{
_progressSlider.value = _audioPlayer.currentTime / _audioPlayer.duration;
}
#pragma mark - 设置播放资源
-(void)initData{
_currentIndex = 0;
self.localMusicArray = @[@"北京北京",@"春天里",@"狐狸叫",@"江南-style",@"蓝莲花",@"像梦一样自由"];
self.urlArray = @[@"http://cdn.thenbapps.com/music/69/bca89cbe07e89d934d8c905a5e0859.mp3",@"http://cdn.thenbapps.com/music/a5/02585992d872766da13088d8d2a2c8.mp3",@"http://cdn.thenbapps.com/music/28/27f3b5eade19f8ba7df580f94e9063.mp3",@"http://cdn.thenbapps.com/music/b4/3a08f71ab2d1747ee563d322a357a3.mp3",@"http://cdn.thenbapps.com/music/b7/fa061a39d363f2fcab87f84062e10c.mp3"];
}
#pragma mark - 创建音乐播放器
-(void)createAudioPlayer{
//初始化播放器
//第一种:用NSURL初始化,这里的url指的不是网络url。而是本地url
NSURL * songUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:self.localMusicArray[_currentIndex] ofType:@"mp3"]];
//_audioPlayer = [[AVAudioPlayer alloc]initWithContentsOfURL:songUrl error:nil];
//第二种:用NSData初始化,这里实际上是将网络url缓冲到本地,最终播放的还是本地文件
_audioPlayer = [[AVAudioPlayer alloc]initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:self.urlArray[_currentIndex]]] error:nil];
//设置代理
_audioPlayer.delegate = self;
//设置音量
_audioPlayer.volume = 0.5;//在0.1~1.0之间
//设置播放的循环次数,负数表示无限循环播放,0表示只播放一次,正数是几就播放几次。默认情况下只播放一次
_audioPlayer.numberOfLoops = 0;
//设置开始播放的位置
_audioPlayer.currentTime = 0;//可以指定从任意位置开始播放
//声道数,只读属性
int channals = _audioPlayer.numberOfChannels;
//设置计数
_audioPlayer.meteringEnabled = YES;//开启音频计数
[_audioPlayer updateMeters];//更新音频读数
//获取平均电平和峰值电平
for (int i = 0; i < channals; i ++) {
//平均电平
//float average = [_audioPlayer averagePowerForChannel:i];
//峰值电平
//float peak = [_audioPlayer peakPowerForChannel:i];
}
//获取播放持续时间
//NSTimeInterval time = _audioPlayer.duration;
//分配播放资源,并将其添加到内部实现队列中
[_audioPlayer prepareToPlay];
}
#pragma mark - 创建UI
-(void)createUI {
//播放进度条
_progressSlider = [[UISlider alloc]initWithFrame:CGRectMake(10, 100, self.view.frame.size.width - 20, 20)];
[_progressSlider addTarget:self action:@selector(progrssValuechanged:) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:_progressSlider];
//控制声音进度条
_volumeSlider = [[UISlider alloc]initWithFrame:CGRectMake(10, 200, self.view.frame.size.width - 20, 20)];
[_volumeSlider addTarget:self action:@selector(volumeValueChanged:) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:_volumeSlider];
//播放控制按钮
NSArray * buttonArray = @[@"上一曲",@"播放",@"下一曲"];
for (int i = 0; i < buttonArray.count; i ++) {
UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(i * self.view.frame.size.width / 3, 300, self.view.frame.size.width / 3, 40);
//设置标题
[button setTitle:buttonArray[i] forState:UIControlStateNormal];
[button setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
//设置tag值
button.tag = 10 +i;
//添加响应事件
[button addTarget:self action:@selector(buttonClick:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
}
#pragma mark - 进度条响应函数
-(void)progrssValuechanged:(UISlider *)slider {
_audioPlayer.currentTime = slider.value * _audioPlayer.duration;
}
-(void)volumeValueChanged:(UISlider *)slider{
_audioPlayer.volume = slider.value;
}
#pragma mark - 按钮响应方法
-(void)buttonClick:(UIButton *)button{
switch (button.tag - 10) {
case 0:
{
//上一曲
[self previous];
}
break;
case 1:
{
//播放/暂停
if (_audioPlayer.isPlaying) {
//正在播放状态
//暂停
[_audioPlayer pause];
//定时器也暂停
[_timer setFireDate:[NSDate distantFuture]];
[button setTitle:@"播放" forState:UIControlStateNormal];
}else{
//暂停状态
//播放
[_audioPlayer play];
//恢复定时器
[_timer setFireDate:[NSDate distantPast]];
//[_timer invalidate];//销毁定时器,一旦执行这个方法,定时器将无法恢复
[button setTitle:@"暂停" forState:UIControlStateNormal];
}
}
break;
case 2:
{
//下一曲
[self next];
}
break;
default:
break;
}
}
#pragma mark - 歌曲切换
//实现原理:因为AVAudioPlayer每次只能播放一个音频文件,所以歌曲切换实质上是实例化了不同的AVAudioPlayer对象,来播放不同的音频文件
//上一曲
-(void)previous {
//首先停止当前播放的音频文件
[_audioPlayer stop];
if (_currentIndex == 0) {
_currentIndex = (int)self.localMusicArray.count - 1;
} else {
_currentIndex --;
}
//重新实例化播放器
[self createAudioPlayer];
[_audioPlayer play];
}
//下一曲
-(void)next {
[_audioPlayer stop];
if (_currentIndex == self.localMusicArray.count - 1) {
_currentIndex = 0;
}else{
_currentIndex ++;
}
[self createAudioPlayer];
[_audioPlayer play];
}
#pragma mark - 实现代理方法,处理播放过程中的突发状况
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
NSLog(@"播放完成");
if (flag) {
//说明音频文件是在正常情况下播放完成的
}else{
//说明音频虽然播放结束了,但是数据解码错误
}
}
-(void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error{
//数据解码错误
NSLog(@"%@",error);
}
//处理音频播放过程中被中断的情况,iOS8之前必须要实现这两个代理方法,iOS8之后系统作自动处理
//播放被中断,比如说突然地来电或者用户按home返回
-(void)audioPlayerBeginInterruption:(AVAudioPlayer *)player{
//如果当前正在播放则暂停
if (_audioPlayer.isPlaying) {
[_audioPlayer pause];
}
}
//结束中断,返回程序
-(void)audioPlayerEndInterruption:(AVAudioPlayer *)player{
//恢复播放
[_audioPlayer play];
}
iOS中的视频代码实现包含四个方面。接下来一一为大家进行列举。
1.MPMoviePlayerController
MPMoviePlayerController支持本地视频和网络视频播放。这个类实现了MPMediaPlayback协议,因此具备一般的播放器控制功能,例如播放、暂停、停止等。但是MPMediaPlayerController自身并不是一个完整的视图控制器,如果要在UI中展示视频需要将view属性添加到界面中
下面是实现相关代码
#import "ViewController.h"
//视频播放
#import
@interface ViewController ()
@property(nonatomic,strong) MPMoviePlayerController * moviePlayer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self createMoviePlayer];
//添加监听观察状态的变化
[self addNotifications];
}
-(void)createMoviePlayer{
//本地资源
NSURL * url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"MovieTest" ofType:@"mp4"]];
//初始化播放器
self.moviePlayer = [[MPMoviePlayerController alloc]initWithContentURL:url];
self.moviePlayer.view.frame =self.view.bounds;
[self.view addSubview:self.moviePlayer.view];
//开始播放
[self.moviePlayer play];
}
-(void)addNotifications{
NSNotificationCenter * notificationCenter = [NSNotificationCenter defaultCenter];
//观察播放的状态
[notificationCenter addObserver:self selector:@selector(playStateChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:nil];
//观察播放完成之后
[notificationCenter addObserver:self selector:@selector(playFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:nil];
}
-(void)playStateChange:(NSNotification *)noti{
switch (self.moviePlayer.playbackState) {
case MPMoviePlaybackStatePlaying:
NSLog(@"正在播放");
break;
case MPMoviePlaybackStatePaused:
NSLog(@"暂停播放");
break;
case MPMoviePlaybackStateStopped:
NSLog(@"停止播放");
break;
default:
NSLog(@"播放状态~~~%ld",self.moviePlayer.playbackState);
break;
}
}
-(void)playFinish:(NSNotification *)noti{
NSLog(@"播放完成");
}
//移除通知
-(void)dealloc{
[[NSNotificationCenter defaultCenter]removeObserver:self];
}
2. MPMoviePlayerViewController
其实MPMoviePlayerController如果不作为嵌入视频来播放(例如在新闻中嵌入一个视频),通常在播放时都是占满一个屏幕的,特别是在iPhone、iTouch上。因此从iOS3.2以后苹果也在思考既然MPMoviePlayerController在使用时通常都是将其视图view添加到另外一个视图控制器中作为子视图,那么何不直接创建一个控制器视图内部创建一个MPMoviePlayerController属性并且默认全屏播放,开发者在开发的时候直接使用这个视图控制器。这个内部有一个MPMoviePlayerController的视图控制器就是MPMoviePlayerViewController,它继承于UIViewController。MPMoviePlayerViewController内部多了一个moviePlayer属性和一个带有url的初始化方法,同时它内部实现了一些作为模态视图展示所特有的功能,例如默认是全屏模式展示、弹出后自动播放、作为模态窗口展示时如果点击“Done”按钮会自动退出模态窗口,但是在iOS9之后,MPMoviePlayerController和MPMoviePlayerViewController都被废弃掉了,所以下面介绍iOS9之前和iOS9之后的系统视频播放该如何实现。
1)iOS9之前的视频播放(添加头文件#import
//实例化播放器
MPMoviePlayerViewController * playVC = [[MPMoviePlayerViewController alloc]initWithContentURL:url];
//设置播放资源的来源
playVC.moviePlayer.movieSourceType = MPMovieSourceTypeFile;
//设置全屏播放
playVC.moviePlayer.controlStyle = MPMovieControlStyleFullscreen;
//播放之前进行预播放
[playVC.moviePlayer prepareToPlay];
//开始播放
[playVC.moviePlayer play];
//需要推出播放控制器
[self presentViewController:playVC animated:YES completion:nil];
2)iOS9之后的视频播放(添加头文件#import
//实例化
AVPlayerViewController * playVC = [[AVPlayerViewController alloc]init];
//设置播放资源
AVPlayer * player = [AVPlayer playerWithURL:url];
//转换播放器
playVC.player = player;
//推出播放的视图控制器
[self presentViewController:playVC animated:YES completion:nil];
3)强制横屏播放(要实现强制横屏播放,只需要新建一个类,继承自MPMoviePlayerViewController或者AVPlayerViewController,然后重写下面系统的两个方法)
-(BOOL)shouldAutorotate
{
return YES;
}
-(UIInterfaceOrientationMask)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskLandscape;
}
3.AVPlayer
MPMoviePlayerController足够强大,几乎不用写几行代码就能完成一个播放器,但是正是由于它的高度封装使得要自定义这个播放器变得很复杂,甚至是不可能完成。例如有些时候需要自定义播放器的样式,那么如果要使用MPMoviePlayerController就不合适了,如果要对视频有自由的控制则可以使用AVPlayer。AVPlayer存在于AVFoundation中,它更加接近于底层,所以灵活性也更强
AVPlayer本身并不能显示视频,而且它也不像MPMoviePlayerController有一个view属性。如果AVPlayer要显示必须创建一个播放器层AVPlayerLayer用于展示,播放器层继承于CALayer,有了AVPlayerLayer之添加到控制器视图的layer中即可
功能简单介绍:
视频的播放、暂停功能,这也是最基本的功能,AVPlayer对应着两个方法play、pause来实现。但是关键问题是如何判断当前视频是否在播放,在前面的内容中无论是音频播放器还是视频播放器都有对应的状态来判断,但是AVPlayer却没有这样的状态属性,通常情况下可以通过判断播放器的播放速度来获得播放状态。如果rate为0说明是停止状态,1是则是正常播放状态。
到目前为止无论是MPMoviePlayerController还是AVPlayer来播放视频都相当强大,但是它也存在着一些不可回避的问题,那就是支持的视频编码格式很有限:H.264、MPEG-4,扩展名(压缩格式):.mp4、.mov、.m4v、.m2v、.3gp、.3g2等。但是无论是MPMoviePlayerController还是AVPlayer它们都支持绝大多数音频编码
下面是AVPlayer实现简易视频播放的代码
//读取视频文件
NSURL * url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"MovieTest" ofType:@"mp4"]];
//设置播放资源
AVPlayerItem * item = [AVPlayerItem playerItemWithURL:url];
//初始化AVPlayer
_player = [AVPlayer playerWithPlayerItem:item];
//创建播放器层(AVPlayerLayer),并将其添加到view的layer层上
AVPlayerLayer * playerLayer = [AVPlayerLayer playerLayerWithPlayer:_player];
//设置frame
playerLayer.frame = _drawableView.frame;
//添加到定义的播放界面上
[_drawableView.layer addSublayer:playerLayer];
4.VLC视频播放
VLC是一个第三方的视频播放,具有强大的功能,可以播放多种格式的视频,但是集成起来比较麻烦,是和c++进行混编的
1)集成步骤
a.导入libMobileVLCKit
b.加库:ibstdc++ libiconv libbz2 Security.framework QuartzCore.framework CoreText.framework CFnetWork.framework OpenGLES.framework AudioToolbox.framework
c.修改C++编译器为stdC++:
在[Build Setting]输入[c++][Apple LLVM 7.1 - Languate - C++]
[C++ Standard..]选第一个[libstdc++(GNU C++ standard library)]
d.在[Build Setting][search][Search Paths][Header..]加一个路径
打开[libMobileVLCKit][include][MobileVLCKit]显示简介复制地址
把地址的前半部(包括文件夹名称)分改为$(SRCROOT)
e.添加头文件//#import "VLCMediaPlayer.h"
f.使用的文件改为 .mm支持C++编译
2)实现简易代码
#import "ViewController.h"
#import "VLCMediaPlayer.h"
@interface ViewController (){
VLCMediaPlayer * _mediaPlayer;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UIView * view = [[UIView alloc]initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 400)];
view.backgroundColor = [UIColor redColor];
[self.view addSubview:view];
//初始化播放器
_mediaPlayer = [[VLCMediaPlayer alloc]init];
//设置播放界面
_mediaPlayer.drawable = view;
//设置播放资源
_mediaPlayer.media = [VLCMedia mediaWithURL:[NSURL URLWithString:@"http://video.szzhangchu.com/1442391674615_6895658983.mp4"]];
//开始播放
[_mediaPlayer play];
}
相关文章
了解千锋动态
关注千锋教育服务号
扫一扫快速进入
千锋移动端页面
扫码匿名提建议
直达CEO信箱