@scrypted/prebuffer-mixin 0.1.258 → 0.1.261

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) {
@@ -666,160 +682,23 @@ class PrebufferSession {
666
682
  let { parser, isDefault } = this.getParser(rtspMode, sessionMso);
667
683
  usingScryptedParser = parser === SCRYPTED_PARSER_TCP || parser === SCRYPTED_PARSER_UDP;
668
684
  if (isDefault && (parser === SCRYPTED_PARSER_TCP || parser === SCRYPTED_PARSER_UDP) && this.getLastH264Probe().seiDetected) {
669
- this.console.warn('SEI packet detected was in video stream, the Default Scrypted RTSP Parser will not be used. Falling back to FFmpeg. This can be overriden by setting the RTSP Parser to Scrypted.');
670
- usingScryptedParser = false;
671
- parser = FFMPEG_PARSER_TCP;
685
+ if (sessionMso.tool === 'scrypted') {
686
+ this.console.warn('SEI packet detected was in video stream, but stream is marked safe by Scrypted. The Default Scrypted RTSP Parser will be used. This can be overriden by setting the RTSP Parser to Scrypted.');
687
+ }
688
+ else {
689
+ this.console.warn('SEI packet detected was in video stream, the Default Scrypted RTSP Parser will not be used. Falling back to FFmpeg. This can be overriden by setting the RTSP Parser to Scrypted.');
690
+ usingScryptedParser = false;
691
+ parser = FFMPEG_PARSER_TCP;
692
+ }
672
693
  }
673
694
 
674
695
  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
- }
696
+ session = await startRtspSession(this.console, ffmpegInput.url, ffmpegInput.mediaStreamOptions, {
697
+ useUdp: parser === SCRYPTED_PARSER_UDP,
698
+ audioSoftMuted,
699
+ rtspRequestTimeout: 10000,
700
+ });
701
+ this.sdp = session.sdp.then(buffers => Buffer.concat(buffers).toString());
823
702
  }
824
703
  else {
825
704
  if (parser === FFMPEG_PARSER_UDP)
@@ -1004,6 +883,7 @@ class PrebufferSession {
1004
883
  }
1005
884
 
1006
885
  async handleRebroadcasterClient(options: {
886
+ requestedContainer: string,
1007
887
  isActiveClient: boolean,
1008
888
  container: PrebufferParsers,
1009
889
  session: ParserSession<PrebufferParsers>,
@@ -1051,7 +931,16 @@ class PrebufferSession {
1051
931
  session.once('killed', cleanup);
1052
932
 
1053
933
  const prebufferContainer: PrebufferStreamChunk[] = this.prebuffers[container];
1054
- if (container !== 'rtsp') {
934
+ // if the requested container or the source container is not rtsp, use an exact seek.
935
+ // this works better when the requested container is mp4, and rtsp is the source.
936
+ // if starting on a sync frame, ffmpeg will skip the first segment while initializing
937
+ // on live sources like rtsp. the buffer before the sync frame stream will be enough
938
+ // for ffmpeg to analyze and start up in time for the sync frame.
939
+ // may be worth considering playing with a few other things to avoid this:
940
+ // mpeg-ts as a container (would need to write a muxer)
941
+ // specifying the buffer before the sync frame with probesize.
942
+ if (container !== 'rtsp'
943
+ || (options?.requestedContainer && options?.requestedContainer !== 'rtsp')) {
1055
944
  for (const chunk of prebufferContainer) {
1056
945
  if (chunk.time < now - requestedPrebuffer)
1057
946
  continue;
@@ -1099,17 +988,11 @@ class PrebufferSession {
1099
988
  requestedPrebuffer = idrInterval * 1.5;
1100
989
  }
1101
990
 
1102
- const { rtspMode } = this.getRebroadcastContainer();
991
+ const { rtspMode, muxingMp4 } = this.getRebroadcastContainer();
1103
992
  const defaultContainer = rtspMode ? 'rtsp' : 'mpegts';
1104
993
 
1105
994
  let container: PrebufferParsers = this.parsers[options?.container] ? options?.container as PrebufferParsers : defaultContainer;
1106
995
 
1107
- // If a mp4 prebuffer was explicitly requested, but an mp4 prebuffer is not available (rtsp mode),
1108
- // rewind a little bit earlier to gaurantee a valid full segment of that length is sent.
1109
- if (requestedPrebuffer && container !== 'mp4' && options?.container === 'mp4') {
1110
- requestedPrebuffer += idrInterval * 1.5;
1111
- }
1112
-
1113
996
  const mediaStreamOptions: ResponseMediaStreamOptions = session.negotiateMediaStream(options);
1114
997
  let sdp = await this.sdp;
1115
998
 
@@ -1120,7 +1003,11 @@ class PrebufferSession {
1120
1003
 
1121
1004
  if (container === 'rtsp') {
1122
1005
  const parsedSdp = parseSdp(sdp);
1123
- parsedSdp.msections = parsedSdp.msections.filter(msection => msection.codec === mediaStreamOptions.video?.codec || msection.codec === mediaStreamOptions.audio?.codec);
1006
+ const videoSection = parsedSdp.msections.find(msection => msection.codec && msection.codec === mediaStreamOptions.video?.codec) || parsedSdp.msections.find(msection => msection.type === 'video');
1007
+ let audioSection = parsedSdp.msections.find(msection => msection.codec && msection.codec === mediaStreamOptions.audio?.codec) || parsedSdp.msections.find(msection => msection.type === 'audio');
1008
+ if (mediaStreamOptions.audio === null)
1009
+ audioSection = undefined;
1010
+ parsedSdp.msections = parsedSdp.msections.filter(msection => msection === videoSection || msection === audioSection);
1124
1011
  const filterPrebufferAudio = options?.prebuffer === undefined;
1125
1012
  const videoCodec = parsedSdp.msections.find(msection => msection.type === 'video')?.codec;
1126
1013
  sdp = parsedSdp.toSdp();
@@ -1166,6 +1053,7 @@ class PrebufferSession {
1166
1053
  const isActiveClient = options?.refresh !== false;
1167
1054
 
1168
1055
  this.handleRebroadcasterClient({
1056
+ requestedContainer: options?.container,
1169
1057
  isActiveClient,
1170
1058
  container,
1171
1059
  requestedPrebuffer,
@@ -1181,7 +1069,7 @@ class PrebufferSession {
1181
1069
  if (this.audioDisabled) {
1182
1070
  mediaStreamOptions.audio = null;
1183
1071
  }
1184
- else if (reencodeAudio) {
1072
+ else if (reencodeAudio && muxingMp4) {
1185
1073
  mediaStreamOptions.audio = {
1186
1074
  codec: 'aac',
1187
1075
  encoder: 'aac',
@@ -1623,6 +1511,7 @@ export class RebroadcastPlugin extends AutoenableMixinProvider implements MixinP
1623
1511
  const requestedPrebuffer = Math.max(4000, (idrInterval || 4000)) * 1.5;
1624
1512
 
1625
1513
  prebufferSession.handleRebroadcasterClient({
1514
+ requestedContainer: 'rtsp',
1626
1515
  isActiveClient: true,
1627
1516
  container: 'rtsp',
1628
1517
  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
+ }