/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* 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.
*/
// @lint-ignore-every LICENSELINT
using System;
using UnityEngine;
namespace Oculus.Haptics
{
///
/// HapticClipPlayer provides controls for playing a HapticClip.
///
///
///
///
/// A HapticClipPlayer only plays valid HapticClips. You can start and
/// stop a HapticClip assigned to a HapticClipPlayer as often as required.
///
///
///
/// A HapticClipPlayer can be in a stopped, playing, or paused state.
///
/// By default a HapticClipPlayer is in a stopped state. A player returns to the stopped state when the loaded clip reaches its end during playback,
/// or by explicitly calling .
///
/// When calling the player enters a playing state.
///
/// A HapticClipPlayer in the playing state can enter a paused state by calling .
/// Playback can be unpaused (i.e. playing) from the current paused playback position by calling
/// or .
///
/// Calling on a playing player has no effect.
///
/// Calling on a playing player makes it play again from the start.
///
/// Calling on a stopped player will move into a paused state. The playback location defaults to both controllers in this case,
/// making it possible to call . To deliberately start playback from the seeked playback location, use .
///
///
///
/// The rendered amplitude and frequency can be modulated during runtime using the
/// and properties respectively.
///
/// You can also loop a clip using the property.
///
///
///
/// It is possible to release HapticClipPlayer objects as needed to free up memory using the method.
/// Of course, calling any method on a released HapticClipPlayer will cause a runtime error.
///
///
public class HapticClipPlayer : IDisposable
{
/// The internal ID of the associated with the HapticClipPlayer. This ID is used internally to identify the
/// clip.
///
/// The _clipID is set when creating a new HapticClipPlayer instance with a HapticClip, typically through
/// the constructor and when assigning the clip via the clip property
private int _clipId = Ffi.InvalidId;
/// The internal ID of the HapticClipPlayer. As long as the player has an ID, it is considered to be in a valid state and can play.
/// If the player is explicitly disposed, its ID is invalidated.
private int _playerId = Ffi.InvalidId;
///
/// The implementation of for HapticClipPlayer to use. This field is protected to allow derived
/// classes to provide a custom implementation.
/// The HapticClipPlayer uses this instance to play haptic clips and access haptic-related functionality.
///
protected Haptics _haptics;
///
/// Creates a HapticClipPlayer with no assigned to it.
///
///
///
/// You must assign a HapticClip before you can play this HapticClipPlayer.
/// You can either call the overloaded version of this constructor that accepts
/// a HapticClip or assign it with .
///
public HapticClipPlayer()
{
SetHaptics();
// Create player and check if that succeeded.
int playerReturnValue = _haptics.CreateHapticPlayer();
if (Ffi.InvalidId != playerReturnValue)
{
_playerId = playerReturnValue;
}
}
///
/// Creates a HapticClipPlayer and assigns the given to it. You can use
/// this player to play, stop, and generally control the haptic clip's playback properties.
///
///
/// The HapticClip to be played by this HapticClipPlayer.
/// Providing invalid clip data (e.g., null or empty) will throw an ArgumentNullException.
///
public HapticClipPlayer(HapticClip clip)
{
SetHaptics();
// Create player and check if that succeeded.
int playerReturnValue = _haptics.CreateHapticPlayer();
if (Ffi.InvalidId != playerReturnValue)
{
_playerId = playerReturnValue;
this.clip = clip;
}
}
///
/// Sets the implementation that HapticClipPlayer will call
/// into for all haptics operations.
/// See also: for more information on Haptics.
///
protected virtual void SetHaptics()
{
_haptics = Haptics.Instance;
}
///
/// Starts playing the assigned haptic clip on the specified controller.
///
///
/// The controller(s) to play back on. See
///
public void Play(Controller controller)
{
_haptics.PlayHapticPlayer(_playerId, controller);
}
///
/// Pauses playback on the HapticClipPlayer.
/// If a haptic clip is currently playing, it will be paused immediately and maintain it's current playback position.
/// You can call this method at any time to pause playback, regardless of whether a clip is currently playing or not.
///
public void Pause()
{
_haptics.PauseHapticPlayer(_playerId);
}
///
/// Resumes playback on the HapticClipPlayer.
/// If the playback of a haptic clip is currently paused, playback will be resumed immediately on the controller previously defined by .
/// If the clip player is currently stopped or already playing, calling this method has no effect.
/// If playback was previously seeked on a stopped clip player, playback will resume on both controllers by default from the seeked playback position.
///
public void Resume()
{
_haptics.ResumeHapticPlayer(_playerId);
}
///
/// Stops playback of the HapticClipPlayer.
/// If a haptic clip is currently playing, it will be stopped immediately.
/// You can call this method at any time to stop playback, regardless of whether a clip is currently playing or not.
///
public void Stop()
{
_haptics.StopHapticPlayer(_playerId);
}
///
/// Moves the current playback position of the HapticClipPlayer to the provided time in seconds.
/// If a haptic clip is currently playing, the playback position will jump to the provided time immediately.
/// If the player is currently paused or stopped, it will require a deliberate call to or
/// to start playback from the seeked playback position.
///
///
/// The target time in seconds to move the current playback position to.
public void Seek(float time)
{
_haptics.SeekPlaybackPositionHapticPlayer(_playerId, time);
}
///
/// Whether looping is enabled or not. When set to true, the haptic clip will loop continuously
/// until stopped.
///
///
/// true if looping is enabled.
public bool isLooping
{
get => _haptics.IsHapticPlayerLooping(_playerId);
set => _haptics.LoopHapticPlayer(_playerId, value);
}
///
/// Gets the duration of the loaded haptic clip of this HapticClipPlayer's instance.
///
///
/// The duration of the haptic clip in seconds.
///
/// This property returns the length of the haptic clip in seconds. If no haptic clip is loaded, this property will return 0.
///
public float clipDuration => _haptics.GetClipDuration(_clipId);
///
/// Sets/gets the HapticClipPlayer's amplitude.
///
/// During playback, the individual amplitudes in the clip will be multiplied by the player's amplitude.
/// This changes how strong the vibration is. Amplitude values in a clip range from 0.0 to 1.0,
/// and the result after applying the amplitude scale will be clipped to that range.
///
/// An amplitude of 0.0 means that no vibration will be triggered, and an amplitude of 0.5 will
/// result in the clip being played back at half of its amplitude.
///
/// Example: if you apply amplitude of 5.0 to a haptic clip and the following amplitudes are in the
/// clip: [0.2, 0.5, 0.1], the initial amplitude calculation would produce these values: [1.0, 2.5, 0.5]
/// which will then be clamped like this: [1.0, 1.0, 0.5]
///
/// This method can be called during active playback, in which case the amplitude is applied
/// immediately, with a small delay in the tens of milliseconds.
///
///
/// A value of zero or greater. Negative values will
/// cause an exception.
public float amplitude
{
get => _haptics.GetAmplitudeHapticPlayer(_playerId);
set => _haptics.SetAmplitudeHapticPlayer(_playerId, value);
}
///
/// Sets/gets the HapticClipPlayer's frequency shift.
///
/// The frequencies in a haptic clip are in the range zero to one. This property shifts the
/// individual frequencies up or down. The acceptable range of values is -1.0 to 1.0 inclusive.
///
/// Once the frequencies in a clip have been shifted, they will be clamped to the playable
/// range of frequency values, i.e. zero to one.
///
/// Setting this property to 0.0 means that the frequencies will not be changed.
///
/// Example: if you apply a frequency shift of 0.8 to a haptic clip and the following frequencies
/// are in the clip: [0.1, 0.5, 0.0], the initial frequency shift calculation will produce these
/// frequencies: [0.9, 1.3, 0.8] which will then be clamped like this: [0.9, 1.0, 0.8]
///
/// This method can be called during active playback, in which case the frequency shift is applied
/// immediately, with a small delay in the tens of milliseconds.
///
///
/// A value between -1.0 and 1.0. Values outside this range will cause an exception.
public float frequencyShift
{
get => _haptics.GetFrequencyShiftHapticPlayer(_playerId);
set => _haptics.SetFrequencyShiftHapticPlayer(_playerId, value);
}
///
/// Sets/gets the HapticClipPlayer's playback priority.
///
/// The playback engine of the Haptics SDK only ever renders the vibrations of a single HapticClipPlayer
/// clip on the same controller at the same time. Meaning, haptic clips are not "mixed" when played back.
/// If you have multiple players playing on the same controller at the same time, then only the player with the
/// highest priority will trigger vibrations.
///
/// Given the same priority value, the engine will always play the most recent clip that was started. All other
/// players are muted, but continue tracking playback on their respective timeline (i.e. they are not paused).
/// If a clip finishes (or is stopped), the engine will resume playback of the second most recent clip with an
/// equal or higher priority level and so on.
///
/// Example: Setting priority can be helpful if some haptic clips are more important than others, and allow us to
/// design a hierarchy of haptic feedback based on context or the overall importance. For example, we could want
/// a user to always receive a distinct haptic feedback if they are hit. Setting this "hit" clips priority higher
/// compared to other haptic clips will ensure that the user always receives this haptic feedback.
///
/// Priority values can be on the range of 0 (high priority) to 255 (low priority).
/// By default, the priority value is set to 128 for every HapticClipPlayer.
/// The player's priority can be changed before and during playback.
///
///
/// An integer value within the range 0 to 255 (inclusive).
/// Values outside this range will cause an exception.
public uint priority
{
get => _haptics.GetPriorityHapticPlayer(_playerId);
set => _haptics.SetPriorityHapticPlayer(_playerId, value);
}
///
/// Sets the HapticClipPlayer's current haptic clip.
///
/// This feature allows you to change the clip loaded in a clip player.
/// If the player is currently playing it will be stopped. All other properties like amplitude, frequency
/// shift, looping and priority are kept.
///
///
/// A valid, JSON formatted, UTF-8 encoded haptic clip.
/// Providing invalid clip data will cause an exception.
public HapticClip clip
{
set
{
int returnValue = _haptics.LoadClip(value.json);
if (Ffi.InvalidId != returnValue)
{
_haptics.SetHapticPlayerClip(_playerId, returnValue);
// Remove previously assigned haptic clip.
if (_clipId != Ffi.InvalidId)
{
_haptics.ReleaseClip(_clipId);
}
_clipId = returnValue;
}
}
}
///
/// Call this method to explicitly/deterministically release a HapticClipPlayer object, otherwise
/// the garbage collector will release it. Of course, any calls to a disposed HapticClipPlayer
/// will result in runtime errors.
///
///
///
/// A given HapticClip will not be freed until all HapticClipPlayers to which
/// it is assigned have also been freed.
///
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
///
/// Releases the assigned clip and clip player from memory.
///
protected virtual void Dispose(bool disposing)
{
if (_playerId != Ffi.InvalidId)
{
if (!_haptics.ReleaseClip(_clipId) & _haptics.ReleaseHapticPlayer(_playerId))
{
Debug.LogError($"Error: HapticClipPlayer or HapticClip could not be released");
}
_clipId = Ffi.InvalidId;
_playerId = Ffi.InvalidId;
}
}
~HapticClipPlayer()
{
Dispose(false);
}
}
}