iOS開發(fā)系列--音頻播放斩祭、錄音、視頻播放尖啡、拍照橄仆、視頻錄制

概覽

隨著移動(dòng)互聯(lián)網(wǎng)的發(fā)展,如今的手機(jī)早已不是打電話衅斩、發(fā)短信那么簡單了盆顾,播放音樂、視頻畏梆、錄音您宪、拍照等都是很常用的功能奈懒。在iOS中對于多媒體的支持是非常強(qiáng)大的,無論是音視頻播放宪巨、錄制磷杏,還是對麥克風(fēng)、攝像頭的操作都提供了多套API揖铜,在今天的文章中將會(huì)對這些內(nèi)容進(jìn)行一一介紹茴丰。

音頻

在iOS中音頻播放從形式上可以分為音效播放和音樂播放。前者主要指的是一些短音頻播放天吓,通常作為點(diǎn)綴音頻贿肩,對于這類音頻不需要進(jìn)行進(jìn)度、循環(huán)等控制龄寞。后者指的是一些較長的音頻汰规,通常是主音頻,對于這些音頻的播放通常需要進(jìn)行精確的控制物邑。在iOS中播放兩類音頻分別使用AudioToolbox.frameworkAVFoundation.framework來完成音效和音樂播放溜哮。

音效

AudioToolbox.framework是一套基于C語言的框架,使用它來播放音效其本質(zhì)是將短音頻注冊到系統(tǒng)聲音服務(wù)System Sound Service色解。System Sound Service是一種簡單茂嗓、底層的聲音播放服務(wù),但是它本身也存在著一些限制:

  • 音頻播放時(shí)間不能超過30s
  • 數(shù)據(jù)必須是PCM或者IMA4格式
  • 音頻文件必須打包成.caf科阎、.aif述吸、.wav中的一種(注意這是官方文檔的說法,實(shí)際測試發(fā)現(xiàn)一些.mp3也可以播放)

使用System Sound Service 播放音效的步驟如下:

1锣笨、調(diào)用AudioServicesCreateSystemSoundID( CFURLRef inFileURL, SystemSoundID* outSystemSoundID)函數(shù)獲得系統(tǒng)聲音ID蝌矛。
2、如果需要監(jiān)聽播放完成操作错英,則使用AudioServicesAddSystemSoundCompletion( SystemSoundID inSystemSoundID, CFRunLoopRef inRunLoop, CFStringRef inRunLoopMode, AudioServicesSystemSoundCompletionProc inCompletionRoutine, void* inClientData)方法注冊回調(diào)函數(shù)入撒。
3、調(diào)用AudioServicesPlaySystemSound(SystemSoundID inSystemSoundID) 或者AudioServicesPlayAlertSound(SystemSoundID inSystemSoundID) 方法播放音效(后者帶有震動(dòng)效果)椭岩。

下面是一個(gè)簡單的示例程序:

//
//  KCMainViewController.m
//  Audio
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//  音效播放

#import "KCMainViewController.h"
#import <AudioToolbox/AudioToolbox.h>

@interface KCMainViewController ()

@end

@implementation KCMainViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self playSoundEffect:@"videoRing.caf"];
}

/**
 *  播放完成回調(diào)函數(shù)
 *
 *  @param soundID    系統(tǒng)聲音ID
 *  @param clientData 回調(diào)時(shí)傳遞的數(shù)據(jù)
 */
void soundCompleteCallback(SystemSoundID soundID,void * clientData){
    NSLog(@"播放完成...");
}

/**
 *  播放音效文件
 *
 *  @param name 音頻文件名稱
 */
-(void)playSoundEffect:(NSString *)name{
    NSString *audioFile=[[NSBundle mainBundle] pathForResource:name ofType:nil];
    NSURL *fileUrl=[NSURL fileURLWithPath:audioFile];
    //1.獲得系統(tǒng)聲音ID
    SystemSoundID soundID=0;
    /**
     * inFileUrl:音頻文件url
     * outSystemSoundID:聲音id(此函數(shù)會(huì)將音效文件加入到系統(tǒng)音頻服務(wù)中并返回一個(gè)長整形ID)
     */
    AudioServicesCreateSystemSoundID((__bridge CFURLRef)(fileUrl), &soundID);
    //如果需要在播放完之后執(zhí)行某些操作茅逮,可以調(diào)用如下方法注冊一個(gè)播放完成回調(diào)函數(shù)
    AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallback, NULL);
    //2.播放音頻
    AudioServicesPlaySystemSound(soundID);//播放音效
//    AudioServicesPlayAlertSound(soundID);//播放音效并震動(dòng)
}

@end

音樂

如果播放較大的音頻或者要對音頻有精確的控制則System Sound Service可能就很難滿足實(shí)際需求了,通常這種情況會(huì)選擇使用AVFoundation.framework中的AVAudioPlayer來實(shí)現(xiàn)判哥。AVAudioPlayer可以看成一個(gè)播放器献雅,它支持多種音頻格式,而且能夠進(jìn)行進(jìn)度姨伟、音量惩琉、播放速度等控制。

首先簡單看一下AVAudioPlayer常用的屬性和方法:

屬性 說明
@property(readonly, getter=isPlaying) BOOL playing 是否正在播放夺荒,只讀
@property(readonly) NSUInteger numberOfChannels 音頻聲道數(shù)瞒渠,只讀
@property(readonly) NSTimeInterval duration 音頻時(shí)長
@property(readonly) NSURL *url 音頻文件路徑良蒸,只讀
@property(readonly) NSData *data 音頻數(shù)據(jù),只讀
@property float pan 立體聲平衡伍玖,如果為-1.0則完全左聲道嫩痰,如果0.0則左右聲道平衡,如果為1.0則完全為右聲道
@property float volume 音量大小窍箍,范圍0-1.0
@property BOOL enableRate 是否允許改變播放速率
@property float rate 播放速率串纺,范圍0.5-2.0,如果為1.0則正常播放椰棘,如果要修改播放速率則必須設(shè)置enableRate為YES
@property NSTimeInterval currentTime 當(dāng)前播放時(shí)長
@property(readonly) NSTimeInterval deviceCurrentTime 輸出設(shè)備播放音頻的時(shí)間纺棺,注意如果播放中被暫停此時(shí)間也會(huì)繼續(xù)累加
@property NSInteger numberOfLoops 循環(huán)播放次數(shù),如果為0則不循環(huán)邪狞,如果小于0則無限循環(huán)祷蝌,大于0則表示循環(huán)次數(shù)
@property(readonly) NSDictionary *settings 音頻播放設(shè)置信息,只讀
@property(getter=isMeteringEnabled) BOOL meteringEnabled 是否啟用音頻測量帆卓,默認(rèn)為NO巨朦,一旦啟用音頻測量可以通過updateMeters方法更新測量值
對象方法 說明
- (instancetype)initWithContentsOfURL:(NSURL *)url error:(NSError **)outError 使用文件URL初始化播放器,注意這個(gè)URL不能是HTTP URL剑令,AVAudioPlayer不支持加載網(wǎng)絡(luò)媒體流糊啡,只能播放本地文件
- (instancetype)initWithData:(NSData *)data error:(NSError **)outError 使用NSData初始化播放器,注意使用此方法時(shí)必須文件格式和文件后綴一致吁津,否則出錯(cuò)棚蓄,所以相比此方法更推薦使用上述方法或- (instancetype)initWithData:(NSData *)data fileTypeHint:(NSString *)utiString error:(NSError **)outError方法進(jìn)行初始化
- (BOOL)prepareToPlay; 加載音頻文件到緩沖區(qū),注意即使在播放之前音頻文件沒有加載到緩沖區(qū)程序也會(huì)隱式調(diào)用此方法腺毫。
- (BOOL)play; 播放音頻文件
- (BOOL)playAtTime:(NSTimeInterval)time
在指定的時(shí)間開始播放音頻
- (void)pause; 暫停播放
- (void)stop; 停止播放
- (void)updateMeters 更新音頻測量值癣疟,注意如果要更新音頻測量值必須設(shè)置meteringEnabled為YES挣柬,通過音頻測量值可以即時(shí)獲得音頻分貝等信息
(float)peakPowerForChannel:(NSUInteger)channelNumber; 獲得指定聲道的分貝峰值潮酒,注意如果要獲得分貝峰值必須在此之前調(diào)用updateMeters方法
- (float)averagePowerForChannel:(NSUInteger)channelNumber 獲得指定聲道的分貝平均值,注意如果要獲得分貝平均值必須在此之前調(diào)用updateMeters方法
@property(nonatomic, copy) NSArray *channelAssignments 獲得或設(shè)置播放聲道
代理方法 說明
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag 音頻播放完成
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error 音頻解碼發(fā)生錯(cuò)誤

AVAudioPlayer的使用比較簡單:

1邪蛔、初始化AVAudioPlayer對象急黎,此時(shí)通常指定本地文件路徑。
2侧到、設(shè)置播放器屬性勃教,例如重復(fù)次數(shù)、音量大小等匠抗。
3故源、調(diào)用play方法播放。

下面就使用AVAudioPlayer實(shí)現(xiàn)一個(gè)簡單播放器汞贸,在這個(gè)播放器中實(shí)現(xiàn)了播放绳军、暫停印机、顯示播放進(jìn)度功能,當(dāng)然例如調(diào)節(jié)音量门驾、設(shè)置循環(huán)模式射赛、甚至是聲波圖像(通過分析音頻分貝值)等功能都可以實(shí)現(xiàn),這里就不再一一演示奶是。界面效果如下:

當(dāng)然由于AVAudioPlayer一次只能播放一個(gè)音頻文件楣责,所有上一曲、下一曲其實(shí)可以通過創(chuàng)建多個(gè)播放器對象來完成聂沙,這里暫不實(shí)現(xiàn)秆麸。播放進(jìn)度的實(shí)現(xiàn)主要依靠一個(gè)定時(shí)器實(shí)時(shí)計(jì)算當(dāng)前播放時(shí)長和音頻總時(shí)長的比例,另外為了演示委托方法及汉,下面的代碼中也實(shí)現(xiàn)了播放完成委托方法蛔屹,通常如果有下一曲功能的話播放完可以觸發(fā)下一曲音樂播放。下面是主要代碼:

//
//  ViewController.m
//  KCAVAudioPlayer
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#define kMusicFile @"劉若英 - 原來你也在這里.mp3"
#define kMusicSinger @"劉若英"
#define kMusicTitle @"原來你也在這里"

@interface ViewController ()<AVAudioPlayerDelegate>

@property (nonatomic,strong) AVAudioPlayer *audioPlayer;//播放器
@property (weak, nonatomic) IBOutlet UILabel *controlPanel; //控制面板
@property (weak, nonatomic) IBOutlet UIProgressView *playProgress;//播放進(jìn)度
@property (weak, nonatomic) IBOutlet UILabel *musicSinger; //演唱者
@property (weak, nonatomic) IBOutlet UIButton *playOrPause; //播放/暫停按鈕(如果tag為0認(rèn)為是暫停狀態(tài)豁生,1是播放狀態(tài))

@property (weak ,nonatomic) NSTimer *timer;//進(jìn)度更新定時(shí)器

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupUI];
    
}

/**
 *  初始化UI
 */
-(void)setupUI{
    self.title=kMusicTitle;
    self.musicSinger.text=kMusicSinger;
}

-(NSTimer *)timer{
    if (!_timer) {
        _timer=[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateProgress) userInfo:nil repeats:true];
    }
    return _timer;
}

/**
 *  創(chuàng)建播放器
 *
 *  @return 音頻播放器
 */
-(AVAudioPlayer *)audioPlayer{
    if (!_audioPlayer) {
        NSString *urlStr=[[NSBundle mainBundle]pathForResource:kMusicFile ofType:nil];
        NSURL *url=[NSURL fileURLWithPath:urlStr];
        NSError *error=nil;
        //初始化播放器兔毒,注意這里的Url參數(shù)只能時(shí)文件路徑,不支持HTTP Url
        _audioPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error];
        //設(shè)置播放器屬性
        _audioPlayer.numberOfLoops=0;//設(shè)置為0不循環(huán)
        _audioPlayer.delegate=self;
        [_audioPlayer prepareToPlay];//加載音頻文件到緩存
        if(error){
            NSLog(@"初始化播放器過程發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@",error.localizedDescription);
            return nil;
        }
    }
    return _audioPlayer;
}

/**
 *  播放音頻
 */
-(void)play{
    if (![self.audioPlayer isPlaying]) {
        [self.audioPlayer play];
        self.timer.fireDate=[NSDate distantPast];//恢復(fù)定時(shí)器
    }
}

/**
 *  暫停播放
 */
-(void)pause{
    if ([self.audioPlayer isPlaying]) {
        [self.audioPlayer pause];
        self.timer.fireDate=[NSDate distantFuture];//暫停定時(shí)器甸箱,注意不能調(diào)用invalidate方法育叁,此方法會(huì)取消,之后無法恢復(fù)
        
    }
}

/**
 *  點(diǎn)擊播放/暫停按鈕
 *
 *  @param sender 播放/暫停按鈕
 */
- (IBAction)playClick:(UIButton *)sender {
    if(sender.tag){
        sender.tag=0;
        [sender setImage:[UIImage imageNamed:@"playing_btn_play_n"] forState:UIControlStateNormal];
        [sender setImage:[UIImage imageNamed:@"playing_btn_play_h"] forState:UIControlStateHighlighted];
        [self pause];
    }else{
        sender.tag=1;
        [sender setImage:[UIImage imageNamed:@"playing_btn_pause_n"] forState:UIControlStateNormal];
        [sender setImage:[UIImage imageNamed:@"playing_btn_pause_h"] forState:UIControlStateHighlighted];
        [self play];
    }
}

/**
 *  更新播放進(jìn)度
 */
-(void)updateProgress{
    float progress= self.audioPlayer.currentTime /self.audioPlayer.duration;
    [self.playProgress setProgress:progress animated:true];
}

#pragma mark - 播放器代理方法
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
    NSLog(@"音樂播放完成...");
}

@end

運(yùn)行效果:


音頻會(huì)話

事實(shí)上上面的播放器還存在一些問題芍殖,例如通常我們看到的播放器即使退出到后臺(tái)也是可以播放的豪嗽,而這個(gè)播放器如果退出到后臺(tái)它會(huì)自動(dòng)暫停。如果要支持后臺(tái)播放需要做下面幾件事情:

1豌骏、設(shè)置后臺(tái)運(yùn)行模式:在plist文件中添加Required background modes龟梦,并且設(shè)置item 0=App plays audio or streams audio/video using AirPlay(其實(shí)可以直接通過Xcode在Project Targets-Capabilities-Background Modes中設(shè)置)

2、設(shè)置AVAudioSession的類型為AVAudioSessionCategoryPlayback并且調(diào)用setActive::方法啟動(dòng)會(huì)話窃躲。

  AVAudioSession *audioSession=[AVAudioSession sharedInstance];
  [audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
  [audioSession setActive:YES error:nil];

3计贰、為了能夠讓應(yīng)用退到后臺(tái)之后支持耳機(jī)控制,建議添加遠(yuǎn)程控制事件(這一步不是后臺(tái)播放必須的)

前兩步是后臺(tái)播放所必須設(shè)置的蒂窒,第三步主要用于接收遠(yuǎn)程事件躁倒,這部分內(nèi)容之前的文章中有詳細(xì)介紹,如果這一步不設(shè)置雖讓也能夠在后臺(tái)播放洒琢,但是無法獲得音頻控制權(quán)(如果在使用當(dāng)前應(yīng)用之前使用其他播放器播放音樂的話秧秉,此時(shí)如果按耳機(jī)播放鍵或者控制中心的播放按鈕則會(huì)播放前一個(gè)應(yīng)用的音頻),并且不能使用耳機(jī)進(jìn)行音頻控制衰抑。第一步操作相信大家都很容易理解象迎,如果應(yīng)用程序要允許運(yùn)行到后臺(tái)必須設(shè)置,正常情況下應(yīng)用如果進(jìn)入后臺(tái)會(huì)被掛起呛踊,通過該設(shè)置可以上應(yīng)用程序繼續(xù)在后臺(tái)運(yùn)行砾淌。但是第二步使用的AVAudioSession有必要進(jìn)行一下詳細(xì)的說明完丽。

在iOS中每個(gè)應(yīng)用都有一個(gè)音頻會(huì)話,這個(gè)會(huì)話就通過AVAudioSession來表示拇舀。AVAudioSession同樣存在于AVFoundation框架中逻族,它是單例模式設(shè)計(jì),通過sharedInstance進(jìn)行訪問骄崩。在使用Apple設(shè)備時(shí)大家會(huì)發(fā)現(xiàn)有些應(yīng)用只要打開其他音頻播放就會(huì)終止聘鳞,而有些應(yīng)用卻可以和其他應(yīng)用同時(shí)播放,在多種音頻環(huán)境中如何去控制播放的方式就是通過音頻會(huì)話來完成的要拂。下面是音頻會(huì)話的幾種會(huì)話模式:

會(huì)話類型 說明 是否要求輸入 是否要求輸出 是否遵從靜音鍵
AVAudioSessionCategoryAmbient 混音播放抠璃,可以與其他音頻應(yīng)用同時(shí)播放
AVAudioSessionCategorySoloAmbient 獨(dú)占播放
AVAudioSessionCategoryPlayback 后臺(tái)播放,也是獨(dú)占的
AVAudioSessionCategoryRecord 錄音模式脱惰,用于錄音時(shí)使用
AVAudioSessionCategoryPlayAndRecord 播放和錄音搏嗡,此時(shí)可以錄音也可以播放
AVAudioSessionCategoryAudioProcessing 硬件解碼音頻,此時(shí)不能播放和錄制
AVAudioSessionCategoryMultiRoute 多種輸入輸出拉一,例如可以耳機(jī)采盒、USB設(shè)備同時(shí)播放

注意:是否遵循靜音鍵表示在播放過程中如果用戶通過硬件設(shè)置為靜音是否能關(guān)閉聲音。

根據(jù)前面對音頻會(huì)話的理解蔚润,相信大家開發(fā)出能夠在后臺(tái)播放的音頻播放器并不難磅氨,但是注意一下,在前面的代碼中也提到設(shè)置完音頻會(huì)話類型之后需要調(diào)用setActive::方法將會(huì)話激活才能起作用嫡纠。類似的烦租,如果一個(gè)應(yīng)用已經(jīng)在播放音頻,打開我們的應(yīng)用之后設(shè)置了在后臺(tái)播放的會(huì)話類型除盏,此時(shí)其他應(yīng)用的音頻會(huì)停止而播放我們的音頻叉橱,如果希望我們的程序音頻播放完之后(關(guān)閉或退出到后臺(tái)之后)能夠繼續(xù)播放其他應(yīng)用的音頻的話則可以調(diào)用setActive::方法關(guān)閉會(huì)話。代碼如下:

//
//  ViewController.m
//  KCAVAudioPlayer
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//  AVAudioSession 音頻會(huì)話

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#define kMusicFile @"劉若英 - 原來你也在這里.mp3"
#define kMusicSinger @"劉若英"
#define kMusicTitle @"原來你也在這里"

@interface ViewController ()<AVAudioPlayerDelegate>

@property (nonatomic,strong) AVAudioPlayer *audioPlayer;//播放器
@property (weak, nonatomic) IBOutlet UILabel *controlPanel; //控制面板
@property (weak, nonatomic) IBOutlet UIProgressView *playProgress;//播放進(jìn)度
@property (weak, nonatomic) IBOutlet UILabel *musicSinger; //演唱者
@property (weak, nonatomic) IBOutlet UIButton *playOrPause; //播放/暫停按鈕(如果tag為0認(rèn)為是暫停狀態(tài)者蠕,1是播放狀態(tài))

@property (weak ,nonatomic) NSTimer *timer;//進(jìn)度更新定時(shí)器

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self setupUI];
    
}

/**
 *  顯示當(dāng)面視圖控制器時(shí)注冊遠(yuǎn)程事件
 *
 *  @param animated 是否以動(dòng)畫的形式顯示
 */
