composer create-project swoft/swoft swoft
安装完后进入目录
composer require swoft/websocket-server
# HTTP
HTTP_HOST=0.0.0.0
HTTP_PORT=8188
HTTP_MODE=SWOOLE_PROCESS
HTTP_TYPE=SWOOLE_SOCK_TCP
php bin/swoft gen:websocket Camera --prefix /Camera
/**
* Class CameraController
* @package App\WebSocket
* @WebSocket("/con_camera/camera")
*/
class CameraController implements HandlerInterface
注意
@WebSocket("/con_camera/camera")
这里是访问的路由需要自己改
/**
* @param Server $server
* @param Request $request
* @param int $fd
*/
public function onOpen(Server $server, Request $request, int $fd)
{
$server->push($fd, '{"type":"id", "id":"'.$fd.'"}');
}
/**
* @param Server $server
* @param Frame $frame
* @return mixed
*/
public function onMessage(Server $server, Frame $frame)
{
// 这里是广播。 自己发出的内容,不广播给自己
\Swoft::$server->sendToSome($frame->data, [], [$frame->fd]);
// $server->push($frame->fd, $frame->data);
}
/**
* @param Server $server
* @param int $fd
* @return mixed
*/
public function onClose(Server $server, int $fd)
{
$server->close($fd);
// do something. eg. record log
}
后台运行swoft项目
php bin/swoft ws:restart -d
php bin/swoft ws:stop
/**
* @param Server $server
* @param Request $request
* @param int $fd
*/
public function onOpen(Server $server, Request $request, int $fd)
{
var_dump($fd,'这里是测试部分');
$server->push($fd, '{"type":"id", "id":"'.$fd.'"}');
}
测试方法很多,我只用
websocket 测试
谷歌浏览器
<html>
<head>
<meta charset="utf-8">
head>
<body>
<script type="text/javascript">
function websocketOpen () {
if ("WebSocket" in window) {
alert("您的浏览器支持 WebSocket!")
return false;
} else {
// 浏览器不支持 WebSocket123
alert("您的浏览器不支持 WebSocket!");
return true;
}
}
if(websocketOpen()){
console.log('链接失败')
}
let ws = new WebSocket("wss://localhost.com/con_camera/camera");
ws.onopen = function (evt) {
alert('连接成功')
}
ws.onmessage = function (evt) {
let received_msg = evt.data
console.log(received_msg);
}
ws.onclose = function (){
alert('关闭链接')
}
script>
body>
html>
new WebSocket(“wss://localhost.com/con_camera/camera”)
以上内容是PHP部分,没有多少需要编辑的。php.swoft只做转发和广播
基本都知道吧,不懂的看 官方文档
WebSocket 对象提供了用于创建和管理 WebSocket 连接,以及可以通过该连接发送和接收数据的 API。
基本理解成 它是一个双向管道
简单的理解成。调用媒体设备,获取 摄像头媒体设备 输入的信息 官方文档
简单理解成本地到远端的连接 官方文档
官方推荐引入Adapter.js保持浏览器的兼容性
作为webRTC中极为重要的一部分,会话管理需要建立服务器端与客户端之间的连接。
有人就问了:webRTC建立的是点对点连接,流数据是从浏览器直接传输到另一个浏览器,不需要服务器周转,怎么还需要建立服务器端与客户端连接呢?
这是个很好的问题!尽管webRTC建立的是P2P连接,但由于流数据传输需要一条信道,而这个信道则是由信令服务器提供的。而在webRTC中并没有这一过程,所以需要我们手动建立信号的传递和交涉过程。
信令服务器的实现软件有很多,我们这里用coturn官方文档
参考原文:https://blog.csdn.net/vainfanfan/article/details/82632737
步骤很简单,但是中间遇到很多坑,我会把遇到的问题,也列出来
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="php web-rtc例子,一对一聊天-基于workerman">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<meta itemprop="description" content="Video chat using the reference WebRTC application">
<meta itemprop="name" content="AppRTC">
<meta name="mobile-web-app-capable" content="yes">
<meta id="theme-color" name="theme-color" content="#1e1e1e">
<title>webRtc视频通话title>
head>
<body>
<div class="videos">
<video id="localVideo" autoplay>video>
<video id="remoteVideo" autoplay class="hidden">video>
div>
<script src="jquery-3.2.1.min.js">script>
<script src="adapter.js">script>
<script type="text/javascript">
var localVideo = document.getElementById('localVideo');
var remoteVideo = document.getElementById('remoteVideo');
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
navigator.mediaDevices.getUserMedia({
// audio: true,
video: true
}).then(function (stream) {
localVideo.srcObject = stream;
localStream = stream;
localVideo.addEventListener('loadedmetadata', function () {
console.log('视频加载成功')
});
}).catch(function (e) {
alert(e);
});
script>
body>
html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="php web-rtc例子,一对一聊天-基于workerman">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<meta itemprop="description" content="Video chat using the reference WebRTC application">
<meta itemprop="name" content="AppRTC">
<meta name="mobile-web-app-capable" content="yes">
<meta id="theme-color" name="theme-color" content="#1e1e1e">
<title>webRtc视频通话title>
head>
<body>
<div class="videos">
<video id="localVideo" autoplay>video>
<video id="remoteVideo" autoplay class="hidden">video>
div>
<script src="jquery-3.2.1.min.js">script>
<script src="adapter.js">script>
<script type="text/javascript">
var localVideo = document.getElementById('localVideo');
var remoteVideo = document.getElementById('remoteVideo');
// 流媒体对象
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
// 流媒体通道配置
var configuration = {
iceTransportPolicy:"all",
iceCandidatePoolSize:"0"
};
// 协商次数
var answer = 1;
/*
流媒体获取
*/
function openMediaSetSend() {
navigator.mediaDevices.getUserMedia({
// audio: true,
video: true
}).then(function (stream) {
localVideo.srcObject = stream;
localStream = stream;
localVideo.addEventListener('loadedmetadata', function () {
// 广播客户端 准备连接
publish('client-call', null)
});
}).catch(function (e) {
alert(e);
});
}
/*
websocketOpen
*/
function websocketOpen () {
if ("WebSocket" in window) {
alert("您的浏览器支持 WebSocket!")
return false;
} else {
// 浏览器不支持 WebSocket123
alert("您的浏览器不支持 WebSocket!");
return true;
}
}
if(websocketOpen()){
console.log('链接失败')
}
let ws = new WebSocket("wss://www.cwtui.com/con_camera/camera");
//发送消息
function publish(event, data) {
ws.send(JSON.stringify({
cmd:'publish',
// subject: subject, // 房间号 暂时不做这个
event:event,
data:data
}));
}
ws.onopen = function (evt) {
openMediaSetSend() // 广播客户端 准备连接
}
ws.onmessage = function(e){
let package = JSON.parse(e.data)
let data = package.data;
switch (package.event) {
case 'client-call':
// 主播模块
client_call();
break;
case 'client-answer':
// 更改与连接关联的 远程描述
pc.setRemoteDescription(data).then().catch(e=>{alert(e)})
break;
case 'client-offer':
// 粉丝模块
client_offer(data)
break;
case 'client-candidate':
/*
将新接收的候选服务器传递到浏览器的ICE代理服务器。
值为空字符串(“”),则表示已传递所有远程候选对象(候选对象结束)
在协商过程中,您的应用程序可能会收到许多候选项,您可以通过这种方式将这些候选项传递给ICE代理,
从而允许它构建一个潜在连接方法的列表。
*/
pc.addIceCandidate(new RTCIceCandidate(data)).then().catch(e=>{alert(e)})
break;
}
};
ws.onclose = function (){
alert('关闭链接')
}
/*
流媒体通道创建
*/
function icecandidate(localStream) {
// 创建链接
pc = new RTCPeerConnection(configuration);
/**
onicecandidate属性 是一个事件处理程序
当调用 RTCPeerConnection.setlocaldescription() 或将 RTCIceCandidate 添加到 RTCPeerConnection 时
应将候选对象传输到 远程客户端 以便 ICE代理与远程客户端 协商
*/
pc.onicecandidate = event => {
if (event.candidate) {
publish('client-candidate', event.candidate);
}
}
var tracks = localStream.getTracks();
// 将一个媒体流 添加到一组流中,这些新流将被传输给 客户端
for(const val of tracks){
pc.addTrack(val, localStream);
}
pc.ontrack = event => {
$('#remoteVideo').removeClass('hidden');
$('#localVideo').remove();
alert(event.streams)
remoteVideo.srcObject = event.streams[0];
};
}
// 主播 开始准备 插座 信息
function client_call() {
// 创建流媒体通道
icecandidate(localStream);
// 生成插座
pc.createOffer({ // 创建一个 SDP 提供一个新的 webrtc 连接到远程节点
offerToReceiveAudio: false,
offerToReceiveVideo: true
}).then(function (desc) {
// 更改与连接关联的本地描述 设置后 onicecandidate 协商
pc.setLocalDescription(desc).then(function () {
publish('client-offer', pc.localDescription);
}).catch(function (e) {
alert(e);
});
}).catch(function (e) {
alert(e);
});
}
// 粉丝端 收到消息 开始链接
function client_offer(data) {
// 创建流媒体通道
icecandidate(localStream);
// 更改与连接关联的 远程描述
pc.setRemoteDescription(data).then(function (){
if (answer) {
answer = 0;
/*
方法在WebRTC 连接的要约/答复 协商期间,为从粉丝户端接收到的要约创建一个SDP信息。
答案包含关于已经附加到会话的任何媒体、浏览器支持的编解码器和选项以及已经收集到的ICE候选项的信息。
答案是交付给返回的承诺,然后应该发送到报价源继续谈判过程。
*/
return pc.createAnswer();
}
return false;
}).then( desc => {
if(desc !== false){
// 更改与连接关联的本地描述 设置后 onicecandidate 协商 createAnswer
pc.setLocalDescription(desc).then(function (){
publish('client-answer', pc.localDescription);
})
}
}).catch( e => {
alert(e)
})
}
script>
body>
html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="php web-rtc例子,一对一聊天-基于workerman">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<meta itemprop="description" content="Video chat using the reference WebRTC application">
<meta itemprop="name" content="AppRTC">
<meta name="mobile-web-app-capable" content="yes">
<meta id="theme-color" name="theme-color" content="#1e1e1e">
<title>webRtc主播title>
head>
<body>
<div class="videos">
<video id="localVideo" autoplay>video>
<video id="remoteVideo" autoplay class="hidden">video>
div>
<script src="jquery-3.2.1.min.js">script>
<script src="adapter.js">script>
<script type="text/javascript">
var localVideo = document.getElementById('localVideo');
var remoteVideo = document.getElementById('remoteVideo');
// 流媒体对象
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
// 流媒体通道配置
var configuration = {
iceTransportPolicy:"all",
iceCandidatePoolSize:"0"
};
// 协商次数
var answer = 1;
/*
流媒体获取
*/
function openMediaSetSend() {
navigator.mediaDevices.getUserMedia({
// audio: true,
video: true
}).then(function (stream) {
localVideo.srcObject = stream;
localStream = stream;
localVideo.addEventListener('loadedmetadata', function () {
// 广播客户端 准备连接
publish('client-call', null)
});
}).catch(function (e) {
alert(e);
});
}
/*
websocketOpen
*/
function websocketOpen () {
if ("WebSocket" in window) {
alert("您的浏览器支持 WebSocket!")
return false;
} else {
// 浏览器不支持 WebSocket123
alert("您的浏览器不支持 WebSocket!");
return true;
}
}
if(websocketOpen()){
console.log('链接失败')
}
let ws = new WebSocket("wss://www.cwtui.com/con_camera/camera");
//发送消息
function publish(event, data) {
ws.send(JSON.stringify({
cmd:'publish',
// subject: subject, // 房间号 暂时不做这个
event:event,
data:data
}));
}
ws.onopen = function (evt) {
openMediaSetSend() // 广播客户端 准备连接
}
ws.onmessage = function(e){
let package = JSON.parse(e.data)
let data = package.data;
switch (package.event) {
case 'client-call':
// 主播模块
client_call();
break;
case 'client-answer':
// 更改与连接关联的 远程描述
pc.setRemoteDescription(data).then().catch(e=>{alert(e)})
break;
}
};
ws.onclose = function (){
alert('关闭链接')
}
/*
流媒体通道创建
*/
function icecandidate(localStream) {
// 创建链接
pc = new RTCPeerConnection(configuration);
/**
onicecandidate属性 是一个事件处理程序
当调用 RTCPeerConnection.setlocaldescription() 或将 RTCIceCandidate 添加到 RTCPeerConnection 时
应将候选对象传输到 远程客户端 以便 ICE代理与远程客户端 协商
*/
pc.onicecandidate = event => {
if (event.candidate) {
publish('client-candidate', event.candidate);
}
}
var tracks = localStream.getTracks();
// 将一个媒体流 添加到一组流中,这些新流将被传输给 客户端
for(const val of tracks){
pc.addTrack(val, localStream);
}
pc.ontrack = event => {
$('#remoteVideo').removeClass('hidden');
$('#localVideo').remove();
// alert(event.streams)
remoteVideo.srcObject = event.streams[0];
};
}
// 主播 开始准备 插座 信息
function client_call() {
// 创建流媒体通道
icecandidate(localStream);
// 生成插座
pc.createOffer({ // 创建一个 SDP 提供一个新的 webrtc 连接到远程节点
offerToReceiveAudio: false,
offerToReceiveVideo: true
}).then(function (desc) {
// 更改与连接关联的本地描述 设置后 onicecandidate 协商
pc.setLocalDescription(desc).then(function () {
publish('client-offer', pc.localDescription);
}).catch(function (e) {
alert(e);
});
}).catch(function (e) {
alert(e);
});
}
script>
body>
html>
<html>
<head>
<meta charset="utf-8">
<meta name="description" content="php web-rtc例子,一对一聊天-基于workerman">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1, maximum-scale=1">
<meta itemprop="description" content="Video chat using the reference WebRTC application">
<meta itemprop="name" content="AppRTC">
<meta name="mobile-web-app-capable" content="yes">
<meta id="theme-color" name="theme-color" content="#1e1e1e">
<title>webRtc粉丝title>
head>
<body>
<div class="videos">
<video id="localVideo" autoplay>video>
<video id="remoteVideo" autoplay class="hidden">video>
div>
<script src="jquery-3.2.1.min.js">script>
<script src="adapter.js">script>
<script type="text/javascript">
var localVideo = document.getElementById('localVideo');
var remoteVideo = document.getElementById('remoteVideo');
// 流媒体对象
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
// 流媒体通道配置
var configuration = {
iceTransportPolicy:"all",
iceCandidatePoolSize:"0"
};
// 协商次数
var answer = 1;
/*
websocketOpen
*/
function websocketOpen () {
if ("WebSocket" in window) {
alert("您的浏览器支持 WebSocket!")
return false;
} else {
// 浏览器不支持 WebSocket123
alert("您的浏览器不支持 WebSocket!");
return true;
}
}
if(websocketOpen()){
console.log('链接失败')
}
let ws = new WebSocket("wss://www.cwtui.com/con_camera/camera");
//发送消息
function publish(event, data) {
ws.send(JSON.stringify({
cmd:'publish',
// subject: subject, // 房间号 暂时不做这个
event:event,
data:data
}));
}
ws.onopen = function (evt) {
publish('client-call', null) // 广播客户端 准备连接
}
ws.onmessage = function(e){
let package = JSON.parse(e.data)
let data = package.data;
switch (package.event) {
case 'client-offer':
// 粉丝模块
if(answer){
client_offer(data)
}
break;
case 'client-candidate':
/*
将新接收的候选服务器传递到浏览器的ICE代理服务器。
值为空字符串(“”),则表示已传递所有远程候选对象(候选对象结束)
在协商过程中,您的应用程序可能会收到许多候选项,您可以通过这种方式将这些候选项传递给ICE代理,
从而允许它构建一个潜在连接方法的列表。
*/
pc.addIceCandidate(new RTCIceCandidate(data)).then().catch(e=>{alert(e)})
break;
}
};
ws.onclose = function (){
alert('关闭链接')
}
/*
流媒体通道创建
*/
function icecandidate() {
// 创建链接
pc = new RTCPeerConnection(configuration);
/**
onicecandidate属性 是一个事件处理程序
当调用 RTCPeerConnection.setlocaldescription() 或将 RTCIceCandidate 添加到 RTCPeerConnection 时
应将候选对象传输到 远程客户端 以便 ICE代理与远程客户端 协商
*/
pc.ontrack = event => {
$('#remoteVideo').removeClass('hidden');
$('#localVideo').remove();
// alert(event.streams)
remoteVideo.srcObject = event.streams[0];
};
}
// 粉丝端 收到消息 开始链接
function client_offer(data) {
// 创建流媒体通道
icecandidate();
// 更改与连接关联的 远程描述
pc.setRemoteDescription(data).then(function (){
answer = 0;
/*
方法在WebRTC 连接的要约/答复 协商期间,为从粉丝户端接收到的要约创建一个SDP信息。
答案包含关于已经附加到会话的任何媒体、浏览器支持的编解码器和选项以及已经收集到的ICE候选项的信息。
答案是交付给返回的承诺,然后应该发送到报价源继续谈判过程。
*/
return pc.createAnswer();
}).then( desc => {
if(desc !== false){
// // 更改与连接关联的 远程描述
pc.setLocalDescription(desc).then(function (){
publish('client-answer', pc.localDescription);
})
}
}).catch( e => {
alert(e)
})
}
script>
body>
html>
var configuration = {
iceServers:[{
urls:["turn:www.cwtui.com:3478"], //地址
username:"hu", // 账号
credential:"123456" // 密码
}],
iceTransportPolicy:"all",
iceCandidatePoolSize:"0"
};
重点注意下 turn 协议和 stun 这两个协议 我们配置的协议是turn
coturn 下载页面
cd coturn
./configure
make
sudo make install
查看是否安装成功
使用命令:which turnserver
我的配置
listening-port=3478
tls-listening-port=5349
listening-ip=172.17.142.214 // 内网ip
relay-device=eth0
relay-ip=172.17.142.214 // 内网ip
external-ip=47.94.228.114/172.17.142.214 // 外网ip/内网ip
relay-threads=5
min-port=49152
max-port=65535
fingerprint
lt-cred-mech
user=hu:123456
realm=www.cwtui.com
cert=/etc/turn_server_cert.pem
pkey=/etc/turn_server_pkey.pem
pidfile="/var/run/turnserver.pid"
cli-password=qwerty
参考原文: https://www.pressc.cn/967.html
参考原文: https://blog.csdn.net/bvngh3247/article/details/80742396
参考原文:https://www.cnblogs.com/mobilecard/p/6542294.html
希望朋友给点个赞~~~~ 谢谢大家看到这里