/* * 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); } } }