[Multimedia] Deprecate constructors related to ElmSharp (#4540)
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.Remoting / WebRTC / WebRTC.cs
1 /*
2  * Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an AS IS BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 using System;
18 using System.Collections.ObjectModel;
19 using System.Collections.Generic;
20 using System.ComponentModel;
21 using System.Diagnostics;
22 using System.Runtime.InteropServices;
23 using System.Threading;
24 using System.Threading.Tasks;
25 using Tizen.Applications;
26 using static Interop;
27
28 namespace Tizen.Multimedia.Remoting
29 {
30     internal static class WebRTCLog
31     {
32         internal const string Tag = "Tizen.Multimedia.WebRTC";
33     }
34
35     /// <summary>
36     /// Provides the ability to control WebRTC.
37     /// </summary>
38     /// <since_tizen> 9 </since_tizen>
39     public partial class WebRTC : IDisposable
40     {
41         private readonly WebRTCHandle _handle;
42         private List<MediaSource> _source;
43
44         /// <summary>
45         /// Initializes a new instance of the <see cref="WebRTC"/> class.
46         /// </summary>
47         /// <feature>http://tizen.org/feature/network.wifi</feature>
48         /// <feature>http://tizen.org/feature/network.telephony</feature>
49         /// <feature>http://tizen.org/feature/network.ethernet</feature>
50         /// <privilege>http://tizen.org/privilege/internet</privilege>
51         /// <exception cref="UnauthorizedAccessException">Thrown when the permission is denied.</exception>
52         /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
53         /// <since_tizen> 9 </since_tizen>
54         public WebRTC()
55         {
56             if (!Features.IsSupported(WebRTCFeatures.Wifi) &&
57                 !Features.IsSupported(WebRTCFeatures.Telephony) &&
58                 !Features.IsSupported(WebRTCFeatures.Ethernet))
59             {
60                 throw new NotSupportedException("Network features are not supported.");
61             }
62
63             NativeWebRTC.Create(out _handle).ThrowIfFailed("Failed to create webrtc");
64
65             Debug.Assert(_handle != null);
66
67             RegisterEvents();
68
69             _source = new List<MediaSource>();
70         }
71
72         internal void ValidateWebRTCState(params WebRTCState[] desiredStates)
73         {
74             Debug.Assert(desiredStates.Length > 0);
75
76             ValidateNotDisposed();
77
78             WebRTCState curState = State;
79             if (!curState.IsAnyOf(desiredStates))
80             {
81                 throw new InvalidOperationException("The WebRTC is not in a valid state. " +
82                     $"Current State : { curState }, Valid State : { string.Join(", ", desiredStates) }.");
83             }
84         }
85
86         #region Dispose support
87         private bool _disposed;
88
89         /// <summary>
90         /// Releases all resources used by the current instance.
91         /// </summary>
92         /// <since_tizen> 9 </since_tizen>
93         public void Dispose()
94         {
95             Dispose(true);
96             GC.SuppressFinalize(this);
97         }
98
99         /// <summary>
100         /// Releases the unmanaged resources used by the <see cref="WebRTC"/>.
101         /// </summary>
102         /// <param name="disposing">
103         /// true to release both managed and unmanaged resources;
104         /// false to release only unmanaged resources.
105         /// </param>
106         [EditorBrowsable(EditorBrowsableState.Never)]
107         protected virtual void Dispose(bool disposing)
108         {
109             if (_disposed || !disposing)
110                 return;
111
112             if (_handle != null)
113             {
114                 _handle.Dispose();
115                 _disposed = true;
116             }
117         }
118
119         internal void ValidateNotDisposed()
120         {
121             if (_disposed)
122             {
123                 Log.Warn(WebRTCLog.Tag, "WebRTC was disposed");
124                 throw new ObjectDisposedException(nameof(WebRTC));
125             }
126         }
127
128         internal bool IsDisposed => _disposed;
129         #endregion
130
131         /// <summary>
132         /// Starts the WebRTC.
133         /// </summary>
134         /// <remarks>
135         /// The WebRTC must be in the <see cref="WebRTCState.Idle"/> state.<br/>
136         /// The WebRTC state will be <see cref="WebRTCState.Negotiating"/> state.<br/>
137         /// The user should check whether <see cref="State" /> is changed to <see cref="WebRTCState.Negotiating"/> state or not.
138         /// </remarks>
139         /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
140         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
141         /// <see also="WebRTCState"/>
142         /// <see also="StateChanged"/>
143         /// <see also="CreateOffer"/>
144         /// <since_tizen> 9 </since_tizen>
145         public void Start()
146         {
147             ValidateWebRTCState(WebRTCState.Idle);
148
149             NativeWebRTC.Start(Handle).ThrowIfFailed("Failed to start the WebRTC");
150         }
151
152         /// <summary>
153         /// Starts the WebRTC asynchronously.
154         /// </summary>
155         /// <remarks>
156         /// The WebRTC must be in the <see cref="WebRTCState.Idle"/> state.<br/>
157         /// The WebRTC state will be <see cref="WebRTCState.Negotiating"/> state.<br/>
158         /// This ensures that <see cref="State" /> is changed to <see cref="WebRTCState.Negotiating"/> state.
159         /// </remarks>
160         /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
161         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
162         /// <see also="WebRTCState"/>
163         /// <see also="CreateOffer"/>
164         /// <since_tizen> 9 </since_tizen>
165         public async Task StartAsync()
166         {
167             ValidateWebRTCState(WebRTCState.Idle);
168
169             var tcs = new TaskCompletionSource<bool>();
170             var error = WebRTCError.ConnectionFailed;
171
172             EventHandler<WebRTCStateChangedEventArgs> stateChangedEventHandler = (s, e) =>
173             {
174                 if (e.Current == WebRTCState.Negotiating)
175                 {
176                     tcs.TrySetResult(true);
177                 }
178             };
179             EventHandler<WebRTCErrorOccurredEventArgs> errorEventHandler = (s, e) =>
180             {
181                 Log.Info(WebRTCLog.Tag, e.ToString());
182                 error = e.Error;
183                 tcs.TrySetResult(false);
184             };
185
186             try
187             {
188                 StateChanged += stateChangedEventHandler;
189                 ErrorOccurred += errorEventHandler;
190
191                 NativeWebRTC.Start(Handle).ThrowIfFailed("Failed to start the WebRTC");
192
193                 var result = await tcs.Task.ConfigureAwait(false);
194                 await Task.Yield();
195
196                 if (!result)
197                 {
198                     throw new InvalidOperationException(error.ToString());
199                 }
200             }
201             finally
202             {
203                 StateChanged -= stateChangedEventHandler;
204                 ErrorOccurred -= errorEventHandler;
205             }
206         }
207
208         /// <summary>
209         /// Stops the WebRTC.
210         /// </summary>
211         /// <remarks>
212         /// The WebRTC must be in the <see cref="WebRTCState.Negotiating"/> or <see cref="WebRTCState.Playing"/> state.<br/>
213         /// The WebRTC state will be <see cref="WebRTCState.Idle"/> state.<br/>
214         /// The user should check whether <see cref="State" /> is changed to <see cref="WebRTCState.Idle"/> state or not.
215         /// </remarks>
216         /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
217         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
218         /// <since_tizen> 9 </since_tizen>
219         public void Stop()
220         {
221             ValidateWebRTCState(WebRTCState.Negotiating, WebRTCState.Playing);
222
223             NativeWebRTC.Stop(Handle).ThrowIfFailed("Failed to stop the WebRTC");
224         }
225
226         /// <summary>
227         /// Creates SDP offer asynchronously to start a new WebRTC connection to a remote peer.
228         /// </summary>
229         /// <remarks>The WebRTC must be in the <see cref="WebRTCState.Negotiating"/></remarks>
230         /// <returns>The SDP offer.</returns>
231         /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
232         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
233         /// <seealso cref="CreateAnswerAsync()"/>
234         /// <since_tizen> 9 </since_tizen>
235         public async Task<string> CreateOfferAsync()
236         {
237             ValidateWebRTCState(WebRTCState.Negotiating);
238
239             var tcsSdpCreated = new TaskCompletionSource<string>();
240
241             NativeWebRTC.SdpCreatedCallback cb = (handle, sdp, _) =>
242             {
243                 tcsSdpCreated.TrySetResult(sdp);
244             };
245
246             string offer = null;
247             using (var cbKeeper = ObjectKeeper.Get(cb))
248             {
249                 NativeWebRTC.CreateSDPOfferAsync(Handle, new SafeBundleHandle(), cb, IntPtr.Zero).
250                     ThrowIfFailed("Failed to create offer asynchronously");
251
252                 offer = await tcsSdpCreated.Task.ConfigureAwait(false);
253                 await Task.Yield();
254             }
255
256             return offer;
257         }
258
259         /// <summary>
260         /// Creates SDP answer asynchronously with option to an offer received from a remote peer.
261         /// </summary>
262         /// <remarks>The WebRTC must be in the <see cref="WebRTCState.Negotiating"/></remarks>
263         /// <returns>The SDP answer.</returns>
264         /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
265         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
266         /// <seealso cref="CreateOfferAsync()"/>
267         /// <since_tizen> 9 </since_tizen>
268         public async Task<string> CreateAnswerAsync()
269         {
270             ValidateWebRTCState(WebRTCState.Negotiating);
271
272             var tcsSdpCreated = new TaskCompletionSource<string>();
273
274             NativeWebRTC.SdpCreatedCallback cb = (handle, sdp, _) =>
275             {
276                 tcsSdpCreated.TrySetResult(sdp);
277             };
278
279             string answer = null;
280             using (var cbKeeper = ObjectKeeper.Get(cb))
281             {
282                 NativeWebRTC.CreateSDPAnswerAsync(Handle, new SafeBundleHandle(), cb, IntPtr.Zero).
283                     ThrowIfFailed("Failed to create answer asynchronously");
284
285                 answer = await tcsSdpCreated.Task.ConfigureAwait(false);
286                 await Task.Yield();
287             }
288
289             return answer;
290         }
291
292         /// <summary>
293         /// Sets the session description for a local peer.
294         /// </summary>
295         /// <remarks>The WebRTC must be in the <see cref="WebRTCState.Negotiating"/>.</remarks>
296         /// <param name="description">The local session description.</param>
297         /// <exception cref="ArgumentException">The description is empty string.</exception>
298         /// <exception cref="ArgumentNullException">The description is null.</exception>
299         /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
300         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
301         /// <seealso cref="CreateOfferAsync()"/>
302         /// <seealso cref="CreateAnswerAsync()"/>
303         /// <since_tizen> 9 </since_tizen>
304         public void SetLocalDescription(string description)
305         {
306             ValidateWebRTCState(WebRTCState.Negotiating);
307
308             ValidationUtil.ValidateIsNullOrEmpty(description, nameof(description));
309
310             NativeWebRTC.SetLocalDescription(Handle, description).ThrowIfFailed("Failed to set description.");
311         }
312
313         /// <summary>
314         /// Sets the session description of the remote peer's current offer or answer.
315         /// </summary>
316         /// <remarks>The WebRTC must be in the <see cref="WebRTCState.Negotiating"/>.</remarks>
317         /// <param name="description">The remote session description.</param>
318         /// <exception cref="ArgumentException">The description is empty string.</exception>
319         /// <exception cref="ArgumentNullException">The description is null.</exception>
320         /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
321         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
322         /// <seealso cref="CreateOfferAsync()"/>
323         /// <seealso cref="CreateAnswerAsync()"/>
324         /// <since_tizen> 9 </since_tizen>
325         public void SetRemoteDescription(string description)
326         {
327             ValidateWebRTCState(WebRTCState.Negotiating);
328
329             ValidationUtil.ValidateIsNullOrEmpty(description, nameof(description));
330
331             NativeWebRTC.SetRemoteDescription(Handle, description).ThrowIfFailed("Failed to set description.");
332         }
333
334         /// <summary>
335         /// Adds a new ICE candidate from the remote peer over its signaling channel.
336         /// </summary>
337         /// <remarks>The WebRTC must be in the <see cref="WebRTCState.Negotiating"/>.</remarks>
338         /// <param name="iceCandidate">The ICE candidate.</param>
339         /// <exception cref="ArgumentException">The ICE candidate is empty string.</exception>
340         /// <exception cref="ArgumentNullException">The ICE candidate is null.</exception>
341         /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
342         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
343         /// <since_tizen> 9 </since_tizen>
344         public void AddIceCandidate(string iceCandidate)
345         {
346             ValidateWebRTCState(WebRTCState.Negotiating);
347
348             ValidationUtil.ValidateIsNullOrEmpty(iceCandidate, nameof(iceCandidate));
349
350             NativeWebRTC.AddIceCandidate(Handle, iceCandidate).ThrowIfFailed("Failed to set ICE candidate.");
351         }
352
353         /// <summary>
354         /// Adds new ICE candidates from the remote peer over its signaling channel.
355         /// </summary>
356         /// <remarks>The WebRTC must be in the <see cref="WebRTCState.Negotiating"/>.</remarks>
357         /// <param name="iceCandidates">The ICE candidates.</param>
358         /// <exception cref="ArgumentException">The ICE candidate is empty string.</exception>
359         /// <exception cref="ArgumentNullException">The ICE candidate is null.</exception>
360         /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
361         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
362         /// <since_tizen> 9 </since_tizen>
363         public void AddIceCandidates(IEnumerable<string> iceCandidates)
364         {
365             ValidateWebRTCState(WebRTCState.Negotiating);
366
367             ValidationUtil.ValidateIsAny(iceCandidates);
368
369             #pragma warning disable CA1062
370             foreach (string iceCandidate in iceCandidates)
371             {
372                 AddIceCandidate(iceCandidate);
373             }
374             #pragma warning restore CA1062
375         }
376
377         /// <summary>
378         /// Adds media source.
379         /// </summary>
380         /// <remarks>
381         /// The WebRTC must be in the <see cref="WebRTCState.Idle"/>.<br/>
382         /// Each MediaSource requires different feature or privilege.<br/>
383         /// <see cref="MediaCameraSource"/> needs camera feature and privilege.<br/>
384         /// <see cref="MediaMicrophoneSource"/> needs microphone feature and recorder privilege.<br/>
385         /// </remarks>
386         /// <param name="source">The media sources to add.</param>
387         /// <feature>http://tizen.org/feature/camera</feature>
388         /// <feature>http://tizen.org/feature/microphone</feature>
389         /// <privilege>http://tizen.org/privilege/camera</privilege>
390         /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
391         /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
392         /// <privilege>http://tizen.org/privilege/recorder</privilege>
393         /// <exception cref="ArgumentNullException">The media source is null.</exception>
394         /// <exception cref="InvalidOperationException">
395         /// The WebRTC is not in the valid state.<br/>
396         /// - or -<br/>
397         /// All or one of <paramref name="source"/> was already detached.
398         /// </exception>
399         /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
400         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
401         /// <exception cref="UnauthorizedAccessException">Thrown when the permission is denied.</exception>
402         /// <seealso cref="MediaCameraSource"/>
403         /// <seealso cref="MediaMicrophoneSource"/>
404         /// <seealso cref="MediaTestSource"/>
405         /// <seealso cref="MediaPacketSource"/>
406         /// <seealso cref="AddSources"/>
407         /// <seealso cref="RemoveSource"/>
408         /// <seealso cref="RemoveSources"/>
409         /// <since_tizen> 9 </since_tizen>
410         public void AddSource(MediaSource source)
411         {
412             if (source == null)
413             {
414                 throw new ArgumentNullException(nameof(source), "source is null");
415             }
416
417             ValidateWebRTCState(WebRTCState.Idle);
418
419             source?.AttachTo(this);
420
421             _source.Add(source);
422         }
423
424         /// <summary>
425         /// Adds media sources.
426         /// </summary>
427         /// <remarks>
428         /// The WebRTC must be in the <see cref="WebRTCState.Idle"/>.<br/>
429         /// Each MediaSource requires different feature or privilege.<br/>
430         /// <see cref="MediaCameraSource"/> needs camera feature and privilege.<br/>
431         /// <see cref="MediaMicrophoneSource"/> needs microphone feature and recorder privilege.<br/>
432         /// </remarks>
433         /// <param name="sources">The media sources to add.</param>
434         /// <feature>http://tizen.org/feature/camera</feature>
435         /// <feature>http://tizen.org/feature/microphone</feature>
436         /// <privilege>http://tizen.org/privilege/camera</privilege>
437         /// <privilege>http://tizen.org/privilege/mediastorage</privilege>
438         /// <privilege>http://tizen.org/privilege/externalstorage</privilege>
439         /// <privilege>http://tizen.org/privilege/recorder</privilege>
440         /// <exception cref="ArgumentNullException">The media source is null.</exception>
441         /// <exception cref="InvalidOperationException">
442         /// The WebRTC is not in the valid state.<br/>
443         /// - or -<br/>
444         /// All or one of <paramref name="sources"/> was already detached.
445         /// </exception>
446         /// <exception cref="NotSupportedException">The required feature is not supported.</exception>
447         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
448         /// <exception cref="UnauthorizedAccessException">Thrown when the permission is denied.</exception>
449         /// <seealso cref="MediaCameraSource"/>
450         /// <seealso cref="MediaMicrophoneSource"/>
451         /// <seealso cref="MediaTestSource"/>
452         /// <seealso cref="MediaPacketSource"/>
453         /// <seealso cref="AddSource"/>
454         /// <seealso cref="RemoveSource"/>
455         /// <seealso cref="RemoveSources"/>
456         /// <since_tizen> 9 </since_tizen>
457         public void AddSources(params MediaSource[] sources)
458         {
459             if (sources == null)
460             {
461                 throw new ArgumentNullException(nameof(sources), "sources are null");
462             }
463
464             foreach (var source in sources)
465             {
466                 AddSource(source);
467             }
468         }
469
470         /// <summary>
471         /// Removes media source.
472         /// </summary>
473         /// <remarks>
474         /// The WebRTC must be in the <see cref="WebRTCState.Idle"/>.<br/>
475         /// If user want to use removed MediaSource again, user should create new instance for it.
476         /// </remarks>
477         /// <param name="source">The media source to remove.</param>
478         /// <exception cref="ArgumentNullException">The media source is null.</exception>
479         /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
480         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
481         /// <seealso cref="MediaCameraSource"/>
482         /// <seealso cref="MediaMicrophoneSource"/>
483         /// <seealso cref="MediaTestSource"/>
484         /// <seealso cref="MediaPacketSource"/>
485         /// <seealso cref="AddSource"/>
486         /// <seealso cref="AddSources"/>
487         /// <seealso cref="RemoveSources"/>
488         /// <since_tizen> 9 </since_tizen>
489         public void RemoveSource(MediaSource source)
490         {
491             if (source == null)
492             {
493                 throw new ArgumentNullException(nameof(source), "source is null");
494             }
495
496             ValidateWebRTCState(WebRTCState.Idle);
497
498             source?.DetachFrom(this);
499
500             _source.Remove(source);
501
502             source = null;
503         }
504
505         /// <summary>
506         /// Removes media sources.
507         /// </summary>
508         /// <remarks>
509         /// The WebRTC must be in the <see cref="WebRTCState.Idle"/>.<br/>
510         /// If user want to use removed MediaSource again, user should create new instance for it.
511         /// </remarks>
512         /// <param name="sources">The media source to remove.</param>
513         /// <exception cref="ArgumentNullException">The media source is null.</exception>
514         /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
515         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
516         /// <seealso cref="MediaCameraSource"/>
517         /// <seealso cref="MediaMicrophoneSource"/>
518         /// <seealso cref="MediaTestSource"/>
519         /// <seealso cref="MediaPacketSource"/>
520         /// <seealso cref="AddSource"/>
521         /// <seealso cref="AddSources"/>
522         /// <seealso cref="RemoveSource"/>
523         /// <since_tizen> 9 </since_tizen>
524         public void RemoveSources(params MediaSource[] sources)
525         {
526             foreach (var source in sources)
527             {
528                 RemoveSource(source);
529             }
530         }
531
532         /// <summary>
533         /// Sets a turn server.
534         /// </summary>
535         /// <exception cref="ArgumentNullException">The <paramref name="turnServer"/> is null.</exception>
536         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
537         /// <since_tizen> 9 </since_tizen>
538         public void SetTurnServer(string turnServer)
539         {
540             ValidateNotDisposed();
541
542             if (turnServer == null)
543             {
544                 throw new ArgumentNullException(nameof(turnServer), "Turn server name is null.");
545             }
546
547             NativeWebRTC.AddTurnServer(Handle, turnServer).
548                 ThrowIfFailed("Failed to add turn server");
549         }
550
551         /// <summary>
552         /// Sets turn servers.
553         /// </summary>
554         /// <exception cref="ArgumentNullException">The one of <paramref name="turnServers"/> is null.</exception>
555         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
556         /// <since_tizen> 9 </since_tizen>
557         public void SetTurnServers(params string[] turnServers)
558         {
559             ValidateNotDisposed();
560
561             if (turnServers == null)
562             {
563                 throw new ArgumentNullException(nameof(turnServers), "Turn server names are null.");
564             }
565
566             foreach (var turnServer in turnServers)
567             {
568                 SetTurnServer(turnServer);
569             }
570         }
571
572         /// <summary>
573         /// Retrieves all turn servers.
574         /// </summary>
575         /// <returns>The turn server list.</returns>
576         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
577         /// <since_tizen> 9 </since_tizen>
578         public ReadOnlyCollection<string> GetTurnServer()
579         {
580             ValidateNotDisposed();
581
582             var list = new List<string>();
583
584             NativeWebRTC.RetrieveTurnServerCallback cb = (server, _) =>
585             {
586                 if (!string.IsNullOrWhiteSpace(server))
587                 {
588                     list.Add(server);
589                 }
590
591                 return true;
592             };
593
594             NativeWebRTC.ForeachTurnServer(Handle, cb).ThrowIfFailed("Failed to retrieve turn server");
595
596             return list.AsReadOnly();
597         }
598
599         /// <summary>
600         /// Retrieves the current statistics information.
601         /// </summary>
602         /// <remarks>The WebRTC must be in the <see cref="WebRTCState.Playing"/></remarks>
603         /// <returns>The WebRTC statistics informations.</returns>
604         /// <param name="category">The category of statistics to get.</param>
605         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
606         /// <exception cref="InvalidOperationException">The WebRTC is not in the valid state.</exception>
607         /// <since_tizen> 10 </since_tizen>
608         public ReadOnlyCollection<WebRTCStatistics> GetStatistics(WebRTCStatisticsCategory category)
609         {
610             ValidateWebRTCState(WebRTCState.Playing);
611
612             var stats = new List<WebRTCStatistics>();
613             Exception caught = null;
614
615             NativeWebRTC.RetrieveStatsCallback cb = (category_, prop, _) =>
616             {
617                 try
618                 {
619                     stats.Add(new WebRTCStatistics(category_, prop));
620                 }
621                 catch (Exception e)
622                 {
623                     caught = e;
624                     return false;
625                 }
626
627                 return true;
628             };
629
630             using (var cbKeeper = ObjectKeeper.Get(cb))
631             {
632                 NativeWebRTC.ForeachStats(Handle, (int)category, cb, IntPtr.Zero).
633                     ThrowIfFailed("failed to retrieve stats");
634
635                 if (caught != null)
636                 {
637                     throw caught;
638                 }
639             }
640
641             return new ReadOnlyCollection<WebRTCStatistics>(stats);
642         }
643
644         /// <summary>
645         /// Represents WebRTC statistics information.
646         /// </summary>
647         /// <since_tizen> 10 </since_tizen>
648         public class WebRTCStatistics
649         {
650             internal WebRTCStatistics(WebRTCStatisticsCategory type, IntPtr prop)
651             {
652                 var unmanagedStruct = Marshal.PtrToStructure<NativeWebRTC.StatsPropertyStruct>(prop);
653
654                 Category = type;
655                 Name = unmanagedStruct.name;
656                 Property = unmanagedStruct.property;
657
658                 switch (unmanagedStruct.propertyType)
659                 {
660                     case WebRTCStatsPropertyType.TypeBool:
661                         Value = unmanagedStruct.value.@bool;
662                         break;
663                     case WebRTCStatsPropertyType.TypeInt:
664                         Value = unmanagedStruct.value.@int;
665                         break;
666                     case WebRTCStatsPropertyType.TypeUint:
667                         Value = unmanagedStruct.value.@uint;
668                         break;
669                     case WebRTCStatsPropertyType.TypeInt64:
670                         Value = unmanagedStruct.value.@long;
671                         break;
672                     case WebRTCStatsPropertyType.TypeUint64:
673                         Value = unmanagedStruct.value.@ulong;
674                         break;
675                     case WebRTCStatsPropertyType.TypeFloat:
676                         Value = unmanagedStruct.value.@float;
677                         break;
678                     case WebRTCStatsPropertyType.TypeDouble:
679                         Value = unmanagedStruct.value.@double;
680                         break;
681                     case WebRTCStatsPropertyType.TypeString:
682                         Value = Marshal.PtrToStringAnsi(unmanagedStruct.value.@string);
683                         break;
684                     default:
685                         throw new InvalidOperationException($"No matching type [{unmanagedStruct.propertyType}]");
686                 }
687             }
688
689             /// <summary>
690             /// Gets the category of statistics.
691             /// </summary>
692             /// <value>The category of WebRTC statistics information</value>
693             /// <since_tizen> 10 </since_tizen>
694             public WebRTCStatisticsCategory Category { get; }
695
696             /// <summary>
697             /// Gets the name of statistics.
698             /// </summary>
699             /// <value>The name of WebRTC statistics information</value>
700             /// <since_tizen> 10 </since_tizen>
701             public string Name { get; }
702
703             /// <summary>
704             /// Gets the property of statistics.
705             /// </summary>
706             /// <value>The property of WebRTC statistics information</value>
707             /// <since_tizen> 10 </since_tizen>
708             public WebRTCStatisticsProperty Property { get; }
709
710             /// <summary>
711             /// Gets the value of statistics.
712             /// </summary>
713             /// <value>The value of WebRTC statistics information</value>
714             /// <since_tizen> 10 </since_tizen>
715             public object Value { get; }
716
717             /// <summary>
718             /// Returns a string that represents the current object.
719             /// </summary>
720             /// <returns>A string that represents the current object.</returns>
721             /// <since_tizen> 10 </since_tizen>
722             public override string ToString() =>
723                 $"Category={Category}, Name={Name}, Property={Property}, Value={Value}, Type={Value.GetType()}";
724         }
725     }
726 }