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:

ValueWhen to fire
LOADMedia begins loading
LAUNCHUser initiates playback for the first time
PAUSEUser pauses playback
CONTINUEUser resumes playback after pausing
COMPLETEUser reaches the end of the media
STOPUser stops playback before completion
ENLARGEUser 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.

📘

Note

A 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)
  • SharedPreferences for 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.

📘

Note

We 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?>?): Boolean

Sends 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

FieldSchema typeDescription
mediaIdStringURL or unique identifier of the media file
signalTypeStringSignal type. Value: "richMediaInteraction".

Optional

FieldSchema typeDescription
audienceJSONObjectKey-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.
categoryStringSignal category. Value: "Behavior".
descriptionStringA description of the signal
effectStringDescribes the effect of the signal on engagement. Use "positive" for rich media interactions.
interactionTypeStringThe type of user interaction with the media (for example, "LAUNCH", "PAUSE", "COMPLETE").
mediaCategoryStringCategory of the media content (for example, "product demo", "tutorial", "review")
mediaNameStringTitle of the audio or video
nameStringA label to differentiate this signal from others (for example, "product demo interaction"). Max 256 characters

Things to know

  • Pass audience as a JSONObject, not a HashMap. A HashMap value 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.JSONObject

Basic 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 before logSignal.
  • Capture the return value and check that it is true: val accepted = Connect.logSignal(data). A false return indicates the SDK rejected the call — no additional configuration is required to use the return value.
  • Verify the appKey and postMessageUrl match 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 audience is a JSONObject, not a HashMap.
  • Verify attribute key names match exactly how they appear in Connect — including capitalization and spacing.

Related pages