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

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

Open
dsalasjanda opened this issue Nov 6, 2023 · 2 comments
Open

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

dsalasjanda opened this issue Nov 6, 2023 · 2 comments

Comments

@dsalasjanda
Copy link

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

  • Backend Server and version
  • Browser and version

Additional context
Add any other context about the problem here.

@dsalasjanda
Copy link
Author

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
Copy link

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!

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

2 participants