-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    //開啟遠(yuǎn)程控制
    [[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
    //作為第一響應(yīng)者
    //[self becomeFirstResponder];
}
/**
 *  當(dāng)前控制器視圖不顯示時(shí)取消遠(yuǎn)程控制
 *
 *  @param animated 是否以動(dòng)畫的形式消失
 */
-(void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    [[UIApplication sharedApplication] endReceivingRemoteControlEvents];
    //[self resignFirstResponder];
}

/**
 *  初始化UI
 */
-(void)setupUI{
    self.title=kMusicTitle;
    self.musicSinger.text=kMusicSinger;
}

-(NSTimer *)timer{
    if (!_timer) {
        _timer=[NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(updateProgress) userInfo:nil repeats:true];
    }
    return _timer;
}

/**
 *  創(chuàng)建播放器
 *
 *  @return 音頻播放器
 */
-(AVAudioPlayer *)audioPlayer{
    if (!_audioPlayer) {
        NSString *urlStr=[[NSBundle mainBundle]pathForResource:kMusicFile ofType:nil];
        NSURL *url=[NSURL fileURLWithPath:urlStr];
        NSError *error=nil;
        //初始化播放器窃祝,注意這里的Url參數(shù)只能時(shí)文件路徑,不支持HTTP Url
        _audioPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error];
        //設(shè)置播放器屬性
        _audioPlayer.numberOfLoops=0;//設(shè)置為0不循環(huán)
        _audioPlayer.delegate=self;
        [_audioPlayer prepareToPlay];//加載音頻文件到緩存
        if(error){
            NSLog(@"初始化播放器過程發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@",error.localizedDescription);
            return nil;
        }
        //設(shè)置后臺(tái)播放模式
        AVAudioSession *audioSession=[AVAudioSession sharedInstance];
        [audioSession setCategory:AVAudioSessionCategoryPlayback error:nil];
//        [audioSession setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:nil];
        [audioSession setActive:YES error:nil];
        //添加通知蠢棱,拔出耳機(jī)后暫停播放
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(routeChange:) name:AVAudioSessionRouteChangeNotification object:nil];
    }
    return _audioPlayer;
}

/**
 *  播放音頻
 */
-(void)play{
    if (![self.audioPlayer isPlaying]) {
        [self.audioPlayer play];
        self.timer.fireDate=[NSDate distantPast];//恢復(fù)定時(shí)器
    }
}

/**
 *  暫停播放
 */
-(void)pause{
    if ([self.audioPlayer isPlaying]) {
        [self.audioPlayer pause];
        self.timer.fireDate=[NSDate distantFuture];//暫停定時(shí)器锌杀,注意不能調(diào)用invalidate方法几睛,此方法會(huì)取消臊旭,之后無法恢復(fù)
        
    }
}

/**
 *  點(diǎn)擊播放/暫停按鈕
 *
 *  @param sender 播放/暫停按鈕
 */
- (IBAction)playClick:(UIButton *)sender {
    if(sender.tag){
        sender.tag=0;
        [sender setImage:[UIImage imageNamed:@"playing_btn_play_n"] forState:UIControlStateNormal];
        [sender setImage:[UIImage imageNamed:@"playing_btn_play_h"] forState:UIControlStateHighlighted];
        [self pause];
    }else{
        sender.tag=1;
        [sender setImage:[UIImage imageNamed:@"playing_btn_pause_n"] forState:UIControlStateNormal];
        [sender setImage:[UIImage imageNamed:@"playing_btn_pause_h"] forState:UIControlStateHighlighted];
        [self play];
    }
}

/**
 *  更新播放進(jìn)度
 */
-(void)updateProgress{
    float progress= self.audioPlayer.currentTime /self.audioPlayer.duration;
    [self.playProgress setProgress:progress animated:true];
}

/**
 *  一旦輸出改變則執(zhí)行此方法
 *
 *  @param notification 輸出改變通知對象
 */
-(void)routeChange:(NSNotification *)notification{
    NSDictionary *dic=notification.userInfo;
    int changeReason= [dic[AVAudioSessionRouteChangeReasonKey] intValue];
    //等于AVAudioSessionRouteChangeReasonOldDeviceUnavailable表示舊輸出不可用
    if (changeReason==AVAudioSessionRouteChangeReasonOldDeviceUnavailable) {
        AVAudioSessionRouteDescription *routeDescription=dic[AVAudioSessionRouteChangePreviousRouteKey];
        AVAudioSessionPortDescription *portDescription= [routeDescription.outputs firstObject];
        //原設(shè)備為耳機(jī)則暫停
        if ([portDescription.portType isEqualToString:@"Headphones"]) {
            [self pause];
        }
    }
    
//    [dic enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
//        NSLog(@"%@:%@",key,obj);
//    }];
}

-(void)dealloc{
    [[NSNotificationCenter defaultCenter] removeObserver:self name:AVAudioSessionRouteChangeNotification object:nil];
}

#pragma mark - 播放器代理方法
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag{
    NSLog(@"音樂播放完成...");
    //根據(jù)實(shí)際情況播放完成可以將會(huì)話關(guān)閉哈踱,其他音頻應(yīng)用繼續(xù)播放
    [[AVAudioSession sharedInstance]setActive:NO error:nil];
}

@end
擴(kuò)展--播放音樂庫中的音樂

眾所周知音樂是iOS的重要組成播放,無論是iPod玉转、iTouch、iPhone還是iPad都可以在iTunes購買音樂或添加本地音樂到音樂庫中同步到你的iOS設(shè)備殴蹄。在MediaPlayer.frameowork中有一個(gè)MPMusicPlayerController用于播放音樂庫中的音樂究抓。

下面先來看一下MPMusicPlayerController的常用屬性和方法:

屬性 說明
@property (nonatomic, readonly) MPMusicPlaybackState playbackState 播放器狀態(tài)猾担,枚舉類型:MPMusicPlaybackStateStopped:停止播放 MPMusicPlaybackStatePlaying:正在播放 MPMusicPlaybackStatePaused:暫停播放 MPMusicPlaybackStateInterrupted:播放中斷 MPMusicPlaybackStateSeekingForward:向前查找 MPMusicPlaybackStateSeekingBackward:向后查找
@property (nonatomic) MPMusicRepeatMode repeatMode 重復(fù)模式,枚舉類型:MPMusicRepeatModeDefault:默認(rèn)模式刺下,使用用戶的首選項(xiàng)(系統(tǒng)音樂程序設(shè)置)MPMusicRepeatModeNone:不重復(fù)MPMusicRepeatModeOne:單曲循環(huán) MPMusicRepeatModeAll:在當(dāng)前列表內(nèi)循環(huán)
@property (nonatomic) MPMusicShuffleMode shuffleMode 隨機(jī)播放模式绑嘹,枚舉類型:MPMusicShuffleModeDefault:默認(rèn)模式,使用用戶首選項(xiàng)(系統(tǒng)音樂程序設(shè)置)MPMusicShuffleModeOff:不隨機(jī)播放MPMusicShuffleModeSongs:按歌曲隨機(jī)播放MPMusicShuffleModeAlbums:按專輯隨機(jī)播放
@property (nonatomic, copy) MPMediaItem *nowPlayingItem 正在播放的音樂項(xiàng)
@property (nonatomic, readonly) NSUInteger indexOfNowPlayingItem 當(dāng)前正在播放的音樂在播放隊(duì)列中的索引
@property(nonatomic, readonly) BOOL isPreparedToPlay 是否準(zhǔn)好播放準(zhǔn)備
@property(nonatomic) NSTimeInterval currentPlaybackTime 當(dāng)前已播放時(shí)間橘茉,單位:秒
@property(nonatomic) float currentPlaybackRate 當(dāng)前播放速度工腋,是一個(gè)播放速度倍率,0表示暫停播放畅卓,1代表正常速度
類方法 說明
+ (MPMusicPlayerController *)applicationMusicPlayer; 獲取應(yīng)用播放器擅腰,注意此類播放器無法在后臺(tái)播放
+ (MPMusicPlayerController *)systemMusicPlayer 獲取系統(tǒng)播放器,支持后臺(tái)播放
對象方法 說明
- (void)setQueueWithQuery:(MPMediaQuery *)query 使用媒體隊(duì)列設(shè)置播放源媒體隊(duì)列
- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection 使用媒體項(xiàng)集合設(shè)置播放源媒體隊(duì)列
- (void)skipToNextItem 下一曲
- (void)skipToBeginning 從起始位置播放
- (void)skipToPreviousItem 上一曲
- (void)beginGeneratingPlaybackNotifications 開啟播放通知翁潘,注意不同于其他播放器趁冈,MPMusicPlayerController要想獲得通知必須首先開啟,默認(rèn)情況無法獲得通知
- (void)endGeneratingPlaybackNotifications 關(guān)閉播放通知
- (void)prepareToPlay 做好播放準(zhǔn)備(加載音頻到緩沖區(qū))拜马,在使用play方法播放時(shí)如果沒有做好準(zhǔn)備回自動(dòng)調(diào)用該方法
- (void)play 開始播放
- (void)pause 暫停播放
- (void)stop 停止播放
- (void)beginSeekingForward 開始向前查找(快進(jìn))
- (void)beginSeekingBackward 開始向后查找(快退)
- (void)endSeeking 結(jié)束查找
通知 說明(注意:要想獲得MPMusicPlayerController通知必須首先調(diào)用beginGeneratingPlaybackNotifications開啟通知)
MPMusicPlayerControllerPlaybackStateDidChangeNotification 播放狀態(tài)改變
MPMusicPlayerControllerNowPlayingItemDidChangeNotification 當(dāng)前播放音頻改變
MPMusicPlayerControllerVolumeDidChangeNotification 聲音大小改變
MPMediaPlaybackIsPreparedToPlayDidChangeNotification 準(zhǔn)備好播放
  • MPMusicPlayerController有兩種播放器:applicationMusicPlayersystemMusicPlayer渗勘,前者在應(yīng)用退出后音樂播放會(huì)自動(dòng)停止,后者在應(yīng)用停止后不會(huì)退出播放狀態(tài)俩莽。
  • MPMusicPlayerController加載音樂不同于前面的AVAudioPlayer是通過一個(gè)文件路徑來加載呀邢,而是需要一個(gè)播放隊(duì)列。在MPMusicPlayerController中提供了兩個(gè)方法來加載播放隊(duì)列:- (void)setQueueWithQuery:(MPMediaQuery *)query- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection豹绪,正是由于它的播放音頻來源是一個(gè)隊(duì)列价淌,因此MPMusicPlayerController支持上一曲、下一曲等操作瞒津。
    那么接下來的問題就是如何獲取MPMediaQueue或者MPMediaItemCollection蝉衣?MPMediaQueue對象有一系列的類方法來獲得媒體隊(duì)列:
  • (MPMediaQuery *)albumsQuery;
  • (MPMediaQuery *)artistsQuery;
  • (MPMediaQuery *)songsQuery;
  • (MPMediaQuery *)playlistsQuery;
  • (MPMediaQuery *)podcastsQuery;
  • (MPMediaQuery *)audiobooksQuery;
  • (MPMediaQuery *)compilationsQuery;
  • (MPMediaQuery *)composersQuery;
  • (MPMediaQuery *)genresQuery;

有了這些方法,就可以很容易獲到歌曲巷蚪、播放列表病毡、專輯媒體等媒體隊(duì)列了,這樣就可以通過:- (void)setQueueWithQuery:(MPMediaQuery *)query方法設(shè)置音樂來源了屁柏。又或者得到MPMediaQueue之后創(chuàng)建MPMediaItemCollection啦膜,使用- (void)setQueueWithItemCollection:(MPMediaItemCollection *)itemCollection設(shè)置音樂來源。

有時(shí)候可能希望用戶自己來選擇要播放的音樂淌喻,這時(shí)可以使用MPMediaPickerController僧家,它是一個(gè)視圖控制器,類似于UIImagePickerController裸删,選擇完播放來源后可以在其代理方法中獲得MPMediaItemCollection對象八拱。

無論是通過哪種方式獲得MPMusicPlayerController的媒體源,可能都希望將每個(gè)媒體的信息顯示出來,這時(shí)候可以通過MPMediaItem對象獲得肌稻。一個(gè)MPMediaItem代表一個(gè)媒體文件清蚀,通過它可以訪問媒體標(biāo)題、專輯名稱爹谭、專輯封面枷邪、音樂時(shí)長等等。無論是MPMediaQueue還是MPMediaItemCollection都有一個(gè)items屬性诺凡,它是MPMediaItem數(shù)組齿风,通過這個(gè)屬性可以獲得MPMediaItem對象。

下面就簡單看一下MPMusicPlayerController的使用绑洛,在下面的例子中簡單演示了音樂的選擇救斑、播放、暫停真屯、通知脸候、下一曲、上一曲功能绑蔫,相信有了上面的概念运沦,代碼讀起來并不復(fù)雜(示例中是直接通過MPMeidaPicker進(jìn)行音樂選擇的,但是仍然提供了兩個(gè)方法getLocalMediaQuerygetLocalMediaItemCollection來演示如何直接通過MPMediaQueue獲得媒體隊(duì)列或媒體集合):

//
//  ViewController.m
//  MPMusicPlayerController
//
//  Created by Kenshin Cui 14/03/30
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <MediaPlayer/MediaPlayer.h>

@interface ViewController ()<MPMediaPickerControllerDelegate>

@property (nonatomic,strong) MPMediaPickerController *mediaPicker;//媒體選擇控制器
@property (nonatomic,strong) MPMusicPlayerController *musicPlayer; //音樂播放器

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
}

-(void)dealloc{
    [self.musicPlayer endGeneratingPlaybackNotifications];
}

/**
 *  獲得音樂播放器
 *
 *  @return 音樂播放器
 */
-(MPMusicPlayerController *)musicPlayer{
    if (!_musicPlayer) {
        _musicPlayer=[MPMusicPlayerController systemMusicPlayer];
        [_musicPlayer beginGeneratingPlaybackNotifications];//開啟通知配深,否則監(jiān)控不到MPMusicPlayerController的通知
        [self addNotification];//添加通知
        //如果不使用MPMediaPickerController可以使用如下方法獲得音樂庫媒體隊(duì)列
        //[_musicPlayer setQueueWithItemCollection:[self getLocalMediaItemCollection]];
    }
    return _musicPlayer;
}

/**
 *  創(chuàng)建媒體選擇器
 *
 *  @return 媒體選擇器
 */
-(MPMediaPickerController *)mediaPicker{
    if (!_mediaPicker) {
        //初始化媒體選擇器携添,這里設(shè)置媒體類型為音樂,其實(shí)這里也可以選擇視頻篓叶、廣播等
//        _mediaPicker=[[MPMediaPickerController alloc]initWithMediaTypes:MPMediaTypeMusic];
        _mediaPicker=[[MPMediaPickerController alloc]initWithMediaTypes:MPMediaTypeAny];
        _mediaPicker.allowsPickingMultipleItems=YES;//允許多選
//        _mediaPicker.showsCloudItems=YES;//顯示icloud選項(xiàng)
        _mediaPicker.prompt=@"請選擇要播放的音樂";
        _mediaPicker.delegate=self;//設(shè)置選擇器代理
    }
    return _mediaPicker;
}

/**
 *  取得媒體隊(duì)列
 *
 *  @return 媒體隊(duì)列
 */
-(MPMediaQuery *)getLocalMediaQuery{
    MPMediaQuery *mediaQueue=[MPMediaQuery songsQuery];
    for (MPMediaItem *item in mediaQueue.items) {
        NSLog(@"標(biāo)題:%@,%@",item.title,item.albumTitle);
    }
    return mediaQueue;
}

/**
 *  取得媒體集合
 *
 *  @return 媒體集合
 */
-(MPMediaItemCollection *)getLocalMediaItemCollection{
    MPMediaQuery *mediaQueue=[MPMediaQuery songsQuery];
    NSMutableArray *array=[NSMutableArray array];
    for (MPMediaItem *item in mediaQueue.items) {
        [array addObject:item];
        NSLog(@"標(biāo)題:%@,%@",item.title,item.albumTitle);
    }
    MPMediaItemCollection *mediaItemCollection=[[MPMediaItemCollection alloc]initWithItems:[array copy]];
    return mediaItemCollection;
}

#pragma mark - MPMediaPickerController代理方法
//選擇完成
-(void)mediaPicker:(MPMediaPickerController *)mediaPicker didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection{
    MPMediaItem *mediaItem=[mediaItemCollection.items firstObject];//第一個(gè)播放音樂
    //注意很多音樂信息如標(biāo)題烈掠、專輯、表演者缸托、封面左敌、時(shí)長等信息都可以通過MPMediaItem的valueForKey:方法得到,但是從iOS7開始都有對應(yīng)的屬性可以直接訪問
//    NSString *title= [mediaItem valueForKey:MPMediaItemPropertyAlbumTitle];
//    NSString *artist= [mediaItem valueForKey:MPMediaItemPropertyAlbumArtist];
//    MPMediaItemArtwork *artwork= [mediaItem valueForKey:MPMediaItemPropertyArtwork];
    //UIImage *image=[artwork imageWithSize:CGSizeMake(100, 100)];//專輯圖片
    NSLog(@"標(biāo)題:%@,表演者:%@,專輯:%@",mediaItem.title ,mediaItem.artist,mediaItem.albumTitle);
    [self.musicPlayer setQueueWithItemCollection:mediaItemCollection];
    [self dismissViewControllerAnimated:YES completion:nil];
}
//取消選擇
-(void)mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker{
    [self dismissViewControllerAnimated:YES completion:nil];
}

#pragma mark - 通知
/**
 *  添加通知
 */
-(void)addNotification{
    NSNotificationCenter *notificationCenter=[NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:self selector:@selector(playbackStateChange:) name:MPMusicPlayerControllerPlaybackStateDidChangeNotification object:self.musicPlayer];
}

/**
 *  播放狀態(tài)改變通知
 *
 *  @param notification 通知對象
 */
-(void)playbackStateChange:(NSNotification *)notification{
    switch (self.musicPlayer.playbackState) {
        case MPMusicPlaybackStatePlaying:
            NSLog(@"正在播放...");
            break;
        case MPMusicPlaybackStatePaused:
            NSLog(@"播放暫停.");
            break;
        case MPMusicPlaybackStateStopped:
            NSLog(@"播放停止.");
            break;
        default:
            break;
    }
}

#pragma mark - UI事件
- (IBAction)selectClick:(UIButton *)sender {
    [self presentViewController:self.mediaPicker animated:YES completion:nil];
}

- (IBAction)playClick:(UIButton *)sender {
    [self.musicPlayer play];
}

- (IBAction)puaseClick:(UIButton *)sender {
    [self.musicPlayer pause];
}

- (IBAction)stopClick:(UIButton *)sender {
    [self.musicPlayer stop];
}

- (IBAction)nextClick:(UIButton *)sender {
    [self.musicPlayer skipToNextItem];
}

- (IBAction)prevClick:(UIButton *)sender {
    [self.musicPlayer skipToPreviousItem];
}

@end

錄音

除了上面說的,在AVFoundation框架中還要一個(gè)AVAudioRecorder類專門處理錄音操作俐镐,它同樣支持多種音頻格式矫限。與AVAudioPlayer類似,你完全可以將它看成是一個(gè)錄音機(jī)控制類佩抹,下面是常用的屬性和方法:

屬性 說明
@property(readonly, getter=isRecording) BOOL recording; 是否正在錄音叼风,只讀
@property(readonly) NSURL *url 錄音文件地址,只讀
@property(readonly) NSDictionary *settings 錄音文件設(shè)置棍苹,只讀
@property(readonly) NSTimeInterval currentTime 錄音時(shí)長无宿,只讀,注意僅僅在錄音狀態(tài)可用
@property(readonly) NSTimeInterval deviceCurrentTime 輸入設(shè)置的時(shí)間長度廊勃,只讀懈贺,注意此屬性一直可訪問
@property(getter=isMeteringEnabled) BOOL meteringEnabled; 是否啟用錄音測量经窖,如果啟用錄音測量可以獲得錄音分貝等數(shù)據(jù)信息
@property(nonatomic, copy) NSArray *channelAssignments 當(dāng)前錄音的通道
對象方法 說明
- (instancetype)initWithURL:(NSURL *)url settings:(NSDictionary *)settings error:(NSError **)outError 錄音機(jī)對象初始化方法坡垫,注意其中的url必須是本地文件url梭灿,settings是錄音格式、編碼等設(shè)置
- (BOOL)prepareToRecord 準(zhǔn)備錄音冰悠,主要用于創(chuàng)建緩沖區(qū)堡妒,如果不手動(dòng)調(diào)用,在調(diào)用record錄音時(shí)也會(huì)自動(dòng)調(diào)用
- (BOOL)record 開始錄音
- (BOOL)recordAtTime:(NSTimeInterval)time 在指定的時(shí)間開始音溉卓,一般用于錄音暫停再恢復(fù)錄音
- (BOOL)recordForDuration:(NSTimeInterval) duration 按指定的時(shí)長開始錄音
- (BOOL)recordAtTime:(NSTimeInterval)time forDuration:(NSTimeInterval) duration 在指定的時(shí)間開始錄音皮迟,并指定錄音時(shí)長
- (void)pause; 暫停錄音
- (void)stop; 停止錄音
- (BOOL)deleteRecording; 刪除錄音,注意要?jiǎng)h除錄音此時(shí)錄音機(jī)必須處于停止?fàn)顟B(tài)
- (void)updateMeters; 更新測量數(shù)據(jù)桑寨,注意只有meteringEnabled為YES此方法才可用
- (float)peakPowerForChannel:(NSUInteger)channelNumber; 指定通道的測量峰值伏尼,注意只有調(diào)用完updateMeters才有值
- (float)averagePowerForChannel:(NSUInteger)channelNumber 指定通道的測量平均值,注意只有調(diào)用完updateMeters才有值
代理方法 說明
- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag 完成錄音
- (void)audioRecorderEncodeErrorDidOccur:(AVAudioRecorder *)recorder error:(NSError *)error 錄音編碼發(fā)生錯(cuò)誤

AVAudioRecorder很多屬性和方法跟AVAudioPlayer都是類似的,但是它的創(chuàng)建有所不同尉尾,在創(chuàng)建錄音機(jī)時(shí)除了指定路徑外還必須指定錄音設(shè)置信息爆阶,因?yàn)殇浺魴C(jī)必須知道錄音文件的格式、采樣率沙咏、通道數(shù)辨图、每個(gè)采樣點(diǎn)的位數(shù)等信息,但是也并不是所有的信息都必須設(shè)置肢藐,通常只需要幾個(gè)常用設(shè)置故河。關(guān)于錄音設(shè)置詳見幫助文檔中的“AV Foundation Audio Settings Constants”。

下面就使用AVAudioRecorder創(chuàng)建一個(gè)錄音機(jī)吆豹,實(shí)現(xiàn)了錄音鱼的、暫停、停止痘煤、播放等功能鸳吸,實(shí)現(xiàn)效果大致如下:

在這個(gè)示例中將實(shí)行一個(gè)完整的錄音控制,包括錄音速勇、暫停晌砾、恢復(fù)、停止烦磁,同時(shí)還會(huì)實(shí)時(shí)展示用戶錄音的聲音波動(dòng)养匈,當(dāng)用戶點(diǎn)擊完停止按鈕還會(huì)自動(dòng)播放錄音文件。程序的構(gòu)建主要分為以下幾步:

1都伪、設(shè)置音頻會(huì)話類型為AVAudioSessionCategoryPlayAndRecord呕乎,因?yàn)槌绦蛑袪砍兜戒浺艉筒シ挪僮鳌?br> 2、創(chuàng)建錄音機(jī)AVAudioRecorder陨晶,指定錄音保存的路徑并且設(shè)置錄音屬性猬仁,注意對于一般的錄音文件要求的采樣率帝璧、位數(shù)并不高,需要適當(dāng)設(shè)置以保證錄音文件的大小和效果湿刽。
3的烁、設(shè)置錄音機(jī)代理以便在錄音完成后播放錄音,打開錄音測量保證能夠?qū)崟r(shí)獲得錄音時(shí)的聲音強(qiáng)度诈闺。(注意聲音強(qiáng)度范圍-160到0,0代表最大輸入)
4渴庆、創(chuàng)建音頻播放器AVAudioPlayer,用于在錄音完成之后播放錄音雅镊。
5襟雷、創(chuàng)建一個(gè)定時(shí)器以便實(shí)時(shí)刷新錄音測量值并更新錄音強(qiáng)度到UIProgressView中顯示。
6仁烹、添加錄音耸弄、暫停、恢復(fù)卓缰、停止操作计呈,需要注意錄音的恢復(fù)操作其實(shí)是有音頻會(huì)話管理的,恢復(fù)時(shí)只要再次調(diào)用record方法即可僚饭,無需手動(dòng)管理恢復(fù)時(shí)間等震叮。

下面是主要代碼:

//
//  ViewController.m
//  AVAudioRecorder
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#define kRecordAudioFile @"myRecord.caf"

@interface ViewController ()<AVAudioRecorderDelegate>

@property (nonatomic,strong) AVAudioRecorder *audioRecorder;//音頻錄音機(jī)
@property (nonatomic,strong) AVAudioPlayer *audioPlayer;//音頻播放器,用于播放錄音文件
@property (nonatomic,strong) NSTimer *timer;//錄音聲波監(jiān)控(注意這里暫時(shí)不對播放進(jìn)行監(jiān)控)

@property (weak, nonatomic) IBOutlet UIButton *record;//開始錄音
@property (weak, nonatomic) IBOutlet UIButton *pause;//暫停錄音
@property (weak, nonatomic) IBOutlet UIButton *resume;//恢復(fù)錄音
@property (weak, nonatomic) IBOutlet UIButton *stop;//停止錄音
@property (weak, nonatomic) IBOutlet UIProgressView *audioPower;//音頻波動(dòng)

@end

@implementation ViewController

#pragma mark - 控制器視圖方法
- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self setAudioSession];
}

#pragma mark - 私有方法
/**
 *  設(shè)置音頻會(huì)話
 */
-(void)setAudioSession{
    AVAudioSession *audioSession=[AVAudioSession sharedInstance];
    //設(shè)置為播放和錄音狀態(tài)鳍鸵,以便可以在錄制完之后播放錄音
    [audioSession setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
    [audioSession setActive:YES error:nil];
}

/**
 *  取得錄音文件保存路徑
 *
 *  @return 錄音文件路徑
 */
-(NSURL *)getSavePath{
    NSString *urlStr=[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
    urlStr=[urlStr stringByAppendingPathComponent:kRecordAudioFile];
    NSLog(@"file path:%@",urlStr);
    NSURL *url=[NSURL fileURLWithPath:urlStr];
    return url;
}

/**
 *  取得錄音文件設(shè)置
 *
 *  @return 錄音設(shè)置
 */
-(NSDictionary *)getAudioSetting{
    NSMutableDictionary *dicM=[NSMutableDictionary dictionary];
    //設(shè)置錄音格式
    [dicM setObject:@(kAudioFormatLinearPCM) forKey:AVFormatIDKey];
    //設(shè)置錄音采樣率苇瓣,8000是電話采樣率,對于一般錄音已經(jīng)夠了
    [dicM setObject:@(8000) forKey:AVSampleRateKey];
    //設(shè)置通道,這里采用單聲道
    [dicM setObject:@(1) forKey:AVNumberOfChannelsKey];
    //每個(gè)采樣點(diǎn)位數(shù),分為8偿乖、16击罪、24、32
    [dicM setObject:@(8) forKey:AVLinearPCMBitDepthKey];
    //是否使用浮點(diǎn)數(shù)采樣
    [dicM setObject:@(YES) forKey:AVLinearPCMIsFloatKey];
    //....其他設(shè)置等
    return dicM;
}

/**
 *  獲得錄音機(jī)對象
 *
 *  @return 錄音機(jī)對象
 */
-(AVAudioRecorder *)audioRecorder{
    if (!_audioRecorder) {
        //創(chuàng)建錄音文件保存路徑
        NSURL *url=[self getSavePath];
        //創(chuàng)建錄音格式設(shè)置
        NSDictionary *setting=[self getAudioSetting];
        //創(chuàng)建錄音機(jī)
        NSError *error=nil;
        _audioRecorder=[[AVAudioRecorder alloc]initWithURL:url settings:setting error:&error];
        _audioRecorder.delegate=self;
        _audioRecorder.meteringEnabled=YES;//如果要監(jiān)控聲波則必須設(shè)置為YES
        if (error) {
            NSLog(@"創(chuàng)建錄音機(jī)對象時(shí)發(fā)生錯(cuò)誤贪薪,錯(cuò)誤信息:%@",error.localizedDescription);
            return nil;
        }
    }
    return _audioRecorder;
}

/**
 *  創(chuàng)建播放器
 *
 *  @return 播放器
 */
-(AVAudioPlayer *)audioPlayer{
    if (!_audioPlayer) {
        NSURL *url=[self getSavePath];
        NSError *error=nil;
        _audioPlayer=[[AVAudioPlayer alloc]initWithContentsOfURL:url error:&error];
        _audioPlayer.numberOfLoops=0;
        [_audioPlayer prepareToPlay];
        if (error) {
            NSLog(@"創(chuàng)建播放器過程中發(fā)生錯(cuò)誤媳禁,錯(cuò)誤信息:%@",error.localizedDescription);
            return nil;
        }
    }
    return _audioPlayer;
}

/**
 *  錄音聲波監(jiān)控定制器
 *
 *  @return 定時(shí)器
 */
-(NSTimer *)timer{
    if (!_timer) {
        _timer=[NSTimer scheduledTimerWithTimeInterval:0.1f target:self selector:@selector(audioPowerChange) userInfo:nil repeats:YES];
    }
    return _timer;
}

/**
 *  錄音聲波狀態(tài)設(shè)置
 */
-(void)audioPowerChange{
    [self.audioRecorder updateMeters];//更新測量值
    float power= [self.audioRecorder averagePowerForChannel:0];//取得第一個(gè)通道的音頻,注意音頻強(qiáng)度范圍時(shí)-160到0
    CGFloat progress=(1.0/160.0)*(power+160.0);
    [self.audioPower setProgress:progress];
}
#pragma mark - UI事件
/**
 *  點(diǎn)擊錄音按鈕
 *
 *  @param sender 錄音按鈕
 */
- (IBAction)recordClick:(UIButton *)sender {
    if (![self.audioRecorder isRecording]) {
        [self.audioRecorder record];//首次使用應(yīng)用時(shí)如果調(diào)用record方法會(huì)詢問用戶是否允許使用麥克風(fēng)
        self.timer.fireDate=[NSDate distantPast];
    }
}

/**
 *  點(diǎn)擊暫定按鈕
 *
 *  @param sender 暫停按鈕
 */
- (IBAction)pauseClick:(UIButton *)sender {
    if ([self.audioRecorder isRecording]) {
        [self.audioRecorder pause];
        self.timer.fireDate=[NSDate distantFuture];
    }
}

/**
 *  點(diǎn)擊恢復(fù)按鈕
 *  恢復(fù)錄音只需要再次調(diào)用record画切,AVAudioSession會(huì)幫助你記錄上次錄音位置并追加錄音
 *
 *  @param sender 恢復(fù)按鈕
 */
- (IBAction)resumeClick:(UIButton *)sender {
    [self recordClick:sender];
}

/**
 *  點(diǎn)擊停止按鈕
 *
 *  @param sender 停止按鈕
 */
- (IBAction)stopClick:(UIButton *)sender {
    [self.audioRecorder stop];
    self.timer.fireDate=[NSDate distantFuture];
    self.audioPower.progress=0.0;
}

#pragma mark - 錄音機(jī)代理方法
/**
 *  錄音完成竣稽,錄音完成后播放錄音
 *
 *  @param recorder 錄音機(jī)對象
 *  @param flag     是否成功
 */
-(void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag{
    if (![self.audioPlayer isPlaying]) {
        [self.audioPlayer play];
    }
    NSLog(@"錄音完成!");
}

@end

音頻隊(duì)列服務(wù)

大家應(yīng)該已經(jīng)注意到了,無論是前面的錄音還是音頻播放均不支持網(wǎng)絡(luò)流媒體播放霍弹,當(dāng)然對于錄音來說這種需求可能不大毫别,但是對于音頻播放來說有時(shí)候就很有必要了。 AVAudioPlayer只能播放本地文件典格,并且是一次性加載所以音頻數(shù)據(jù)岛宦,初始化AVAudioPlayer時(shí)指定的URL也只能是File URL而不能是HTTP URL。當(dāng)然耍缴,將音頻文件下載到本地然后再調(diào)用AVAudioPlayer來播放也是一種播放網(wǎng)絡(luò)音頻的辦法砾肺,但是這種方式最大的弊端就是必須等到整個(gè)音頻播放完成才能播放挽霉,而不能使用流式播放,這往往在實(shí)際開發(fā)中是不切實(shí)際的变汪。那么在iOS中如何播放網(wǎng)絡(luò)流媒體呢侠坎?就是使用AudioToolbox框架中的音頻隊(duì)列服務(wù)Audio Queue Services

使用音頻隊(duì)列服務(wù)完全可以做到音頻播放和錄制疫衩,首先看一下錄音音頻服務(wù)隊(duì)列:

一個(gè)音頻服務(wù)隊(duì)列Audio Queue有三部分組成:

三個(gè)緩沖器Buffers:每個(gè)緩沖器都是一個(gè)存儲(chǔ)音頻數(shù)據(jù)的臨時(shí)倉庫硅蹦。

一個(gè)緩沖隊(duì)列Buffer Queue:一個(gè)包含音頻緩沖器的有序隊(duì)列荣德。

一個(gè)回調(diào)Callback:一個(gè)自定義的隊(duì)列回調(diào)函數(shù)闷煤。

聲音通過輸入設(shè)備進(jìn)入緩沖隊(duì)列中,首先填充第一個(gè)緩沖器涮瞻;當(dāng)?shù)谝粋€(gè)緩沖器填充滿之后自動(dòng)填充下一個(gè)緩沖器鲤拿,同時(shí)會(huì)調(diào)用回調(diào)函數(shù)片任;在回調(diào)函數(shù)中需要將緩沖器中的音頻數(shù)據(jù)寫入磁盤榜跌,同時(shí)將緩沖器放回到緩沖隊(duì)列中以便重用麻裁。下面是Apple官方關(guān)于音頻隊(duì)列服務(wù)的流程示意圖:

類似的撒犀,看一下音頻播放緩沖隊(duì)列声滥,其組成部分和錄音緩沖隊(duì)列類似副渴。

但是在音頻播放緩沖隊(duì)列中跷睦,回調(diào)函數(shù)調(diào)用的時(shí)機(jī)不同于音頻錄制緩沖隊(duì)列赡矢,流程剛好相反慕匠。將音頻讀取到緩沖器中饱须,一旦一個(gè)緩沖器填充滿之后就放到緩沖隊(duì)列中,然后繼續(xù)填充其他緩沖器台谊;當(dāng)開始播放時(shí)蓉媳,則從第一個(gè)緩沖器中讀取音頻進(jìn)行播放;一旦播放完之后就會(huì)觸發(fā)回調(diào)函數(shù)锅铅,開始播放下一個(gè)緩沖器中的音頻酪呻,同時(shí)填充第一個(gè)緩沖器放;填充滿之后再次放回到緩沖隊(duì)列盐须。下面是詳細(xì)的流程:

當(dāng)然玩荠,要明白音頻隊(duì)列服務(wù)的原理并不難,問題是如何實(shí)現(xiàn)這個(gè)自定義的回調(diào)函數(shù)贼邓,這其中我們有大量的工作要做阶冈,控制播放狀態(tài)、處理異常中斷立帖、進(jìn)行音頻編碼等等眼溶。由于牽扯內(nèi)容過多,而且不是本文目的晓勇,如果以后有時(shí)間將另開一篇文章重點(diǎn)介紹堂飞,目前有很多第三方優(yōu)秀框架可以直接使用灌旧,例如AudioStreamerFreeStreamer绰筛。由于前者當(dāng)前只有非ARC版本枢泰,所以下面不妨使用FreeStreamer來簡單演示在線音頻播放的過程,當(dāng)然在使用之前要做如下準(zhǔn)備工作:

1铝噩、拷貝FreeStreamer中的Reachability.h衡蚂、Reachability.mCommonastreamer兩個(gè)文件夾中的內(nèi)容到項(xiàng)目中骏庸。
2毛甲、添加FreeStreamer使用的類庫:CFNetwork.frameworkAudioToolbox.framework具被、AVFoundation.framework
玻募、libxml2.dylibMediaPlayer.framework一姿。
3七咧、如果引用libxml2.dylib編譯不通過,需要在Xcode的Targets-Build Settings-Header Build Path中添加$(SDKROOT)/usr/include/libxml2叮叹。
4艾栋、將FreeStreamer中的FreeStreamerMobile-Prefix.pch文件添加到項(xiàng)目中并將Targets-Build Settings-Precompile Prefix Header設(shè)置為YES,在Targets-Build Settings-Prefix Header設(shè)置為$(SRCROOT)/項(xiàng)目名稱/FreeStreamerMobile-Prefix.pch(因?yàn)閄code6默認(rèn)沒有pch文件)

然后就可以編寫代碼播放網(wǎng)絡(luò)音頻了:

//
//  ViewController.m
//  AudioQueueServices
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//  使用FreeStreamer實(shí)現(xiàn)網(wǎng)絡(luò)音頻播放

#import "ViewController.h"
#import "FSAudioStream.h"

@interface ViewController ()

@property (nonatomic,strong) FSAudioStream *audioStream;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self.audioStream play];
}

/**
 *  取得本地文件路徑
 *
 *  @return 文件路徑
 */
-(NSURL *)getFileUrl{
    NSString *urlStr=[[NSBundle mainBundle]pathForResource:@"劉若英 - 原來你也在這里.mp3" ofType:nil];
    NSURL *url=[NSURL fileURLWithPath:urlStr];
    return url;
}
-(NSURL *)getNetworkUrl{
    NSString *urlStr=@"http://192.168.1.102/liu.mp3";
    NSURL *url=[NSURL URLWithString:urlStr];
    return url;
}

/**
 *  創(chuàng)建FSAudioStream對象
 *
 *  @return FSAudioStream對象
 */
-(FSAudioStream *)audioStream{
    if (!_audioStream) {
        NSURL *url=[self getNetworkUrl];
        //創(chuàng)建FSAudioStream對象
        _audioStream=[[FSAudioStream alloc]initWithUrl:url];
        _audioStream.onFailure=^(FSAudioStreamError error,NSString *description){
            NSLog(@"播放過程中發(fā)生錯(cuò)誤蛉顽,錯(cuò)誤信息:%@",description);
        };
        _audioStream.onCompletion=^(){
            NSLog(@"播放完成!");
        };
        [_audioStream setVolume:0.5];//設(shè)置聲音
    }
    return _audioStream;
}

@end

其實(shí)FreeStreamer的功能很強(qiáng)大蝗砾,不僅僅是播放本地、網(wǎng)絡(luò)音頻那么簡單蜂林,它還支持播放列表遥诉、檢查包內(nèi)容、RSS訂閱噪叙、播放中斷等很多強(qiáng)大的功能矮锈,甚至還包含了一個(gè)音頻分析器,有興趣的朋友可以訪問官網(wǎng)查看詳細(xì)用法

視頻

MPMoviePlayerController

在iOS中播放視頻可以使用MediaPlayer.framework中的MPMoviePlayerController類來完成睁蕾,它支持本地視頻和網(wǎng)絡(luò)視頻播放苞笨。這個(gè)類實(shí)現(xiàn)了MPMediaPlayback協(xié)議,因此具備一般的播放器控制功能子眶,例如播放瀑凝、暫停、停止等臭杰。但是MPMediaPlayerController自身并不是一個(gè)完整的視圖控制器粤咪,如果要在UI中展示視頻需要將view屬性添加到界面中。下面列出了MPMoviePlayerController的常用屬性和方法:

