Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Firefox throws "MSID is already used" error when using _acceptJvbIncomingCall #2631

Open
quannal opened this issue Feb 6, 2025 · 12 comments

Comments

@quannal
Copy link

quannal commented Feb 6, 2025

Description

When using Unified Plan in Firefox, WebRTC throws the following error: MSID is already used.
The issue occurs when calling the function _acceptJvbIncomingCall. This does not happen in Chrome. A possible workaround is applying toPlanB().

Possible Cause

Firefox may require a different msid structure for Unified Plan.

This is the image of the error:
Image

This is what I tried to fix to make it work:
Image

@quannal quannal changed the title Firefox throws "MSID is already used" error with Unified Plan Firefox throws "MSID is already used" error when using _acceptJvbIncomingCall Feb 6, 2025
@saghul
Copy link
Member

saghul commented Feb 6, 2025

What version of lib-jitsi-meet are you using?

@quannal
Copy link
Author

quannal commented Feb 6, 2025

@saghul I am using version 1878, but I have also tested on the latest master branch, and the issue is the same.

@saghul
Copy link
Member

saghul commented Feb 6, 2025

Ping @jallamsetty1

@quannal
Copy link
Author

quannal commented Feb 6, 2025

Additionally, downgrading to version 1877 resolves the issue.

@quannal
Copy link
Author

quannal commented Feb 10, 2025

Hi @jallamsetty1, could you to review this issue?

@jallamsetty1
Copy link
Member

Hi, can you pls provide me more details? Can you please share STR for this? Also does this happen on meet.jit.si? Are you able to share the full browser console log from the FIrefox client?

@quannal
Copy link
Author

quannal commented Feb 12, 2025

Hi @jallamsetty1, here is some the issue details:

STR: Attached video.

This is a custom feature in my app, so I’m not sure if meet.jit.si has this feature. Additional information, downgrading to version 1877 resolves the issue.

Error object:

{
  "reason": "bad-request",
  "msg": "MSID is already used: 7b5f54f5-video-0-2 cfbeb70c-c8c7-4342-832f-b660bf739f1d-2.",
  "session": "JingleSessionPC[session=JVB,initiator=false,sid=73h98e7t6ipb2]"
}

Error full stack:

2025-02-12T03:57:26.965Z [JitsiConference.js] <505/Hm.prototype._acceptJvbIncomingCall/<>:  Failed to accept incoming Jingle session 
Object { code: undefined, reason: "bad-request", msg: "MSID is already used: 7b5f54f5-video-0-2 cfbeb70c-c8c7-4342-832f-b660bf739f1d-2.", session: "JingleSessionPC[session=JVB,initiator=false,sid=73h98e7t6ipb2]" }
Logger.js:155:26
    r Logger.js:155
    _acceptJvbIncomingCall JitsiConference.js:2284
    acceptOffer JingleSessionPC.js:1016
    _sendSessionAccept JingleSessionPC.js:910
    newJingleErrorHandler JingleSessionPC.js:1659
    e strophe.umd.js:3400
    run strophe.umd.js:2507
    handlers strophe.umd.js:3843
    _dataRecv strophe.umd.js:3840
    forEachChild strophe.umd.js:1502
    _dataRecv strophe.umd.js:3838
    _onMessage strophe.umd.js:6187
    onmessage strophe.umd.js:5920
    (Async: EventHandlerNonNull)
    _replaceMessageHandler strophe.umd.js:5920
    _onInitialMessage strophe.umd.js:5901
    onmessage strophe.umd.js:5788
    (Async: EventHandlerNonNull)
    _connect strophe.umd.js:5788
    connect strophe.umd.js:3040
    _interceptConnectArgs strophe.stream-management.js:224
    connect XmppConnection.js:274
    _connect xmpp.js:602
    connect xmpp.js:706
    connect JitsiConnection.js:77
    _connectionConnect JvbLogic.ts:81
    connect JvbLogic.ts:404
    _startTestJVB JvbTestComponent.tsx:234
    (Async: promise callback)
    _startTestJVB JvbTestComponent.tsx:232
    componentDidMount JvbTestComponent.tsx:262
    React 12
    handleGoToNextCheck CheckPageDialog.tsx:104
    Lodash 3
    handler usePlatformLeafEventHandler.js:61
    React 21
