/*
* Copyright (c) 2016 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.Diagnostics;
namespace Tizen.Multimedia
{
///
/// Provides the ability to control the sound stream.
///
/// 3
public class AudioStreamPolicy : IDisposable
{
private AudioStreamPolicyHandle _handle;
private bool _disposed = false;
private Interop.AudioStreamPolicy.FocusStateChangedCallback _focusStateChangedCallback;
private static AudioDevice _inputDevice = null;
private static AudioDevice _outputDevice = null;
private const string Tag = "Tizen.Multimedia.AudioStreamPolicy";
///
/// Initializes a new instance of the class with .
///
///
/// To apply the stream policy according to this stream information, the AudioStreamPolicy should
/// be passed to other APIs related to playback or recording. (For example., ,
/// , etc.)
///
/// The type of the sound stream for which the policy needs to be created.
/// is invalid.
/// 3
public AudioStreamPolicy(AudioStreamType streamType)
{
ValidationUtil.ValidateEnum(typeof(AudioStreamType), streamType, nameof(streamType));
_focusStateChangedCallback = (IntPtr streamInfo, AudioStreamFocusOptions focusMask,
AudioStreamFocusState state, AudioStreamFocusChangedReason reason, AudioStreamBehaviors behaviors,
string extraInfo, IntPtr _) =>
{
FocusStateChanged?.Invoke(this,
new AudioStreamPolicyFocusStateChangedEventArgs(focusMask, state, reason, behaviors, extraInfo));
};
Interop.AudioStreamPolicy.Create(streamType, _focusStateChangedCallback,
IntPtr.Zero, out _handle).ThrowIfError("Unable to create stream information");
Debug.Assert(_handle != null);
}
///
/// Occurs when the state of focus that belongs to the current AudioStreamPolicy is changed.
///
///
/// The event is raised in the internal thread.
///
/// 4
public event EventHandler FocusStateChanged;
///
/// Gets the .
///
///
/// If the of the current AudioStreamPolicy is ,
/// it returns .
///
/// The of the policy instance.
/// The has already been disposed of.
/// 3
public AudioVolumeType VolumeType
{
get
{
var ret = Interop.AudioStreamPolicy.GetSoundType(Handle, out var type);
if (ret == AudioManagerError.NoData)
{
return AudioVolumeType.None;
}
ret.ThrowIfError("Failed to get volume type");
return type;
}
}
private AudioStreamFocusState GetFocusState(bool playback)
{
int ret = Interop.AudioStreamPolicy.GetFocusState(Handle, out var stateForPlayback, out var stateForRecording);
MultimediaDebug.AssertNoError(ret);
return playback ? stateForPlayback : stateForRecording;
}
///
/// Gets the state of focus for the playback.
///
/// The state of focus for playback.
/// The has already been disposed of.
/// 3
public AudioStreamFocusState PlaybackFocusState => GetFocusState(true);
///
/// Gets the state of focus for the recording.
///
/// The state of focus for recording.
/// The has already been disposed of.
/// 3
public AudioStreamFocusState RecordingFocusState => GetFocusState(false);
///
/// Gets or sets the auto focus reacquisition.
///
///
/// true if the auto focus reacquisition is enabled; otherwise, false.
/// The default is true.
///
///
/// If you don't want to reacquire the focus you've lost automatically,
/// disable the focus reacquisition.
///
/// The has already been disposed of.
/// 3
public bool FocusReacquisitionEnabled
{
get
{
Interop.AudioStreamPolicy.GetFocusReacquisition(Handle, out var enabled).
ThrowIfError("Failed to get focus reacquisition state");
return enabled;
}
set
{
Interop.AudioStreamPolicy.SetFocusReacquisition(Handle, value).
ThrowIfError("Failed to set focus reacquisition");
}
}
internal AudioStreamPolicyHandle Handle
{
get
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(AudioStreamPolicy));
}
return _handle;
}
}
///
/// Acquires the stream focus.
///
/// The focuses that you want to acquire.
/// The requesting behaviors.
/// The extra information for this request. This value can be null.
/// is zero.
///
/// contain a invalid bit.
/// -or-
/// contain a invalid bit.
///
/// The focus has already been acquired.
/// Called in raised by releasing focus.
/// The has already been disposed of.
/// 3
public void AcquireFocus(AudioStreamFocusOptions options, AudioStreamBehaviors behaviors, string extraInfo)
{
if (options == 0)
{
throw new ArgumentException("options can't be zero.", nameof(options));
}
if (options.IsValid() == false)
{
throw new ArgumentOutOfRangeException(nameof(options), options, "options contains a invalid bit.");
}
if (behaviors.IsValid() == false)
{
throw new ArgumentOutOfRangeException(nameof(behaviors), behaviors, "behaviors contains a invalid bit.");
}
Interop.AudioStreamPolicy.AcquireFocus(Handle, options, behaviors, extraInfo).
ThrowIfError("Failed to acquire focus");
}
///
/// Releases the acquired focus.
///
/// The focus mask that you want to release.
/// The requesting behaviors.
/// The extra information for this request. This value can be null.
/// is zero.
///
/// contain a invalid bit.
/// -or-
/// contain a invalid bit.
///
/// The focus has not been acquired.
/// The has already been disposed of.
/// 3
public void ReleaseFocus(AudioStreamFocusOptions options, AudioStreamBehaviors behaviors, string extraInfo)
{
if (options == 0)
{
throw new ArgumentException("options can't be zero.", nameof(options));
}
if (options.IsValid() == false)
{
throw new ArgumentOutOfRangeException(nameof(options), options, "options contains a invalid bit.");
}
if (behaviors.IsValid() == false)
{
throw new ArgumentOutOfRangeException(nameof(behaviors), behaviors, "behaviors contains a invalid bit.");
}
Interop.AudioStreamPolicy.ReleaseFocus(Handle, options, behaviors, extraInfo).
ThrowIfError("Failed to release focus");
}
///
/// Applies the stream routing.
///
///
/// If the stream has not been made yet, this will be applied when the stream starts to play.
///
///
///
/// The has already been disposed of.
/// 3
public void ApplyStreamRouting()
{
Interop.AudioStreamPolicy.ApplyStreamRouting(Handle).ThrowIfError("Failed to apply stream routing");
}
///
/// Adds a device for the stream routing.
///
/// The device to add.
///
/// The available is and .
///
///
/// The device is not connected.
/// -or-
/// An internal error occurs.
///
/// is null.
/// of is unavailable for this.
/// The has already been disposed of.
///
///
/// 3
public void AddDeviceForStreamRouting(AudioDevice device)
{
if (device == null)
{
throw new ArgumentNullException(nameof(device));
}
var ret = Interop.AudioStreamPolicy.AddDeviceForStreamRouting(Handle, device.Id);
if (ret == AudioManagerError.NoData)
{
throw new InvalidOperationException("The device seems not connected.");
}
ret.ThrowIfError("Failed to add device for stream routing");
}
///
/// Removes the device for the stream routing.
///
/// The device to remove.
///
/// The available is and .
///
/// An internal error occurs.
/// is null.
/// The has already been disposed of.
///
/// 3
public void RemoveDeviceForStreamRouting(AudioDevice device)
{
if (device == null)
{
throw new ArgumentNullException(nameof(device));
}
Interop.AudioStreamPolicy.RemoveDeviceForStreamRouting(Handle, device.Id).
ThrowIfError("Failed to remove device for stream routing");
}
///
/// Gets or sets the preferred input device.
///
///
/// The instance.
/// The default is null which means any device is not set on this property.
///
///
/// This property is to set a specific built-in device when the system has multiple devices of the same built-in device type.
/// When there's only one device for a built-in device type in the system, nothing will happen even if this property is set successfully.
///
/// A device is not for input.
/// An internal error occurs.
/// A device is not supported by this instance.
/// The has already been disposed of.
///
/// 6
public AudioDevice PreferredInputDevice
{
get
{
/* This P/Invoke intends to validate if the core audio system
* is normal. Otherwise, it'll throw an error here. */
Interop.AudioStreamPolicy.GetPreferredDevice(Handle, out var inDeviceId, out _).
ThrowIfError("Failed to get preferred input device");
Log.Debug(Tag, $"preferred input device id:{inDeviceId}");
return _inputDevice;
}
set
{
Interop.AudioStreamPolicy.SetPreferredDevice(Handle, AudioDeviceIoDirection.Input, value?.Id ?? 0).
ThrowIfError("Failed to set preferred input device");
_inputDevice = value;
}
}
///
/// Gets or sets the preferred output device.
///
///
/// The instance.
/// The default is null which means any device is not set on this property.
///
///
/// This property is to set a specific built-in device when the system has multiple devices of the same built-in device type.
/// When there's only one device for a built-in device type in the system, nothing will happen even if this property is set successfully.
///
/// A device is not for output.
/// An internal error occurs.
/// A device is not supported by this instance.
/// The has already been disposed of.
///
/// 6
public AudioDevice PreferredOutputDevice
{
get
{
/* This P/Invoke intends to validate if the core audio system
* is normal. Otherwise, it'll throw an error here. */
Interop.AudioStreamPolicy.GetPreferredDevice(Handle, out _, out var outDeviceId).
ThrowIfError("Failed to get preferred output device");
Log.Debug(Tag, $"preferred output device id:{outDeviceId}");
return _outputDevice;
}
set
{
Interop.AudioStreamPolicy.SetPreferredDevice(Handle, AudioDeviceIoDirection.Output, value?.Id ?? 0).
ThrowIfError("Failed to set preferred output device");
_outputDevice = value;
}
}
///
/// Checks if any stream from the current AudioStreamPolicy is using the device.
///
/// true if any audio stream from the current AudioStreamPolicy is using the device; otherwise, false.
/// The device to be checked.
///
/// The AudioStreamPolicy can be applied to each playback or recording stream via other API set.
/// (For example., , ,
/// , , etc.)
/// This method returns true only when the device is used for the stream which meets to the two conditions.
/// One is that the current AudioStreamPolicy sets a audio route path to the device and the other is that the playback
/// or recording stream from other API set should have already started to prepare or to play.(It depends on the API set.)
///
/// is null.
/// An internal error occurs.
/// The has already been disposed of.
///
/// 6
public bool HasStreamOnDevice(AudioDevice device)
{
if (device == null)
{
throw new ArgumentNullException(nameof(device));
}
var ret = Interop.AudioStreamPolicy.IsStreamOnDevice(Handle, device.Id, out var isOn);
ret.ThrowIfError("Failed to check stream on device");
return isOn;
}
///
/// Releases all resources used by the .
///
/// 3
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Releases the unmanaged resources used by the .
///
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
/// 3
protected virtual void Dispose(bool disposing)
{
if (_disposed)
{
return;
}
if (disposing)
{
if (_handle != null)
{
_handle.Dispose();
}
_disposed = true;
}
}
#region Static events
private static bool _isWatchCallbackRegistered;
private static EventHandler _streamFocusStateChanged;
private static Interop.AudioStreamPolicy.FocusStateWatchCallback _focusStateWatchCallback;
private static readonly object _streamFocusEventLock = new object();
///
/// Occurs when the focus state for stream types is changed regardless of the process.
///
/// 3
public static event EventHandler StreamFocusStateChanged
{
add
{
lock (_streamFocusEventLock)
{
if (_isWatchCallbackRegistered == false)
{
RegisterFocusStateWatch();
_isWatchCallbackRegistered = true;
}
_streamFocusStateChanged += value;
}
}
remove
{
lock (_streamFocusEventLock)
{
_streamFocusStateChanged -= value;
}
}
}
private static void RegisterFocusStateWatch()
{
_focusStateWatchCallback = (id, options, focusState, reason, extraInfo, _) =>
{
_streamFocusStateChanged?.Invoke(null,
new StreamFocusStateChangedEventArgs(options, focusState, reason, extraInfo));
};
Interop.AudioStreamPolicy.AddFocusStateWatchCallback(
AudioStreamFocusOptions.Playback | AudioStreamFocusOptions.Recording,
_focusStateWatchCallback, IntPtr.Zero, out var cbId).
ThrowIfError("Failed to initialize focus state event");
}
#endregion
}
}