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

1 回复

更多关于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 冲突问题。关键是在每次通话结束后彻底停止所有媒体轨道并清理引用。

回到顶部