/*
* Copyright (c) 2021 Samsung Electronics Co., Ltd All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an AS IS BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using static Interop;
namespace Tizen.Multimedia.Remoting
{
///
/// MediaSource is a base class for sources.
///
/// 9
public abstract class MediaSource : IDisplayable
{
internal WebRTC WebRtc { get; set; }
internal uint? SourceId { get; set; }
private Display _display;
///
/// Gets the type of MediaSource.
///
///
/// 9
protected MediaType MediaType { get; }
private bool IsDetached {get; set;} = false;
///
/// Initializes a new instance of the class.
///
/// 9
protected MediaSource(MediaType mediaType)
{
MediaType = mediaType;
}
internal void AttachTo(WebRTC webRtc)
{
if (IsDetached)
{
throw new InvalidOperationException("MediaSource was already detached.");
}
OnAttached(webRtc);
}
internal void DetachFrom(WebRTC webRtc)
{
OnDetached(webRtc);
IsDetached = true;
}
internal abstract void OnAttached(WebRTC webRtc);
internal abstract void OnDetached(WebRTC webRtc);
///
/// Gets or sets the transceiver direction of current media source.
///
/// A that specifies the transceiver direction.
/// MediaSource is not attached yet.
/// The WebRTC has already been disposed.
/// 9
public TransceiverDirection TransceiverDirection
{
get
{
if (!SourceId.HasValue)
{
throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
}
NativeWebRTC.GetTransceiverDirection(WebRtc.Handle, SourceId.Value, MediaType, out TransceiverDirection mode).
ThrowIfFailed("Failed to get transceiver direction.");
return mode;
}
set
{
if (!SourceId.HasValue)
{
throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
}
NativeWebRTC.SetTransceiverDirection(WebRtc.Handle, SourceId.Value, MediaType, value).
ThrowIfFailed("Failed to get transceiver direction.");
}
}
///
/// Gets or sets the transceiver codec of current media source.
///
///
/// This API is not supported in , .
/// The WebRTC must be in the state when transceiver codec is set.
///
/// The transceiver codec.
///
/// MediaSource is not attached yet.
/// -or-
/// This MediaSource is not supported type of MediaSource.
/// -or-
/// The WebRTC is not in the valid state.
///
/// The WebRTC has already been disposed.
/// 10
public TransceiverCodec TransceiverCodec
{
get
{
if (!SourceId.HasValue)
{
throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
}
if (this is MediaFileSource || this is MediaPacketSource)
{
throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
}
NativeWebRTC.GetTransceiverCodec(WebRtc.Handle, SourceId.Value, MediaType, out TransceiverCodec codec).
ThrowIfFailed("Failed to get transceiver codec");
return codec;
}
set
{
if (!SourceId.HasValue)
{
throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
}
if (this is MediaFileSource || this is MediaPacketSource)
{
throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
}
WebRtc.ValidateWebRTCState(WebRTCState.Idle);
NativeWebRTC.SetTransceiverCodec(WebRtc.Handle, SourceId.Value, MediaType, value).
ThrowIfFailed("Failed to set transceiver codec");
}
}
///
/// Retrieves the supported transceiver codecs.
///
///
/// This API is not supported in , .
///
/// The transceiver codecs.
/// This MediaSource is not supported type of MediaSource.
/// The WebRTC has already been disposed.
/// 10
public ReadOnlyCollection SupportedTransceiverCodecs
{
get
{
if (this is MediaFileSource || this is MediaPacketSource)
{
throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
}
var codecs = new List();
Exception caught = null;
NativeWebRTC.RetrieveTransceiverCodecCallback cb = (codec, _) =>
{
try
{
codecs.Add(codec);
}
catch (Exception e)
{
caught = e;
return false;
}
return true;
};
using (var cbKeeper = ObjectKeeper.Get(cb))
{
NativeWebRTC.ForeachSupportedTransceiverCodec(WebRtc.Handle, GetMediaSourceType(), MediaType, cb).
ThrowIfFailed("failed to retrieve stats");
if (caught != null)
{
throw caught;
}
}
return new ReadOnlyCollection(codecs);
}
}
private MediaSourceType GetMediaSourceType()
{
if (this is MediaTestSource)
{
if (MediaType == MediaType.Audio)
{
return MediaSourceType.AudioTest;
}
else
{
return MediaSourceType.VideoTest;
}
}
else if (this is MediaMicrophoneSource)
{
return MediaSourceType.Microphone;
}
else if (this is MediaCameraSource)
{
return MediaSourceType.Camera;
}
else if (this is MediaScreenSource)
{
return MediaSourceType.Screen;
}
else if (this is MediaFileSource)
{
return MediaSourceType.File;
}
else if (this is MediaPacketSource)
{
return MediaSourceType.MediaPacket;
}
return MediaSourceType.Null;
}
///
/// Gets or sets the pause status of current media source.
///
/// A value that specifies the pause status.
/// MediaSource is not attached yet.
/// The WebRTC has already been disposed.
/// 9
public bool Pause
{
get
{
if (!SourceId.HasValue)
{
throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
}
NativeWebRTC.GetPause(WebRtc.Handle, SourceId.Value, MediaType, out bool isPaused).
ThrowIfFailed("Failed to get pause");
return isPaused;
}
set
{
if (!SourceId.HasValue)
{
throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
}
NativeWebRTC.SetPause(WebRtc.Handle, SourceId.Value, MediaType, value).
ThrowIfFailed("Failed to set pause");
}
}
///
/// Gets or sets the mute status of the current media source.
///
/// A value that specifies the mute status.
/// MediaSource is not attached yet.
/// The WebRTC has already been disposed.
/// 9
public bool Mute
{
get
{
if (!SourceId.HasValue)
{
throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
}
NativeWebRTC.GetMute(WebRtc.Handle, SourceId.Value, MediaType, out bool isMuted).
ThrowIfFailed("Failed to get mute");
return isMuted;
}
set
{
if (!SourceId.HasValue)
{
throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
}
NativeWebRTC.SetMute(WebRtc.Handle, SourceId.Value, MediaType, value).
ThrowIfFailed("Failed to set mute");
}
}
///
/// Gets or sets the video resolution of the current media source.
///
/// A value that specifies the video resolution.
///
/// MediaSource is not attached yet.
/// -or-
/// This MediaSource is not Video.
///
/// The WebRTC has already been disposed.
/// 9
public Size VideoResolution
{
get
{
if (!SourceId.HasValue)
{
throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
}
if (MediaType != MediaType.Video)
{
throw new InvalidOperationException("This property is only for video.");
}
NativeWebRTC.GetVideoResolution(WebRtc.Handle, SourceId.Value, out int width, out int height).
ThrowIfFailed("Failed to get video resolution");
return new Size(width, height);
}
set
{
if (!SourceId.HasValue)
{
throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
}
if (MediaType != MediaType.Video)
{
throw new InvalidOperationException("This property is only for video.");
}
NativeWebRTC.SetVideoResolution(WebRtc.Handle, SourceId.Value, value.Width, value.Height).
ThrowIfFailed("Failed to set video resolution");
}
}
///
/// Gets or sets the video frame rate of the current media source.
///
///
/// This API is only supported in video media source, especially ,
/// , .
///
/// A value that specifies the video frame rate.
/// VideoFrameRate is less than or equal to zero.
///
/// MediaSource is not attached yet.
/// -or-
/// This MediaSource is not Video.
/// -or-
/// This MediaSource is not supported type of MediaSource.
///
/// The WebRTC has already been disposed.
/// 10
public int VideoFrameRate
{
get
{
if (!SourceId.HasValue)
{
throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
}
if (MediaType != MediaType.Video)
{
throw new InvalidOperationException("This property is only for video.");
}
if (this is MediaFileSource || this is MediaPacketSource)
{
throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
}
NativeWebRTC.GetVideoFrameRate(WebRtc.Handle, SourceId.Value, out int frameRate).
ThrowIfFailed("Failed to get video frame rate");
return frameRate;
}
set
{
if (value <= 0)
{
throw new ArgumentException($"VideoFrameRate should be greater than zero.");
}
if (!SourceId.HasValue)
{
throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
}
if (MediaType != MediaType.Video)
{
throw new InvalidOperationException("This property is only for video.");
}
if (this is MediaFileSource || this is MediaPacketSource)
{
throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
}
NativeWebRTC.SetVideoFrameRate(WebRtc.Handle, SourceId.Value, value).
ThrowIfFailed("Failed to set video frame rate");
}
}
///
/// Gets or sets the encoder bitrate of the current media source.
///
///
/// This API is not supported in , .
///
/// A value that specifies the encoder bitrate.
/// EncoderBitrate is less than or equal to zero.
///
/// MediaSource is not attached yet.
/// -or-
/// This MediaSource is not Video.
/// -or-
/// This MediaSource is not supported type of MediaSource.
///
/// The WebRTC has already been disposed.
/// 10
public int EncoderBitrate
{
get
{
if (!SourceId.HasValue)
{
throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
}
if (this is MediaFileSource || this is MediaPacketSource)
{
throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
}
NativeWebRTC.GetEncoderBitrate(WebRtc.Handle, SourceId.Value, MediaType, out int bitrate).
ThrowIfFailed("Failed to get encoder bitrate");
return bitrate;
}
set
{
if (value <= 0)
{
throw new ArgumentException($"EncoderBitrate should be greater than zero.");
}
if (!SourceId.HasValue)
{
throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
}
if (this is MediaFileSource || this is MediaPacketSource)
{
throw new InvalidOperationException($"This property is not supported in {this.GetType()}.");
}
NativeWebRTC.SetEncoderBitrate(WebRtc.Handle, SourceId.Value, MediaType, value).
ThrowIfFailed("Failed to set encoder bitrate");
}
}
///
/// Enables the audio loopback. The local audio will be played with .
///
/// The to apply.
///
/// does not support all .
/// Supported types are , ,
/// .
///
/// is null.
///
/// MediaSource is not attached yet.
/// -or-
/// This MediaSource is not Audio
///
///
/// of is not supported on the current platform.
///
///
/// or WebRTC has already been disposed.
///
///
public MediaStreamTrack EnableAudioLoopback(AudioStreamPolicy policy)
{
if (!SourceId.HasValue)
{
throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
}
if (policy == null)
{
throw new ArgumentNullException(nameof(policy));
}
if (MediaType != MediaType.Audio)
{
throw new InvalidOperationException("AudioLoopback is only for Audio MediaSource");
}
var ret = NativeWebRTC.SetAudioLoopback(WebRtc.Handle, SourceId.Value, policy.Handle, out uint trackId);
if (ret == WebRTCErrorCode.InvalidArgument)
{
throw new NotSupportedException("The specified policy is not supported on the current system.");
}
ret.ThrowIfFailed("Failed to set the audio stream policy to the WebRTC");
return new MediaStreamTrack(WebRtc, MediaType, trackId);
}
private uint SetDisplay(Display display)
=> display.ApplyTo(this);
internal void ReplaceDisplay(Display newDisplay)
{
_display?.SetOwner(null);
_display = newDisplay;
_display?.SetOwner(this);
}
///
/// Enables the video loopback. The local video will be diaplayed in .
///
/// The to apply.
/// The display has already been assigned to another.
/// is null.
///
/// MediaSource is not attached yet.
/// -or-
/// This MediaSource is not Video
///
/// The WebRTC has already been disposed.
///
public MediaStreamTrack EnableVideoLoopback(Display display)
{
uint trackId = 0;
if (!SourceId.HasValue)
{
throw new InvalidOperationException("MediaSource is not attached yet. Call AddSource() first.");
}
if (display == null)
{
throw new ArgumentNullException(nameof(display), "Display cannot be null.");
}
if (MediaType != MediaType.Video)
{
throw new InvalidOperationException("VideoLoopback is only for Video MediaSource");
}
if (display?.Owner != null)
{
if (ReferenceEquals(this, display.Owner))
{
throw new ArgumentException("The display has already been assigned to another.");
}
}
else
{
trackId = SetDisplay(display);
ReplaceDisplay(display);
}
return new MediaStreamTrack(WebRtc, MediaType, trackId);
}
uint IDisplayable.ApplyEvasDisplay(DisplayType type, ElmSharp.EvasObject evasObject)
{
Debug.Assert(Enum.IsDefined(typeof(DisplayType), type));
Debug.Assert(type != DisplayType.None);
NativeWebRTC.SetVideoLoopback(WebRtc.Handle, SourceId.Value,
type == DisplayType.Overlay ? WebRTCDisplayType.Overlay : WebRTCDisplayType.Evas, evasObject,
out uint trackId).ThrowIfFailed("Failed to set video loopback");
return trackId;
}
uint IDisplayable.ApplyEcoreWindow(IntPtr windowHandle)
{
NativeWebRTC.SetEcoreVideoLoopback(WebRtc.Handle, SourceId.Value, windowHandle, out uint trackId).
ThrowIfFailed("Failed to set ecore video loopback");
return trackId;
}
}
}