mirror of
https://github.com/koush/scrypted.git
synced 2026-02-13 18:32:56 +00:00
* Added some to parse optional video streams from the SIP call * Added some to parse optional video streams from the SIP call
126 lines
3.0 KiB
TypeScript
126 lines
3.0 KiB
TypeScript
// by @dgrief from @homebridge/camera-utils
|
|
import dgram from 'dgram'
|
|
const stun = require('stun')
|
|
|
|
const stunMagicCookie = 0x2112a442 // https://tools.ietf.org/html/rfc5389#section-6
|
|
|
|
export interface RtpStreamOptions {
|
|
port: number
|
|
rtcpPort: number
|
|
}
|
|
|
|
export interface RtpOptions {
|
|
audio: RtpStreamOptions
|
|
video: RtpStreamOptions
|
|
}
|
|
|
|
export interface RtpStreamDescription extends RtpStreamOptions {
|
|
ssrc?: number
|
|
iceUFrag?: string
|
|
icePwd?: string
|
|
}
|
|
|
|
export interface RtpDescription {
|
|
address: string
|
|
audio: RtpStreamDescription
|
|
video: RtpStreamDescription
|
|
sdp: string
|
|
}
|
|
|
|
export function isRtpMessagePayloadType(payloadType: number) {
|
|
return payloadType > 90 || payloadType === 0
|
|
}
|
|
|
|
export function getPayloadType(message: Buffer) {
|
|
return message.readUInt8(1) & 0x7f
|
|
}
|
|
|
|
export function getSequenceNumber(message: Buffer) {
|
|
return message.readUInt16BE(2)
|
|
}
|
|
|
|
export function isStunMessage(message: Buffer) {
|
|
return message.length > 8 && message.readInt32BE(4) === stunMagicCookie
|
|
}
|
|
|
|
export function sendStunBindingRequest({
|
|
rtpDescription,
|
|
rtpSplitter,
|
|
rtcpSplitter,
|
|
localUfrag,
|
|
type,
|
|
}: {
|
|
rtpSplitter: dgram.Socket
|
|
rtcpSplitter: dgram.Socket
|
|
rtpDescription: RtpDescription
|
|
localUfrag?: string
|
|
type: 'audio' | 'video'
|
|
}) {
|
|
const remoteDescription = rtpDescription[type];
|
|
if( remoteDescription.port == 0 )
|
|
return;
|
|
const message = stun.createMessage(1),
|
|
{ address } = rtpDescription,
|
|
{ iceUFrag, icePwd, port, rtcpPort } = remoteDescription
|
|
|
|
if (iceUFrag && icePwd && localUfrag) {
|
|
// Full ICE supported. Send as formal stun request
|
|
message.addUsername(iceUFrag + ':' + localUfrag)
|
|
message.addMessageIntegrity(icePwd)
|
|
|
|
stun
|
|
.request(`${address}:${port}`, {
|
|
socket: rtpSplitter,
|
|
message,
|
|
})
|
|
.then(() => console.debug(`${type} stun complete`))
|
|
.catch((e: Error) => {
|
|
console.error(`${type} stun error`)
|
|
console.error(e)
|
|
})
|
|
} else {
|
|
// ICE not supported. Fire and forget the stun request for RTP and RTCP
|
|
const encodedMessage = stun.encode(message)
|
|
try {
|
|
rtpSplitter.send(encodedMessage, port, address)
|
|
} catch (e) {
|
|
console.error(e)
|
|
}
|
|
|
|
try {
|
|
rtcpSplitter.send(encodedMessage, rtcpPort, address)
|
|
} catch (e) {
|
|
console.error(e)
|
|
}
|
|
}
|
|
}
|
|
|
|
export function createStunResponder(rtpSplitter: dgram.Socket) {
|
|
return rtpSplitter.on('message', (message, info) => {
|
|
if (!isStunMessage(message)) {
|
|
return null
|
|
}
|
|
|
|
try {
|
|
const decodedMessage = stun.decode(message),
|
|
response = stun.createMessage(
|
|
stun.constants.STUN_BINDING_RESPONSE,
|
|
decodedMessage.transactionId
|
|
)
|
|
|
|
response.addXorAddress(info.address, info.port)
|
|
try {
|
|
rtpSplitter.send(stun.encode(response), info.port, info.address)
|
|
} catch (e) {
|
|
console.error(e)
|
|
}
|
|
} catch (e) {
|
|
console.debug('Failed to Decode STUN Message')
|
|
console.debug(message.toString('hex'))
|
|
console.debug(e)
|
|
}
|
|
|
|
return null
|
|
})
|
|
}
|