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

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

陈杰1年前 (2021-01-29)PHP2367


实现原理利用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的Observer观察者模式模型事件实战

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

laravel个推设置别名

laravel个推设置别名

上一篇文章我们说过了怎么用laravel接个推的官方sdk实现个推推送和厂商离线推送,因为我们要用到别名推送,而前端又用到的是unipush,在前端去绑定别名的难度肯定要大一点,所以就有一个思路就是,...

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

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

laravel实现微信公众号授权登录实战

微信公众号授权登录实战框架:laravel依赖:overtrue/wechat首先安装一下easywechat依赖composer require overtrue/wechat:...

Laravel记录SQL操作日志的方法

Laravel记录SQL操作日志的方法

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

通过supervisor管理laravel的queue队列

通过supervisor管理laravel的queue队列

配置文件[program:queue]command=php artisan queue:work redis --tries=3 --delay=3directory=/www/wwwroot/sh...

发表评论

访客

看不清,换一张

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