@scrypted/prebuffer-mixin 0.1.257 → 0.1.260

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/main.ts CHANGED
@@ -2,20 +2,19 @@
2
2
  import { AutoenableMixinProvider } from '@scrypted/common/src/autoenable-mixin-provider';
3
3
  import { getH264EncoderArgs, LIBX264_ENCODER_TITLE } from '@scrypted/common/src/ffmpeg-hardware-acceleration';
4
4
  import { handleRebroadcasterClient, ParserOptions, ParserSession, startParserSession } from '@scrypted/common/src/ffmpeg-rebroadcast';
5
- import { closeQuiet, createBindZero, listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
5
+ import { closeQuiet, listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
6
6
  import { readLength } from '@scrypted/common/src/read-stream';
7
- import { createRtspParser, findH264NaluType, H264_NAL_TYPE_IDR, H264_NAL_TYPE_SEI, parseSemicolonDelimited, RtspClient, RtspServer, RTSP_FRAME_MAGIC } from '@scrypted/common/src/rtsp-server';
7
+ import { createRtspParser, findH264NaluType, H264_NAL_TYPE_IDR, H264_NAL_TYPE_SEI, RtspServer } from '@scrypted/common/src/rtsp-server';
8
8
  import { addTrackControls, parseSdp } from '@scrypted/common/src/sdp-utils';
9
9
  import { StorageSettings } from '@scrypted/common/src/settings';
10
10
  import { SettingsMixinDeviceBase, SettingsMixinDeviceOptions } from "@scrypted/common/src/settings-mixin";
11
- import { sleep } from '@scrypted/common/src/sleep';
12
11
  import { createFragmentedMp4Parser, createMpegTsParser, StreamChunk, StreamParser } from '@scrypted/common/src/stream-parser';
13
12
  import sdk, { BufferConverter, DeviceProvider, FFmpegInput, MediaObject, MediaStreamOptions, MixinProvider, RequestMediaStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoCameraConfiguration } from '@scrypted/sdk';
14
13
  import crypto from 'crypto';
15
- import dgram from 'dgram';
16
14
  import net from 'net';
17
15
  import { Duplex } from 'stream';
18
- import { connectRFC4571Parser, requeueRtspVideoData, RtspChannelCodecMapping, startRFC4571Parser } from './rfc4571';
16
+ import { connectRFC4571Parser, startRFC4571Parser } from './rfc4571';
17
+ import { startRtspSession } from './rtsp-session';
19
18
  import { createStreamSettings, getPrebufferedStreams } from './stream-settings';
20
19
  import { getTranscodeMixinProviderId, REBROADCAST_MIXIN_INTERFACE_TOKEN, TranscodeMixinProvider, TRANSCODE_MIXIN_PROVIDER_NATIVE_ID } from './transcode-settings';
21
20
 
@@ -267,7 +266,7 @@ class PrebufferSession {
267
266
 
268
267
  return {
269
268
  rtspMode: mode?.startsWith('RTSP'),
270
- muxingMp4: !rtspMode || mode?.includes('MP4'),
269
+ muxingMp4: !rtspMode,
271
270
  };
272
271
  }
273
272
 
@@ -306,7 +305,7 @@ class PrebufferSession {
306
305
  }
307
306
  );
308
307
 
309
- const addFFmpegSettings = () => {
308
+ const addFFmpegAudioSettings = () => {
310
309
  settings.push(
311
310
  {
312
311
  title: 'Audio Codec Transcoding',
@@ -322,6 +321,11 @@ class PrebufferSession {
322
321
  TRANSCODE_AUDIO_DESCRIPTION,
323
322
  ],
324
323
  },
324
+ );
325
+ };
326
+
327
+ const addFFmpegInputSettings = () => {
328
+ settings.push(
325
329
  {
326
330
  title: 'FFmpeg Input Arguments Prefix',
327
331
  group,
@@ -337,7 +341,9 @@ class PrebufferSession {
337
341
  combobox: true,
338
342
  },
339
343
  )
340
- };
344
+ }
345
+
346
+ let usingFFmpeg = muxingMp4;
341
347
 
342
348
  if (this.canUseRtspParser(this.advertisedMediaStreamOptions)) {
343
349
  const canUseScryptedParser = rtspMode;
@@ -349,13 +355,15 @@ class PrebufferSession {
349
355
  SCRYPTED_PARSER_UDP,
350
356
  ] : [];
351
357
 
358
+ const currentParser = this.storage.getItem(this.rtspParserKey) || STRING_DEFAULT;
359
+
352
360
  settings.push(
353
361
  {
354
362
  key: this.rtspParserKey,
355
363
  group,
356
364
  title: 'RTSP Parser',
357
365
  description: `The RTSP Parser used to read the stream. The default is "${defaultValue}" for this container.`,
358
- value: this.storage.getItem(this.rtspParserKey) || STRING_DEFAULT,
366
+ value: currentParser,
359
367
  choices: [
360
368
  STRING_DEFAULT,
361
369
  ...scryptedOptions,
@@ -364,9 +372,17 @@ class PrebufferSession {
364
372
  ],
365
373
  }
366
374
  );
375
+
376
+ if (!(currentParser === STRING_DEFAULT ? defaultValue : currentParser).includes('Scrypted')) {
377
+ usingFFmpeg = true;
378
+ }
367
379
  }
368
- else {
369
- addFFmpegSettings();
380
+
381
+ if (muxingMp4) {
382
+ addFFmpegAudioSettings();
383
+ }
384
+ if (usingFFmpeg) {
385
+ addFFmpegInputSettings();
370
386
  }
371
387
 
372
388
  if (session) {
@@ -672,154 +688,12 @@ class PrebufferSession {
672
688
  }
673
689
 
674
690
  if (usingScryptedParser) {
675
- this.console.log('bypassing ffmpeg: using scrypted rtsp/rfc4571 parser');
676
- const rtspClient = new RtspClient(ffmpegInput.url, this.console);
677
-
678
- let servers: dgram.Socket[] = [];
679
- const cleanupServers = () => {
680
- for (const server of servers) {
681
- closeQuiet(server);
682
- }
683
- }
684
-
685
- try {
686
- rtspClient.requestTimeout = 10000;
687
- await rtspClient.options();
688
- const sdpResponse = await rtspClient.describe();
689
- const contentBase = sdpResponse.headers['content-base'];
690
- if (contentBase) {
691
- const url = new URL(contentBase);
692
- const existing = new URL(rtspClient.url);
693
- url.username = existing.username;
694
- url.password = existing.password;
695
- rtspClient.url = url.toString();
696
- }
697
- let sdp = sdpResponse.body.toString().trim();
698
- this.console.log('sdp', sdp);
699
-
700
- const parsedSdp = parseSdp(sdp);
701
- let channel = 0;
702
- const mapping: RtspChannelCodecMapping = {};
703
- const useUdp = parser === SCRYPTED_PARSER_UDP;
704
- let udpSessionTimeout: number;
705
- const checkUdpSessionTimeout = (headers: { [key: string]: string }) => {
706
- if (useUdp && headers.session && !udpSessionTimeout) {
707
- const sessionDict = parseSemicolonDelimited(headers.session);
708
- udpSessionTimeout = parseInt(sessionDict['timeout']);
709
- }
710
- }
711
-
712
- const doSetup = async (control: string, codec: string) => {
713
- let setupChannel = channel;
714
- let udp: dgram.Socket;
715
- if (useUdp) {
716
- const rtspChannel = channel;
717
- const { port, server } = await createBindZero();
718
- udp = server;
719
- servers.push(server);
720
- setupChannel = port;
721
- server.on('message', data => {
722
- const prefix = Buffer.alloc(4);
723
- prefix.writeUInt8(RTSP_FRAME_MAGIC, 0);
724
- prefix.writeUInt8(rtspChannel, 1);
725
- prefix.writeUInt16BE(data.length, 2);
726
- const chunk: StreamChunk = {
727
- chunks: [prefix, data],
728
- type: codec,
729
- };
730
- session?.emit('rtsp', chunk);
731
- session?.resetActivityTimer?.();
732
- })
733
- }
734
- const setupResult = await rtspClient.setup(setupChannel, control, useUdp);
735
- checkUdpSessionTimeout(setupResult.headers);
736
-
737
- if (udp) {
738
- const punch = Buffer.alloc(1);
739
- const transport = setupResult.headers['transport'];
740
- const match = transport.match(/.*?server_port=([0-9]+)-([0-9]+)/);
741
- const [_, rtp, rtcp] = match;
742
- const { hostname } = new URL(rtspClient.url);
743
- udp.send(punch, parseInt(rtp), hostname)
744
-
745
- mapping[channel] = codec;
746
- }
747
- else {
748
- if (setupResult.interleaved)
749
- mapping[setupResult.interleaved.begin] = codec;
750
- else
751
- mapping[channel] = codec;
752
- }
753
-
754
- channel += 2;
755
- }
756
-
757
- let setupVideoSection = false;
758
-
759
- parsedSdp.msections = parsedSdp.msections.filter(section => {
760
- if (section.type === 'video') {
761
- if (setupVideoSection) {
762
- this.console.warn('additional video section found. skipping.');
763
- return false;
764
- }
765
- setupVideoSection = true;
766
- }
767
- else if (section.type !== 'audio') {
768
- this.console.warn('unknown section', section.type);
769
- return false;
770
- }
771
- else if (audioSoftMuted) {
772
- return false;
773
- }
774
- return true;
775
- });
776
-
777
- for (const section of parsedSdp.msections) {
778
- await doSetup(section.control, section.codec)
779
- }
780
-
781
- // sdp may contain multiple audio/video sections. take only the first video section.
782
- sdp = [...parsedSdp.header.lines, ...parsedSdp.msections.map(msection => msection.lines).flat()].join('\r\n');
783
-
784
- this.sdp = Promise.resolve(sdp);
785
-
786
- const play = await rtspClient.play();
787
- checkUdpSessionTimeout(play.headers);
788
-
789
- requeueRtspVideoData(rtspClient);
790
-
791
- session = startRFC4571Parser(this.console, rtspClient.client, sdp, ffmpegInput.mediaStreamOptions, rbo, {
792
- channelMap: mapping,
793
- rtspClient,
794
- udpSessionTimeout,
795
- });
796
- const sessionKill = session.kill.bind(session);
797
- let issuedTeardown = false;
798
- session.kill = async () => {
799
- try {
800
- cleanupServers();
801
- // issue a teardown to upstream to close gracefully but don't rely on it responding.
802
- if (!issuedTeardown) {
803
- issuedTeardown = true;
804
- rtspClient.writeTeardown();
805
- }
806
- await sleep(500);
807
- }
808
- finally {
809
- rtspClient.client.destroy();
810
- sessionKill();
811
- }
812
- }
813
- if (!session.isActive)
814
- throw new Error('parser was killed before rtsp client started');
815
-
816
- rtspClient.client.on('close', () => session.kill());
817
- }
818
- catch (e) {
819
- cleanupServers();
820
- rtspClient.client.destroy();
821
- throw e;
822
- }
691
+ session = await startRtspSession(this.console, ffmpegInput.url, ffmpegInput.mediaStreamOptions, {
692
+ useUdp: parser === SCRYPTED_PARSER_UDP,
693
+ audioSoftMuted,
694
+ rtspRequestTimeout: 10000,
695
+ });
696
+ this.sdp = session.sdp.then(buffers => Buffer.concat(buffers).toString());
823
697
  }
824
698
  else {
825
699
  if (parser === FFMPEG_PARSER_UDP)
@@ -851,8 +725,10 @@ class PrebufferSession {
851
725
  return;
852
726
 
853
727
  let { isDefault } = this.getParser(rtspMode, sessionMso);
854
- if (!isDefault)
728
+ if (!isDefault) {
729
+ this.console.warn('SEI packet detected while operating with Scrypted Parser. If there are issues streaming, consider using the Default parser.');
855
730
  return;
731
+ }
856
732
  this.console.warn('SEI packet detected while operating with Scrypted Parser as default. Restarting rebroadcast.');
857
733
  session.kill();
858
734
  this.startPrebufferSession();
@@ -1002,6 +878,7 @@ class PrebufferSession {
1002
878
  }
1003
879
 
1004
880
  async handleRebroadcasterClient(options: {
881
+ requestedContainer: string,
1005
882
  isActiveClient: boolean,
1006
883
  container: PrebufferParsers,
1007
884
  session: ParserSession<PrebufferParsers>,
@@ -1049,7 +926,16 @@ class PrebufferSession {
1049
926
  session.once('killed', cleanup);
1050
927
 
1051
928
  const prebufferContainer: PrebufferStreamChunk[] = this.prebuffers[container];
1052
- if (container !== 'rtsp') {
929
+ // if the requested container or the source container is not rtsp, use an exact seek.
930
+ // this works better when the requested container is mp4, and rtsp is the source.
931
+ // if starting on a sync frame, ffmpeg will skip the first segment while initializing
932
+ // on live sources like rtsp. the buffer before the sync frame stream will be enough
933
+ // for ffmpeg to analyze and start up in time for the sync frame.
934
+ // may be worth considering playing with a few other things to avoid this:
935
+ // mpeg-ts as a container (would need to write a muxer)
936
+ // specifying the buffer before the sync frame with probesize.
937
+ if (container !== 'rtsp'
938
+ || (options?.requestedContainer && options?.requestedContainer !== 'rtsp')) {
1053
939
  for (const chunk of prebufferContainer) {
1054
940
  if (chunk.time < now - requestedPrebuffer)
1055
941
  continue;
@@ -1097,17 +983,11 @@ class PrebufferSession {
1097
983
  requestedPrebuffer = idrInterval * 1.5;
1098
984
  }
1099
985
 
1100
- const { rtspMode } = this.getRebroadcastContainer();
986
+ const { rtspMode, muxingMp4 } = this.getRebroadcastContainer();
1101
987
  const defaultContainer = rtspMode ? 'rtsp' : 'mpegts';
1102
988
 
1103
989
  let container: PrebufferParsers = this.parsers[options?.container] ? options?.container as PrebufferParsers : defaultContainer;
1104
990
 
1105
- // If a mp4 prebuffer was explicitly requested, but an mp4 prebuffer is not available (rtsp mode),
1106
- // rewind a little bit earlier to gaurantee a valid full segment of that length is sent.
1107
- if (requestedPrebuffer && container !== 'mp4' && options?.container === 'mp4') {
1108
- requestedPrebuffer += idrInterval * 1.5;
1109
- }
1110
-
1111
991
  const mediaStreamOptions: ResponseMediaStreamOptions = session.negotiateMediaStream(options);
1112
992
  let sdp = await this.sdp;
1113
993
 
@@ -1118,7 +998,11 @@ class PrebufferSession {
1118
998
 
1119
999
  if (container === 'rtsp') {
1120
1000
  const parsedSdp = parseSdp(sdp);
1121
- parsedSdp.msections = parsedSdp.msections.filter(msection => msection.codec === mediaStreamOptions.video?.codec || msection.codec === mediaStreamOptions.audio?.codec);
1001
+ const videoSection = parsedSdp.msections.find(msection => msection.codec && msection.codec === mediaStreamOptions.video?.codec) || parsedSdp.msections.find(msection => msection.type === 'video');
1002
+ let audioSection = parsedSdp.msections.find(msection => msection.codec && msection.codec === mediaStreamOptions.audio?.codec) || parsedSdp.msections.find(msection => msection.type === 'audio');
1003
+ if (mediaStreamOptions.audio === null)
1004
+ audioSection = undefined;
1005
+ parsedSdp.msections = parsedSdp.msections.filter(msection => msection === videoSection || msection === audioSection);
1122
1006
  const filterPrebufferAudio = options?.prebuffer === undefined;
1123
1007
  const videoCodec = parsedSdp.msections.find(msection => msection.type === 'video')?.codec;
1124
1008
  sdp = parsedSdp.toSdp();
@@ -1164,6 +1048,7 @@ class PrebufferSession {
1164
1048
  const isActiveClient = options?.refresh !== false;
1165
1049
 
1166
1050
  this.handleRebroadcasterClient({
1051
+ requestedContainer: options?.container,
1167
1052
  isActiveClient,
1168
1053
  container,
1169
1054
  requestedPrebuffer,
@@ -1179,7 +1064,7 @@ class PrebufferSession {
1179
1064
  if (this.audioDisabled) {
1180
1065
  mediaStreamOptions.audio = null;
1181
1066
  }
1182
- else if (reencodeAudio) {
1067
+ else if (reencodeAudio && muxingMp4) {
1183
1068
  mediaStreamOptions.audio = {
1184
1069
  codec: 'aac',
1185
1070
  encoder: 'aac',
@@ -1621,6 +1506,7 @@ export class RebroadcastPlugin extends AutoenableMixinProvider implements MixinP
1621
1506
  const requestedPrebuffer = Math.max(4000, (idrInterval || 4000)) * 1.5;
1622
1507
 
1623
1508
  prebufferSession.handleRebroadcasterClient({
1509
+ requestedContainer: 'rtsp',
1624
1510
  isActiveClient: true,
1625
1511
  container: 'rtsp',
1626
1512
  session,
package/src/rfc4571.ts CHANGED
@@ -1,14 +1,59 @@
1
1
  import { cloneDeep } from "@scrypted/common/src/clone-deep";
2
2
  import { ParserOptions, ParserSession, setupActivityTimer } from "@scrypted/common/src/ffmpeg-rebroadcast";
3
- import { read16BELengthLoop, readLength } from "@scrypted/common/src/read-stream";
4
- import { findH264NaluType, H264_NAL_TYPE_SPS, RtspClient, RTSP_FRAME_MAGIC } from "@scrypted/common/src/rtsp-server";
3
+ import { read16BELengthLoop } from "@scrypted/common/src/read-stream";
4
+ import { findH264NaluType, H264_NAL_TYPE_SPS, RTSP_FRAME_MAGIC } from "@scrypted/common/src/rtsp-server";
5
5
  import { parseSdp } from "@scrypted/common/src/sdp-utils";
6
6
  import { sleep } from "@scrypted/common/src/sleep";
7
7
  import { StreamChunk } from "@scrypted/common/src/stream-parser";
8
- import { ResponseMediaStreamOptions } from "@scrypted/sdk";
8
+ import { MediaStreamOptions, ResponseMediaStreamOptions } from "@scrypted/sdk";
9
+ import { parse as spsParse } from "h264-sps-parser";
9
10
  import net from 'net';
10
11
  import { EventEmitter, Readable } from "stream";
11
- import { getSpsResolution, parse as spsParse } from "./sps-parser";
12
+ import { getSpsResolution } from "./sps-resolution";
13
+
14
+ export function negotiateMediaStream(sdp: string, mediaStreamOptions: MediaStreamOptions, inputVideoCodec: string, inputAudioCodec: string, requestMediaStream: MediaStreamOptions) {
15
+ const parsedSdp = parseSdp(sdp);
16
+ const ret: ResponseMediaStreamOptions = cloneDeep(mediaStreamOptions) || {
17
+ id: undefined,
18
+ name: undefined,
19
+ };
20
+
21
+ // if the source doesn't provide a video codec, dummy one up
22
+ if (ret.video === undefined)
23
+ ret.video = {};
24
+
25
+ // the requests does not want video
26
+ if (requestMediaStream?.video === null)
27
+ ret.video = null;
28
+
29
+ if (ret.video)
30
+ ret.video.codec = inputVideoCodec;
31
+
32
+ // some rtsp like unifi offer alternate audio tracks (aac and opus).
33
+ if (requestMediaStream?.audio?.codec && requestMediaStream?.audio?.codec !== inputAudioCodec) {
34
+ const alternateAudio = parsedSdp.msections.find(msection => msection.type === 'audio' && msection.codec === requestMediaStream?.audio?.codec);
35
+ if (alternateAudio) {
36
+ ret.audio = {
37
+ codec: requestMediaStream?.audio?.codec,
38
+ };
39
+
40
+ return ret;
41
+ }
42
+ }
43
+
44
+ // reported codecs may be wrong/cached/etc, so before blindly copying the audio codec info,
45
+ // verify what was found.
46
+ if (ret?.audio?.codec === inputAudioCodec) {
47
+ ret.audio = mediaStreamOptions?.audio;
48
+ }
49
+ else {
50
+ ret.audio = {
51
+ codec: inputAudioCodec,
52
+ }
53
+ }
54
+
55
+ return ret;
56
+ }
12
57
 
13
58
  export function connectRFC4571Parser(url: string) {
14
59
  const u = new URL(url);
@@ -19,21 +64,7 @@ export function connectRFC4571Parser(url: string) {
19
64
  return socket;
20
65
  }
21
66
 
22
- export type RtspChannelCodecMapping = { [key: number]: string };
23
-
24
- const RTSP_BUFFER = Buffer.from('RTSP');
25
-
26
- export function requeueRtspVideoData(rtspClient: RtspClient) {
27
- const videoData = rtspClient.rfc4571.read();
28
- if (videoData)
29
- rtspClient.client.unshift(videoData);
30
- }
31
-
32
- export function startRFC4571Parser(console: Console, socket: Readable, sdp: string, mediaStreamOptions: ResponseMediaStreamOptions, options?: ParserOptions<"rtsp">, rtspOptions?: {
33
- channelMap: RtspChannelCodecMapping,
34
- rtspClient: RtspClient,
35
- udpSessionTimeout: number,
36
- }): ParserSession<"rtsp"> {
67
+ export function startRFC4571Parser(console: Console, socket: Readable, sdp: string, mediaStreamOptions: ResponseMediaStreamOptions, options?: ParserOptions<"rtsp">): ParserSession<"rtsp"> {
37
68
  let isActive = true;
38
69
  const events = new EventEmitter();
39
70
  // need this to prevent kill from throwing due to uncaught Error during cleanup
@@ -98,65 +129,27 @@ export function startRFC4571Parser(console: Console, socket: Readable, sdp: stri
98
129
  // don't start parsing until next tick, to prevent missed packets.
99
130
  await sleep(0);
100
131
 
101
- if (rtspOptions?.udpSessionTimeout) {
102
- while (true) {
103
- await sleep(rtspOptions.udpSessionTimeout * 1000 - 5000);
104
- await rtspOptions.rtspClient.getParameter();
105
- }
106
- }
107
-
108
- const headerLength = rtspOptions?.channelMap ? 4 : 2;
109
- const offset = rtspOptions?.channelMap ? 2 : 0;
110
- const skipHeader = (header: Buffer, resumeRead: () => void) => {
111
- if (!rtspOptions?.rtspClient.needKeepAlive)
112
- return false;
113
-
114
- socket.unshift(header);
115
- rtspOptions.rtspClient.needKeepAlive = false;
116
- rtspOptions.rtspClient.getParameter().then(() => {
117
- requeueRtspVideoData(rtspOptions.rtspClient);
118
- resumeRead();
119
- })
120
- .catch(e => {
121
- console.error('error during RTSP keepalive', e);
122
- kill();
123
- });
124
- return true;
125
- }
126
132
  await read16BELengthLoop(socket, {
127
- headerLength,
128
- offset,
129
- skipHeader,
133
+ headerLength: 2,
134
+ skipHeader: undefined,
130
135
  callback: (header, data) => {
131
136
  let type: string;
137
+ const pt = data[1] & 0x7f;
132
138
 
133
- if (rtspOptions?.channelMap) {
134
- const channel = header.readUInt8(1);
135
- type = rtspOptions?.channelMap[channel];
136
- if (!type) {
137
- const rtpType = rtspOptions?.channelMap[channel - 1];
138
- if (rtpType)
139
- type = `rtcp-${rtpType}`;
140
- }
139
+ const prefix = Buffer.alloc(2);
140
+ prefix[0] = RTSP_FRAME_MAGIC;
141
+ if (pt === audioPt) {
142
+ prefix[1] = 0;
141
143
  }
142
- else {
143
- const pt = data[1] & 0x7f;
144
-
145
- const prefix = Buffer.alloc(2);
146
- prefix[0] = RTSP_FRAME_MAGIC;
147
- if (pt === audioPt) {
148
- prefix[1] = 0;
149
- }
150
- else if (pt === videoPt) {
151
- prefix[1] = 2;
152
- }
153
- header = Buffer.concat([prefix, header]);
154
-
155
- if (pt === audioPt)
156
- type = inputAudioCodec;
157
- else if (pt === videoPt)
158
- type = inputVideoCodec;
144
+ else if (pt === videoPt) {
145
+ prefix[1] = 2;
159
146
  }
147
+ header = Buffer.concat([prefix, header]);
148
+
149
+ if (pt === audioPt)
150
+ type = inputAudioCodec;
151
+ else if (pt === videoPt)
152
+ type = inputVideoCodec;
160
153
 
161
154
  const chunk: StreamChunk = {
162
155
  chunks: [header, data],
@@ -209,46 +202,7 @@ export function startRFC4571Parser(console: Console, socket: Readable, sdp: stri
209
202
  killed,
210
203
  resetActivityTimer,
211
204
  negotiateMediaStream: (requestMediaStream) => {
212
- const ret: ResponseMediaStreamOptions = cloneDeep(mediaStreamOptions) || {
213
- id: undefined,
214
- name: undefined,
215
- };
216
-
217
- // if the source doesn't provide a video codec, dummy one up
218
- if (ret.video === undefined)
219
- ret.video = {};
220
-
221
- // the requests does not want video
222
- if (requestMediaStream?.video === null)
223
- ret.video = null;
224
-
225
- if (ret.video)
226
- ret.video.codec = inputVideoCodec;
227
-
228
- // some rtsp like unifi offer alternate audio tracks (aac and opus).
229
- if (requestMediaStream?.audio?.codec && requestMediaStream?.audio?.codec !== inputAudioCodec) {
230
- const alternateAudio = parsedSdp.msections.find(msection => msection.type === 'audio' && msection.codec === requestMediaStream?.audio?.codec);
231
- if (alternateAudio) {
232
- ret.audio = {
233
- codec: requestMediaStream?.audio?.codec,
234
- };
235
-
236
- return ret;
237
- }
238
- }
239
-
240
- // reported codecs may be wrong/cached/etc, so before blindly copying the audio codec info,
241
- // verify what was found.
242
- if (ret?.audio?.codec === inputAudioCodec) {
243
- ret.audio = mediaStreamOptions?.audio;
244
- }
245
- else {
246
- ret.audio = {
247
- codec: inputAudioCodec,
248
- }
249
- }
250
-
251
- return ret;
205
+ return negotiateMediaStream(sdp, mediaStreamOptions, inputVideoCodec, inputAudioCodec, requestMediaStream);
252
206
  },
253
207
  emit(container: 'rtsp', chunk: StreamChunk) {
254
208
  events.emit(container, chunk);
@@ -267,4 +221,4 @@ export function startRFC4571Parser(console: Console, socket: Readable, sdp: stri
267
221
  return this;
268
222
  }
269
223
  }
270
- }
224
+ }