屬性 說明
@property (nonatomic, copy) NSURL *contentURL 播放媒體URL渴杆,這個(gè)URL可以是本地路徑寥枝,也可以是網(wǎng)絡(luò)路徑
@property (nonatomic, readonly) UIView *view 播放器視圖宪塔,如果要顯示視頻必須將此視圖添加到控制器視圖中
@property (nonatomic, readonly) UIView *backgroundView 播放器背景視圖
@property (nonatomic, readonly) MPMoviePlaybackState playbackState 媒體播放狀態(tài),枚舉類型:MPMoviePlaybackStateStopped:停止播放MPMoviePlaybackStatePlaying:正在播放MPMoviePlaybackStatePaused:暫停MPMoviePlaybackStateInterrupted:中斷MPMoviePlaybackStateSeekingForward:向前定位MPMoviePlaybackStateSeekingBackward:向后定位
@property (nonatomic, readonly) MPMovieLoadState loadState 網(wǎng)絡(luò)媒體加載狀態(tài)囊拜,枚舉類型:MPMovieLoadStateUnknown:未知類型MPMovieLoadStatePlayable:MPMovieLoadStatePlaythroughOK:這種狀態(tài)如果shouldAutoPlay為YES將自動(dòng)播放MPMovieLoadStateStalled:停滯狀態(tài)
@property (nonatomic) MPMovieControlStyle controlStyle 控制面板風(fēng)格某筐,枚舉類型:MPMovieControlStyleNone:無控制面板 MPMovieControlStyleEmbedded:嵌入視頻風(fēng)格 MPMovieControlStyleFullscreen:全屏 MPMovieControlStyleDefault:默認(rèn)風(fēng)格
@property (nonatomic) MPMovieRepeatMode repeatMode; 重復(fù)播放模式,枚舉類型: MPMovieRepeatModeNone:不重復(fù)冠跷,默認(rèn)值MPMovieRepeatModeOne:重復(fù)播放
@property (nonatomic) BOOL shouldAutoplay 當(dāng)網(wǎng)絡(luò)媒體緩存到一定數(shù)據(jù)時(shí)是否自動(dòng)播放南誊,默認(rèn)為YES
@property (nonatomic, getter=isFullscreen) BOOL fullscreen 是否全屏展示,默認(rèn)為NO蜜托,注意如果要通過此屬性設(shè)置全屏必須在視圖顯示完成后設(shè)置抄囚,否則無效
@property (nonatomic) MPMovieScalingMode scalingMode 視頻縮放填充模式,枚舉類型:MPMovieScalingModeNone:不進(jìn)行任何縮放MPMovieScalingModeAspectFit:固定縮放比例并且盡量全部展示視頻盗冷,不會(huì)裁切視頻 MPMovieScalingModeAspectFill:固定縮放比例并填充滿整個(gè)視圖展示怠苔,可能會(huì)裁切視頻 MPMovieScalingModeFill:不固定縮放比例壓縮填充整個(gè)視圖同廉,視頻不會(huì)被裁切但是比例失衡
@property (nonatomic, readonly) BOOL readyForDisplay 是否有相關(guān)媒體被播放
@property (nonatomic, readonly) MPMovieMediaTypeMask movieMediaTypes 媒體類別仪糖,枚舉類型: MPMovieMediaTypeMaskNone:未知類型MPMovieMediaTypeMaskVideo:視頻 MPMovieMediaTypeMaskAudio:音頻
@property (nonatomic) MPMovieSourceType movieSourceType 媒體源,枚舉類型:MPMovieSourceTypeUnknown:未知來源MPMovieSourceTypeFile:本地文件 MPMovieSourceTypeStreaming:流媒體(直播或點(diǎn)播)
@property (nonatomic, readonly) NSTimeInterval duration 媒體時(shí)長迫肖,如果未知?jiǎng)t返回0
@property (nonatomic, readonly) NSTimeInterval playableDuration 媒體可播放時(shí)長锅劝,主要用于表示網(wǎng)絡(luò)媒體已下載視頻時(shí)長
@property (nonatomic, readonly) CGSize naturalSize 視頻實(shí)際尺寸,如果未知?jiǎng)t返回CGSizeZero
@property (nonatomic) NSTimeInterval initialPlaybackTime 起始播放時(shí)間
@property (nonatomic) NSTimeInterval endPlaybackTime 終止播放時(shí)間
@property (nonatomic) BOOL allowsAirPlay 是否允許無線播放蟆湖,默認(rèn)為YES
@property (nonatomic, readonly, getter=isAirPlayVideoActive) BOOL airPlayVideoActive 當(dāng)前媒體是否正在通過AirPlay播放
@property(nonatomic, readonly) BOOL isPreparedToPlay 是否準(zhǔn)備好播放
@property(nonatomic) NSTimeInterval currentPlaybackTime 當(dāng)前播放時(shí)間故爵,單位:秒
@property(nonatomic) float currentPlaybackRate 當(dāng)前播放速度,如果暫停則為0隅津,正常速度為1.0诬垂,非0數(shù)據(jù)表示倍率
對象方法 說明
- (instancetype)initWithContentURL:(NSURL *)url 使用指定的URL初始化媒體播放控制器對象
- (void)setFullscreen:(BOOL)fullscreen animated:(BOOL)animated 設(shè)置視頻全屏,注意如果要通過此方法設(shè)置全屏則必須在其視圖顯示之后設(shè)置伦仍,否則無效
- (void)requestThumbnailImagesAtTimes:(NSArray *)playbackTimes timeOption:(MPMovieTimeOption)option 獲取在指定播放時(shí)間的視頻縮略圖结窘,第一個(gè)參數(shù)是獲取縮略圖的時(shí)間點(diǎn)數(shù)組;第二個(gè)參數(shù)代表時(shí)間點(diǎn)精度充蓝,枚舉類型:MPMovieTimeOptionNearestKeyFrame:時(shí)間點(diǎn)附近MPMovieTimeOptionExact:準(zhǔn)確時(shí)間
- (void)cancelAllThumbnailImageRequests 取消所有縮略圖獲取請求
- (void)prepareToPlay 準(zhǔn)備播放隧枫,加載視頻數(shù)據(jù)到緩存,當(dāng)調(diào)用play方法時(shí)如果沒有準(zhǔn)備好會(huì)自動(dòng)調(diào)用此方法
- (void)play 開始播放
- (void)pause 暫停播放
- (void)stop 停止播放
- (void)beginSeekingForward 向前定位
- (void)beginSeekingBackward 向后定位
- (void)endSeeking 停止快進(jìn)/快退
通知 說明
MPMoviePlayerScalingModeDidChangeNotification 視頻縮放填充模式發(fā)生改變
MPMoviePlayerPlaybackDidFinishNotification 媒體播放完成或用戶手動(dòng)退出谓苟,具體完成原因可以通過通知userInfo中的key為MPMoviePlayerPlaybackDidFinishReasonUserInfoKey的對象獲取
MPMoviePlayerPlaybackStateDidChangeNotification 播放狀態(tài)改變官脓,可配合playbakcState屬性獲取具體狀態(tài)
MPMoviePlayerLoadStateDidChangeNotification 媒體網(wǎng)絡(luò)加載狀態(tài)改變
MPMoviePlayerNowPlayingMovieDidChangeNotification 當(dāng)前播放的媒體內(nèi)容發(fā)生改變
MPMoviePlayerWillEnterFullscreenNotification 將要進(jìn)入全屏
MPMoviePlayerDidEnterFullscreenNotification 進(jìn)入全屏后
MPMoviePlayerWillExitFullscreenNotification 將要退出全屏
MPMoviePlayerDidExitFullscreenNotification 退出全屏后
MPMoviePlayerIsAirPlayVideoActiveDidChangeNotification 當(dāng)媒體開始通過AirPlay播放或者結(jié)束AirPlay播放
MPMoviePlayerReadyForDisplayDidChangeNotification 視頻顯示狀態(tài)改變
MPMovieMediaTypesAvailableNotification 確定了媒體可用類型后
MPMovieSourceTypeAvailableNotification 確定了媒體來源后
MPMovieDurationAvailableNotification 確定了媒體播放時(shí)長后
MPMovieNaturalSizeAvailableNotification 確定了媒體的實(shí)際尺寸后
MPMoviePlayerThumbnailImageRequestDidFinishNotification 縮略圖請求完成之后
MPMediaPlaybackIsPreparedToPlayDidChangeNotification 做好播放準(zhǔn)備后

注意MPMediaPlayerController的狀態(tài)等信息并不是通過代理來和外界交互的,而是通過通知中心涝焙,因此從上面的列表中可以看到常用的一些通知卑笨。由于MPMoviePlayerController本身對于媒體播放做了深度的封裝,使用起來就相當(dāng)簡單:創(chuàng)建MPMoviePlayerController對象仑撞,設(shè)置frame屬性赤兴,將MPMoviePlayerControllerview添加到控制器視圖中芭商。下面的示例中將創(chuàng)建一個(gè)播放控制器并添加播放狀態(tài)改變及播放完成的通知:

//
//  ViewController.m
//  MPMoviePlayerController
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <MediaPlayer/MediaPlayer.h>

@interface ViewController ()

@property (nonatomic,strong) MPMoviePlayerController *moviePlayer;//視頻播放控制器

@end

@implementation ViewController

#pragma mark - 控制器視圖方法
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //播放
    [self.moviePlayer play];
    
    //添加通知
    [self addNotification];
    
}

-(void)dealloc{
    //移除所有通知監(jiān)控
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}


#pragma mark - 私有方法
/**
 *  取得本地文件路徑
 *
 *  @return 文件路徑
 */
-(NSURL *)getFileUrl{
    NSString *urlStr=[[NSBundle mainBundle] pathForResource:@"The New Look of OS X Yosemite.mp4" ofType:nil];
    NSURL *url=[NSURL fileURLWithPath:urlStr];
    return url;
}

/**
 *  取得網(wǎng)絡(luò)文件路徑
 *
 *  @return 文件路徑
 */
-(NSURL *)getNetworkUrl{
    NSString *urlStr=@"http://192.168.1.161/The New Look of OS X Yosemite.mp4";
    urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url=[NSURL URLWithString:urlStr];
    return url;
}

/**
 *  創(chuàng)建媒體播放控制器
 *
 *  @return 媒體播放控制器
 */
-(MPMoviePlayerController *)moviePlayer{
    if (!_moviePlayer) {
        NSURL *url=[self getNetworkUrl];
        _moviePlayer=[[MPMoviePlayerController alloc]initWithContentURL:url];
        _moviePlayer.view.frame=self.view.bounds;
        _moviePlayer.view.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
        [self.view addSubview:_moviePlayer.view];
    }
    return _moviePlayer;
}

/**
 *  添加通知監(jiān)控媒體播放控制器狀態(tài)
 */
-(void)addNotification{
    NSNotificationCenter *notificationCenter=[NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackStateChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:self.moviePlayer];
    [notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackFinished:) name:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayer];
    
}

/**
 *  播放狀態(tài)改變,注意播放完成時(shí)的狀態(tài)是暫停
 *
 *  @param notification 通知對象
 */
-(void)mediaPlayerPlaybackStateChange:(NSNotification *)notification{
    switch (self.moviePlayer.playbackState) {
        case MPMoviePlaybackStatePlaying:
            NSLog(@"正在播放...");
            break;
        case MPMoviePlaybackStatePaused:
            NSLog(@"暫停播放.");
            break;
        case MPMoviePlaybackStateStopped:
            NSLog(@"停止播放.");
            break;
        default:
            NSLog(@"播放狀態(tài):%li",self.moviePlayer.playbackState);
            break;
    }
}

/**
 *  播放完成
 *
 *  @param notification 通知對象
 */
-(void)mediaPlayerPlaybackFinished:(NSNotification *)notification{
    NSLog(@"播放完成.%li",self.moviePlayer.playbackState);
}


@end

運(yùn)行效果:


從上面的API大家也不難看出其實(shí)MPMoviePlayerController功能相當(dāng)強(qiáng)大搀缠,日常開發(fā)中作為一般的媒體播放器也完全沒有問題铛楣。MPMoviePlayerController除了一般的視頻播放和控制外還有一些強(qiáng)大的功能,例如截取視頻縮略圖艺普。請求視頻縮略圖時(shí)只要調(diào)用- (void)requestThumbnailImagesAtTimes:(NSArray *)playbackTimes timeOption:(MPMovieTimeOption)option方法指定獲得縮略圖的時(shí)間點(diǎn),然后監(jiān)控MPMoviePlayerThumbnailImageRequestDidFinishNotification通知歧譬,每個(gè)時(shí)間點(diǎn)的縮略圖請求完成就會(huì)調(diào)用通知岸浑,在通知調(diào)用方法中可以通過MPMoviePlayerThumbnailImageKey獲得UIImage對象處理即可。例如下面的程序演示了在程序啟動(dòng)后獲得兩個(gè)時(shí)間點(diǎn)的縮略圖的過程瑰步,截圖成功后保存到相冊:

//
//  ViewController.m
//  MPMoviePlayerController
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//  視頻截圖

#import "ViewController.h"
#import <MediaPlayer/MediaPlayer.h>

@interface ViewController ()

@property (nonatomic,strong) MPMoviePlayerController *moviePlayer;//視頻播放控制器

@end

@implementation ViewController

#pragma mark - 控制器視圖方法
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //播放
    [self.moviePlayer play];
    
    //添加通知
    [self addNotification];
    
    //獲取縮略圖
    [self thumbnailImageRequest];
}

-(void)dealloc{
    //移除所有通知監(jiān)控
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}


#pragma mark - 私有方法
/**
 *  取得本地文件路徑
 *
 *  @return 文件路徑
 */
-(NSURL *)getFileUrl{
    NSString *urlStr=[[NSBundle mainBundle] pathForResource:@"The New Look of OS X Yosemite.mp4" ofType:nil];
    NSURL *url=[NSURL fileURLWithPath:urlStr];
    return url;
}

/**
 *  取得網(wǎng)絡(luò)文件路徑
 *
 *  @return 文件路徑
 */
-(NSURL *)getNetworkUrl{
    NSString *urlStr=@"http://192.168.1.161/The New Look of OS X Yosemite.mp4";
    urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url=[NSURL URLWithString:urlStr];
    return url;
}

/**
 *  創(chuàng)建媒體播放控制器
 *
 *  @return 媒體播放控制器
 */
-(MPMoviePlayerController *)moviePlayer{
    if (!_moviePlayer) {
        NSURL *url=[self getNetworkUrl];
        _moviePlayer=[[MPMoviePlayerController alloc]initWithContentURL:url];
        _moviePlayer.view.frame=self.view.bounds;
        _moviePlayer.view.autoresizingMask=UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
        [self.view addSubview:_moviePlayer.view];
    }
    return _moviePlayer;
}

/**
 *  獲取視頻縮略圖
 */
-(void)thumbnailImageRequest{
    //獲取13.0s矢洲、21.5s的縮略圖
    [self.moviePlayer requestThumbnailImagesAtTimes:@[@13.0,@21.5] timeOption:MPMovieTimeOptionNearestKeyFrame];
}

#pragma mark - 控制器通知
/**
 *  添加通知監(jiān)控媒體播放控制器狀態(tài)
 */
-(void)addNotification{
    NSNotificationCenter *notificationCenter=[NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackStateChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:self.moviePlayer];
    [notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackFinished:) name:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayer];
    [notificationCenter addObserver:self selector:@selector(mediaPlayerThumbnailRequestFinished:) name:MPMoviePlayerThumbnailImageRequestDidFinishNotification object:self.moviePlayer];
    
}

/**
 *  播放狀態(tài)改變,注意播放完成時(shí)的狀態(tài)是暫停
 *
 *  @param notification 通知對象
 */
-(void)mediaPlayerPlaybackStateChange:(NSNotification *)notification{
    switch (self.moviePlayer.playbackState) {
        case MPMoviePlaybackStatePlaying:
            NSLog(@"正在播放...");
            break;
        case MPMoviePlaybackStatePaused:
            NSLog(@"暫停播放.");
            break;
        case MPMoviePlaybackStateStopped:
            NSLog(@"停止播放.");
            break;
        default:
            NSLog(@"播放狀態(tài):%li",self.moviePlayer.playbackState);
            break;
    }
}

/**
 *  播放完成
 *
 *  @param notification 通知對象
 */
-(void)mediaPlayerPlaybackFinished:(NSNotification *)notification{
    NSLog(@"播放完成.%li",self.moviePlayer.playbackState);
}

/**
 *  縮略圖請求完成,此方法每次截圖成功都會(huì)調(diào)用一次
 *
 *  @param notification 通知對象
 */
-(void)mediaPlayerThumbnailRequestFinished:(NSNotification *)notification{
    NSLog(@"視頻截圖完成.");
    UIImage *image=notification.userInfo[MPMoviePlayerThumbnailImageKey];
    //保存圖片到相冊(首次調(diào)用會(huì)請求用戶獲得訪問相冊權(quán)限)
    UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
}

@end

截圖效果:



擴(kuò)展--使用AVFoundation生成縮略圖

通過前面的方法大家應(yīng)該已經(jīng)看到缩焦,使用MPMoviePlayerController來生成縮略圖足夠簡單读虏,但是如果僅僅是是為了生成縮略圖而不進(jìn)行視頻播放的話,此刻使用MPMoviePlayerController就有點(diǎn)大材小用了袁滥。其實(shí)使用AVFundation框架中的AVAssetImageGenerator就可以獲取視頻縮略圖盖桥。使用AVAssetImageGenerator獲取縮略圖大致分為三個(gè)步驟:

1、創(chuàng)建AVURLAsset對象(此類主要用于獲取媒體信息题翻,包括視頻揩徊、聲音等)。
2嵌赠、根據(jù)AVURLAsset創(chuàng)建AVAssetImageGenerator對象塑荒。
3、使用AVAssetImageGenerator的copyCGImageAtTime::方法獲得指定時(shí)間點(diǎn)的截圖姜挺。

//
//  ViewController.m
//  AVAssetImageGenerator
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //獲取第13.0s的縮略圖
    [self thumbnailImageRequest:13.0];
}

#pragma mark - 私有方法
/**
 *  取得本地文件路徑
 *
 *  @return 文件路徑
 */
-(NSURL *)getFileUrl{
    NSString *urlStr=[[NSBundle mainBundle] pathForResource:@"The New Look of OS X Yosemite.mp4" ofType:nil];
    NSURL *url=[NSURL fileURLWithPath:urlStr];
    return url;
}

/**
 *  取得網(wǎng)絡(luò)文件路徑
 *
 *  @return 文件路徑
 */
-(NSURL *)getNetworkUrl{
    NSString *urlStr=@"http://192.168.1.161/The New Look of OS X Yosemite.mp4";
    urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url=[NSURL URLWithString:urlStr];
    return url;
}

/**
 *  截取指定時(shí)間的視頻縮略圖
 *
 *  @param timeBySecond 時(shí)間點(diǎn)
 */
