You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
477 lines
14 KiB
477 lines
14 KiB
|
|
import {
|
|
EventEmitter
|
|
} from 'events'
|
|
import jssip from 'jssip'
|
|
|
|
let $baseWebSocketUrl = null
|
|
let $subscriber = null
|
|
let $socket = null
|
|
let $userAgent = null
|
|
let $isVideoScreen = false
|
|
let $outgoingRtcSession = null
|
|
let $incomingRtcSession = null
|
|
let $localMediaStream = null
|
|
let $remoteMediaStream = null
|
|
let $videoTransceiver = null
|
|
let $audioTransceiver = null
|
|
|
|
const TERMINATION_OPTIONS = {
|
|
status_code: 603,
|
|
reason_phrase: 'Decline'
|
|
}
|
|
|
|
const VIDEO_MAX_FRAME_RATE = 30
|
|
const VIDEO_IDEAL_WIDTH = 1920
|
|
const VIDEO_IDEAL_HEIGHT = 1080
|
|
|
|
function callGetVideoConstraints () {
|
|
const supported = navigator?.mediaDevices?.getSupportedConstraints()
|
|
const constraints = {}
|
|
if (supported.frameRate) {
|
|
constraints.frameRate = {
|
|
max: VIDEO_MAX_FRAME_RATE
|
|
}
|
|
}
|
|
constraints.width = {
|
|
ideal: VIDEO_IDEAL_WIDTH
|
|
}
|
|
constraints.height = {
|
|
ideal: VIDEO_IDEAL_HEIGHT
|
|
}
|
|
return constraints
|
|
}
|
|
|
|
function callGetCameraConstraints () {
|
|
const supported = navigator?.mediaDevices?.getSupportedConstraints()
|
|
const constraints = callGetVideoConstraints()
|
|
if (supported.facingMode) {
|
|
constraints.facingMode = 'user'
|
|
}
|
|
return constraints
|
|
}
|
|
|
|
function callGetScreenConstraints () {
|
|
return callGetVideoConstraints()
|
|
}
|
|
|
|
export const callEvent = new EventEmitter()
|
|
|
|
function callTrackMuteHandler () {
|
|
if ($audioTransceiver) {
|
|
$remoteMediaStream = new MediaStream([
|
|
$audioTransceiver.receiver.track
|
|
])
|
|
callEvent.emit('remoteStream', $remoteMediaStream)
|
|
}
|
|
}
|
|
|
|
function callTrackUnMuteHandler () {
|
|
if ($audioTransceiver && $videoTransceiver) {
|
|
$remoteMediaStream = new MediaStream([
|
|
$audioTransceiver.receiver.track,
|
|
$videoTransceiver.receiver.track
|
|
])
|
|
callEvent.emit('remoteStream', $remoteMediaStream)
|
|
}
|
|
}
|
|
|
|
function handleRemoteMediaStream ({ transceiver }) {
|
|
if (!$audioTransceiver && transceiver.receiver.track.kind === 'audio') {
|
|
$audioTransceiver = transceiver
|
|
} else if (!$videoTransceiver && transceiver.receiver.track.kind === 'video') {
|
|
$videoTransceiver = transceiver
|
|
$videoTransceiver.receiver.track.onmute = callTrackMuteHandler
|
|
$videoTransceiver.receiver.track.onunmute = callTrackUnMuteHandler
|
|
}
|
|
const tracks = []
|
|
if ($audioTransceiver) {
|
|
tracks.push($audioTransceiver.receiver.track)
|
|
}
|
|
if ($videoTransceiver) {
|
|
tracks.push($videoTransceiver.receiver.track)
|
|
}
|
|
$remoteMediaStream = new MediaStream(tracks)
|
|
callEvent.emit('remoteStream', $remoteMediaStream)
|
|
}
|
|
|
|
function getSubscriberUri () {
|
|
return 'sip:' + $subscriber.username + '@' + $subscriber.domain
|
|
}
|
|
|
|
function callCreateSocket () {
|
|
return new jssip.WebSocketInterface($baseWebSocketUrl + '/' + $subscriber.username)
|
|
}
|
|
|
|
export function callConfigure ({ baseWebSocketUrl }) {
|
|
$baseWebSocketUrl = baseWebSocketUrl
|
|
}
|
|
|
|
export async function callInitialize ({ subscriber, instanceId }) {
|
|
$subscriber = subscriber
|
|
callRegister({ instanceId })
|
|
}
|
|
|
|
export function callRegister ({ instanceId }) {
|
|
if (!$socket) {
|
|
$socket = callCreateSocket()
|
|
const config = {
|
|
sockets: [$socket],
|
|
uri: getSubscriberUri(),
|
|
password: $subscriber.password,
|
|
instance_id: instanceId
|
|
}
|
|
$userAgent = new jssip.UA(config)
|
|
const delegateEvent = (eventName) => {
|
|
$userAgent.on(eventName, (event) => callEvent.emit(eventName, event))
|
|
}
|
|
delegateEvent('connected')
|
|
delegateEvent('disconnected')
|
|
delegateEvent('newRTCSession')
|
|
delegateEvent('newMessage')
|
|
delegateEvent('registered')
|
|
delegateEvent('unregistered')
|
|
delegateEvent('registrationFailed')
|
|
$userAgent.on('newRTCSession', (event) => {
|
|
if (event.originator === 'remote') {
|
|
if ($incomingRtcSession || $outgoingRtcSession) {
|
|
event.session.terminate({
|
|
status_code: 486,
|
|
reason_phrase: 'Busy'
|
|
})
|
|
} else {
|
|
$incomingRtcSession = event.session
|
|
$incomingRtcSession.on('peerconnection', () => {
|
|
$incomingRtcSession.connection.ontrack = handleRemoteMediaStream
|
|
})
|
|
$incomingRtcSession.on('failed', (failedEvent) => {
|
|
callEvent.emit('incomingFailed', failedEvent)
|
|
})
|
|
$incomingRtcSession.on('ended', (failedEvent) => {
|
|
callEvent.emit('incomingEnded', failedEvent)
|
|
$incomingRtcSession = null
|
|
})
|
|
callEvent.emit('incoming', $incomingRtcSession)
|
|
$incomingRtcSession.on('hold', (holdEvent) => {
|
|
callEvent.emit('incomingHold', holdEvent)
|
|
})
|
|
$incomingRtcSession.on('unhold', (unholdEvent) => {
|
|
callEvent.emit('incomingUnHold', unholdEvent)
|
|
})
|
|
$incomingRtcSession.on('refer', (event) => {
|
|
callEvent.emit('incomingRefer', event)
|
|
})
|
|
}
|
|
}
|
|
})
|
|
$userAgent.start()
|
|
}
|
|
}
|
|
|
|
export function callUnregister () {
|
|
if ($userAgent) {
|
|
$userAgent.unregister({
|
|
all: true
|
|
})
|
|
}
|
|
}
|
|
|
|
export async function callStart ({ number }) {
|
|
try {
|
|
$localMediaStream = await callCreateLocalAudioStream()
|
|
callEvent.emit('localStream', $localMediaStream)
|
|
$outgoingRtcSession = $userAgent.call(number, {
|
|
eventHandlers: {
|
|
progress (event) {
|
|
if (event.response.status_code === 183) {
|
|
callEvent.emit('outgoingProgress', event)
|
|
} else {
|
|
callEvent.emit('outgoingRinging', event)
|
|
}
|
|
},
|
|
failed (event) {
|
|
callEvent.emit('outgoingFailed', event)
|
|
},
|
|
confirmed (event) {
|
|
callEvent.emit('outgoingConfirmed', event)
|
|
},
|
|
ended (event) {
|
|
callEvent.emit('outgoingEnded', event)
|
|
$outgoingRtcSession = null
|
|
},
|
|
hold (event) {
|
|
if (event.originator === 'local') {
|
|
callEvent.emit('outgoingHold', event)
|
|
} else {
|
|
callEvent.emit('outgoingHolded', event)
|
|
}
|
|
},
|
|
unhold (event) {
|
|
if (event.originator === 'local') {
|
|
callEvent.emit('outgoingUnHold', event)
|
|
} else {
|
|
callEvent.emit('outgoingUnHolded', event)
|
|
}
|
|
},
|
|
refer (event) {
|
|
callEvent.emit('outgoingRefer', event)
|
|
}
|
|
|
|
},
|
|
mediaStream: $localMediaStream
|
|
})
|
|
$outgoingRtcSession.connection.ontrack = handleRemoteMediaStream
|
|
return true
|
|
} catch (e) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
export async function callAccept () {
|
|
$localMediaStream = await callCreateLocalAudioStream()
|
|
callEvent.emit('localStream', $localMediaStream)
|
|
if ($incomingRtcSession) {
|
|
$incomingRtcSession.answer({
|
|
mediaStream: $localMediaStream
|
|
})
|
|
}
|
|
}
|
|
|
|
export async function callCreateLocalAudioStream () {
|
|
return await navigator.mediaDevices.getUserMedia({
|
|
video: false,
|
|
audio: true
|
|
})
|
|
}
|
|
|
|
export function callGetRemoteMediaStream () {
|
|
return $remoteMediaStream
|
|
}
|
|
|
|
export function callGetRemoteMediaStreamId () {
|
|
return $remoteMediaStream?.id
|
|
}
|
|
|
|
export function callGetLocalMediaStream () {
|
|
return $localMediaStream
|
|
}
|
|
|
|
export function callGetLocalMediaStreamId () {
|
|
return $localMediaStream?.id
|
|
}
|
|
|
|
export function callGetRtcSession () {
|
|
if ($outgoingRtcSession) {
|
|
return $outgoingRtcSession
|
|
} else if ($incomingRtcSession) {
|
|
return $incomingRtcSession
|
|
}
|
|
}
|
|
|
|
export function callGetRtcConnection () {
|
|
return callGetRtcSession().connection
|
|
}
|
|
|
|
async function callStopVideo () {
|
|
if ($videoTransceiver && $localMediaStream) {
|
|
$localMediaStream.removeTrack($videoTransceiver.sender.track)
|
|
$videoTransceiver.sender.track.stop()
|
|
$videoTransceiver.direction = 'recvonly'
|
|
await $videoTransceiver.sender.replaceTrack(null)
|
|
await callRenegotiate()
|
|
}
|
|
}
|
|
|
|
export async function callSendVideo (stream, audioMuted) {
|
|
const videoTrack = stream.getVideoTracks()[0]
|
|
if ($videoTransceiver?.sender?.track) {
|
|
$localMediaStream.removeTrack($videoTransceiver.sender.track)
|
|
$videoTransceiver.sender.track.stop()
|
|
}
|
|
$localMediaStream.addTrack(videoTrack)
|
|
callEvent.emit('localStream', $localMediaStream)
|
|
if (!$videoTransceiver) {
|
|
$videoTransceiver = callGetRtcConnection().addTransceiver(videoTrack, { direction: 'sendrecv' })
|
|
$videoTransceiver.receiver.track.onmute = callTrackMuteHandler
|
|
$videoTransceiver.receiver.track.onunmute = callTrackUnMuteHandler
|
|
} else {
|
|
$videoTransceiver.direction = 'sendrecv'
|
|
await $videoTransceiver.sender.replaceTrack(videoTrack)
|
|
}
|
|
await callRenegotiate()
|
|
}
|
|
|
|
export async function callAddCamera () {
|
|
$isVideoScreen = false
|
|
await callSendVideo(await navigator.mediaDevices.getUserMedia({
|
|
video: callGetCameraConstraints(),
|
|
audio: false
|
|
}))
|
|
}
|
|
|
|
export async function callAddScreen () {
|
|
$isVideoScreen = true
|
|
await callSendVideo(await navigator.mediaDevices.getDisplayMedia({
|
|
video: callGetScreenConstraints(),
|
|
audio: false
|
|
}))
|
|
}
|
|
|
|
export async function callRemoveVideo () {
|
|
$isVideoScreen = false
|
|
await callStopVideo()
|
|
}
|
|
|
|
export async function callRenegotiate () {
|
|
callGetRtcSession().renegotiate({
|
|
useUpdate: false
|
|
}, () => {
|
|
callEvent.emit('renegotiationSucceeded')
|
|
})
|
|
}
|
|
|
|
export function callHasRemoteVideo () {
|
|
return $videoTransceiver?.receiver?.track?.enabled
|
|
}
|
|
|
|
export function callHasLocalVideo () {
|
|
return $videoTransceiver?.sender?.track?.enabled
|
|
}
|
|
|
|
export function callHasLocalCamera () {
|
|
return callHasLocalVideo() && !$isVideoScreen
|
|
}
|
|
|
|
export function callHasLocalScreen () {
|
|
return callHasLocalVideo() && $isVideoScreen
|
|
}
|
|
|
|
export function callToggleMicrophone () {
|
|
if ($audioTransceiver?.sender?.track) {
|
|
$audioTransceiver.sender.track.enabled = !$audioTransceiver.sender.track.enabled
|
|
}
|
|
}
|
|
|
|
export function callMute () {
|
|
return callGetRtcSession()?.mute()
|
|
}
|
|
|
|
export function callUnMute () {
|
|
return callGetRtcSession()?.unmute()
|
|
}
|
|
|
|
export function callIsMuted () {
|
|
if ($audioTransceiver?.sender?.track) {
|
|
return !$audioTransceiver.sender.track.enabled
|
|
}
|
|
return false
|
|
}
|
|
|
|
export function callSendDTMF (tone, transport = 'RFC2833') {
|
|
const rtcSession = callGetRtcSession()
|
|
if (rtcSession) {
|
|
rtcSession.sendDTMF(tone, {
|
|
transportType: transport
|
|
})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enables or disables the remote audio depending on the current state.
|
|
*/
|
|
export function callToggleRemoteAudio () {
|
|
if ($audioTransceiver?.receiver?.track) {
|
|
$audioTransceiver.receiver.track.enabled = !$audioTransceiver.receiver.track.enabled
|
|
}
|
|
}
|
|
|
|
export function callMuteRemote () {
|
|
if ($audioTransceiver?.receiver?.track) {
|
|
$audioTransceiver.receiver.track.enabled = false
|
|
}
|
|
}
|
|
|
|
export function callUnMuteRemote () {
|
|
if ($audioTransceiver?.receiver?.track) {
|
|
$audioTransceiver.receiver.track.enabled = true
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks whether remote audio is muted or not.
|
|
* @returns {boolean}
|
|
*/
|
|
export function callIsRemoteMuted () {
|
|
return !$audioTransceiver?.receiver?.track?.enabled
|
|
}
|
|
|
|
/**
|
|
* Terminates the call if not ended and cleans up all related resources.
|
|
*/
|
|
export function callEnd () {
|
|
const rtcSession = callGetRtcSession()
|
|
if (rtcSession && !rtcSession.isEnded()) {
|
|
rtcSession.terminate(TERMINATION_OPTIONS)
|
|
}
|
|
try {
|
|
if ($localMediaStream) {
|
|
$localMediaStream.getTracks().forEach(track => track.stop())
|
|
}
|
|
} finally {
|
|
$localMediaStream = null
|
|
}
|
|
try {
|
|
if ($remoteMediaStream) {
|
|
$remoteMediaStream.getTracks().forEach(track => track.stop())
|
|
}
|
|
} finally {
|
|
$remoteMediaStream = null
|
|
}
|
|
$outgoingRtcSession = null
|
|
$incomingRtcSession = null
|
|
$audioTransceiver = null
|
|
$videoTransceiver = null
|
|
}
|
|
/**
|
|
* Hold on.
|
|
*/
|
|
export function callToggleHold () {
|
|
const rtcSession = callGetRtcSession()
|
|
if (rtcSession) {
|
|
if (rtcSession.isOnHold().local) {
|
|
rtcSession.unhold()
|
|
} else {
|
|
rtcSession.hold()
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* Blind Transfer.
|
|
*/
|
|
export function callBlindTransfer (numberToTransfer) {
|
|
return new Promise((resolve, reject) => {
|
|
const rtcSession = callGetRtcSession()
|
|
const eventHandlers = {
|
|
requestFailed: function (e) {
|
|
console.log('Transfer failed')
|
|
reject(new Error('Transfer failed'))
|
|
},
|
|
requestSucceeded: function (e) {
|
|
console.log('Success', e)
|
|
resolve(true)
|
|
}
|
|
}
|
|
try {
|
|
const uriString = `Referred-By: <sip:${rtcSession.local_identity.uri.user}@${rtcSession.local_identity.uri.host}>`
|
|
rtcSession.refer(numberToTransfer, {
|
|
eventHandlers,
|
|
extraHeaders: [
|
|
uriString
|
|
]
|
|
})
|
|
} catch (err) {
|
|
reject(err)
|
|
}
|
|
})
|
|
}
|