phonertc视频聊天

日期:2016-5-20 13:51 | 标签: | 阅读:700

phonertc是针对cordova平台基于webrtc的一种实现,参考网上现有实现,我们可以试着部署一个demo。

准备

  1. 安装phonertc插件 https://github.com/alongubkin/phonertc/wiki/Installation (注:ios一定要按照这个步骤来,不然会失败,奇葩的是只能在真实的手机上才能运行起来,模拟器是不行的!)
  2. 搭建信令服务器(Singaling Server),基于socket.io,目的是为了交换双方的SDP信息。
  3. 搭建视频流转发服务器turn/stun server,安装教程,原理是这样:webrtc使用ICE框架,其首先会尝试用设备系统或网卡获取到的主机地址去建立连接;如果失败了(设备在NAT后面)ICE则从STUN服务器获得外部的地址,如果这个也失败了,就用TURN中转服务器做通讯。
  4. 可以结合其他插件一起使用,实现更多功能,
    参考:https://github.com/alongubkin/phonertc/wiki

Stun/Turn

STUN(Session Traversal Utilities for NAT)协议,解决了三个问题:

  1. 获得外网 IP 和端口
  2. 在 NAT 中建立路由条目,绑定外网端口,使得到达外网 IP 和端口的入站分组能找到应用程序,不被丢弃;
  3. 定义了一个简单的 keep-alive 机制,保证 NAT 路由条目不会因为超时而被删除。STUN 服务器必须架设在公网上,可以自己搭建,也可以使用第三方提供的公开服务,例如 Google 的「stun:stun.l.google.com:19302」。

TURN(Traversal Using Relays around NAT)协议,依赖外网中继设备在两端之间传递数据。简单说就是通过两端都可以访问的 TURN 服务转发消息,间接把两端连起来。TURN 还会尝试使用 TCP 建立,而不仅仅是 UDP,可靠性大大增强,带宽成本也随着大幅提升。根据 Google 的统计,UDP 服务中,有 8% 左右的情况下需要 TURN。

Singaling Server

要建立端到端的信道,还是需要借助服务端来交换和协商一些信息,这个过程被称之为 Signaling。WebRTC 并没有规则 Signaling 必须使用某种协议,而把选择权交给了应用程序。我们可以选用XMLHttpRequest、WebSocket等方式,采用已有的 SIP、Jingle、ISUP 等发信协议,来建立信道。
通常,在 WebRTC 应用中,建立信道优先走 WebSocket,并支持降级为 HTTP。一来支持 WebRTC 的浏览器肯定都支持 WebSocket;二来 WebSocket 实时性更好一些。特别需要注意的是,WebSocket 只用来辅助建立端到端连接,一旦连接建立,信源在端到端之间的传输就完全不需要服务端了(当然 TURN 这种中继模式就另当别论)。

客户端实现

视频显示指令

    // 协同相关指令
    .directive('videoView', function ($rootScope, $timeout) {
        return {
            restrict: 'E',
            template: '<div class="video-container"></div>',
            replace: true,
            link: function (scope, element, attrs) {
                function updatePosition() {
                    try {
                        cordova.plugins.phonertc.setVideoView({
                            container: element[0],
                            local: {
                                position: [240, 240],
                                size: [50, 50]
                            }
                        });
                    } catch (err) {
                        alert('err' + err);
                    }
                }
                $timeout(updatePosition, 500);
                $rootScope.$on('videoView.updatePosition', updatePosition);
            }
        }
    })

全局监听call消息,放在app.js中

       // 全局监听PhoneRtc消息
      .run(function ($state, signaling, $ionicLoading) {
        signaling.on('messageReceived', function (name, message) {
          switch (message.type) {
            case 'call':
              if ($state.current.name === 'call') {
                return;
              }
              // 来电直接跳转至接听页面
              $state.go('call', { isCalling: false, contactName: name });
              break;
          }
        });
      })