-(void)thumbnailImageRequest:(CGFloat )timeBySecond{
    //創(chuàng)建URL
    NSURL *url=[self getNetworkUrl];
    //根據(jù)url創(chuàng)建AVURLAsset
    AVURLAsset *urlAsset=[AVURLAsset assetWithURL:url];
    //根據(jù)AVURLAsset創(chuàng)建AVAssetImageGenerator
    AVAssetImageGenerator *imageGenerator=[AVAssetImageGenerator assetImageGeneratorWithAsset:urlAsset];
    /*截圖
     * requestTime:縮略圖創(chuàng)建時(shí)間
     * actualTime:縮略圖實(shí)際生成的時(shí)間
     */
    NSError *error=nil;
    CMTime time=CMTimeMakeWithSeconds(timeBySecond, 10);//CMTime是表示電影時(shí)間信息的結(jié)構(gòu)體齿税,第一個(gè)參數(shù)表示是視頻第幾秒,第二個(gè)參數(shù)表示每秒幀數(shù).(如果要活的某一秒的第幾幀可以使用CMTimeMake方法)
    CMTime actualTime;
    CGImageRef cgImage= [imageGenerator copyCGImageAtTime:time actualTime:&actualTime error:&error];
    if(error){
        NSLog(@"截取視頻縮略圖時(shí)發(fā)生錯(cuò)誤初家,錯(cuò)誤信息:%@",error.localizedDescription);
        return;
    }
    CMTimeShow(actualTime);
    UIImage *image=[UIImage imageWithCGImage:cgImage];//轉(zhuǎn)化為UIImage
    //保存到相冊
    UIImageWriteToSavedPhotosAlbum(image,nil, nil, nil);
    CGImageRelease(cgImage);
}

@end

生成的縮略圖效果:


MPMoviePlayerViewController

其實(shí)MPMoviePlayerController如果不作為嵌入視頻來播放(例如在新聞中嵌入一個(gè)視頻)偎窘,通常在播放時(shí)都是占滿一個(gè)屏幕的,特別是在iPhone溜在、iTouch上陌知。因此從iOS3.2以后蘋果也在思考既然MPMoviePlayerController在使用時(shí)通常都是將其視圖view添加到另外一個(gè)視圖控制器中作為子視圖,那么何不直接創(chuàng)建一個(gè)控制器視圖內(nèi)部創(chuàng)建一個(gè)MPMoviePlayerController屬性并且默認(rèn)全屏播放掖肋,開發(fā)者在開發(fā)的時(shí)候直接使用這個(gè)視圖控制器仆葡。這個(gè)內(nèi)部有一個(gè)MPMoviePlayerController的視圖控制器就是MPMoviePlayerViewController,它繼承于UIViewControllerMPMoviePlayerViewController內(nèi)部多了一個(gè)moviePlayer屬性和一個(gè)帶有url的初始化方法沿盅,同時(shí)它內(nèi)部實(shí)現(xiàn)了一些作為模態(tài)視圖展示所特有的功能把篓,例如默認(rèn)是全屏模式展示、彈出后自動(dòng)播放腰涧、作為模態(tài)窗口展示時(shí)如果點(diǎn)擊“Done”按鈕會(huì)自動(dòng)退出模態(tài)窗口等韧掩。在下面的示例中就不直接將播放器放到主視圖控制器,而是放到一個(gè)模態(tài)視圖控制器中窖铡,簡單演示MPMoviePlayerViewController的使用疗锐。

//
//  ViewController.m
//  MPMoviePlayerViewController
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//  MPMoviePlayerViewController使用

#import "ViewController.h"
#import <MediaPlayer/MediaPlayer.h>

@interface ViewController ()

//播放器視圖控制器
@property (nonatomic,strong) MPMoviePlayerViewController *moviePlayerViewController;

@end

@implementation ViewController

#pragma mark - 控制器視圖方法
- (void)viewDidLoad {
    [super viewDidLoad];

}

-(void)dealloc{
    //移除所有通知監(jiān)控
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}


#pragma mark - 私有方法
/**
 *  取得本地文件路徑
 *
 *  @return 文件路徑
 */
-(NSURL *)getFileUrl{
    NSString *urlStr=[[NSBundle mainBundle] pathForResource:@"The New Look of OS X Yosemite.mp4" ofType:nil];
    NSURL *url=[NSURL fileURLWithPath:urlStr];
    return url;
}

/**
 *  取得網(wǎng)絡(luò)文件路徑
 *
 *  @return 文件路徑
 */
-(NSURL *)getNetworkUrl{
    NSString *urlStr=@"http://192.168.1.161/The New Look of OS X Yosemite.mp4";
    urlStr=[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url=[NSURL URLWithString:urlStr];
    return url;
}

-(MPMoviePlayerViewController *)moviePlayerViewController{
    if (!_moviePlayerViewController) {
        NSURL *url=[self getNetworkUrl];
        _moviePlayerViewController=[[MPMoviePlayerViewController alloc]initWithContentURL:url];
        [self addNotification];
    }
    return _moviePlayerViewController;
}
#pragma mark - UI事件
- (IBAction)playClick:(UIButton *)sender {
    self.moviePlayerViewController=nil;//保證每次點(diǎn)擊都重新創(chuàng)建視頻播放控制器視圖,避免再次點(diǎn)擊時(shí)由于不播放的問題
//    [self presentViewController:self.moviePlayerViewController animated:YES completion:nil];
    //注意费彼,在MPMoviePlayerViewController.h中對UIViewController擴(kuò)展兩個(gè)用于模態(tài)展示和關(guān)閉MPMoviePlayerViewController的方法滑臊,增加了一種下拉展示動(dòng)畫效果
    [self presentMoviePlayerViewControllerAnimated:self.moviePlayerViewController];
}

#pragma mark - 控制器通知
/**
 *  添加通知監(jiān)控媒體播放控制器狀態(tài)
 */
-(void)addNotification{
    NSNotificationCenter *notificationCenter=[NSNotificationCenter defaultCenter];
    [notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackStateChange:) name:MPMoviePlayerPlaybackStateDidChangeNotification object:self.moviePlayerViewController.moviePlayer];
    [notificationCenter addObserver:self selector:@selector(mediaPlayerPlaybackFinished:) name:MPMoviePlayerPlaybackDidFinishNotification object:self.moviePlayerViewController.moviePlayer];
    
}

/**
 *  播放狀態(tài)改變,注意播放完成時(shí)的狀態(tài)是暫停
 *
 *  @param notification 通知對象
 */
-(void)mediaPlayerPlaybackStateChange:(NSNotification *)notification{
    switch (self.moviePlayerViewController.moviePlayer.playbackState) {
        case MPMoviePlaybackStatePlaying:
            NSLog(@"正在播放...");
            break;
        case MPMoviePlaybackStatePaused:
            NSLog(@"暫停播放.");
            break;
        case MPMoviePlaybackStateStopped:
            NSLog(@"停止播放.");
            break;
        default:
            NSLog(@"播放狀態(tài):%li",self.moviePlayerViewController.moviePlayer.playbackState);
            break;
    }
}

/**
 *  播放完成
 *
 *  @param notification 通知對象
 */
-(void)mediaPlayerPlaybackFinished:(NSNotification *)notification{
    NSLog(@"播放完成.%li",self.moviePlayerViewController.moviePlayer.playbackState);
}

@end

運(yùn)行效果:


這里需要強(qiáng)調(diào)一下箍铲,由于MPMoviePlayerViewController的初始化方法做了大量工作(例如設(shè)置URL雇卷、自動(dòng)播放、添加點(diǎn)擊Done完成的監(jiān)控等)颠猴,所以當(dāng)再次點(diǎn)擊播放彈出新的模態(tài)窗口的時(shí)如果不銷毀之前的MPMoviePlayerViewController关划,那么新的對象就無法完成初始化,這樣也就不能再次進(jìn)行播放芙粱。

AVPlayer

MPMoviePlayerController足夠強(qiáng)大祭玉,幾乎不用寫幾行代碼就能完成一個(gè)播放器,但是正是由于它的高度封裝使得要自定義這個(gè)播放器變得很復(fù)雜春畔,甚至是不可能完成。例如有些時(shí)候需要自定義播放器的樣式岛都,那么如果要使用MPMoviePlayerController就不合適了律姨,如果要對視頻有自由的控制則可以使用AVPlayerAVPlayer存在于AVFoundation中臼疫,它更加接近于底層择份,所以靈活性也更強(qiáng):

AVPlayer本身并不能顯示視頻,而且它也不像MPMoviePlayerController有一個(gè)view屬性烫堤。如果AVPlayer要顯示必須創(chuàng)建一個(gè)播放器層AVPlayerLayer用于展示荣赶,播放器層繼承于CALayer,有了AVPlayerLayer之添加到控制器視圖的layer中即可鸽斟。要使用AVPlayer首先了解一下幾個(gè)常用的類:

AVAsset:主要用于獲取多媒體信息拔创,是一個(gè)抽象類,不能直接使用富蓄。

AVURLAssetAVAsset的子類剩燥,可以根據(jù)一個(gè)URL路徑創(chuàng)建一個(gè)包含媒體信息的AVURLAsset對象。

AVPlayerItem:一個(gè)媒體資源管理對象立倍,管理者視頻的一些基本信息和狀態(tài)灭红,一個(gè)AVPlayerItem對應(yīng)著一個(gè)視頻資源侣滩。

下面簡單通過一個(gè)播放器來演示AVPlayer的使用,播放器的效果如下:

在這個(gè)自定義的播放器中實(shí)現(xiàn)了視頻播放变擒、暫停君珠、進(jìn)度展示和視頻列表功能,下面將對這些功能一一介紹娇斑。

首先說一下視頻的播放葛躏、暫停功能咪鲜,這也是最基本的功能讼积,AVPlayer對應(yīng)著兩個(gè)方法playpause來實(shí)現(xiàn)讥此。但是關(guān)鍵問題是如何判斷當(dāng)前視頻是否在播放悔醋,在前面的內(nèi)容中無論是音頻播放器還是視頻播放器都有對應(yīng)的狀態(tài)來判斷摩窃,但是AVPlayer卻沒有這樣的狀態(tài)屬性,通常情況下可以通過判斷播放器的播放速度來獲得播放狀態(tài)芬骄。如果rate為0說明是停止?fàn)顟B(tài)猾愿,1是則是正常播放狀態(tài)。

其次要展示播放進(jìn)度就沒有其他播放器那么簡單了账阻。在前面的播放器中通常是使用通知來獲得播放器的狀態(tài)蒂秘,媒體加載狀態(tài)等,但是無論是AVPlayer還是AVPlayerItemAVPlayer有一個(gè)屬性currentItemAVPlayerItem類型淘太,表示當(dāng)前播放的視頻對象)都無法獲得這些信息姻僧。當(dāng)然AVPlayerItem是有通知的,但是對于獲得播放狀態(tài)和加載狀態(tài)有用的通知只有一個(gè):播放完成通知AVPlayerItemDidPlayToEndTimeNotification蒲牧。在播放視頻時(shí)撇贺,特別是播放網(wǎng)絡(luò)視頻往往需要知道視頻加載情況、緩沖情況冰抢、播放情況松嘶,這些信息可以通過KVO監(jiān)控AVPlayerItemstatusloadedTimeRanges屬性來獲得挎扰。當(dāng)AVPlayerItemstatus屬性為AVPlayerStatusReadyToPlay是說明正在播放翠订,只有處于這個(gè)狀態(tài)時(shí)才能獲得視頻時(shí)長等信息;當(dāng)loadedTimeRanges的改變時(shí)(每緩沖一部分?jǐn)?shù)據(jù)就會(huì)更新此屬性)可以獲得本次緩沖加載的視頻范圍(包含起始時(shí)間遵倦、本次加載時(shí)長)尽超,這樣一來就可以實(shí)時(shí)獲得緩沖情況。然后就是依靠AVPlayer- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block方法獲得播放進(jìn)度骇吭,這個(gè)方法會(huì)在設(shè)定的時(shí)間間隔內(nèi)定時(shí)更新播放進(jìn)度橙弱,通過time參數(shù)通知客戶端。相信有了這些視頻信息播放進(jìn)度就不成問題了,事實(shí)上通過這些信息就算是平時(shí)看到的其他播放器的緩沖進(jìn)度顯示以及拖動(dòng)播放的功能也可以順利的實(shí)現(xiàn)棘脐。

最后就是視頻切換的功能斜筐,在前面介紹的所有播放器中每個(gè)播放器對象一次只能播放一個(gè)視頻,如果要切換視頻只能重新創(chuàng)建一個(gè)對象蛀缝,但是AVPlayer卻提供了- (void)replaceCurrentItemWithPlayerItem:(AVPlayerItem *)item方法用于在不同的視頻之間切換(事實(shí)上在AVFoundation內(nèi)部還有一個(gè)AVQueuePlayer專門處理播放列表切換顷链,有興趣的朋友可以自行研究,這里不再贅述)屈梁。

下面附上代碼:

//
//  ViewController.m
//  AVPlayer
//
//  Created by Kenshin Cui on 14/03/30.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()

@property (nonatomic,strong) AVPlayer *player;//播放器對象

@property (weak, nonatomic) IBOutlet UIView *container; //播放器容器
@property (weak, nonatomic) IBOutlet UIButton *playOrPause; //播放/暫停按鈕
@property (weak, nonatomic) IBOutlet UIProgressView *progress;//播放進(jìn)度

@end

@implementation ViewController

#pragma mark - 控制器視圖方法
- (void)viewDidLoad {
    [super viewDidLoad];
    [self setupUI];
    [self.player play];
}

-(void)dealloc{
    [self removeObserverFromPlayerItem:self.player.currentItem];
    [self removeNotification];
}

#pragma mark - 私有方法
-(void)setupUI{
    //創(chuàng)建播放器層
    AVPlayerLayer *playerLayer=[AVPlayerLayer playerLayerWithPlayer:self.player];
    playerLayer.frame=self.container.frame;
    //playerLayer.videoGravity=AVLayerVideoGravityResizeAspect;//視頻填充模式
    [self.container.layer addSublayer:playerLayer];
}

/**
 *  截取指定時(shí)間的視頻縮略圖
 *
 *  @param timeBySecond 時(shí)間點(diǎn)
 */

/**
 *  初始化播放器
 *
 *  @return 播放器對象
 */
-(AVPlayer *)player{
    if (!_player) {
        AVPlayerItem *playerItem=[self getPlayItem:0];
        _player=[AVPlayer playerWithPlayerItem:playerItem];
        [self addProgressObserver];
        [self addObserverToPlayerItem:playerItem];
    }
    return _player;
}

/**
 *  根據(jù)視頻索引取得AVPlayerItem對象
 *
 *  @param videoIndex 視頻順序索引
 *
 *  @return AVPlayerItem對象
 */
-(AVPlayerItem *)getPlayItem:(int)videoIndex{
    NSString *urlStr=[NSString stringWithFormat:@"http://192.168.1.161/%i.mp4",videoIndex];
    urlStr =[urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
    NSURL *url=[NSURL URLWithString:urlStr];
    AVPlayerItem *playerItem=[AVPlayerItem playerItemWithURL:url];
    return playerItem;
}
#pragma mark - 通知
/**
 *  添加播放器通知
 */
-(void)addNotification{
    //給AVPlayerItem添加播放完成通知
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.player.currentItem];
}

-(void)removeNotification{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

/**
 *  播放完成通知
 *
 *  @param notification 通知對象
 */
-(void)playbackFinished:(NSNotification *)notification{
    NSLog(@"視頻播放完成.");
}

#pragma mark - 監(jiān)控
/**
 *  給播放器添加進(jìn)度更新
 */
-(void)addProgressObserver{
    AVPlayerItem *playerItem=self.player.currentItem;
    UIProgressView *progress=self.progress;
    //這里設(shè)置每秒執(zhí)行一次
    [self.player addPeriodicTimeObserverForInterval:CMTimeMake(1.0, 1.0) queue:dispatch_get_main_queue() usingBlock:^(CMTime time) {
        float current=CMTimeGetSeconds(time);
        float total=CMTimeGetSeconds([playerItem duration]);
        NSLog(@"當(dāng)前已經(jīng)播放%.2fs.",current);
        if (current) {
            [progress setProgress:(current/total) animated:YES];
        }
    }];
}

/**
 *  給AVPlayerItem添加監(jiān)控
 *
 *  @param playerItem AVPlayerItem對象
 */
-(void)addObserverToPlayerItem:(AVPlayerItem *)playerItem{
    //監(jiān)控狀態(tài)屬性嗤练,注意AVPlayer也有一個(gè)status屬性,通過監(jiān)控它的status也可以獲得播放狀態(tài)
    [playerItem addObserver:self forKeyPath:@"status" options:NSKeyValueObservingOptionNew context:nil];
    //監(jiān)控網(wǎng)絡(luò)加載情況屬性
    [playerItem addObserver:self forKeyPath:@"loadedTimeRanges" options:NSKeyValueObservingOptionNew context:nil];
}
-(void)removeObserverFromPlayerItem:(AVPlayerItem *)playerItem{
    [playerItem removeObserver:self forKeyPath:@"status"];
    [playerItem removeObserver:self forKeyPath:@"loadedTimeRanges"];
}
/**
 *  通過KVO監(jiān)控播放器狀態(tài)
 *
 *  @param keyPath 監(jiān)控屬性
 *  @param object  監(jiān)視器
 *  @param change  狀態(tài)改變
 *  @param context 上下文
 */
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
    AVPlayerItem *playerItem=object;
    if ([keyPath isEqualToString:@"status"]) {
        AVPlayerStatus status= [[change objectForKey:@"new"] intValue];
        if(status==AVPlayerStatusReadyToPlay){
            NSLog(@"正在播放...在讶,視頻總長度:%.2f",CMTimeGetSeconds(playerItem.duration));
        }
    }else if([keyPath isEqualToString:@"loadedTimeRanges"]){
        NSArray *array=playerItem.loadedTimeRanges;
        CMTimeRange timeRange = [array.firstObject CMTimeRangeValue];//本次緩沖時(shí)間范圍
        float startSeconds = CMTimeGetSeconds(timeRange.start);
        float durationSeconds = CMTimeGetSeconds(timeRange.duration);
        NSTimeInterval totalBuffer = startSeconds + durationSeconds;//緩沖總長度
        NSLog(@"共緩沖:%.2f",totalBuffer);
//
    }
}

#pragma mark - UI事件
/**
 *  點(diǎn)擊播放/暫停按鈕
 *
 *  @param sender 播放/暫停按鈕
 */
- (IBAction)playClick:(UIButton *)sender {
//    AVPlayerItemDidPlayToEndTimeNotification
    //AVPlayerItem *playerItem= self.player.currentItem;
    if(self.player.rate==0){ //說明時(shí)暫停
        [sender setImage:[UIImage imageNamed:@"player_pause"] forState:UIControlStateNormal];
        [self.player play];
    }else if(self.player.rate==1){//正在播放
        [self.player pause];
        [sender setImage:[UIImage imageNamed:@"player_play"] forState:UIControlStateNormal];
    }
}


/**
 *  切換選集煞抬,這里使用按鈕的tag代表視頻名稱
 *
 *  @param sender 點(diǎn)擊按鈕對象
 */
- (IBAction)navigationButtonClick:(UIButton *)sender {
    [self removeNotification];
    [self removeObserverFromPlayerItem:self.player.currentItem];
    AVPlayerItem *playerItem=[self getPlayItem:sender.tag];
    [self addObserverToPlayerItem:playerItem];
    //切換視頻
    [self.player replaceCurrentItemWithPlayerItem:playerItem];
    [self addNotification];
}

@end

運(yùn)行效果:


到目前為止無論是MPMoviePlayerController還是AVPlayer來播放視頻都相當(dāng)強(qiáng)大,但是它也存在著一些不可回避的問題构哺,那就是支持的視頻編碼格式很有限:H.264革答、MPEG-4,擴(kuò)展名(壓縮格式):.mp4曙强、.mov残拐、.m4v、.m2v碟嘴、.3gp溪食、.3g2等。但是無論是MPMoviePlayerController還是AVPlayer它們都支持絕大多數(shù)音頻編碼娜扇,所以大家如果純粹是為了播放音樂的話也可以考慮使用這兩個(gè)播放器错沃。那么如何支持更多視頻編碼格式呢?目前來說主要還是依靠第三方框架袱衷,在iOS上常用的視頻編碼捎废、解碼框架有:VLCffmpeg致燥, 具體使用方式今天就不再做詳細(xì)介紹。

攝像頭

UIImagePickerController拍照和視頻錄制

下面看一下在iOS如何拍照和錄制視頻排截。在iOS中要拍照和錄制視頻最簡單的方法就是使用UIImagePickerController嫌蚤。UIImagePickerController繼承于UINavigationController,前面的文章中主要使用它來選取照片断傲,其實(shí)UIImagePickerController的功能不僅如此脱吱,它還可以用來拍照和錄制視頻。首先看一下這個(gè)類常用的屬性和方法:

