前言
本文大部分參考 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)境測試狂打。