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 /// 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)
89 /// <value>A <see cref="TransceiverDirection"/> that specifies the transceiver direction.</value>
90 /// <exception cref="InvalidOperationException">
91 /// MediaSource is not attached yet.<br/>
93 /// <see cref="TransceiverDirection.SendOnly"/> or <see cref="TransceiverDirection.SendRecv"/> is set for MediaNullSource. (Since API level 10)
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
103 if (!SourceId.HasValue)
105 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
107 if (this is MediaNullSource)
109 return TransceiverDirection.RecvOnly;
112 NativeWebRTC.GetTransceiverDirection(WebRtc.Handle, SourceId.Value, MediaType, out TransceiverDirection mode).
113 ThrowIfFailed("Failed to get transceiver direction.");
119 if (!SourceId.HasValue)
121 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
123 if (this is MediaNullSource)
125 if (value != TransceiverDirection.RecvOnly)
127 throw new InvalidOperationException("Only RecvOnly is allowed for MediaNullSource.");
133 if (this is MediaNullSource || this is MediaFileSource)
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.");
142 NativeWebRTC.SetTransceiverDirection(WebRtc.Handle, SourceId.Value, MediaType, value).
143 ThrowIfFailed("Failed to set transceiver direction.");
149 /// Gets or sets the transceiver codec of current media source.
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.
157 /// <value>The transceiver codec.</value>
158 /// <exception cref="InvalidOperationException">
159 /// MediaSource is not attached yet.<br/>
161 /// This MediaSource is not supported type of MediaSource.<br/>
163 /// The WebRTC is not in the valid state.
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
173 if (!SourceId.HasValue)
175 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
177 if (this is MediaFileSource || this is MediaPacketSource || this is MediaNullSource)
179 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
182 NativeWebRTC.GetTransceiverCodec(WebRtc.Handle, SourceId.Value, MediaType, out TransceiverCodec codec).
183 ThrowIfFailed("Failed to get transceiver codec");
189 if (!SourceId.HasValue)
191 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
193 if (this is MediaFileSource || this is MediaPacketSource || this is MediaNullSource)
195 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
198 WebRtc.ValidateWebRTCState(WebRTCState.Idle);
200 NativeWebRTC.SetTransceiverCodec(WebRtc.Handle, SourceId.Value, MediaType, value).
201 ThrowIfFailed("Failed to set transceiver codec");
206 /// Retrieves the supported transceiver codecs.
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.
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
222 if (this is MediaFileSource || this is MediaPacketSource)
224 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
226 if (this is MediaNullSource)
228 var codecs = ForeachSupportedTransceiverCodecs(MediaType.Audio);
229 codecs.AddRange(ForeachSupportedTransceiverCodecs(MediaType.Video));
231 return new ReadOnlyCollection<TransceiverCodec>(codecs);
234 return new ReadOnlyCollection<TransceiverCodec>(ForeachSupportedTransceiverCodecs(MediaType));
238 internal List<TransceiverCodec> ForeachSupportedTransceiverCodecs(MediaType type)
240 var codecs = new List<TransceiverCodec>();
241 Exception caught = null;
243 NativeWebRTC.RetrieveTransceiverCodecCallback cb = (codec, _) =>
258 using (var cbKeeper = ObjectKeeper.Get(cb))
262 NativeWebRTC.ForeachSupportedTransceiverCodec(WebRtc.Handle, MediaSourceType.Null, type, cb).
263 ThrowIfFailed("failed to retrieve stats");
265 catch (ObjectDisposedException)
271 Log.Info(WebRTCLog.Tag, "This is not error in csharp.");
284 /// Gets or sets the pause status of current media source.
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/>
292 /// This MediaSource is not supported type of MediaSource. (Since API level 10)
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>
302 if (!SourceId.HasValue)
304 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
306 if (this is MediaFileSource || this is MediaNullSource)
308 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
311 NativeWebRTC.GetPause(WebRtc.Handle, SourceId.Value, MediaType, out bool isPaused).
312 ThrowIfFailed("Failed to get pause");
318 if (!SourceId.HasValue)
320 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
322 if (this is MediaFileSource || this is MediaNullSource)
324 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
327 NativeWebRTC.SetPause(WebRtc.Handle, SourceId.Value, MediaType, value).
328 ThrowIfFailed("Failed to set pause");
333 /// Gets or sets the mute status of the current media source.
336 /// This API is not supported in <see cref="MediaFileSource"/>, <see cref="MediaPacketSource"/>, <see cref="MediaNullSource"/>. (Since API level 10)
338 /// <value>A value that specifies the mute status.</value>
339 /// <exception cref="InvalidOperationException">
340 /// MediaSource is not attached yet.<br/>
342 /// This MediaSource is not supported type of MediaSource. (Since API level 10)
344 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
345 /// <since_tizen> 9 </since_tizen>
350 if (!SourceId.HasValue)
352 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
354 if (this is MediaFileSource || this is MediaPacketSource || this is MediaNullSource)
356 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
359 NativeWebRTC.GetMute(WebRtc.Handle, SourceId.Value, MediaType, out bool isMuted).
360 ThrowIfFailed("Failed to get mute");
366 if (!SourceId.HasValue)
368 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
370 if (this is MediaFileSource || this is MediaPacketSource || this is MediaNullSource)
372 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
375 NativeWebRTC.SetMute(WebRtc.Handle, SourceId.Value, MediaType, value).
376 ThrowIfFailed("Failed to set mute");
381 /// Gets or sets the video resolution of the current media source.
383 /// <value>A value that specifies the video resolution.</value>
384 /// <exception cref="InvalidOperationException">
385 /// MediaSource is not attached yet.<br/>
387 /// This MediaSource is not Video.
389 /// This MediaSource is not supported type of MediaSource.
391 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
392 /// <since_tizen> 9 </since_tizen>
393 public Size VideoResolution
397 if (!SourceId.HasValue)
399 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
401 if (MediaType != MediaType.Video)
403 throw new InvalidOperationException("This property is only for video.");
405 if (this is MediaFileSource || this is MediaPacketSource)
407 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
410 NativeWebRTC.GetVideoResolution(WebRtc.Handle, SourceId.Value, out int width, out int height).
411 ThrowIfFailed("Failed to get video resolution");
413 return new Size(width, height);
417 if (!SourceId.HasValue)
419 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
421 if (MediaType != MediaType.Video)
423 throw new InvalidOperationException("This property is only for video.");
425 if (this is MediaFileSource || this is MediaPacketSource)
427 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
430 NativeWebRTC.SetVideoResolution(WebRtc.Handle, SourceId.Value, value.Width, value.Height).
431 ThrowIfFailed("Failed to set video resolution");
436 /// Gets or sets the video frame rate of the current media source.
439 /// This API is only supported in video media source, especially <see cref="MediaCameraSource"/>,
440 /// <see cref="MediaScreenSource"/>, <see cref="MediaTestSource"/>.<br/>
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/>
447 /// This MediaSource is not Video.
449 /// This MediaSource is not supported type of MediaSource.
451 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
452 /// <since_tizen> 10 </since_tizen>
453 public int VideoFrameRate
457 if (!SourceId.HasValue)
459 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
461 if (MediaType != MediaType.Video)
463 throw new InvalidOperationException("This property is only for video.");
465 if (this is MediaFileSource || this is MediaPacketSource)
467 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
470 NativeWebRTC.GetVideoFrameRate(WebRtc.Handle, SourceId.Value, out int frameRate).
471 ThrowIfFailed("Failed to get video frame rate");
479 throw new ArgumentException($"VideoFrameRate should be greater than zero.");
481 if (!SourceId.HasValue)
483 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
485 if (MediaType != MediaType.Video)
487 throw new InvalidOperationException("This property is only for video.");
489 if (this is MediaFileSource || this is MediaPacketSource)
491 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
494 NativeWebRTC.SetVideoFrameRate(WebRtc.Handle, SourceId.Value, value).
495 ThrowIfFailed("Failed to set video frame rate");
500 /// Gets or sets the encoder bitrate of the current media source.
503 /// This API is not supported in <see cref="MediaFileSource"/>, <see cref="MediaPacketSource"/>.
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/>
510 /// This MediaSource is not Video.
512 /// This MediaSource is not supported type of MediaSource.
514 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
515 /// <since_tizen> 10 </since_tizen>
516 public int EncoderBitrate
520 if (!SourceId.HasValue)
522 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
524 if (this is MediaFileSource || this is MediaPacketSource)
526 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
529 NativeWebRTC.GetEncoderBitrate(WebRtc.Handle, SourceId.Value, MediaType, out int bitrate).
530 ThrowIfFailed("Failed to get encoder bitrate");
538 throw new ArgumentException($"EncoderBitrate should be greater than zero.");
540 if (!SourceId.HasValue)
542 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
544 if (this is MediaFileSource || this is MediaPacketSource)
546 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
549 NativeWebRTC.SetEncoderBitrate(WebRtc.Handle, SourceId.Value, MediaType, value).
550 ThrowIfFailed("Failed to set encoder bitrate");
555 /// Enables the audio loopback. The local audio will be played with <paramref name="policy"/>.
557 /// <param name="policy">The <see cref="AudioStreamPolicy"/> to apply.</param>
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/>
563 /// <exception cref="ArgumentNullException"><paramref name="policy"/> is null.</exception>
564 /// <exception cref="InvalidOperationException">
565 /// MediaSource is not attached yet.<br/>
567 /// This MediaSource is not Audio
569 /// <exception cref="NotSupportedException">
570 /// <see cref="AudioStreamType"/> of <paramref name="policy"/> is not supported on the current platform.
572 /// <exception cref="ObjectDisposedException">
573 /// <paramref name="policy"/> or WebRTC has already been disposed.
575 /// <returns><see cref="MediaStreamTrack"/></returns>
576 public MediaStreamTrack EnableAudioLoopback(AudioStreamPolicy policy)
578 if (!SourceId.HasValue)
580 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
584 throw new ArgumentNullException(nameof(policy));
586 if (MediaType != MediaType.Audio)
588 throw new InvalidOperationException("AudioLoopback is only for Audio MediaSource");
591 var ret = NativeWebRTC.SetAudioLoopback(WebRtc.Handle, SourceId.Value, policy.Handle, out uint trackId);
593 if (ret == WebRTCErrorCode.InvalidArgument)
595 throw new NotSupportedException("The specified policy is not supported on the current system.");
598 ret.ThrowIfFailed("Failed to set the audio stream policy to the WebRTC");
600 return new MediaStreamTrack(WebRtc, MediaType, trackId);
603 private uint SetDisplay(Display display)
604 => display.ApplyTo(this);
606 internal void ReplaceDisplay(Display newDisplay)
608 _display?.SetOwner(null);
609 _display = newDisplay;
610 _display?.SetOwner(this);
614 /// Enables the video loopback. The local video will be diaplayed in <paramref name="display"/>.
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/>
622 /// This MediaSource is not Video
624 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
625 /// <returns><see cref="MediaStreamTrack"/></returns>
626 public MediaStreamTrack EnableVideoLoopback(Display display)
630 if (!SourceId.HasValue)
632 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
636 throw new ArgumentNullException(nameof(display), "Display cannot be null.");
638 if (MediaType != MediaType.Video)
640 throw new InvalidOperationException("VideoLoopback is only for Video MediaSource");
643 if (display?.Owner != null)
645 if (ReferenceEquals(this, display.Owner))
647 throw new ArgumentException("The display has already been assigned to another.");
652 trackId = SetDisplay(display);
653 ReplaceDisplay(display);
656 return new MediaStreamTrack(WebRtc, MediaType, trackId);
659 uint IDisplayable<uint>.ApplyEvasDisplay(DisplayType type, ElmSharp.EvasObject evasObject)
661 Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
662 Debug.Assert(type != DisplayType.None);
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");
671 uint IDisplayable<uint>.ApplyEcoreWindow(IntPtr windowHandle)
673 NativeWebRTC.SetEcoreVideoLoopback(WebRtc.Handle, SourceId.Value, windowHandle, out uint trackId).
674 ThrowIfFailed("Failed to set ecore video loopback");