屬性 說明
@property(nonatomic) UIImagePickerControllerSourceType sourceType 選取源類型认罩,sourceType是枚舉類型:UIImagePickerControllerSourceTypePhotoLibrary:照片庫箱蝠,默認(rèn)值 UIImagePickerControllerSourceTypeCamera:攝像頭UIImagePickerControllerSourceTypeSavedPhotosAlbum:相簿
@property(nonatomic,copy) NSArray *mediaTypes 媒體類型,默認(rèn)情況下此數(shù)組包含kUTTypeImage,所以拍照時(shí)可以不用設(shè)置;但是當(dāng)要錄像的時(shí)候必須設(shè)置宦搬,可以設(shè)置為kUTTypeVideo(視頻牙瓢,但不帶聲音)或者kUTTypeMovie(視頻并帶有聲音)
@property(nonatomic) NSTimeInterval videoMaximumDuration 視頻最大錄制時(shí)長,默認(rèn)為10 s
@property(nonatomic) UIImagePickerControllerQualityType videoQuality 視頻質(zhì)量间校,枚舉類型:UIImagePickerControllerQualityTypeHigh:高清質(zhì)量UIImagePickerControllerQualityTypeMedium:中等質(zhì)量矾克,適合WiFi傳輸U(kuò)IImagePickerControllerQualityTypeLow:低質(zhì)量,適合蜂窩網(wǎng)傳輸U(kuò)IImagePickerControllerQualityType640x480:640480 UIImagePickerControllerQualityTypeIFrame1280x720:1280720 UIImagePickerControllerQualityTypeIFrame960x540:960*540
@property(nonatomic) BOOL showsCameraControls 是否顯示攝像頭控制面板憔足,默認(rèn)為YES
@property(nonatomic,retain) UIView *cameraOverlayView 攝像頭上覆蓋的視圖胁附,可用通過這個(gè)視頻來自定義拍照或錄像界面
@property(nonatomic) CGAffineTransform cameraViewTransform 攝像頭形變
@property(nonatomic) UIImagePickerControllerCameraCaptureMode cameraCaptureMode 攝像頭捕獲模式,捕獲模式是枚舉類型:UIImagePickerControllerCameraCaptureModePhoto:拍照模式UIImagePickerControllerCameraCaptureModeVideo:視頻錄制模式
@property(nonatomic) UIImagePickerControllerCameraDevice cameraDevice 攝像頭設(shè)備滓彰,cameraDevice是枚舉類型:UIImagePickerControllerCameraDeviceRear:前置攝像頭UIImagePickerControllerCameraDeviceFront:后置攝像頭
@property(nonatomic) UIImagePickerControllerCameraFlashMode cameraFlashMode 閃光燈模式控妻,枚舉類型:UIImagePickerControllerCameraFlashModeOff:關(guān)閉閃光燈UIImagePickerControllerCameraFlashModeAuto:閃光燈自動(dòng)UIImagePickerControllerCameraFlashModeOn:打開閃光燈
類方法 說明
+ (BOOL)isSourceTypeAvailable:(UIImagePickerControllerSourceType)sourceType 指定的源類型是否可用,sourceType是枚舉類型:UIImagePickerControllerSourceTypePhotoLibrary:照片庫UIImagePickerControllerSourceTypeCamera:攝像頭UIImagePickerControllerSourceTypeSavedPhotosAlbum:相簿
+ (NSArray *)availableMediaTypesForSourceType:(UIImagePickerControllerSourceType)sourceType 指定的源設(shè)備上可用的媒體類型揭绑,一般就是圖片和視頻
+ (BOOL)isCameraDeviceAvailable:(UIImagePickerControllerCameraDevice)cameraDevice 指定的攝像頭是否可用弓候,cameraDevice是枚舉類型:UIImagePickerControllerCameraDeviceRear:前置攝像頭UIImagePickerControllerCameraDeviceFront:后置攝像頭
+ (BOOL)isFlashAvailableForCameraDevice:(UIImagePickerControllerCameraDevice)cameraDevice 指定攝像頭的閃光燈是否可用
+ (NSArray *)availableCaptureModesForCameraDevice:(UIImagePickerControllerCameraDevice)cameraDevice 獲得指定攝像頭上的可用捕獲模式,捕獲模式是枚舉類型:UIImagePickerControllerCameraCaptureModePhoto:拍照模式UIImagePickerControllerCameraCaptureModeVideo:視頻錄制模式
對象方法 說明
- (void)takePicture 編程方式拍照
- (BOOL)startVideoCapture 編程方式錄制視頻
- (void)stopVideoCapture 編程方式停止錄制視頻
代理方法 說明
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info 媒體選取完成
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker 取消選取
擴(kuò)展方法(主要用于保存照片洗做、視頻到相簿) 說明
UIImageWriteToSavedPhotosAlbum(UIImage *image, id completionTarget, SEL completionSelector, void *contextInfo) 保存照片到相簿
UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(NSString *videoPath) 能否將視頻保存到相簿
void UISaveVideoAtPathToSavedPhotosAlbum(NSString *videoPath, id completionTarget, SEL completionSelector, void *contextInfo) 保存視頻到相簿

要用UIImagePickerController來拍照或者錄制視頻通彻眩可以分為如下步驟:

1、創(chuàng)建UIImagePickerController對象诚纸。
2撰筷、指定選取源,平時(shí)選擇照片時(shí)使用的選取源是照片庫或者相簿畦徘,此刻需要指定為攝像頭類型毕籽。
3、指定攝像頭井辆,前置攝像頭或者后置攝像頭关筒。
4、設(shè)置媒體類型mediaType杯缺,注意如果是錄像必須設(shè)置蒸播,如果是拍照此步驟可以省略,因?yàn)?code>mediaType默認(rèn)包含kUTTypeImage(注意媒體類型定義在MobileCoreServices.framework中)
5萍肆、指定捕獲模式袍榆,拍照或者錄制視頻。(視頻錄制時(shí)必須先設(shè)置媒體類型再設(shè)置捕獲模式

6塘揣、展示UIImagePickerController(通常以模態(tài)窗口形式打開)包雀。
7、拍照和錄制視頻結(jié)束后在代理方法中展示/保存照片或視頻亲铡。

當(dāng)然這個(gè)過程中有很多細(xì)節(jié)可以設(shè)置才写,例如是否顯示拍照控制面板葡兑,拍照后是否允許編輯等等,通過上面的屬性/方法列表相信并不難理解赞草。下面就以一個(gè)示例展示如何使用UIImagePickerController來拍照和錄制視頻讹堤,下面的程序中只要將_isVideo設(shè)置為YES就是視頻錄制模式,錄制完后在主視圖控制器中自動(dòng)播放房资;如果將_isVideo設(shè)置為NO則為拍照模式蜕劝,拍照完成之后在主視圖控制器中顯示拍攝的照片:

//
//  ViewController.m
//  UIImagePickerController
//
//  Created by Kenshin Cui on 14/04/05.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <MobileCoreServices/MobileCoreServices.h>
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()<UIImagePickerControllerDelegate,UINavigationControllerDelegate>
@property (assign,nonatomic) int isVideo;//是否錄制視頻,如果為1表示錄制視頻轰异,0代表拍照
@property (strong,nonatomic) UIImagePickerController *imagePicker;
@property (weak, nonatomic) IBOutlet UIImageView *photo;//照片展示視圖
@property (strong ,nonatomic) AVPlayer *player;//播放器岖沛,用于錄制完視頻后播放視頻

@end

@implementation ViewController

#pragma mark - 控制器視圖事件
- (void)viewDidLoad {
    [super viewDidLoad];
    //通過這里設(shè)置當(dāng)前程序是拍照還是錄制視頻
    _isVideo=YES;
}

#pragma mark - UI事件
//點(diǎn)擊拍照按鈕
- (IBAction)takeClick:(UIButton *)sender {
    [self presentViewController:self.imagePicker animated:YES completion:nil];
}

#pragma mark - UIImagePickerController代理方法
//完成
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
    NSString *mediaType=[info objectForKey:UIImagePickerControllerMediaType];
    if ([mediaType isEqualToString:(NSString *)kUTTypeImage]) {//如果是拍照
        UIImage *image;
        //如果允許編輯則獲得編輯后的照片,否則獲取原始照片
        if (self.imagePicker.allowsEditing) {
            image=[info objectForKey:UIImagePickerControllerEditedImage];//獲取編輯后的照片
        }else{
            image=[info objectForKey:UIImagePickerControllerOriginalImage];//獲取原始照片
        }
        [self.photo setImage:image];//顯示照片
        UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);//保存到相簿
    }else if([mediaType isEqualToString:(NSString *)kUTTypeMovie]){//如果是錄制視頻
        NSLog(@"video...");
        NSURL *url=[info objectForKey:UIImagePickerControllerMediaURL];//視頻路徑
        NSString *urlStr=[url path];
        if (UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(urlStr)) {
            //保存視頻到相簿搭独,注意也可以使用ALAssetsLibrary來保存
            UISaveVideoAtPathToSavedPhotosAlbum(urlStr, self, @selector(video:didFinishSavingWithError:contextInfo:), nil);//保存視頻到相簿
        }
        
    }

    [self dismissViewControllerAnimated:YES completion:nil];
}
-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
    NSLog(@"取消");
}

#pragma mark - 私有方法
-(UIImagePickerController *)imagePicker{
    if (!_imagePicker) {
        _imagePicker=[[UIImagePickerController alloc]init];
        _imagePicker.sourceType=UIImagePickerControllerSourceTypeCamera;//設(shè)置image picker的來源婴削,這里設(shè)置為攝像頭
        _imagePicker.cameraDevice=UIImagePickerControllerCameraDeviceRear;//設(shè)置使用哪個(gè)攝像頭,這里設(shè)置為后置攝像頭
        if (self.isVideo) {
            _imagePicker.mediaTypes=@[(NSString *)kUTTypeMovie];
            _imagePicker.videoQuality=UIImagePickerControllerQualityTypeIFrame1280x720;
            _imagePicker.cameraCaptureMode=UIImagePickerControllerCameraCaptureModeVideo;//設(shè)置攝像頭模式(拍照牙肝,錄制視頻)
            
        }else{
            _imagePicker.cameraCaptureMode=UIImagePickerControllerCameraCaptureModePhoto;
        }
        _imagePicker.allowsEditing=YES;//允許編輯
        _imagePicker.delegate=self;//設(shè)置代理唉俗,檢測操作
    }
    return _imagePicker;
}

//視頻保存后的回調(diào)
- (void)video:(NSString *)videoPath didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo{
    if (error) {
        NSLog(@"保存視頻過程中發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@",error.localizedDescription);
    }else{
        NSLog(@"視頻保存成功.");
        //錄制完之后自動(dòng)播放
        NSURL *url=[NSURL fileURLWithPath:videoPath];
        _player=[AVPlayer playerWithURL:url];
        AVPlayerLayer *playerLayer=[AVPlayerLayer playerLayerWithPlayer:_player];
        playerLayer.frame=self.photo.frame;
        [self.photo.layer addSublayer:playerLayer];
        [_player play];
        
    }
}
@end

運(yùn)行效果(視頻錄制):


AVFoundation拍照和錄制視頻

不得不說UIImagePickerController確實(shí)強(qiáng)大配椭,但是與MPMoviePlayerController類似虫溜,由于它的高度封裝性,要進(jìn)行某些自定義工作就比較復(fù)雜了股缸。例如要做出一款類似于美顏相機(jī)的拍照界面就比較難以實(shí)現(xiàn)了衡楞,此時(shí)就可以考慮使用AVFoundation來實(shí)現(xiàn)。AVFoundation中提供了很多現(xiàn)成的播放器和錄音機(jī)敦姻,但是事實(shí)上它還有更加底層的內(nèi)容可以供開發(fā)者使用瘾境。因?yàn)?code>AVFoundation中抽了很多和底層輸入、輸出設(shè)備打交道的類镰惦,依靠這些類開發(fā)人員面對的不再是封裝好的音頻播放器AVAudioPlayer迷守、錄音機(jī)(AVAudioRecorder)、視頻(包括音頻)播放器AVPlayer旺入,而是輸入設(shè)備(例如麥克風(fēng)兑凿、攝像頭)、輸出設(shè)備(圖片茵瘾、視頻)等急膀。首先了解一下使用AVFoundation做拍照和視頻錄制開發(fā)用到的相關(guān)類:

AVCaptureSession:媒體(音、視頻)捕獲會(huì)話龄捡,負(fù)責(zé)把捕獲的音視頻數(shù)據(jù)輸出到輸出設(shè)備中。一個(gè)AVCaptureSession可以有多個(gè)輸入輸出:

AVCaptureDevice:輸入設(shè)備慷暂,包括麥克風(fēng)聘殖、攝像頭晨雳,通過該對象可以設(shè)置物理設(shè)備的一些屬性(例如相機(jī)聚焦、白平衡等)奸腺。

AVCaptureDeviceInput:設(shè)備輸入數(shù)據(jù)管理對象餐禁,可以根據(jù)AVCaptureDevice創(chuàng)建對應(yīng)的AVCaptureDeviceInput對象,該對象將會(huì)被添加到AVCaptureSession中管理突照。

AVCaptureOutput:輸出數(shù)據(jù)管理對象帮非,用于接收各類輸出數(shù)據(jù),通常使用對應(yīng)的子類AVCaptureAudioDataOutput讹蘑、AVCaptureStillImageOutput末盔、AVCaptureVideoDataOutputAVCaptureFileOutput座慰,該對象將會(huì)被添加到AVCaptureSession中管理陨舱。注意:前面幾個(gè)對象的輸出數(shù)據(jù)都是NSData類型,而AVCaptureFileOutput代表數(shù)據(jù)以文件形式輸出版仔,類似的游盲,AVCcaptureFileOutput也不會(huì)直接創(chuàng)建使用,通常會(huì)使用其子類:AVCaptureAudioFileOutput蛮粮、AVCaptureMovieFileOutput益缎。當(dāng)把一個(gè)輸入或者輸出添加到AVCaptureSession之后AVCaptureSession就會(huì)在所有相符的輸入、輸出設(shè)備之間建立連接(AVCaptionConnection):

AVCaptureVideoPreviewLayer:相機(jī)拍攝預(yù)覽圖層然想,是CALayer的子類莺奔,使用該對象可以實(shí)時(shí)查看拍照或視頻錄制效果,創(chuàng)建該對象需要指定對應(yīng)的AVCaptureSession對象又沾。

使用AVFoundation拍照和錄制視頻的一般步驟如下:

1弊仪、創(chuàng)建AVCaptureSession對象。
2杖刷、使用AVCaptureDevice的靜態(tài)方法獲得需要使用的設(shè)備励饵,例如拍照和錄像就需要獲得攝像頭設(shè)備,錄音就要獲得麥克風(fēng)設(shè)備滑燃。
3役听、利用輸入設(shè)備AVCaptureDevice初始化AVCaptureDeviceInput對象。
4表窘、初始化輸出數(shù)據(jù)管理對象典予,如果要拍照就初始化AVCaptureStillImageOutput對象;如果拍攝視頻就初始化AVCaptureMovieFileOutput對象乐严。
5瘤袖、將數(shù)據(jù)輸入對象AVCaptureDeviceInput、數(shù)據(jù)輸出對象AVCaptureOutput添加到媒體會(huì)話管理對象AVCaptureSession中昂验。
6捂敌、創(chuàng)建視頻預(yù)覽圖層AVCaptureVideoPreviewLayer并指定媒體會(huì)話艾扮,添加圖層到顯示容器中,調(diào)用AVCaptureSessionstartRuning方法開始捕獲占婉。
7泡嘴、將捕獲的音頻或視頻數(shù)據(jù)輸出到指定文件。

拍照

下面看一下如何使用AVFoundation實(shí)現(xiàn)一個(gè)拍照程序逆济,在這個(gè)程序中將實(shí)現(xiàn)攝像頭預(yù)覽酌予、切換前后攝像頭、閃光燈設(shè)置奖慌、對焦抛虫、拍照保存等功能。應(yīng)用大致效果如下:

在程序中定義會(huì)話升薯、輸入莱褒、輸出等相關(guān)對象。

@interface ViewController ()
@property (strong,nonatomic) AVCaptureSession *captureSession;//負(fù)責(zé)輸入和輸出設(shè)備之間的數(shù)據(jù)傳遞
@property (strong,nonatomic) AVCaptureDeviceInput *captureDeviceInput;//負(fù)責(zé)從AVCaptureDevice獲得輸入數(shù)據(jù)
@property (strong,nonatomic) AVCaptureStillImageOutput *captureStillImageOutput;//照片輸出流
@property (strong,nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;//相機(jī)拍攝預(yù)覽圖層
@property (weak, nonatomic) IBOutlet UIView *viewContainer;
@property (weak, nonatomic) IBOutlet UIButton *takeButton;//拍照按鈕
@property (weak, nonatomic) IBOutlet UIButton *flashAutoButton;//自動(dòng)閃光燈按鈕
@property (weak, nonatomic) IBOutlet UIButton *flashOnButton;//打開閃光燈按鈕
@property (weak, nonatomic) IBOutlet UIButton *flashOffButton;//關(guān)閉閃光燈按鈕
@property (weak, nonatomic) IBOutlet UIImageView *focusCursor; //聚焦光標(biāo)
@end

在控制器視圖將要展示時(shí)創(chuàng)建并初始化會(huì)話涎劈、攝像頭設(shè)備广凸、輸入、輸出蛛枚、預(yù)覽圖層谅海,并且添加預(yù)覽圖層到視圖中,除此之外還做了一些初始化工作蹦浦,例如添加手勢(點(diǎn)擊屏幕進(jìn)行聚焦)扭吁、初始化界面等。

-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    //初始化會(huì)話
    _captureSession=[[AVCaptureSession alloc]init];
    if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {//設(shè)置分辨率
        _captureSession.sessionPreset=AVCaptureSessionPreset1280x720;
    }
    //獲得輸入設(shè)備
    AVCaptureDevice *captureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置攝像頭
    if (!captureDevice) {
        NSLog(@"取得后置攝像頭時(shí)出現(xiàn)問題.");
        return;
    }
    
    NSError *error=nil;
    //根據(jù)輸入設(shè)備初始化設(shè)備輸入對象盲镶,用于獲得輸入數(shù)據(jù)
    _captureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error];
    if (error) {
        NSLog(@"取得設(shè)備輸入對象時(shí)出錯(cuò)侥袜,錯(cuò)誤原因:%@",error.localizedDescription);
        return;
    }
    //初始化設(shè)備輸出對象,用于獲得輸出數(shù)據(jù)
    _captureStillImageOutput=[[AVCaptureStillImageOutput alloc]init];
    NSDictionary *outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};
    [_captureStillImageOutput setOutputSettings:outputSettings];//輸出設(shè)置
    
    //將設(shè)備輸入添加到會(huì)話中
    if ([_captureSession canAddInput:_captureDeviceInput]) {
        [_captureSession addInput:_captureDeviceInput];
    }
    
    //將設(shè)備輸出添加到會(huì)話中
    if ([_captureSession canAddOutput:_captureStillImageOutput]) {
        [_captureSession addOutput:_captureStillImageOutput];
    }
    
    //創(chuàng)建視頻預(yù)覽層溉贿,用于實(shí)時(shí)展示攝像頭狀態(tài)
    _captureVideoPreviewLayer=[[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];
    
    CALayer *layer=self.viewContainer.layer;
    layer.masksToBounds=YES;
    
    _captureVideoPreviewLayer.frame=layer.bounds;
    _captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式
    //將視頻預(yù)覽層添加到界面中
    //[layer addSublayer:_captureVideoPreviewLayer];
    [layer insertSublayer:_captureVideoPreviewLayer below:self.focusCursor.layer];
    
    [self addNotificationToCaptureDevice:captureDevice];
    [self addGenstureRecognizer];
    [self setFlashModeButtonStatus];
}

