引入websocket
const recoder = function (window) {
// 兼容
window.URL = window.URL || window.webkitURL
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia
var HZRecorder = function (stream, config) {
config = config || {}
config.sampleBits = config.sampleBits || 16 // 采样数位 8, 16
config.sampleRate = config.sampleRate || (16000 / 1) // 采样率(1/6
// 44100)
var context = new (window.webkitAudioContext || window.AudioContext)()
// console.log(stream)
var audioInput = context.createMediaStreamSource(stream)
var createScript = context.createScriptProcessor || context.createJavaScriptNode
var recorder = createScript.apply(context, [4096, 1, 1])
var audioData = {
size: 0, // 录音文件长度
buffer: [], // 录音缓存
inputSampleRate: context.sampleRate, // 输入采样率
inputSampleBits: 16, // 输入采样数位 8, 16
outputSampleRate: config.sampleRate, // 输出采样率
oututSampleBits: config.sampleBits, // 输出采样数位 8, 16
input: function (data) {
this.buffer.push(new Float32Array(data))
this.size += data.length
},
compress: function () { // 合并压缩
// 合并
var data = new Float32Array(this.size)
var offset = 0
for (var i = 0; i < this.buffer.length; i++) {
data.set(this.buffer[i], offset)
offset += this.buffer[i].length
}
// 压缩
var compression = parseInt(this.inputSampleRate / this.outputSampleRate)
var length = data.length / compression
var result = new Float32Array(length)
var index = 0; var j = 0
while (index < length) {
result[index] = data[j]
j += compression
index++
}
return result
},
clear: function () {
this.size = 0
this.buffer = []
},
encodeWAV: function () {
var sampleRate = Math.min(this.inputSampleRate, this.outputSampleRate)
// console.log(sampleRate);
var sampleBits = Math.min(this.inputSampleBits, this.oututSampleBits)
// console.log(sampleBits);
var bytes = this.compress()
var dataLength = bytes.length * (sampleBits / 8)
var buffer = new ArrayBuffer(44 + dataLength)
var data = new DataView(buffer)
var channelCount = 1// 单声道
var offset = 0
var writeString = function (str) {
for (var i = 0; i < str.length; i++) {
data.setUint8(offset + i, str.charCodeAt(i))
}
}
// 资源交换文件标识符
writeString('RIFF'); offset += 4
// 下个地址开始到文件尾总字节数,即文件大小-8
data.setUint32(offset, 36 + dataLength, true); offset += 4
// WAV文件标志
writeString('WAVE'); offset += 4
// 波形格式标志
writeString('fmt '); offset += 4
// 过滤字节,一般为 0x10 = 16
data.setUint32(offset, 16, true); offset += 4
// 格式类别 (PCM形式采样数据)
data.setUint16(offset, 1, true); offset += 2
// 通道数
data.setUint16(offset, channelCount, true); offset += 2
// 采样率,每秒样本数,表示每个通道的播放速度
data.setUint32(offset, sampleRate, true); offset += 4
// 波形数据传输率 (每秒平均字节数) 单声道×每秒数据位数×每样本数据位/8
data.setUint32(offset, channelCount * sampleRate * (sampleBits / 8), true); offset += 4
// 快数据调整数 采样一次占用字节数 单声道×每样本的数据位数/8
data.setUint16(offset, channelCount * (sampleBits / 8), true); offset += 2
// 每样本数据位数
data.setUint16(offset, sampleBits, true); offset += 2
// 数据标识符
writeString('data'); offset += 4
// 采样数据总数,即数据总大小-44
data.setUint32(offset, dataLength * 100, true); offset += 4
// 写入采样数据
if (sampleBits === 8) {
for (var i = 0; i < bytes.length; i++, offset++) {
var s = Math.max(-1, Math.min(1, bytes[i]))
var val = s < 0 ? s * 0x8000 : s * 0x7FFF
val = parseInt(255 / (65535 / (val + 32768)))
data.setInt8(offset, val, true)
}
} else {
for (var i = 0; i < bytes.length; i++, offset += 2) {
var s = Math.max(-1, Math.min(1, bytes[i]))
data.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7FFF, true)
}
}
return new Blob([data], { type: 'audio/wav' })
}
}
// 开始录音
this.start = function () {
audioInput.connect(recorder)
recorder.connect(context.destination)
}
// 停止
this.stop = function () {
recorder.disconnect()
}
// 获取音频文件
this.getBlob = function () {
this.stop()
return audioData.encodeWAV()
}
// 回放
this.play = function (audio) {
audio.src = window.URL.createObjectURL(this.getBlob())
}
this.exportWAV = function (callback) {
if (callback) {
var blob = audioData.encodeWAV()
audioData.clear()
callback(blob)
}
}
// 音频采集
recorder.onaudioprocess = function (e) {
audioData.input(e.inputBuffer.getChannelData(0))
// record(e.inputBuffer.getChannelData(0));
}
}
// 抛出异常
HZRecorder.throwError = function (message) {
alert(message)
throw new function () { this.toString = function () { return message } }()
}
// 是否支持录音
HZRecorder.canRecording = (navigator.getUserMedia != null)
// 获取录音机
HZRecorder.get = function (callback, config) {
if (callback) {
if (navigator.getUserMedia) {
navigator.getUserMedia(
{ audio: true } // 只启用音频
, function (stream) {
var rec = new HZRecorder(stream, config)
callback(rec)
}
, function (error) {
switch (error.code || error.name) {
case 'PERMISSION_DENIED':
case 'PermissionDeniedError':
HZRecorder.throwError('用户拒绝提供信息。')
break
case 'NOT_SUPPORTED_ERROR':
case 'NotSupportedError':
HZRecorder.throwError('浏览器不支持硬件设备。')
break
case 'MANDATORY_UNSATISFIED_ERROR':
case 'MandatoryUnsatisfiedError':
HZRecorder.throwError('无法发现指定的硬件设备。')
break
default:
HZRecorder.throwError('无法打开麦克风。异常信息:' + (error.code || error.name))
break
}
})
} else {
HZRecorder.throwErr('当前浏览器不支持录音功能。')
}
}
}
window.HZRecorder = HZRecorder
}
recoder(window)
var recorder
var intervalKey
var ws
export function startRecord(callback) {
try {
window.HZRecorder.get(rec => {
if (rec.error)
return callback.error(rec.error);
recorder = rec;
recorder.start();
callback.success("Recording...");
// console.log('Recording...')
})
} catch (error) {
callback.error("Recordingfail" + error);
}
}
export function stopRecord(callback) {
try {
let blobData = recorder.getBlob();
callback.success(blobData);
} catch (error) {
callback.error("StopRecordingfail" + error);
}
// recorder.stop()
// console.log('.........stoped...........')
}
export function startRecording(callback) {
console.log(2222)
console.log(callback)
initWs(e => {
window.HZRecorder.get(function (rec) {
recorder = rec
recorder.start()
console.log('Recording...')
intervalKey = setInterval(function () {
recorder.exportWAV(function (blob) {
ws.send(blob)
})
}, 50)
})
console.log('.........正在录音......')
},
callback
)
}
export function stopRecording() {
recorder.stop()
console.log('.........stoped...........')
clearInterval(intervalKey)
// document.getElementById('ptime').innerHTML = "";
ws.close()
}
window.start = startRecording
window.stop = stopRecording
let sessionId
function initWs(call, callback) {
sessionId = 'ASTDEMO_' + _getRandomString(8)
// var wsuri = "wss://124.114.129.219:2256/" + document.location.host + "/tuling/demo/ast/"+sessionId; // 可以通
var wsuri = 'ws://10.1.25.49:4567/tuling/ast/v2/' + sessionId + '?appId=10101&bizId=123&bizName=WebSocket&lan=chin&sr=16000&bps=16&fs=4096'
// var wsuri = "ws://124.114.129.219:4567/tuling/ast/v2/" + sessionId; // 不能通
console.log(wsuri)
ws = new WebSocket(wsuri)
ws.onopen = function () {
console.log('Openened connection to websocket')
call()
}
ws.onmessage = function (e) {
console.log(`收到消息`)
callback(e.data)
}
// console.log(callback)
};
// 获取长度为len的随机字符串
function _getRandomString(len) {
len = len || 32
var $chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678' // 默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1
var maxPos = $chars.length
var pwd = ''
for (let i = 0; i < len; i++) {
pwd += $chars.charAt(Math.floor(Math.random() * maxPos))
}
return pwd
}
vue文件
<template>
<div class="voice-page">
<div class="handle">
<div class="dis-flex">
<div class="handle-btn" @click="getAudio" v-if="!isRecord">
<img class="handle-icon" src="@/assets/newversion/voice_icon.png" />
<div>开始</div>
</div>
<div class="handle-btn" @click="onEnd" v-show="isRecord">
<img src="@/assets/newversion/recording.png" class="handle-icon recording-icon" v-if="unStart" />
<img src="@/assets/newversion/recording.png" class="handle-icon" v-if="!unStart" />
<div>结束</div>
</div>
<div class="handle-btn" @click="onPause" v-if="unStart">
<img class="handle-icon" src="@/assets/newversion/audio-pause.png" />
<div>暂停</div>
</div>
<div class="handle-btn" @click="onStart" v-if="!unStart">
<img class="handle-icon" src="@/assets/newversion/audio-start.png" />
<div>继续</div>
</div>
<div class="handle-btn" @click="onSave">
<img class="handle-icon" src="@/assets/newversion/save.png" />
<div>保存</div>
</div>
<div class="handle-btn" @click="onDownload">
<img class="handle-icon" src="@/assets/newversion/download.png" />
<div>下载</div>
</div>
<div class="handle-btn" v-clipboard:copy="message" v-clipboard:success="onCopy" v-clipboard:error="onError">
<img class="handle-icon" src="@/assets/newversion/copy.png" />
<div>复制</div>
</div>
<div class="handle-btn reset-btn" @click="onReset">
<img class="handle-icon" src="@/assets/newversion/reset.png" />
<div>重置</div>
</div>
</div>
<div class="dis-flex">
<el-select v-model="fontSize" placeholder="请选择字号" class="font-select">
<el-option v-for="(item,index) in fonts" :key="index" :label="item.label" :value="item.value">
</el-option>
</el-select>
<img class="font-icon" src="@/assets/newversion/font-reduce.png" title="减小字号" @click="reduceFont" />
<img class="font-icon" src="@/assets/newversion/font-add.png" title="增大字号" @click="addFont" />
</div>
</div>
<div class="voice-content" :style="{fontSize: fontSize + 'rem'}">
{{message}}
</div>
<div class="dialog-wrap" v-show="voiceVisible"></div>
<div class="sound-dialog" v-show="voiceVisible"></div>
</div>
</template>
<script>
import "../recoder";
var recorder;
let newRecorder;
import host from "../plugins/host"
export default {
name: "VoiceComponent",
data() {
return {
voiceVisible: false,
isAudioClick: false, //开始识别是否被点击了
isRecord: false, //识别中
unStart: true, //是否开始录制
audioTimer: 60, //定时时间
timerObj: null, //定时器对象
voiceTxt: "", //语音识别后的文字
audioLength: 0, //音频时间
ws: null,
intervalKey: null,
message: "",
config: {
sampleBits: 16,
sampleRate: 16000
},
fontSize: '0.24',
progressive: [],
sentence: [],
msgBox: [],
};
},
mounted() {
// 获取缓存
let saved_msg = sessionStorage.getItem('saved_msg');
if (saved_msg) {
this.message = saved_msg;
}
},
computed: {
fonts: function () {
return this.$store.state.fonts;
},
},
methods: {
onCopy: function (e) {
// this.$message.success("复制成功");
console.log('复制成功');
},
onError: function (e) {
// this.$message.error("复制失败");
console.log('复制失败');
},
// 字体减小
reduceFont() {
let fonts = this.fonts;
let fontSize = this.fontSize;
for (let i in fonts) {
if (fonts[i].value == fontSize) {
if (i > 0) {
this.fontSize = fonts[Number(i) - 1].value;
}
}
}
},
// 字体加大
addFont() {
let fonts = this.fonts;
let fontSize = this.fontSize;
for (let i in fonts) {
if (fonts[i].value == fontSize) {
if (i < fonts.length - 1) {
this.fontSize = fonts[Number(i) + 1].value;
}
}
}
},
// 暂停
onPause() {
if (!this.isRecord) return;
let msgBox = sessionStorage.getItem('msgBox');
if (msgBox) {
msgBox = JSON.parse(msgBox);
sessionStorage.setItem('msgBox', JSON.stringify(msgBox.concat(this.msgBox)));
} else {
sessionStorage.setItem('msgBox', JSON.stringify(this.msgBox));
}
this.msgBox = [];
this.unStart = false;
recorder.stop();
this.ws.close();
clearInterval(this.intervalKey);
this.intervalKey = null;
},
// 继续
onStart() {
let config = this.config;
let that = this;
this.initWs(() => {
this.unStart = true;
HZRecorder.get(function (rec) {
recorder = rec;
recorder.start();
if (typeof recorder === "object") {
that.intervalKey = setInterval(function () {
recorder.exportWAV(function (blob) {
// console.log(blob); // 类型大小
if (that.ws.readyState === that.ws.OPEN) {
// 若是ws开启状态
that.ws.send(blob);
}
})
}, 50)
}
}, config);
})
},
// 结束
onEnd() {
this.isRecord = false;
this.unStart = true;
recorder.stop();
this.ws.close();
clearInterval(this.intervalKey);
this.intervalKey = null;
},
// 重置
onReset() {
this.msgBox = [];
sessionStorage.removeItem('msgBox');
this.message = "";
this.fontSize = '0.24';
},
// 保存
onSave() {
if (this.message === '') {
// this.$message.error("请先录制语音");
console.log('请先录制语音');
return;
}
sessionStorage.setItem('saved_msg', this.message);
// this.$message.success("保存成功");
console.log('保存成功');
},
// 下载为txt
onDownload() {
if (this.message === '') {
// this.$message.error("请先录制语音");
console.log('请先录制语音');
return;
}
// 没有服务器的情况下使用下方可获取连接
let urlObj = window.URL || window.webkitURL || window;
let export_blob = new Blob([this.message]);
// 建立A标签
let save_link = document.createElementNS("http://www.w3.org/1999/xhtml", "a");
save_link.href = urlObj.createObjectURL(export_blob);
save_link.download = this.formatDate() + ".txt";
save_link.target = "_blank";
let ev = document.createEvent("MouseEvents");
ev.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
save_link.dispatchEvent(ev);
},
formatDate() {
let now = new Date();
return (String(now.getFullYear()) + "-" + String(now.getMonth() + 1) + "-" + String(now.getDate()) + " " + String(now.getHours()) + "-" + String(now.getMinutes()) + "-" + String(now.getSeconds()));
},
stopRecoder() {
newRecorder.stop()
},
// 新版获取音频流
startRecorder() {
// 老的浏览器可能根本没有实现 mediaDevices,所以我们可以先设置一个空的对象
if (navigator.mediaDevices === undefined) {
navigator.mediaDevices = {};
}
// 一些浏览器部分支持 mediaDevices。我们不能直接给对象设置 getUserMedia
// 因为这样可能会覆盖已有的属性。这里我们只会在没有getUserMedia属性的时候添加它。
if (navigator.mediaDevices.getUserMedia === undefined) {
navigator.mediaDevices.getUserMedia = function (constraints) {
// 首先,如果有getUserMedia的话,就获得它
var getUserMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
// 一些浏览器根本没实现它 - 那么就返回一个error到promise的reject来保持一个统一的接口
if (!getUserMedia) {
return Promise.reject(new Error('getUserMedia is not implemented in this browser'));
}
// 否则,为老的navigator.getUserMedia方法包裹一个Promise
return new Promise(function (resolve, reject) {
getUserMedia.call(navigator, constraints, resolve, reject);
});
}
}
let that = this;
navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) {
that.initWs(function () {
newRecorder = new MediaRecorder(stream);
const recordedChunks = [];
newRecorder.ondataavailable = event => {
const blob = event.data;
let newBlob = new Blob([blob], { type: 'audio/wav' })
if (blob.size > 0) {
recordedChunks.push(newBlob);
}
console.log(newBlob)
that.ws.send(newBlob);
};
newRecorder.onstop = function () {
that.$refs.myAudio.src = URL.createObjectURL(new Blob(recordedChunks));
}
newRecorder.start(1000);
})
}).catch(function (err) {
that.$message.error("未找到录音设备");
console.log(err.name + ": " + err.message);
})
},
// 开始识别
getAudio(active) {
console.log(active);
let getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
if (!getUserMedia) {
// this.$message.error("当前浏览器不支持语音");
console.log('当前浏览器不支持语音');
return;
}
let that = this;
if (this.voiceVisible) return;
this.initWs(() => {
this.voiceVisible = true; //启动通话框
setTimeout(() => {
this.voiceVisible = false;
this.isRecord = true; // 暂停识别中
this.message = "";
this.msgBox = [];
sessionStorage.removeItem('msgBox');
//开始采集音频 (这里的意思 开始 录音)
let config = this.config;
HZRecorder.get(function (rec) {
console.log(rec);
recorder = rec;
recorder.start();
if (typeof recorder === "object") {
that.intervalKey = setInterval(function () {
recorder.exportWAV(function (blob) {
console.log(blob);
if (that.ws.readyState === that.ws.OPEN) {
// 若是ws开启状态
that.ws.send(blob);
}
});
}, 50);
}
}, config);
}, 2000)
})
},
// 初始化ws
initWs(callback) {
let loadIns = this.$loading({
text: "正在连接服务器...",
background: "rgba(0, 0, 0, 0.5)"
});
let that = this;
let sessionId = "ASTDEMO_" + this.getRandomString(8); // 随机数
let wsuri = host.voiceUrl +sessionId +"?appId=10101&bizId=123&bizName=WebSocket&lan=chin&sr=16000&bps=16&fs=4096";
this.ws = new WebSocket(wsuri); // 开启websocket
this.ws.onopen = function () { //开启
loadIns.close(); // 关闭正在连接服务
callback();
};
// 接受的文字
this.ws.onmessage = function (e) {
console.log(`收到消息:`);
console.log(JSON.parse(e.data));
let data = JSON.parse(e.data);
that.handleMsg(data);
};
this.ws.onerror = function (err) {
loadIns.close();
that.$message.error("服务器连接失败,请重试");
}
},
// 统一处理消息
handleMsg(data) {
// jquery
const { finalResult, result } = data;
if (!result) return;
msgBox[msgBox.length ? msgBox.length - 1 : 0] = result
if (finalResult) { msgBox.push('') }
$('.voice-content').html(msgBox.join(''))
//
let msgBox = this.msgBox;
if (msgBox.length == 0) {
msgBox.push(data);
} else {
let status = false;
for (let p in msgBox) {
// 数组当中已存在,替换
if (msgBox[p].segId == data.segId) {
msgBox.splice(p, 1, data);
status = true;
}
}
// 不存在,push一条新数据
if (!status) {
this.msgBox.push(data);
}
}
// 合并消息
let list = [];
let savedData = sessionStorage.getItem('msgBox');
if (savedData) {
savedData = JSON.parse(savedData);
list = list.concat(savedData).concat(this.msgBox);
} else {
list = list.concat(this.msgBox);
}
// 渲染
let str = '';
for (let i in list) {
let ws = list[i].ws;
for (let j in ws) {
let cw = ws[j].cw;
for (let k in cw) {
let item = cw[k];
str += item.w;
}
}
}
this.message = str;
},
// 处理未校验消息
hanldeProcessive(data) {
let progressive = this.progressive;
if (progressive.length > 0) {
let status = false;
for (let p in progressive) {
// 数组当中已存在,替换
if (progressive[p].segId == data.segId) {
progressive.splice(p, 1, data);
status = true;
}
}
if (!status) {
this.progressive.push(data);
}
} else {
this.progressive.push(data);
}
let list = [];
let savedData = sessionStorage.getItem('progressive');
if (savedData) {
savedData = JSON.parse(savedData);
list = list.concat(savedData).concat(this.progressive);
} else {
list = list.concat(this.progressive);
}
// 渲染
let str = '';
for (let i in list) {
let ws = list[i].ws;
for (let j in ws) {
let cw = ws[j].cw;
for (let k in cw) {
let item = cw[k];
str += item.w;
}
}
}
this.message = str;
},
// 处理校验消息
hanldeSentence(data) {
this.sentence.push(data);
let sentence = this.sentence;
let progressive = this.progressive;
if (progressive.length > 0) {
let status = false;
// 覆盖未校验消息
for (let p in progressive) {
if (progressive[p].segId == data.segId) {
progressive.splice(p, 1, data);
status = true;
}
}
if (!status) {
this.progressive.push(data);
}
} else {
// 直接返回的是最终校验消息
this.progressive.push(data);
}
let list = [];
let savedData = sessionStorage.getItem('sentence');
if (savedData) {
savedData = JSON.parse(savedData);
list = list.concat(savedData).concat(this.sentence);
} else {
list = list.concat(this.sentence);
}
// 渲染
let str = '';
for (let i in list) {
let ws = list[i].ws;
for (let j in ws) {
let cw = ws[j].cw;
for (let k in cw) {
let item = cw[k];
str += item.w;
}
}
}
this.message = str;
},
// 随机字符串
getRandomString(len) {
len = len || 32;
let $chars = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678"; // 默认去掉了容易混淆的字符oOLl,9gq,Vv,Uu,I1
let maxPos = $chars.length;
let pwd = "";
for (let i = 0; i < len; i++) {
pwd += $chars.charAt(Math.floor(Math.random() * maxPos));
}
return pwd;
}
},
destroyed() {
if (this.ws) {
this.ws.close();
sessionStorage.removeItem('msgBox');
}
}
};
</script>
<style scoped>
.dis-flex {
display: flex;
align-items: center;
}
.voice-page {
width: 100%;
height: 100%;
padding: 0.2rem;
background: url("../assets/newversion/cont-bg.png") no-repeat;
background-size: 100% 100%;
}
.voice-page .handle {
display: flex;
align-items: center;
justify-content: space-between;
padding-bottom: 0.2rem;
font-size: 0.16rem;
}
.voice-page .handle .handle-btn {
display: flex;
justify-content: center;
align-items: center;
width: 1.2rem;
height: 0.4rem;
color: #fff;
background-color: #378eef;
border-radius: 5px;
cursor: pointer;
}
.voice-page .handle .handle-btn.clear {
background-color: #909399;
}
.voice-page .handle .handle-btn.reset {
/* background-color: #f56c6c; */
background-color: #909399;
}
.voice-page .handle .handle-btn .handle-icon {
display: block;
width: 0.2rem;
margin-right: 0.1rem;
}
.voice-page .handle .handle-btn:not(:first-child) {
margin-left: 0.15rem;
}
.line-box {
display: inline-block;
vertical-align: middle;
}
.line-box div {
display: inline-block;
vertical-align: middle;
margin-right: 0.04rem;
/* width: 0.02rem; */
width: 2px;
background-color: #fff;
animation-play-state: running;
-webkit-animation-play-state: running;
}
.line-box .line-1 {
animation: line 0.6s infinite linear alternate;
}
.line-box .line-2 {
animation: line 0.6s 0.2s infinite linear alternate;
}
.line-box .line-3 {
animation: line 0.6s 0.4s infinite linear alternate;
}
.line-box .line-4 {
animation: line 0.6s 0.6s infinite linear alternate;
}
.line-box div.paused {
animation-play-state: paused;
-webkit-animation-play-state: paused;
}
@keyframes line {
0%,
20% {
height: 0.06rem;
}
100% {
height: 0.2rem;
}
}
.recording-icon {
animation: recording 0.6s infinite ease-in alternate;
}
@keyframes recording {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.voice-page .voice-content {
height: calc(100% - 0.6rem);
color: #fff;
padding: 0.2rem;
word-break: break-all;
background: url("../assets/newversion/cont-inner-new.png") no-repeat;
background-size: 100% 100%;
overflow-y: auto;
}
.recording-box {
margin-top: 0.2rem;
padding: 0.2rem;
overflow-y: auto;
}
.finally-box,
.recording-box {
height: calc((100% - 0.2rem) / 2);
word-break: break-all;
background: url("../assets/newversion/cont-inner-new.png") no-repeat;
background-size: 100% 100%;
}
.finally-txt {
margin: 0;
padding: 0.2rem;
width: 100%;
height: 100%;
resize: none;
border: none;
outline: none;
color: #fff;
background-color: transparent;
}
.voice-content::-webkit-scrollbar,
.recording-box::-webkit-scrollbar,
.finally-txt::-webkit-scrollbar {
width: 10px;
height: 10px;
}
.voice-content::-webkit-scrollbar-thumb,
.recording-box::-webkit-scrollbar-thumb,
.finally-txt::-webkit-scrollbar-thumb {
border-radius: 5px;
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.2);
background-color: rgba(255, 255, 255, 0.75);
}
.voice-content::-webkit-scrollbar-track,
.recording-box::-webkit-scrollbar-track,
.finally-txt::-webkit-scrollbar-track {
border-radius: 0;
background: none;
}
.voice-page .dialog-wrap {
z-index: 1000;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
overflow: auto;
margin: 0;
background: rgba(0, 0, 0, 0.4);
}
.voice-page .sound-dialog {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 389px;
height: 280px;
z-index: 1000;
background: url("../assets/newversion/mkf.png") no-repeat 0 0;
background-size: 100% 100%;
}
</style>