Skip to content

Commit 61708ae

Browse files
authored
Merge pull request #16 from jimm98y/features/client-reconnect
Added support for reconnecting the RTSP client after the connection to the server is lost. Fixed disposal of transport streams and allowed multiple calls to Connect to succeed.
2 parents e501724 + 461f4f5 commit 61708ae

3 files changed

Lines changed: 148 additions & 52 deletions

File tree

src/Directory.Build.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<Nullable>disable</Nullable>
66
<ImplicitUsings>disable</ImplicitUsings>
77
<Title>$(ProjectName)</Title>
8-
<Version>0.0.8</Version>
8+
<Version>0.1.0</Version>
99
<Authors>Lukas Volf</Authors>
1010
<Copyright>MIT</Copyright>
1111
<PackageProjectUrl>https://github.com/jimm98y/SharpRealTimeStreaming</PackageProjectUrl>

src/RTSPClientApp/Program.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,13 @@
1313
client.ReceivedVideoData += (sender, e) => Console.Write("*");
1414
client.NewAudioStream += (sender, e) => Console.WriteLine(e.ToString());
1515
client.ReceivedAudioData += (sender, e) => Console.Write("+");
16+
client.Stopped += (sender, e) =>
17+
{
18+
Console.WriteLine("Stopped");
19+
client.TryReconnect();
20+
};
1621

17-
client.Connect(rtspUri, RTPTransport.TCP, userName, password);
22+
client.Connect(rtspUri, RTPTransport.TCP, userName, password, MediaRequest.VIDEO_AND_AUDIO, false, null, true);
1823

1924
Console.WriteLine("Press any key to exit");
2025
while (!Console.KeyAvailable)

src/SharpRTSPClient/RTSPClient.cs

Lines changed: 141 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using System.IO;
1111
using System.Linq;
1212
using System.Net;
13+
using System.Net.Security;
1314
using System.Text;
1415

1516
namespace SharpRTSPClient
@@ -42,6 +43,7 @@ public class RTSPClient : IDisposable
4243
public event EventHandler<NewStreamEventArgs> NewAudioStream;
4344
public event EventHandler<SimpleDataEventArgs> ReceivedVideoData;
4445
public event EventHandler<SimpleDataEventArgs> ReceivedAudioData;
46+
public event EventHandler<EventArgs> Stopped;
4547