在控制器視圖展示和視圖離開界面時(shí)啟動(dòng)枫吧、停止會(huì)話。

-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    [self.captureSession startRunning];
}

-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    [self.captureSession stopRunning];
}

定義閃光燈開閉及自動(dòng)模式功能宇色,注意無論是設(shè)置閃光燈九杂、白平衡還是其他輸入設(shè)備屬性,在設(shè)置之前必須先鎖定配置宣蠕,修改完后解鎖例隆。

/**
 *  改變設(shè)備屬性的統(tǒng)一操作方法
 *
 *  @param propertyChange 屬性改變操作
 */
-(void)changeDeviceProperty:(PropertyChangeBlock)propertyChange{
    AVCaptureDevice *captureDevice= [self.captureDeviceInput device];
    NSError *error;
    //注意改變設(shè)備屬性前一定要首先調(diào)用lockForConfiguration:調(diào)用完之后使用unlockForConfiguration方法解鎖
    if ([captureDevice lockForConfiguration:&error]) {
        propertyChange(captureDevice);
        [captureDevice unlockForConfiguration];
    }else{
        NSLog(@"設(shè)置設(shè)備屬性過程發(fā)生錯(cuò)誤,錯(cuò)誤信息:%@",error.localizedDescription);
    }
}

/**
 *  設(shè)置閃光燈模式
 *
 *  @param flashMode 閃光燈模式
 */
-(void)setFlashMode:(AVCaptureFlashMode )flashMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFlashModeSupported:flashMode]) {
            [captureDevice setFlashMode:flashMode];
        }
    }];
}

定義切換攝像頭功能抢蚀,切換攝像頭的過程就是將原有輸入移除窜醉,在會(huì)話中添加新的輸入脑蠕,但是注意動(dòng)態(tài)修改會(huì)話需要首先開啟配置丹禀,配置成功后提交配置。

#pragma mark 切換前后攝像頭
- (IBAction)toggleButtonClick:(UIButton *)sender {
    AVCaptureDevice *currentDevice=[self.captureDeviceInput device];
    AVCaptureDevicePosition currentPosition=[currentDevice position];
    [self removeNotificationFromCaptureDevice:currentDevice];
    AVCaptureDevice *toChangeDevice;
    AVCaptureDevicePosition toChangePosition=AVCaptureDevicePositionFront;
    if (currentPosition==AVCaptureDevicePositionUnspecified||currentPosition==AVCaptureDevicePositionFront) {
        toChangePosition=AVCaptureDevicePositionBack;
    }
    toChangeDevice=[self getCameraDeviceWithPosition:toChangePosition];
    [self addNotificationToCaptureDevice:toChangeDevice];
    //獲得要調(diào)整的設(shè)備輸入對象
    AVCaptureDeviceInput *toChangeDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:toChangeDevice error:nil];
    
    //改變會(huì)話的配置前一定要先開啟配置羡微,配置完成后提交配置改變
    [self.captureSession beginConfiguration];
    //移除原有輸入對象
    [self.captureSession removeInput:self.captureDeviceInput];
    //添加新的輸入對象
    if ([self.captureSession canAddInput:toChangeDeviceInput]) {
        [self.captureSession addInput:toChangeDeviceInput];
        self.captureDeviceInput=toChangeDeviceInput;
    }
    //提交會(huì)話配置
    [self.captureSession commitConfiguration];
    
    [self setFlashModeButtonStatus];
}

添加點(diǎn)擊手勢操作,點(diǎn)按預(yù)覽視圖時(shí)進(jìn)行聚焦惶我、白平衡設(shè)置。

/**
 *  設(shè)置聚焦點(diǎn)
 *
 *  @param point 聚焦點(diǎn)
 */
-(void)focusWithMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode atPoint:(CGPoint)point{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFocusModeSupported:focusMode]) {
            [captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
        }
        if ([captureDevice isFocusPointOfInterestSupported]) {
            [captureDevice setFocusPointOfInterest:point];
        }
        if ([captureDevice isExposureModeSupported:exposureMode]) {
            [captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
        }
        if ([captureDevice isExposurePointOfInterestSupported]) {
            [captureDevice setExposurePointOfInterest:point];
        }
    }];
}

/**
 *  添加點(diǎn)按手勢博投,點(diǎn)按時(shí)聚焦
 */
-(void)addGenstureRecognizer{
    UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapScreen:)];
    [self.viewContainer addGestureRecognizer:tapGesture];
}
-(void)tapScreen:(UITapGestureRecognizer *)tapGesture{
    CGPoint point= [tapGesture locationInView:self.viewContainer];
    //將UI坐標(biāo)轉(zhuǎn)化為攝像頭坐標(biāo)
    CGPoint cameraPoint= [self.captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];
    [self setFocusCursorWithPoint:point];
    [self focusWithMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose atPoint:cameraPoint];
}

定義拍照功能绸贡,拍照的過程就是獲取連接,從連接中獲得捕獲的輸出數(shù)據(jù)并做保存操作毅哗。

#pragma mark 拍照
- (IBAction)takeButtonClick:(UIButton *)sender {
    //根據(jù)設(shè)備輸出獲得連接
    AVCaptureConnection *captureConnection=[self.captureStillImageOutput connectionWithMediaType:AVMediaTypeVideo];
    //根據(jù)連接取得設(shè)備輸出的數(shù)據(jù)
    [self.captureStillImageOutput captureStillImageAsynchronouslyFromConnection:captureConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
        if (imageDataSampleBuffer) {
            NSData *imageData=[AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
            UIImage *image=[UIImage imageWithData:imageData];
            UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
//            ALAssetsLibrary *assetsLibrary=[[ALAssetsLibrary alloc]init];
//            [assetsLibrary writeImageToSavedPhotosAlbum:[image CGImage] orientation:(ALAssetOrientation)[image imageOrientation] completionBlock:nil];
        }
        
    }];
}

最后附上完整代碼:

//
//  ViewController.m
//  AVFoundationCamera
//
//  Created by Kenshin Cui on 14/04/05.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <AssetsLibrary/AssetsLibrary.h>
typedef void(^PropertyChangeBlock)(AVCaptureDevice *captureDevice);

@interface ViewController ()

@property (strong,nonatomic) AVCaptureSession *captureSession;//負(fù)責(zé)輸入和輸出設(shè)備之間的數(shù)據(jù)傳遞
@property (strong,nonatomic) AVCaptureDeviceInput *captureDeviceInput;//負(fù)責(zé)從AVCaptureDevice獲得輸入數(shù)據(jù)
@property (strong,nonatomic) AVCaptureStillImageOutput *captureStillImageOutput;//照片輸出流
@property (strong,nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;//相機(jī)拍攝預(yù)覽圖層
@property (weak, nonatomic) IBOutlet UIView *viewContainer;
@property (weak, nonatomic) IBOutlet UIButton *takeButton;//拍照按鈕
@property (weak, nonatomic) IBOutlet UIButton *flashAutoButton;//自動(dòng)閃光燈按鈕
@property (weak, nonatomic) IBOutlet UIButton *flashOnButton;//打開閃光燈按鈕
@property (weak, nonatomic) IBOutlet UIButton *flashOffButton;//關(guān)閉閃光燈按鈕
@property (weak, nonatomic) IBOutlet UIImageView *focusCursor; //聚焦光標(biāo)



@end

@implementation ViewController

#pragma mark - 控制器視圖方法
- (void)viewDidLoad {
    [super viewDidLoad];
    
}

-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    //初始化會(huì)話
    _captureSession=[[AVCaptureSession alloc]init];
    if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {//設(shè)置分辨率
        _captureSession.sessionPreset=AVCaptureSessionPreset1280x720;
    }
    //獲得輸入設(shè)備
    AVCaptureDevice *captureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置攝像頭
    if (!captureDevice) {
        NSLog(@"取得后置攝像頭時(shí)出現(xiàn)問題.");
        return;
    }
    
    NSError *error=nil;
    //根據(jù)輸入設(shè)備初始化設(shè)備輸入對象听怕,用于獲得輸入數(shù)據(jù)
    _captureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error];
    if (error) {
        NSLog(@"取得設(shè)備輸入對象時(shí)出錯(cuò),錯(cuò)誤原因:%@",error.localizedDescription);
        return;
    }
    //初始化設(shè)備輸出對象虑绵,用于獲得輸出數(shù)據(jù)
    _captureStillImageOutput=[[AVCaptureStillImageOutput alloc]init];
    NSDictionary *outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};
    [_captureStillImageOutput setOutputSettings:outputSettings];//輸出設(shè)置
    
    //將設(shè)備輸入添加到會(huì)話中
    if ([_captureSession canAddInput:_captureDeviceInput]) {
        [_captureSession addInput:_captureDeviceInput];
    }
    
    //將設(shè)備輸出添加到會(huì)話中
    if ([_captureSession canAddOutput:_captureStillImageOutput]) {
        [_captureSession addOutput:_captureStillImageOutput];
    }
    
    //創(chuàng)建視頻預(yù)覽層尿瞭,用于實(shí)時(shí)展示攝像頭狀態(tài)
    _captureVideoPreviewLayer=[[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];
    
    CALayer *layer=self.viewContainer.layer;
    layer.masksToBounds=YES;
    
    _captureVideoPreviewLayer.frame=layer.bounds;
    _captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式
    //將視頻預(yù)覽層添加到界面中
    //[layer addSublayer:_captureVideoPreviewLayer];
    [layer insertSublayer:_captureVideoPreviewLayer below:self.focusCursor.layer];
    
    [self addNotificationToCaptureDevice:captureDevice];
    [self addGenstureRecognizer];
    [self setFlashModeButtonStatus];
}

-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    [self.captureSession startRunning];
}

-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    [self.captureSession stopRunning];
}

-(void)dealloc{
    [self removeNotification];
}
#pragma mark - UI方法
#pragma mark 拍照
- (IBAction)takeButtonClick:(UIButton *)sender {
    //根據(jù)設(shè)備輸出獲得連接
    AVCaptureConnection *captureConnection=[self.captureStillImageOutput connectionWithMediaType:AVMediaTypeVideo];
    //根據(jù)連接取得設(shè)備輸出的數(shù)據(jù)
    [self.captureStillImageOutput captureStillImageAsynchronouslyFromConnection:captureConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
        if (imageDataSampleBuffer) {
            NSData *imageData=[AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
            UIImage *image=[UIImage imageWithData:imageData];
            UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
//            ALAssetsLibrary *assetsLibrary=[[ALAssetsLibrary alloc]init];
//            [assetsLibrary writeImageToSavedPhotosAlbum:[image CGImage] orientation:(ALAssetOrientation)[image imageOrientation] completionBlock:nil];
        }
        
    }];
}
#pragma mark 切換前后攝像頭
- (IBAction)toggleButtonClick:(UIButton *)sender {
    AVCaptureDevice *currentDevice=[self.captureDeviceInput device];
    AVCaptureDevicePosition currentPosition=[currentDevice position];
    [self removeNotificationFromCaptureDevice:currentDevice];
    AVCaptureDevice *toChangeDevice;
    AVCaptureDevicePosition toChangePosition=AVCaptureDevicePositionFront;
    if (currentPosition==AVCaptureDevicePositionUnspecified||currentPosition==AVCaptureDevicePositionFront) {
        toChangePosition=AVCaptureDevicePositionBack;
    }
    toChangeDevice=[self getCameraDeviceWithPosition:toChangePosition];
    [self addNotificationToCaptureDevice:toChangeDevice];
    //獲得要調(diào)整的設(shè)備輸入對象
    AVCaptureDeviceInput *toChangeDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:toChangeDevice error:nil];
    
    //改變會(huì)話的配置前一定要先開啟配置,配置完成后提交配置改變
    [self.captureSession beginConfiguration];
    //移除原有輸入對象
    [self.captureSession removeInput:self.captureDeviceInput];
    //添加新的輸入對象
    if ([self.captureSession canAddInput:toChangeDeviceInput]) {
        [self.captureSession addInput:toChangeDeviceInput];
        self.captureDeviceInput=toChangeDeviceInput;
    }
    //提交會(huì)話配置
    [self.captureSession commitConfiguration];
    
    [self setFlashModeButtonStatus];
}

#pragma mark 自動(dòng)閃光燈開啟
- (IBAction)flashAutoClick:(UIButton *)sender {
    [self setFlashMode:AVCaptureFlashModeAuto];
    [self setFlashModeButtonStatus];
}
#pragma mark 打開閃光燈
- (IBAction)flashOnClick:(UIButton *)sender {
    [self setFlashMode:AVCaptureFlashModeOn];
    [self setFlashModeButtonStatus];
}
#pragma mark 關(guān)閉閃光燈
- (IBAction)flashOffClick:(UIButton *)sender {
    [self setFlashMode:AVCaptureFlashModeOff];
    [self setFlashModeButtonStatus];
}

#pragma mark - 通知
/**
 *  給輸入設(shè)備添加通知
 */
-(void)addNotificationToCaptureDevice:(AVCaptureDevice *)captureDevice{
    //注意添加區(qū)域改變捕獲通知必須首先設(shè)置設(shè)備允許捕獲
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        captureDevice.subjectAreaChangeMonitoringEnabled=YES;
    }];
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    //捕獲區(qū)域發(fā)生改變
    [notificationCenter addObserver:self selector:@selector(areaChange:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
-(void)removeNotificationFromCaptureDevice:(AVCaptureDevice *)captureDevice{
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    [notificationCenter removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
/**
 *  移除所有通知
 */
-(void)removeNotification{
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    [notificationCenter removeObserver:self];
}

-(void)addNotificationToCaptureSession:(AVCaptureSession *)captureSession{
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    //會(huì)話出錯(cuò)
    [notificationCenter addObserver:self selector:@selector(sessionRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:captureSession];
}

/**
 *  設(shè)備連接成功
 *
 *  @param notification 通知對象
 */
-(void)deviceConnected:(NSNotification *)notification{
    NSLog(@"設(shè)備已連接...");
}
/**
 *  設(shè)備連接斷開
 *
 *  @param notification 通知對象
 */
-(void)deviceDisconnected:(NSNotification *)notification{
    NSLog(@"設(shè)備已斷開.");
}
/**
 *  捕獲區(qū)域改變
 *
 *  @param notification 通知對象
 */
-(void)areaChange:(NSNotification *)notification{
    NSLog(@"捕獲區(qū)域改變...");
}

/**
 *  會(huì)話出錯(cuò)
 *
 *  @param notification 通知對象
 */
-(void)sessionRuntimeError:(NSNotification *)notification{
    NSLog(@"會(huì)話發(fā)生錯(cuò)誤.");
}

#pragma mark - 私有方法

/**
 *  取得指定位置的攝像頭
 *
 *  @param position 攝像頭位置
 *
 *  @return 攝像頭設(shè)備
 */
-(AVCaptureDevice *)getCameraDeviceWithPosition:(AVCaptureDevicePosition )position{
    NSArray *cameras= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *camera in cameras) {
        if ([camera position]==position) {
            return camera;
        }
    }
    return nil;
}

/**
 *  改變設(shè)備屬性的統(tǒng)一操作方法
 *
 *  @param propertyChange 屬性改變操作
 */
-(void)changeDeviceProperty:(PropertyChangeBlock)propertyChange{
    AVCaptureDevice *captureDevice= [self.captureDeviceInput device];
    NSError *error;
    //注意改變設(shè)備屬性前一定要首先調(diào)用lockForConfiguration:調(diào)用完之后使用unlockForConfiguration方法解鎖
    if ([captureDevice lockForConfiguration:&error]) {
        propertyChange(captureDevice);
        [captureDevice unlockForConfiguration];
    }else{
        NSLog(@"設(shè)置設(shè)備屬性過程發(fā)生錯(cuò)誤翅睛,錯(cuò)誤信息:%@",error.localizedDescription);
    }
}

/**
 *  設(shè)置閃光燈模式
 *
 *  @param flashMode 閃光燈模式
 */
-(void)setFlashMode:(AVCaptureFlashMode )flashMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFlashModeSupported:flashMode]) {
            [captureDevice setFlashMode:flashMode];
        }
    }];
}
/**
 *  設(shè)置聚焦模式
 *
 *  @param focusMode 聚焦模式
 */
-(void)setFocusMode:(AVCaptureFocusMode )focusMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFocusModeSupported:focusMode]) {
            [captureDevice setFocusMode:focusMode];
        }
    }];
}
/**
 *  設(shè)置曝光模式
 *
 *  @param exposureMode 曝光模式
 */
-(void)setExposureMode:(AVCaptureExposureMode)exposureMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isExposureModeSupported:exposureMode]) {
            [captureDevice setExposureMode:exposureMode];
        }
    }];
}
/**
 *  設(shè)置聚焦點(diǎn)
 *
 *  @param point 聚焦點(diǎn)
 */
-(void)focusWithMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode atPoint:(CGPoint)point{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFocusModeSupported:focusMode]) {
            [captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
        }
        if ([captureDevice isFocusPointOfInterestSupported]) {
            [captureDevice setFocusPointOfInterest:point];
        }
        if ([captureDevice isExposureModeSupported:exposureMode]) {
            [captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
        }
        if ([captureDevice isExposurePointOfInterestSupported]) {
            [captureDevice setExposurePointOfInterest:point];
        }
    }];
}

/**
 *  添加點(diǎn)按手勢声搁,點(diǎn)按時(shí)聚焦
 */
-(void)addGenstureRecognizer{
    UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapScreen:)];
    [self.viewContainer addGestureRecognizer:tapGesture];
}
-(void)tapScreen:(UITapGestureRecognizer *)tapGesture{
    CGPoint point= [tapGesture locationInView:self.viewContainer];
    //將UI坐標(biāo)轉(zhuǎn)化為攝像頭坐標(biāo)
    CGPoint cameraPoint= [self.captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];
    [self setFocusCursorWithPoint:point];
    [self focusWithMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose atPoint:cameraPoint];
}

/**
 *  設(shè)置閃光燈按鈕狀態(tài)
 */
-(void)setFlashModeButtonStatus{
    AVCaptureDevice *captureDevice=[self.captureDeviceInput device];
    AVCaptureFlashMode flashMode=captureDevice.flashMode;
    if([captureDevice isFlashAvailable]){
        self.flashAutoButton.hidden=NO;
        self.flashOnButton.hidden=NO;
        self.flashOffButton.hidden=NO;
        self.flashAutoButton.enabled=YES;
        self.flashOnButton.enabled=YES;
        self.flashOffButton.enabled=YES;
        switch (flashMode) {
            case AVCaptureFlashModeAuto:
                self.flashAutoButton.enabled=NO;
                break;
            case AVCaptureFlashModeOn:
                self.flashOnButton.enabled=NO;
                break;
            case AVCaptureFlashModeOff:
                self.flashOffButton.enabled=NO;
                break;
            default:
                break;
        }
    }else{
        self.flashAutoButton.hidden=YES;
        self.flashOnButton.hidden=YES;
        self.flashOffButton.hidden=YES;
    }
}

/**
 *  設(shè)置聚焦光標(biāo)位置
 *
 *  @param point 光標(biāo)位置
 */
-(void)setFocusCursorWithPoint:(CGPoint)point{
    self.focusCursor.center=point;
    self.focusCursor.transform=CGAffineTransformMakeScale(1.5, 1.5);
    self.focusCursor.alpha=1.0;
    [UIView animateWithDuration:1.0 animations:^{
        self.focusCursor.transform=CGAffineTransformIdentity;
    } completion:^(BOOL finished) {
        self.focusCursor.alpha=0;
        
    }];
}
@end
視頻錄制

其實(shí)有了前面的拍照應(yīng)用之后要在此基礎(chǔ)上做視頻錄制功能并不復(fù)雜,程序只需要做如下修改:

