通過(guò)使用指定方式播放一段極小音頻,比較播放的開(kāi)始和完成時(shí)間皆辽,來(lái)判斷當(dāng)前靜音按鈕的狀態(tài)萨赁。
我也針對(duì)常見(jiàn)的對(duì)音量方面的需求做了一個(gè)小工具弊琴,歡迎大家使用、指正位迂。
2018年9月6日更新:
1.【修正】——App從后臺(tái)切到前臺(tái)時(shí)【AVPlayerItemDidPlayToEndTimeNotification】通知被無(wú)故調(diào)用的問(wèn)題访雪;
2.【修正】——在有其他音/視頻播放時(shí)详瑞,初始化該工具會(huì)使正在播放的音/視頻間斷一下的問(wèn)題掂林。
解釋下第一個(gè)bug:實(shí)際使用中發(fā)現(xiàn),在使用原有AVPlayerItem的方式進(jìn)行預(yù)播放音頻后(用于本文最后說(shuō)的那個(gè)小坑)坝橡,使用AVPlayer正常播放音/視頻泻帮,此時(shí)將App從后臺(tái)切換到前臺(tái),用于預(yù)播放音頻的AVPlayerItem會(huì)無(wú)故發(fā)出AVPlayerItemDidPlayToEndTimeNotification的通知计寇,可能會(huì)影響到業(yè)務(wù)層锣杂。
為什么說(shuō)是無(wú)故呢?因?yàn)锳pp從后臺(tái)切換到前臺(tái)時(shí)番宁,預(yù)播放音頻早已經(jīng)結(jié)束了很久元莫,而且,當(dāng)時(shí)結(jié)束時(shí)已經(jīng)發(fā)出過(guò)AVPlayerItemDidPlayToEndTimeNotification通知了蝶押;也就是說(shuō)踱蠢,本已結(jié)束的音/視頻,會(huì)由于從后臺(tái)切到前臺(tái)而反復(fù)發(fā)出AVPlayerItemDidPlayToEndTimeNotification通知(這也是很神奇棋电,并沒(méi)有想通是什么原理茎截,也沒(méi)有查到相關(guān)的資料苇侵,如有大神了解,希望您不吝賜教~)企锌。
剛開(kāi)始看到這個(gè)需求的時(shí)候榆浓,覺(jué)得這個(gè)應(yīng)該會(huì)有相應(yīng)的api,直接調(diào)用就可以了撕攒。但是實(shí)際一查才發(fā)現(xiàn):并沒(méi)有陡鹃,準(zhǔn)確的說(shuō)是iOS5之后的版本相關(guān)api就不再支持了。細(xì)想下抖坪,其實(shí)這也挺符合Apple的行事作風(fēng)的杉适,app只要為用戶提供服務(wù)就好了,用戶的操作并不會(huì)讓你知曉柳击,極盡的保護(hù)用戶的一切隱私猿推。
那么在iOS5之后,我們?cè)跊](méi)有直接api的情況下捌肴,應(yīng)該如何檢測(cè)設(shè)備靜音按鈕處于什么狀態(tài)呢蹬叭?曲線救國(guó)~
首先鳴謝下RBDMuteSwitch這個(gè)庫(kù)。因?yàn)槲宜褂煤诵姆椒ㄒ彩墙梃b了這個(gè)庫(kù)中的方式來(lái)實(shí)現(xiàn)状知。
- 首先秽五,這里我們要使用到一種平時(shí)不是很常用的播放音頻的方式
-(void)monitorMute{
CFURLRef soundFileURLRef = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("detection"), CFSTR("aiff"), NULL);
SystemSoundID soundFileID;
AudioServicesCreateSystemSoundID(soundFileURLRef, &soundFileID);
AudioServicesAddSystemSoundCompletion(soundFileID, NULL, NULL, soundCompletionBlock, (__bridge void*) self);
AudioServicesPlaySystemSound(soundFileID);
}
static void soundCompletionBlock(SystemSoundID SSID, void *mySelf){
AudioServicesRemoveSystemSoundCompletion(SSID);
[[XTVolumeMonitor defaultMonitor] playToEnd];
}
AudioServicesPlaySystemSound
方法支持的格式少,而且還要求音頻時(shí)長(zhǎng)為30s以下饥悴,但是坦喘,他有一個(gè)對(duì)我們最有用的特性:如果靜音按鈕為靜音狀態(tài),那么會(huì)《立即》執(zhí)行預(yù)先植入的soundCompletionBlock西设。相信到這您就可以瞬間想通后面的一切問(wèn)題了~
針對(duì)這一特性瓣铣,我通過(guò)記錄開(kāi)始播放和完成播放的時(shí)間,計(jì)算二者的差值贷揽,來(lái)判斷靜音按鈕的狀態(tài)棠笑。
由此也可以發(fā)現(xiàn),這里需要以回調(diào)的方式來(lái)向詢問(wèn)者返回值禽绪,這里我選擇了block蓖救,具體方式下文詳述。而且為了回調(diào)足夠及時(shí)印屁,所以使用了一個(gè)長(zhǎng)度僅為0.1s的音頻(該音頻素材也取自RBDMuteSwitch循捺,再次鳴謝!)雄人。
還有一點(diǎn)想強(qiáng)調(diào)一下从橘,就是這里的通過(guò)靜音按鈕置于靜音和將音量調(diào)小至0,是兩種不同的狀態(tài),對(duì)于該種檢測(cè)方式洋满,只有通過(guò)靜音按鈕置于靜音的方式晶乔,才會(huì)被判斷為靜音狀態(tài),完全滿足我的要求牺勾。
- 那么其他的靜音狀態(tài)或者音量狀態(tài)正罢,怎么辦呢?
根據(jù)我目前能夠想象到的需求驻民,我的這個(gè)工具主要提供了三個(gè)api:
- 判斷當(dāng)前靜音按鈕是否為靜音狀態(tài)
/**
用于回調(diào)當(dāng)前靜音按鈕是否為靜音狀態(tài)的block
@param isMute 如果靜音按鈕當(dāng)前為靜音狀態(tài)翻具,則為YES,否則為NO
*/
typedef void(^MuteBlock)(BOOL isMute);
/**
當(dāng)前靜音按鈕是否為靜音狀態(tài)
*/
@property (nonatomic, copy) MuteBlock muteBlock;
- 獲取當(dāng)前的真實(shí)音量(靜音按鈕處于靜音狀態(tài)時(shí)音量為0)
/**
用于回調(diào)當(dāng)前真實(shí)音量的block
@param currentRealVolume 當(dāng)前的真實(shí)音量
*/
typedef void(^RealVolumeBlock)(CGFloat currentRealVolume);
/**
當(dāng)前的真實(shí)音量(靜音按鈕處于靜音狀態(tài)時(shí)音量為0)
*/
@property (nonatomic, copy) RealVolumeBlock realVolumeBlock;
- 監(jiān)聽(tīng)音量變化(不考慮靜音按鈕處于靜音狀態(tài)的情況回还,該情況下仍正常返回實(shí)際的音量值裆泳,而不是0)
/**
用于監(jiān)聽(tīng)音量變化的block
@param oldVolume 原始音量值
@param newVolume 新的音量值
*/
typedef void(^VolumeChangeBlock)(CGFloat oldVolume, CGFloat newVolume);
/**
監(jiān)聽(tīng)音量變化(不考慮靜音按鈕處于靜音狀態(tài)的情況,該情況下仍正常返回實(shí)際的音量值柠硕,而不是0)
*/
@property (nonatomic, copy) VolumeChangeBlock volumeChangeBlock;
- 這里有一個(gè)小坑需要強(qiáng)調(diào)下:
常規(guī)的獲取當(dāng)前音量和監(jiān)聽(tīng)音量變化的操作工禾,相信大家都能瞬間實(shí)現(xiàn),但是蝗柔,如果你在執(zhí)行這兩個(gè)方法之前闻葵,沒(méi)有過(guò)任何一次的音頻播放,那么獲取到的這兩個(gè)值都是不準(zhǔn)確的:
獲取當(dāng)前音量一直為一個(gè)值癣丧,也就是說(shuō)即使你調(diào)整了音量槽畔,獲取的還是最初的值;
對(duì)音量變化的監(jiān)聽(tīng)則徹底不會(huì)觸發(fā)-(void)observeValueForKeyPath:ofObject: change:context:
方法胁编。
因此厢钧,我在工具的初始化方法中,執(zhí)行了一次極小音頻的播放嬉橙,保證您在任何情況下獲取的音量值及對(duì)音量變化的監(jiān)聽(tīng)都是正確的早直。