df7f9d62f160ae3057b536297c16ae4e7c0dbf35
[platform/core/csapi/tizenfx.git] / src / Tizen.Multimedia.Remoting / WebRTC / MediaSource.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.Generic;
19 using System.Collections.ObjectModel;
20 using System.Diagnostics;
21 using static Interop;
22
23 namespace Tizen.Multimedia.Remoting
24 {
25     /// <summary>
26     /// MediaSource is a base class for <see cref="WebRTC"/> sources.
27     /// </summary>
28     /// <since_tizen> 9 </since_tizen>
29     public abstract class MediaSource : IDisplayable<uint>
30     {
31         internal WebRTC WebRtc { get; set; }
32         internal uint? SourceId { get; set; }
33         private Display _display;
34
35         /// <summary>
36         /// Gets the type of MediaSource.
37         /// </summary>
38         /// <value><see cref="MediaType"/></value>
39         /// <since_tizen> 9 </since_tizen>
40         protected MediaType MediaType { get; }
41
42         private bool IsDetached {get; set;} = false;
43
44         /// <summary>
45         /// Initializes a new instance of the <see cref="MediaSource"/> class.
46         /// </summary>
47         /// <since_tizen> 9 </since_tizen>
48         protected MediaSource(MediaType mediaType)
49         {
50             MediaType = mediaType;
51         }
52
53         /// <summary>
54         /// Initializes a new instance of the <see cref="MediaSource"/> class.
55         /// </summary>
56         /// <since_tizen> 10 </since_tizen>
57         protected MediaSource() { }
58
59         internal void AttachTo(WebRTC webRtc)
60         {
61             if (IsDetached)
62             {
63                 throw new InvalidOperationException("MediaSource was already detached.");
64             }
65
66             OnAttached(webRtc);
67         }
68
69         internal void DetachFrom(WebRTC webRtc)
70         {
71             OnDetached(webRtc);
72             IsDetached = true;
73         }
74
75         internal abstract void OnAttached(WebRTC webRtc);
76
77         internal abstract void OnDetached(WebRTC webRtc);
78
79         internal virtual MediaSourceType MediaSourceType => MediaSourceType.Null;
80
81         /// <summary>
82         /// Gets or sets the transceiver direction of current media source.
83         /// </summary>
84         /// <remarks>
85         /// The default value is <see cref="TransceiverDirection.SendRecv"/> except <see cref="MediaNullSource"/>.<br/>
86         /// If user want to set each audio, video direction in <see cref="MediaFileSource"/>,
87         /// please use <see cref="MediaFileSource.SetTransceiverDirection"/>. (Since API level 10)<br/>
88         /// In <see cref="MediaNullSource"/>, only <see cref="TransceiverDirection.RecvOnly"/> can be set.(Since API level 10)
89         /// </remarks>
90         /// <value>A <see cref="TransceiverDirection"/> that specifies the transceiver direction.</value>
91         /// <exception cref="InvalidOperationException">
92         ///     MediaSource is not attached yet.<br/>
93         /// -or-<br/>
94         ///     <see cref="TransceiverDirection.SendOnly"/> or <see cref="TransceiverDirection.SendRecv"/> is set for MediaNullSource. (Since API level 10)
95         /// </exception>
96         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
97         /// <seealso cref="MediaFileSource.GetTransceiverDirection"/>
98         /// <seealso cref="MediaFileSource.SetTransceiverDirection"/>
99         /// <since_tizen> 9 </since_tizen>
100         public TransceiverDirection TransceiverDirection
101         {
102             get
103             {
104                 if (!SourceId.HasValue)
105                 {
106                     throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
107                 }
108                 if (this is MediaNullSource)
109                 {
110                     return TransceiverDirection.RecvOnly;
111                 }
112
113                 NativeWebRTC.GetTransceiverDirection(WebRtc.Handle, SourceId.Value, MediaType, out TransceiverDirection mode).
114                     ThrowIfFailed("Failed to get transceiver direction.");
115
116                 return mode;
117             }
118             set
119             {
120                 if (!SourceId.HasValue)
121                 {
122                     throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
123                 }
124                 if (this is MediaNullSource)
125                 {
126                     if (value != TransceiverDirection.RecvOnly)
127                     {
128                         throw new InvalidOperationException("Only RecvOnly is allowed for MediaNullSource.");
129                     }
130
131                     return;
132                 }
133
134                 if (this is MediaNullSource || this is MediaFileSource)
135                 {
136                     NativeWebRTC.SetTransceiverDirection(WebRtc.Handle, SourceId.Value, MediaType.Audio, value).
137                         ThrowIfFailed("Failed to set audio transceiver direction.");
138                     NativeWebRTC.SetTransceiverDirection(WebRtc.Handle, SourceId.Value, MediaType.Video, value).
139                         ThrowIfFailed("Failed to set video transceiver direction.");
140                 }
141                 else
142                 {
143                     NativeWebRTC.SetTransceiverDirection(WebRtc.Handle, SourceId.Value, MediaType, value).
144                         ThrowIfFailed("Failed to set transceiver direction.");
145                 }
146             }
147         }
148
149         /// <summary>
150         /// Gets or sets the transceiver codec of current media source.
151         /// </summary>
152         /// <remarks>
153         /// This API is not supported in <see cref="MediaFileSource"/>, <see cref="MediaPacketSource"/>.<br/>
154         /// If <see cref="MediaNullSource"/>, please use <see cref="MediaNullSource.GetTransceiverCodec"/>
155         /// or <see cref="MediaNullSource.SetTransceiverCodec"/> instead.<br/>
156         /// The WebRTC must be in the <see cref="WebRTCState.Idle"/> state when transceiver codec is set.
157         /// </remarks>
158         /// <value>The transceiver codec.</value>
159         /// <exception cref="InvalidOperationException">
160         ///     MediaSource is not attached yet.<br/>
161         /// -or-<br/>
162         ///     This MediaSource is not supported type of MediaSource.<br/>
163         /// -or-<br/>
164         /// The WebRTC is not in the valid state.
165         /// </exception>
166         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
167         /// <seealso cref="MediaNullSource.GetTransceiverCodec"/>
168         /// <seealso cref="MediaNullSource.SetTransceiverCodec"/>
169         /// <since_tizen> 10 </since_tizen>
170         public TransceiverCodec TransceiverCodec
171         {
172             get
173             {
174                 if (!SourceId.HasValue)
175                 {
176                     throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
177                 }
178                 if (this is MediaFileSource || this is MediaPacketSource || this is MediaNullSource)
179                 {
180                     throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
181                 }
182
183                 NativeWebRTC.GetTransceiverCodec(WebRtc.Handle, SourceId.Value, MediaType, out TransceiverCodec codec).
184                     ThrowIfFailed("Failed to get transceiver codec");
185
186                 return codec;
187             }
188             set
189             {
190                 if (!SourceId.HasValue)
191                 {
192                     throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
193                 }
194                 if (this is MediaFileSource || this is MediaPacketSource || this is MediaNullSource)
195                 {
196                     throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
197                 }
198
199                 WebRtc.ValidateWebRTCState(WebRTCState.Idle);
200
201                 NativeWebRTC.SetTransceiverCodec(WebRtc.Handle, SourceId.Value, MediaType, value).
202                     ThrowIfFailed("Failed to set transceiver codec");
203             }
204         }
205
206         /// <summary>
207         /// Retrieves the supported transceiver codecs.
208         /// </summary>
209         /// <remarks>
210         /// This API is not supported in <see cref="MediaFileSource"/>, <see cref="MediaPacketSource"/>.<br/>
211         /// If user want to get supported codecs for each audio or video in <see cref="MediaNullSource"/>,
212         /// please use <see cref="MediaNullSource.GetSupportedTransceiverCodecs"/> instead.
213         /// </remarks>
214         /// <returns>The transceiver codecs.</returns>
215         /// <exception cref="InvalidOperationException">This MediaSource is not supported type of MediaSource.</exception>
216         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
217         /// <seealso cref="MediaNullSource.GetSupportedTransceiverCodecs"/>
218         /// <since_tizen> 10 </since_tizen>
219         public ReadOnlyCollection<TransceiverCodec> SupportedTransceiverCodecs
220         {
221             get
222             {
223                 if (this is MediaFileSource || this is MediaPacketSource)
224                 {
225                     throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
226                 }
227                 if (this is MediaNullSource)
228                 {
229                     var codecs = ForeachSupportedTransceiverCodecs(MediaType.Audio);
230                     codecs.AddRange(ForeachSupportedTransceiverCodecs(MediaType.Video));
231
232                     return new ReadOnlyCollection<TransceiverCodec>(codecs);
233                 }
234
235                 return new ReadOnlyCollection<TransceiverCodec>(ForeachSupportedTransceiverCodecs(MediaType));
236             }
237         }
238
239         internal List<TransceiverCodec> ForeachSupportedTransceiverCodecs(MediaType type)
240         {
241             var codecs = new List<TransceiverCodec>();
242             Exception caught = null;
243
244             NativeWebRTC.RetrieveTransceiverCodecCallback cb = (codec, _) =>
245             {
246                 try
247                 {
248                     codecs.Add(codec);
249                 }
250                 catch (Exception e)
251                 {
252                     caught = e;
253                     return false;
254                 }
255
256                 return true;
257             };
258
259             using (var cbKeeper = ObjectKeeper.Get(cb))
260             {
261                 try
262                 {
263                     NativeWebRTC.ForeachSupportedTransceiverCodec(WebRtc.Handle, MediaSourceType, type, cb).
264                         ThrowIfFailed("failed to retrieve stats");
265                 }
266                 catch (ObjectDisposedException)
267                 {
268                     throw;
269                 }
270                 catch
271                 {
272                     Log.Info(WebRTCLog.Tag, "This is not error in csharp.");
273                 }
274
275                 if (caught != null)
276                 {
277                     throw caught;
278                 }
279             }
280
281             return codecs;
282         }
283
284         /// <summary>
285         /// Gets or sets the pause status of current media source.
286         /// </summary>
287         /// If <see cref="MediaFileSource"/>, please use <see cref="MediaFileSource.GetPause"/>
288         /// or <see cref="MediaFileSource.SetPause"/> instead.<br/> (Since API level 10)
289         /// <value>A value that specifies the pause status.</value>
290         /// <exception cref="InvalidOperationException">
291         ///     MediaSource is not attached yet.<br/>
292         /// -or-<br/>
293         ///     This MediaSource is not supported type of MediaSource. (Since API level 10)
294         /// </exception>
295         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
296         /// <seealso cref="MediaFileSource.GetPause"/>
297         /// <seealso cref="MediaFileSource.SetPause"/>
298         /// <since_tizen> 9 </since_tizen>
299         public bool Pause
300         {
301             get
302             {
303                 if (!SourceId.HasValue)
304                 {
305                     throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
306                 }
307                 if (this is MediaFileSource || this is MediaNullSource)
308                 {
309                     throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
310                 }
311
312                 NativeWebRTC.GetPause(WebRtc.Handle, SourceId.Value, MediaType, out bool isPaused).
313                     ThrowIfFailed("Failed to get pause");
314
315                 return isPaused;
316             }
317             set
318             {
319                 if (!SourceId.HasValue)
320                 {
321                     throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
322                 }
323                 if (this is MediaFileSource || this is MediaNullSource)
324                 {
325                     throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
326                 }
327
328                 NativeWebRTC.SetPause(WebRtc.Handle, SourceId.Value, MediaType, value).
329                     ThrowIfFailed("Failed to set pause");
330             }
331         }
332
333         /// <summary>
334         /// Gets or sets the mute status of the current media source.
335         /// </summary>
336         /// <remarks>
337         /// This API is not supported in <see cref="MediaFileSource"/>, <see cref="MediaPacketSource"/>, <see cref="MediaNullSource"/>. (Since API level 10)
338         /// </remarks>
339         /// <value>A value that specifies the mute status.</value>
340         /// <exception cref="InvalidOperationException">
341         ///     MediaSource is not attached yet.<br/>
342         /// -or-<br/>
343         ///     This MediaSource is not supported type of MediaSource. (Since API level 10)
344         /// </exception>
345         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
346         /// <since_tizen> 9 </since_tizen>
347         public bool Mute
348         {
349             get
350             {
351                 if (!SourceId.HasValue)
352                 {
353                     throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
354                 }
355                 if (this is MediaFileSource || this is MediaPacketSource || this is MediaNullSource)
356                 {
357                     throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
358                 }
359
360                 NativeWebRTC.GetMute(WebRtc.Handle, SourceId.Value, MediaType, out bool isMuted).
361                     ThrowIfFailed("Failed to get mute");
362
363                 return isMuted;
364             }
365             set
366             {
367                 if (!SourceId.HasValue)
368                 {
369                     throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
370                 }
371                 if (this is MediaFileSource || this is MediaPacketSource || this is MediaNullSource)
372                 {
373                     throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
374                 }
375
376                 NativeWebRTC.SetMute(WebRtc.Handle, SourceId.Value, MediaType, value).
377                     ThrowIfFailed("Failed to set mute");
378             }
379         }
380
381         /// <summary>
382         /// Gets or sets the video resolution of the current media source.
383         /// </summary>
384         /// <value>A value that specifies the video resolution.</value>
385         /// <exception cref="InvalidOperationException">
386         ///     MediaSource is not attached yet.<br/>
387         /// -or-<br/>
388         ///     This MediaSource is not Video.
389         /// -or-<br/>
390         ///     This MediaSource is not supported type of MediaSource.
391         /// </exception>
392         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
393         /// <since_tizen> 9 </since_tizen>
394         public Size VideoResolution
395         {
396             get
397             {
398                 if (!SourceId.HasValue)
399                 {
400                     throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
401                 }
402                 if (MediaType != MediaType.Video)
403                 {
404                     throw new InvalidOperationException("This property is only for video.");
405                 }
406                 if (this is MediaFileSource || this is MediaPacketSource)
407                 {
408                     throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
409                 }
410
411                 NativeWebRTC.GetVideoResolution(WebRtc.Handle, SourceId.Value, out int width, out int height).
412                     ThrowIfFailed("Failed to get video resolution");
413
414                 return new Size(width, height);
415             }
416             set
417             {
418                 if (!SourceId.HasValue)
419                 {
420                     throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
421                 }
422                 if (MediaType != MediaType.Video)
423                 {
424                     throw new InvalidOperationException("This property is only for video.");
425                 }
426                 if (this is MediaFileSource || this is MediaPacketSource)
427                 {
428                     throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
429                 }
430
431                 NativeWebRTC.SetVideoResolution(WebRtc.Handle, SourceId.Value, value.Width, value.Height).
432                     ThrowIfFailed("Failed to set video resolution");
433             }
434         }
435
436         /// <summary>
437         /// Gets or sets the video frame rate of the current media source.
438         /// </summary>
439         /// <remarks>
440         /// This API is only supported in video media source, especially <see cref="MediaCameraSource"/>,
441         /// <see cref="MediaScreenSource"/>, <see cref="MediaTestSource"/>.<br/>
442         /// </remarks>
443         /// <value>A value that specifies the video frame rate.</value>
444         /// <exception cref="ArgumentException">VideoFrameRate is less than or equal to zero.</exception>
445         /// <exception cref="InvalidOperationException">
446         ///     MediaSource is not attached yet.<br/>
447         /// -or-<br/>
448         ///     This MediaSource is not Video.
449         /// -or-<br/>
450         ///     This MediaSource is not supported type of MediaSource.
451         /// </exception>
452         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
453         /// <since_tizen> 10 </since_tizen>
454         public int VideoFrameRate
455         {
456             get
457             {
458                 if (!SourceId.HasValue)
459                 {
460                     throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
461                 }
462                 if (MediaType != MediaType.Video)
463                 {
464                     throw new InvalidOperationException("This property is only for video.");
465                 }
466                 if (this is MediaFileSource || this is MediaPacketSource)
467                 {
468                     throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
469                 }
470
471                 NativeWebRTC.GetVideoFrameRate(WebRtc.Handle, SourceId.Value, out int frameRate).
472                     ThrowIfFailed("Failed to get video frame rate");
473
474                 return frameRate;
475             }
476             set
477             {
478                 if (value <= 0)
479                 {
480                     throw new ArgumentException($"VideoFrameRate should be greater than zero.");
481                 }
482                 if (!SourceId.HasValue)
483                 {
484                     throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
485                 }
486                 if (MediaType != MediaType.Video)
487                 {
488                     throw new InvalidOperationException("This property is only for video.");
489                 }
490                 if (this is MediaFileSource || this is MediaPacketSource)
491                 {
492                     throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
493                 }
494
495                 NativeWebRTC.SetVideoFrameRate(WebRtc.Handle, SourceId.Value, value).
496                     ThrowIfFailed("Failed to set video frame rate");
497             }
498         }
499
500         /// <summary>
501         /// Gets or sets the encoder bitrate of the current media source.
502         /// </summary>
503         /// <remarks>
504         /// This API is not supported in <see cref="MediaFileSource"/>, <see cref="MediaPacketSource"/>.
505         /// </remarks>
506         /// <value>A value that specifies the encoder bitrate.</value>
507         /// <exception cref="ArgumentException">EncoderBitrate is less than or equal to zero.</exception>
508         /// <exception cref="InvalidOperationException">
509         ///     MediaSource is not attached yet.<br/>
510         /// -or-<br/>
511         ///     This MediaSource is not Video.
512         /// -or-<br/>
513         ///     This MediaSource is not supported type of MediaSource.
514         /// </exception>
515         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
516         /// <since_tizen> 10 </since_tizen>
517         public int EncoderBitrate
518         {
519             get
520             {
521                 if (!SourceId.HasValue)
522                 {
523                     throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
524                 }
525                 if (this is MediaFileSource || this is MediaPacketSource)
526                 {
527                     throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
528                 }
529
530                 NativeWebRTC.GetEncoderBitrate(WebRtc.Handle, SourceId.Value, MediaType, out int bitrate).
531                     ThrowIfFailed("Failed to get encoder bitrate");
532
533                 return bitrate;
534             }
535             set
536             {
537                 if (value <= 0)
538                 {
539                     throw new ArgumentException($"EncoderBitrate should be greater than zero.");
540                 }
541                 if (!SourceId.HasValue)
542                 {
543                     throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
544                 }
545                 if (this is MediaFileSource || this is MediaPacketSource)
546                 {
547                     throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
548                 }
549
550                 NativeWebRTC.SetEncoderBitrate(WebRtc.Handle, SourceId.Value, MediaType, value).
551                     ThrowIfFailed("Failed to set encoder bitrate");
552             }
553         }
554
555         /// <summary>
556         /// Enables the audio loopback. The local audio will be played with <paramref name="policy"/>.
557         /// </summary>
558         /// <param name="policy">The <see cref="AudioStreamPolicy"/> to apply.</param>
559         /// <remarks>
560         /// <see cref="MediaSource"/> does not support all <see cref="AudioStreamType"/>.<br/>
561         /// Supported types are <see cref="AudioStreamType.Media"/>, <see cref="AudioStreamType.Voip"/>,
562         /// <see cref="AudioStreamType.MediaExternalOnly"/>.<br/>
563         /// </remarks>
564         /// <exception cref="ArgumentNullException"><paramref name="policy"/> is null.</exception>
565         /// <exception cref="InvalidOperationException">
566         ///     MediaSource is not attached yet.<br/>
567         /// -or-<br/>
568         ///     This MediaSource is not Audio
569         /// </exception>
570         /// <exception cref="NotSupportedException">
571         ///     <see cref="AudioStreamType"/> of <paramref name="policy"/> is not supported on the current platform.
572         /// </exception>
573         /// <exception cref="ObjectDisposedException">
574         ///     <paramref name="policy"/> or WebRTC has already been disposed.
575         /// </exception>
576         /// <returns><see cref="MediaStreamTrack"/></returns>
577         public MediaStreamTrack EnableAudioLoopback(AudioStreamPolicy policy)
578         {
579             if (!SourceId.HasValue)
580             {
581                 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
582             }
583             if (policy == null)
584             {
585                 throw new ArgumentNullException(nameof(policy));
586             }
587             if (MediaType != MediaType.Audio)
588             {
589                 throw new InvalidOperationException("AudioLoopback is only for Audio MediaSource");
590             }
591
592             var ret = NativeWebRTC.SetAudioLoopback(WebRtc.Handle, SourceId.Value, policy.Handle, out uint trackId);
593
594             if (ret == WebRTCErrorCode.InvalidArgument)
595             {
596                 throw new NotSupportedException("The specified policy is not supported on the current system.");
597             }
598
599             ret.ThrowIfFailed("Failed to set the audio stream policy to the WebRTC");
600
601             return new MediaStreamTrack(WebRtc, MediaType, trackId);
602         }
603
604         private uint SetDisplay(Display display)
605             => display.ApplyTo(this);
606
607         internal void ReplaceDisplay(Display newDisplay)
608         {
609             _display?.SetOwner(null);
610             _display = newDisplay;
611             _display?.SetOwner(this);
612         }
613
614         /// <summary>
615         /// Enables the video loopback. The local video will be diaplayed in <paramref name="display"/>.
616         /// </summary>
617         /// <param name="display">The <see cref="Display"/> to apply.</param>
618         /// <exception cref="ArgumentException">The display has already been assigned to another.</exception>
619         /// <exception cref="ArgumentNullException"><paramref name="display"/> is null.</exception>
620         /// <exception cref="InvalidOperationException">
621         ///     MediaSource is not attached yet.<br/>
622         /// -or-<br/>
623         ///     This MediaSource is not Video
624         /// </exception>
625         /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
626         /// <returns><see cref="MediaStreamTrack"/></returns>
627         public MediaStreamTrack EnableVideoLoopback(Display display)
628         {
629             uint trackId = 0;
630
631             if (!SourceId.HasValue)
632             {
633                 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
634             }
635             if (display == null)
636             {
637                 throw new ArgumentNullException(nameof(display), "Display cannot be null.");
638             }
639             if (MediaType != MediaType.Video)
640             {
641                 throw new InvalidOperationException("VideoLoopback is only for Video MediaSource");
642             }
643
644             if (display?.Owner != null)
645             {
646                 if (ReferenceEquals(this, display.Owner))
647                 {
648                     throw new ArgumentException("The display has already been assigned to another.");
649                 }
650             }
651             else
652             {
653                 trackId = SetDisplay(display);
654                 ReplaceDisplay(display);
655             }
656
657             return new MediaStreamTrack(WebRtc, MediaType, trackId);
658         }
659
660         uint IDisplayable<uint>.ApplyEvasDisplay(DisplayType type, ElmSharp.EvasObject evasObject)
661         {
662             Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
663             Debug.Assert(type != DisplayType.None);
664
665             NativeWebRTC.SetVideoLoopback(WebRtc.Handle, SourceId.Value,
666                 type == DisplayType.Overlay ? WebRTCDisplayType.Overlay : WebRTCDisplayType.Evas, evasObject,
667                 out uint trackId).ThrowIfFailed("Failed to set video loopback");
668
669             return trackId;
670         }
671
672         uint IDisplayable<uint>.ApplyEcoreWindow(IntPtr windowHandle, NUI.Rectangle rect)
673         {
674             NativeWebRTC.SetEcoreVideoLoopback(WebRtc.Handle, SourceId.Value, windowHandle, out uint trackId).
675                 ThrowIfFailed("Failed to set ecore video loopback");
676
677             return trackId;
678         }
679     }
680 }