Skip to content

3. Lofelt SDK for Android

João Freire edited this page Aug 10, 2022 · 2 revisions

Using the 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.

Adding the Android library to Your Android Studio Project

Copying the Library into the Project

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:

LofeltHaptics in lib folder

Add the Library to build.gradle

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')

Setting Up the API Documentation

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.

Initializing the Android library

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.

Playing Haptic Clips

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:

  1. Load the haptic clip into the LofeltHaptics object with the load() method.
  2. Start playback of the haptic clip with the play() method.
  3. 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).

Checking if device meets haptic requirements

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:

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.

Load

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().

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.

Stop

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.

Seek

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.

Loop

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.

Amplitude modulation

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.

Clip duration

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.

Code Example

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).

Example Project

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.

Troubleshooting

The pre-authored haptics are out of sync with the audio

This is known issue with the current version and will be addressed in an upcoming update.

The pre-authored haptics feel stretched

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.

Error Handling

The LofeltHaptics API throws exceptions on errors which you need to handle. For example, you could wrap your calls in try statements. See:

Error Handling Example

try {
	haptics.load(hapticClipBytes);
} catch (Exception e) {
	e.printStackTrace();
}

Error Types

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.