源码(vue)
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 = null
onMounted(() => {
if (!videoElement.value) return
player = 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
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) return
if (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.hostname
let 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.port
if (!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 = null
resolve(respone)
}
}
xhr.open('POST', url, true)
xhr.timeout = 5e3
xhr.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.url
if (!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 = element
addStyles(this.container, {
display: 'inline-block',
position: 'relative',
minWidth: '80px',
minHeight: '80px'
})
this.video = document.createElement('video')
this.video.width = 960
this.video.height = 540
addStyles(this.video, { display: 'block', width: '100%' })
this.container.appendChild(this.video)
this.playButton = document.createElement('div')
this.playButton.innerHTML = VideoElement.PLAY_BUTTON
addStyles(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.player
if (options.poster && !options.autoplay) {
options.decodeFirstFrame = false
this.poster = new Image()
this.poster.src = options.poster
this.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.container
if (options.autoplay) {
this.unmuteButton = document.createElement('div')
this.unmuteButton.innerHTML = VideoElement.UNMUTE_BUTTON
addStyles(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 = null
this.autoplay = !!options.autoplay || false
this.paused = true
if (this.autoplay) this.options.video.muted = true
this.startLoading()
}
Player.prototype.startLoading = function () {
let _self = this
if (_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 || 1985
let api = _self.urlParams.user_query.play || '/rtc/v1/play/'
if (api.lastIndexOf('/') != api.length - 1) {
api += '/'
}
let url = 'http://' + _self.urlParams.server + ':' + port + api
for (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 = null
this.isPlaying = false
this.paused = true
this.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 = true
this.options.video.play()
if (this.options.onPlay) {
this.options.onPlay(this)
}
}
}
return Player
})()