-
Notifications
You must be signed in to change notification settings - Fork 18
3. Lofelt SDK for Android
Lofelt has developed an Android library to simplify the integration of haptics into applications. The applications you create with this library will work on Android phones that meet the Device requirements set forth earlier in this document.
The library provides a mechanism to play a pre-authored haptic clip. This method allows a haptic effect to match a sound file used in your app—for example, when you want to play the haptic effect of a door closing simultaneously with the sound of the door closing.
Before jumping ahead to the section on playing haptic clips, please read the following sections on adding the correct library to your project and initializing it.
Simply copy the LofeltHaptics.aar
file from the sdk/android-library
folder
within the Lofelt SDK download to the libs
folder of your Android Project.
The library should then appear in Android Studio:
Once you have copied the LofeltHaptics.aar
file into your project, you will
also need to add the following line at the bottom of the dependencies
section
of the app's build.gradle
file:
implementation files('libs/LofeltHaptics.aar')
The Lofelt SDK download contains API documentation for the Android library in
HTML format. It can be viewed in a browser by opening
android-library/docs/index.html
. It can also be integrated into Android Studio
to provide context-sensitive help, by following the steps at
https://stackoverflow.com/a/34721648/1005419.
To use Lofelt SDK haptics in your app, you must first create a
LofeltHaptics
object. You will then be able to play and control haptic clips
with that object. Note that all of the following code examples are written in
Java.
import com.lofelt.haptics.LofeltHaptics;
LofeltHaptics haptics = new LofeltHaptics(this);
The constructor of LofeltHaptics
needs a Context
object as a parameter to
trigger vibrations. Therefore, a good place to initialize LofeltHaptics
would
be in the onCreate()
method of your activity.
Each LofeltHaptics
object you create is capable of playing back one haptic
file at a time.
Haptic clips use the .haptic file format. A haptic clip contains high-definition, device-agnostic haptic data that the Studio library for Android uses to create an optimal haptic effect on playback devices. The playback of these haptic clips requires only three simple steps:
- Load the haptic clip into the
LofeltHaptics
object with theload()
method. - Start playback of the haptic clip with the
play()
method. - Stop playback of the haptic clip with the
stop()
method (which is only needed when you wish to stop the haptic clip before it has finished playing).
public boolean deviceMeetsMinimumRequirements()
The deviceMeetsMinimumRequirements()
method allows you to easily check if the
Android device meets the minimum requirements to playback haptics designed with
the .haptic file format. At the moment, the Android minimum requirements are:
- The device allows access to the hardware vibrator
- The device has amplitude control capability.
It will return true
if minimum requirements are met, or false
otherwise.
If the device does not the minimum requirements, play()
will throw an
exception (see below). You can therefore bypass haptic calls in your application
if the device doesn't support the needed haptic abilities.
public void load(byte[] clip)
The load()
method allows you to load the contents of a haptic clip into memory
before triggering playback with play()
.
public void play()
The play()
method initiates haptic playback of the haptic clip currently
loaded into the LofeltHaptics
instance.
Playback of the haptic effect always starts from the beginning of the haptic
clip and automatically stops when playback reaches the end of the haptic clip.
However, you can stop playback of the haptic effect prematurely with the
stop()
method.
play()
will throw a RuntimeException
if the device is not capable of
advanced haptics.
❗NOTE: On Android 11 and lower, playback of haptic clips with more than ~50 breakpoints might feel "stretched" during playback making clips play longer than designed. However, if your app is CPU load intensive, this might not happen. This is a limitation of the Android Vibration API and it has been fixed by the Android team for Android 12.
public void stop()
If a LofeltHaptics
object is currently playing a haptic clip, the stop()
method terminates playback. If a haptic clip is not playing, the stop()
method
will have no effect.
Using the stop()
method leaves the currently loaded haptic clip in memory. A
subsequent play()
method will play the same haptic clip again starting from
the beginning.
public void seek(float time)
The seek(float time)
jumps to a absolute time position in the haptic clip.
time
corresponds to a time value since the beginning of the clip, in seconds.
The playback will always be stopped when this function is called. This means
play()
needs to be called again to play from the new time
position.
Also:
- If a seek beyond the end of the clip has occurred, play will not reproduce any haptics until a seek to a position within the clip occurs
- Seeking to a negative position will seek to the beginning of the clip.
public loop(boolean enabled)
This method allows the playback of a haptic clip to loop. This means playback will repeat from the beginning once it reaches the end.
Changing the looping state will only take effect in the next call to play()
,
it does not affect a clip that is currently playing
When a new clip is loaded, looping is disabled.
If seek()
is called when looping is enabled, it won't jump to the sought
position. When looping is enabled, playback will always start from the
beginning.
public void setAmplitudeMultiplication(float amplitudeMultiplication)
This method allows you to modulate the amplitude of a clip.
The amplitude of a clip is modulated by multiplying each breakpoint's amplitude by the given multiplication factor. The multiplication factor must be 0 or greater.
If the resulting amplitude is smaller than 0.0 or larger than 1.0, it will be clipped to that range. Clipping can cause artifacts, so you should take care to avoid excessive clipping. For example, if the breakpoints in a clip already have a high amplitude, a high amplitude multiplication factor should be avoided.
The modulation is applied the next time play()
is called.
public float getClipDuration()
Once a haptic clip has been loaded with load()
, it is possible to get its
duration in seconds. If no clip is loaded and getClipDuration
is called, the
returned value will be 0.0.
LofeltHaptics haptics = new LofeltHaptics(this);
try {
// Load haptic clip
final InputStream stream = getResources().openRawResource(hapticClipResourceId);
final byte[] hapticClipBytes = IOUtils.toByteArray(stream);
haptics.load(hapticClipBytes);
// Play haptic clip
haptics.play();
} catch (Exception e) {
// ...
}
In this example, the haptic clip is loaded from an Android resource (of course, you can load it in any way you prefer).
In the sdk/examples/android
folder within the Lofelt SDK download, there is
an example project for playing haptic clips on a phone called
LofeltHapticsExamplePreAuthored
. To run the example, open the project in
Android Studio, target your phone, and build. The example provides two buttons
that, when pressed and released, will trigger playback of audio files and
accompanying haptic clips.
This is known issue with the current version and will be addressed in an upcoming update.
This is a current limitation of Android.
On Android 11 and lower, playback of haptic clips with more than ~50 breakpoints might feel "stretched" during playback making clips play longer than designed. However, if your app is CPU load intensive, this might not happen. This is a limitation of the Android Vibration API and it has been fixed by the Android team for Android 12.
The LofeltHaptics
API throws exceptions on errors which you need to handle.
For example, you could wrap your calls in try
statements. See:
try {
haptics.load(hapticClipBytes);
} catch (Exception e) {
e.printStackTrace();
}
In the event you encounter an error message while using the Studio library for Android, the tips below can help resolve the issue. If the error persists even after trying these remedies, please submit a bug report in the Issues section this repository with the details of the error you receive.
Function Called | Error Message | Recovery Steps |
---|---|---|
Static initialization while loading the Lofelt SDK library | Any exception that System.loadLibrary() throws |
Make sure that the Lofelt SDK core library (liblofelt_sdk.so ) is included in your APK or AAB app package. This should be the case by default when including LofeltHaptics.aar in your project.Please it as a bug otherwise. |
load() | Reading clip data as UTF-8 failed: <error details> |
The bytes passed to load() are not valid UTF-8.Make sure you are following the .haptic file format. Make sure you read the file, asset or resource as a binary file, not a text file. For example use IOUtils.toByteArray() instead of IOUtils.toString() . |
load() | Error validating V0/V1: <error details> |
Make sure you are following the .haptic file format. |
load() | Error deserializing V0/V1: <error details> |
Make sure you are following the .haptic file format. |
load() | Unsupported version |
Get the latest version of the Android library from GitHub releases and include this in your app. |
load() | Any exception that Context::getSystemService() throws |
Android was unable to find the Vibrator service. Please it as a bug otherwise. |
load() | Any exception that VibrationEffect::createWaveform() throws |
Android was unable to create a waveform from the haptic clip data. Please it as a bug otherwise. |
play() | Unable to play, no clip loaded |
Call play() only after a haptic clip was loaded without any errors. |
play() | Any exception that Vibrator::vibrate() throws |
Android was unable to play the haptic clip. Please it as a bug otherwise. |
stop() | Unable to stop, no clip loaded |
Call stop() only after play() . |
stop() | Any exception that Vibrator::cancel() throws |
Android was unable to stop the playback of the haptic clip. Please it as a bug otherwise. |
loop() | Unable to set looping, no clip loaded. |
Call loop() only after a haptic clip was loaded without any errors. |
setAmplitudeMultiplication() | Unable to apply amplitude multiplication factor <factor>, needs to be 0 or greater |
Use a multiplication factor between 0.0 and ∞. |
setAmplitudeMultiplication() | Unable to apply amplitude multiplication, no clip loaded. |
Call setAmplitudeMultiplication() only after a haptic clip was loaded without any errors. |