issue-msid.mp4

@jallamsetty1
Copy link
Member

Are you using the latest stable on your deployment?

@quannal
Copy link
Author

quannal commented Feb 13, 2025

Hi @jallamsetty1, yep I am using latest master branch

@saghul
Copy link
Member

saghul commented Feb 13, 2025

Can you provide a code snippet of how you are using the library?

@quannal
Copy link
Author

quannal commented Feb 13, 2025

Hi @saghul,

This is the Connection class:

constructor(conferene?: string, password?: string, onToggleGoToNext?: (isOpen: boolean) => void) {

        this.conference = conferene;
        this.password = password;
        this.connectFailed = false;
        this.onToggleGoToNext = onToggleGoToNext;

        this._onConnectionSuccess = this._onConnectionSuccess.bind(this);
        this._onConnectionFailed = this._onConnectionFailed.bind(this);
        this._connectionDisconnect = this._connectionDisconnect.bind(this);
        this._onConferenceJoined = this._onConferenceJoined.bind(this);
        this._onUserLeft = this._onUserLeft.bind(this);
        this._onConferenceFailed = this._onConferenceFailed.bind(this);
        this._getConnectionOptions = this._getConnectionOptions.bind(this);
        this.connection = new JitsiMeetJS.JitsiConnection(null, null, this._getConnectionOptions());
        this._addConnctionListeners();
        this.rtcId = new Set();
}

_getConnectionOptions() {
    return {
        hosts: {
            domain: 'meet.jitsi',
            muc: 'muc.meet.jitsi'
        },
        websocket: `wss://${SERVER_PROXY_TARGET}/xmpp-websocket`,
        serviceUrl: `wss://${SERVER_PROXY_TARGET}/xmpp-websocket?room=${this.conference}`,
        disableRtx: true,
        disableAudioLevels: true
    };
}

/**
 * Connect.
 */
_connectionConnect() {
    this.connection.connect(); // <- the error here
}

/**
 * This function is called when we disconnect.
 */
_connectionDisconnect() {
    this.connection.removeEventListener(
        JitsiMeetJS.events.connection.CONNECTION_ESTABLISHED,
        this._onConnectionSuccess);
    this.connection.removeEventListener(
        JitsiMeetJS.events.connection.CONNECTION_FAILED,
        this._onConnectionFailed);
    this.connection.removeEventListener(
        JitsiMeetJS.events.connection.CONNECTION_DISCONNECTED,
        this._connectionDisconnect);
}

This is UserConnection class that extends Connection:

export class UserConnection extends Connection {

    /**
     * @class
     */
    constructor(...args: ConstructorParameters<typeof Connection>) {
        super(...args);
        this._createLocalTrack = this._createLocalTrack.bind(this);
        this._onLocalTracks = this._onLocalTracks.bind(this);
        this.replaceTrack = this.replaceTrack.bind(this);
        this.connect = this.connect.bind(this);
        this.disconnect = this.disconnect.bind(this);
    }

    /**
     * Camera and micro open is require.
     * Need create localtrack and add it to room. This action will let JVB create media channel to allocate.
     */
    _createLocalTrack(onError: Function) {
        JitsiMeetJS.init({ disableAudioLevels: false });

        return JitsiMeetJS.createLocalTracks({ devices: [ 'audio', 'video' ] })
            .then((tracks: Track[]) => {
                tracks.forEach(track => track.type === 'audio' && (track.track.enabled = false));
                this._onLocalTracks(tracks);
            })
            .catch((error: Error) => {
                onError();
                throw error;
            });
    }

    /*
     * Doc
     *
     * @param {any[]} tracks tracks
     */
    _onLocalTracks(tracks: Track[]) {
        this.localTracks = tracks;
        for (let i = 0; i < this.localTracks.length; i++) {
            // prevent leak camera/audio
            // In some case, issue blank video will appear by this.
            // this.localTracks[i].track.enabled = false;
            this.localTracks[i].addEventListener(
                JitsiMeetJS.events.track.TRACK_AUDIO_LEVEL_CHANGED,
                (audioLevel: number) => console.log(`Audio Level local: ${audioLevel}`));
            this.localTracks[i].addEventListener(
                JitsiMeetJS.events.track.TRACK_MUTE_CHANGED,
                () => console.log('local track muted'));
            this.localTracks[i].addEventListener(
                JitsiMeetJS.events.track.LOCAL_TRACK_STOPPED,
                () => console.log('local track stoped'));
            this.localTracks[i].addEventListener(
                JitsiMeetJS.events.track.TRACK_AUDIO_OUTPUT_CHANGED,
                (deviceId: string) =>
                    console.log(
                        `track audio output device was changed to ${deviceId}`));
            if (this.isJoined && !this.rtcId?.has(this.localTracks[i].rtcId)) {
                this.rtcId?.add(this.localTracks[i].rtcId);
                this.room.addTrack(this.localTracks[i]);
            }
        }
    }

