{
"from":"1",
"to":"2",
"type":"webrtc",
"timestamp":"2023-05-17T05:52:01.123Z",
"userAgent":"platform/version;outdoor/unit/version",
"sessionId":"xxxxx",
"payload":"ssss",
"statusResponse":{
"statusCode":404,
"description":"user not found"
}
}
from:消息源的用户id
to:消息目标用户id
type:分为 webrtc,message两种,不同的type对应不同的payload,解析的时候请注意
timestamp: 消息发送时间,ISO8601 format
userAgent: 见Useragent定义。
sessionId:会话标识,有时候这个标识很有用,可以标识一组信息的归属
payload:经过base64编码的负载内容
以下内容客户端不要设置,这个是服务器专有回复,而且只在发生转发错误得时候才会设置
statusResponse:服务器返回的状态,不是对端返回的。而且只返回给发送端。
statusCode:404:对方离线,400:json格式有误,比如to与from相同
useragent分为两段:
platform/version:表明平台和版本,比如android/11.0, linux/1.0,web/1.0
room/pad/version,room/mobile/version,表示室内机/室内机子类型/室内机app版本,
如果是室外机:outdoor/unit/version, outdoor/center/version,outdoor/wall/version,outdoor/villa/version,分别表示单元门口机,中心管理机,围墙机,别墅门口机
其他类型可扩展。
{
"type":"SDP_OFFER",
"server":[
{
"username":"",
"credential":"",
"ttl":86400, #以秒为单位的这个账号的有效期。从服务器返回的时候开始计时间。注意,ttl只影响到turn服务器,对stun没有影响。
#应为stun服务器负载极低,一般使用google免费的即可。而turn服务器用来中转,必须注意账号的安全性。
#在使用的时候,如果账号存活时间超过 ttl-3600秒就算过期,需要重新获取。服务器一般默认配置24小时有效期
"urls":[
"stun:stun.l.google.com:19302" #这个是google免费的stun服务器,这里没有配置turn服务器,因为后期再部署。
]
}
],
"jsep":"base64"
}
type:表示消息类型:SDP_OFFER:表示向对方提供offer,SDP_ANSWER:表示向对方提供answer,ICE_CANDIDATE:表示ice协商消息
server:当type=SDP_OFFER 时需要提供给对方本次所用的ICE服务器信息。其他类型不需要,可以不提供。
参考: https://developer.mozilla.org/en-US/docs/Web/API/RTCIceServer/urls
jsep:经过base64编码的webrtc sdp或者ice详细信息。这个由webrtc生成,可以直接给webrtc。这里经由base64编码,是因为上层一般不会关心里面具体的内容,直接传给
webrtc即可。
消息负载,为实现门禁的状态交互,也可以用于文字聊天
{
"type":"消息类型",
"encode":true,
"data":"",
"ack":true,
"result":0
}
type: 包含如下定义:
---invite:表示想要邀请对方对讲,接受方在第一个通道收到之后需要回复同意或者busy,其他通道收到相同的会话则忽略
---pickup:告诉对方,我方已经摘机,不用回复
---hangup:挂断,无需回复
---unlock:开锁,需要回复
---elevator:梯控,召梯:"data":"1", 授权:"data":"2", 需要回复
---text:文本消息,可以传递任意内容
---ping:用来探测对方是否活着,对方收到回复ping,ack=true
encode:表示data字段是否base64编码,也用于发送方指示data字段是否需要编码,有些短文本,可以不编码,如有复杂格式需要编码
data:正文
ack:是否是确认包,有些包需要对方确认
result:命令执行结果,如下:
enum MessageResult {
kResultOK, //for everthing
kResultBusy,//for invite,当我们无法接受对方的邀请,可以回复busy,当然也可以不理,不过快速的回复加快对端的邀请过程,避免无意义的等待。
kResultTimeout, //for invite,当接受对方的邀请之后,收到对端的挂断,错误代码为这个,则表示回复已经晚了,对方已经结束了邀请进入了会话状态。立即结束会话即可
kResultAbort,//for hangup,这是一个特殊的错误码,发起端收到这个错误代码,如果当前回复无人接听,则立即中止所有呼叫。场景就是,如果室内不想接听,直接挂断,那么
室外机将中止呼叫,不再呼叫其他设备。这必须是有人干预的场景,如果室内机无人取消,最终超时应当发送kResultOK,室外机将继续等待其他设备接听
kResultAnsweredOnOtherDevice, //for hangup,表明会话已经在其他设备上接听了,data中包含接听设备的信息
kResultWebRtcError, //start webrtc failed , 启动webrc失败,会话将中止
kResultBadCredentials,//for session not match,or other ,目前没有用到,可能用在开锁召梯等功能,表示无权操作
kResultNetworkError, //for everything: local error,内部错误,不会再网络上给对方
};
室外机呼叫逻辑:
室外机一般由三种组合呼叫逻辑,首先是pad室内机呼叫,其次是移动端呼叫,最后是电话呼叫。三种逻辑由策略来配置,其中,pad室内机呼叫是立即执行,无需任何等待,
移动端app呼叫需要指定一个延时,比如30秒。正常情况下,会在会话开始的30秒后开始向移动端推送呼叫请求,然后等待移动端回复。
当移动端和室内机都超时之后,才开始呼叫电话(因为技术原因,此电话呼叫和其他两种不能共存,必须在前二者结束之后才能开始电话呼叫)。但呼叫室内机和移动端可以同时,
本质上都是webrtc呼叫。电话呼叫虽然也是webrtc底层技术,但二者不是一回事,共存时导致录音设备冲突。
首先,室外机会通过点对点的http向指定的用户id发送http请求(接口在/api/webrtc),当室外机获取到iceserverlist的时候,将通过websocket服务器向指定设备发送请求。
二者之间有微小的时间差,通过websocket发送请求之前,会检查p2p有没有接收,如果接受了,则不会从websocket发送请求。同时走两个路由发送请求的可能性是存在的。
室内机两个路由都可以接受之,室外机只会选择最先收到回复的路由。被呼叫端回复invite,ack=true,如果result=ResultOK,表示接受,其他的表示拒绝,室外机将忽略。
室内机必须回复invite,可以result=ResultBusy,表明自己此刻忙碌,比如正在其他会话。不回复的话将拖延会话进度。
当室外机收到invite ack,且ResultOK之后,立即向接受方发送offer,注意,接受方无需向服务器请求ice 服务器信息,offer中已经包含。如果是点对点呼叫,无需ice server信息。
室内机/移动端收到offer之后。建立webrtc peerchannel,并将webrtc生成的answer和ice candidation信息按照invite路由转发给室外机,呼叫建立完成。此刻可以看到室外机视频。
作为webrtc来说,没有早期媒体的概念,一般是在建立连接之前进行呼叫确认,我们这里是模拟sip的早期媒体,接收端需要发送pickup信令,以使得室外机开启声音。
如果室内机发送接听指令,则室外机会挂断除了接听设备之外的其他所有会话。当然,如果接听指令到达时,会话已经被其他设备接听,则回复挂断,result= ResultkAnsweredOnOtherDevice,data中包含接听的userid。
如果室内机没有接听直接挂断,此刻发送hangup,但result=ResultAbort,表示会话立即中止。如果result=ResultOK,则其他会话将保持。
查看监控的室内机按照上述室外机的逻辑处理即可。
通讯双方,一旦发现错误,需要给对方发送hangup,并给出合理的MessageResult
注意,invite中的sessionid在整个会话逻辑中保持不变,这个由发起方来定义。如果给出错误的sessionid,视为无效。
可以有两种方式:
1.通过 websocket,这个需要服务器支持,服务器可以部署在社区里,或者云端,根据场景而定。
2.通过 http 点对点发送。双方均需要侦听在 18080 端口,发送消息直接往对方这个端口发送,从这个端口接收其他端的消息。
如果是别墅方案,这个端口通过ssdp协议约定,如果是普通方案,需要强制约定。 接口定义为: /api/webrtc
一般来说,室内机和室外机两种方式同时支持(除非是不支持点对点连接的场景),其他端只需要支持websocket即可。
X-Genius-ClientId: userid
X-Genius-Key:xxxx
X-Genius-Nonce=16位随机数
X-Genius-Timestamp=2018-01-29T04:43:02.999Z
X-Genius-Signature=, 将所有key(除了Signature,ClientId)按照字典序排序,最后在拼接成 key=value&key=value,算法: hexencode(HMAC-SHA256(message))
key,secret需要通过其他途径获得
室外机发起会话流程:
首先执行邀请操作,这时候可以设置多个通道邀请(上层可以配置一个或者多个通道的名称),总体邀请有效期为10秒(可配置),超时无人应答则视为呼叫失败。
其中,如果配置P2P通道的话,则应该优先发送邀请,其他通道会延迟500毫秒开始发送邀请。如果没有配置P2P通道,则立即向所有通道发送邀请。
室内机可能在一个或者多个通道接收到相同sessionid的邀请,如果有接听条件的话(此刻用户无感知,只要有接听的物理条件即可),立即回复ack包。对于从其他通道的相同sessionid的
邀请应立即拒绝(回复ack,result=busy)。当然也可以置之不理。
室内机一旦接受了邀请,应该创建一个对象来保存这个状态,并且等待室外机发送webrtc的 offer。当然,接受邀请之后应该启动定时器,确保当室外机或者网络异常的情况下,会话过期之后清理现场。
对于每一个室内机标识(可以是房号:单元门口机等,或者唯一的id:别墅门口机),只要收到一个接受邀请的回复,这个室内机的邀请过程立即结束,并立即进入呼叫过程,开始向接受的
终端发送offer。在室内机回复 answer之后,室内机将看到画面。但此刻没有声音,因为室外机默认禁止了声音,需要用户在室内机上点击【接听】按钮,室内机发送pickup指令之后,室外机
使能声音,双方都可以如此处理。这不是webrtc默认的逻辑,是经过修改的以实现可视对讲的场景需求。
在对讲过程中,任意一方均可向对方发送hangup指令,则会话结束。注意,hangup指令无需回复。发送方发送之后立即结束会话,不管对端有没有收到。
在邀请过程中还经常存在另外一种场景,当某个设备接受邀请之后,就收到了挂断指令,原因是消息到达太迟了,会话已经在其他设备上接听了。
室内机发送会话流程(比如查看室外机监控,或者呼叫其他终端):
这个过程和室外机发送邀请过程一致。
补充一点,对于P2P通道,邀请会立即发送,对于其他通道,发送端首先场景获取ice server列表(从本地缓存或者服务器),只有成功获取到ice sever之后才会发送邀请。
因为没有ice server通话无法建立。
ICE 服务器信息对通话至关重要,没有的话无法建立会话(除了P2P通道,无需ice服务器信息)。一般来所,turn服务器分配的账号都有一定的期限(ttl),如果在设备端需要维护这个
有效期。
具体可以参考下面的源码
我们不使用webrtc数据通道传输指令,因为有些指令并不依赖webrtc通话
webrtc有着固定的呼叫逻辑。对webrtc来说,当我们需要呼叫对方的时候,按照如下步骤进行:
1)创建webrtc peerconnection,一般首先需要设置 ice server list,如上所示,包含stun和turn服务器信息。其中stun服务器一般不需要账号,但turn服务器一般需要认证。
2)peerconnection 生成 offer,就是jsep中描述的信息,通过websocket或者其他通道传递给被叫方。
3)被叫方收到offer之后,解析ice serverlist,然后初始化peerconnection,然后将jsep传递给peerconnection
4)被叫方根据offer生成对应的answer,通过网络传给主叫方
5)主被双方开始交换ice信息,寻找合适的媒体传输通道。如果点对点不成功,最终会经过turn服务器进行中转。如果配置turn服务器,呼叫总是能够成功。
6)一旦传输通道协商好之后,通话正式开始。
webrtc本身没有早期媒体(就是在接听之前传输视频)的能力,所以,要对webrtc自己的逻辑适当的定制,具体如下:
1)在接听之前,将webrtc静音,具体是设置 peerconnection.SetAudioPlayout(false),SetAudioRecording(false)
2) 被叫方接听之后,在将上述两个设置为true。
需要注意的是,webrtc不能先协商一个通道(比如视频),然后中途再协商另外一个通道,所以,一开始协商的时候,音视频均需要同时协商。
通过静音的方式模拟接听的效果。
另外,就是视频的单向传输,室外机和室内机基本都是单向传输视频。
主叫方可以指定视频:webrtc::RtpTransceiverDirection::kSendOnly(java或者js中应该有类似设置)
作为被叫方不能指定。但是,可以在给peerconnection设置主叫方的offer,修改视频的媒体方向。然后再生成answer。
记住,单向视频可以提高设备多路会话的承载能力。
信令服务器只关心外层协议,如文章开始所示。只要保持外层协议不变,可以自行扩展协议的具体内容。
具体来说,signal服务器通过设备端登录的时候提供的clientid,标识连接的身份。并通过协议中的to字段,转发给指定的连接。
如果to指定的连接不存在,则通过statusResponse返回错误给发送方。如果转发成功,不会给发送方发送回复.
客户端一旦发现收到的协议包中存在statusResponse节点,一定是sigal服务器回复的,是对最近所发包的状态回复。通讯双方交换的包不能包含statusResponse节点。
signal服务器没有数据库,也不依赖其他的服务(除了认证),所以,服务器本身的状态极其简单,客户端掉线之后,直接删除。因而,signal服务器不判断to指定的用户
是否合法。
signal服务器对客户端登录时候的认证。
客户端登录signal服务器需要拥有一个key和一个secret,这个可以通过其他业务系统获取。secret用来对登录信息进行加密,加密算法参考别墅门口机。
signal服务器预先配置一个认证服务器的接口地址。将使用 key 和clientid请求对方,以获得secret。
然后用secret对登录信息进行解密。如果解密成功,则允许登录,否则将中断连接。
第三方认证服务器需要提供认证接口(POST),
请求方发送json格式:
{
"X-Genius-ClientId":"客户端的id",
"X-Genius-Key":"客户端指定的key"
}
认证方返回:
{
"code":200,表示成功,其他表示失败
"secret":"xxx",密钥
}
为减少对认证服务器的压力,提高登录效率,signal服务器有cache(是内存cache,不是redis),可以指定secret的有效时间。
我们支持一个P2P通道和多个Server通道,每个通道以不同的名称区别
在呼叫的时候,我们可以配置一个或者多个通道,具体的场景如下:
对于室外机,可以呼叫P2P通道,也可以同时呼叫社区内网的server通道,也可以呼叫位于云端的通道(包括室内机和手机端)
对于室内机,可以通过云端上线,也可以通过社区内网服务器上线,也可二者均可以上线,这样布线方式非常灵活。
对于移动端,一般通过云端上线即可。
通过invite机制,我们可以寻找到最佳的呼叫通道,哪个通道反馈最快就选择哪个
室内机和室外机均启动http server,侦听到双方共识的端口(别墅门口机可以通过ssdp协议协商)。httpserver本身可以用于其他用途,但我们保留 /api/webrtc(POST方法,且
无任何认证)接口专门
用于webrtc信令交互。一般而言,为了处理方便,我们不适用request/response模式,向对方发送信令,对方只需要返回200即可,不要在请求中发送回复信令。
如果要发送新的信令或者ack包,请向对端发送request操作。
/api/ping专门用来探测对方是否存活。不适用于对讲,对讲使用invite来探测,
#ifndef WEBRTC_CORE_WEBRTC_MESSAGE_H_
#define WEBRTC_CORE_WEBRTC_MESSAGE_H_
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/time/time.h"
#include "base/values.h"
#include "base/optional.h"
namespace webrtc {
//https://github.com/housekeeper-software/tech/wiki/WebRtc-%E4%BF%A1%E4%BB%A4%E6%9C%8D%E5%8A%A1%E5%99%A8%E5%8D%8F%E8%AE%AE%E6%AD%A3%E5%BC%8F%E7%89%88
extern const char kTypeWebRtc[];
extern const char kTypeMessage[];
//subtype with kTypeWebRtc
extern const char kSDPOffer[];
extern const char kSDPAnswer[];
extern const char kICECandidate[];
//subtype with kTypeMessage
extern const char kTypeInvite[];
extern const char kTypePickup[];
extern const char kTypeHangup[];
extern const char kTypeUnlock[];
extern const char kTypeElevator[];
extern const char kTypeText[];
extern const char kTypePing[];
extern const char kTelephoneUserAgent[];
class IceServer {
public:
IceServer();
~IceServer();
IceServer(const IceServer &);
IceServer &operator=(const IceServer &);
bool IsExpired() const;
bool IsTurn() const;
std::string username;
std::string credential;
int ttl;
std::vector<std::string> urls;
base::TimeTicks expired_time;
};
class IceServerList {
public:
IceServerList();
~IceServerList();
IceServerList(const IceServerList &);
IceServerList &operator=(const IceServerList &);
static bool FromString(const std::string &json, IceServerList *ice_server_list);
static bool FromJson(const base::DictionaryValue *root, IceServerList *ice_server_list);
std::string ToJson() const;
void ToJson(base::DictionaryValue *root) const;
void AddServer(const IceServer &server);
bool IsExpired() const;
bool empty() const { return server_list.empty(); }
std::vector<IceServer> server_list;
};
enum class DeviceType : int {
DeviceTypeUnknown,
DeviceTypePad,
DeviceTypeMobile,
DeviceTypeUnit,
DeviceTypeCenter,
DeviceTypeWall,
DeviceTypeVilla,
DeviceTypeTelephone,
};
class UserAgent {
public:
UserAgent();
UserAgent(const std::string &str);
~UserAgent();
UserAgent(const UserAgent &);
UserAgent &operator=(const UserAgent &);
static bool FromString(const std::string &str, UserAgent *user_agent);
std::string ToString() const;
DeviceType type() const;
std::string platform;
std::string platform_version;
std::string device_type;
std::string device_subtype;
std::string device_version;
private:
bool Parse(const std::string &str);
};
/*
{
"type":"SDP_OFFER",
"server":[
{
"userName":"",
"password":"",
"ttl":86400,
"urls":[
"stun:stun.l.google.com:19302"
]
}
],
"jsep":"base64"
}
*/
class WebRtcMessage {
public:
WebRtcMessage();
~WebRtcMessage();
WebRtcMessage(const WebRtcMessage &other);
WebRtcMessage &operator=(const WebRtcMessage &other);
std::string ToJson() const;
std::string ToString() const;
static bool FromString(const std::string &json, WebRtcMessage *message);
std::string type;
std::string jsep;
IceServerList ice_server_list;
};
enum MessageResult {
kResultOK, //for everthing
kResultBusy,//for invite
kResultTimeout, //for invite
kResultAbort,//for hangup
kResultAnsweredOnOtherDevice, //for hangup
kResultWebRtcError, //start webrtc failed
kResultBadCredentials,//for session not match,or other
kResultNetworkError, //for everything: local error
};
/*
{
"type":"消息类型",
"encode":true,
"data":"",
"ack":true,
"result":0
}
*/
class CommonMessage {
public:
CommonMessage();
~CommonMessage();
explicit CommonMessage(const std::string &type, bool ack, int result,
const std::string &data = {}, bool encode = false);
CommonMessage(const CommonMessage &);
CommonMessage &operator=(const CommonMessage &);
std::string ToString() const;
std::string ToJson() const;
bool IsIntercomMessage() const;
static bool FromString(const std::string &json, CommonMessage *message);
std::string type;
bool ack;
int result;
std::string data;
private:
bool encode;
};
enum StatusCode {
StatusBadRequest = 400,
StatusNotFound = 404,
StatusServiceUnavailable = 503,
};
/*
{
"from":"1",
"to":"2",
"type":"webrtc",
"timestamp":"2023-05-17T05:52:01.123Z",
"userAgent":"platform/version;app/version",
"sessionId":"xxxxx",
"payload":"ssss",
"statusResponse":{
"statusCode":404,
"description":"user not found"
}
}
*/
class SignalingMessage {
public:
SignalingMessage();
~SignalingMessage();
SignalingMessage(const SignalingMessage &);
SignalingMessage &operator=(const SignalingMessage &);
static bool FromString(const std::string &json, SignalingMessage *message);
static bool FromByteArray(const uint8_t *data, size_t len, SignalingMessage *message);
std::string ToString() const;
std::string ToJson() const;
std::string PeerId() const;
bool OK() const;
bool IsWebRtcMessage() const;
bool IsCommonMessage() const;
bool IsValidCommonMessage() const;
bool IsValidWebRtcMessage() const;
bool IsValidInviteMessage() const;
bool IsValidInviteAckMessage() const;
bool IsSessionMatch(const std::string &sessionid) const;
SignalingMessage ServiceUnavailable() const;
void ClearError();
std::string from; //sender userid
std::string to; //target userid
std::string type;
base::Time timestamp;
UserAgent user_agent;
std::string sessiond_id;
base::Optional<WebRtcMessage> webrtc_message;
base::Optional<CommonMessage> common_message;
base::Optional<int> status_code; //see StatusCode
base::Optional<std::string> status_description;
};
}
#endif //WEBRTC_CORE_WEBRTC_MESSAGE_H_
///////////////////////////////////////////////
//这里是具体实现
#include "webrtc/core/webrtc_message.h"
#include "base/values.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.h"
#include "base/base64.h"
#include "base/time/time_to_iso8601.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/strings/string_util.h"
namespace webrtc {
const char kTypeWebRtc[] = "webrtc";
const char kTypeMessage[] = "message";
const char kSDPOffer[] = "SDP_OFFER";
const char kSDPAnswer[] = "SDP_ANSWER";
const char kICECandidate[] = "ICE_CANDIDATE";
const char kTypeInvite[] = "invite";
const char kTypePickup[] = "pickup";
const char kTypeHangup[] = "hangup";
const char kTypeUnlock[] = "unlock";
const char kTypeElevator[] = "elevator";
const char kTypeText[] = "text";
const char kTypePing[] = "ping";
const char kTelephoneUserAgent[] = "Unknown/1.0;room/telephone/1.0";
IceServer::IceServer()
: ttl(0) {}
IceServer::~IceServer() = default;
IceServer::IceServer(const IceServer &) = default;
IceServer &IceServer::operator=(const IceServer &) = default;
bool IceServer::IsExpired() const {
return expired_time > base::TimeTicks::Now() - base::TimeDelta::FromHours(1);
}
bool IceServer::IsTurn() const {
if (urls.empty()) return false;
return base::StartsWith(urls[0], "turn:", base::CompareCase::INSENSITIVE_ASCII);
}
IceServerList::IceServerList() = default;
IceServerList::~IceServerList() = default;
IceServerList::IceServerList(const IceServerList &) = default;
IceServerList &IceServerList::operator=(const IceServerList &) = default;
std::string IceServerList::ToJson() const {
base::ListValue servers;
for (const auto &i : server_list) {
std::unique_ptr<base::DictionaryValue> ice(new base::DictionaryValue());
ice->SetString("username", i.username);
ice->SetString("credential", i.credential);
ice->SetInteger("ttl", i.ttl);
std::unique_ptr<base::ListValue> urls(new base::ListValue());
for (const auto &j : i.urls) {
urls->AppendString(j);
}
ice->Set("urls", std::move(urls));
servers.Append(std::move(ice));
}
std::string json;
base::JSONWriter::Write(servers, &json);
return json;
}
void IceServerList::AddServer(const IceServer &server) {
server_list.push_back(server);
}
bool IceServerList::IsExpired() const {
for (const auto &i : server_list) {
if (i.IsTurn() && i.IsExpired())
return true;
}
return false;
}
bool IceServerList::FromString(const std::string &json, IceServerList *ice_server_list) {
auto value = base::JSONReader::ReadDeprecated(json);
if (!value) return false;
const base::DictionaryValue *root = nullptr;
if (!value->GetAsDictionary(&root) || !root)
return false;
return FromJson(root, ice_server_list);
}
//static
bool IceServerList::FromJson(const base::DictionaryValue *value, IceServerList *ice_server_list) {
const base::ListValue *root = nullptr;
if (!value->GetList("server", &root))
return false;
for (size_t i = 0; i < root->GetSize(); ++i) {
const base::DictionaryValue *item = nullptr;
if (!root->GetDictionary(i, &item) || !item)
continue;
IceServer server;
const base::ListValue *urls = nullptr;
if (item->GetList("urls", &urls) && urls) {
for (size_t j = 0; j < urls->GetSize(); ++j) {
std::string str;
urls->GetString(j, &str);
if (!str.empty()) {
server.urls.emplace_back(std::move(str));
}
}
}
item->GetString("username", &server.username);
item->GetString("credential", &server.credential);
item->GetInteger("ttl", &server.ttl);
if (server.ttl > 0) {
server.expired_time = base::TimeTicks::Now() + base::TimeDelta::FromSeconds(server.ttl);
}
ice_server_list->AddServer(server);
}
return true;
}
void IceServerList::ToJson(base::DictionaryValue *root) const {
std::unique_ptr<base::ListValue> servers(new base::ListValue());
for (const auto &i : server_list) {
std::unique_ptr<base::DictionaryValue> ice(new base::DictionaryValue());
ice->SetString("username", i.username);
ice->SetString("credential", i.credential);
ice->SetInteger("ttl", i.ttl);
std::unique_ptr<base::ListValue> urls(new base::ListValue());
for (const auto &j : i.urls) {
urls->AppendString(j);
}
ice->Set("urls", std::move(urls));
servers->Append(std::move(ice));
}
root->Set("server", std::move(servers));
}
UserAgent::UserAgent() = default;
UserAgent::~UserAgent() = default;
UserAgent::UserAgent(const UserAgent &) = default;
UserAgent &UserAgent::operator=(const UserAgent &) = default;
UserAgent::UserAgent(const std::string &str) {
Parse(str);
}
bool UserAgent::Parse(const std::string &str) {
std::vector<std::string> v = base::SplitString(str, ";", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
if (v.size() < 2)
return false;
auto platform_vector = base::SplitString(v[0], "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
if (platform_vector.size() < 2)
return false;
platform = platform_vector[0];
platform_version = platform_vector[1];
auto device = base::SplitString(v[1], "/", base::KEEP_WHITESPACE, base::SPLIT_WANT_ALL);
if (device.size() < 3)
return false;
device_type = device[0];
device_subtype = device[1];
device_version = device[2];
return true;
}
//static
bool UserAgent::FromString(const std::string &str, UserAgent *user_agent) {
UserAgent ua;
if (!ua.Parse(str))
return false;
*user_agent = ua;
return true;
}
std::string UserAgent::ToString() const {
if (platform.empty() && platform_version.empty() && device_type.empty() && device_subtype.empty()
&& device_version.empty())
return {};
return base::StringPrintf("%s/%s;%s/%s/%s", platform.c_str(),
platform_version.c_str(),
device_type.c_str(),
device_subtype.c_str(),
device_version.c_str());
}
DeviceType UserAgent::type() const {
if (base::CompareCaseInsensitiveASCII(device_type, "room") == 0) {
if (base::CompareCaseInsensitiveASCII(device_subtype, "pad") == 0) {
return DeviceType::DeviceTypePad;
}
if (base::CompareCaseInsensitiveASCII(device_subtype, "mobile") == 0) {
return DeviceType::DeviceTypeMobile;
}
if (base::CompareCaseInsensitiveASCII(device_subtype, "telephone") == 0) {
return DeviceType::DeviceTypeTelephone;
}
} else if (base::CompareCaseInsensitiveASCII(device_type, "outdoor") == 0) {
if (base::CompareCaseInsensitiveASCII(device_subtype, "unit") == 0) {
return DeviceType::DeviceTypeUnit;
}
if (base::CompareCaseInsensitiveASCII(device_subtype, "center") == 0) {
return DeviceType::DeviceTypeCenter;
}
if (base::CompareCaseInsensitiveASCII(device_subtype, "wall") == 0) {
return DeviceType::DeviceTypeWall;
}
if (base::CompareCaseInsensitiveASCII(device_subtype, "villa") == 0) {
return DeviceType::DeviceTypeVilla;
}
}
return DeviceType::DeviceTypeUnknown;
}
WebRtcMessage::WebRtcMessage() = default;
WebRtcMessage::~WebRtcMessage() = default;
WebRtcMessage::WebRtcMessage(const WebRtcMessage &other) = default;
WebRtcMessage &WebRtcMessage::operator=(const WebRtcMessage &other) = default;
bool WebRtcMessage::FromString(const std::string &json, WebRtcMessage *message) {
auto value = base::JSONReader::ReadDeprecated(json);
if (!value) return false;
const base::DictionaryValue *root = nullptr;
if (!value->GetAsDictionary(&root))
return false;
root->GetString("type", &message->type);
IceServerList::FromJson(root, &message->ice_server_list);
if (root->GetString("jsep", &message->jsep)) {
base::Base64Decode(message->jsep, &message->jsep);
}
return true;
}
std::string WebRtcMessage::ToJson() const {
base::DictionaryValue root;
root.SetString("type", type);
if (!jsep.empty()) {
std::string str;
base::Base64Encode(jsep, &str);
root.SetString("jsep", str);
}
if (!ice_server_list.empty()) {
ice_server_list.ToJson(&root);
}
std::string json;
base::JSONWriter::Write(root, &json);
return json;
}
std::string WebRtcMessage::ToString() const {
return base::StringPrintf("TYPE:%s\nJSEP:%s\nICE:%s\n",
type.c_str(),
jsep.c_str(),
ice_server_list.ToJson().c_str());
}
CommonMessage::CommonMessage()
: ack(false),
result(kResultOK),
encode(false) {}
CommonMessage::~CommonMessage() = default;
CommonMessage::CommonMessage(const CommonMessage &) = default;
CommonMessage &CommonMessage::operator=(const CommonMessage &) = default;
CommonMessage::CommonMessage(const std::string &type, bool ack, int result,
const std::string &data, bool encode)
: type(type),
ack(ack),
result(result),
data(data),
encode(encode) {}
//static
bool CommonMessage::FromString(const std::string &json, CommonMessage *message) {
auto value = base::JSONReader::ReadDeprecated(json);
if (!value) return false;
const base::DictionaryValue *root = nullptr;
if (!value->GetAsDictionary(&root))
return false;
root->GetString("type", &message->type);
root->GetBoolean("ack", &message->ack);
root->GetInteger("result", &message->result);
root->GetBoolean("encode", &message->encode);
root->GetString("data", &message->data);
if (message->encode && !message->data.empty()) {
base::Base64Decode(message->data, &message->data);
}
return true;
}
std::string CommonMessage::ToJson() const {
base::DictionaryValue root;
root.SetString("type", type);
root.SetBoolean("ack", ack);
root.SetInteger("result", result);
root.SetBoolean("encode", encode);
if (encode && !data.empty()) {
std::string str;
base::Base64Encode(data, &str);
root.SetString("data", str);
} else {
root.SetString("data", data);
}
std::string json;
base::JSONWriter::Write(root, &json);
return json;
}
std::string CommonMessage::ToString() const {
return base::StringPrintf("TYPE:%s\nACK=%s\nRESULT=%d\nDATA:%s\n",
type.c_str(),
ack ? "true" : "false",
result,
data.c_str());
}
bool CommonMessage::IsIntercomMessage() const {
return base::CompareCaseInsensitiveASCII(type, kTypeInvite) == 0 ||
base::CompareCaseInsensitiveASCII(type, kTypeHangup) == 0 ||
base::CompareCaseInsensitiveASCII(type, kTypePickup) == 0;
}
SignalingMessage::SignalingMessage()
: timestamp(base::Time::Now()) {}
SignalingMessage::~SignalingMessage() = default;
SignalingMessage::SignalingMessage(const SignalingMessage &) = default;
SignalingMessage &SignalingMessage::operator=(const SignalingMessage &) = default;
bool SignalingMessage::FromByteArray(const uint8_t *data, size_t len, SignalingMessage *message) {
std::string json(data, data + len);
return FromString(json, message);
}
bool SignalingMessage::FromString(const std::string &json, SignalingMessage *message) {
auto value = base::JSONReader::ReadDeprecated(json);
if (!value) return false;
const base::DictionaryValue *root = nullptr;
if (!value->GetAsDictionary(&root))
return false;
root->GetString("from", &message->from);
root->GetString("to", &message->to);
root->GetString("type", &message->type);
std::string str;
if (root->GetString("userAgent", &str)) {
UserAgent::FromString(str, &message->user_agent);
}
root->GetString("sessionId", &message->sessiond_id);
if (root->GetString("payload", &str)) {
base::Base64Decode(str, &str);
if (base::CompareCaseInsensitiveASCII(message->type, kTypeWebRtc) == 0) {
WebRtcMessage webrtc_message;
if (WebRtcMessage::FromString(str, &webrtc_message)) {
message->webrtc_message = base::make_optional<WebRtcMessage>(std::move(webrtc_message));
} else {
LOG(ERROR) << "Failed to parse webrtc message(" << str << ")";
}
} else if (base::CompareCaseInsensitiveASCII(message->type, kTypeMessage) == 0) {
CommonMessage common_message;
if (CommonMessage::FromString(str, &common_message)) {
message->common_message = base::make_optional<CommonMessage>(std::move(common_message));
} else {
LOG(ERROR) << "Failed to parse common message(" << str << ")";
}
}
}
if (root->GetString("timestamp", &str)) {
if (!base::Time::FromUTCString(str.c_str(), &message->timestamp)) {
LOG(ERROR) << "Failed to parse timestamp(" << str << ")";
}
}
const base::DictionaryValue *response = nullptr;
if (root->GetDictionary("statusResponse", &response)) {
int v = 0;
if (response->GetInteger("statusCode", &v)) {
message->status_code = base::make_optional(v);
}
std::string description;
if (response->GetString("description", &description)) {
message->status_description = base::make_optional(description);
}
LOG(ERROR) << "Failed to send message to(" << message->to
<< "), code(" << v << "),description(" << description << ")";
}
return true;
}
std::string SignalingMessage::ToJson() const {
base::DictionaryValue root;
root.SetString("from", from);
root.SetString("to", to);
root.SetString("type", type);
root.SetString("userAgent", user_agent.ToString());
root.SetString("sessionId", sessiond_id);
std::string payload;
if (base::CompareCaseInsensitiveASCII(type, kTypeWebRtc) == 0) {
if (webrtc_message) {
payload = webrtc_message->ToJson();
}
} else if (base::CompareCaseInsensitiveASCII(type, kTypeMessage) == 0) {
if (common_message) {
payload = common_message->ToJson();
}
}
if (!payload.empty()) {
base::Base64Encode(payload, &payload);
root.SetString("payload", payload);
}
root.SetString("timestamp", base::TimeToISO8601(timestamp));
if (status_code) {
std::unique_ptr<base::DictionaryValue> status(new base::DictionaryValue());
status->SetInteger("statusCode", *status_code);
if (status_description) {
status->SetString("description", *status_description);
}
root.Set("statusResponse", std::move(status));
}
std::string json;
base::JSONWriter::Write(root, &json);
return json;
}
std::string SignalingMessage::ToString() const {
std::string str = base::StringPrintf("\nFROM:%s\nTO:%s\nTYPE:%s\nUSERAGENT:%s\nSESSION:%s\nTIMESTAMP:%s",
from.c_str(), to.c_str(), type.c_str(), user_agent.ToString().c_str(),
sessiond_id.c_str(), base::TimeToISO8601(timestamp).c_str());
if (!OK()) {
base::StringAppendF(&str, "\nCODE:%d\nDESCRIPTION:%s",
*status_code,
status_description.has_value() ? (*status_description).c_str() : "");
}
if (webrtc_message) {
base::StringAppendF(&str, "\nMESSAGE:%s", webrtc_message->ToString().c_str());
} else if (common_message) {
base::StringAppendF(&str, "\nMESSAGE:%s", common_message->ToString().c_str());
}
return str;
}
std::string SignalingMessage::PeerId() const {
if (status_code) return to;
return from;
}
bool SignalingMessage::OK() const {
return !status_code;
}
bool SignalingMessage::IsWebRtcMessage() const {
return base::CompareCaseInsensitiveASCII(type, kTypeWebRtc) == 0;
}
bool SignalingMessage::IsCommonMessage() const {
return base::CompareCaseInsensitiveASCII(type, kTypeMessage) == 0;
}
bool SignalingMessage::IsValidCommonMessage() const {
return IsCommonMessage() && common_message;
}
bool SignalingMessage::IsValidWebRtcMessage() const {
return IsWebRtcMessage() && webrtc_message;
}
bool SignalingMessage::IsValidInviteMessage() const {
return OK() && IsValidCommonMessage() &&
base::CompareCaseInsensitiveASCII(common_message->type, kTypeInvite) == 0 &&
!common_message->ack;
}
bool SignalingMessage::IsValidInviteAckMessage() const {
return OK() && IsValidCommonMessage() &&
base::CompareCaseInsensitiveASCII(common_message->type, kTypeInvite) == 0 &&
common_message->ack;
}
bool SignalingMessage::IsSessionMatch(const std::string &sessionid) const {
return sessiond_id == sessionid;
}
SignalingMessage SignalingMessage::ServiceUnavailable() const {
SignalingMessage response = *this;
response.status_code = base::make_optional<int>(StatusServiceUnavailable);
response.status_description = base::make_optional<std::string>("server offline");
return response;
}
void SignalingMessage::ClearError() {
status_code.reset();
status_description.reset();
}
}
头文件
#ifndef WEBRTC_CORE_WEBRTC_MESSAGE_SENDER_H_
#define WEBRTC_CORE_WEBRTC_MESSAGE_SENDER_H_
#include <memory>
#include <string>
#include <set>
#include "base/macros.h"
#include "base/threading/thread_checker.h"
#include "base/memory/ref_counted.h"
#include "webrtc/core/webrtc_message.h"
#include "webrtc/core/webrtc_interface.h"
#include "webrtc/core/webrtc_call_state.h"
namespace webrtc {
class WebRtcMessageSender;
class SignalingMessageBuilder
: public base::RefCountedThreadSafe<SignalingMessageBuilder> {
public:
explicit SignalingMessageBuilder(const std::string &local_userid,
const UserAgent &user_agent);
explicit SignalingMessageBuilder(const std::string &local_userid,
const std::string &user_agent);
SignalingMessage BuildCommonMessage(const std::string &to,
const std::string &session_id,
const CommonMessage &message) const;
SignalingMessage BuildCommonMessage(const std::string &to,
const std::string &session_id,
const char *type,
int result = kResultOK,
bool ack = false,
const std::string &data = {},
bool encode = false) const;
SignalingMessage BuildWebRtcMessage(const std::string &to,
const std::string &session_id,
const WebRtcMessage &message) const;
const std::string local_userid_;
const UserAgent user_agent_;
private:
friend class base::RefCountedThreadSafe<SignalingMessageBuilder>;
virtual ~SignalingMessageBuilder();
DISALLOW_COPY_AND_ASSIGN(SignalingMessageBuilder);
};
class WebRtcMessageSender {
public:
class Builder {
public:
~Builder() = default;
void SendCommonMessage(const char *type,
int result = kResultOK,
bool ack = false,
const std::string &data = {},
bool encode = false);
void SendHangUpMessage(const CallEndReason &reason);
void SendInviteAckMessage(int result);
void SendWebRtcMessage(const WebRtcMessage &message) const;
private:
friend class WebRtcMessageSender;
Builder(WebRtcMessageSender *sender,
const std::string &channel_name,
const std::string &to,
const std::string &session_id);
WebRtcMessageSender *sender_;
const std::string channel_name_;
const std::string to_;
const std::string session_id_;
DISALLOW_COPY_AND_ASSIGN(Builder);
};
explicit WebRtcMessageSender(const scoped_refptr<SignalingMessageBuilder> &message_builder,
WebRtcInterface *webrtc_interface);
virtual ~WebRtcMessageSender() = default;
bool is_hangup_sent() const;
bool is_command_sent(const char *type) const;
scoped_refptr<SignalingMessageBuilder> message_builder() const;
WebRtcInterface *webrtc_interface() const;
Builder Get(const std::string &channel_name,
const std::string &to,
const std::string &session_id);
Builder Get(const WebRtcCallInfo &info);
private:
void SendCommonMessage(const std::string &channel_name,
const std::string &to,
const std::string &session_id,
const char *type,
int result = kResultOK,
bool ack = false,
const std::string &data = {},
bool encode = false);
void SendWebRtcMessage(const std::string &channel_name,
const std::string &to,
const std::string &session_id,
const WebRtcMessage &message) const;
scoped_refptr<SignalingMessageBuilder> message_builder_;
WebRtcInterface *webrtc_interface_;
std::set<std::string> type_history_;
base::ThreadChecker thread_checker_;
DISALLOW_COPY_AND_ASSIGN(WebRtcMessageSender);
};
}
#endif //WEBRTC_CORE_WEBRTC_MESSAGE_SENDER_H_
///////////////////////////////////////////////
//具体实现
#include "webrtc/core/webrtc_message_sender.h"
#include "base/stl_util.h"
namespace webrtc {
SignalingMessageBuilder::SignalingMessageBuilder(const std::string &local_userid,
const UserAgent &user_agent)
: local_userid_(local_userid),
user_agent_(user_agent) {}
SignalingMessageBuilder::SignalingMessageBuilder(const std::string &local_userid,
const std::string &user_agent)
: SignalingMessageBuilder(local_userid, UserAgent(user_agent)) {}
SignalingMessageBuilder::~SignalingMessageBuilder() = default;
SignalingMessage
SignalingMessageBuilder::BuildCommonMessage(const std::string &to,
const std::string &session_id,
const CommonMessage &message) const {
SignalingMessage signaling_message;
signaling_message.type = kTypeMessage;
signaling_message.user_agent = user_agent_;
signaling_message.sessiond_id = session_id;
signaling_message.from = local_userid_;
signaling_message.to = to;
signaling_message.common_message = base::make_optional<CommonMessage>(message);
return signaling_message;
}
SignalingMessage
SignalingMessageBuilder::BuildCommonMessage(const std::string &to,
const std::string &session_id,
const char *type,
int result,
bool ack,
const std::string &data,
bool encode) const {
CommonMessage common_message(type, ack, result, data, encode);
return BuildCommonMessage(to, session_id, common_message);
}
SignalingMessage
SignalingMessageBuilder::BuildWebRtcMessage(const std::string &to,
const std::string &session_id,
const WebRtcMessage &message) const {
SignalingMessage signaling_message;
signaling_message.type = kTypeWebRtc;
signaling_message.user_agent = user_agent_;
signaling_message.sessiond_id = session_id;
signaling_message.from = local_userid_;
signaling_message.to = to;
signaling_message.webrtc_message = base::make_optional<WebRtcMessage>(message);
return signaling_message;
}
WebRtcMessageSender::Builder::Builder(WebRtcMessageSender *sender,
const std::string &channel_name,
const std::string &to,
const std::string &session_id)
: sender_(sender),
channel_name_(channel_name),
to_(to),
session_id_(session_id) {}
void WebRtcMessageSender::Builder::SendCommonMessage(const char *type,
int result,
bool ack,
const std::string &data,
bool encode) {
if (channel_name_.empty()) return;
sender_->SendCommonMessage(channel_name_, to_, session_id_,
type, result, ack, data, encode);
}
void WebRtcMessageSender::Builder::SendHangUpMessage(const CallEndReason &reason) {
if (channel_name_.empty()) return;
sender_->SendCommonMessage(channel_name_, to_, session_id_,
kTypeHangup, reason.result, false, reason.source.ToJson(), true);
}
void WebRtcMessageSender::Builder::SendInviteAckMessage(int result) {
if (channel_name_.empty()) return;
sender_->SendCommonMessage(channel_name_, to_, session_id_,
kTypeInvite, result, true);
}
void WebRtcMessageSender::Builder::SendWebRtcMessage(const WebRtcMessage &message) const {
if (channel_name_.empty()) return;
sender_->SendWebRtcMessage(channel_name_, to_, session_id_, message);
}
WebRtcMessageSender::WebRtcMessageSender(const scoped_refptr<SignalingMessageBuilder> &message_builder,
WebRtcInterface *webrtc_interface)
: message_builder_(message_builder),
webrtc_interface_(webrtc_interface) {
thread_checker_.DetachFromThread();
}
bool WebRtcMessageSender::is_hangup_sent() const {
return is_command_sent(kTypeHangup);
}
bool WebRtcMessageSender::is_command_sent(const char *type) const {
DCHECK(thread_checker_.CalledOnValidThread());
return base::ContainsKey(type_history_, type);
}
scoped_refptr<SignalingMessageBuilder>
WebRtcMessageSender::message_builder() const {
return message_builder_;
}
WebRtcInterface *WebRtcMessageSender::webrtc_interface() const {
return webrtc_interface_;
}
WebRtcMessageSender::Builder WebRtcMessageSender::Get(const std::string &channel_name,
const std::string &to,
const std::string &session_id) {
return {this, channel_name, to, session_id};
}
WebRtcMessageSender::Builder WebRtcMessageSender::Get(const WebRtcCallInfo &info) {
return {this, info.channel_name, info.peer_userid, info.session_id};
}
void WebRtcMessageSender::SendCommonMessage(const std::string &channel_name,
const std::string &to,
const std::string &session_id,
const char *type,
int result,
bool ack,
const std::string &data,
bool encode) {
DCHECK(thread_checker_.CalledOnValidThread());
type_history_.emplace(type);
auto message = message_builder_->BuildCommonMessage(to, session_id, type, result, ack, data, encode);
webrtc_interface_->OnWebRtcCallSignalingMessageToPeer(channel_name, message);
}
void WebRtcMessageSender::SendWebRtcMessage(const std::string &channel_name,
const std::string &to,
const std::string &session_id,
const WebRtcMessage &message) const {
auto signaling_message
= message_builder_->BuildWebRtcMessage(to, session_id, message);
webrtc_interface_->OnWebRtcCallSignalingMessageToPeer(channel_name, signaling_message);
}
}