SIP.js
SIP.js copied to clipboard
The second call hold don't get incoming audio on unhold
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<SipCallNotification>();
private connectionStatus = new BehaviorSubject
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<SipCallNotification> {
return this.notifications.asObservable();
}
public getConnectionStatus(): Observable
setCallData(data : SipCallNotification[]){ this.callList = data; } }
Steps to reproduce the behavior:
- Go to '...'
- Click on '....'
- Scroll down to '....'
- 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.
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.
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!