mirror of
https://github.com/koush/scrypted.git
synced 2026-03-09 20:22:18 +00:00
rename pcm_ulaw to pcm_mulaw per ffmpeg codec name support transcode free rtp forwarding of audio only streams onvif two audio codec negotiation with upstream
120 lines
3.7 KiB
TypeScript
120 lines
3.7 KiB
TypeScript
import { RTCRtpTransceiver } from "./werift";
|
|
import { Deferred } from "@scrypted/common/src/deferred";
|
|
import { listenZeroSingleClient } from "@scrypted/common/src/listen-cluster";
|
|
import { RtspServer } from "@scrypted/common/src/rtsp-server";
|
|
import { createSdpInput, parseSdp } from "@scrypted/common/src/sdp-utils";
|
|
import sdk, { FFmpegInput, Intercom, MediaObject, RTCSessionControl } from "@scrypted/sdk";
|
|
|
|
const { mediaManager } = sdk;
|
|
|
|
export class ScryptedSessionControl implements RTCSessionControl {
|
|
rtspServer: RtspServer;
|
|
killed = new Deferred<void>();
|
|
|
|
constructor(public intercom: Intercom, public audioTransceiver: RTCRtpTransceiver) {
|
|
this.killed.promise.finally(async () => {
|
|
this.rtspServer?.client.destroy();
|
|
try {
|
|
await this.intercom?.stopIntercom();
|
|
}
|
|
catch (e) {
|
|
}
|
|
});
|
|
}
|
|
|
|
async setPlayback(options: { audio: boolean; video: boolean; }): Promise<void> {
|
|
await this.setPlaybackInternal(options);
|
|
}
|
|
|
|
async setPlaybackInternal(options: { audio: boolean; video: boolean; }): Promise<MediaObject> {
|
|
if (this.killed.finished)
|
|
return;
|
|
|
|
if (!this.intercom)
|
|
return;
|
|
|
|
if (!this.audioTransceiver.receiver.track)
|
|
await this.audioTransceiver.onTrack.asPromise()
|
|
const track = this.audioTransceiver.receiver.track;
|
|
|
|
track.onReceiveRtp.allUnsubscribe();
|
|
await this.intercom.stopIntercom();
|
|
|
|
if (!options.audio) {
|
|
return;
|
|
}
|
|
|
|
this.rtspServer?.client.destroy();
|
|
|
|
const rtspTcpServer = await listenZeroSingleClient();
|
|
|
|
const url = rtspTcpServer.url.replace('tcp:', 'rtsp:');
|
|
const ffmpegInput: FFmpegInput = {
|
|
container: 'rtsp',
|
|
url,
|
|
mediaStreamOptions: {
|
|
id: undefined,
|
|
video: null,
|
|
},
|
|
inputArguments: [
|
|
'-analyzeduration', '0',
|
|
'-probesize', '512',
|
|
'-rtsp_transport', 'tcp',
|
|
'-i', url,
|
|
],
|
|
};
|
|
|
|
|
|
const mo = await mediaManager.createFFmpegMediaObject(ffmpegInput);
|
|
rtspTcpServer.clientPromise.then(async client => {
|
|
const sdpReturnAudio = [
|
|
"v=0",
|
|
"o=- 0 0 IN IP4 127.0.0.1",
|
|
"s=" + "WebRTC Audio Talkback",
|
|
"c=IN IP4 127.0.0.1",
|
|
"t=0 0",
|
|
"b=AS:24",
|
|
|
|
// HACK, this may not be opus
|
|
"m=audio 0 RTP/AVP 110",
|
|
"a=rtpmap:110 opus/48000/2",
|
|
"a=fmtp:101 minptime=10;useinbandfec=1",
|
|
|
|
// "m=audio 0 RTP/AVP 0",
|
|
// "a=rtpmap:0 PCMU/8000",
|
|
|
|
// "m=audio 0 RTP/AVP 8",
|
|
// "a=rtpmap:8 PCMA/8000",
|
|
];
|
|
let sdp = sdpReturnAudio.join('\r\n');
|
|
sdp = createSdpInput(0, 0, sdp);
|
|
|
|
|
|
const rtspServer = new RtspServer(client, sdp);
|
|
this.rtspServer = rtspServer;
|
|
rtspServer.console = console;
|
|
await rtspServer.handlePlayback();
|
|
const parsedSdp = parseSdp(rtspServer.sdp);
|
|
const audioTrack = parsedSdp.msections.find(msection => msection.type === 'audio').control;
|
|
|
|
track.onReceiveRtp.subscribe(rtpPacket => {
|
|
rtpPacket.header.payloadType = 110;
|
|
rtspServer.sendTrack(audioTrack, rtpPacket.serialize(), false);
|
|
});
|
|
});
|
|
|
|
await this.intercom.startIntercom(mo);
|
|
await rtspTcpServer.clientPromise;
|
|
return mo;
|
|
}
|
|
|
|
async getRefreshAt() {
|
|
}
|
|
async extendSession() {
|
|
}
|
|
|
|
async endSession() {
|
|
this.killed.resolve(undefined);
|
|
}
|
|
}
|