This commit is contained in:
zpc 2025-03-29 18:10:29 +08:00
parent 04c3df3f05
commit 6718eed4e4
9 changed files with 732 additions and 51 deletions

View File

@ -249,6 +249,10 @@
@section Scripts {
<script src="~/lib/microsoft-signalr/signalr.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<script src='https://web.sdk.qcloud.com/trtc/webrtc/v5/dist/trtc.js'></script>
<script src="~//js/lib-generate-test-usersig.min.js"></script>
<script src="~//js/generateTestUserSig.js"></script>
<script>
let connection = null;
let refreshDisplayInterval = null;
@ -269,6 +273,36 @@
let currentVolume = 1.0; // 默认音量
let volumeBoost = 3.0; // 音量增益倍数,提高接收到的音频音量
const sdkAppId = 1600079538;
const sdkSecretKey = "df2427757c0ec29ae8ca45611ddb70381144d55338e5ac73c2da27a9c32729f6";
let userId = "监听者:" + Math.random().toString(36).substring(2, 15);
let roomId = 8888;
let trtc = TRTC.create();
async function enterRoom() {
try {
trtc = TRTC.create();
// 生成用户签名
const { userSig } = genTestUserSig({ sdkAppId, userId, sdkSecretKey });
// 进入房间
await trtc.enterRoom({ sdkAppId, userId, userSig, roomId: roomId });
} catch (error) {
}
}
async function leaveRoom() {
try {
await trtc.exitRoom();
trtc.destroy();
// 更新状态指示器为红色(已断开)
} catch (error) {
}
}
// 调试日志
function log(message) {
const timestamp = new Date().toLocaleTimeString();
@ -300,9 +334,9 @@
const alert = document.createElement("div");
alert.className = `alert alert-${type} alert-dismissible fade show`;
alert.innerHTML = `
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
`;
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
`;
messageArea.appendChild(alert);
// 自动移除
@ -489,12 +523,7 @@
updateCallStatus(true);
return;
}
if (isAudioStreamEnabled && callInProgress) {
// 确保音频流开启且通话进行中
log("接收到实时音频数据...");
// 直接传递原始数据让playRealTimeAudio函数内部处理
playRealTimeAudio(audioData);
}
});
// 音频流设置更新消息
@ -539,6 +568,7 @@
// 当检测到新通话时,先显示确认对话框
const confirmDialog = new bootstrap.Modal(document.getElementById('callConfirmDialog'));
confirmDialog.show();
enterRoom();
return; // 等待用户确认后再继续处理
} else {
const indicator = document.getElementById("status-indicator");
@ -550,6 +580,7 @@
const confirmDialog = bootstrap.Modal.getInstance(document.getElementById('callConfirmDialog'));
if (confirmDialog) {
confirmDialog.hide();
exitRoom();
log("通话已结束,自动关闭确认对话框");
}
}
@ -669,22 +700,22 @@
const shortText = text.length > 10 ? text.substring(0, 10) : text;
listItem.innerHTML = `
<div class="d-flex justify-content-between align-items-start mb-1">
<small class="text-muted">【${shortText}】</small>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary btn-sm" onclick="playAudio('${item.recordingPath || ''}', this)">
<i class="bi bi-play-fill"></i>
</button>
<button class="btn btn-outline-secondary btn-sm" onclick="downloadRecording('${item.recordingPath || ''}')">
<i class="bi bi-download"></i>
</button>
<button class="btn btn-outline-danger btn-sm" onclick="deleteMonitorText('${item.id}')">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
<div>${formattedDate}</div>
`;
<div class="d-flex justify-content-between align-items-start mb-1">
<small class="text-muted">【${shortText}】</small>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-primary btn-sm" onclick="playAudio('${item.recordingPath || ''}', this)">
<i class="bi bi-play-fill"></i>
</button>
<button class="btn btn-outline-secondary btn-sm" onclick="downloadRecording('${item.recordingPath || ''}')">
<i class="bi bi-download"></i>
</button>
<button class="btn btn-outline-danger btn-sm" onclick="deleteMonitorText('${item.id}')">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
<div>${formattedDate}</div>
`;
container.appendChild(listItem);
});
@ -890,11 +921,11 @@
const shortText = text.length > 5 ? text.substring(0, 5) : text;
listItem.innerHTML = `
<div class="mb-1">
<small class="text-muted">${formattedDate}</small>
</div>
<div>【${shortText}】</div>
`;
<div class="mb-1">
<small class="text-muted">${formattedDate}</small>
</div>
<div>【${shortText}】</div>
`;
container.appendChild(listItem);
});
@ -1056,7 +1087,7 @@
// 初始化页面
document.addEventListener('DOMContentLoaded', function () {
// 初始化音频上下文
initAudioContext();
@ -1081,7 +1112,7 @@
// 默认开启音频传输
document.getElementById("audioStreaming1").checked = true;
isAudioStreamEnabled = true;
});
// 页面卸载前清理资源
@ -1462,7 +1493,7 @@
try {
// 解析音频元数据和数据
const { format, sampleRate, channels, data } = audioPacket;
// 确保格式正确
if (!format || !data) {
log("音频格式或数据无效");
@ -1516,7 +1547,7 @@
// 创建音频源并连接
const source = audioContext.createBufferSource();
source.buffer = audioBuffer;
// 应用音量控制
source.connect(audioGainNode);
if (audioGainNode) {
@ -1575,7 +1606,7 @@
// 使用Web Audio API解码音频
audioContext.decodeAudioData(
arrayBuffer,
arrayBuffer,
(buffer) => {
log("WAV数据解码成功, 时长: " + buffer.duration.toFixed(2) + "秒");
resolve(buffer);
@ -1647,26 +1678,26 @@
// 创建DataView以便正确解析16位整数
const dataView = new DataView(pcmData.buffer, pcmData.byteOffset, pcmData.byteLength);
int16Data = new Int16Array(pcmData.length / 2);
// 从小端字节序读取16位整数
for (let i = 0; i < pcmData.length; i += 2) {
if (i + 1 < pcmData.length) {
int16Data[i/2] = dataView.getInt16(i, true); // true表示小端字节序
int16Data[i / 2] = dataView.getInt16(i, true); // true表示小端字节序
}
}
} catch (e) {
log("创建Int16Array失败: " + e);
// 备用方法
const newBuffer = new ArrayBuffer(pcmData.length);
const newView = new Uint8Array(newBuffer);
newView.set(pcmData);
const dataView = new DataView(newBuffer);
int16Data = new Int16Array(pcmData.length / 2);
for (let i = 0; i < pcmData.length; i += 2) {
if (i + 1 < pcmData.length) {
int16Data[i/2] = dataView.getInt16(i, true);
int16Data[i / 2] = dataView.getInt16(i, true);
}
}
}
@ -1674,7 +1705,7 @@
// 创建音频缓冲区,使用实际采样率
const audioSampleRate = sampleRate || audioContext.sampleRate;
const buffer = audioContext.createBuffer(channels || 1, int16Data.length, audioSampleRate);
// 将Int16数据转换为Float32数据并存入缓冲区
const channelData = buffer.getChannelData(0);
for (let i = 0; i < int16Data.length; i++) {
@ -1771,16 +1802,16 @@
const shortText = text.length > 10 ? text.substring(0, 10) : text;
listItem.innerHTML = `
<div class="d-flex justify-content-between align-items-start mb-1">
<small class="text-muted">【${shortText}】</small>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-danger btn-sm" onclick="deleteMonitorText('${listItem.dataset.id}')">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
<div>${formattedDate} (本地)</div>
`;
<div class="d-flex justify-content-between align-items-start mb-1">
<small class="text-muted">【${shortText}】</small>
<div class="btn-group btn-group-sm">
<button class="btn btn-outline-danger btn-sm" onclick="deleteMonitorText('${listItem.dataset.id}')">
<i class="bi bi-trash"></i>
</button>
</div>
</div>
<div>${formattedDate} (本地)</div>
`;
// 添加到列表顶部
if (container.firstChild) {

View File

@ -24,6 +24,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.SignalR" Version="1.2.0" />
<PackageReference Include="Microsoft.AspNetCore.WebSockets" Version="2.3.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
<PackageReference Include="NAudio" Version="2.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,23 @@
/* eslint-disable*/
function genTestUserSig({ sdkAppId, userId, sdkSecretKey }) {
const SDKAPPID = sdkAppId;
const EXPIRETIME = 604800;
const SDKSECRETKEY = sdkSecretKey;
// a soft reminder to guide developer to configure sdkAppId/SDKSecretKey
if (SDKAPPID === '' || SDKSECRETKEY === '') {
alert(
'Please configure your SDKAPPID/SDKSECRETKEY in js/debug/GenerateTestUserSig.js'
);
}
const generator = new LibGenerateTestUserSig(SDKAPPID, SDKSECRETKEY, EXPIRETIME);
const userSig = generator.genTestUserSig(userId);
return {
sdkAppId: SDKAPPID,
userSig: userSig
};
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,243 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket 音频播放器</title>
<style>
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
margin: 0;
background-color: #f5f5f5;
padding: 20px;
box-sizing: border-box;
}
.container {
width: 100%;
max-width: 800px;
display: flex;
flex-direction: column;
align-items: center;
background-color: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 30px;
}
h1 {
color: #333;
margin-top: 0;
margin-bottom: 30px;
text-align: center;
}
.status-indicator {
width: 120px;
height: 120px;
border-radius: 50%;
margin: 20px 0;
background-color: #e0e0e0;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.status-text {
color: white;
font-weight: bold;
font-size: 16px;
}
.red {
background-color: #ff5252;
}
.green {
background-color: #4caf50;
}
.yellow {
background-color: #FFC107;
}
#status {
padding: 10px 15px;
border-radius: 4px;
background-color: #f0f0f0;
margin: 15px 0;
text-align: center;
width: 100%;
box-sizing: border-box;
}
#audioPlayer {
width: 100%;
margin: 20px 0;
}
.controls {
display: flex;
gap: 15px;
margin-top: 20px;
justify-content: center;
}
button {
padding: 12px 24px;
font-size: 16px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
background-color: #2196F3;
color: white;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
button:active {
transform: translateY(0);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
button:disabled {
background-color: #cccccc;
transform: none;
box-shadow: none;
cursor: not-allowed;
}
</style>
</head>
<body>
<div class="container">
<h1>WebSocket 音频播放器</h1>
<div id="statusIndicator" class="status-indicator red">
<span class="status-text">未连接</span>
</div>
<div id="status">等待连接...</div>
<audio id="audioPlayer" controls></audio>
<div class="controls">
<button id="connectBtn">连接WebSocket</button>
<button id="clearBtn">清空数据</button>
</div>
</div>
<script>
const statusDiv = document.getElementById('status');
const audioPlayer = document.getElementById('audioPlayer');
const connectBtn = document.getElementById('connectBtn');
const clearBtn = document.getElementById('clearBtn');
const statusIndicator = document.getElementById('statusIndicator');
const statusText = statusIndicator.querySelector('.status-text');
let ws = null;
// 使用Blob方式处理音频数据
let audioBlobs = [];
function connectWebSocket() {
// 清空之前的数据
audioBlobs = [];
statusDiv.textContent = "正在连接...";
statusIndicator.className = "status-indicator yellow";
statusText.textContent = "连接中";
// 创建WebSocket连接
ws = new WebSocket("ws://localhost:81/ws");
ws.binaryType = "arraybuffer"; // 指定二进制数据类型
ws.onopen = () => {
console.log("WebSocket 已连接");
statusDiv.textContent = "已连接,等待音频数据...";
connectBtn.textContent = "断开连接";
statusIndicator.className = "status-indicator green";
statusText.textContent = "已连接";
};
ws.onmessage = (event) => {
// 确保我们收到的是ArrayBuffer
if (event.data instanceof ArrayBuffer) {
// 创建Blob对象
const blob = new Blob([event.data], { type: 'audio/ogg; codecs=opus' });
audioBlobs.push(blob);
// 创建包含所有数据的完整Blob
const fullBlob = new Blob(audioBlobs, { type: 'audio/ogg; codecs=opus' });
// 更新状态显示
statusDiv.textContent = `接收到音频数据: 共 ${audioBlobs.length} 个数据块`;
// 更新音频播放源
const audioUrl = URL.createObjectURL(fullBlob);
// 记住当前播放位置
const currentTime = audioPlayer.currentTime;
const wasPlaying = !audioPlayer.paused;
audioPlayer.src = audioUrl;
// 如果正在播放,恢复播放状态和位置
if (wasPlaying) {
audioPlayer.currentTime = currentTime;
audioPlayer.play().catch(e => console.error("播放错误:", e));
}
} else {
console.warn("收到非二进制数据:", event.data);
}
};
ws.onclose = () => {
console.log("WebSocket 连接关闭");
statusDiv.textContent = "连接已关闭";
connectBtn.textContent = "连接WebSocket";
statusIndicator.className = "status-indicator red";
statusText.textContent = "未连接";
};
ws.onerror = (error) => {
console.error("WebSocket 错误:", error);
statusDiv.textContent = "连接错误";
connectBtn.textContent = "重新连接";
statusIndicator.className = "status-indicator red";
statusText.textContent = "连接错误";
};
}
connectBtn.addEventListener('click', () => {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.close();
} else {
connectWebSocket();
}
});
clearBtn.addEventListener('click', () => {
audioBlobs = [];
audioPlayer.src = "";
statusDiv.textContent = "数据已清空";
});
// 初始连接
connectWebSocket();
</script>
</body>
</html>

View File

@ -0,0 +1,187 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>录音</title>
<style>
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
background-color: #f5f5f5;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 600px;
padding: 20px;
box-sizing: border-box;
}
.title {
font-size: 24px;
font-weight: bold;
margin-bottom: 30px;
color: #333;
}
.status-indicator {
width: 120px;
height: 120px;
border-radius: 50%;
margin: 20px 0 40px;
background-color: #e0e0e0;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.status-text {
color: white;
font-weight: bold;
font-size: 16px;
}
.red {
background-color: #ff5252;
}
.green {
background-color: #4caf50;
}
.btn-group {
display: flex;
gap: 20px;
margin-top: 20px;
}
button {
padding: 12px 24px;
font-size: 16px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
background-color: #2196F3;
color: white;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
button:active {
transform: translateY(0);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
#start {
background-color: #4CAF50;
}
#stop {
background-color: #f44336;
}
</style>
</head>
<body>
<div class="container">
<h1 class="title">实时音频录制</h1>
<div id="statusIndicator" class="status-indicator red">
<span class="status-text">未连接</span>
</div>
<div class="btn-group">
<button id="start" onclick="enterRoom()">启动直播</button>
<button id="stop" onclick="leaveRoom()">关闭直播</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<!-- <script src="https://www.unpkg.com/trtc-sdk-v5@5.9.1/trtc.js"></script> -->
<script src='https://web.sdk.qcloud.com/trtc/webrtc/v5/dist/trtc.js'></script>
<script src="/js/lib-generate-test-usersig.min.js"></script>
<script src="/js/generateTestUserSig.js"></script>
<script>
/* eslint-disable*/
const sdkAppId = 1600079538;
const sdkSecretKey = "df2427757c0ec29ae8ca45611ddb70381144d55338e5ac73c2da27a9c32729f6";
let userId = "主播";
let roomId = 8888;
const statusIndicator = document.getElementById('statusIndicator');
const statusText = statusIndicator.querySelector('.status-text');
let trtc = TRTC.create();
async function enterRoom() {
try {
trtc = TRTC.create();
// 更新状态指示器为过渡状态
statusIndicator.style.backgroundColor = '#FFC107';
statusText.textContent = '连接中...';
// 生成用户签名
const { userSig } = genTestUserSig({ sdkAppId, userId, sdkSecretKey });
// 进入房间
await trtc.enterRoom({ sdkAppId, userId, userSig, roomId: roomId });
// 开启本地音频
await trtc.startLocalAudio();
// 更新状态指示器为绿色(已连接)
statusIndicator.className = 'status-indicator green';
statusText.textContent = '直播中';
// 禁用启动按钮
document.getElementById('start').disabled = true;
} catch (error) {
console.error('进入房间失败:', error);
statusIndicator.className = 'status-indicator red';
statusText.textContent = '连接失败';
}
}
async function leaveRoom() {
try {
await trtc.exitRoom();
trtc.destroy();
// 更新状态指示器为红色(已断开)
statusIndicator.className = 'status-indicator red';
statusText.textContent = '未连接';
// 启用启动按钮
document.getElementById('start').disabled = false;
} catch (error) {
console.error('离开房间失败:', error);
}
}
$(function () {
// 初始化时设置状态
statusIndicator.className = 'status-indicator red';
statusText.textContent = '未连接';
});
</script>
</body>
</html>

