利用voip push實現(xiàn)類似微信(QQ)電話連續(xù)響鈴效果

前言

本文大部分參考 iOS利用voip push實現(xiàn)類似微信(QQ)電話連續(xù)響鈴效果 有興趣的可以多參考它下
自己代碼:gitHubDemo


最近客戶要求需要做出類似微信的 視頻拉起的呼叫的功能锉矢,開始使用的是mqtt的方式來發(fā)起的消息拉起件缸,

結(jié)果:發(fā)現(xiàn) app退到后臺榕吼、或者app殺死了矾端,就不會收到了(也就拉起不了了)

試了:APNs的方式,結(jié)果APNs根本實現(xiàn)不了連續(xù)通知幅虑,而且它也不會實現(xiàn)像本地通知那樣會有連續(xù)響鈴的效果(微信一般大概30s左右)

為了實現(xiàn)類似微信的方式耳胎,最終:我們通過 voip的方式來實現(xiàn)app的視頻的拉起

  • 說明

    • VoIP 推送基于pushKit框架
    • 好處:
      • 只有當VoIP發(fā)生推送時奖年,設(shè)備才會喚醒细诸,從而節(jié)省能源。
      • VoIP推送被認為是高優(yōu)先級通知陋守,并且毫無延遲地傳送震贵。
      • VoIP推送可以包括比標準推送通知提供的數(shù)據(jù)更多的數(shù)據(jù)。
      • 如果收到VoIP推送時水评,您的應(yīng)用程序未運行猩系,則會自動重新啟動。
      • 即使您的應(yīng)用在后臺運行中燥,您的應(yīng)用也會在運行時處理推送寇甸。
  • voip的集成

    • 在Xcode中開啟VoIP推送
    voip_01.jpeg
  • 在Apple Developer創(chuàng)建VoIP證書


    voip_02.jpeg
  • 跟APNs證書不同,VoIP證書不區(qū)分開發(fā)和生產(chǎn)環(huán)境疗涉,VoIP證書只有一個拿霉,生產(chǎn)和開發(fā)都可用同一個證書。另外有自己做過推送的應(yīng)該都知道服務(wù)器一般集成的.pem格式的證書博敬,所以還需將證書轉(zhuǎn)成.pem格式友浸,后面會介紹怎么轉(zhuǎn)換.pem證書。
    導(dǎo)入framework:PushKit.framework

  • Objective-C代碼集成偏窝,導(dǎo)入頭文件.

    #import <PushKit/PushKit.h>
    
  • 設(shè)置代理

    PKPushRegistry *pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
    pushRegistry.delegate = self;
    pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
    
  • 代理方法

    //應(yīng)用啟動此代理方法會返回設(shè)備Token 收恢、一般在此將token上傳服務(wù)器
    - (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type{
    }
    
    //當VoIP推送過來會調(diào)用此方法,一般在這里調(diào)起本地通知實現(xiàn)連續(xù)響鈴祭往、接收視頻呼叫請求等操作
    - (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type { 
    }
    

下面是我項目里面的代碼:(本人親用有效)

RingCall.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface RingCall : NSObject
+ (instancetype)sharedMCCall;
- (void)regsionPush;
@end
NS_ASSUME_NONNULL_END

RingCall.m
#import "RingCall.h"
#import "TalkVideoManager.h"
#import <UserNotifications/UserNotifications.h>
#import <AudioToolbox/AudioToolbox.h>

@interface RingCall ()<VideoCallbackDelegate>{
    UILocalNotification *callNotification;
    UNNotificationRequest *request;//ios 10
    NSTimer *_vibrationTimer;
}
@end

@implementation RingCall
+ (instancetype)sharedMCCall {
    static  RingCall *callInstane;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (callInstane == nil) {
            callInstane = [[RingCall alloc] init];
            [[TalkVideoManager sharedClient] setDelegate:callInstane];
        }
    });
    return callInstane;
}

- (void)regsionPush {
    //iOS 10
    if(@available(iOS 10.0, *)){
        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        [center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) {
            if (!error) {
                NSLog(@"request authorization succeeded!");
            }
        }];
        [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
            NSLog(@"%@",settings);
        }];
    }
}

