webrtc 搭建直播平台

article/2025/8/20 11:31:43

设计思路

需求: 一个直播页面,可以输入直播名。一个观看页面输入客户名个要看的直播名建立直播视频传输

思路:

  1. 直播页面输入直播名建立websocket连接,创建PeerConnection对象组存放连接本直播端的PeerConnection对象。
  2. 观看页面输入客户名与直播名建立websocket连接,通知直播端发送__offer给观看端
  3. 观看接收到__offer指令,将__offer中携带的ice会话描述信息加入PeerConnection中并发起一个_answer和n个__ice_candidate指令给直播端
  4. 直播端收到__answer 和 __ice_candidate指定 追加至 对应的PeerConnection对象中

引入的maven依赖

<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>1.5.10.RELEASE</version></parent><dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--测试类包--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- freemarker模板引擎--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.31</version></dependency><!--websocket--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency></dependencies>

直播页面

<!doctype html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>播放页</title>
</head>
<body>
<video id="video" autoplay></video><span>主播名</span>
<input id="zhubo" type="text"/>
<button onclick="connect()">建立视频连接</button></body><script type="text/javascript">/*** socket.send 数据描述* event: 指令类型* data: 数据* name: 发送人* receiver: 接收人** *///使用Google的stun服务器const iceServer = {"iceServers": [{"url": "stun:stun.l.google.com:19302"}, {"url": "turn:numb.viagenie.ca","username": "webrtc@live.com","credential": "muazkh"}]};//兼容浏览器的getUserMedia写法const getUserMedia = (navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia);//兼容浏览器的PeerConnection写法const PeerConnection = (window.PeerConnection ||window.webkitPeerConnection00 ||window.webkitRTCPeerConnection ||window.RTCPeerConnection ||window.mozRTCPeerConnection);/*** 信令websocket* @type {WebSocket}*/var socket;/*** 视频信息* */var stream_two;/*** 播放视频video组件* */const video = document.getElementById('video');/*** 连接的浏览器PeerConnection对象组* {*  'id':PeerConnection* }* @type {{}}*/var pc = {};// 建立scoket连接function connect() {// 获取主播名称const zhubo = document.getElementById('zhubo').value;/*** 信令websocket* @type {WebSocket}*/socket = new WebSocket("ws://192.168.31.13:6533/websocket?name=" + zhubo);//获取本地的媒体流,并绑定到一个video标签上输出,并且发送这个媒体流给其他客户端getUserMedia.call(navigator, {"audio": true,"video": true}, function (stream) {//发送offer和answer的函数,发送本地session描述stream_two = stream;video.srcObject = stream//向PeerConnection中加入需要发送的流//如果是发送方则发送一个offer信令,否则发送一个answer信令}, function (error) {//处理媒体流创建失败错误alert("处理媒体流创建失败错误");});socket.close = function () {console.log("连接关闭")}//有浏览器建立视频连接socket.onmessage = function (event) {var json = JSON.parse(event.data);if (json.name && json.name != null && !json.event) {pc[json.name] = new PeerConnection(iceServer);pc[json.name].addStream(stream_two);// 浏览器兼容var agent = navigator.userAgent.toLowerCase();if (agent.indexOf("firefox") > 0) {pc[json.name].createOffer().then(function (desc) {pc[json.name].setLocalDescription(desc);socket.send(JSON.stringify({"event": "__offer","data": {"sdp": desc},name: zhubo,receiver: json.name}));});} else if (agent.indexOf("chrome") > 0) {pc[json.name].createOffer(function (desc) {pc[json.name].setLocalDescription(desc);socket.send(JSON.stringify({"event": "__offer","data": {"sdp": desc},name: zhubo,receiver: json.name}));});} else {pc[json.name].createOffer(function (desc) {pc[json.name].setLocalDescription(desc);socket.send(JSON.stringify({"event": "__offer","data": {"sdp": desc},name: zhubo,receiver: json.name}));});}} else {if (json.event === "__ice_candidate") {//如果是一个ICE的候选,则将其加入到PeerConnection中pc[json.name].addIceCandidate(new RTCIceCandidate(json.data.candidate));} else if (json.event === "__answer") {// 将远程session描述添加到PeerConnection中pc[json.name].setRemoteDescription(new RTCSessionDescription(json.data.sdp));}}};}</script>
</html>

观看页面

<!doctype html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>观看页</title>
</head>
<body>
<div id="eee"><video id="video" autoplay></video>
</div>
<span>用户名</span><input id="userName"/>
<span>主播名</span><input id="receiver"/>
<button onclick="communication()">建立视频通信</button></body>
<script type="text/javascript">/*** socket.send 数据描述* event: 指令类型* data: 数据* name: 发送人* receiver: 接收人** *///使用Google的stun服务器const iceServer = {"iceServers": [{"url": "stun:stun.l.google.com:19302"}, {"url": "turn:numb.viagenie.ca","username": "webrtc@live.com","credential": "muazkh"}]};//兼容浏览器的getUserMedia写法const getUserMedia = (navigator.getUserMedia || navigator.mozGetUserMedia || navigator.webkitGetUserMedia || navigator.msGetUserMedia);//兼容浏览器的PeerConnection写法const PeerConnection = (window.PeerConnection ||window.webkitPeerConnection00 ||window.webkitRTCPeerConnection ||window.mozRTCPeerConnection);/*** 信令websocket* @type {WebSocket}*/var socket;function communication() {//创建PeerConnection实例var pc = new PeerConnection(iceServer);const video = document.getElementById('video');//如果检测到媒体流连接到本地,将其绑定到一个video标签上输出pc.ontrack = function async(event) {video.srcObject = event.streams[0]};const userName = document.getElementById('userName').value;const receiver = document.getElementById('receiver').value;/*** 信令websocket* @type {WebSocket}*/socket = new WebSocket("ws://192.168.31.13:6533/websocket?name=" + userName + "&receiver=" + receiver);socket.close = function () {console.log("连接关闭")}//处理到来的信令socket.onmessage = function (event) {var json = JSON.parse(event.data);//如果是一个ICE的候选,则将其加入到PeerConnection中if (json.event === "__offer") {pc.setRemoteDescription(new RTCSessionDescription(json.data.sdp));pc.onicecandidate = function (event) {if (event.candidate !== null && event.candidate !== undefined && event.candidate !== '') {socket.send(JSON.stringify({"event": "__ice_candidate","data": {"candidate": event.candidate},name: userName,receiver: receiver,}));}};var agent = navigator.userAgent.toLowerCase();if (agent.indexOf("firefox") > 0) {pc.createAnswer().then(function (desc) {pc.setLocalDescription(desc);socket.send(JSON.stringify({"event": "__answer","data": {"sdp": desc},name: userName,receiver: receiver,}));});} else {pc.createAnswer(function (desc) {pc.setLocalDescription(desc);socket.send(JSON.stringify({"event": "__answer","data": {"sdp": desc},name: userName,receiver: receiver,}));}, function (eorr) {alert(eorr);});}}};}</script></html>

WebSocket处理类

package com.webrtc.config;import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;/*** @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,* 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端*/
@ServerEndpoint(value = "/websocket")
@Component
public class WebSocket {//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。private static int onlineCount = 0;//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识private static CopyOnWriteArraySet<Map<String,WebSocket>> webSocketSet = new CopyOnWriteArraySet<Map<String,WebSocket>>();//与某个客户端的连接会话,需要通过它来给客户端发送数据private Session session;/*** 连接建立成功调用的方法* @param session  可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据* @throws EncodeException* @throws IOException */@OnOpenpublic void onOpen(Session session) throws EncodeException, IOException{this.session = session;Map<String,WebSocket> map = new HashMap<String,WebSocket>();String name = "";Map<String, List<String>> listMap = session.getRequestParameterMap();// 非主播建立连接if (listMap.get("name") != null && listMap.get("receiver") != null) {name = listMap.get("name").get(0);String receiver = listMap.get("receiver").get(0);map.put(name,this);// 通知主播发送__offer指令this.onMessage("{\"name\": \"" + name + "\",\"receiver\": \"" + receiver + "\"}", session);} else {// 主播建立连接name = listMap.get("name").get(0);map.put(name,this);}addSocket(map, name);}// 添加map 到 webSocketSet,public void addSocket(Map<String,WebSocket> map, String name) {// 删除重复的连接for(Map<String,WebSocket> item: webSocketSet){for(String key : item.keySet()){if (key.toString().equals(name)) {webSocketSet.remove(item);subOnlineCount();           //在线数减1System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());}}}webSocketSet.add(map); //加入set中addOnlineCount();           //在线数加1System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());}/*** 连接关闭调用的方法*/@OnClosepublic void onClose(){for (Map<String,WebSocket> item : webSocketSet) {for(String key : item.keySet()){if(item.get(key) == this){// 删除关闭的连接webSocketSet.remove(item);subOnlineCount();           //在线数减1System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());}}}}/*** 收到客户端消息后调用的方法* @param message 客户端发送过来的消息* @param session 可选的参数* @throws EncodeException*/@OnMessagepublic void onMessage(String message, Session session) throws EncodeException {System.out.println("来自客户端的消息:" + message);Map<String,Object> map = (Map<String, Object>) JSONObject.parse(message);// 接收人String receiver = (String) map.get("receiver");for(Map<String,WebSocket> item: webSocketSet){for(String key : item.keySet()){if (key.toString().equals(receiver.toString())) {WebSocket webSocket = item.get(key);try {webSocket.sendMessage(message);} catch (IOException e) {e.printStackTrace();continue;}}}}}/*** 发生错误时调用* @param session* @param error*/@OnErrorpublic void onError(Session session, Throwable error){System.out.println("发生错误");error.printStackTrace();}/*** 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。* @param message* @throws IOException*/public void sendMessage(String message) throws IOException{synchronized (this.session) {this.session.getBasicRemote().sendText(message);}}public static synchronized int getOnlineCount() {return onlineCount;}public static synchronized void addOnlineCount() {WebSocket.onlineCount++;}public static synchronized void subOnlineCount() {WebSocket.onlineCount--;}
}

