从文对讲
This commit is contained in:
parent
fa100abc89
commit
1af684224c
|
@ -12,26 +12,14 @@ declare module 'vue' {
|
||||||
AButton: typeof import('ant-design-vue/es')['Button']
|
AButton: typeof import('ant-design-vue/es')['Button']
|
||||||
ACascader: typeof import('ant-design-vue/es')['Cascader']
|
ACascader: typeof import('ant-design-vue/es')['Cascader']
|
||||||
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
|
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
|
||||||
ADivider: typeof import('ant-design-vue/es')['Divider']
|
|
||||||
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
|
|
||||||
AForm: typeof import('ant-design-vue/es')['Form']
|
AForm: typeof import('ant-design-vue/es')['Form']
|
||||||
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
||||||
AImage: typeof import('ant-design-vue/es')['Image']
|
|
||||||
AInput: typeof import('ant-design-vue/es')['Input']
|
AInput: typeof import('ant-design-vue/es')['Input']
|
||||||
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||||
ALayout: typeof import('ant-design-vue/es')['Layout']
|
|
||||||
ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent']
|
|
||||||
ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider']
|
|
||||||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
|
||||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
|
||||||
ASegmented: typeof import('ant-design-vue/es')['Segmented']
|
|
||||||
ASpace: typeof import('ant-design-vue/es')['Space']
|
|
||||||
ASpin: typeof import('ant-design-vue/es')['Spin']
|
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||||
AStatistic: typeof import('ant-design-vue/es')['Statistic']
|
|
||||||
ATimeline: typeof import('ant-design-vue/es')['Timeline']
|
ATimeline: typeof import('ant-design-vue/es')['Timeline']
|
||||||
ATimelineItem: typeof import('ant-design-vue/es')['TimelineItem']
|
ATimelineItem: typeof import('ant-design-vue/es')['TimelineItem']
|
||||||
ATree: typeof import('ant-design-vue/es')['Tree']
|
ATree: typeof import('ant-design-vue/es')['Tree']
|
||||||
ATreeSelect: typeof import('ant-design-vue/es')['TreeSelect']
|
|
||||||
CodemirrorPro: typeof import('./src/components/codemirror/CodemirrorPro.vue')['default']
|
CodemirrorPro: typeof import('./src/components/codemirror/CodemirrorPro.vue')['default']
|
||||||
Digitalscroll: typeof import('./src/components/index/digitalscroll.vue')['default']
|
Digitalscroll: typeof import('./src/components/index/digitalscroll.vue')['default']
|
||||||
Eldrawertransition: typeof import('./src/components/eldrawertransition.vue')['default']
|
Eldrawertransition: typeof import('./src/components/eldrawertransition.vue')['default']
|
||||||
|
|
|
@ -61,11 +61,14 @@
|
||||||
|
|
||||||
<body ontouchstart>
|
<body ontouchstart>
|
||||||
<div id="playerBox"></div>
|
<div id="playerBox"></div>
|
||||||
<div>
|
<div style="display: flex;">
|
||||||
<button class="layui-btn" id="talk-real-server">实时视频对讲(请配置URL)</button>
|
<button class="layui-btn" id="talk-real-server">实时视频对讲</button>
|
||||||
<!-- <input type="text" id="talkUrl" style="width: 600px" value="" /> -->
|
<button style="margin-left: 20px;" class="layui-btn" id="talk-real-server-close">关闭实时视频对讲</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
let stream, audioContext, source, processor;
|
||||||
|
|
||||||
window.onload = function () {
|
window.onload = function () {
|
||||||
self.player = new Player($("#playerBox"));
|
self.player = new Player($("#playerBox"));
|
||||||
function getParams(key) {
|
function getParams(key) {
|
||||||
|
@ -108,7 +111,7 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
$("html>head>title").html(title);
|
$("html>head>title").html(title);
|
||||||
|
var socket
|
||||||
var wsUrl = ''
|
var wsUrl = ''
|
||||||
document.getElementById("talk-real-server").onclick = function () {
|
document.getElementById("talk-real-server").onclick = function () {
|
||||||
console.log(1111);
|
console.log(1111);
|
||||||
|
@ -118,44 +121,79 @@
|
||||||
data: { ...requestBody }
|
data: { ...requestBody }
|
||||||
}).then(res => {
|
}).then(res => {
|
||||||
console.log('talk/channel', res);
|
console.log('talk/channel', res);
|
||||||
wsUrl = res.data.data.videoUrl
|
// wsUrl = res.data.data.videoUrl
|
||||||
|
wsUrl = 'ws://222.245.132.168:19555/talk/pcma/stream/COWN-ZK9-Z2-C3E/QsTzeGZPrpiCxmlok44NMsdE'
|
||||||
}).catch(function (error) {
|
|
||||||
console.log(error)
|
|
||||||
})
|
|
||||||
if (wsUrl.length > 0) {
|
|
||||||
// 创建 WebSocket 连接
|
// 创建 WebSocket 连接
|
||||||
const socket = new WebSocket(wsUrl);
|
socket = new WebSocket(wsUrl);
|
||||||
|
|
||||||
// 监听 WebSocket 连接的打开事件
|
// 监听 WebSocket 连接的打开事件
|
||||||
socket.onopen = function (event) {
|
socket.onopen = function (event) {
|
||||||
console.log('WebSocket 连接已打开');
|
console.log('WebSocket 连接已打开');
|
||||||
|
console.log('socket_______________', socket);
|
||||||
|
|
||||||
|
|
||||||
// 获取麦克风的音频流
|
// 获取麦克风的音频流
|
||||||
navigator.mediaDevices.getUserMedia({ audio: true })
|
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||||
.then(stream => {
|
console.log("支持 getUserMedia");
|
||||||
// 使用 AudioContext 处理音频数据
|
} else {
|
||||||
const audioContext = new AudioContext();
|
console.error("当前浏览器不支持 getUserMedia");
|
||||||
const source = audioContext.createMediaStreamSource(stream);
|
}
|
||||||
const processor = audioContext.createScriptProcessor(4096, 1, 1);
|
// 转换格式
|
||||||
|
function encodeALaw(sample) {
|
||||||
|
const ALAW_MAX = 0xFFF; // 12-bit max value
|
||||||
|
const ALAW_BIAS = 0x84; // 132
|
||||||
|
|
||||||
|
let sign = (sample & 0x8000) >> 8; // 提取符号位
|
||||||
|
if (sign !== 0) sample = -sample; // 如果是负数,取反
|
||||||
|
|
||||||
|
if (sample > ALAW_MAX) sample = ALAW_MAX; // 限制最大值
|
||||||
|
|
||||||
|
// A-Law 编码公式
|
||||||
|
let magnitude;
|
||||||
|
if (sample >= 256) {
|
||||||
|
magnitude = (sample >> 8) & 0x7F;
|
||||||
|
magnitude += 8;
|
||||||
|
} else {
|
||||||
|
magnitude = (sample >> 4) & 0x0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
let aLawByte = sign | magnitude;
|
||||||
|
|
||||||
|
return aLawByte ^ 0xD5; // 反转特定位以匹配 A-Law 规范
|
||||||
|
}
|
||||||
|
|
||||||
|
// 示例代码:将 PCM 数据转换为 A-Law 数据
|
||||||
|
navigator.mediaDevices.getUserMedia({ audio: true })
|
||||||
|
.then(audioStream => {
|
||||||
|
stream = audioStream
|
||||||
|
audioContext = new AudioContext({ sampleRate: 8000 });
|
||||||
|
source = audioContext.createMediaStreamSource(stream);
|
||||||
|
processor = audioContext.createScriptProcessor(4096, 1, 1);
|
||||||
|
|
||||||
// 监听音频数据
|
|
||||||
processor.onaudioprocess = function (event) {
|
processor.onaudioprocess = function (event) {
|
||||||
const audioData = event.inputBuffer.getChannelData(0);
|
const audioData = event.inputBuffer.getChannelData(0);
|
||||||
const float32Array = new Float32Array(audioData);
|
const int16Array = new Int16Array(audioData.length);
|
||||||
|
|
||||||
// 将浮点音频数据转换为 16 位 PCM 格式
|
// 转换为 16 位 PCM 数据
|
||||||
const int16Array = new Int16Array(float32Array.length);
|
for (let i = 0; i < audioData.length; i++) {
|
||||||
for (let i = 0; i < float32Array.length; i++) {
|
int16Array[i] = Math.max(-1, Math.min(1, audioData[i])) * 0x7FFF;
|
||||||
int16Array[i] = float32Array[i] * 0x7FFF; // 将 [-1, 1] 映射到 [-32768, 32767]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送音频数据
|
// 将 16 位 PCM 转换为 A-Law 编码
|
||||||
socket.send(int16Array.buffer);
|
const aLawData = new Uint8Array(int16Array.length);
|
||||||
console.log('音频数据已发送');
|
for (let i = 0; i < int16Array.length; i++) {
|
||||||
|
aLawData[i] = encodeALaw(int16Array[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分块发送 A-Law 数据,块大小可以为 320, 480, 640 或 960 字节
|
||||||
|
const blockSize = 320;
|
||||||
|
for (let j = 0; j < aLawData.length; j += blockSize) {
|
||||||
|
const chunk = aLawData.slice(j, j + blockSize);
|
||||||
|
socket.send(chunk.buffer);
|
||||||
|
console.log('发送音频数据块:', chunk);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 将音频流连接到处理器并开始处理
|
|
||||||
source.connect(processor);
|
source.connect(processor);
|
||||||
processor.connect(audioContext.destination);
|
processor.connect(audioContext.destination);
|
||||||
})
|
})
|
||||||
|
@ -179,12 +217,43 @@
|
||||||
console.error('WebSocket 错误:', event);
|
console.error('WebSocket 错误:', event);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}).catch(function (error) {
|
||||||
// var playUrl = document.getElementById("realplayUrl").value;
|
console.log(error)
|
||||||
// var talkUrl = document.getElementById("talkUrl").value;
|
})
|
||||||
// self.player.talkUrl = talkUrl;
|
|
||||||
// self.player.play(playUrl, true, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.getElementById("talk-real-server-close").onclick = function () {
|
||||||
|
// socket.close(); // 关闭 WebSocket 连接
|
||||||
|
console.log(socket);
|
||||||
|
console.log(socket.readyState);
|
||||||
|
console.log(WebSocket.OPEN);
|
||||||
|
|
||||||
|
if (socket && socket.readyState === WebSocket.OPEN) {
|
||||||
|
socket.close(); // 关闭 WebSocket 连接
|
||||||
|
// 停止麦克风录音
|
||||||
|
if (stream) {
|
||||||
|
const tracks = stream.getTracks();
|
||||||
|
tracks.forEach(track => track.stop());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭 AudioContext 和处理器
|
||||||
|
if (audioContext) {
|
||||||
|
audioContext.close();
|
||||||
|
}
|
||||||
|
if (processor) {
|
||||||
|
processor.disconnect();
|
||||||
|
}
|
||||||
|
if (source) {
|
||||||
|
source.disconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('WebSocket 连接已关闭');
|
||||||
|
} else {
|
||||||
|
console.log('WebSocket 连接未打开或已关闭');
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in New Issue