当前位置:首页 > PHP > 正文内容

利用workerman实现webrtc实时音视频通话

陈杰6个月前 (01-29)PHP1094


实现原理利用workerman的websocket实现实时消息传递。


webrtc自带p2p功能,利用STUN中继服务器实现webrtc实时音视频

image.png



image.png


看看我们的前端文件,只是一个单页面

<html>
<head>
    <meta name="referrer" content="always">
    <meta charset="utf-8">
    <meta name="viewport"
          content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no">
    <meta name="format-detection" content="telephone=no">
    <title>新申言自研rtc实时音视频通话</title>
    <meta name="keywords" content="申研社,申研社官方网站">
    <meta name="description" itemprop="description" content="申研社是一款专注于公考训练的专业APP">
</head>
<!-- 引入样式文件 -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vant@2.5.9/lib/index.css">
<!-- 引入 Vue 和 Vant 的 JS 文件 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vant@2.5.9/lib/vant.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js"></script>
<style type="text/css">
    [v-cloak] {
        display: none;
    }

    body { /* IOS禁止微信调整字体大小 */
        -webkit-text-size-adjust: 100% !important;
        text-size-adjust: 100% !important;
        -moz-text-size-adjust: 100% !important;
    }
</style>
<body>
<div id="app" v-cloak>
    <div v-for="(v,k) in list">{{v.cid}}</div>
    <div @click="play" style="background-color: black;color: #ffffff;width: 200px;text-align: center">播放</div>
    <h2>My Stream</h2>
    <video id="localStream" autoplay src=""></video>
    <h2>Remote Stream</h2>
    <video id="remoteStream" autoplay src=""></video>
</div>
</body>
<script>
    // 在 #app 标签下渲染一个按钮组件
    new Vue({
        el: '#app',
        data: {
            cid: {},
            list: "",
            ws: '',
            ws_url: "ws://127.0.0.1:8282",
            localStream: '',
            p2p: '',
            stunConfig: {
                iceServers: [{
                    urls: "stun:stun.xten.com"
                }]
            },
            remoteStream: '',
            answer: false
        },
        created() {
            this.openSocket()
        },
        methods: {
            play() {
                console.log(this.remoteStream)
                console.log(this.localStream)

                document.getElementById('localStream').srcObject = this.localStream
                document.getElementById('remoteStream').srcObject = this.remoteStream

                // this.remoteStream.play()
                // this.localStream.play()
            },
            openSocket() {
                this.ws = new WebSocket(this.ws_url)

                this.ws.onopen = (res) => {
                }
                this.ws.onmessage = (res) => {
                    res = JSON.parse(res.data)
                    if (res.type === 'init') {
                        this.cid.cid = res.data.cid
                        this.createStream()
                    }
                    if (res.type === "list") {
                        this.list = res.data
                    }
                    if (res.type === "call") {
                        console.log("call")
                        this.icecandidate()
                        this.p2p.createOffer({
                            offerToReceiveAudio: 1
                        }).then(ret => {
                            this.p2p.setLocalDescription(ret).then(rey => {
                                this.send('offer', this.p2p.localDescription)
                            })
                        })
                    }
                    if (res.type === "answer") {
                        this.p2p.setRemoteDescription(new RTCSessionDescription(res.data))
                    }
                    if (res.type === "offer") {
                        this.icecandidate()
                        this.p2p.setRemoteDescription(new RTCSessionDescription(res.data))
                        console.log("offer")
                        if (!this.answer) {
                            console.log("aaa")
                            this.p2p.createAnswer((ret) => {
                                    this.p2p.setLocalDescription(ret).then(() => {
                                        this.send("answer", this.p2p.localDescription)
                                    }, err => {
                                        alert(err)
                                    })
                                }
                                , (e) => {
                                    alert(e);
                                });
                            this.answer = true;
                        }
                    }
                    if (res.type === "candidate") {
                        console.log("candidate")
                        this.p2p.addIceCandidate(new RTCIceCandidate(res.data))
                    }
                }
            },
            //p2p打洞
            icecandidate() {
                this.p2p = new RTCPeerConnection(this.stunConfig)

                console.log(this.p2p)
                this.p2p.onicecandidate = (res => {
                    if (res.candidate) {
                        this.send("candidate", res.candidate)
                    }
                })
                try {
                    this.p2p.addStream(this.localStream)
                } catch (e) {
                    console.log(111111111)
                    alert(e)
                }
                this.p2p.onaddstream = (res) => {
                    this.remoteStream = res.stream
                }
            },
            createStream() {
                if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
                    alert("当前浏览器不支持webrtc!")
                    return false
                }
                navigator.mediaDevices.getUserMedia({
                    audio: true
                }).then(res => {
                    console.log(res)
                    this.localStream = res
                    this.send("call", null)
                }, err => {
                    alert(err)
                })
            },
            send(type, data) {
                let msg = {
                    type: type,
                    data: data
                }
                msg = JSON.stringify(msg)
                this.ws.send(msg)
            }


        }
    })
