2 * Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
18 using System.Collections.Generic;
19 using System.Collections.ObjectModel;
20 using System.Diagnostics;
23 namespace Tizen.Multimedia.Remoting
26 /// MediaSource is a base class for <see cref="WebRTC"/> sources.
28 /// <since_tizen> 9 </since_tizen>
29 public abstract class MediaSource : IDisplayable<uint>
31 internal WebRTC WebRtc { get; set; }
32 internal uint? SourceId { get; set; }
33 private Display _display;
36 /// Gets the type of MediaSource.
38 /// <value><see cref="MediaType"/></value>
39 /// <since_tizen> 9 </since_tizen>
40 protected MediaType MediaType { get; }
42 private bool IsDetached {get; set;} = false;
45 /// Initializes a new instance of the <see cref="MediaSource"/> class.
47 /// <since_tizen> 9 </since_tizen>
48 protected MediaSource(MediaType mediaType)
50 MediaType = mediaType;
54 /// Initializes a new instance of the <see cref="MediaSource"/> class.
56 /// <since_tizen> 10 </since_tizen>
57 protected MediaSource() { }
59 internal void AttachTo(WebRTC webRtc)
63 throw new InvalidOperationException("MediaSource was already detached.");
69 internal void DetachFrom(WebRTC webRtc)
75 internal abstract void OnAttached(WebRTC webRtc);
77 internal abstract void OnDetached(WebRTC webRtc);
79 internal virtual MediaSourceType MediaSourceType => MediaSourceType.Null;
82 /// Gets or sets the transceiver direction of current media source.
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)
90 /// <value>A <see cref="TransceiverDirection"/> that specifies the transceiver direction.</value>
91 /// <exception cref="InvalidOperationException">
92 /// MediaSource is not attached yet.<br/>
94 /// <see cref="TransceiverDirection.SendOnly"/> or <see cref="TransceiverDirection.SendRecv"/> is set for MediaNullSource. (Since API level 10)
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
104 if (!SourceId.HasValue)
106 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
108 if (this is MediaNullSource)
110 return TransceiverDirection.RecvOnly;
113 NativeWebRTC.GetTransceiverDirection(WebRtc.Handle, SourceId.Value, MediaType, out TransceiverDirection mode).
114 ThrowIfFailed("Failed to get transceiver direction.");
120 if (!SourceId.HasValue)
122 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
124 if (this is MediaNullSource)
126 if (value != TransceiverDirection.RecvOnly)
128 throw new InvalidOperationException("Only RecvOnly is allowed for MediaNullSource.");
134 if (this is MediaNullSource || this is MediaFileSource)
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.");
143 NativeWebRTC.SetTransceiverDirection(WebRtc.Handle, SourceId.Value, MediaType, value).
144 ThrowIfFailed("Failed to set transceiver direction.");
150 /// Gets or sets the transceiver codec of current media source.
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.
158 /// <value>The transceiver codec.</value>
159 /// <exception cref="InvalidOperationException">
160 /// MediaSource is not attached yet.<br/>
162 /// This MediaSource is not supported type of MediaSource.<br/>
164 /// The WebRTC is not in the valid state.
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
174 if (!SourceId.HasValue)
176 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
178 if (this is MediaFileSource || this is MediaPacketSource || this is MediaNullSource)
180 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
183 NativeWebRTC.GetTransceiverCodec(WebRtc.Handle, SourceId.Value, MediaType, out TransceiverCodec codec).
184 ThrowIfFailed("Failed to get transceiver codec");
190 if (!SourceId.HasValue)
192 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
194 if (this is MediaFileSource || this is MediaPacketSource || this is MediaNullSource)
196 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
199 WebRtc.ValidateWebRTCState(WebRTCState.Idle);
201 NativeWebRTC.SetTransceiverCodec(WebRtc.Handle, SourceId.Value, MediaType, value).
202 ThrowIfFailed("Failed to set transceiver codec");
207 /// Retrieves the supported transceiver codecs.
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.
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
223 if (this is MediaFileSource || this is MediaPacketSource)
225 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
227 if (this is MediaNullSource)
229 var codecs = ForeachSupportedTransceiverCodecs(MediaType.Audio);
230 codecs.AddRange(ForeachSupportedTransceiverCodecs(MediaType.Video));
232 return new ReadOnlyCollection<TransceiverCodec>(codecs);
235 return new ReadOnlyCollection<TransceiverCodec>(ForeachSupportedTransceiverCodecs(MediaType));
239 internal List<TransceiverCodec> ForeachSupportedTransceiverCodecs(MediaType type)
241 var codecs = new List<TransceiverCodec>();
242 Exception caught = null;
244 NativeWebRTC.RetrieveTransceiverCodecCallback cb = (codec, _) =>
259 using (var cbKeeper = ObjectKeeper.Get(cb))
263 NativeWebRTC.ForeachSupportedTransceiverCodec(WebRtc.Handle, MediaSourceType, type, cb).
264 ThrowIfFailed("failed to retrieve stats");
266 catch (ObjectDisposedException)
272 Log.Info(WebRTCLog.Tag, "This is not error in csharp.");
285 /// Gets or sets the pause status of current media source.
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/>
293 /// This MediaSource is not supported type of MediaSource. (Since API level 10)
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>
303 if (!SourceId.HasValue)
305 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
307 if (this is MediaFileSource || this is MediaNullSource)
309 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
312 NativeWebRTC.GetPause(WebRtc.Handle, SourceId.Value, MediaType, out bool isPaused).
313 ThrowIfFailed("Failed to get pause");
319 if (!SourceId.HasValue)
321 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
323 if (this is MediaFileSource || this is MediaNullSource)
325 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
328 NativeWebRTC.SetPause(WebRtc.Handle, SourceId.Value, MediaType, value).
329 ThrowIfFailed("Failed to set pause");
334 /// Gets or sets the mute status of the current media source.
337 /// This API is not supported in <see cref="MediaFileSource"/>, <see cref="MediaPacketSource"/>, <see cref="MediaNullSource"/>. (Since API level 10)
339 /// <value>A value that specifies the mute status.</value>
340 /// <exception cref="InvalidOperationException">
341 /// MediaSource is not attached yet.<br/>
343 /// This MediaSource is not supported type of MediaSource. (Since API level 10)
345 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
346 /// <since_tizen> 9 </since_tizen>
351 if (!SourceId.HasValue)
353 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
355 if (this is MediaFileSource || this is MediaPacketSource || this is MediaNullSource)
357 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
360 NativeWebRTC.GetMute(WebRtc.Handle, SourceId.Value, MediaType, out bool isMuted).
361 ThrowIfFailed("Failed to get mute");
367 if (!SourceId.HasValue)
369 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
371 if (this is MediaFileSource || this is MediaPacketSource || this is MediaNullSource)
373 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
376 NativeWebRTC.SetMute(WebRtc.Handle, SourceId.Value, MediaType, value).
377 ThrowIfFailed("Failed to set mute");
382 /// Gets or sets the video resolution of the current media source.
384 /// <value>A value that specifies the video resolution.</value>
385 /// <exception cref="InvalidOperationException">
386 /// MediaSource is not attached yet.<br/>
388 /// This MediaSource is not Video.
390 /// This MediaSource is not supported type of MediaSource.
392 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
393 /// <since_tizen> 9 </since_tizen>
394 public Size VideoResolution
398 if (!SourceId.HasValue)
400 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
402 if (MediaType != MediaType.Video)
404 throw new InvalidOperationException("This property is only for video.");
406 if (this is MediaFileSource || this is MediaPacketSource)
408 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
411 NativeWebRTC.GetVideoResolution(WebRtc.Handle, SourceId.Value, out int width, out int height).
412 ThrowIfFailed("Failed to get video resolution");
414 return new Size(width, height);
418 if (!SourceId.HasValue)
420 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
422 if (MediaType != MediaType.Video)
424 throw new InvalidOperationException("This property is only for video.");
426 if (this is MediaFileSource || this is MediaPacketSource)
428 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
431 NativeWebRTC.SetVideoResolution(WebRtc.Handle, SourceId.Value, value.Width, value.Height).
432 ThrowIfFailed("Failed to set video resolution");
437 /// Gets or sets the video frame rate of the current media source.
440 /// This API is only supported in video media source, especially <see cref="MediaCameraSource"/>,
441 /// <see cref="MediaScreenSource"/>, <see cref="MediaTestSource"/>.<br/>
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/>
448 /// This MediaSource is not Video.
450 /// This MediaSource is not supported type of MediaSource.
452 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
453 /// <since_tizen> 10 </since_tizen>
454 public int VideoFrameRate
458 if (!SourceId.HasValue)
460 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
462 if (MediaType != MediaType.Video)
464 throw new InvalidOperationException("This property is only for video.");
466 if (this is MediaFileSource || this is MediaPacketSource)
468 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
471 NativeWebRTC.GetVideoFrameRate(WebRtc.Handle, SourceId.Value, out int frameRate).
472 ThrowIfFailed("Failed to get video frame rate");
480 throw new ArgumentException($"VideoFrameRate should be greater than zero.");
482 if (!SourceId.HasValue)
484 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
486 if (MediaType != MediaType.Video)
488 throw new InvalidOperationException("This property is only for video.");
490 if (this is MediaFileSource || this is MediaPacketSource)
492 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
495 NativeWebRTC.SetVideoFrameRate(WebRtc.Handle, SourceId.Value, value).
496 ThrowIfFailed("Failed to set video frame rate");
501 /// Gets or sets the encoder bitrate of the current media source.
504 /// This API is not supported in <see cref="MediaFileSource"/>, <see cref="MediaPacketSource"/>.
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/>
511 /// This MediaSource is not Video.
513 /// This MediaSource is not supported type of MediaSource.
515 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
516 /// <since_tizen> 10 </since_tizen>
517 public int EncoderBitrate
521 if (!SourceId.HasValue)
523 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
525 if (this is MediaFileSource || this is MediaPacketSource)
527 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
530 NativeWebRTC.GetEncoderBitrate(WebRtc.Handle, SourceId.Value, MediaType, out int bitrate).
531 ThrowIfFailed("Failed to get encoder bitrate");
539 throw new ArgumentException($"EncoderBitrate should be greater than zero.");
541 if (!SourceId.HasValue)
543 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
545 if (this is MediaFileSource || this is MediaPacketSource)
547 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
550 NativeWebRTC.SetEncoderBitrate(WebRtc.Handle, SourceId.Value, MediaType, value).
551 ThrowIfFailed("Failed to set encoder bitrate");
556 /// Enables the audio loopback. The local audio will be played with <paramref name="policy"/>.
558 /// <param name="policy">The <see cref="AudioStreamPolicy"/> to apply.</param>
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/>
564 /// <exception cref="ArgumentNullException"><paramref name="policy"/> is null.</exception>
565 /// <exception cref="InvalidOperationException">
566 /// MediaSource is not attached yet.<br/>
568 /// This MediaSource is not Audio
570 /// <exception cref="NotSupportedException">
571 /// <see cref="AudioStreamType"/> of <paramref name="policy"/> is not supported on the current platform.
573 /// <exception cref="ObjectDisposedException">
574 /// <paramref name="policy"/> or WebRTC has already been disposed.
576 /// <returns><see cref="MediaStreamTrack"/></returns>
577 public MediaStreamTrack EnableAudioLoopback(AudioStreamPolicy policy)
579 if (!SourceId.HasValue)
581 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
585 throw new ArgumentNullException(nameof(policy));
587 if (MediaType != MediaType.Audio)
589 throw new InvalidOperationException("AudioLoopback is only for Audio MediaSource");
592 var ret = NativeWebRTC.SetAudioLoopback(WebRtc.Handle, SourceId.Value, policy.Handle, out uint trackId);
594 if (ret == WebRTCErrorCode.InvalidArgument)
596 throw new NotSupportedException("The specified policy is not supported on the current system.");
599 ret.ThrowIfFailed("Failed to set the audio stream policy to the WebRTC");
601 return new MediaStreamTrack(WebRtc, MediaType, trackId);
604 private uint SetDisplay(Display display)
605 => display.ApplyTo(this);
607 internal void ReplaceDisplay(Display newDisplay)
609 _display?.SetOwner(null);
610 _display = newDisplay;
611 _display?.SetOwner(this);
615 /// Enables the video loopback. The local video will be diaplayed in <paramref name="display"/>.
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/>
623 /// This MediaSource is not Video
625 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
626 /// <returns><see cref="MediaStreamTrack"/></returns>
627 public MediaStreamTrack EnableVideoLoopback(Display display)
631 if (!SourceId.HasValue)
633 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
637 throw new ArgumentNullException(nameof(display), "Display cannot be null.");
639 if (MediaType != MediaType.Video)
641 throw new InvalidOperationException("VideoLoopback is only for Video MediaSource");
644 if (display?.Owner != null)
646 if (ReferenceEquals(this, display.Owner))
648 throw new ArgumentException("The display has already been assigned to another.");
653 trackId = SetDisplay(display);
654 ReplaceDisplay(display);
657 return new MediaStreamTrack(WebRtc, MediaType, trackId);
660 uint IDisplayable<uint>.ApplyEvasDisplay(DisplayType type, ElmSharp.EvasObject evasObject)
662 Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
663 Debug.Assert(type != DisplayType.None);
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");
672 uint IDisplayable<uint>.ApplyEcoreWindow(IntPtr windowHandle)
674 NativeWebRTC.SetEcoreVideoLoopback(WebRtc.Handle, SourceId.Value, windowHandle, out uint trackId).
675 ThrowIfFailed("Failed to set ecore video loopback");