4648
public bool ProcessRTCP { get; set; } = true; // answer RTCP
4749
public event EventHandler<RawRtcpDataEventArgs> ReceivedRawVideoRTCP;
@@ -65,13 +67,13 @@ private enum RtspStatus { WaitingToConnect, Connecting, ConnectFailed, Connected
6567
private IRtpTransport _videoRtpTransport;
6668
private IRtpTransport _audioRtpTransport;
6769

68-
private Uri _uri; // RTSP URI (username & password will be stripped out)
70+
private Uri _uri = null; // RTSP URI (username & password will be stripped out)
6971
private string _session = ""; // RTSP Session
7072
private Authentication _authentication;
7173
private NetworkCredential _credentials = new NetworkCredential();
72-
private bool _clientWantsVideo = false; // Client wants to receive Video
73-
private bool _clientWantsAudio = false; // Client wants to receive Audio
74-
74+
private MediaRequest _mediaRequest = MediaRequest.VIDEO_AND_AUDIO;
75+
private RemoteCertificateValidationCallback _userCertificateSelectionCallback = null;
76+
private bool _autoReconnect = false;
7577
private Uri _videoUri = null; // URI used for the Video Track
7678
private int _videoPayload = -1; // Payload Type for the Video. (often 96 which is the first dynamic payload value. Bosch use 35)
7779

@@ -148,9 +150,21 @@ public RTSPClient(ILoggerFactory loggerFactory)
148150
/// <param name="mediaRequest">Media request type <see cref="MediaRequest>."/></param>
149151
/// <param name="playbackSession">Playback session.</param>
150152
/// <param name="userCertificateSelectionCallback">Callback for user certificate selection.</param>
151-
public void Connect(string url, RTPTransport rtpTransport, string username = null, string password = null, MediaRequest mediaRequest = MediaRequest.VIDEO_AND_AUDIO, bool playbackSession = false, System.Net.Security.RemoteCertificateValidationCallback userCertificateSelectionCallback = null)
153+
/// <param name="autoReconnect">Automatically try to reconnect after losing the connection.</param>
154+
public void Connect(
155+
string url,
156+
RTPTransport rtpTransport,
157+
string username = null,
158+
string password = null,
159+
MediaRequest mediaRequest = MediaRequest.VIDEO_AND_AUDIO,
160+
bool playbackSession = false,
161+
RemoteCertificateValidationCallback userCertificateSelectionCallback = null,
162+
bool autoReconnect = false)
152163
{
153-
Connect(new Uri(url), rtpTransport, username, password, mediaRequest, playbackSession, userCertificateSelectionCallback);
164+
if (string.IsNullOrEmpty(url))
165+
throw new ArgumentNullException(nameof(url));
166+
167+
Connect(new Uri(url), rtpTransport, username, password, mediaRequest, playbackSession, userCertificateSelectionCallback, autoReconnect);
154168
}
155169

156170
/// <summary>
@@ -163,40 +177,78 @@ public void Connect(string url, RTPTransport rtpTransport, string username = nul
163177
/// <param name="mediaRequest">Media request type <see cref="MediaRequest>."/></param>
164178
/// <param name="playbackSession">Playback session.</param>
165179
/// <param name="userCertificateSelectionCallback">Callback for user certificate selection.</param>
166-
public void Connect(Uri uri, RTPTransport rtpTransport, string username, string password, MediaRequest mediaRequest, bool playbackSession, System.Net.Security.RemoteCertificateValidationCallback userCertificateSelectionCallback)
180+
/// <param name="autoReconnect">Automatically try to reconnect after losing the connection.</param>
181+
public void Connect(
182+
Uri uri,
183+
RTPTransport rtpTransport,
184+
string username = null,
185+
string password = null,
186+
MediaRequest mediaRequest = MediaRequest.VIDEO_AND_AUDIO,
187+
bool playbackSession = false,
188+
RemoteCertificateValidationCallback userCertificateSelectionCallback = null,
189+
bool autoReconnect = false)
167190
{
168-
_logger.LogDebug("Connecting to {url} ", uri);
169-
170-
_uri = uri;
171-
172-
_playbackSession = playbackSession;
191+
if (uri == null)
192+
throw new ArgumentNullException(nameof(uri));
173193

174194
// Use URI to extract username and password and to make a new URL without the username and password
175-
var hostname = _uri.Host;
176-
var port = _uri.Port;
177-
try
195+
var hostname = uri.Host;
196+
var port = uri.Port;
197+
NetworkCredential credentials = null;
198+
199+
if (uri.UserInfo.Length > 0)
178200
{
179-
if (_uri.UserInfo.Length > 0)
180-
{
181-
_credentials = new NetworkCredential(_uri.UserInfo.Split(':')[0], _uri.UserInfo.Split(':')[1]);
182-
_uri = new Uri(_uri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.UserInfo, UriFormat.UriEscaped));
183-
}
184-
else
185-
{
186-
_credentials = new NetworkCredential(username, password);
187-
}
201+
credentials = new NetworkCredential(uri.UserInfo.Split(':')[0], uri.UserInfo.Split(':')[1]);
202+
uri = new Uri(uri.GetComponents(UriComponents.AbsoluteUri & ~UriComponents.UserInfo, UriFormat.UriEscaped));
188203
}
189-
catch
204+
else
190205
{
191-
_credentials = new NetworkCredential();
206+
credentials = new NetworkCredential(username, password);
192207
}
193208

209+
Connect(uri, rtpTransport, credentials, mediaRequest, playbackSession, userCertificateSelectionCallback, autoReconnect);
210+
}
211+
212+
/// <summary>
213+
/// Connects to the specified RTSP server.
214+
/// </summary>
215+
/// <param name="uri">The URI of the RTSP server.</param>
216+
/// <param name="rtpTransport">Type of the RTP transport <see cref="RTPTransport"/>.</param>
217+
/// <param name="credentials">Network credentials.</param>
218+
/// <param name="mediaRequest">Media request type <see cref="MediaRequest>."/></param>
219+
/// <param name="playbackSession">Playback session.</param>
220+
/// <param name="userCertificateSelectionCallback">Callback for user certificate selection.</param>
221+
/// <param name="autoReconnect">Automatically try to reconnect after losing the connection.</param>
222+
public void Connect(
223+
Uri uri,
224+
RTPTransport rtpTransport,
225+
NetworkCredential credentials = null,
226+
MediaRequest mediaRequest = MediaRequest.VIDEO_AND_AUDIO,
227+
bool playbackSession = false,
228+
RemoteCertificateValidationCallback userCertificateSelectionCallback = null,
229+
bool autoReconnect = false)
230+
{
231+
if (_rtspClient != null)
232+
throw new InvalidOperationException("You must first call Stop() before re-connecting!");
233+
234+
_logger.LogDebug("Connecting to {url} ", uri);
235+
236+
this._uri = uri;
237+
// Check the RTP Transport
238+
// If the RTP transport is TCP then we interleave the RTP packets in the RTSP stream
239+
// If the RTP transport is UDP, we initialise two UDP sockets (one for video, one for RTCP status messages)
240+
// If the RTP transport is MULTICAST, we have to wait for the SETUP message to get the Multicast Address from the RTSP server
241+
this._rtpTransport = rtpTransport;
242+
this._credentials = credentials ?? new NetworkCredential();
194243
// We can ask the RTSP server for Video, Audio or both. If we don't want audio we don't need to SETUP the audio channel or receive it
195-
_clientWantsVideo = (mediaRequest is MediaRequest.VIDEO_ONLY || mediaRequest is MediaRequest.VIDEO_AND_AUDIO);
196-
_clientWantsAudio = (mediaRequest is MediaRequest.AUDIO_ONLY || mediaRequest is MediaRequest.VIDEO_AND_AUDIO);
244+
this._mediaRequest = mediaRequest;
245+
this._playbackSession = playbackSession;
246+
this._userCertificateSelectionCallback = userCertificateSelectionCallback;
247+
this._autoReconnect = autoReconnect;
197248

198249
// Connect to a RTSP Server. The RTSP session is a TCP connection
199250
_rtspSocketStatus = RtspStatus.Connecting;
251+
200252
try
201253
{
202254
switch (_uri.Scheme)
@@ -210,7 +262,7 @@ public void Connect(Uri uri, RTPTransport rtpTransport, string username, string
210262

211263
default:
212264
{
213-
_rtspSocket = Rtsp.RtspUtils.CreateRtspTransportFromUrl(_uri, userCertificateSelectionCallback);
265+
_rtspSocket = Rtsp.RtspUtils.CreateRtspTransportFromUrl(_uri, _userCertificateSelectionCallback);
214266
}
215267
break;
216268
}
@@ -219,13 +271,15 @@ public void Connect(Uri uri, RTPTransport rtpTransport, string username, string
219271
{
220272
_rtspSocketStatus = RtspStatus.ConnectFailed;
221273
_logger.LogWarning("Error - did not connect");
274+
Stopped?.Invoke(this, EventArgs.Empty);
222275
return;
223276
}
224277

225278
if (!_rtspSocket.Connected)
226279
{
227280
_rtspSocketStatus = RtspStatus.ConnectFailed;
228281
_logger.LogWarning("Error - did not connect");
282+
Stopped?.Invoke(this, EventArgs.Empty);
229283
return;
230284
}
231285

@@ -234,22 +288,16 @@ public void Connect(Uri uri, RTPTransport rtpTransport, string username, string
234288
// Connect a RTSP Listener to the RTSP Socket (or other Stream) to send RTSP messages and listen for RTSP replies
235289
_rtspClient = new RtspListener(_rtspSocket, _loggerFactory.CreateLogger<RtspListener>())
236290
{
237-
AutoReconnect = false
291+
AutoReconnect = _autoReconnect
238292
};
239293

240294
_rtspClient.MessageReceived += RtspMessageReceived;
241295
_rtspClient.Start(); // start listening for messages from the server (messages fire the MessageReceived event)
242296

243-
// Check the RTP Transport
244-
// If the RTP transport is TCP then we interleave the RTP packets in the RTSP stream
245-
// If the RTP transport is UDP, we initialise two UDP sockets (one for video, one for RTCP status messages)
246-
// If the RTP transport is MULTICAST, we have to wait for the SETUP message to get the Multicast Address from the RTSP server
247-
this._rtpTransport = rtpTransport;
248-
249297
if (rtpTransport == RTPTransport.UDP)
250298
{
251299
// give a range of 500 pairs (1000 addresses) to try incase some address are in use
252-
_videoRtpTransport = new UDPSocket(50000, 51000);
300+
_videoRtpTransport = new UDPSocket(50000, 51000);
253301
_audioRtpTransport = new UDPSocket(50000, 51000);
254302
}
255303

@@ -285,6 +333,18 @@ public void Connect(Uri uri, RTPTransport rtpTransport, string username, string
285333
_rtspClient.SendMessage(optionsMessage);
286334
}
287335

336+
/// <summary>
337+
/// Attempt to reconnect when a connection to the server is lost.
338+
/// </summary>
339+
/// <exception cref="InvalidOperationException">Reconnect can only be called after calling Connect.</exception>
340+
public void TryReconnect()
341+
{
342+
if (_uri == null)
343+
throw new InvalidOperationException("You must first call Connect() before re-connecting!");
344+
345+
Connect(_uri, _rtpTransport, _credentials, _mediaRequest, _playbackSession, _userCertificateSelectionCallback, _autoReconnect);
346+
}
347+
288348
/// <summary>
289349
/// Returns true if this connection failed, or if it connected but is no longer connected.
290350
/// </summary>
@@ -398,6 +458,11 @@ public void Play(DateTime seekTimeFrom, DateTime seekTimeTo, double speed = 1.0)
398458
/// Stop playing.
399459
/// </summary>
400460
public void Stop()
461+
{
462+
StopClient();
463+
}
464+
465+
private void StopClient()
401466
{
402467
// Send TEARDOWN
403468
RtspRequest teardown_message = new RtspRequestTeardown
@@ -409,14 +474,43 @@ public void Stop()
409474
_rtspClient?.SendMessage(teardown_message);
410475

411476
// Stop the keepalive timer
412-
_keepaliveTimer?.Stop();
477+
var keepaliveTimer = _keepaliveTimer;
478+
if(keepaliveTimer != null)
479+
{
480+
keepaliveTimer.Elapsed -= SendKeepAlive;
481+
keepaliveTimer.Dispose();
482+
_keepaliveTimer = null;
483+
}
413484

414485
// clear up any UDP sockets
415-
_videoRtpTransport?.Stop();
416-
_audioRtpTransport?.Stop();
486+
var videoRtpTransport = _videoRtpTransport;
487+
if (videoRtpTransport != null)
488+
{
489+
videoRtpTransport.Stop();
490+
videoRtpTransport.DataReceived -= VideoRtpDataReceived;
491+
videoRtpTransport.ControlReceived -= VideoRtcpControlDataReceived;
492+
_videoRtpTransport = null;
493+
}
494+
495+
var audioRtpTransport = _audioRtpTransport;
496+
if (audioRtpTransport != null)
497+
{
498+
audioRtpTransport.Stop();
499+
audioRtpTransport.DataReceived -= AudioRtpDataReceived;
500+
audioRtpTransport.ControlReceived -= AudioRtcpControlDataReceived;
501+
_audioRtpTransport = null;
502+
}
417503

418504
// Drop the RTSP session
419-
_rtspClient?.Stop();
505+
var rtspClient = _rtspClient;
506+
if (rtspClient != null)
507+
{
508+
rtspClient.MessageReceived -= RtspMessageReceived;
509+
rtspClient.Stop();
510+
_rtspClient = null;
511+
}
512+
513+
_rtspSocket = null; // closed by rtspClient.Stop()
420514
}
421515

422516
/// <summary>
@@ -705,8 +799,9 @@ private void RtspMessageReceived(object sender, RtspChunkEventArgs e)
705799

706800
if (message.ReturnCode == 401 && message.OriginalRequest?.Headers.ContainsKey(RtspHeaderNames.Authorization) == true)
707801
{
708-
_logger.LogError("Fail to authenticate stoping here");
709-
Stop();
802+
_logger.LogError("Fail to authenticate stopping here");
803+
StopClient();
804+
Stopped?.Invoke(this, EventArgs.Empty);
710805
return;
711806
}
712807

@@ -911,7 +1006,7 @@ private void HandleDescribeResponse(RtspResponse message)
9111006

9121007
// Process each 'Media' Attribute in the SDP (each sub-stream)
9131008
// to look for first supported video substream
914-
if (_clientWantsVideo)
1009+
if (_mediaRequest is MediaRequest.VIDEO_ONLY || _mediaRequest is MediaRequest.VIDEO_AND_AUDIO)
9151010
{
9161011
foreach (Media media in sdpData.Medias.Where(m => m.MediaType == Media.MediaTypes.video))
9171012
{
@@ -1057,7 +1152,7 @@ private void HandleDescribeResponse(RtspResponse message)
10571152
}
10581153
}
10591154

1060-
if (_clientWantsAudio)
1155+
if (_mediaRequest is MediaRequest.AUDIO_ONLY || _mediaRequest is MediaRequest.VIDEO_AND_AUDIO)
10611156
{
10621157
foreach (Media media in sdpData.Medias.Where(m => m.MediaType == Media.MediaTypes.audio))
10631158
{
@@ -1262,11 +1357,7 @@ protected virtual void Dispose(bool disposing)
12621357
{
12631358
if (disposing)
12641359
{
1265-
Stop();
1266-
1267-
_rtspClient?.Dispose();
1268-
_videoRtpTransport?.Dispose();
1269-
_audioRtpTransport?.Dispose();
1360+
StopClient();
12701361
}
12711362

12721363
_disposedValue = true;

0 commit comments

Comments
 (0)