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