Files
RaindropViewer/Assets/Raindrop/Core/Media/MediaObject.cs
T

296 lines
9.4 KiB
C#

/**
* Radegast Metaverse Client
* Copyright(c) 2009-2014, Radegast Development Team
* Copyright(c) 2016-2020, Sjofn, LLC
* All rights reserved.
*
* Radegast is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.If not, see<https://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Threading;
using System.Runtime.InteropServices;
using FMOD;
using OpenMetaverse;
namespace Raindrop.Media
{
public abstract class MediaObject : object, IDisposable
{
/// <summary>
/// Indicates if this object's resources have already been disposed
/// </summary>
public bool Disposed { get; private set; } = false;
protected bool finished = false;
/// All commands are made through queued delegate calls, so they
/// are guaranteed to take place in the same thread. FMOD requires this.
public delegate void SoundDelegate();
/// Queue of sound commands
///
protected static Queue<SoundDelegate> queue;
protected static MediaManager manager;
protected static Dictionary<UUID,BufferSound> allBuffers;
// A SOUND represents the data (buffer or stream)
public Sound FMODSound => sound;
protected Sound sound;// = null;
// A CHANNEL represents a playback instance of a sound.
public Channel FMODChannel => channel;
protected Channel channel;// = null;
// Additional info for callbacks goes here.
protected CREATESOUNDEXINFO extraInfo;
protected static CHANNELCONTROL_CALLBACK endCallback;
// Vectors used for orienting spatial axes.
protected static VECTOR UpVector;
protected static VECTOR ZeroVector;
/// <summary>
/// Base FMOD system object, of which there is only one.
/// </summary>
protected static FMOD.System system;// = null;
public FMOD.System FMODSystem => system;
protected static float AllObjectVolume = 0.8f;
protected MediaObject()
{
// Zero out the extra info structure.
extraInfo = new CREATESOUNDEXINFO();
extraInfo.cbsize = Marshal.SizeOf(extraInfo);
}
protected bool Cloned = false;
public virtual void Dispose()
{
if (!Cloned && sound.hasHandle())
{
sound.release();
sound.clearHandle();
}
Disposed = true;
}
//public bool Active => (sound != null);
/// <summary>
/// Put a delegate call on the command queue. These will be executed on
/// the FMOD control thread. All FMOD calls must happen there.
/// </summary>
/// <param name="action"></param>
protected void invoke(SoundDelegate action)
{
// Do nothing if queue not ready yet.
if (queue == null) return;
// Put that on the queue and wake up the background thread.
lock (queue)
{
queue.Enqueue( action );
Monitor.Pulse(queue);
}
}
/// <summary>
/// Change a playback volume
/// </summary>
protected float volume = 0.8f;
public float Volume
{
get => volume;
set
{
volume = value;
//if (channel == null) return;
invoke(new SoundDelegate(delegate
{
try
{
FMODExec(channel.setVolume(volume));
}
catch (Exception ex)
{
Logger.Log(
$"Error on volume change on channel {channel.handle.ToString("X")} sound {sound.handle.ToString("X")} finished {finished}",
Helpers.LogLevel.Error,
ex);
}
}));
}
}
/// <summary>
/// Update the 3D position of a sound source.
/// </summary>
protected VECTOR position = new VECTOR();
public Vector3 Position
{
set
{
position = FromOMVSpace(value); //check position is problem?
//if (channel == null) return;
invoke(new SoundDelegate(delegate
{
try
{
FMODExec(channel.set3DAttributes(
ref position,
ref ZeroVector));
}
catch (Exception ex)
{
Logger.Log(
$"Error on position change on channel {channel.handle.ToString("X")} sound {sound.handle.ToString("X")} finished {finished}",
Helpers.LogLevel.Error, ex);
}
}));
}
}
public void Stop()
{
//if (channel != null)
{
invoke(new SoundDelegate(delegate
{
FMODExec(channel.stop());
}));
}
}
/// <summary>
/// Convert OpenMetaVerse to FMOD coordinate space.
/// </summary>
/// <param name="omvV"></param>
/// <returns></returns>
protected VECTOR FromOMVSpace(Vector3 omvV)
{
// OMV X is forward/East, Y is left/North, Z is up.
// FMOD Z is forward/East, X is right/South, Y is up.
VECTOR v = new VECTOR
{
x = -omvV.Y,
y = omvV.Z,
z = omvV.X
};
return v;
}
protected static Dictionary<IntPtr,MediaObject> allSounds;
protected static Dictionary<IntPtr, MediaObject> allChannels;
protected void RegisterSound(Sound sound)
{
IntPtr raw = sound.handle;
if (allSounds.ContainsKey(raw))
allSounds.Remove(raw);
allSounds.Add(raw, this);
}
protected void RegisterChannel(Channel channel)
{
IntPtr raw = channel.handle;
if (allChannels.ContainsKey(raw))
allChannels.Remove(raw);
allChannels.Add(raw, this);
}
protected void UnRegisterSound()
{
//if (sound == null) return;
IntPtr raw = sound.handle;
if (allSounds.ContainsKey( raw ))
{
allSounds.Remove( raw );
}
}
protected void UnRegisterChannel()
{
//if (channel == null) return;
IntPtr raw = channel.handle;
if (allChannels.ContainsKey(raw))
{
allChannels.Remove(raw);
}
}
/// <summary>
/// A callback for asynchronous FMOD calls.
/// </summary>
/// <returns></returns>
// Subclasses override these methods to handle callbacks.
protected virtual RESULT EndCallbackHandler() { return RESULT.OK; }
/// <summary>
/// Main handler for playback-end callback.
/// </summary>
/// <param name="channelraw"></param>
/// <param name="controltype"></param>
/// <param name="type"></param>
/// <param name="commanddata1"></param>
/// <param name="commanddata2"></param>
/// <returns></returns>
/// We changed this to static, in hope that it will fix that nasty IL2cpp does not support marshalling runtime error
[AOT.MonoPInvokeCallback(typeof(CHANNELCONTROL_CALLBACK))]
protected static RESULT DispatchEndCallback(
IntPtr channelraw,
CHANNELCONTROL_TYPE controltype,
CHANNELCONTROL_CALLBACK_TYPE type,
IntPtr commanddata1,
IntPtr commanddata2)
{
// There are several callback types
if (type == CHANNELCONTROL_CALLBACK_TYPE.END)
{
if (allChannels.ContainsKey(channelraw))
{
MediaObject sndobj = allChannels[channelraw];
return sndobj.EndCallbackHandler();
}
}
return RESULT.OK;
}
protected static void FMODExec(RESULT result)
{
if (result != RESULT.OK)
{
throw new MediaException($"FMOD error! {result} - {Error.String(result)}");
}
}
protected static void FMODExec(RESULT result, string trace)
{
Logger.Log($"FMOD call {trace} returned {result}", Helpers.LogLevel.Info);
if (result != RESULT.OK)
{
throw new MediaException($"FMOD error! {result} - {Error.String(result)}");
}
}
}
}