</script>
</html>

前端文件框架用的vue,主要是为了简单,只要实现webrtc就行了。


简单分析一下,加载完成后首先开启websocket链接并绑定到一个频道号上面,在该频道上得客户端均实现webrtc链接。

webrtc基本传输频道是流 stream,由本地摄像头和麦克风采集的就是本地流 localStream,由远端客服端采集发送给我们的就是远端流remoteStream。

我们要做的就是把本地localSteam推送给其他用户,把接收到其他的用户的remoteStream播放出来,这样就实现了实时音视频。


在这个过程中有一个p2p打洞的过程,现代浏览器差不多都自带了webrtc功能

this.p2p = new RTCPeerConnection(this.stunConfig)


RTCPeerConnnection可以自行百度该类的用法,我们的webrtc基本上都是基于该浏览器自带功能实现。




后台workerman Gateway  Events.php代码内容

<?php

//declare(ticks=1);

use \GatewayWorker\Lib\Gateway;

$list = array();

class Events
{

    public static function onConnect($client_id)
    {
        $data = self::success('init', ['cid' => $client_id]);
        Gateway::sendToCurrentClient($data);
        $list = Gateway::getAllClientIdList();
        $arr  = [];
        foreach ($list as $v) {
            $arr[$v] = ["cid" => $v];
        }
        $data = self::success('list', $arr);
        Gateway::sendToAll($data);


    }

    public static function onMessage($client_id, $message)
    {
        // 向所有人发送
        $message = json_decode($message, true);
        $data = self::success($message['type'], $message['data']);
        $list = Gateway::getAllClientIdList();
        foreach ($list as $v) {
            if ($v !== $client_id)
                Gateway::sendToClient($v, $data);
        }

    }


    public static function onClose($client_id)
    {
        $data = self::success('leave', ['cid' => $client_id]);
        GateWay::sendToAll($data);

    }

    public static function success($type = 'success', $data = [], $msg = '成功', $code = 200)
    {
        $error = [
            'type' => $type,
            'msg'  => $msg,
            'code' => $code,
            'data' => $data
        ];
        return json_encode($error, 256);
    }

    public static function error($type = 'error', $msg = '错误', $code = 400, $data = [])
    {
        $error = [
            'type' => $type,
            'msg'  => $msg,
            'code' => $code,
            'data' => $data
        ];
        return json_encode($error, 256);
    }
}



扫描二维码至手机访问

扫描二维码推送至手机访问。

版权声明:本文由何烦过虎溪发布,如需转载请注明出处。

转载请注明出处:http://95shouyou.com/?id=36

分享给朋友:

相关文章

Laravel记录SQL操作日志的方法

Laravel记录SQL操作日志的方法

在laravel中我们有一个需求就是,涉及到sql操作的update,insert,delete操作的语句,我们都要写一个日志来记录一下说说方法吧。在项目目录app/Providers/AppServ...

php对接七牛云短信验证码实战

短信验证码登录的用处非常的大,登录,注册,修改密码,安全相关的啥都可以干。选定的七牛云短信是因为存储也是用的七牛云,七牛的sdk都加载进来了,也懒得去换其他的厂家了。下面上代码:Controller层...

Lumen框架报错Class session does not exist

Lumen框架报错Class session does not exist

由于lumen框架是为速度而生的 Laravel 框架,所以移除了session的支持,虽然在ide中代码提示可以敲出Session::put()方法出来,但是最终还是提示的是Class sessio...

通过代码创建多个同样的mysql表

在分库分表中可能要同时创建多个结构相同但后缀不同的表,通过代码实现for ($i = 3; $i <= 20; $i++) {    DB::connection('...

mysql查找附近的人,经纬度查询

经纬度排序mysql函数CREATE DEFINER=`root`@`localhost` FUNCTION `get_distance`(`lon1` float,`lat1` float,`lon...

laravel分表model映射的思路

预先估计会出现大数据量并且访问频繁的表,将其分为若干个表这种预估大差不差的,论坛里面发表帖子的表,时间长了这张表肯定很大,几十万,几百万都有可能。 聊天室里面信息表,几十个人在一起一聊一个晚上,时间长...

发表评论

访客

看不清,换一张

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。