喊话

<template>
<div>
<Music v-if="open"/>
<a-modal
:visible="isLoadLayer"
:width="340"
:maskClosable="false"
:footer="null"
>
<a-icon type="loading" class="loadIcon"/>
</a-modal>
</div>
</template>

<script>
import Music from './miusic'

export default {
name: 'MediatalkIndex',
components: { Music },
props: {},
data () {
return {
ws: undefined, // talk websocket
audioCtx: null,
currentId: '',
isLoadLayer: false,
open: false
}
},
computed: { },
watch: {},
created: function () {},
mounted: function () { },
methods: {
// id 喊话设备终端id ;index 判断是哪个窗口在喊话,以便赋予喊话跳动样式, -1无喊话样式
initTalk (id, index) {
if (this.ws) {
this.ws.close()
this.ws = undefined
}
const ws = new WebSocket(‘url喊话地址’)
ws.binaryType = 'arraybuffer'
ws.onmessage = ev => {
this.open = false
this.$emit('funMedia', { 'open': false, 'active': -1 }) // 连接失败 隐藏加载层,open是否成功喊话标志,active喊话跳动样式
this.$notification.info({
message: '提示',
description: '已有人在进行喊话操作!',
duration: 3
})
}
ws.onopen = ev => {
this.open = true
this.$emit('funMedia', { 'open': true, 'active': index }) // 连接成功 隐藏加载层,open是否成功喊话标志,active喊话跳动样式
const bytes = this.strToUint8(id)
const lengthByte = new Int8Array(new Int16Array([bytes.length]).buffer)
const register = new Int8Array(5 + bytes.length)
register[0] = 0
register.set(lengthByte, 1)
register.set(bytes, 5)
this.ws.send(register)

this.sendAudio(ws)
}
ws.onclose = _ => { }
ws.onerror = ie => {
this.$notification.error({
message: '失败',
description: '连接失败!',
duration: 3
})
this.open = false
this.$emit('funMedia', { 'open': false, 'active': -1 }) // 连接失败 隐藏加载层,open是否成功喊话标志,active喊话跳动样式
}
this.ws = ws
},
sendAudio (websocket) {
this.audioCtx && this.audioCtx.close()
const bufferSize = 4096
const targetSampleRate = 48000
const frameBodyLength = 4608
let audios = new Int8Array(0)
const channelNum = 2
if (navigator.mediaDevices) {
// 音频设备不存在会有错误打印
const constraints = {
audio: {
autoGainControl: false,
googAutoGainControl: false,
googAutoGainControl2: false,
echoCancellation: true,
noiseSuppression: true,
googEchoCancellation: true,
googEchoCancellation2: true,
googNoiseSuppression: true,
googNoiseSuppression2: true,
channelCount: channelNum,
sampleRate: targetSampleRate
// sampleSize: 16
},
video: false
}
navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
// that.audioStream = stream
const newAudioCtx = new AudioContext({ sampleRate: targetSampleRate })
const source = newAudioCtx.createMediaStreamSource(stream)
const recorder = newAudioCtx.createScriptProcessor(bufferSize, channelNum, channelNum)
const gainNode = newAudioCtx.createGain()
this.audioCtx = newAudioCtx
gainNode.gain.value = 1
source.connect(gainNode)
gainNode.connect(recorder)
recorder.connect(this.audioCtx.destination)
// 音频帧长度
recorder.onaudioprocess = function (ev) {

const offlineAudioContext = new OfflineAudioContext(
channelNum,
bufferSize,
targetSampleRate
)
const offSource = offlineAudioContext.createBufferSource()
offSource.connect(offlineAudioContext.destination)
offSource.buffer = ev.inputBuffer
offSource.start()
offlineAudioContext.startRendering().then(function (buffer) {
// console.log(buffer);
// const channel2 = renderBuffer.getChannelData(1);
offSource.stop()
if (websocket && websocket.readyState === WebSocket.OPEN) {
const channelNum = buffer.numberOfChannels
const length = buffer.length
const samples = new Int16Array(length * channelNum)
for (let channel = 0; channel < channelNum; channel++) {
let offset = channel
const channelData = buffer.getChannelData(channel)
for (let i = 0; i < length; i++) {
const s = Math.max(-1, Math.min(1, channelData[i]))
if (s < 0) {
samples[offset] = s * 0x8000
} else {
samples[offset] = s * 0x7fff
}
offset += channelNum
}
}
const int8Buffer = new Int8Array(samples.buffer)
let byteLength = audios.byteLength
const newAudios = new Int8Array(byteLength + int8Buffer.byteLength)
newAudios.set(audios)
newAudios.set(int8Buffer, byteLength)
audios = newAudios
byteLength = audios.byteLength
const framePageSize = parseInt(byteLength / frameBodyLength + '')
for (let i = 0; i < framePageSize; i++) {
const audio = audios.slice(
i * frameBodyLength,
i * frameBodyLength + frameBodyLength
)
const audioFrame = new Int8Array(frameBodyLength)
audioFrame.set(audio)
websocket.send(audioFrame)
}
audios = audios.slice(framePageSize * frameBodyLength, audios.byteLength)
}
})
}
})
}
},
_doSend (result) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
const length = result.length
const int16BitData = new Int16Array(length)
for (let i = 0; i < length; i++) {
const s = Math.max(-1, Math.min(1, result[i]))
if (s < 0) {
int16BitData[i] = s * 0x8000
} else {
int16BitData[i] = s * 0x7FFF
}
}
const int8Buffer = new Int8Array(int16BitData.buffer)
const audioFrame = new Int8Array(5 + int8Buffer.byteLength)
audioFrame[0] = 1
audioFrame.set(new Int8Array(new Int32Array([int8Buffer.length]).buffer), 1)
audioFrame.set(int8Buffer, 5)
this.ws.send(audioFrame)
}
},
strToUint8 (str) {
const arr = []
for (let i = 0; i < str.length; i++) {
arr.push(str.charCodeAt(i))
}
return new Uint8Array(arr)
},
// 多个喊话界面 index 标记是哪一个
startTalk (id, index) {
this.currentId = id
this.isLoadLayer = true
xxxx({ 'id':喊话设备}).then(res => {
                    if (res.code === '0' && res.data === true) {
this.isLoadLayer = false
this.initTalk(id, index)
} else {
this.isLoadLayer = false
this.$notification.error({
message: '失败',
description: res.msg || '连接失败!',
duration: 3
})
}
})
},
// 关闭喊话
closeTalk (currentId) {
           xxxx({ 'id':currentId}).then(res => {
                    this.open = false
this.$emit('funMedia', { 'open': false, 'active': -1 }) // 连接失败 隐藏加载层 lay喊话样式加载图层,open是否成功喊话标志,active喊话跳动样式
if (this.ws) {
this.ws.close()
this.ws = undefined
}
this.audioCtx && this.audioCtx.close()
})
}
},
beforeDestroy () {
if (this.ws) {
this.closeTalk(this.currentId)
            }
}
}
/*
#####html调用
<MediaTalk ref="mediaTalkEle" @funMedia="funMedia"/>
#####js调用
// 开始喊话
handOpenTalk (id, index) {
this.$refs.mediaTalkEle.startTalk(id, index)
},
// 关闭喊话
handCloseTalk (id) {
this.$refs.mediaTalkEle.closeTalk(id)
},
// 关于喊话样式的展示 isLoadLayer喊话样式加载图层,open是否成功喊话的标志,activeTalck喊话跳动样式
funMedia (data) {
this.isActiveDev = data.active
this.open = data.open
},
* */
</script>

<style lang="less" scoped>
.loadIng{
position: fixed;
width: 100vw;
height: 100vh;
top: 0;
left: 0;
background: red;
}
.loadIcon{
position: absolute;
top: 40%;
left: 50%;
z-index: 100;
color: #07b7ff;
font-size: 40px;
font-weight: bold
}
/deep/ .ant-modal-content, /deep/.ant-modal .ant-modal-body{
background: transparent!important;
}
/deep/ .ant-modal .ant-modal-content{
box-shadow: none!important;
}
/deep/ .ant-modal-close {
display: none;
}
</style>

posted on 2023-03-31 10:17  每天暴走三公里  阅读(73)  评论(0编辑  收藏  举报

导航