common: use webrtc sdp. add webrtc signaling logging. settings mixin release.

This commit is contained in:
Koushik Dutta
2022-02-28 14:16:50 -08:00
parent 65fd20d2fe
commit bc02c86c91
3 changed files with 44 additions and 44 deletions

View File

@@ -1,20 +1,36 @@
import type { RTCSignalingSession, RTCAVSignalingSetup, ScryptedDevice, RTCSignalingChannel } from "@scrypted/sdk/types";
import type { RTCSignalingSession, RTCAVSignalingSetup } from "@scrypted/sdk/types";
import type { RTCSignalingChannelOptions, RTCSignalingSendIceCandidate } from "@scrypted/sdk";
export async function startRTCSignalingSession(session: RTCSignalingSession, offer: RTCSessionDescriptionInit,
console: Console,
createSetup: () => Promise<RTCAVSignalingSetup>,
setRemoteDescription: (remoteDescription: RTCSessionDescriptionInit) => Promise<RTCSessionDescriptionInit>,
addIceCandidate?: (candidate: RTCIceCandidate) => Promise<void>) {
const setup = await createSetup();
if (!offer) {
const offer = await session.createLocalDescription('offer', setup, addIceCandidate);
const answer = await setRemoteDescription(offer);
await session.setRemoteDescription(answer, setup);
try {
const setup = await createSetup();
console.log('offer', offer?.sdp, 'rtc setup', setup);
if (!offer) {
console.log('session.createLocalDescription');
const offer = await session.createLocalDescription('offer', setup, addIceCandidate);
console.log('rtc offer', offer.sdp);
const answer = await setRemoteDescription(offer);
console.log('rtc answer', answer);
await session.setRemoteDescription(answer, setup);
console.log('session.setRemoteDescription done');
}
else {
console.log('session.setRemoteDescription');
await session.setRemoteDescription(offer, setup);
console.log('session.createLocalDescription');
const answer = await session.createLocalDescription('answer', setup, addIceCandidate);
console.log('rtc answer', answer.sdp);
await setRemoteDescription(answer);
console.log('session.setRemoteDescription done');
}
}
else {
await session.setRemoteDescription(offer, setup);
const answer = await session.createLocalDescription('answer', setup, addIceCandidate);
await setRemoteDescription(answer);
catch (e) {
console.error('RTC signaling failed', e);
throw e;
}
}

View File

@@ -84,7 +84,7 @@ export abstract class SettingsMixinDeviceBase<T> extends MixinDeviceBase<T & Set
deviceManager.onMixinEvent(this.id, this, ScryptedInterface.Settings, null);
}
release() {
deviceManager.onMixinEvent(this.id, this, ScryptedInterface.Settings, null);
async release() {
await deviceManager.onMixinEvent(this.id, this, ScryptedInterface.Settings, null);
}
}

View File

@@ -5,35 +5,21 @@ import dgram from 'dgram';
import { RtspServer } from "./rtsp-server";
import { Socket } from "net";
import { ScryptedDeviceBase } from "@scrypted/sdk";
import { parsePayloadTypes } from './sdp-utils';
// this is an sdp corresponding to what is requested from webrtc.
// h264 baseline and opus are required codecs that all webrtc implementations must provide.
function createSdpInput(audioPort: number, videoPort: number, sdp: string) {
const { audioPayloadTypes, videoPayloadTypes } = parsePayloadTypes(sdp);
let outputSdp = sdp
.replace(/m=audio \d+/, `m=audio ${audioPort}`)
.replace(/m=video \d+/, `m=video ${videoPort}`);
return `v=0
o=- 0 0 IN IP4 127.0.0.1
s=-
c=IN IP4 127.0.0.1
t=0 0
m=audio ${audioPort} UDP ${[...audioPayloadTypes].join(' ')}
a=control:trackID=audio
a=rtpmap:101 opus/48000/2
a=fmtp:101 minptime=10;useinbandfec=1
a=rtcp-fb:101 transport-cc
a=sendrecv
m=video ${videoPort} UDP ${[...videoPayloadTypes].join(' ')}
a=control:trackID=video
a=rtpmap:96 H264/90000
a=rtcp-fb:96 ccm fir
a=rtcp-fb:96 nack
a=rtcp-fb:96 nack pli
a=rtcp-fb:96 goog-remb
a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f
a=sendrecv
`;
const lines = outputSdp.split('\n').map(line => line.trim());
const vindex = lines.findIndex(line => line.startsWith('m=video'));
lines.splice(vindex + 1, 0, 'a=control:trackID=video');
const aindex = lines.findIndex(line => line.startsWith('m=audio'));
lines.splice(aindex + 1, 0, 'a=control:trackID=audio');
outputSdp = lines.join('\r\n')
return outputSdp;
}
const useUdp = false;
@@ -62,19 +48,17 @@ export async function createRTCPeerConnectionSource(channel: ScryptedDeviceBase
const { clientPromise, port } = await listenZeroSingleClient();
let ai: NodeJS.Timeout;
let vi: NodeJS.Timeout;
let pictureLossInterval: NodeJS.Timeout;
let pc: RTCPeerConnection;
let socket: Socket;
// rtsp server must operate in udp forwarding mode to accomodate packet reordering.
let udp = dgram.createSocket('udp4');
const cleanup = () => {
console.log('cleanup');
console.log('webrtc/rtsp cleaning up');
pc?.close();
socket?.destroy();
clearInterval(ai);
clearInterval(vi);
clearInterval(pictureLossInterval);
try {
udp.close();
}
@@ -82,7 +66,7 @@ export async function createRTCPeerConnectionSource(channel: ScryptedDeviceBase
}
};
clientPromise.then(async (client) => {
clientPromise.then((client) => {
socket = client;
const rtspServer = new RtspServer(socket, undefined, udp);
// rtspServer.console = console;
@@ -163,12 +147,12 @@ export async function createRTCPeerConnectionSource(channel: ScryptedDeviceBase
});
track.onReceiveRtcp.subscribe(rtcp => rtspServer.sendVideo(rtcp.serialize(), true))
track.onReceiveRtp.once(() => {
vi = setInterval(() => videoTransceiver.receiver.sendRtcpPLI(track.ssrc!), 2000);
pictureLossInterval = setInterval(() => videoTransceiver.receiver.sendRtcpPLI(track.ssrc!), 4000);
});
});
}
channel.startRTCSignalingSession({
return channel.startRTCSignalingSession({
createLocalDescription: async (type, setup, sendIceCandidate) => {
if (type === 'offer')
doSetup(setup);