浏览器播放 WebRTC 视频流
源码(vue)
<template><video ref="videoElement" class="video" autoplay muted playsinline></video>
</template><script setup lang="ts">import { onBeforeUnmount, onMounted, ref } from 'vue'import { JSWebrtc } from '@/utils/jswebrtc.min.js'const videoElement = ref<HTMLVideoElement | null>(null)let player: JSWebrtc.Player | null = nullonMounted(() => {if (!videoElement.value) returnplayer = new JSWebrtc.Player('webrtc://192.168.20.222/live/34020000001320000002', {video: videoElement.value,autoplay: true,onPlay: (obj: any) => {console.log('start play', obj)},onError: (error: Error) => {console.error('Playback error:', error)}})})onBeforeUnmount(() => {player?.destroy()player = null})
</script>
jswebrtc.min.js
export var JSWebrtc = {Player: null,VideoElement: null,CreateVideoElements: function () {let elements = document.querySelectorAll('.jswebrtc')for (let i = 0; i < elements.length; i++) {new JSWebrtc.VideoElement(elements[i])}},FillQuery: function (query_string, obj) {obj.user_query = {}if (query_string.length == 0) returnif (query_string.indexOf('?') >= 0) query_string = query_string.split('?')[1]let queries = query_string.split('&')for (let i = 0; i < queries.length; i++) {let query = queries[i].split('=')obj[query[0]] = query[1]obj.user_query[query[0]] = query[1]}if (obj.domain) obj.vhost = obj.domain},ParseUrl: function (rtmp_url) {let a = document.createElement('a')a.href = rtmp_url.replace('rtmp://', 'http://').replace('webrtc://', 'http://').replace('rtc://', 'http://')let vhost = a.hostnamelet app = a.pathname.substr(1, a.pathname.lastIndexOf('/') - 1)let stream = a.pathname.substr(a.pathname.lastIndexOf('/') + 1)app = app.replace('...vhost...', '?vhost=')if (app.indexOf('?') >= 0) {let params = app.substr(app.indexOf('?'))app = app.substr(0, app.indexOf('?'))if (params.indexOf('vhost=') > 0) {vhost = params.substr(params.indexOf('vhost=') + 'vhost='.length)if (vhost.indexOf('&') > 0) {vhost = vhost.substr(0, vhost.indexOf('&'))}}}if (a.hostname == vhost) {let re = /^(\d+)\.(\d+)\.(\d+)\.(\d+)$/if (re.test(a.hostname)) vhost = '__defaultVhost__'}let schema = 'rtmp'if (rtmp_url.indexOf('://') > 0) schema = rtmp_url.substr(0, rtmp_url.indexOf('://'))let port = a.portif (!port) {if (schema === 'http') {port = 80} else if (schema === 'https') {port = 443} else if (schema === 'rtmp') {port = 1935} else if (schema === 'webrtc' || schema === 'rtc') {port = 1985}}let ret = {url: rtmp_url,schema: schema,server: a.hostname,port: port,vhost: vhost,app: app,stream: stream}JSWebrtc.FillQuery(a.search, ret)return ret},HttpPost: function (url, data) {return new Promise(function (resolve, reject) {let xhr = new XMLHttpRequest()xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {let respone = JSON.parse(xhr.responseText)xhr.onreadystatechange = new Function()xhr = nullresolve(respone)}}xhr.open('POST', url, true)xhr.timeout = 5e3xhr.responseType = 'text'xhr.setRequestHeader('Content-Type', 'application/json')xhr.send(data)})}
}
if (document.readyState === 'complete') {JSWebrtc.CreateVideoElements()
} else {document.addEventListener('DOMContentLoaded', JSWebrtc.CreateVideoElements)
}
JSWebrtc.VideoElement = (function () {'use strict'let VideoElement = function (element) {let url = element.dataset.urlif (!url) {throw 'VideoElement has no `data-url` attribute'}let addStyles = function (element, styles) {for (let name in styles) {element.style[name] = styles[name]}}this.container = elementaddStyles(this.container, {display: 'inline-block',position: 'relative',minWidth: '80px',minHeight: '80px'})this.video = document.createElement('video')this.video.width = 960this.video.height = 540addStyles(this.video, { display: 'block', width: '100%' })this.container.appendChild(this.video)this.playButton = document.createElement('div')this.playButton.innerHTML = VideoElement.PLAY_BUTTONaddStyles(this.playButton, {zIndex: 2,position: 'absolute',top: '0',bottom: '0',left: '0',right: '0',maxWidth: '75px',maxHeight: '75px',margin: 'auto',opacity: '0.7',cursor: 'pointer'})this.container.appendChild(this.playButton)let options = { video: this.video }for (let option in element.dataset) {try {options[option] = JSON.parse(element.dataset[option])} catch (err) {options[option] = element.dataset[option]}}this.player = new JSWebrtc.Player(url, options)element.playerInstance = this.playerif (options.poster && !options.autoplay) {options.decodeFirstFrame = falsethis.poster = new Image()this.poster.src = options.posterthis.poster.addEventListener('load', this.posterLoaded)addStyles(this.poster, {display: 'block',zIndex: 1,position: 'absolute',top: 0,left: 0,bottom: 0,right: 0})this.container.appendChild(this.poster)}if (!this.player.options.streaming) {this.container.addEventListener('click', this.onClick.bind(this))}if (options.autoplay) {this.playButton.style.display = 'none'}if (this.player.audioOut && !this.player.audioOut.unlocked) {let unlockAudioElement = this.containerif (options.autoplay) {this.unmuteButton = document.createElement('div')this.unmuteButton.innerHTML = VideoElement.UNMUTE_BUTTONaddStyles(this.unmuteButton, {zIndex: 2,position: 'absolute',bottom: '10px',right: '20px',width: '75px',height: '75px',margin: 'auto',opacity: '0.7',cursor: 'pointer'})this.container.appendChild(this.unmuteButton)unlockAudioElement = this.unmuteButton}this.unlockAudioBound = this.onUnlockAudio.bind(this, unlockAudioElement)unlockAudioElement.addEventListener('touchstart', this.unlockAudioBound, false)unlockAudioElement.addEventListener('click', this.unlockAudioBound, true)}}VideoElement.prototype.onUnlockAudio = function (element, ev) {if (this.unmuteButton) {ev.preventDefault()ev.stopPropagation()}this.player.audioOut.unlock(function () {if (this.unmuteButton) {this.unmuteButton.style.display = 'none'}element.removeEventListener('touchstart', this.unlockAudioBound)element.removeEventListener('click', this.unlockAudioBound)}.bind(this))}VideoElement.prototype.onClick = function (ev) {if (this.player.isPlaying) {this.player.pause()this.playButton.style.display = 'block'} else {this.player.play()this.playButton.style.display = 'none'if (this.poster) {this.poster.style.display = 'none'}}}VideoElement.PLAY_BUTTON ='<svg style="max-width: 75px; max-height: 75px;" ' +'viewBox="0 0 200 200" alt="Play video">' +'<circle cx="100" cy="100" r="90" fill="none" ' +'stroke-width="15" stroke="#fff"/>' +'<polygon points="70, 55 70, 145 145, 100" fill="#fff"/>' +'</svg>'VideoElement.UNMUTE_BUTTON ='<svg style="max-width: 75px; max-height: 75px;" viewBox="0 0 75 75">' +'<polygon class="audio-speaker" stroke="none" fill="#fff" ' +'points="39,13 22,28 6,28 6,47 21,47 39,62 39,13"/>' +'<g stroke="#fff" stroke-width="5">' +'<path d="M 49,50 69,26"/>' +'<path d="M 69,50 49,26"/>' +'</g>' +'</svg>'return VideoElement
})()
JSWebrtc.Player = (function () {'use strict'let Player = function (url, options) {this.options = options || {}if (!url.match(/^webrtc?:\/\//)) {throw 'JSWebrtc just work with webrtc'}if (!this.options.video) {throw 'VideoElement is null'}this.urlParams = JSWebrtc.ParseUrl(url)this.pc = nullthis.autoplay = !!options.autoplay || falsethis.paused = trueif (this.autoplay) this.options.video.muted = truethis.startLoading()}Player.prototype.startLoading = function () {let _self = thisif (_self.pc) {_self.pc.close()}_self.pc = new RTCPeerConnection(null)_self.pc.ontrack = function (event) {_self.options.video['srcObject'] = event.streams[0]}_self.pc.addTransceiver('audio', { direction: 'recvonly' })_self.pc.addTransceiver('video', { direction: 'recvonly' })_self.pc.createOffer().then(function (offer) {return _self.pc.setLocalDescription(offer).then(function () {return offer})}).then(function (offer) {return new Promise(function (resolve, reject) {let port = _self.urlParams.port || 1985let api = _self.urlParams.user_query.play || '/rtc/v1/play/'if (api.lastIndexOf('/') != api.length - 1) {api += '/'}let url = 'http://' + _self.urlParams.server + ':' + port + apifor (let key in _self.urlParams.user_query) {if (key != 'api' && key != 'play') {url += '&' + key + '=' + _self.urlParams.user_query[key]}}let data = {api: url,streamurl: _self.urlParams.url,clientip: null,sdp: offer.sdp,tid: Number(parseInt(new Date().getTime() * Math.random() * 100)).toString(16).slice(0, 7)}// console.log('offer:1111111111111 ' + JSON.stringify(data))JSWebrtc.HttpPost(url, JSON.stringify(data)).then(function (res) {// console.log('answer: ' + JSON.stringify(res))resolve(res.sdp)},function (rej) {reject(rej)})})}).then(function (answer) {return _self.pc.setRemoteDescription(new RTCSessionDescription({ type: 'answer', sdp: answer }))}).catch(function (reason) {throw reason})if (this.autoplay) {this.play()}}Player.prototype.play = function (ev) {if (this.animationId) {return}this.animationId = requestAnimationFrame(this.update.bind(this))this.paused = false}Player.prototype.pause = function (ev) {if (this.paused) {return}cancelAnimationFrame(this.animationId)this.animationId = nullthis.isPlaying = falsethis.paused = truethis.options.video.pause()if (this.options.onPause) {this.options.onPause(this)}}Player.prototype.stop = function (ev) {this.pause()}Player.prototype.destroy = function () {this.pause()this.pc && this.pc.close() && this.pc.destroy()this.audioOut && this.audioOut.destroy()}Player.prototype.update = function () {this.animationId = requestAnimationFrame(this.update.bind(this))if (this.options.video.readyState < 4) {return}if (!this.isPlaying) {this.isPlaying = truethis.options.video.play()if (this.options.onPlay) {this.options.onPlay(this)}}}return Player
})()
相关文章:
浏览器播放 WebRTC 视频流
源码(vue) <template><video ref"videoElement" class"video" autoplay muted playsinline></video> </template><script setup lang"ts">import { onBeforeUnmount, onMounted, ref } fr…...
从零开始:使用 PyTorch 构建深度学习网络
从零开始:使用 PyTorch 构建深度学习网络 目录 PyTorch 简介环境配置PyTorch 基础构建神经网络训练模型评估与测试案例实战:手写数字识别进阶技巧常见问题解答 PyTorch 简介 PyTorch 是一个开源的深度学习框架,由 Facebook(现…...

分类算法 Kmeans、KNN、Meanshift 实战
任务 1、采用 Kmeans 算法实现 2D 数据自动聚类,预测 V180,V260 数据类别; 2、计算预测准确率,完成结果矫正 3、采用 KNN、Meanshift 算法,重复步骤 1-2 代码工具:jupyter notebook 视频资料 无监督学习ÿ…...
【razor】回环结构导致的控制信令错位:例如发送端收到 SR的问题
一、razor的echo程序 根据对 yuanrongxi/razor 仓库的代码和 echo 测试程序相关实现的分析,下面详细解读 echo 程序中 RTCP sender report(SR)、receiver report(RR)回显的问题及项目的解决方式。 1. 问题背景 在 RTP/RTCP 体系下,SR(Sender Report)由发送端周期性发…...

网络安全之身份验证绕过漏洞
漏洞简介 CrushFTP 是一款由 CrushFTP LLC 开发的强大文件传输服务器软件,支持FTP、SFTP、HTTP、WebDAV等多种协议,为企业和个人用户提供安全文件传输服务。近期,一个被编号为CVE-2025-2825的严重安全漏洞被发现,该漏洞影响版本1…...

MySQL 主从复制搭建全流程:基于 Docker 与 Harbor 仓库
一、引言 在数据库管理中,MySQL 主从复制是一种非常重要的技术,它可以实现数据的备份、读写分离,减轻主数据库的压力。本文将详细介绍如何使用 Docker 和 Harbor 仓库来搭建 MySQL 主从复制环境,适合刚接触数据库和 Docker 的新手…...
vscode打开vue + element项目
好嘞,我帮你详细整理一个用 VS Code 来可视化开发 Vue Element UI 的完整步骤,让你能舒服地写代码、预览界面、调试和管理项目。 用 VS Code 可视化开发 Vue Element UI 全流程指南 一、准备工作 安装 VS Code 官网下载安装:https://code…...

Django框架的前端部分使用Ajax请求一
Ajax请求 目录 1.ajax请求使用 2.增加任务列表功能(只有查看和新增) 3.代码展示集合 这篇文章, 要开始讲关于ajax请求的内容了。这个和以前文章中写道的Vue框架里面的axios请求, 很相似。后端代码, 会有一些细节点, 跟前几节文章写的有些区别。 一、ajax请求使用 我们先…...

cmd如何从C盘默认路径切换到D盘某指定目录
以从C盘cmd打开后的默认目录切换到目录"D:\Program Files\MySQL\MySQL Server 8.0\bin\mysqld"为例 打开cmd 首先点击开始键,搜索cms,右键以管理员身份运行打开管理员端的命令行提示符 1、首先要先切换到D盘 直接输入D:然后回车就可以&…...
693SJBH基于.NET的题库管理系统
计算机与信息学院 本科毕业论文(设计)开题报告 论文中文题目 基于asp.net的题库管理系统设计与实现 论文英文题目 Asp.net based database management system design and Implementation 学生姓名 专业班级 XXXXXX专业08 班 ⒈选题的背景和意…...
[Vue]跨组件传值
父子组件传值 详情可以看文章 跨组件传值 Vue 的核⼼是单向数据流。所以在父子组件间传值的时候,数据通常是通过属性从⽗组件向⼦组件,⽽⼦组件通过事件将数据传递回⽗组件。多层嵌套场景⼀般使⽤链式传递的⽅式实现provideinject的⽅式适⽤于需要跨层级…...

每日Prompt:实物与手绘涂鸦创意广告
提示词 一则简约且富有创意的广告,设置在纯白背景上。 一个真实的 [真实物体] 与手绘黑色墨水涂鸦相结合,线条松散而俏皮。涂鸦描绘了:[涂鸦概念及交互:以巧妙、富有想象力的方式与物体互动]。在顶部或中部加入粗体黑色 [广告文案…...

学习笔记:黑马程序员JavaWeb开发教程(2025.4.8)
12.11 登录校验-Filter-详解(过滤器链) 过滤器链及其执行顺序,一个Filter一个过滤器链,类名排名越靠前(按照ABC这样的顺序),就先执行谁 12.12 登录校验-Filter-登录校验过滤器 获取请求参数&…...
vue3 在线播放语音 mp3
播放、暂停、停止 <template><div><button click"togglePlay">{{ isPlaying ? "暂停" : "播放" }}</button><button click"stopAudio">停止</button><p>播放进度:{{ Math.rou…...

Ubuntu部署私有Gitlab
这个东西安装其实挺简单的,但是因为我这边迁移了数据目录和使用自己安装的 nginx 代理还是踩了几个坑,所以大家可以注意下 先看下安装 # 先安装必要组件 sudo apt update sudo apt install -y curl openssh-server ca-certificates tzdata perl# 添加gi…...

genicamtl_lmi_gocator_objectmodel3d
目录 一、在halcon中找不到genicamtl_lmi_gocator_objectmodel3d例程二、在halcon中运行genicamtl_lmi_gocator_objectmodel3d,该如何配置三、代码分段详解(一)传感器连接四、代码分段详解(二)采集图像并显示五、代码分段详解(三)坐标变换六、常见问题一、在halcon中找不…...

[LevelDB]LevelDB版本管理的黑魔法-为什么能在不锁表的情况下管理数据?
文章摘要 LevelDB的日志管理系统是怎么通过双链表来进行数据管理为什么LevelDB能够在不锁表的情况下进行日志新增 适用人群: 对版本管理机制有开发诉求,并且希望参考LevelDB的版本开发机制。数据库相关从业者的专业人士。计算机狂热爱好者,对计算机的…...

bisheng系列(二)- 本地部署(前后端)
一、导读 环境:Ubuntu 24.04、open Euler 23.03、Windows 11、WSL 2、Python 3.10 、bisheng 1.1.1 背景:需要bisheng二开商用,故而此处进行本地部署,便于后期调试开发 时间:20250519 说明:bisheng前后…...

【网络编程】十二、两万字详解 IP协议
文章目录 Ⅰ. 基本概念1、网络层解决的问题2、保证数据可靠的从一台主机送到另一台主机的前提3、路径选择4、主机和路由器的区别 Ⅱ. IP协议格式IP如何将报头与有效载荷进行分离?IP如何决定将有效载荷交付给上层的哪一个协议?理解socket编程 Ⅲ. 分片与组…...

Linux探秘:驾驭开源,解锁高效能——基础指令
♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ ✨✨✨✨✨✨ 个…...

WebSocket解决方案的一些细节阐述
今天我们来看看WebSocket解决方案的一些细节问题: 实际上,集成WebSocket的方法都有相关的工程挑战,这可能会影响项目成本和交付期限。在最简单的层面上,构建 WebSocket 解决方案似乎是添加接收实时更新功能的前进方向。但是&…...
大数据量下Redis分片的5种策略
随着业务规模的增长,单一Redis实例面临着内存容量、网络带宽和计算能力的瓶颈。 分片(Sharding)成为扩展Redis的关键策略,它将数据分散到多个Redis节点上,每个节点负责整个数据集的一个子集。 本文将分享5种Redis分片策略。 1. 取模分片(M…...
muduo库TcpServer模块详解
Muduo库核心模块——TcpServer Muduo库的TcpServer模块是一个基于Reactor模式的高性能TCP服务端实现,负责管理监听端口、接受新连接、分发IO事件及处理连接生命周期。 一、核心组件与职责 Acceptor 监听指定端口,接受新连接,通过epoll监听l…...

Java 代码生成工具:如何快速构建项目骨架?
Java 代码生成工具:如何快速构建项目骨架? 在 Java 项目开发过程中,构建项目骨架是一项繁琐但又基础重要的工作。幸运的是,Java 领域有许多代码生成工具可以帮助我们快速完成这一任务,大大提高开发效率。 一、代码生…...

Nginx核心服务
一.正向代理 正向代理(Forward Proxy)是一种位于客户端和原始服务器之间的代理服务器,其主要作用是将客户端的请求转发给目标服务器,并将响应返回给客户端 Nginx 的 正向代理 充当客户端的“中间人”,代…...

第22天-Python ttkbootstrap 界面美化指南
环境安装 pip install ttkbootstrap 示例1:基础主题切换器 import ttkbootstrap as ttk from ttkbootstrap.constants import *def create_theme_switcher():root = ttk.Window(title="主题切换器", themename="cosmo")def change_theme():selected = t…...

Kubernetes控制平面组件:Kubelet详解(七):容器网络接口 CNI
云原生学习路线导航页(持续更新中) kubernetes学习系列快捷链接 Kubernetes架构原则和对象设计(一)Kubernetes架构原则和对象设计(二)Kubernetes架构原则和对象设计(三)Kubernetes控…...

web应用技术第6次课---Apifox的使用
Apifox - API 文档、调试、Mock、测试一体化协作平台。拥有接口文档管理、接口调试、Mock、自动化测试等功能,接口开发、测试、联调效率,提升 10 倍。最好用的接口文档管理工具,接口自动化测试工具。 第一个问题:为什么需要用Apif…...
Flutter与Kotlin Multiplatform(KMP)深度对比及鸿蒙生态适配解析
Flutter 与 Kotlin Multiplatform(KMP)深度对比及鸿蒙生态适配解析 在跨平台开发领域,Flutter 与 Kotlin Multiplatform(KMP)代表了两种不同的技术路线:前者以 “统一 UI 体验” 为核心,后者以…...
Predict Podcast Listening Time-(回归+特征工程+xgb)
Predict Podcast Listening Time 题意: 给你每个播客的信息,让你预测观众的聆听时间。 数据处理: 1.构造新特征收听效率进行分组 2.对数据异常处理 3.对时间情绪等进行数值编码 4.求某特征值求多项式特征 5.生成特征组合 6.交叉验证并enc…...