源码地址:https://github.com/2425358736/webrtc_live.git

截图

直播页
image
观看页
[外链图片转存失败(img-4CC7hmLt-1565920868044)(https://raw.githubusercontent.com/2425358736/webrtc_live/master/guankan.png)]


http://chatgpt.dhexx.cn/article/kv1ddCaU.shtml

相关文章

一对一直播平台搭建,选择直播系统源码,这几点不容忽视

想要运营一个一对一直播平台&#xff0c;搭建网站是前提&#xff0c;而选择源码则是一切的基础&#xff0c;他关系到直播平台能否平稳运行。当然&#xff0c;源码的价格也都不便宜&#xff0c;便有很多想要自己开发平台的人&#xff0c;从各种渠道查找免费的直播源码使用&#…

直播网站云服务器搭建,什么是云服务器,直播平台搭建又该如何选择呢?

一个直播平台搭建的成功离不开云服务器&#xff0c;互联网云时代的到来&#xff0c;为平台搭建提供了便利&#xff0c;很多人都听说过但对云服务器不是很了解&#xff0c;那么现在跟大家介绍一下什么叫做云服务器&#xff0c;我们在直播平台搭建时该如何选择云服务器&#xff1…

Android如何来搭建直播平台

Android如何来搭建直播平台 目录 环境准备 ●Centos系统安装&#xff1a;请查看我的另一篇博客Java后端之路&#xff08;六&#xff09;安装Linux系统 ●git安装&#xff08;系统如果没有的话&#xff09;&#xff1a;www.cnblogs.com/imyalost/p/…&#xff0c;git配置完后记…

直播平台搭建的主要方式和开发细节

在线直播平台搭建的方式有很多&#xff0c;常见的方式主要有以下几种&#xff1a; 1. 招聘并组建研发团队&#xff0c;自主研发&#xff1a;自主研发需要硬件和维护成本&#xff0c;最主要的还是时间成本&#xff0c;一般需要至少半年的时间&#xff0c;有意向自主研发搭建直播…

一对一直播源码,一对一直播平台搭建,特色功能智能匹配

一对多直播平台上&#xff0c;互动随着播主的火热程度下降&#xff0c;而一对一直播开发特性&#xff0c;让其具备社交互动&#xff0c;更是主打的聊天&#xff0c;在社交价值上显然一对一直播开发更具有想象的空间。 从一对一直播源码效果看一对一直播是全方位实时同步&#…

java搭建直播平台_直播平台简单搭建笔记

直播平台大致流程 实时传输协议有&#xff1a;RTMP、HLS、HDL(HTTP-FLV) 编译环境 apt-get install build-essential nginx安装 安装pcre(目前最新8.44) ./configuremake && make installpcre-config --version //查看版本 下载nginx-rtmp-module源 git 下载https://gi…

直播平台搭建|实现完整直播流程,考验直播平台性能

直播平台搭建的意义是为了实现完整的直播流程&#xff1a; 前处理: 最重要的部分是实时GPU渲染美感&#xff0c;前处理中还要去除水印、时间戳等&#xff0c;这也是在直播平台必要的防范措施。实时美颜本身就相当考验APP厂商的技术经济实力&#xff0c;如何发展能够充分利用能…

直播平台怎么搭建,老司机带你了解

直播平台怎么搭建&#xff0c;老司机带你了解 1.创建前端工程 直播平台怎么搭建毫无疑问&#xff0c;搭建一个项目的框架&#xff0c;那第一步肯定是得创建一个工程啦。cmd命令&#xff0c;输入vue create mylive &#xff0c;然后一直回车就好了。然后等待一小会&#xff0c…

Nim问题和阶梯Nim(staircase nim)

Nim问题和阶梯Nim&#xff08;staircase nim&#xff09; Nim问题&#xff1a; 有若干堆石子&#xff0c;每堆石子的数量都是有限的&#xff0c;合法的移动是“选择一堆石子并拿走若干颗&#xff08;不能不拿&#xff09;”&#xff0c;如果轮到某个人时所有的石子堆都已经被…

B. Stairs(构造+规律寻找)Codeforces Round #671 (Div. 2)

原题链接&#xff1a; https://codeforces.com/contest/1419/problems 测试样例 input 4 1 8 6 1000000000000000000 output 1 2 1 30 Note In the first test case, it is possible to build only one staircase, that consists of 1 stair. It’s nice. That’s why the answ…

《中英双解》leetCode Arranging Coins (排列硬币)

Arranging Coins 难度简单182收藏分享切换为中文接收动态反馈 You have n coins and you want to build a staircase with these coins. The staircase consists of k rows where the ith row has exactly i coins. The last row of the staircase may be incomplete. Given th…

house of cat

2022强网杯 house of cat 跟着大佬的文章学习了一个新的利用手法 house of cat&#xff0c;原文链接&#xff1a;House of cat新型glibc中IO利用手法解析 && 第六届强网杯House of cat详解 利用条件&#xff1a; 1.能够任意写一个可控地址。 2.能够泄露堆地址和libc…

我谈阶梯博弈(Staircase Nim)

今天在POJ做了一道博弈题..进而了解到了阶梯博弈...下面阐述一下我对于阶梯博弈的理解.. 首先是对阶梯博弈的阐述...博弈在一列阶梯上进行...每个阶梯上放着自然数个点..两个人进行阶梯博弈...每一步则是将一个集体上的若干个点( >1 )移到前面去..最后没有点可以移动的人输.…

阶梯博弈(Staircase Nim)

阶梯博弈&#xff01;&#xff01;&#xff01;下面阐述一下我对于阶梯博弈的理解.. 首先是对阶梯博弈的阐述...博弈在一列阶梯上进行...每个阶梯上放着自然数个点..两个人进行阶梯博弈...每一步则是将一个集体上的若干个点( >1 )移到前面去..最后没有点可以移动的人输.. 如…

我谈阶梯博弈( Staircase Nim )

今天在POJ做了一道博弈题..进而了解到了阶梯博弈...下面阐述一下我对于阶梯博弈的理解.. 首先是对阶梯博弈的阐述...博弈在一列阶梯上进行...每个阶梯上放着自然数个点..两个人进行阶梯博弈...每一步则是将一个集体上的若干个点( >1 )移到前面去..最后没有点可以移动的人输.…

Scala class和case class的区别

在Scala中存在case class&#xff0c;它其实就是一个普通的class。但是它又和普通的class略有区别&#xff0c;如下&#xff1a;   1、初始化的时候可以不用new&#xff0c;当然你也可以加上&#xff0c;普通类一定需要加new&#xff1b; scala> case class Iteblog(name…

hackerrank初级篇之staircase

题目说明&#xff1a; 示例代码&#xff1a; // staircase.cpp: 定义控制台应用程序的入口点。 // // n4 // # // ## // ### //#### // //#include "stdafx.h" #include <windows.h> #include <iostream> using namespace std;void staircase( int …

Staircases

Staircases Time Limit : 2000/1000ms (Java/Other) Memory Limit : 32768/16384K (Java/Other) Total Submission(s) : 8 Accepted Submission(s) : 5 Problem Description One curious child has a set of N little bricks (5 ≤ N ≤ 500). From these bricks he buil…

自旋锁是什么?

本文内容如有错误、不足之处&#xff0c;欢迎技术爱好者们一同探讨&#xff0c;在本文下面讨论区留言&#xff0c;感谢。 文章目录 定义特点和互斥锁比较适用场景 结论混合是什么意思&#xff1f; 结尾参考资料 定义 自旋锁 spin lock 下面内容摘自维基百科 在软件工程中&…

【自旋锁】

1. 原理 PV操作原理 记录一个锁定状态(就是一个共享资源&#xff0c;基于原子操作) 2. 适用 1. 解决多cpu之间的竞态 2. 可以解决中断程序和普通程序之间的竞态(自旋锁可以用于中断上下文) 3. 加锁时间不宜过长 4. 获得自旋锁期间&#xff0c;不能进行调度(sleep) 例&#xff1…