视频会议
This commit is contained in:
@@ -467,26 +467,27 @@ const acquireVideoTrack = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onMicToggle = async () => {
|
const onMicToggle = async () => {
|
||||||
pushDebug(`mic点击 loading=${audioLoading.value} leaving=${leaving.value} on=${audioEnabled.value} producer=${meetingStore.localProducers?.audio || 'null'}`)
|
pushDebug(`mic点击 loading=${audioLoading.value} on=${audioEnabled.value} producer=${meetingStore.localProducers?.audio || 'null'}`)
|
||||||
if (audioLoading.value || leaving.value) {
|
if (audioLoading.value || leaving.value) {
|
||||||
pushDebug('mic 被 loading/leaving 拦住')
|
pushDebug('mic 被 loading/leaving 拦住')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
audioLoading.value = true
|
audioLoading.value = true
|
||||||
try {
|
try {
|
||||||
if (audioEnabled.value) {
|
if (meetingStore.localProducers?.audio) {
|
||||||
pushDebug('mic -> stopLocalAudio')
|
// 已经有 producer:只 mute/unmute,不释放硬件(关键:WebView 释放后申请会被拒)
|
||||||
await meetingStore.stopLocalAudio()
|
pushDebug(`mic -> setLocalAudioMuted(${audioEnabled.value})`)
|
||||||
pushDebug('mic stop 完成')
|
await meetingStore.setLocalAudioMuted(audioEnabled.value)
|
||||||
|
pushDebug(`mic mute 切换完成 enabled=${meetingStore.localAudioEnabled}`)
|
||||||
} else {
|
} else {
|
||||||
|
// 没 producer:兜底走完整流程,自己 getUserMedia 拿 track(用户手势作用域内)
|
||||||
pushDebug('mic -> getUserMedia(audio)')
|
pushDebug('mic -> getUserMedia(audio)')
|
||||||
const track = await acquireAudioTrack()
|
const track = await acquireAudioTrack()
|
||||||
pushDebug(`mic 拿到 track id=${track?.id?.slice(0, 8)} label=${track?.label || ''}`)
|
pushDebug(`mic 拿到 track id=${track?.id?.slice(0, 8)} label=${track?.label || ''}`)
|
||||||
pushDebug('mic -> startLocalAudio(track)')
|
|
||||||
await meetingStore.startLocalAudio(undefined, track)
|
await meetingStore.startLocalAudio(undefined, track)
|
||||||
pushDebug(`mic start 完成 enabled=${meetingStore.localAudioEnabled}`)
|
pushDebug(`mic start 完成 enabled=${meetingStore.localAudioEnabled}`)
|
||||||
localMediaError.value = ''
|
|
||||||
}
|
}
|
||||||
|
localMediaError.value = ''
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
pushDebug(`mic 报错: ${err?.name || ''} ${err?.message || err}`)
|
pushDebug(`mic 报错: ${err?.name || ''} ${err?.message || err}`)
|
||||||
localMediaError.value = `麦克风:${err?.message || err}`
|
localMediaError.value = `麦克风:${err?.message || err}`
|
||||||
@@ -496,26 +497,25 @@ const onMicToggle = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const onCamToggle = async () => {
|
const onCamToggle = async () => {
|
||||||
pushDebug(`cam点击 loading=${videoLoading.value} leaving=${leaving.value} on=${videoEnabled.value} producer=${meetingStore.localProducers?.video || 'null'}`)
|
pushDebug(`cam点击 loading=${videoLoading.value} on=${videoEnabled.value} producer=${meetingStore.localProducers?.video || 'null'}`)
|
||||||
if (videoLoading.value || leaving.value) {
|
if (videoLoading.value || leaving.value) {
|
||||||
pushDebug('cam 被 loading/leaving 拦住')
|
pushDebug('cam 被 loading/leaving 拦住')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
videoLoading.value = true
|
videoLoading.value = true
|
||||||
try {
|
try {
|
||||||
if (videoEnabled.value) {
|
if (meetingStore.localProducers?.video) {
|
||||||
pushDebug('cam -> stopLocalVideo')
|
pushDebug(`cam -> setLocalVideoMuted(${videoEnabled.value})`)
|
||||||
await meetingStore.stopLocalVideo()
|
await meetingStore.setLocalVideoMuted(videoEnabled.value)
|
||||||
pushDebug('cam stop 完成')
|
pushDebug(`cam mute 切换完成 enabled=${meetingStore.localVideoEnabled}`)
|
||||||
} else {
|
} else {
|
||||||
pushDebug('cam -> getUserMedia(video)')
|
pushDebug('cam -> getUserMedia(video)')
|
||||||
const track = await acquireVideoTrack()
|
const track = await acquireVideoTrack()
|
||||||
pushDebug(`cam 拿到 track id=${track?.id?.slice(0, 8)} label=${track?.label || ''}`)
|
pushDebug(`cam 拿到 track id=${track?.id?.slice(0, 8)} label=${track?.label || ''}`)
|
||||||
pushDebug('cam -> startLocalVideo(track)')
|
|
||||||
await meetingStore.startLocalVideo(undefined, track)
|
await meetingStore.startLocalVideo(undefined, track)
|
||||||
pushDebug(`cam start 完成 enabled=${meetingStore.localVideoEnabled}`)
|
pushDebug(`cam start 完成 enabled=${meetingStore.localVideoEnabled}`)
|
||||||
localMediaError.value = ''
|
|
||||||
}
|
}
|
||||||
|
localMediaError.value = ''
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
pushDebug(`cam 报错: ${err?.name || ''} ${err?.message || err}`)
|
pushDebug(`cam 报错: ${err?.name || ''} ${err?.message || err}`)
|
||||||
localMediaError.value = `摄像头:${err?.message || err}`
|
localMediaError.value = `摄像头:${err?.message || err}`
|
||||||
|
|||||||
@@ -1217,6 +1217,36 @@ export const useMeetingStore = defineStore('meeting', () => {
|
|||||||
_broadcastSelfState({ video_enabled: true })
|
_broadcastSelfState({ video_enabled: true })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 静音/解除静音本地音频(不释放硬件)
|
||||||
|
* 用于"点击麦克风按钮关/开"场景:避免在 iOS Safari / 微信 WebView 上释放硬件后再申请被拒。
|
||||||
|
* 与 stopLocalAudio 区别:本方法只暂停 Producer,track 仍持有硬件,可幂等多次切换。
|
||||||
|
*/
|
||||||
|
const setLocalAudioMuted = async (muted) => {
|
||||||
|
if (!_engine || !localProducers.audio) return false
|
||||||
|
const ok = muted
|
||||||
|
? _engine.pauseProducer(localProducers.audio)
|
||||||
|
: _engine.resumeProducer(localProducers.audio)
|
||||||
|
if (ok) {
|
||||||
|
localAudioEnabled.value = !muted
|
||||||
|
_broadcastSelfState({ audio_enabled: !muted })
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 静音/解除静音本地视频(不释放硬件),用法同 setLocalAudioMuted */
|
||||||
|
const setLocalVideoMuted = async (muted) => {
|
||||||
|
if (!_engine || !localProducers.video) return false
|
||||||
|
const ok = muted
|
||||||
|
? _engine.pauseProducer(localProducers.video)
|
||||||
|
: _engine.resumeProducer(localProducers.video)
|
||||||
|
if (ok) {
|
||||||
|
localVideoEnabled.value = !muted
|
||||||
|
_broadcastSelfState({ video_enabled: !muted })
|
||||||
|
}
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
/** 关闭本地音频 */
|
/** 关闭本地音频 */
|
||||||
const stopLocalAudio = async () => {
|
const stopLocalAudio = async () => {
|
||||||
if (!_engine || !localProducers.audio) return
|
if (!_engine || !localProducers.audio) return
|
||||||
@@ -1617,6 +1647,8 @@ export const useMeetingStore = defineStore('meeting', () => {
|
|||||||
startLocalVideo,
|
startLocalVideo,
|
||||||
stopLocalAudio,
|
stopLocalAudio,
|
||||||
stopLocalVideo,
|
stopLocalVideo,
|
||||||
|
setLocalAudioMuted,
|
||||||
|
setLocalVideoMuted,
|
||||||
startScreenShare,
|
startScreenShare,
|
||||||
stopScreenShare,
|
stopScreenShare,
|
||||||
recording,
|
recording,
|
||||||
|
|||||||
@@ -405,6 +405,39 @@ export function createMediaEngine({ roomCode, userId, sendWithAck, logger = defa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 暂停指定 Producer:mediasoup-client 内部会把 track.enabled=false,
|
||||||
|
* 远端 Consumer 收到的就是静音/黑帧,但本地 track 仍持有硬件不释放。
|
||||||
|
* 用于"关摄像头/麦克风"按钮:避免在 iOS Safari / 微信 WebView 上释放硬件后再申请被拒(NotAllowedError)。
|
||||||
|
*/
|
||||||
|
const pauseProducer = (producerId) => {
|
||||||
|
const producer = producers.get(producerId)
|
||||||
|
if (!producer || producer.closed) return false
|
||||||
|
try {
|
||||||
|
if (!producer.paused) producer.pause()
|
||||||
|
// 双保险:有些 mediasoup-client 版本不会自动改 track.enabled
|
||||||
|
if (producer.track) producer.track.enabled = false
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
logger('warn', '[MediaEngine] producer.pause 抛错', e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 恢复 Producer:与 pauseProducer 对应 */
|
||||||
|
const resumeProducer = (producerId) => {
|
||||||
|
const producer = producers.get(producerId)
|
||||||
|
if (!producer || producer.closed) return false
|
||||||
|
try {
|
||||||
|
if (producer.paused) producer.resume()
|
||||||
|
if (producer.track) producer.track.enabled = true
|
||||||
|
return true
|
||||||
|
} catch (e) {
|
||||||
|
logger('warn', '[MediaEngine] producer.resume 抛错', e)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 本地 Consumer 关闭(通常由 producer.new closed=true 广播触发,无需额外 WS) */
|
/** 本地 Consumer 关闭(通常由 producer.new closed=true 广播触发,无需额外 WS) */
|
||||||
const closeConsumer = (consumerId) => {
|
const closeConsumer = (consumerId) => {
|
||||||
const consumer = consumers.get(consumerId)
|
const consumer = consumers.get(consumerId)
|
||||||
@@ -445,6 +478,8 @@ export function createMediaEngine({ roomCode, userId, sendWithAck, logger = defa
|
|||||||
consume,
|
consume,
|
||||||
resumeConsumer,
|
resumeConsumer,
|
||||||
closeProducer,
|
closeProducer,
|
||||||
|
pauseProducer,
|
||||||
|
resumeProducer,
|
||||||
closeConsumer,
|
closeConsumer,
|
||||||
close,
|
close,
|
||||||
getDevice: () => device,
|
getDevice: () => device,
|
||||||
|
|||||||
Reference in New Issue
Block a user