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;
53 internal void AttachTo(WebRTC webRtc)
57 throw new InvalidOperationException("MediaSource was already detached.");
63 internal void DetachFrom(WebRTC webRtc)
69 internal abstract void OnAttached(WebRTC webRtc);
71 internal abstract void OnDetached(WebRTC webRtc);
74 /// Gets or sets the transceiver direction of current media source.
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
84 if (!SourceId.HasValue)
86 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
89 NativeWebRTC.GetTransceiverDirection(WebRtc.Handle, SourceId.Value, MediaType, out TransceiverDirection mode).
90 ThrowIfFailed("Failed to get transceiver direction.");
96 if (!SourceId.HasValue)
98 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
101 NativeWebRTC.SetTransceiverDirection(WebRtc.Handle, SourceId.Value, MediaType, value).
102 ThrowIfFailed("Failed to get transceiver direction.");
107 /// Gets or sets the transceiver codec of current media source.
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.
113 /// <value>The transceiver codec.</value>
114 /// <exception cref="InvalidOperationException">
115 /// MediaSource is not attached yet.<br/>
117 /// This MediaSource is not supported type of MediaSource.<br/>
119 /// The WebRTC is not in the valid state.
121 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
122 /// <since_tizen> 10 </since_tizen>
123 public TransceiverCodec TransceiverCodec
127 if (!SourceId.HasValue)
129 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
131 if (this is MediaFileSource || this is MediaPacketSource)
133 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
136 NativeWebRTC.GetTransceiverCodec(WebRtc.Handle, SourceId.Value, MediaType, out TransceiverCodec codec).
137 ThrowIfFailed("Failed to get transceiver codec");
143 if (!SourceId.HasValue)
145 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
147 if (this is MediaFileSource || this is MediaPacketSource)
149 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
152 WebRtc.ValidateWebRTCState(WebRTCState.Idle);
154 NativeWebRTC.SetTransceiverCodec(WebRtc.Handle, SourceId.Value, MediaType, value).
155 ThrowIfFailed("Failed to set transceiver codec");
160 /// Retrieves the supported transceiver codecs.
163 /// This API is not supported in <see cref="MediaFileSource"/>, <see cref="MediaPacketSource"/>.
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
173 if (this is MediaFileSource || this is MediaPacketSource)
175 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
178 var codecs = new List<TransceiverCodec>();
179 Exception caught = null;
181 NativeWebRTC.RetrieveTransceiverCodecCallback cb = (codec, _) =>
196 using (var cbKeeper = ObjectKeeper.Get(cb))
198 NativeWebRTC.ForeachSupportedTransceiverCodec(WebRtc.Handle, GetMediaSourceType(), MediaType, cb).
199 ThrowIfFailed("failed to retrieve stats");
207 return new ReadOnlyCollection<TransceiverCodec>(codecs);
211 private MediaSourceType GetMediaSourceType()
213 if (this is MediaTestSource)
215 if (MediaType == MediaType.Audio)
217 return MediaSourceType.AudioTest;
221 return MediaSourceType.VideoTest;
224 else if (this is MediaMicrophoneSource)
226 return MediaSourceType.Microphone;
228 else if (this is MediaCameraSource)
230 return MediaSourceType.Camera;
232 else if (this is MediaScreenSource)
234 return MediaSourceType.Screen;
236 else if (this is MediaFileSource)
238 return MediaSourceType.File;
240 else if (this is MediaPacketSource)
242 return MediaSourceType.MediaPacket;
245 return MediaSourceType.Null;
249 /// Gets or sets the pause status of current media source.
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>
259 if (!SourceId.HasValue)
261 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
264 NativeWebRTC.GetPause(WebRtc.Handle, SourceId.Value, MediaType, out bool isPaused).
265 ThrowIfFailed("Failed to get pause");
271 if (!SourceId.HasValue)
273 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
276 NativeWebRTC.SetPause(WebRtc.Handle, SourceId.Value, MediaType, value).
277 ThrowIfFailed("Failed to set pause");
282 /// Gets or sets the mute status of the current media source.
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>
292 if (!SourceId.HasValue)
294 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
297 NativeWebRTC.GetMute(WebRtc.Handle, SourceId.Value, MediaType, out bool isMuted).
298 ThrowIfFailed("Failed to get mute");
304 if (!SourceId.HasValue)
306 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
309 NativeWebRTC.SetMute(WebRtc.Handle, SourceId.Value, MediaType, value).
310 ThrowIfFailed("Failed to set mute");
315 /// Gets or sets the video resolution of the current media source.
317 /// <value>A value that specifies the video resolution.</value>
318 /// <exception cref="InvalidOperationException">
319 /// MediaSource is not attached yet.<br/>
321 /// This MediaSource is not Video.
323 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
324 /// <since_tizen> 9 </since_tizen>
325 public Size VideoResolution
329 if (!SourceId.HasValue)
331 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
333 if (MediaType != MediaType.Video)
335 throw new InvalidOperationException("This property is only for video.");
338 NativeWebRTC.GetVideoResolution(WebRtc.Handle, SourceId.Value, out int width, out int height).
339 ThrowIfFailed("Failed to get video resolution");
341 return new Size(width, height);
345 if (!SourceId.HasValue)
347 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
349 if (MediaType != MediaType.Video)
351 throw new InvalidOperationException("This property is only for video.");
354 NativeWebRTC.SetVideoResolution(WebRtc.Handle, SourceId.Value, value.Width, value.Height).
355 ThrowIfFailed("Failed to set video resolution");
360 /// Gets or sets the video frame rate of the current media source.
363 /// This API is only supported in video media source, especially <see cref="MediaCameraSource"/>,
364 /// <see cref="MediaScreenSource"/>, <see cref="MediaTestSource"/>.<br/>
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/>
371 /// This MediaSource is not Video.
373 /// This MediaSource is not supported type of MediaSource.
375 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
376 /// <since_tizen> 10 </since_tizen>
377 public int VideoFrameRate
381 if (!SourceId.HasValue)
383 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
385 if (MediaType != MediaType.Video)
387 throw new InvalidOperationException("This property is only for video.");
389 if (this is MediaFileSource || this is MediaPacketSource)
391 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
394 NativeWebRTC.GetVideoFrameRate(WebRtc.Handle, SourceId.Value, out int frameRate).
395 ThrowIfFailed("Failed to get video frame rate");
403 throw new ArgumentException($"VideoFrameRate should be greater than zero.");
405 if (!SourceId.HasValue)
407 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
409 if (MediaType != MediaType.Video)
411 throw new InvalidOperationException("This property is only for video.");
413 if (this is MediaFileSource || this is MediaPacketSource)
415 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
418 NativeWebRTC.SetVideoFrameRate(WebRtc.Handle, SourceId.Value, value).
419 ThrowIfFailed("Failed to set video frame rate");
424 /// Gets or sets the encoder bitrate of the current media source.
427 /// This API is not supported in <see cref="MediaFileSource"/>, <see cref="MediaPacketSource"/>.<br/>
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/>
434 /// This MediaSource is not Video.
436 /// This MediaSource is not supported type of MediaSource.
438 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
439 /// <since_tizen> 10 </since_tizen>
440 public int EncoderBitrate
444 if (!SourceId.HasValue)
446 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
448 if (this is MediaFileSource || this is MediaPacketSource)
450 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
453 NativeWebRTC.GetEncoderBitrate(WebRtc.Handle, SourceId.Value, MediaType, out int bitrate).
454 ThrowIfFailed("Failed to get encoder bitrate");
462 throw new ArgumentException($"EncoderBitrate should be greater than zero.");
464 if (!SourceId.HasValue)
466 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
468 if (this is MediaFileSource || this is MediaPacketSource)
470 throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
473 NativeWebRTC.SetEncoderBitrate(WebRtc.Handle, SourceId.Value, MediaType, value).
474 ThrowIfFailed("Failed to set encoder bitrate");
479 /// Enables the audio loopback. The local audio will be played with <paramref name="policy"/>.
481 /// <param name="policy">The <see cref="AudioStreamPolicy"/> to apply.</param>
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/>
487 /// <exception cref="ArgumentNullException"><paramref name="policy"/> is null.</exception>
488 /// <exception cref="InvalidOperationException">
489 /// MediaSource is not attached yet.<br/>
491 /// This MediaSource is not Audio
493 /// <exception cref="NotSupportedException">
494 /// <see cref="AudioStreamType"/> of <paramref name="policy"/> is not supported on the current platform.
496 /// <exception cref="ObjectDisposedException">
497 /// <paramref name="policy"/> or WebRTC has already been disposed.
499 /// <returns><see cref="MediaStreamTrack"/></returns>
500 public MediaStreamTrack EnableAudioLoopback(AudioStreamPolicy policy)
502 if (!SourceId.HasValue)
504 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
508 throw new ArgumentNullException(nameof(policy));
510 if (MediaType != MediaType.Audio)
512 throw new InvalidOperationException("AudioLoopback is only for Audio MediaSource");
515 var ret = NativeWebRTC.SetAudioLoopback(WebRtc.Handle, SourceId.Value, policy.Handle, out uint trackId);
517 if (ret == WebRTCErrorCode.InvalidArgument)
519 throw new NotSupportedException("The specified policy is not supported on the current system.");
522 ret.ThrowIfFailed("Failed to set the audio stream policy to the WebRTC");
524 return new MediaStreamTrack(WebRtc, MediaType, trackId);
527 private uint SetDisplay(Display display)
528 => display.ApplyTo(this);
530 internal void ReplaceDisplay(Display newDisplay)
532 _display?.SetOwner(null);
533 _display = newDisplay;
534 _display?.SetOwner(this);
538 /// Enables the video loopback. The local video will be diaplayed in <paramref name="display"/>.
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/>
546 /// This MediaSource is not Video
548 /// <exception cref="ObjectDisposedException">The WebRTC has already been disposed.</exception>
549 /// <returns><see cref="MediaStreamTrack"/></returns>
550 public MediaStreamTrack EnableVideoLoopback(Display display)
554 if (!SourceId.HasValue)
556 throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
560 throw new ArgumentNullException(nameof(display), "Display cannot be null.");
562 if (MediaType != MediaType.Video)
564 throw new InvalidOperationException("VideoLoopback is only for Video MediaSource");
567 if (display?.Owner != null)
569 if (ReferenceEquals(this, display.Owner))
571 throw new ArgumentException("The display has already been assigned to another.");
576 trackId = SetDisplay(display);
577 ReplaceDisplay(display);
580 return new MediaStreamTrack(WebRtc, MediaType, trackId);
583 uint IDisplayable<uint>.ApplyEvasDisplay(DisplayType type, ElmSharp.EvasObject evasObject)
585 Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
586 Debug.Assert(type != DisplayType.None);
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");
595 uint IDisplayable<uint>.ApplyEcoreWindow(IntPtr windowHandle)
597 NativeWebRTC.SetEcoreVideoLoopback(WebRtc.Handle, SourceId.Value, windowHandle, out uint trackId).
598 ThrowIfFailed("Failed to set ecore video loopback");