ZLMediaKit流媒体服务器WebRTC页面显示:不用docker ,直接一个工程部署搞定_zlmediakit webrtc 拉流
效果:
开源工具:j-media-server: 基于ZLM4J搭建的SpringBoot流媒体服务器
参考文档:ZLM4J常见问题及注意事项 - 飞书云文档
1 拉代码:
开源流媒体服务器ZLMediaKit 的Java Api实现的Java版ZLMediaKit流媒体服务器
本项目可以作为ZLM4J使用示例代码。
本项目接口风格部分兼容ZLMediaKit REST API
😁项目功能
- 接口(可以使用knife4j):
- 拉流代理接口:/index/api/addStreamProxy
- 关闭拉流代理接口:/index/api/delStreamProxy
- 推流代理接口:/index/api/addStreamPusherProxy
- 关闭推流代理接口:/index/api/delStreamPusherProxy
- 关闭流接口:/index/api/close_stream&/index/api/close_streams
- 在线流列表接口:/index/api/getMediaList
- 流详情:/index/api/getMediaInfo
- 流是否在线:/index/api/isMediaOnline
- 开始录像接口:/index/api/startRecord
- 停止录像接口:/index/api/stopRecord
- 获取录像状态接口:/index/api/isRecording
- 获取内存资源信息:/index/api/getStatistic
- 获取服务器配置:/index/api/getServerConfig
- 设置服务器配置:/index/api/setServerConfig
- 开启rtp服务:/index/api/openRtpServer
- 关闭rtp服务:/index/api/closeRtpServer
- 获取rtp服务列表:/index/api/listRtpServer
- 截图:/index/api/getSnap
- 转码(beta) :/index/api/transcode
- 开始拼接屏任务(beta) :/index/api/stack/start
- 重设拼接屏任务(beta) :/index/api/stack/rest
- 停止拼接屏任务(beta) :/index/api/stack/stop
- 开发中:😁
核心工要点:准备 配置域名和证书
3 nginx 配置好可访问- 换成你自己的域名
server{ listen 443 ssl; server_name media.xxxx.com localhost; #换成你自己的域名 ssl_certificate /usr/local/nginx/cert/cert.pem; ssl_certificate_key /usr/local/nginx/cert/key.pem; # ssl验证相关配置 ssl_session_timeout 5m; #缓存有效期 ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; #加密算法 ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #安全链接可选的加密协议 ssl_prefer_server_ciphers on; #使用服务器端的首选算法 location /webrtc/ { root html; # 对应 nginx/html/webrtc/ index webrtc.html; try_files $uri $uri/ /webrtc/webrtc.html; } location /index/api/webrtc { proxy_pass http://127.0.0.1:8899/index/api/webrtc; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection \"upgrade\"; proxy_set_header Host $host; } location /index/api/ { proxy_pass http://127.0.0.1:8899; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 允许所有 HTTP 方法 add_header \'Access-Control-Allow-Methods\' \'GET, POST, OPTIONS\' always; # 如果有跨域需求,也需要加上 CORS 头 add_header \'Access-Control-Allow-Origin\' \'*\' always; add_header \'Access-Control-Allow-Credentials\' \'true\' always; }}
前端代码:
WebRTC 调试页面 body { font-family: Arial; padding: 20px; } label, input, textarea, button { display: block; width: 100%; margin-top: 10px; } video { width: 100%; max-width: 800px; margin-top: 20px; background-color: black; } WebRTC 拉流测试
<!----> // const serverUrl = \"http://192.168.0.239:8180\"; // 替换为你的本地服务地址 // const serverUrl = \"http://192.168.0.198:8899\"; // 替换为你的本地服务地址 // const serverUrl = \"http://192.168.0.239:8899\"; // 替换为你的本地服务地址 const serverUrl = \"https://media.shenglong.com\"; // 替换为你的本地服务地址 let pc = null; var myHeaders = new Headers(); myHeaders.append(\"secret\", \"035c73f7-bb6b-4889-a715-d9eb2d1925cc\"); myHeaders.append(\"User-Agent\", \"Apifox/1.0.0 (https://apifox.com)\"); myHeaders.append(\"Accept\", \"*/*\"); myHeaders.append(\"Host\", serverUrl); myHeaders.append(\"Connection\", \"keep-alive\"); myHeaders.append(\"Origin\", serverUrl); // 添加 origin myHeaders.append(\"Referer\", serverUrl); // 添加 referer var requestOptions = { method: \'GET\', headers: myHeaders, mode: \'cors\', // 显式声明使用 CORS credentials: \'same-origin\', // 如果需要携带 Cookie 可以设为 include redirect: \'follow\' }; async function generateOffer() { console.log(\"【DEBUG】generateOffer 开始\"); const app = document.getElementById(\'app\').value.trim(); const stream = document.getElementById(\'stream\').value.trim(); const type = document.getElementById(\'type\').value.trim(); const pcSdpInput = document.getElementById(\'pcSdp\'); const videoElement = document.getElementById(\'videoElement\'); pc = new RTCPeerConnection(); // 添加调试日志 pc.onicecandidate = async (event) => { console.log(\"【ICE Candidate】新候选地址:\", event.candidate); if (event.candidate) { // 构造请求头,避免使用多个 Content-Type const headers = new Headers(); headers.append(\"secret\", \"035c73f7-bb6b-4889-a715-d9eb2d1925cc\"); headers.append(\"Origin\", serverUrl); headers.append(\"Referer\", serverUrl); headers.append(\"Content-Type\", \"application/json\"); // 使用 text/plain 避免 MIME 错误 // 将 candidate 发送到信令服务器 fetch(`${serverUrl}/index/api/webrtc_ice`, { method: \'POST\', headers: headers, body: JSON.stringify(event.candidate) }); } }; pc.ontrack = (event) => { console.log(\"【Track】收到远程流\"); if (!videoElement.srcObject) { videoElement.srcObject = event.streams[0]; } }; pc.onconnectionstatechange = () => { console.log(\"【连接状态变化】\", pc.connectionState); }; // 添加音视频收发器 pc.addTransceiver(\"video\", {direction: \'recvonly\'}); pc.addTransceiver(\"audio\", {direction: \'recvonly\'}); try { // 创建 Offer const offer = await pc.createOffer(); console.log(\"✅ 成功创建 Offer SDP\"); // 设置本地描述 await pc.setLocalDescription(offer); console.log(\"📌 已设置 localDescription\"); const response = await fetch(`${serverUrl}/index/api/webrtc?app=${encodeURIComponent(app)}&stream=${encodeURIComponent(stream)}&type=play`, { method: \'POST\', headers: { \'Content-Type\': \'text/plain;charset=UTF-8\', \'secret\': \'035c73f7-bb6b-4889-a715-d9eb2d1925cc\', \'Origin\': serverUrl, \'Referer\': serverUrl }, body: pc.localDescription.sdp }); const data = await response.json(); // 解析为 JSON console.log(\"📨 接口响应数据:\", data); if (data.code === 0 && data.sdp) { const answerDesc = new RTCSessionDescription({ type: \'answer\', sdp: data.sdp // 使用返回的 SDP 构造 Answer }); await pc.setRemoteDescription(answerDesc); console.log(\"✅ 成功设置 remoteDescription\"); } else { alert(\"❌ 获取 Answer SDP 失败:\" + (data.message || data.data)); } } catch (error) { console.error(\"⚠️ 发生错误:\", error); alert(\"发生错误:\" + error.message); } } // 新增函数:关闭播放 function stopPlayback() { console.log(\"【关闭播放】开始\"); if (pc) { pc.close(); pc = null; } const videoElement = document.getElementById(\'videoElement\'); if (videoElement.srcObject) { videoElement.srcObject = null; } alert(\"✅ 播放已关闭\"); } async function testBackend() { myHeaders.append(\"Content-Type\", \"application/json\"); const url = `${serverUrl}/index/api/getMediaList?app=live&schema=rtsp&stream=camera_001&secret=035c73f7-bb6b-4889-a715-d9eb2d1925cc`; try { const res = await fetch(url, requestOptions); const data = await res.json(); console.log(\"🔗 后端接口返回:\", data); alert(\"✅ 后端接口可访问\"); } catch (e) { console.error(\"❌ 连接失败:\", e); alert(\"❌ 后端接口不可访问,请检查服务是否运行\"); } }
springboot配置文件:
server: port: 8899media: ##目前只增加部分重要配置,更多配置请自行配置 thread_num: 8 rtmp_port: 1935 rtsp_port: 8554 http_port: 8180 rtc_port: 8000 rtc_host: localhost extern_ip: 192.168.0.198,192.168.0.239,192.168.0.243 auto_close: 0 # 改为 1 表示不主动关闭无人看的流 streamNoneReaderDelayMS: 30000 maxStreamWaitMS: 1500 enable_ts: 1 enable_hls: 1 enable_fmp4: 0 enable_rtsp: 1 enable_rtmp: 1 enable_mp4: 0 enable_hls_fmp4: 1 enable_audio: 1 mp4_as_player: 1 mp4_max_second: 3600 mp4_save_path: D:/media hls_save_path: D:/media rootPath: D:/media hls_demand: 0 broadcastRecordTs: 1 segDur: 3 segNum: 3 rtsp_demand: 0 rtmp_demand: 0 ts_demand: 1 fmp4_demand: 1 log_level: 2 log_mask: 1 #1console 2file 4fun log_file_days: 1 log_path: D:/media
conf.ini配置:
[rtc]#rtc播放推流、播放超时时间timeoutSec=15#本机对rtc客户端的可见ip,作为服务器时一般为公网ip,置空时,会自动获取网卡ipexternIP=192.168.0.198,192.168.0.239,192.168.0.243#rtc udp服务器监听端口号,所有rtc客户端将通过该端口传输stun/dtls/srtp/srtcp数据,#该端口是多线程的,同时支持客户端网络切换导致的连接迁移#需要注意的是,如果服务器在nat内,需要做端口映射时,必须确保外网映射端口跟该端口一致port=8000#设置remb比特率,非0时关闭twcc并开启remb。该设置在rtc推流时有效,可以控制推流画质rembBitRate=1000000
后端 :
安装好sdp
打包后启动,点生成offer并播放
核心工要点:准备 配置域名和证书
如果有问题参考: