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