当前位置: 首页 > news >正文

浏览器播放 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
})()

相关文章:

  • 通过自签名ssl证书进行js注入的技术,适合注入electron开发的app
  • 欧拉系统离线部署docker
  • window 安装 wsl + cuda + Docker
  • 二、【环境搭建篇】:Django 和 Vue3 开发环境准备
  • Axure中使用动态面板实现图标拖动交换位置
  • 2025年通信系统与智能计算国际学术会议(CSIC2025)
  • Oracle ASM Rebalance Power 了解
  • Easylogging使用笔记
  • 软考 系统架构设计师系列知识点之杂项集萃(64)
  • 从无标注的病理切片中自动提取临床相关的组织形态表型簇,探索其与患者预后、分子表型以及治疗反应的关联
  • DAY 30 超大力王爱学Python
  • MES管理系统电子看板驱动企业智能制造
  • Solana 快照功能全解析及其在空投、治理与激励中的应用
  • FactoryBean是什么,Spring如何实现FactoryBean的?
  • 数据结构与算法学习笔记(Acwing 提高课)----动态规划·状态机模型
  • 19 C 语言位运算、赋值、条件、逗号运算符详解:涵盖运算符优先级与复杂表达式计算过程分析
  • POSTGRESQL 初体验
  • GitLab部署
  • 前端mjs和js文件区别,mjs和cjs区别---.es.js和.mjs的区别
  • Jules 从私有预览阶段推向全球公测
  • 美发布“金穹”导弹防御系统发展规划
  • 商务部新闻发言人就美国企图全球禁用中国先进计算芯片发表谈话
  • B站一季度净亏损收窄99%:游戏营收大增76%,AI类广告收入增近4倍
  • LPR名副其实吗?如果有所偏离又该如何调整?
  • 梅花奖在上海|秦海璐:演了15年《四世同堂》,想演一辈子
  • 芬兰西南部两架直升机相撞坠毁,第一批救援队已抵达现场