#pragma mark-VideoCallbackDelegate
- (void)onCallRing:(NSString *)CallerName withInfo:(NSDictionary *)info{
    if (@available(iOS 10.0, *)) {
        UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter];
        UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init];
        content.body =[NSString localizedUserNotificationStringForKey:[NSString
                                                                       stringWithFormat:@"%@%@", CallerName,
                                                                       @"邀請你進行通話伦意。。硼补。驮肉。"] arguments:nil];
        content.userInfo = info;
        UNNotificationSound *customSound = [UNNotificationSound soundNamed:@"weixin.m4a"];
        content.sound = customSound;
        UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger
                                                      triggerWithTimeInterval:1 repeats:NO];
        request = [UNNotificationRequest requestWithIdentifier:@"Voip_Push"
                                                       content:content trigger:trigger];
        [self playShake];
        [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
            
        }];
    }else {
        callNotification = [[UILocalNotification alloc] init];
        callNotification.userInfo = info;
        callNotification.alertBody = [NSString
                                      stringWithFormat:@"%@%@", CallerName,
                                      @"邀請你進行通話。已骇。离钝。。"];
        callNotification.soundName = @"weixin.m4a";
        [self playShake];
        [[UIApplication sharedApplication] presentLocalNotificationNow:callNotification]; 
    }  
}

- (void)onCancelRing {
    //取消通知欄
    if (@available(iOS 10.0, *)) {
        NSMutableArray *arraylist = [[NSMutableArray alloc]init];
        [arraylist addObject:@"Voip_Push"];
        [[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:arraylist];
    }else {
        [[UIApplication sharedApplication] cancelLocalNotification:callNotification];
    }
    [_vibrationTimer invalidate];
}

-(void)playShake{
    if(_vibrationTimer){
        [_vibrationTimer invalidate];
        _vibrationTimer = nil;
    }else{
        _vibrationTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(playkSystemSound) userInfo:nil repeats:YES];
    }
}
//振動
- (void)playkSystemSound{
    AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
}
@end


TalkVideoManager.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@protocol VideoCallbackDelegate <NSObject>
/**
 *  當APP收到呼叫褪储、處于后臺時調(diào)用卵渴、用來處理通知欄類型和鈴聲。
 *
 *  @param name 呼叫者的名字
 */
- (void)onCallRing:(NSString*)name withInfo:(NSDictionary*)info;
/**
 *  呼叫取消調(diào)用鲤竹、取消通知欄
 */
- (void)onCancelRing;
@end

@interface TalkVideoManager : NSObject
+ (TalkVideoManager *)sharedClient;
- (void)initWithSever;
- (void)setDelegate:(id<VideoCallbackDelegate>)delegate;
//用戶掛斷/接聽  停止震動
-(void)cancleCall;
@end

TalkVideoManager.m
#import "TalkVideoManager.h"
#import <PushKit/PushKit.h>
#import "RingCall.h"
@interface TalkVideoManager ()<PKPushRegistryDelegate>{
    NSString *token;
}

@property (nonatomic,weak)id<VideoCallbackDelegate>mydelegate;
@end

@implementation TalkVideoManager
static TalkVideoManager *instance = nil;
+ (TalkVideoManager *)sharedClient {
    if (instance == nil) {
        instance = [[super allocWithZone:NULL] init];
    }
    return instance;
}

-(void)initWithSever {
    //voip delegate
    PKPushRegistry *pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
    pushRegistry.delegate = self;
    pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
    //ios10注冊本地通知
    if ([[UIDevice currentDevice].systemVersion floatValue] >= 10.0) {
        [[RingCall sharedMCCall] regsionPush];
    }
}

- (void)setDelegate:(id<VideoCallbackDelegate>)delegate {
    self.mydelegate = delegate;
}

#pragma mark -pushkitDelegate
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type{
    if([credentials.token length] == 0) {
        NSLog(@"voip token NULL");
        return;
    }
    //應(yīng)用啟動獲取token浪读,并上傳服務(wù)器
    token = [[[[credentials.token description] stringByReplacingOccurrencesOfString:@"<"withString:@""]
              stringByReplacingOccurrencesOfString:@">" withString:@""]
             stringByReplacingOccurrencesOfString:@" " withString:@""];
    NSLog(@"token:%@",token);
    //token上傳服務(wù)器
    [[ACCacheTool shareACCacheTool] setObjectForKey:token key:@"deviceToken"];
}

- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type{
    BOOL isCalling = false;
    switch ([UIApplication sharedApplication].applicationState) {
        case UIApplicationStateActive: {
            isCalling = false;
        }
            break;
        case UIApplicationStateInactive: {
            isCalling = false;
        }
            break;
        case UIApplicationStateBackground: {
            isCalling = true;
        }
            break;
        default:
            isCalling = true;
            break;
    }
    NSLog(@"payload==%@",payload.dictionaryPayload);
    if (isCalling){
        //獲取推送的內(nèi)容
        NSString *callerStr = payload.dictionaryPayload[@"aps"][@"alert"];
        //本地通知,實現(xiàn)響鈴效果
        [self.mydelegate onCallRing:callerStr withInfo:payload.dictionaryPayload];
        
    }
}

-(void)cancleCall{
    [self.mydelegate onCancelRing];
}
@end

--------------------------------------------------
使用:
在appdelegate.m里面
導(dǎo)入 #import "TalkVideoManager.h"

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {  
  ......
    //配置voIP
    [[TalkVideoManager sharedClient] initWithSever];
    return YES;
}

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
   NSLog(@"點擊了本地通知進來了---%@",notification.userInfo);
   [[TalkVideoManager sharedClient] cancleCall];
   
   .......//處理數(shù)據(jù)
}

說白了:就是通過voip發(fā)的消息回調(diào),來進行發(fā)送本地通知碘橘,然后點擊本地通知互订,再做相應(yīng)的數(shù)據(jù)邏輯處理。

  • 建立本地測試環(huán)境(自己搭建一個簡單的測試環(huán)境先測試通痘拆,然后再與服務(wù)器對接)

    • 制作.pem格式證書

      1仰禽、將之前生成的voip.cer SSL證書雙擊導(dǎo)入鑰匙串
      2、打開鑰匙串訪問纺蛆,在證書中找到對應(yīng)voip.cer生成的證書坟瓢,右鍵導(dǎo)出并選擇.p12格式,這里我們命名為voippush.p12,這里導(dǎo)出需要輸入密碼(隨意輸入,別忘記了)犹撒。
      3折联、目前我們有兩個文件,voip.cer SSL證書和voippush.p12私鑰识颊,新建文件夾命名為VoIP诚镰、并保存兩個文件到VoIP文件夾。
      4祥款、把.cer的SSL證書轉(zhuǎn)換為.pem文件清笨,打開終端命令行cd到VoIP文件夾、執(zhí)行以下命令
      openssl x509 -in voip.cer  -inform der -out VoiPCert.pem
      5刃跛、把.p12私鑰轉(zhuǎn)換成.pem文件抠艾,執(zhí)行以下命令(這里需要輸入之前導(dǎo)出設(shè)置的密碼)
      openssl pkcs12 -nocerts -out VoIPKey.pem -in voippush.p12
      6、再把生成的兩個.pem整合到一個.pem文件中
      cat VoiPCert.pem VoIPKey.pem > ck.pem
      最終生成的ck.pem文件一般就是服務(wù)器用來推送的桨昙。
      
    • 新建php文件(保存名字為push.php)

      <?php
      
      // Put your device token here (without spaces):
      $deviceToken = '這里填寫手機注冊的devideToken';
          
      $passphrase = '這里填寫導(dǎo)出p12文件的密碼';
      
      // Put your alert message here:
      $message = '要推送的內(nèi)容';
      $info =  array("id"=>"1","address"=>"IANA"); //自己寫的一個info字典(自己后臺可以自定義)
      
      ////////////////////////////////////////////////////////////////////////////////
      
      $ctx = stream_context_create();
      stream_context_set_option($ctx, 'ssl', 'local_cert', 'ck.pem'); //這里的ck.pem需要和導(dǎo)出的證書名字要一致
      stream_context_set_option($ctx, 'ssl', 'passphrase', $passphrase);
      stream_context_set_option($ctx, 'ssl', 'verify_peer', false);
      
      // Open a connection to the APNS server
      //ssl://gateway.sandbox.push.apple.com:2195(測試環(huán)境)
      //ssl://gateway.push.apple.com:2195(生產(chǎn)環(huán)境)
      $fp = stream_socket_client('ssl://gateway.sandbox.push.apple.com:2195', $err,$errstr, 60, STREAM_CLIENT_CONNECT|STREAM_CLIENT_PERSISTENT, $ctx);
      
      if (!$fp)
          exit("Failed to connect: $err $errstr" . PHP_EOL);
      
      echo 'Connected to APNS' . PHP_EOL;
      
      // Create the payload body
      $body['aps'] = array(
          'content-available' => '1',
          'alert' => $message,
          'sound' => 'weixin.m4a',
          'badge' => 0,
          'info'=> $info,
          );
      
      // Encode the payload as JSON
      $payload = json_encode($body);
      
      // Build the binary notification
      $msg = chr(0) . pack('n', 32) . pack('H*', $deviceToken) . pack('n', strlen($payload)) . $payload;
      
      // Send it to the server
      $result = fwrite($fp, $msg, strlen($msg));
      
      if (!$result)
          echo 'Message not delivered' . PHP_EOL;
      else
          echo 'Message successfully delivered' . PHP_EOL;
      
      // Close the connection to the server
      fclose($fp);   
      ?>
      
      
    • 推送測試

      • ck.pem 文件和push.php 放在同一個文件夾下(必須)

      • 一般測試VoIP推送的穩(wěn)定性最好是通過Hoc證書打包在生產(chǎn)環(huán)境中測試

      • 打開terminal 检号,cd到 push.php 文件目錄下

      • 輸入:php push.php (不出意外就可以收到推送啦)

        voip_03.png
  • 補充點(摘錄 iOS利用voip push實現(xiàn)類似微信(QQ)電話連續(xù)響鈴效果

    • 當app要上傳App Store時,請在iTunes connect上傳頁面右下角備注中填寫你用到VoIP推送的原因蛙酪,附加上音視頻呼叫用到VoIP推送功能的demo演示鏈接齐苛,演示demo必須提供呼出和呼入功能,demo我一般上傳到優(yōu)酷桂塞。
    • 經(jīng)過大量測試凹蜂,VoIP當應(yīng)用被殺死(雙擊劃掉)并且黑屏大部分情況都能收到推送,很小的情況會收不到推送消息阁危,經(jīng)測試可能跟手機電量消耗還有信號強弱有關(guān)玛痊。 再強調(diào)一遍,測試穩(wěn)定性請在生產(chǎn)環(huán)境測試狂打。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末擂煞,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子菱父,更是在濱河造成了極大的恐慌颈娜,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浙宜,死亡現(xiàn)場離奇詭異官辽,居然都是意外死亡,警方通過查閱死者的電腦和手機粟瞬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門同仆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人裙品,你說我怎么就攤上這事俗批。” “怎么了市怎?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵岁忘,是天一觀的道長。 經(jīng)常有香客問我区匠,道長干像,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任驰弄,我火速辦了婚禮麻汰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘戚篙。我一直安慰自己五鲫,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布岔擂。 她就那樣靜靜地躺著位喂,像睡著了一般。 火紅的嫁衣襯著肌膚如雪乱灵。 梳的紋絲不亂的頭發(fā)上忆某,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音阔蛉,去河邊找鬼弃舒。 笑死,一個胖子當著我的面吹牛状原,可吹牛的內(nèi)容都是我干的聋呢。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼颠区,長吁一口氣:“原來是場噩夢啊……” “哼削锰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起毕莱,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤器贩,失蹤者是張志新(化名)和其女友劉穎颅夺,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蛹稍,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡吧黄,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了唆姐。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拗慨。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖奉芦,靈堂內(nèi)的尸體忽然破棺而出赵抢,到底是詐尸還是另有隱情,我是刑警寧澤声功,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布烦却,位于F島的核電站,受9級特大地震影響先巴,放射性物質(zhì)發(fā)生泄漏短绸。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一筹裕、第九天 我趴在偏房一處隱蔽的房頂上張望醋闭。 院中可真熱鬧,春花似錦朝卒、人聲如沸证逻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽囚企。三九已至,卻和暖如春瑞眼,著一層夾襖步出監(jiān)牢的瞬間龙宏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工伤疙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留银酗,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓徒像,卻偏偏與公主長得像黍特,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子锯蛀,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355