onsip / SIP.js

A simple, intuitive, and powerful JavaScript signaling library
https://sipjs.com
MIT License
1.88k stars 700 forks source link

The second call hold don't get incoming audio on unhold #1059

Open dsalasjanda opened 11 months ago

dsalasjanda commented 11 months ago

Describe the bug We have a small Angular application to receive and make SIP calls through our app. With a single call, the 'hold' and 'unhold' functions work fine. If we receive a second call, the 'hold' and 'unhold' functions also work well. However, when we receive a second call and put it on hold (having the first one on hold previously) and try to put the first call on un-hold, the first call sends audio to the caller but this does not receive audio. We've been reviewing traces from Asterisk and 'chrome://webrtc-internals/' and it seems that the WebRTC packets are arriving, but we are unable to play them.

We tested this service whit a similar app of jssip and it look work find so we supouse that it is not a network or Asterix problem but a sip.js bug

To Reproduce (if possible) This is our service code :

import { Injectable } from "@angular/core"; import { Session } from "sip.js"; import { SessionManager, SessionManagerDelegate, SessionManagerOptions, } from "sip.js/lib/platform/web"; import { CallData } from "./CallData"; import { BehaviorSubject, Observable, Subject } from "rxjs"; import { SipCallEventEnum, SipCallNotification } from "./SipCallNotification";

//https://github.com/onsip/SIP.js/blob/main/docs/session-manager/sip.js.sessionmanager.md @Injectable({ providedIn: "root", }) export class SipSessionService { private sessionManager: SessionManager; private notifications = new Subject(); private connectionStatus = new BehaviorSubject(false); private activeUser: any; public callList: SipCallNotification[]; public domain: string;

constructor() {}

connect( server: string, username: string, password: string, displayName: string, domain: string ) { / const server = 'wss://10.22.45.116:8089/asterisk/ws'; const username = '3002'; const password = '3002'; const displayName = '3002'; / this.activeUser = username; this.domain = domain // Session manager delegate const sessionManagerDelegate: SessionManagerDelegate = { onCallReceived: (session: any) => { let callData: CallData = {}; console.log( [${displayName}] Call onCallReceived from + session._id ); callData.id = session._id; callData.session = session; callData.date = new Date(); this.loadCallData(session,callData,username); if (session.outgoingRequestMessage) { console.log([${displayName}] Call dial); this.notifications.next( new SipCallNotification(SipCallEventEnum.DIAL, callData) ); } else { this.notifications.next( new SipCallNotification(SipCallEventEnum.DIAL, callData) ); }

    this.notifications.next(
      new SipCallNotification(SipCallEventEnum.INCOMING_CALL, callData)
    );
  },
  onCallCreated: (session: any): void => {
    console.log(this.sessionManager.managedSessions);
    console.log(`[${displayName}] Call created`);

    if (session.outgoingRequestMessage) {
      console.log(`[${displayName}] Call dial`);
      let callData: CallData = {};
      callData.id = session._id;
      callData.session = session;
      callData.date = new Date();
      this.loadCallData(session,callData,username);
      this.notifications.next(
        new SipCallNotification(SipCallEventEnum.DIAL, callData)
      );
    }
  },
  onCallAnswered: (session: any): void => {
    let callData: CallData = {};
    callData.id = session._id;
    callData.session = session;
    this.loadCallData(session,callData,username);
    this.notifications.next(
      new SipCallNotification(SipCallEventEnum.ANSWERED, callData)
    );

    console.log(`[${displayName}] Call answered`);
  },
  onCallHangup: (session: any): void => {
    let callData: CallData = {};
    callData.id = session._id;
    callData.session = session;
    this.loadCallData(session,callData,username);
    console.log(`[${displayName}] Call hangup`);
    this.notifications.next(
      new SipCallNotification(SipCallEventEnum.HANGUP, callData)
    );
  },
  onCallHold: (session: any, held: boolean): void => {
    let callData: CallData = {};
    callData.id = session._id;
    callData.session = session;
    this.loadCallData(session,callData,username);
    if (held) {
      this.notifications.next(
        new SipCallNotification(SipCallEventEnum.HOLDED, callData)
      );
    } else {
      this.notifications.next(
        new SipCallNotification(SipCallEventEnum.UNHOLDED, callData)
      );
    }
    console.log(`[${displayName}] Call hold ${held}`);
  },
};
const audioElement = this.getAudio("remoteAudio");
const sessionManagerOptions: SessionManagerOptions = {
  delegate: sessionManagerDelegate,
  maxSimultaneousSessions: 30,
  media: {
    remote: {
      audio: audioElement,
    },
  },
  aor: `sip:${username}@${domain}`,
  userAgentOptions: {
    authorizationPassword: password,
    authorizationUsername: username,
    displayName,
  },
};