View File

@ -0,0 +1,187 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>录音</title>
<style>
body {
font-family: 'Microsoft YaHei', Arial, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
margin: 0;
background-color: #f5f5f5;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
max-width: 600px;
padding: 20px;
box-sizing: border-box;
}
.title {
font-size: 24px;
font-weight: bold;
margin-bottom: 30px;
color: #333;
}
.status-indicator {
width: 120px;
height: 120px;
border-radius: 50%;
margin: 20px 0 40px;
background-color: #e0e0e0;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s ease;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.status-text {
color: white;
font-weight: bold;
font-size: 16px;
}
.red {
background-color: #ff5252;
}
.green {
background-color: #4caf50;
}
.btn-group {
display: flex;
gap: 20px;
margin-top: 20px;
}
button {
padding: 12px 24px;
font-size: 16px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
background-color: #2196F3;
color: white;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2);
}
button:hover {
transform: translateY(-2px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
button:active {
transform: translateY(0);
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
}
#start {
background-color: #4CAF50;
}
#stop {
background-color: #f44336;
}
</style>
</head>
<body>
<div class="container">
<h1 class="title">实时音频录制</h1>
<div id="statusIndicator" class="status-indicator red">
<span class="status-text">未连接</span>
</div>
<div class="btn-group">
<button id="start" onclick="enterRoom()">启动直播</button>
<button id="stop" onclick="leaveRoom()">关闭直播</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.7.1/dist/jquery.min.js"></script>
<!-- <script src="https://www.unpkg.com/trtc-sdk-v5@5.9.1/trtc.js"></script> -->
<script src='https://web.sdk.qcloud.com/trtc/webrtc/v5/dist/trtc.js'></script>
<script src="/js/lib-generate-test-usersig.min.js"></script>
<script src="/js/generateTestUserSig.js"></script>
<script>
const statusIndicator = document.getElementById('statusIndicator');
const statusText = statusIndicator.querySelector('.status-text');
/* eslint-disable*/
const sdkAppId = 1600079538;
const sdkSecretKey = "df2427757c0ec29ae8ca45611ddb70381144d55338e5ac73c2da27a9c32729f6";
let userId = "监听者:" + Math.random().toString(36).substring(2, 15);
let roomId = 8888;
let trtc = TRTC.create();
async function enterRoom() {
try {
trtc = TRTC.create();
// 更新状态指示器为过渡状态
statusIndicator.style.backgroundColor = '#FFC107';
statusText.textContent = '连接中...';
// 生成用户签名
const { userSig } = genTestUserSig({ sdkAppId, userId, sdkSecretKey });
// 进入房间
await trtc.enterRoom({ sdkAppId, userId, userSig, roomId: roomId });
// // 开启本地音频
// await trtc.startLocalAudio();
// 更新状态指示器为绿色(已连接)
statusIndicator.className = 'status-indicator green';
statusText.textContent = '通话中';
// 禁用启动按钮
document.getElementById('start').disabled = true;
} catch (error) {
console.error('进入房间失败:', error);
statusIndicator.className = 'status-indicator red';
statusText.textContent = '连接失败';
}
}
async function leaveRoom() {
try {
await trtc.exitRoom();
trtc.destroy();
// 更新状态指示器为红色(已断开)
statusIndicator.className = 'status-indicator red';
statusText.textContent = '未连接';
// 启用启动按钮
document.getElementById('start').disabled = false;
} catch (error) {
console.error('离开房间失败:', error);
}
}
$(function () {
// 初始化时设置状态
statusIndicator.className = 'status-indicator red';
statusText.textContent = '未连接';
});
</script>
</body>
</html>