| | |
| | | <template> |
| | | <div style="padding:16px"> |
| | | <h2>1v1 语音通话 WebRTC Demo(对齐后端 ChatMessage,JS)</h2> |
| | | <div class="call-container"> |
| | | <div class="content-wrapper"> |
| | | <div class="input-group"> |
| | | <div class="input-item"> |
| | | <span class="input-label">我的 userId:</span> |
| | | <input v-model="uid" class="input-field" /> |
| | | </div> |
| | | |
| | | <div class="input-item"> |
| | | <span class="input-label">对方 userId:</span> |
| | | <input v-model="peerUid" class="input-field" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <div style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;"> |
| | | <span>我的 userId:</span> |
| | | <input v-model="uid" style="width:120px" /> |
| | | <div class="button-group"> |
| | | <button @click="connectWS" :disabled="connected" class="btn btn-connect">连接WS</button> |
| | | </div> |
| | | |
| | | <span>对方 userId:</span> |
| | | <input v-model="peerUid" style="width:120px" /> |
| | | <div class="button-group"> |
| | | <button @click="requestCall" :disabled="!connected || state!=='idle'" class="btn btn-call">呼叫</button> |
| | | |
| | | <button v-if="state==='ringing'" @click="acceptCall" class="btn btn-accept">接听</button> |
| | | <button v-if="state==='ringing'" @click="rejectCall" class="btn btn-reject">拒绝</button> |
| | | |
| | | <button @click="hangup" :disabled="!connected || (state!=='calling' && state!=='in-call')" class="btn btn-hangup">挂断</button> |
| | | </div> |
| | | |
| | | <button @click="connectWS" :disabled="connected">连接WS</button> |
| | | |
| | | <button @click="requestCall" :disabled="!connected || state!=='idle'">呼叫</button> |
| | | |
| | | <button v-if="state==='ringing'" @click="acceptCall">接听</button> |
| | | <button v-if="state==='ringing'" @click="rejectCall">拒绝</button> |
| | | |
| | | <button @click="hangup" :disabled="!connected || (state!=='calling' && state!=='in-call')">挂断</button> |
| | | |
| | | <strong>状态:{{ state }}</strong> |
| | | <strong v-if="state==='in-call'">通话时长:{{ durationText }}</strong> |
| | | <div class="status-group"> |
| | | <strong class="status-text">状态:<span :style="{color: getStateColor(state)}">{{ state }}</span></strong> |
| | | <strong v-if="state==='in-call'" class="status-text">通话时长:{{ durationText }}</strong> |
| | | </div> |
| | | </div> |
| | | |
| | | <div style="margin-top:8px;color:#666"> |
| | | <div class="tip-message"> |
| | | <small>提示:若 getUserMedia 不可用,请用 https 或 localhost 访问页面。</small> |
| | | </div> |
| | | |
| | | <pre style="margin-top:12px;max-height:280px;overflow:auto;">{{ logText }}</pre> |
| | | <div class="log-container"> |
| | | <div class="log-box"> |
| | | <div class="log-title">日志信息:</div> |
| | | <pre class="log-content">{{ logText }}</pre> |
| | | </div> |
| | | </div> |
| | | |
| | | <audio ref="remoteAudio" autoplay></audio> |
| | | <audio ref="remoteAudio" autoplay class="hidden-audio"></audio> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import { ref, computed, onBeforeUnmount, onMounted } from 'vue' |
| | | |
| | | /** ✅ 你的 WS 地址前缀(后面拼 userId) */ |
| | | const WS_BASE = 'ws://192.168.1.33:8081/ws/chat?userId=' |
| | | const WS_BASE = 'wss://wrj.shuixiongit.com/ws/chat?userId=' |
| | | |
| | | // 解析URL参数 |
| | | const parseUrlParams = () => { |
| | |
| | | } |
| | | } |
| | | |
| | | const uid = ref('2') |
| | | const peerUid = ref('3') |
| | | const uid = ref('3') |
| | | const peerUid = ref('2') |
| | | |
| | | const connected = ref(false) |
| | | const state = ref('idle') // idle | calling | ringing | in-call |
| | | const logText = ref('') |
| | | const remoteAudio = ref(null) |
| | | |
| | | /** |
| | | * 根据状态返回对应颜色 |
| | | */ |
| | | const getStateColor = (state) => { |
| | | const colors = { |
| | | 'idle': '#666', |
| | | 'calling': '#2196F3', |
| | | 'ringing': '#FF9800', |
| | | 'in-call': '#4CAF50' |
| | | } |
| | | return colors[state] || '#666' |
| | | } |
| | | |
| | | let ws = null |
| | | let pc = null |
| | |
| | | if (ws) ws.close() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | | .call-container { |
| | | padding: 20px; |
| | | background-color: #f5f5f5; |
| | | min-height: 100vh; |
| | | font-family: Arial, sans-serif; |
| | | } |
| | | |
| | | .content-wrapper { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 15px; |
| | | max-width: 600px; |
| | | margin: 0 auto; |
| | | } |
| | | |
| | | .input-group { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 10px; |
| | | } |
| | | |
| | | .input-item { |
| | | display: flex; |
| | | justify-content: space-between; |
| | | align-items: center; |
| | | } |
| | | |
| | | .input-label { |
| | | font-weight: bold; |
| | | color: #333; |
| | | } |
| | | |
| | | .input-field { |
| | | width: 120px; |
| | | padding: 8px; |
| | | border: 1px solid #ddd; |
| | | border-radius: 4px; |
| | | font-size: 14px; |
| | | } |
| | | |
| | | .button-group { |
| | | display: flex; |
| | | gap: 10px; |
| | | flex-wrap: wrap; |
| | | } |
| | | |
| | | .btn { |
| | | padding: 10px 20px; |
| | | color: white; |
| | | border: none; |
| | | border-radius: 4px; |
| | | cursor: pointer; |
| | | font-size: 14px; |
| | | transition: background-color 0.3s; |
| | | |
| | | &:disabled { |
| | | opacity: 0.6; |
| | | cursor: not-allowed; |
| | | } |
| | | } |
| | | |
| | | .btn-connect { |
| | | background-color: #4CAF50; |
| | | } |
| | | |
| | | .btn-call { |
| | | background-color: #2196F3; |
| | | } |
| | | |
| | | .btn-accept { |
| | | background-color: #4CAF50; |
| | | } |
| | | |
| | | .btn-reject { |
| | | background-color: #f44336; |
| | | } |
| | | |
| | | .btn-hangup { |
| | | background-color: #9E9E9E; |
| | | } |
| | | |
| | | .status-group { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 5px; |
| | | } |
| | | |
| | | .status-text { |
| | | font-size: 16px; |
| | | color: #333; |
| | | } |
| | | |
| | | .tip-message { |
| | | margin-top: 15px; |
| | | color: #666; |
| | | text-align: center; |
| | | } |
| | | |
| | | .log-container { |
| | | margin-top: 20px; |
| | | max-width: 600px; |
| | | margin-left: auto; |
| | | margin-right: auto; |
| | | } |
| | | |
| | | .log-box { |
| | | background-color: white; |
| | | border: 1px solid #ddd; |
| | | border-radius: 8px; |
| | | padding: 10px; |
| | | } |
| | | |
| | | .log-title { |
| | | font-weight: bold; |
| | | color: #333; |
| | | margin-bottom: 8px; |
| | | } |
| | | |
| | | .log-content { |
| | | max-height: 200px; |
| | | overflow: auto; |
| | | background-color: #fafafa; |
| | | padding: 10px; |
| | | border-radius: 4px; |
| | | font-size: 12px; |
| | | line-height: 1.4; |
| | | } |
| | | |
| | | .hidden-audio { |
| | | display: none; |
| | | } |
| | | </style> |