1捕发、添加一個(gè)音頻輸入到會(huì)話(使用[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject]獲得輸入設(shè)備疏旨,然后根據(jù)此輸入設(shè)備創(chuàng)建一個(gè)設(shè)備輸入對象),在拍照程序中已經(jīng)添加了視頻輸入所以此時(shí)不需要添加視頻輸入扎酷。
2檐涝、創(chuàng)建一個(gè)音樂播放文件輸出對象AVCaptureMovieFileOutput取代原來的照片輸出對象。
3法挨、將捕獲到的視頻數(shù)據(jù)寫入到臨時(shí)文件并在停止錄制之后保存到相簿(通過AVCaptureMovieFileOutput的代理方法)谁榜。

相比拍照程序,程序的修改主要就是以上三點(diǎn)凡纳。當(dāng)然為了讓程序更加完善在下面的視頻錄制程序中加入了屏幕旋轉(zhuǎn)視頻窃植、自動(dòng)布局和后臺(tái)保存任務(wù)等細(xì)節(jié)。下面是修改后的程序:

//
//  ViewController.m
//  AVFoundationCamera
//
//  Created by Kenshin Cui on 14/04/05.
//  Copyright (c) 2014年 cmjstudio. All rights reserved.
//  視頻錄制

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <AssetsLibrary/AssetsLibrary.h>
typedef void(^PropertyChangeBlock)(AVCaptureDevice *captureDevice);

@interface ViewController ()<AVCaptureFileOutputRecordingDelegate>//視頻文件輸出代理

@property (strong,nonatomic) AVCaptureSession *captureSession;//負(fù)責(zé)輸入和輸出設(shè)備之間的數(shù)據(jù)傳遞
@property (strong,nonatomic) AVCaptureDeviceInput *captureDeviceInput;//負(fù)責(zé)從AVCaptureDevice獲得輸入數(shù)據(jù)
@property (strong,nonatomic) AVCaptureMovieFileOutput *captureMovieFileOutput;//視頻輸出流
@property (strong,nonatomic) AVCaptureVideoPreviewLayer *captureVideoPreviewLayer;//相機(jī)拍攝預(yù)覽圖層
@property (assign,nonatomic) BOOL enableRotation;//是否允許旋轉(zhuǎn)(注意在視頻錄制過程中禁止屏幕旋轉(zhuǎn))
@property (assign,nonatomic) CGRect *lastBounds;//旋轉(zhuǎn)的前大小
@property (assign,nonatomic) UIBackgroundTaskIdentifier backgroundTaskIdentifier;//后臺(tái)任務(wù)標(biāo)識(shí)
@property (weak, nonatomic) IBOutlet UIView *viewContainer;
@property (weak, nonatomic) IBOutlet UIButton *takeButton;//拍照按鈕
@property (weak, nonatomic) IBOutlet UIImageView *focusCursor; //聚焦光標(biāo)


@end

@implementation ViewController

#pragma mark - 控制器視圖方法
- (void)viewDidLoad {
    [super viewDidLoad];
}

-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    //初始化會(huì)話
    _captureSession=[[AVCaptureSession alloc]init];
    if ([_captureSession canSetSessionPreset:AVCaptureSessionPreset1280x720]) {//設(shè)置分辨率
        _captureSession.sessionPreset=AVCaptureSessionPreset1280x720;
    }
    //獲得輸入設(shè)備
    AVCaptureDevice *captureDevice=[self getCameraDeviceWithPosition:AVCaptureDevicePositionBack];//取得后置攝像頭
    if (!captureDevice) {
        NSLog(@"取得后置攝像頭時(shí)出現(xiàn)問題.");
        return;
    }
    //添加一個(gè)音頻輸入設(shè)備
    AVCaptureDevice *audioCaptureDevice=[[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
    
    
    NSError *error=nil;
    //根據(jù)輸入設(shè)備初始化設(shè)備輸入對象惫企,用于獲得輸入數(shù)據(jù)
    _captureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:captureDevice error:&error];
    if (error) {
        NSLog(@"取得設(shè)備輸入對象時(shí)出錯(cuò)撕瞧,錯(cuò)誤原因:%@",error.localizedDescription);
        return;
    }
    AVCaptureDeviceInput *audioCaptureDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:audioCaptureDevice error:&error];
    if (error) {
        NSLog(@"取得設(shè)備輸入對象時(shí)出錯(cuò),錯(cuò)誤原因:%@",error.localizedDescription);
        return;
    }
    //初始化設(shè)備輸出對象狞尔,用于獲得輸出數(shù)據(jù)
    _captureMovieFileOutput=[[AVCaptureMovieFileOutput alloc]init];
    
    //將設(shè)備輸入添加到會(huì)話中
    if ([_captureSession canAddInput:_captureDeviceInput]) {
        [_captureSession addInput:_captureDeviceInput];
        [_captureSession addInput:audioCaptureDeviceInput];
        AVCaptureConnection *captureConnection=[_captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
        if ([captureConnection isVideoStabilizationSupported ]) {
            captureConnection.preferredVideoStabilizationMode=AVCaptureVideoStabilizationModeAuto;
        }
    }
    
    //將設(shè)備輸出添加到會(huì)話中
    if ([_captureSession canAddOutput:_captureMovieFileOutput]) {
        [_captureSession addOutput:_captureMovieFileOutput];
    }
    
    //創(chuàng)建視頻預(yù)覽層丛版,用于實(shí)時(shí)展示攝像頭狀態(tài)
    _captureVideoPreviewLayer=[[AVCaptureVideoPreviewLayer alloc]initWithSession:self.captureSession];
    
    CALayer *layer=self.viewContainer.layer;
    layer.masksToBounds=YES;
    
    _captureVideoPreviewLayer.frame=layer.bounds;
    _captureVideoPreviewLayer.videoGravity=AVLayerVideoGravityResizeAspectFill;//填充模式
    //將視頻預(yù)覽層添加到界面中
    //[layer addSublayer:_captureVideoPreviewLayer];
    [layer insertSublayer:_captureVideoPreviewLayer below:self.focusCursor.layer];
    
    _enableRotation=YES;
    [self addNotificationToCaptureDevice:captureDevice];
    [self addGenstureRecognizer];
}

-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    [self.captureSession startRunning];
}

-(void)viewDidDisappear:(BOOL)animated{
    [super viewDidDisappear:animated];
    [self.captureSession stopRunning];
}

-(BOOL)shouldAutorotate{
    return self.enableRotation;
}

////屏幕旋轉(zhuǎn)時(shí)調(diào)整視頻預(yù)覽圖層的方向
//-(void)willTransitionToTraitCollection:(UITraitCollection *)newCollection withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator{
//    [super willTransitionToTraitCollection:newCollection withTransitionCoordinator:coordinator];
////    NSLog(@"%i,%i",newCollection.verticalSizeClass,newCollection.horizontalSizeClass);
//    UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
//    NSLog(@"%i",orientation);
//    AVCaptureConnection *captureConnection=[self.captureVideoPreviewLayer connection];
//    captureConnection.videoOrientation=orientation;
//    
//}
//屏幕旋轉(zhuǎn)時(shí)調(diào)整視頻預(yù)覽圖層的方向
-(void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration{
    AVCaptureConnection *captureConnection=[self.captureVideoPreviewLayer connection];
    captureConnection.videoOrientation=(AVCaptureVideoOrientation)toInterfaceOrientation;
}
//旋轉(zhuǎn)后重新設(shè)置大小
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation{
    _captureVideoPreviewLayer.frame=self.viewContainer.bounds;
}

-(void)dealloc{
    [self removeNotification];
}
#pragma mark - UI方法
#pragma mark 視頻錄制
- (IBAction)takeButtonClick:(UIButton *)sender {
    //根據(jù)設(shè)備輸出獲得連接
    AVCaptureConnection *captureConnection=[self.captureMovieFileOutput connectionWithMediaType:AVMediaTypeVideo];
    //根據(jù)連接取得設(shè)備輸出的數(shù)據(jù)
    if (![self.captureMovieFileOutput isRecording]) {
        self.enableRotation=NO;
        //如果支持多任務(wù)則則開始多任務(wù)
        if ([[UIDevice currentDevice] isMultitaskingSupported]) {
            self.backgroundTaskIdentifier=[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil];
        }
        //預(yù)覽圖層和視頻方向保持一致
        captureConnection.videoOrientation=[self.captureVideoPreviewLayer connection].videoOrientation;
        NSString *outputFielPath=[NSTemporaryDirectory() stringByAppendingString:@"myMovie.mov"];
        NSLog(@"save path is :%@",outputFielPath);
        NSURL *fileUrl=[NSURL fileURLWithPath:outputFielPath];
        [self.captureMovieFileOutput startRecordingToOutputFileURL:fileUrl recordingDelegate:self];
    }
    else{
        [self.captureMovieFileOutput stopRecording];//停止錄制
    }
}
#pragma mark 切換前后攝像頭
- (IBAction)toggleButtonClick:(UIButton *)sender {
    AVCaptureDevice *currentDevice=[self.captureDeviceInput device];
    AVCaptureDevicePosition currentPosition=[currentDevice position];
    [self removeNotificationFromCaptureDevice:currentDevice];
    AVCaptureDevice *toChangeDevice;
    AVCaptureDevicePosition toChangePosition=AVCaptureDevicePositionFront;
    if (currentPosition==AVCaptureDevicePositionUnspecified||currentPosition==AVCaptureDevicePositionFront) {
        toChangePosition=AVCaptureDevicePositionBack;
    }
    toChangeDevice=[self getCameraDeviceWithPosition:toChangePosition];
    [self addNotificationToCaptureDevice:toChangeDevice];
    //獲得要調(diào)整的設(shè)備輸入對象
    AVCaptureDeviceInput *toChangeDeviceInput=[[AVCaptureDeviceInput alloc]initWithDevice:toChangeDevice error:nil];
    
    //改變會(huì)話的配置前一定要先開啟配置,配置完成后提交配置改變
    [self.captureSession beginConfiguration];
    //移除原有輸入對象
    [self.captureSession removeInput:self.captureDeviceInput];
    //添加新的輸入對象
    if ([self.captureSession canAddInput:toChangeDeviceInput]) {
        [self.captureSession addInput:toChangeDeviceInput];
        self.captureDeviceInput=toChangeDeviceInput;
    }
    //提交會(huì)話配置
    [self.captureSession commitConfiguration];
    
}

#pragma mark - 視頻輸出代理
-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections{
    NSLog(@"開始錄制...");
}
-(void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error{
    NSLog(@"視頻錄制完成.");
    //視頻錄入完成之后在后臺(tái)將視頻存儲(chǔ)到相簿
    self.enableRotation=YES;
    UIBackgroundTaskIdentifier lastBackgroundTaskIdentifier=self.backgroundTaskIdentifier;
    self.backgroundTaskIdentifier=UIBackgroundTaskInvalid;
    ALAssetsLibrary *assetsLibrary=[[ALAssetsLibrary alloc]init];
    [assetsLibrary writeVideoAtPathToSavedPhotosAlbum:outputFileURL completionBlock:^(NSURL *assetURL, NSError *error) {
        if (error) {
            NSLog(@"保存視頻到相簿過程中發(fā)生錯(cuò)誤偏序,錯(cuò)誤信息:%@",error.localizedDescription);
        }
        if (lastBackgroundTaskIdentifier!=UIBackgroundTaskInvalid) {
            [[UIApplication sharedApplication] endBackgroundTask:lastBackgroundTaskIdentifier];
        }
        NSLog(@"成功保存視頻到相簿.");
    }];
    
}

#pragma mark - 通知
/**
 *  給輸入設(shè)備添加通知
 */
-(void)addNotificationToCaptureDevice:(AVCaptureDevice *)captureDevice{
    //注意添加區(qū)域改變捕獲通知必須首先設(shè)置設(shè)備允許捕獲
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        captureDevice.subjectAreaChangeMonitoringEnabled=YES;
    }];
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    //捕獲區(qū)域發(fā)生改變
    [notificationCenter addObserver:self selector:@selector(areaChange:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
-(void)removeNotificationFromCaptureDevice:(AVCaptureDevice *)captureDevice{
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    [notificationCenter removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:captureDevice];
}
/**
 *  移除所有通知
 */
-(void)removeNotification{
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    [notificationCenter removeObserver:self];
}

-(void)addNotificationToCaptureSession:(AVCaptureSession *)captureSession{
    NSNotificationCenter *notificationCenter= [NSNotificationCenter defaultCenter];
    //會(huì)話出錯(cuò)
    [notificationCenter addObserver:self selector:@selector(sessionRuntimeError:) name:AVCaptureSessionRuntimeErrorNotification object:captureSession];
}

/**
 *  設(shè)備連接成功
 *
 *  @param notification 通知對象
 */
-(void)deviceConnected:(NSNotification *)notification{
    NSLog(@"設(shè)備已連接...");
}
/**
 *  設(shè)備連接斷開
 *
 *  @param notification 通知對象
 */
-(void)deviceDisconnected:(NSNotification *)notification{
    NSLog(@"設(shè)備已斷開.");
}
/**
 *  捕獲區(qū)域改變
 *
 *  @param notification 通知對象
 */
-(void)areaChange:(NSNotification *)notification{
    NSLog(@"捕獲區(qū)域改變...");
}

/**
 *  會(huì)話出錯(cuò)
 *
 *  @param notification 通知對象
 */
-(void)sessionRuntimeError:(NSNotification *)notification{
    NSLog(@"會(huì)話發(fā)生錯(cuò)誤.");
}

#pragma mark - 私有方法

/**
 *  取得指定位置的攝像頭
 *
 *  @param position 攝像頭位置
 *
 *  @return 攝像頭設(shè)備
 */
-(AVCaptureDevice *)getCameraDeviceWithPosition:(AVCaptureDevicePosition )position{
    NSArray *cameras= [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo];
    for (AVCaptureDevice *camera in cameras) {
        if ([camera position]==position) {
            return camera;
        }
    }
    return nil;
}

/**
 *  改變設(shè)備屬性的統(tǒng)一操作方法
 *
 *  @param propertyChange 屬性改變操作
 */
-(void)changeDeviceProperty:(PropertyChangeBlock)propertyChange{
    AVCaptureDevice *captureDevice= [self.captureDeviceInput device];
    NSError *error;
    //注意改變設(shè)備屬性前一定要首先調(diào)用lockForConfiguration:調(diào)用完之后使用unlockForConfiguration方法解鎖
    if ([captureDevice lockForConfiguration:&error]) {
        propertyChange(captureDevice);
        [captureDevice unlockForConfiguration];
    }else{
        NSLog(@"設(shè)置設(shè)備屬性過程發(fā)生錯(cuò)誤页畦,錯(cuò)誤信息:%@",error.localizedDescription);
    }
}

/**
 *  設(shè)置閃光燈模式
 *
 *  @param flashMode 閃光燈模式
 */
-(void)setFlashMode:(AVCaptureFlashMode )flashMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFlashModeSupported:flashMode]) {
            [captureDevice setFlashMode:flashMode];
        }
    }];
}
/**
 *  設(shè)置聚焦模式
 *
 *  @param focusMode 聚焦模式
 */
-(void)setFocusMode:(AVCaptureFocusMode )focusMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFocusModeSupported:focusMode]) {
            [captureDevice setFocusMode:focusMode];
        }
    }];
}
/**
 *  設(shè)置曝光模式
 *
 *  @param exposureMode 曝光模式
 */
-(void)setExposureMode:(AVCaptureExposureMode)exposureMode{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isExposureModeSupported:exposureMode]) {
            [captureDevice setExposureMode:exposureMode];
        }
    }];
}
/**
 *  設(shè)置聚焦點(diǎn)
 *
 *  @param point 聚焦點(diǎn)
 */
-(void)focusWithMode:(AVCaptureFocusMode)focusMode exposureMode:(AVCaptureExposureMode)exposureMode atPoint:(CGPoint)point{
    [self changeDeviceProperty:^(AVCaptureDevice *captureDevice) {
        if ([captureDevice isFocusModeSupported:focusMode]) {
            [captureDevice setFocusMode:AVCaptureFocusModeAutoFocus];
        }
        if ([captureDevice isFocusPointOfInterestSupported]) {
            [captureDevice setFocusPointOfInterest:point];
        }
        if ([captureDevice isExposureModeSupported:exposureMode]) {
            [captureDevice setExposureMode:AVCaptureExposureModeAutoExpose];
        }
        if ([captureDevice isExposurePointOfInterestSupported]) {
            [captureDevice setExposurePointOfInterest:point];
        }
    }];
}

/**
 *  添加點(diǎn)按手勢,點(diǎn)按時(shí)聚焦
 */
-(void)addGenstureRecognizer{
    UITapGestureRecognizer *tapGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tapScreen:)];
    [self.viewContainer addGestureRecognizer:tapGesture];
}
-(void)tapScreen:(UITapGestureRecognizer *)tapGesture{
    CGPoint point= [tapGesture locationInView:self.viewContainer];
    //將UI坐標(biāo)轉(zhuǎn)化為攝像頭坐標(biāo)
    CGPoint cameraPoint= [self.captureVideoPreviewLayer captureDevicePointOfInterestForPoint:point];
    [self setFocusCursorWithPoint:point];
    [self focusWithMode:AVCaptureFocusModeAutoFocus exposureMode:AVCaptureExposureModeAutoExpose atPoint:cameraPoint];
}

/**
 *  設(shè)置聚焦光標(biāo)位置
 *
 *  @param point 光標(biāo)位置
 */
-(void)setFocusCursorWithPoint:(CGPoint)point{
    self.focusCursor.center=point;
    self.focusCursor.transform=CGAffineTransformMakeScale(1.5, 1.5);
    self.focusCursor.alpha=1.0;
    [UIView animateWithDuration:1.0 animations:^{
        self.focusCursor.transform=CGAffineTransformIdentity;
    } completion:^(BOOL finished) {
        self.focusCursor.alpha=0;
        
    }];
}
@end

總結(jié)

前面用了大量的篇幅介紹了iOS中的音研儒、視頻播放和錄制豫缨,有些地方用到了封裝好的播放器独令、錄音機(jī)直接使用,有些是直接調(diào)用系統(tǒng)服務(wù)自己組織封裝好芭,正如本篇開頭所言燃箭,iOS對于多媒體支持相當(dāng)靈活和完善,那么開放過程中如何選擇呢舍败,下面就以一個(gè)表格簡單對比一下各個(gè)開發(fā)技術(shù)的優(yōu)缺點(diǎn)招狸。

參考:
http://www.cnblogs.com/kenshincui/p/4186022.html#mpMoviePlayerViewController

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市邻薯,隨后出現(xiàn)的幾起案子裙戏,更是在濱河造成了極大的恐慌,老刑警劉巖厕诡,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件累榜,死亡現(xiàn)場離奇詭異,居然都是意外死亡灵嫌,警方通過查閱死者的電腦和手機(jī)壹罚,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來醒第,“玉大人渔嚷,你說我怎么就攤上這事〕砺” “怎么了形病?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長霞幅。 經(jīng)常有香客問我漠吻,道長,這世上最難降的妖魔是什么司恳? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任途乃,我火速辦了婚禮,結(jié)果婚禮上扔傅,老公的妹妹穿的比我還像新娘耍共。我一直安慰自己,他們只是感情好猎塞,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布试读。 她就那樣靜靜地躺著,像睡著了一般荠耽。 火紅的嫁衣襯著肌膚如雪钩骇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天,我揣著相機(jī)與錄音倘屹,去河邊找鬼银亲。 笑死,一個(gè)胖子當(dāng)著我的面吹牛纽匙,可吹牛的內(nèi)容都是我干的务蝠。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼哄辣,長吁一口氣:“原來是場噩夢啊……” “哼请梢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起力穗,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎气嫁,沒想到半個(gè)月后当窗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寸宵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年崖面,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梯影。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡巫员,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出甲棍,到底是詐尸還是另有隱情简识,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布感猛,位于F島的核電站七扰,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏陪白。R本人自食惡果不足惜颈走,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望咱士。 院中可真熱鬧立由,春花似錦、人聲如沸序厉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脂矫。三九已至枣耀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背捞奕。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工牺堰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人颅围。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓伟葫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親院促。 傳聞我的和親對象是個(gè)殘疾皇子筏养,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容