Enable rich media interaction signals in an Android app
The rich media interaction signal captures user interactions with video and audio content in your app. In Connect, the signal enables segmentation by media ID, media name, and media category.
What marketers can do with it:
- Retarget users who watched a product demo but did not add to cart
- Segment users by which product video they watched to personalize follow-up content
- Reach out to users who watched a how-to or application walkthrough but did not complete the associated action — they showed intent but needed help getting there
Availability: Premium and Ultimate
Languages: Kotlin and Java
Implementation considerations
Interaction types
The signal supports the following interaction types for the interactionType field:
| Value | When to fire |
|---|---|
LOAD | Media begins loading |
LAUNCH | User initiates playback for the first time |
PAUSE | User pauses playback |
CONTINUE | User resumes playback after pausing |
COMPLETE | User reaches the end of the media |
STOP | User stops playback before completion |
ENLARGE | User enters fullscreen or expands the player |
Not all interaction types are required — instrument the ones relevant to your use case.
Event timing
Some players fire events multiple times during a single interaction — for example, a brief pause before resuming. Decide whether to track every state change or only significant milestones such as launch and complete.
Contact mapping
Use the audience field to map the signal to a contact in Connect. Commonly used identifiers are email address, phone number, and customer ID.
NoteA customer ID mapped to the Contact key attribute in Connect is the most reliable identifier for known contacts. However, if no matching contact exists and the only attribute is a contact key, the signal is discarded — contact keys alone cannot create new contacts.
Sources of the identifier in an Android app:
- In-memory session object (for example,
UserSession.email) SharedPreferencesfor persisted login state- Room database or other local store
The value must be a JSONObject where each key is a contact attribute name as it appears in Connect (including capitalization and spacing) and each value is the attribute value. For phone number attributes, use the E.164 format: +[country code][area code][phone number] — no spaces, dashes, or special characters. If the + symbol is omitted, Connect adds it automatically.
NoteWe recommend attaching identifiers to as many signals as possible. If the user is not authenticated, omit
audience— Connect will attempt to identify the visitor using other signals from the same session.
Configuration
Method
Connect.logSignal(data: HashMap<String?, Any?>?): BooleanSends the signal to the Acoustic Connect endpoint. The Connect SDK must be initialized via Connect.enable() before calling this method.
Pass all fields as a flat HashMap. The SDK handles the signal structure automatically — you do not need to construct a nested object.
Signal fields
Required
| Field | Schema type | Description |
|---|---|---|
mediaId | String | URL or unique identifier of the media file |
signalType | String | Signal type. Value: "richMediaInteraction". |
Optional
| Field | Schema type | Description |
|---|---|---|
audience | JSONObject | Key-value pairs for contact mapping. Must be a flat JSONObject — not a HashMap. Keys must match contact attribute names exactly as they appear in Connect. |
category | String | Signal category. Value: "Behavior". |
description | String | A description of the signal |
effect | String | Describes the effect of the signal on engagement. Use "positive" for rich media interactions. |
interactionType | String | The type of user interaction with the media (for example, "LAUNCH", "PAUSE", "COMPLETE"). |
mediaCategory | String | Category of the media content (for example, "product demo", "tutorial", "review") |
mediaName | String | Title of the audio or video |
name | String | A label to differentiate this signal from others (for example, "product demo interaction"). Max 256 characters |
Things to know
Pass
audienceas aJSONObject, not aHashMap. AHashMapvalue is silently dropped by the SDK serializer and the contact will not be mapped.If a required field is missing or invalid, the entire signal is discarded.
Required import
import org.json.JSONObjectBasic example
val data = hashMapOf<String?, Any?>(
"signalType" to "richMediaInteraction",
"mediaId" to "product-demo-wireless-headphones",
"mediaName" to "Wireless Headphones Demo",
"interactionType" to "LAUNCH",
"effect" to "positive"
)
Connect.logSignal(data)Complete example
This example instruments an ExoPlayer instance to send signals on key playback events. It tracks launch, pause, resume, and completion, and includes optional contact mapping.
import androidx.media3.common.Player
import org.json.JSONObject
import com.acoustic.connect.android.connectmod.Connect
private fun attachSignalListeners(player: Player, mediaId: String, mediaName: String) {
player.addListener(object : Player.Listener {
private var launched = false
override fun onIsPlayingChanged(isPlaying: Boolean) {
if (isPlaying) {
val interactionType = if (!launched) {
launched = true
"LAUNCH"
} else {
"CONTINUE"
}
sendRichMediaSignal(mediaId, mediaName, interactionType)
} else if (player.playbackState != Player.STATE_ENDED) {
sendRichMediaSignal(mediaId, mediaName, "PAUSE")
}
}
override fun onPlaybackStateChanged(state: Int) {
if (state == Player.STATE_ENDED) {
sendRichMediaSignal(mediaId, mediaName, "COMPLETE")
}
}
})
}
private fun sendRichMediaSignal(mediaId: String, mediaName: String, interactionType: String) {
val data = hashMapOf<String?, Any?>(
"signalType" to "richMediaInteraction",
"mediaId" to mediaId,
"mediaName" to mediaName,
"interactionType" to interactionType,
"mediaCategory" to "product demo",
"effect" to "positive"
)
// audience must be JSONObject — HashMap values are silently dropped
UserSession.email?.let { email ->
data["audience"] = JSONObject().put("Email Address", email)
}
Connect.logSignal(data)
}Call attachSignalListeners(player, mediaId, mediaName) after the player is initialized and the media item is set.
Troubleshooting
Signal not appearing in Connect?
- Confirm
Connect.enable()is called beforelogSignal. - Capture the return value and check that it is
true:val accepted = Connect.logSignal(data). Afalsereturn indicates the SDK rejected the call — no additional configuration is required to use the return value. - Verify the
appKeyandpostMessageUrlmatch the Connect org you're checking.
Signal marked as invalid in Connect?
- Confirm all required fields are present:
signalType,mediaId.
Duplicate signals for a single interaction?
- Some players fire state-change events multiple times during a single user action. Add a guard flag (as shown in the complete example with
launched) or debounce to prevent duplicate signals.
Contact not created or updated?
- Confirm
audienceis aJSONObject, not aHashMap. - Verify attribute key names match exactly how they appear in Connect — including capitalization and spacing.
