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

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

陈杰2个月前 (01-29)PHP599


实现原理利用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://blog.95shouyou.com/?id=36

分享给朋友:

相关文章

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

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

Lumen框架报错Cannot declare class Event, because the name is already in use

Lumen框架报错Cannot declare class Event, because the name is already in use

Lumen框架报错信息Cannot declare class Event, because the name is already in use(1/1) ErrorExceptionCa...

php对接支付宝转账到第三方接口实战

公司项目有一个用户钱包系统,用户创作的内容可以收到游客的打赏,当然就需要提现的接口了。最终选定的是支付宝转账接口,公司代收账户直接打款给用户绑定的支付宝账号,再也不用人工手动打款了。上代码准备好工具,...

七牛云删除文件和批量删除文件

在项目中因为用到了七牛云的对象存储,价格公道,也降低我们的项目维护成本。但是在实际使用过程中,例如用户换了头像我们就需要删除用户之前的头像,以降低我们的存储成本。所以研究了一下七牛云的删除单个文件和批...

laravel的Observer观察者模式模型事件实战

laravel的orm特别好用,特别是observer观察者模式,可以在不更改原有业务代码的情况下做到切入编程,有点类似于我们之前常用的钩子函数。下面就是我们深入observer观察者模式的实战代码现...

laravel分表model映射的思路

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

发表评论

访客

看不清,换一张

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