监听消息事件

        function onMessageReceive(name, message) {
            switch (message.type) {
                case 'answer':
                    $scope.$apply(function () {
                        $scope.callInProgress = true;
                        $timeout($scope.updateVideoPosition, 1000);
                    });
                    var existingContacts = Object.keys($scope.contacts);
                    if (existingContacts.length !== 0) {
                        signaling.emit('sendMessage', name, {
                            type: 'add_to_group',
                            contacts: existingContacts,
                            isInitiator: false
                        });
                    }
                    call(true, name);
                    break;
                // 拒绝接听(忽略)
                case 'ignore':
                    var len = Object.keys($scope.contacts).length;
                    if (len > 0) {
                        if ($scope.contacts[name]) {
                            $scope.contacts[name].close();
                            delete $scope.contacts[name];
                        }
                        var i = $scope.hideFromContactList.indexOf(name);
                        if (i > -1) {
                            $scope.hideFromContactList.splice(i, 1);
                        }
                        if (Object.keys($scope.contacts).length === 0) {
                            $scope.callInProgress = false;
                            $ionicHistory.goBack(-1);
                        }
                    } else {
                        $scope.callInProgress = false;
                        $ionicHistory.goBack(-1);
                    }
                    break;
               // 结束通话
                case 'end':
                    Object.keys($scope.contacts).forEach(function (contact) {
                        $scope.contacts[contact].close();
                        delete $scope.contacts[contact];
                    });
                    // 跳转回聊天页面
                    $timeout(function () {
                        $scope.callInProgress = false;
                        $ionicHistory.goBack(-1);
                    }, 1000);
                    break;
                case 'phonertc_handshake':
                    if (duplicateMessages.indexOf(message.data) === -1) { 
                    $scope.contacts[name].receiveMessage(JSON.parse(message.data));
                       duplicateMessages.push(message.data);
                     }
                    break;
                case 'add_to_group':
                    message.contacts.forEach(function (contact) {
                        $scope.hideFromContactList.push(contact);
                        call(message.isInitiator, contact);
                        if (!message.isInitiator) {
                            $timeout(function () {
                                signaling.emit('sendMessage', contact, {
                                    type: 'add_to_group',
                                    contacts: [ContactsService.currentName],
                                    isInitiator: true
                                });
                            }, 1500);
                        }
                    });
                    break;
            }
        }
        signaling.on('messageReceived', onMessageReceive);

发起视频,建立通话通道

        // session通话初始化
        function call(isInitiator, contactName) {
            var config = {
                isInitiator: isInitiator,
                stun: {
                    host: 'stun:115.29.51.196'
                },
                turn: {
                    host: 'turn:115.29.51.196',
                    username: 'test',
                    password: 'test'
                },
                streams: {
                    audio: true, // 支持音频
                    video: true, // 支持视频
                }
            };
            var session = new cordova.plugins.phonertc.Session(config);
            session.on('sendMessage', function (data) {
                signaling.emit('sendMessage', contactName, {
                    type: 'phonertc_handshake',
                    data: JSON.stringify(data)
                });
            });
            session.on('answer', function () {
                // alert('Answered!');
            });
            session.on('disconnect', function () {
                if ($scope.contacts[contactName]) {
                    delete $scope.contacts[contactName];
                }
                if (Object.keys($scope.contacts).length === 0) {
                    signaling.emit('sendMessage', contactName, { type: 'ignore' });
                    $ionicHistory.goBack(-1);
                }
            });
            session.call();
            // 保存连接
            $scope.contacts[contactName] = session;
        }

参考

feWebRTC(一):入门 http://feixiao.github.io/2015/07/01/WebRTC%E5%85%A5%E9%97%A8/
webrtc实现视频聊天: https://imququ.com/post/html5-live-player-3.html

版权声明: 署名-非商业性使用-禁止演绎 4.0 国际(CC BY-NC-ND 4.0
Copyright ©2013-2017 | 粤ICP备14081691号 | yipeng手工打造 | 联系方式