iOS下AirPlay投屏功能實現

這篇文章注重于如何實現AirPlay投屏功能.具體AirPlay的實現邏輯這里不再贅述,網上帖子很多

首先.想要呼出AirPlay列表的話,需要將MPVolumnView控件聲明且添加到UI.上使用之前需要引入頭文件<MediaPlayer/MediaPlayer.h>.后續如果有AirPlay設備可用并且MPVolumnView存在于UI中的話.即可呼出列表
MPVolumnView:一個系統內置的控件.繼承自UIView.內部自定義了三個控件:MPVolumeSlider(音量進度條)UILabel(顯示文字)MPButton(點擊呼出AirPlay選擇器)
①如何解決MPVolumnView添加到UI中之后高亮效果會出現系統原生icon的問題
答:設置不同State的image為ni即可.
code:

MPVolumnView *volumnView = [[MPVolumnView alloc] init];
//此處btnSender的實際類型為MPButton
UIButton *btnSender = [volumnView objectWithBlock:^BOOL(UIView *obj) {
        return [obj isKindOfClass:[UIButton class]];
    }];
    [btnSender setImage:nil forState:UIControlStateNormal];
    [btnSender setImage:nil forState:UIControlStateHighlighted];
    [btnSender setImage:nil forState:UIControlStateSelected];

②如何解決在沒有可用設備情況下點擊MPVolumnView不會呼出AudioRoutePicker的問題
答:目前我找到的一個解決方案是完全無視MPVolumnView.將它在init時frame設置為0,0,0,0 然后在界面上自己添加一個UIButton.在它的點擊事件中給MPVolumnView的MPButton發送一個UIControlEventTouchUpInside來觸發MPVolumnView的_displayAudioRoutePicker事件
code:

//首先.自定義類.并且繼承MPVolumnView
@interface SparkMPVolumnView : MPVolumeView

@property(nonatomic,weak)UIButton *MPButton;
@end

@implementation SparkMPVolumnView

-(instancetype)init{
    if(self = [self initWithFrame:CGRectZero]){
        self.backgroundColor = [UIColor clearColor];
        self.showsVolumeSlider = NO;
        self.tag = kTVMPVolumeViewTag;
        [self initMPButton];
    }
    return self;
}
//這個的目的是在AirPlay沒有任何設備時也能呼出Picker使用
- (void)initMPButton{
    UIButton *btnSender = [self.subviews objectWithBlock:^BOOL(UIView *obj) {
        return [obj isKindOfClass:[UIButton class]];
    }];
    [btnSender setImage:nil forState:UIControlStateNormal];
    [btnSender setImage:nil forState:UIControlStateHighlighted];
    [btnSender setImage:nil forState:UIControlStateSelected];
    [btnSender setBounds:CGRectZero];
    self.MPButton = btnSender;
}
//在自定義按鈕的按下事件中發送給MPButton
//此處sparkVolumView為一個SparkMPVolumnView實例
-(void)airPlayButtonAction:(UIButton *)sender{
        [self.sparkVolumView.MPButton sendActionsForControlEvents:UIControlEventTouchUpInside];
}

③如何知道AirPlay的連接和斷開狀態?
答:其他資料大多是通過注冊MPVolumeViewWirelessRouteActiveDidChangeNotification來進行判斷的.但是這個通知有一個問題就是在存在多個MPVolumnView時會出現多次發送,并且如果在聲明MPVolumnView控件時是連接AirPlay設備的,它會先發送一個屬性wirelessRouteActive為NO的通知(即未連接任何設備),然后立馬發送一個wirelessRouteActive位YES的通知(連接到了設備).故我并未采用此方案.
我注冊了AVAudioSessionRouteChangeNotification通知.這個通知在音頻通道發生變化時會進行調用(例如插入/拔出耳機 揚聲器/聽筒切換 連接/斷開AirPlay設備).這個的通知是唯一且不重復的.在發生改變時[AVAudioSession sharedInstance]會發送此通知.在具體通知中通過獲取當前的AirPlay通道名稱來判斷是否連接到了投屏設備
code:

-(void)registerNotification{
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(audioRouteHasChangedNotification:) name:AVAudioSessionRouteChangeNotification object:[AVAudioSession sharedInstance]];
}
- (void)audioRouteHasChangedNotification:(NSNotification *)notification{
    NSString *airplayDeviceName = [self activeAirplayOutputRouteName];
    BOOL isAirPlay = self.airplayDeviceName.length > 0;
}
//遍歷當前設備所有通道.返回isEqualToString:AVAudioSessionPortAirPlay通道的具體名稱,如果名稱不為nil則為當前連接到了AirPlay
- (NSString*)activeAirplayOutputRouteName
{
    AVAudioSession* audioSession = [AVAudioSession sharedInstance];
    AVAudioSessionRouteDescription* currentRoute = audioSession.currentRoute;
    for (AVAudioSessionPortDescription* outputPort in currentRoute.outputs){
        if ([outputPort.portType isEqualToString:AVAudioSessionPortAirPlay])
            return outputPort.portName;
    }
    
    return nil;
}

④為什么播放過程中會電視端會出現暫停和退出播放的情況
答:AirPlay實現的原理是一個Socket通信.并且會在新的通道請求之后斷開之前的.所有在項目中你理論上是有多處會播放視頻的.只要調用一個新播放器的Play方法.設備端就會認為你重新發起了一個Socket請求,斷開之前的播放并且發起新的視頻播放.故需要保證在AirPlay連接的情況下不調用任何視頻的暫停和播放代碼.
在我的項目中邏輯是比較復雜的,故實現方式是通過一個單例的Manager強引用一個AVPlayer,所有的AirPlay播放和暫停請求都通過它來實現..其他代碼只要保證在連接AirPlay的時候不進行操作就可以了.

⑤AirPlay在播放時播放新視頻為什么可能會投屏失敗?
答:這個我也無解.沒有查到相關的資料.現在的解決方案是在投放新視頻前檢測,如果只有有視頻,暫停播放并且將AVPlayer設置為nil.保證電視端退出投屏,隨后增加1.5f延時再進行播放.基本解決了投屏失敗的問題

其他問題歡迎大家在評論區討論,做AirPlay時趟了許多坑.希望這篇文章能幫助大家少走彎路.有不對的地方希望指出,第一時間修正

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網絡請求組件 FMDB本地數據庫組件 SD...
    陽明AGI閱讀 16,003評論 3 119
  • f43ad14dd74b閱讀 216評論 2 1
  • 《關鍵期關鍵幫助》第三章 P278-291 (4-5歲)孩子需要探索他人心智 這個時期的孩子在跟別人的互動中會發現...
    羅小太陽閱讀 543評論 0 0