this.sessionManager = new SessionManager(server, sessionManagerOptions);
this.sessionManager
  .connect()
  .then(() => {
    console.log("CONECTED");
  })
  .catch((error: Error) => {
    console.error("failed to connect");
    console.error(error);
    this.connectionStatus.next(false);
    alert("Failed to connect.\n" + error);
  });

  console.log(this.sessionManager);
this.sessionManager.register({
  // An example of how to get access to a SIP response message for custom handling
  requestDelegate: {
    onReject: (response) => {
      console.info(`REGISTER rejected`);
      let message = `Registration  rejected.\n`;
      message += `Reason: ${response.message.reasonPhrase}\n`;
      this.connectionStatus.next(false);
      alert(message);
    },
    /**
     * Received a 2xx positive final response to this request.
     * @param response - Incoming response.
     */
    onAccept: (response) => {
      console.info(`REGISTER onAccept`);
      this.connectionStatus.next(true);
    },
    /**
     * Received a 1xx provisional response to this request. Excluding 100 responses.
     * @param response - Incoming response.
     */
    onProgress: (response) => {
      // console.info(`[${this.user.id}] REGISTER onProgress`);
    },
  },
});

}

private loadCallData(session : any, callData : CallData, username : string){ if (session.outgoingRequestMessage) { const CALLED = session.outgoingRequestMessage.toURI.normal?.user; callData.local = true; callData.id = session.id; callData.date = new Date(); callData.called = CALLED; callData.calling = username; } else { const CALLED = session.incomingInviteRequest.message?.from?.uri?.user; callData.local = false; callData.called = username; callData.calling = CALLED; } }

public getCallByExtensionAndActiveUser(extension: string): CallData{ for (const value of this.callList.values()) { if (value.data['called'] === extension && value.data['calling'] === this.activeUser || value.data['calling'] === extension && value.data['called'] === this.activeUser) { return value.data; } } }

makeCall(target: string): void { this.sessionManager .call(target, { inviteWithoutSdp: false, }) .catch((error: Error) => { // console.error([${this.user.id}] failed to place call); console.error(error); alert("Failed to place call.\n" + error); });

// Agrega más eventos y lógica según tus necesidades

}

onCall(extension: string): void { const target="sip:" + extension + "@" + this.domain; this.sessionManager .call(target, { inviteWithoutSdp: false, }) .catch((error: Error) => { // console.error([${this.user.id}] failed to place call); console.error(error); alert("Failed to place call.\n" + error); });

// Agrega más eventos y lógica según tus necesidades

}

hangup(session: Session) { this.sessionManager.hangup(session).catch((error: Error) => { // console.error([${this.user.id}] failed to hangup call); console.error(error); alert("Failed to hangup call.\n" + error); }); }

answer(session: Session) { this.sessionManager.answer(session); }

hold(session: Session) { this.sessionManager.hold(session); }

unhold(session: Session) { this.sessionManager.unhold(session); } getAudio(id: string): HTMLAudioElement { const el = document.getElementById(id); if (!(el instanceof HTMLAudioElement)) { throw new Error(Element "${id}" not found or not an audio element.); } return el; }

public getNotificationsCalls(): Observable { return this.notifications.asObservable(); } public getConnectionStatus(): Observable { return this.connectionStatus.asObservable(); }

setCallData(data : SipCallNotification[]){ this.callList = data; } }

Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Expected behavior A clear and concise description of what you expected to happen.

Observed behavior A clear and concise description of what actually happened

Environment Information

Additional context Add any other context about the problem here.

dsalasjanda commented 11 months ago

I find a Hack to solve the problem but It loock a litle diry: I add setupRemoteMedia after unhold.

  unhold(session: Session) {
    this.sessionManager.unhold(session);
    **(this.sessionManager as any).setupRemoteMedia(session)**

  }

As I Said I think that is only a hack and this bug sould be solved some other way.

bogdanteodoru commented 4 months ago

I find a Hack to solve the problem but It loock a litle diry: I add setupRemoteMedia after unhold.

  unhold(session: Session) {
    this.sessionManager.unhold(session);
    **(this.sessionManager as any).setupRemoteMedia(session)**

  }

As I Said I think that is only a hack and this bug sould be solved some other way.

Hey man, thanks for this! I've been struggling to find a solution and this saved my life. Even if it's not that pretty it does do the job!