| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133 |
- import useStore from '@/store'
- /**
- * WebRTC服务
- */
- export default class WebRtcService {
- private ICE = [
- { urls: [ 'stun:caner.top:3478' ] },
- {
- urls: 'turn:caner.top:3478',
- username: 'admin',
- credential: '123456'
- },
- { urls: 'stun:stun.l.google.com:19302' }
- ]
- public Peer: RTCPeerConnection | null = null
- private audioTack: MediaStreamTrack | null = null
- private store = useStore()
- private lastStats: RTCStatsReport | null = null
- private sleep = (ms: number) => new Promise((resolve) => { setTimeout(resolve, ms) })
- /**
- * 初始化
- * @param DOM HTMLVideoElement
- */
- initRTC(DOM: HTMLVideoElement) {
- try {
- if (this.Peer) this.distory()
- if (!DOM) { this.store.setMqttMessage({ type: 'WebRtcIntError' }); return }
- console.log('start initRTC')
- // create Peer
- this.Peer = new RTCPeerConnection({
- iceServers: this.ICE,
- bundlePolicy: 'max-bundle'
- })
- // listen state
- this.Peer.onicegatheringstatechange = () => {
- console.log('GatheringState: ', this.Peer?.iceGatheringState)
- if (this.Peer?.iceGatheringState === 'complete') {
- const answer = this.Peer?.localDescription
- this.store.setMqttMessage({ type: 'answer', data: answer?.toJSON() })
- }
- }
- // listen track
- this.Peer.ontrack = (evt) => {
- console.log('track', evt)
- // TODO safari 不支持opus ogg 音频编码
- DOM.srcObject = evt.streams[0]
- }
- // listen changestate·
- this.Peer.oniceconnectionstatechange = () => {
- const state = this.Peer?.iceConnectionState
- console.log('StateChange', state)
- // P2P 连接失败
- if (state === 'failed' || state === 'closed') {
- console.log('P2P通信失败')
- this.store.setMqttMessage({ type: 'WebRtcFailed' })
- }
- // ICE连接成功
- if (state === 'connected') {
- this.store.setMqttMessage({ type: 'WebRtcConnected', data: null })
- }
- }
- // get stats
- this.getStats()
- console.log('RTC success')
- } catch (error) {
- console.log('RTC 初始化失败', error)
- this.store.setMqttMessage({ type: 'WebRtcIntError' })
- }
- }
- /**
- * 远程静音
- * @param mute boolean
- */
- muteRemoteAudio(mute: boolean) {
- console.log('远程静音', mute)
- if (!this.audioTack) return
- this.audioTack!.enabled = mute
- }
- /**
- * 比特率|丢包率
- * @returns
- */
- async getStats() {
- if (this.Peer) {
- const stats = await this.Peer.getStats()
- let packetLoss = 0
- let bitrate = 0
- if (this.lastStats) {
- stats.forEach((report) => {
- if (report.type === 'inbound-rtp' && report.kind === 'video') {
- if (this.lastStats && this.lastStats.has(report.id)) {
- const lastReport = this.lastStats.get(report.id)
- const duration = (report.timestamp - lastReport.timestamp) / 1000
- bitrate = (report.bytesReceived - lastReport.bytesReceived) / duration / 1000
- const lostPackets = (report.packetsLost - lastReport.packetsLost)
- if ((report.packetsReceived - lastReport.packetsReceived + lostPackets) === 0) {
- packetLoss = 0
- } else {
- packetLoss = (lostPackets / (report.packetsReceived - lastReport.packetsReceived + lostPackets)) * 100
- }
- }
- }
- })
- }
- this.lastStats = stats
- this.store.setMqttMessage({ type: 'WebRtcStats', data: { packetLoss: Math.floor(packetLoss * 100) / 100, bitrate: Math.floor(bitrate * 100) / 100 } })
- } else {
- this.store.setMqttMessage({ type: 'WebRtcStats', data: { packetLoss: 0, bitrate: 0 } })
- }
- await this.sleep(1000)
- this.getStats()
- }
- distory() {
- this.Peer?.close()
- this.Peer = null
- this.audioTack = null
- }
- }
|