适配微信小程序
This commit is contained in:
@@ -36,7 +36,15 @@
|
|||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 本地推流 -->
|
<!-- 本地推流 -->
|
||||||
|
<!-- autopush: URL 设置后自动推流,组件会自动判断何时准备好 -->
|
||||||
|
<!--
|
||||||
|
关键配置说明:
|
||||||
|
- video-codec: 优先使用硬件编码,兼容性更好
|
||||||
|
- min-bitrate/max-bitrate: 设置合理的码率范围,避免超出设备能力
|
||||||
|
- video-width/video-height: 明确指定分辨率,避免自动选择不支持的分辨率
|
||||||
|
-->
|
||||||
<live-pusher
|
<live-pusher
|
||||||
|
v-if="pushUrl"
|
||||||
id="local-pusher"
|
id="local-pusher"
|
||||||
class="local-pusher"
|
class="local-pusher"
|
||||||
:url="pushUrl"
|
:url="pushUrl"
|
||||||
@@ -45,9 +53,18 @@
|
|||||||
:enable-camera="!call.camOff"
|
:enable-camera="!call.camOff"
|
||||||
:enable-mic="!call.muted"
|
:enable-mic="!call.muted"
|
||||||
aspect="9:16"
|
aspect="9:16"
|
||||||
beauty="5"
|
:beauty="5"
|
||||||
whiteness="3"
|
:whiteness="3"
|
||||||
|
orientation="vertical"
|
||||||
|
device-position="front"
|
||||||
|
video-codec="hardware"
|
||||||
|
:min-bitrate="200"
|
||||||
|
:max-bitrate="1000"
|
||||||
|
:video-width="360"
|
||||||
|
:video-height="640"
|
||||||
|
audio-quality="high"
|
||||||
@statechange="onPusherStateChange"
|
@statechange="onPusherStateChange"
|
||||||
|
@netstatus="onPusherNetStatus"
|
||||||
@error="onPusherError"
|
@error="onPusherError"
|
||||||
/>
|
/>
|
||||||
</view>
|
</view>
|
||||||
@@ -175,24 +192,42 @@ const statusDotClass = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 监听通话激活,初始化 Context
|
// 监听推流地址变化,在 live-pusher 渲染完成后初始化 Context
|
||||||
watch(() => call.active, (newVal) => {
|
// 关键修复:之前监听 call.active,但此时 pushUrl 还是空的,live-pusher 未渲染
|
||||||
if (newVal) {
|
// 现在改为监听 pushUrl,确保 live-pusher 渲染后再初始化 Context
|
||||||
|
watch(() => pushUrl.value, (newVal, oldVal) => {
|
||||||
|
if (newVal && !oldVal) {
|
||||||
|
// pushUrl 从空变为有值,live-pusher 即将渲染
|
||||||
|
console.log('[MiniProgramCall] pushUrl 已设置,准备初始化 Context')
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 传入当前组件实例,确保 live-pusher 能被找到
|
// 等待 live-pusher 完全渲染后再初始化
|
||||||
// 在同层渲染模式下,这点尤为重要
|
setTimeout(() => {
|
||||||
initPusherContext(instance)
|
console.log('[MiniProgramCall] 延迟后初始化 Pusher Context')
|
||||||
|
initPusherContext(instance)
|
||||||
|
}, 500)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const onPusherError = (e: any) => {
|
const onPusherError = (e: any) => {
|
||||||
console.error('Pusher Error:', e)
|
console.error('[MiniProgramCall] Pusher Error:', e)
|
||||||
uni.showToast({ title: '推流失败,请检查摄像头权限', icon: 'none' })
|
const detail = e.detail || e
|
||||||
|
if (detail.errCode === 10001) {
|
||||||
|
uni.showToast({ title: '请授权摄像头和麦克风权限', icon: 'none' })
|
||||||
|
} else {
|
||||||
|
uni.showToast({ title: '推流失败,请检查权限设置', icon: 'none' })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPusherNetStatus = (e: any) => {
|
||||||
|
const info = e.detail?.info || e.detail
|
||||||
|
if (info) {
|
||||||
|
console.log('[MiniProgramCall] 推流网络状态:', info)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onPlayerError = (e: any, userId: string) => {
|
const onPlayerError = (e: any, userId: string) => {
|
||||||
console.error(`Player Error [${userId}]:`, e)
|
console.error(`[MiniProgramCall] Player Error [${userId}]:`, e)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,10 @@ let currentReceiverUserId = ''
|
|||||||
// live-pusher 组件上下文
|
// live-pusher 组件上下文
|
||||||
let pusherContext: UniApp.LivePusherContext | null = null
|
let pusherContext: UniApp.LivePusherContext | null = null
|
||||||
|
|
||||||
|
// 推流状态标记
|
||||||
|
let isPushingSucceeded = false // autopush 是否已成功(收到 1009)
|
||||||
|
let isStartingPush = false // 是否正在启动推流(防止并发调用)
|
||||||
|
|
||||||
export function useMiniProgramCall() {
|
export function useMiniProgramCall() {
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const chatStore = useChatStore()
|
const chatStore = useChatStore()
|
||||||
@@ -273,7 +277,7 @@ export function useMiniProgramCall() {
|
|||||||
uni.showToast({ title: '服务器未返回推流地址', icon: 'none' })
|
uni.showToast({ title: '服务器未返回推流地址', icon: 'none' })
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置拉流地址
|
// 设置拉流地址 (包括 Web/H5 用户的 RTMP 流)
|
||||||
if (response.pull_urls && response.pull_urls.length > 0) {
|
if (response.pull_urls && response.pull_urls.length > 0) {
|
||||||
remoteStreams.value = response.pull_urls.map(p => ({
|
remoteStreams.value = response.pull_urls.map(p => ({
|
||||||
userId: p.user_id,
|
userId: p.user_id,
|
||||||
@@ -281,6 +285,12 @@ export function useMiniProgramCall() {
|
|||||||
flvUrl: p.flv_url,
|
flvUrl: p.flv_url,
|
||||||
}))
|
}))
|
||||||
console.log('[MiniProgramCall] 拉流地址:', remoteStreams.value)
|
console.log('[MiniProgramCall] 拉流地址:', remoteStreams.value)
|
||||||
|
|
||||||
|
// 检测 Web 用户的流并提示
|
||||||
|
const webStreams = remoteStreams.value.filter(s => s.pullUrl && !s.pullUrl.includes('miniprogram'))
|
||||||
|
if (webStreams.length > 0) {
|
||||||
|
console.log('[MiniProgramCall] 检测到 Web 用户流:', webStreams.length)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 参与者信息
|
// 参与者信息
|
||||||
@@ -288,11 +298,41 @@ export function useMiniProgramCall() {
|
|||||||
console.log('[MiniProgramCall] 房间参与者:', response.participants)
|
console.log('[MiniProgramCall] 房间参与者:', response.participants)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关键修复:使用 nextTick 确保 DOM 更新,live-pusher 的 url 属性生效后再启动
|
// 重置推流状态标记
|
||||||
|
isPushingSucceeded = false
|
||||||
|
isStartingPush = false
|
||||||
|
|
||||||
|
// autopush=true 模式下,live-pusher 会在 URL 设置后自动推流
|
||||||
|
// 时序说明:
|
||||||
|
// 1. pushUrl 设置后,MiniProgramCallWindow.vue 中的 watch 会触发
|
||||||
|
// 2. watch 中会延迟 500ms 后调用 initPusherContext()
|
||||||
|
// 3. autopush 会自动开始推流
|
||||||
|
// 4. 当收到状态码 1009 (Send first video frame) 时,标记推流成功
|
||||||
|
// 5. 备用推流只在 autopush 未成功时触发
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
checkAndLogPushStatus()
|
||||||
|
|
||||||
|
// 延迟后检查是否需要手动启动推流(仅作为 autopush 失败的备用方案)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
startPushing()
|
// 关键检查:如果 autopush 已经成功,不再调用手动推流
|
||||||
}, 500) // 延迟500ms确保组件渲染完成
|
if (isPushingSucceeded) {
|
||||||
|
console.log('[MiniProgramCall] ✅ autopush 已成功,跳过备用推流')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (call.active && pushUrl.value && pusherContext) {
|
||||||
|
console.log('[MiniProgramCall] 🔄 autopush 未成功,尝试手动启动推流')
|
||||||
|
startPushing()
|
||||||
|
} else if (call.active && pushUrl.value && !pusherContext) {
|
||||||
|
console.warn('[MiniProgramCall] ⚠️ pusherContext 未初始化,等待后重试')
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!isPushingSucceeded && call.active && pushUrl.value && pusherContext) {
|
||||||
|
console.log('[MiniProgramCall] 🔄 重试手动启动推流')
|
||||||
|
startPushing()
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
}, 3000) // 延长到 3 秒,给 autopush 更多时间
|
||||||
})
|
})
|
||||||
|
|
||||||
// 开始计时
|
// 开始计时
|
||||||
@@ -383,64 +423,233 @@ export function useMiniProgramCall() {
|
|||||||
pushUrl.value = ''
|
pushUrl.value = ''
|
||||||
remoteStreams.value = []
|
remoteStreams.value = []
|
||||||
currentReceiverUserId = ''
|
currentReceiverUserId = ''
|
||||||
|
|
||||||
|
// 重置推流状态标记
|
||||||
|
isPushingSucceeded = false
|
||||||
|
isStartingPush = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开始推流
|
* 检查并请求摄像头和麦克风权限
|
||||||
*/
|
*/
|
||||||
function startPushing() {
|
async function checkAndRequestPermissions(): Promise<boolean> {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
try {
|
||||||
|
// 检查摄像头权限
|
||||||
|
const cameraRes = await uni.getSetting({})
|
||||||
|
const cameraAuth = (cameraRes as any).authSetting?.['scope.camera']
|
||||||
|
const recordAuth = (cameraRes as any).authSetting?.['scope.record']
|
||||||
|
|
||||||
|
if (cameraAuth === false || recordAuth === false) {
|
||||||
|
// 权限被拒绝,引导用户到设置页
|
||||||
|
uni.showModal({
|
||||||
|
title: '权限提示',
|
||||||
|
content: '需要摄像头和麦克风权限才能进行视频通话,请在设置中开启',
|
||||||
|
confirmText: '去设置',
|
||||||
|
success: (res) => {
|
||||||
|
if (res.confirm) {
|
||||||
|
uni.openSetting({})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cameraAuth === undefined) {
|
||||||
|
// 未授权,请求授权
|
||||||
|
await uni.authorize({ scope: 'scope.camera' })
|
||||||
|
}
|
||||||
|
if (recordAuth === undefined) {
|
||||||
|
await uni.authorize({ scope: 'scope.record' })
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[MiniProgramCall] 权限请求失败:', err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查推流状态并记录日志(autopush 模式下使用)
|
||||||
|
*/
|
||||||
|
async function checkAndLogPushStatus() {
|
||||||
// #ifdef MP-WEIXIN
|
// #ifdef MP-WEIXIN
|
||||||
// 检测是否在开发者工具中运行
|
|
||||||
const systemInfo = uni.getSystemInfoSync()
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
const isDevtools = systemInfo.platform === 'devtools'
|
const isDevtools = systemInfo.platform === 'devtools'
|
||||||
|
|
||||||
if (isDevtools) {
|
if (isDevtools) {
|
||||||
console.warn('[MiniProgramCall] ⚠️ 开发者工具不支持 live-pusher,请在真机上测试')
|
console.warn('[MiniProgramCall] ⚠️ 开发者工具不支持 live-pusher')
|
||||||
uni.showToast({ title: '开发者工具不支持推流,请真机测试', icon: 'none', duration: 3000 })
|
console.warn('[MiniProgramCall] 请使用手机扫码在「真机调试」或「预览」模式测试')
|
||||||
|
uni.showToast({
|
||||||
|
title: '开发者工具不支持视频通话,请真机测试',
|
||||||
|
icon: 'none',
|
||||||
|
duration: 4000
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查权限
|
||||||
|
const hasPermission = await checkAndRequestPermissions()
|
||||||
|
if (!hasPermission) {
|
||||||
|
console.error('[MiniProgramCall] ❌ 未获得必要权限')
|
||||||
|
uni.showToast({
|
||||||
|
title: '请授权摄像头和麦克风权限',
|
||||||
|
icon: 'none',
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[MiniProgramCall] 🎬 autopush 模式,推流地址已设置')
|
||||||
|
console.log('[MiniProgramCall] 推流地址:', pushUrl.value)
|
||||||
|
console.log('[MiniProgramCall] 设备信息:', {
|
||||||
|
platform: systemInfo.platform,
|
||||||
|
model: systemInfo.model,
|
||||||
|
system: systemInfo.system,
|
||||||
|
brand: systemInfo.brand,
|
||||||
|
SDKVersion: systemInfo.SDKVersion
|
||||||
|
})
|
||||||
|
|
||||||
|
// 检查基础库版本是否支持 live-pusher
|
||||||
|
const sdkVersion = systemInfo.SDKVersion || ''
|
||||||
|
const versionParts = sdkVersion.split('.').map(Number)
|
||||||
|
if (versionParts[0] < 2 || (versionParts[0] === 2 && versionParts[1] < 9)) {
|
||||||
|
console.warn('[MiniProgramCall] ⚠️ 基础库版本过低,建议升级到 2.9.0+')
|
||||||
|
uni.showToast({
|
||||||
|
title: '微信版本过低,请更新微信',
|
||||||
|
icon: 'none',
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 手动开始推流
|
||||||
|
* 关键:在 RTC 模式下,需要先调用 enterRoom() 再调用 start()
|
||||||
|
* 参考网友方案:enterRoom() 后调用 start(),设置 setTimeout 延迟
|
||||||
|
*/
|
||||||
|
async function startPushing() {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
|
const isDevtools = systemInfo.platform === 'devtools'
|
||||||
|
|
||||||
|
if (isDevtools) {
|
||||||
|
console.warn('[MiniProgramCall] ⚠️ 开发者工具不支持 live-pusher')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防止并发调用
|
||||||
|
if (isStartingPush) {
|
||||||
|
console.log('[MiniProgramCall] ⏳ 推流正在启动中,跳过重复调用')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果已经推流成功,不再重复启动
|
||||||
|
if (isPushingSucceeded) {
|
||||||
|
console.log('[MiniProgramCall] ✅ 推流已成功,跳过重复启动')
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pusherContext) {
|
if (!pusherContext) {
|
||||||
console.error('[MiniProgramCall] ❌ Pusher Context 未初始化')
|
console.error('[MiniProgramCall] ❌ Pusher Context 未初始化')
|
||||||
console.error('[MiniProgramCall] 请确保:')
|
|
||||||
console.error(' 1. 组件中有 <live-pusher id="local-pusher" ...>')
|
|
||||||
console.error(' 2. 调用了 initPusherContext(getCurrentInstance())')
|
|
||||||
uni.showToast({ title: 'live-pusher 组件未初始化', icon: 'none' })
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 确保 URL 存在
|
|
||||||
if (!pushUrl.value) {
|
if (!pushUrl.value) {
|
||||||
console.error('[MiniProgramCall] ❌ 推流地址为空,无法推流')
|
console.error('[MiniProgramCall] ❌ 推流地址为空')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[MiniProgramCall] 开始推流,URL:', pushUrl.value)
|
isStartingPush = true
|
||||||
|
console.log('[MiniProgramCall] 🎬 手动启动推流 - 使用 enterRoom + start 模式')
|
||||||
|
|
||||||
|
// 关键修复:先调用 enterRoom() 进入 RTC 房间
|
||||||
|
// 这是解决 operateXWebLivePusher:fail:internal error 的关键步骤
|
||||||
|
try {
|
||||||
|
// @ts-ignore - enterRoom 是较新的 API,类型定义可能缺失
|
||||||
|
const ctx = pusherContext as any
|
||||||
|
if (typeof ctx.enterRoom === 'function') {
|
||||||
|
ctx.enterRoom({
|
||||||
|
success: () => {
|
||||||
|
console.log('[MiniProgramCall] ✅ enterRoom 成功')
|
||||||
|
// 延迟后调用 start(),确保房间准备就绪
|
||||||
|
setTimeout(() => {
|
||||||
|
doStartPushing()
|
||||||
|
}, 500)
|
||||||
|
},
|
||||||
|
fail: (err: any) => {
|
||||||
|
console.warn('[MiniProgramCall] ⚠️ enterRoom 失败,直接尝试 start:', err)
|
||||||
|
// enterRoom 失败时直接尝试 start
|
||||||
|
doStartPushing()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 如果 enterRoom 不可用,直接调用 start
|
||||||
|
console.log('[MiniProgramCall] enterRoom 不可用,直接调用 start')
|
||||||
|
doStartPushing()
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[MiniProgramCall] enterRoom 调用异常,直接尝试 start:', e)
|
||||||
|
doStartPushing()
|
||||||
|
}
|
||||||
|
// #endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实际执行推流启动
|
||||||
|
*/
|
||||||
|
function doStartPushing() {
|
||||||
|
// #ifdef MP-WEIXIN
|
||||||
|
if (!pusherContext) {
|
||||||
|
isStartingPush = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
pusherContext.start({
|
pusherContext.start({
|
||||||
success: () => {
|
success: () => {
|
||||||
console.log('[MiniProgramCall] ✅ 推流启动成功')
|
console.log('[MiniProgramCall] ✅ 推流启动成功(start回调)')
|
||||||
|
isStartingPush = false
|
||||||
|
// 注意:真正的推流成功标志是收到状态码 1009
|
||||||
},
|
},
|
||||||
fail: (err: any) => {
|
fail: (err: any) => {
|
||||||
console.error('[MiniProgramCall] ❌ 推流启动失败:', err)
|
console.error('[MiniProgramCall] ❌ 推流启动失败:', err)
|
||||||
console.error('[MiniProgramCall] 错误信息:', JSON.stringify(err))
|
isStartingPush = false
|
||||||
|
|
||||||
// 详细的错误提示
|
|
||||||
let errorMsg = '推流失败'
|
let errorMsg = '推流失败'
|
||||||
if (err.errMsg) {
|
const errMsg = err.errMsg || ''
|
||||||
if (err.errMsg.includes('internal error')) {
|
const errno = err.errno
|
||||||
errorMsg = '推流内部错误,请检查RTMP服务器'
|
|
||||||
} else if (err.errMsg.includes('operateLivePusher:fail')) {
|
if (errMsg.includes('internal error') || errno === 4) {
|
||||||
errorMsg = '推流操作失败'
|
// internal error 通常是因为推流器已经在运行,或者状态冲突
|
||||||
} else if (err.errMsg.includes('permission')) {
|
// 如果 autopush 已经在工作,这个错误可以忽略
|
||||||
errorMsg = '缺少摄像头/麦克风权限'
|
if (isPushingSucceeded) {
|
||||||
|
console.log('[MiniProgramCall] ℹ️ internal error,但 autopush 已成功,忽略')
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
errorMsg = '推流服务异常'
|
||||||
|
console.log('[MiniProgramCall] 🔄 internal error,2秒后重试...')
|
||||||
uni.showToast({ title: errorMsg, icon: 'none', duration: 3000 })
|
setTimeout(() => {
|
||||||
|
if (!isPushingSucceeded && call.active && pusherContext) {
|
||||||
// 在开发者工具中,提示用户
|
isStartingPush = true
|
||||||
if (isDevtools) {
|
pusherContext.start({
|
||||||
console.warn('[MiniProgramCall] 💡 提示:开发者工具不支持 live-pusher')
|
success: () => {
|
||||||
console.warn('[MiniProgramCall] 请使用手机扫码在真机预览中测试')
|
console.log('[MiniProgramCall] ✅ 重试推流成功')
|
||||||
|
isStartingPush = false
|
||||||
|
},
|
||||||
|
fail: (e: any) => {
|
||||||
|
console.error('[MiniProgramCall] ❌ 重试推流仍失败:', e)
|
||||||
|
isStartingPush = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, 2000)
|
||||||
|
} else if (errMsg.includes('permission') || errno === 10001) {
|
||||||
|
errorMsg = '请授权摄像头和麦克风权限'
|
||||||
|
uni.showToast({ title: errorMsg, icon: 'none', duration: 3000 })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -605,20 +814,45 @@ export function useMiniProgramCall() {
|
|||||||
break
|
break
|
||||||
case 1004: // 自动调整分辨率
|
case 1004: // 自动调整分辨率
|
||||||
break
|
break
|
||||||
case 1005: // 成功打开麦克风
|
case 1005: // 推流动态调整分辨率
|
||||||
|
break
|
||||||
|
case 1006: // 推流动态调整码率
|
||||||
|
break
|
||||||
|
case 1007: // 首帧画面采集完成
|
||||||
|
console.log('[MiniProgramCall] ✅ 首帧画面采集完成')
|
||||||
|
break
|
||||||
|
case 1008: // 编码器启动
|
||||||
|
console.log('[MiniProgramCall] ✅ 编码器启动')
|
||||||
|
break
|
||||||
|
case 1009: // 已发送首帧视频 - 这是推流真正成功的标志!
|
||||||
|
console.log('[MiniProgramCall] ✅✅ 推流成功!已发送首帧视频')
|
||||||
|
isPushingSucceeded = true // 关键:标记推流成功
|
||||||
|
isStartingPush = false
|
||||||
|
call.statusText = '通话中'
|
||||||
|
break
|
||||||
|
|
||||||
|
// 警告/网络状态
|
||||||
|
case 1101: // 网络状况不佳:上行带宽不足
|
||||||
|
console.warn('[MiniProgramCall] ⚠️ 网络不佳,上行带宽不足')
|
||||||
|
call.statusText = '网络不佳...'
|
||||||
|
break
|
||||||
|
case 1102: // 网络断连,已启动自动重连(不是编码器失败!)
|
||||||
|
console.log('[MiniProgramCall] ℹ️ 网络断连,正在自动重连...')
|
||||||
|
call.statusText = '重连中...'
|
||||||
|
// 重要:1102 是自动重连,不需要手动干预!
|
||||||
|
// 微信会自动处理重连,我们只需要等待
|
||||||
|
break
|
||||||
|
case 1103: // 摄像头被占用
|
||||||
|
console.error('[MiniProgramCall] ❌ 摄像头被占用')
|
||||||
|
uni.showToast({ title: '摄像头被占用', icon: 'none' })
|
||||||
|
break
|
||||||
|
|
||||||
|
// 麦克风状态
|
||||||
|
case 2027: // 麦克风启动成功
|
||||||
console.log('[MiniProgramCall] ✅ 麦克风已打开')
|
console.log('[MiniProgramCall] ✅ 麦克风已打开')
|
||||||
break
|
break
|
||||||
|
|
||||||
// 警告状态
|
// RTMP 错误
|
||||||
case 1101: // 网络状况不佳:上行带宽不足
|
|
||||||
call.statusText = '网络不佳...'
|
|
||||||
break
|
|
||||||
case 1102: // 视频编码器启动失败
|
|
||||||
console.warn('[MiniProgramCall] ⚠️ 视频编码器启动失败')
|
|
||||||
break
|
|
||||||
case 1103: // 摄像头被占用
|
|
||||||
uni.showToast({ title: '摄像头被占用', icon: 'none' })
|
|
||||||
break
|
|
||||||
case 3001: // RTMP 服务器建立连接失败
|
case 3001: // RTMP 服务器建立连接失败
|
||||||
console.error('[MiniProgramCall] ❌ RTMP 服务器连接失败')
|
console.error('[MiniProgramCall] ❌ RTMP 服务器连接失败')
|
||||||
call.statusText = '服务器连接失败'
|
call.statusText = '服务器连接失败'
|
||||||
@@ -639,6 +873,8 @@ export function useMiniProgramCall() {
|
|||||||
case 3005: // RTMP 服务器连接异常断开
|
case 3005: // RTMP 服务器连接异常断开
|
||||||
console.error('[MiniProgramCall] ❌ RTMP 连接异常断开')
|
console.error('[MiniProgramCall] ❌ RTMP 连接异常断开')
|
||||||
call.statusText = '连接异常断开'
|
call.statusText = '连接异常断开'
|
||||||
|
// 连接异常断开时,重置推流状态,允许重试
|
||||||
|
isPushingSucceeded = false
|
||||||
break
|
break
|
||||||
|
|
||||||
// 错误状态
|
// 错误状态
|
||||||
@@ -659,6 +895,7 @@ export function useMiniProgramCall() {
|
|||||||
case -1307: // 推流连接断开
|
case -1307: // 推流连接断开
|
||||||
console.error('[MiniProgramCall] ❌ 推流连接断开')
|
console.error('[MiniProgramCall] ❌ 推流连接断开')
|
||||||
call.statusText = '连接已断开'
|
call.statusText = '连接已断开'
|
||||||
|
isPushingSucceeded = false
|
||||||
break
|
break
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
"urlCheck" : false,
|
"urlCheck" : false,
|
||||||
"preloadBackgroundData" : false
|
"preloadBackgroundData" : false
|
||||||
},
|
},
|
||||||
"libVersion" : "2.7.0",
|
"libVersion" : "2.10.0",
|
||||||
"usingComponents" : true,
|
"usingComponents" : true,
|
||||||
"darkmode" : true,
|
"darkmode" : true,
|
||||||
"themeLocation" : "theme.json",
|
"themeLocation" : "theme.json",
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
"desc" : "用于获取您的位置信息"
|
"desc" : "用于获取您的位置信息"
|
||||||
},
|
},
|
||||||
"scope.record" : {
|
"scope.record" : {
|
||||||
"desc" : "用于语音消息录制"
|
"desc" : "用于语音消息录制和视频通话"
|
||||||
},
|
},
|
||||||
"scope.camera" : {
|
"scope.camera" : {
|
||||||
"desc" : "用于拍摄照片和视频通话"
|
"desc" : "用于拍摄照片和视频通话"
|
||||||
@@ -75,7 +75,8 @@
|
|||||||
"desc" : "用于保存图片到相册"
|
"desc" : "用于保存图片到相册"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"requiredPrivateInfos" : [ "chooseImage", "chooseVideo", "chooseLocation", "getLocation" ]
|
"requiredPrivateInfos" : [ "chooseLocation", "getLocation" ],
|
||||||
|
"requiredBackgroundModes" : [ "audio", "voip" ]
|
||||||
},
|
},
|
||||||
"mp-alipay" : {
|
"mp-alipay" : {
|
||||||
"usingComponents" : true
|
"usingComponents" : true
|
||||||
|
|||||||
Reference in New Issue
Block a user