Golang中iosrtc插件报错:PluginMediaStream ID "any id" 已存在
Golang中iosrtc插件报错:PluginMediaStream ID “any id” 已存在 我开发了一个基于Ionic Cordova和PeerJS的视频通话系统,在Android上运行正常。 现在,当我通过Cordova部署到iOS后,每当我进行第二次通话时,总是会出现错误:具有此ID“任意ID”的PluginMediaStream已存在,并且无法再加入通话,同时peerjs的事件也不会触发。
例如:
peer.on('stream') 完全不会触发。我使用这个插件来处理MediaStreams,但由于某些原因,在第一次视频通话后,它提示MediaStream已存在。
当收到来自peer的呼叫时,如何触发一些事件,例如onstream、onremove?
我尝试在结束视频通话后移除流、轨道和其他内容,但没有效果。
更多关于Golang中iosrtc插件报错:PluginMediaStream ID "any id" 已存在的实战教程也可以访问 https://www.itying.com/category-94-b0.html
更多关于Golang中iosrtc插件报错:PluginMediaStream ID "any id" 已存在的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
这是一个典型的 iOS 上 cordova-plugin-iosrtc 插件与 PeerJS 结合使用时出现的 MediaStream 生命周期管理问题。错误 PluginMediaStream ID "any id" 已存在 表明插件内部缓存中已存在相同 ID 的 MediaStream 对象,导致后续操作失败。
核心问题是:在 iOS 上,cordova-plugin-iosrtc 插件创建的 MediaStream 对象在 JavaScript 层销毁后,其底层原生对象可能未被正确释放,导致 ID 冲突。
以下是解决方案和示例代码:
1. 确保在每次通话结束时彻底销毁 MediaStream
在挂断通话时,必须显式地停止所有轨道并清除引用:
// 假设你有一个全局或结构体来管理当前通话
type VideoCall struct {
localStream *webrtc.MediaStream
remoteStream *webrtc.MediaStream
peer *peerjs.Peer
call *peerjs.MediaConnection
}
func (vc *VideoCall) hangUp() {
// 停止本地流的所有轨道
if vc.localStream != nil {
tracks := vc.localStream.GetTracks()
for _, track := range tracks {
track.Stop()
}
vc.localStream = nil
}
// 停止远程流的所有轨道
if vc.remoteStream != nil {
tracks := vc.remoteStream.GetTracks()
for _, track := range tracks {
track.Stop()
}
vc.remoteStream = nil
}
// 关闭 PeerJS 连接
if vc.call != nil {
vc.call.Close()
vc.call = nil
}
// 重要:强制触发垃圾回收提示
// 在 iOS 上,这有助于 cordova-plugin-iosrtc 清理资源
runtime.GC()
}
2. 使用 PeerJS 事件处理,确保流被正确添加和移除
以下是完整的 PeerJS 事件处理示例:
package main
import (
"github.com/pion/webrtc/v3"
peerjs "github.com/peers/peerjs-go"
)
type VideoCallManager struct {
peer *peerjs.Peer
currentCall *peerjs.MediaConnection
}
func NewVideoCallManager(peerID string) *VideoCallManager {
peer, err := peerjs.NewPeer(peerID, peerjs.Options{
Host: "your-peer-server",
Port: 9000,
Path: "/peerjs",
})
if err != nil {
panic(err)
}
manager := &VideoCallManager{
peer: peer,
}
// 设置事件监听
manager.setupEventListeners()
return manager
}
func (m *VideoCallManager) setupEventListeners() {
// 监听来电
m.peer.On("call", func(call *peerjs.MediaConnection) {
// 获取本地媒体流
localStream, err := m.getUserMedia()
if err != nil {
fmt.Println("获取媒体流失败:", err)
return
}
// 接听来电
call.Answer(localStream)
// 保存当前通话
m.currentCall = call
// 监听远程流
call.On("stream", func(remoteStream *webrtc.MediaStream) {
fmt.Println("收到远程视频流")
// 处理远程视频流
m.handleRemoteStream(remoteStream)
})
// 监听连接关闭
call.On("close", func() {
fmt.Println("通话已结束")
m.cleanupStreams()
m.currentCall = nil
})
// 监听错误
call.On("error", func(err error) {
fmt.Println("通话错误:", err)
m.cleanupStreams()
m.currentCall = nil
})
})
// 监听连接打开
m.peer.On("open", func(id string) {
fmt.Println("PeerJS 已连接,ID:", id)
})
// 监听错误
m.peer.On("error", func(err error) {
fmt.Println("PeerJS 错误:", err)
})
}
func (m *VideoCallManager) getUserMedia() (*webrtc.MediaStream, error) {
// 使用 cordova-plugin-iosrtc 的 API 获取媒体流
// 注意:在 iOS 上,每次通话都应该获取新的媒体流
constraints := webrtc.MediaConstraints{
Audio: true,
Video: webrtc.MediaConstraints{
Width: 640,
Height: 480,
},
}
stream, err := webrtc.GetUserMedia(constraints)
if err != nil {
return nil, err
}
return stream, nil
}
func (m *VideoCallManager) handleRemoteStream(stream *webrtc.MediaStream) {
// 将远程视频流绑定到 video 元素
// 这里假设你有一个函数来处理视频元素的绑定
bindVideoElement("remote-video", stream)
}
func (m *VideoCallManager) cleanupStreams() {
// 清理所有媒体流
if m.currentCall != nil {
// 获取通话中的所有流并停止
if localStream := m.currentCall.GetLocalStream(); localStream != nil {
tracks := localStream.GetTracks()
for _, track := range tracks {
track.Stop()
}
}
if remoteStream := m.currentCall.GetRemoteStream(); remoteStream != nil {
tracks := remoteStream.GetTracks()
for _, track := range tracks {
track.Stop()
}
}
}
// 强制垃圾回收提示
runtime.GC()
}
func (m *VideoCallManager) MakeCall(targetPeerID string) error {
// 获取本地媒体流
localStream, err := m.getUserMedia()
if err != nil {
return err
}
// 发起呼叫
call, err := m.peer.Call(targetPeerID, localStream)
if err != nil {
return err
}
m.currentCall = call
// 设置通话事件监听
call.On("stream", func(remoteStream *webrtc.MediaStream) {
fmt.Println("收到远程视频流")
m.handleRemoteStream(remoteStream)
})
call.On("close", func() {
fmt.Println("通话已结束")
m.cleanupStreams()
m.currentCall = nil
})
return nil
}
func (m *VideoCallManager) EndCall() {
if m.currentCall != nil {
m.currentCall.Close()
m.cleanupStreams()
m.currentCall = nil
}
}
3. iOS 特定处理
对于 cordova-plugin-iosrtc,你还需要在 iOS 端确保正确清理:
// 在挂断时调用 iOS 特定的清理
func (m *VideoCallManager) iosSpecificCleanup() {
// 通过 JavaScript 调用 cordova-plugin-iosrtc 的清理方法
jsCode := `
if (window.cordova && cordova.plugins && cordova.plugins.iosrtc) {
// 获取所有 PluginMediaStream 并释放
for (var streamId in cordova.plugins.iosrtc.refCount.mediaStreams) {
var stream = cordova.plugins.iosrtc.refCount.mediaStreams[streamId];
if (stream) {
stream.release();
}
}
}
`
// 执行 JavaScript 代码
// 这里假设你有一个执行 JS 的方法
executeJavaScript(jsCode)
}
4. 确保每次通话都使用新的 Peer 连接
如果问题仍然存在,考虑每次通话都创建新的 Peer 实例:
func NewCallSession(peerID string) *CallSession {
// 每次通话创建新的 Peer 实例
peer, err := peerjs.NewPeer(peerID, peerjs.Options{
Host: "your-peer-server",
Port: 9000,
Path: "/peerjs",
})
if err != nil {
return nil
}
return &CallSession{
peer: peer,
}
}
func (s *CallSession) Destroy() {
// 结束通话时销毁整个会话
s.EndCall()
s.peer.Destroy()
}
这些代码示例展示了如何正确处理 MediaStream 的生命周期,确保在 iOS 上使用 cordova-plugin-iosrtc 时不会出现 ID 冲突问题。关键是在每次通话结束后彻底停止所有媒体轨道并清理引用。