    /**
     * Replace track
     * There are some cases when replaceTrack does not sync with UI, so we need to manually handle UI for that.
     */
    replaceTrack(stream: Record<string, any>) {
        return new Promise(resolve => {
            const replaceTrackPromises: any[] = [];

            stream.getTracks().forEach(
                (track: Track) => {
                    const oldLocalTrack = this.localTracks.filter(tr => tr.type === track.kind)[0];

                    if (oldLocalTrack) {
                        const newLocalTrack = Object.create(oldLocalTrack);

                        oldLocalTrack.track.stop(); // stop camera/audio track

                        Object.assign(newLocalTrack, oldLocalTrack);
                        newLocalTrack.deviceId = track.id;
                        newLocalTrack.facingMode = false;
                        newLocalTrack.mediaType = track.kind;
                        newLocalTrack.rtcId = oldLocalTrack.rtc;
                        newLocalTrack.stream = stream;
                        newLocalTrack.track = track;
                        newLocalTrack.type = oldLocalTrack.type;

                        replaceTrackPromises.push(
                            this.room.replaceTrack(oldLocalTrack, newLocalTrack).then(() => newLocalTrack)
                            .catch((error: any) => {
                                logger.error(error);
                            })
                        );
                    }

                }
            );
            Promise.all(replaceTrackPromises).then(resolve);
        });
    }

    spawnConnection(replaceTrack: Function) {
        const anonymousConnection = new AnonymousConnection(this.conference, this.password);

        anonymousConnection.doReplaceTrack = replaceTrack;
        this.dependConnections.push(anonymousConnection);
    }

    /**
     * Establish connection.
     */
    connect() {
        this._connectionConnect();
    }

    disconnect() {
        this.dependConnections.forEach(conn => conn.unload());
        this.unload();
    }
}

This is the code call the JVB test:

_startTestJVB(loadedMedia?: boolean) {
    const alterLoadedMedia = loadedMedia || this.loadedMedia;

    if (!alterLoadedMedia) {
        this.isErrorLoadSource = true;

        return this._onErrorTest(JVB_ERROR_TYPE.SOURCE_ERROR);
    }
    if (
        this.userConnection
        && !this.createdTrack
        && !this.isErrorLoadSource
    ) {
        this.createdTrack = true;

        // After 10s, if reconnect not successfully then stop reconnect.
        const timeoutConnect = setTimeout(() => {
            if (
                this.stateMachine.state.currentState
                !== STATE_MACHINE.SUCCESS
            ) {
                this._onErrorTest(JVB_ERROR_TYPE.LOW_NETWORK);
            }
        }, JVB_CONNECTION_TIMEOUT);

        this.timeoutList.push(timeoutConnect);
        this.userConnection
            ._createLocalTrack(this.onGetErrorPrecontition)
            .then(() => {
                this.stateMachine.transition(STATE_MACHINE.RUNNING);
                this.userConnection?.connect();
                this.userConnection?.spawnConnection(this.handleSendVideo);
            })
            .catch(() => {
                this._onErrorTest(JVB_ERROR_TYPE.JVB);
            });
    }
}

@saghul
Copy link
Member

saghul commented Feb 17, 2025

disableRtx: true,

Why are you disabling rtx?

_createLocalTrack(onError: Function) {
JitsiMeetJS.init({ disableAudioLevels: false });

You only want to call JitsiMeetJS.init once, at startup.

JitsiMeetJS.events.track.TRACK_AUDIO_LEVEL_CHANGED

Why are you adding a listener for audio levels, when you disabled them?

this.userConnection?.connect();

Any chance JitsiConnection.connect ends up being called more than once?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants