Integrations & SDKs

Android SDK

Installing and Setup

Our Android SDK is hosted as a GitHub package here: https://github.com/hotmic-wp/android-sdk

Make sure when installing that you follow this guide:

In the build.gradle (root), add the following repositories

JSON

  • For the personal access token, granting the read: packages permission should be enough
  • In the build.gradle(app), include the following dependency: `implementation 'io.hotmic.player:hotmic-android-sdk:1.4.1'`
  • Minimum SDK version of the player SDK is 21
  • Compile / Target SDK version of the player SDK is 30
  • Enable microphone permissions if you want to support TV Sync or Guest Call In.



Android Sample App

HotMic offers a sample app to see how to use our Android SDK in your application. Here is a step-by-step guide to downloading and running the HotMic SDK sample applications for Android.

Requirements

  1. A link to the HotMic SDK sample application provided by HotMic.
  2. An API Key provided by HotMic.
  3. A JWT token created using a Secret provided by HotMic. Instructions for how to create the token can be found below.  
  4. A white labeled production dashboard where you will produce streams.  

Authentication with JWT Token

For security, when using the SDK your app controls authentication with a JWT Token, which you pass to HotMic.

JSON Web Tokens are an open, industry standard RFC 7519 method for representing claims securely between two parties. - jwt.io

You will receive an API Secret from HotMic, which you will use to create the token.  The token will be used to authenticate the user, as well as provide information such as their name, an avatar, a unique ID, or another information which is needed by the SDK. 

This is the expected format of the JWT token:

JS


Note, the user_id field should be consistent for each invocation of the SDK for that particular user. 

Quick Steps to Generate a JWT Token Online

You can also generate JWT tokens online! This is good for test purposes while getting started, or to use the sample app. Here is one way to do it. We do not recommend using this method with any sensitive data.

Go to https://jwt.io/ and scroll down add a payload like the following into the "Payload" section. Paste your secret into the "Verify Signature" section. This screenshot shows how it looks:

Document image


And here is a sample payload that should work for your application:

JSON


Then copy the code from the "Encoded" section of this page, and use that in the sample app.



How the SDK is Implemented

The player screen in the SDK is implemented as a fragment:

  • Single Activity: If you have a single activity app, then the player fragment can be launched in the desired layout container.
  • Separate Activity: In case you want to launch the player in its own separate activity then you can create the activity in your app and then load the fragment into it

Pre-Requisites

You will need an API_KEY that has been generated specifically for your application. In case you do not have this, please contact the HotMic team.

Using the HotMic Android SDK

At a high level, you can use the Android SDK to do things like:

  • Get a list of streams
  • Open a stream
  • Take actions inside a stream
  • Receive callback & analytics events for actions taken inside the SDK

Fetch Streams

For fetching all streams to be listed to the user, call the below code:

Kotlin


You can optionally pass in params to filter the results of the streams. Streams are returned in reverse chronological order. The possible filters are:

  • userID: the userid you want to filter the streams by:
    • Type: string
    • Default: NULL (all possible streams)
  • limit: The max number of streams to return
    • Type: integer
    • Example: 40
    • Default: 30
  • skip: The number of results to skip you want to not return in the results, typically used for pagination.
    • Type: integer
    • Example: 10
    • Default: 0

The data will be returned as a list of HMStreamBasic Objects:

Field

Description

Id

Id of the stream. If the user taps on this listing, then pass this param as the stream id

userId

Uid of the user who is hosting this stream

state

SCHEDULED – The stream has been scheduled but is yet to live

LIVE – The stream is live

VOD – The stream is a past recording

ENDED – The stream has ended (you will not be getting this state in this list)

type

broadcast – The stream is a broadcast

audio_room – The stream is an audio room

watch_party – The stream is a video watch party

title

Title of this stream

thumbnail

An image url of the program thumbnail

scheduledDate

Timestamp of when this program is scheduled to start

viewers

Number of viewers who are currently watching this stream

visitors

Number of users who have visited this stream

eventId

Id of the event to which the stream belongs to

featuredAttendees

List of image uris, each pointing to the profile photo of someone who is viewing/visiting this stream

user

User object holding the information of the host of this stream (name, profile_url)

Starting the Player

When the user taps on any of the listed stream, the app can then show the stream player using the following code:

Kotlin

  • setStreamId - Mandatory – Sets the stream id (stream.id of the listing the user chose)
  • setUICallback – Mandatory – Provide the callback interface that the SDk will use for fetching certain information, for notifying certain events, or to complete flows that only your app code can handle
  • credential – Mandatory – Provide the api-key
  • setAnalyticHandler – Optional – Provide the analytic callback interface which will be called by SDK whenever an analytic event occurs. The app code can then log the event as necessary
  • show – Starts the player screen. Provide the layout container resource id, where the player fragment needs to be launched into

Closing the Player

If the app wants to close the player, then call:

Kotlin

  • reason - Analytic reason for the player closure, probable values will be USER_CLOSED, SIGNOUT
  • allowStateLoss: Setting this to true will result in the fragment being removed with state loss. The default is false. For most scenarios, the default value of false is recommended, especially if the parent activity is having features/fragments other than the player.

Once the player is closed, the SDK will make a call back on PlayerCallback.onPlayerClosed().

The SDK also might trigger a player closure. This may be in response to events like stream getting over, error in fetching the stream or the user leaving the stream from the player. Once the player is closed, the SDK will make a call back to PlayerCallback.onPlayerClosed().

Stream Menu Options

Inside the menu, there are several options which can be used to

  • Leave - The leave button stops the player in the sdk and gives a callback in the onPlayerClosed method, so the parent app can remove the fragment. This should be handled on the host app, but most of the time you would want to "go back" to the prior page.
  • Troubleshoot - When this is enabled, the troubleshoot button allows you to allow the end user to report any technical issue back to the app owner.  For example, if a video was not loading or a chat was missing, the user can tap on troubleshoot, which in turn will invoke a 3rd party diagnostic sdk. The sdk will capture parameters like device logs, etc and make a ticket in the system which the app owner can access. The option is hidden behind a flag which can be set while building the player, default is false. Let the HotMic team know your diagnostic SDK and we can help you set this up.

Handling Back Press

If the user taps the device back key, and if the player is open.

HotMicPlayer.handleBackKeyPress(activity): Boolean

If the player consumes the key press (say for dismissing an overlay/dialog), then it will return true. The app should not handle the back press in this scenario. Else this will return false, and the app can handle the back press as it wants to.

Is the Player open?

The app can check if the player is open in multiple ways. It can check with the sdk by calling:

HotMicPlayer.isPlayerOpen(activity)

Or it can check whether a fragment is loaded in the ui player container:

supportFragmentManager.findFragmentById(R.id.<player_fragment_container>)

Full Screen Mode

To allow your video player to go into full screen mode with a tap, you need to enable it in your application. To do so properly, add android:configChanges="orientation|keyboardHidden|screenSize" to the activity hosting the fragment

Picture-in-Picture (PIP) Mode

Picture in Picture Mode can be of two types:

  1. For a single activity app, showing the player fragment in a PIP when navigating to other fragments in the app. This is achieved by reducing the player fragment layout container to the desired size and moving it to the required location.
  2. Placing the app in the background by tapping the device Home button or by navigating to some other app

In both the cases the player has to resize itself so that in the PIP mode, the player displays just the video.

Also if the user taps on the PIP to get back to the expanded mode, the player will have to again change its UI to show all the layouts and controls.

To change the UI, manually (for scenario 1):

Kotlin

  • enable: Set it to true for the player to show the PIP mode UI, false if the player is coming out of PIP and being expanded
  • Returns true if the operation was success, false if the player is not open

For scenario 2 (android PIP mode), the player will change the UI itself, triggered by the android PIP callback. Note: this assumes that the activity has been enabled for PIP in the manifest. Refer https://developer.android.com/guide/topics/ui/picture-in-picture for more.

Also, for scenario 2, if the user closes the pip and at a later point of time returns to the app, then the app should show the UI with the player closed. To ensure this, add the below line in the activity onStart:

HotMicPlayer.checkPlayerStateOnStart(activity)

Displaying Ads

For displaying an ad, you can pass the view holding the ad (like the Google AdView) to the Player, using the following api:

Kotlin


It is assumed that the click interaction will be implemented in the view that is passed. The SDK is going to display the ad-banner with the passed view. The Ad will be displayed with an initial delay of 0 seconds, i.e., immediate, but this can be changed by passing a displayAfter value (in milliseconds). The Ad will be displayed for 10 seconds by default, this duration can also be changed by passing the right ttl value in the above function (in milliseconds)

If the ad is to be dismissed, before its auto removed, then the below api can be invoked

dismissHotMicAd(activity: FragmentActivity)

Styles

Text Styling

The below table lists the TextAppearance styles used in the app. The SDK does not have any fonts packaged with it, and hence the lack of font definitions in the below styles. You can change the appearance by defining the style with the same name in your app theme/style xml file

Style name

Current Definition

TextAppearance.HMPlayerHeadline4

<style name="TextAppearance.HMPlayerHeadline4" parent="TextAppearance.MaterialComponents.Headline4">

<item name="android:textStyle">normal</item>

<item name="android:textAllCaps">false</item>

<item name="android:textSize">24sp</item>

<item name="android:letterSpacing">0</item>

</style>

TextAppearance.HMPlayerHeadline5

<style name="TextAppearance.HMPlayerHeadline5" parent="TextAppearance.MaterialComponents.Headline5">

<item name="android:textStyle">normal</item>

<item name="android:textAllCaps">false</item>

<item name="android:textSize">20sp</item>

<item name="android:letterSpacing">0</item>

</style>

TextAppearance.HMPlayerHeadline6

<style name="TextAppearance.HMPlayerHeadline6" parent="TextAppearance.MaterialComponents.Headline6">

<item name="android:textStyle">normal</item>

<item name="android:textAllCaps">false</item>

<item name="android:textSize">16sp</item>

<item name="android:letterSpacing">0</item>

</style>

TextAppearance.HMPlayerBody1

<style name="TextAppearance.HMPlayerBody1" parent="TextAppearance.MaterialComponents.Body1">

<item name="android:textStyle">normal</item>

<item name="android:textAllCaps">false</item>

<item name="android:textSize">16sp</item>

<item name="android:letterSpacing">0</item>

</style>

TextAppearance.HMPlayerBody2

<style name="TextAppearance.HMPlayerBody2" parent="TextAppearance.MaterialComponents.Body2">

<item name="android:textStyle">normal</item>

<item name="android:textAllCaps">false</item>

<item name="android:textSize">14sp</item>

<item name="android:letterSpacing">0</item>

</style>

TextAppearance.HMPlayerBody

<style name="TextAppearance.HMPlayerBody3" parent="TextAppearance.MaterialComponents.Body2">

<item name="android:textStyle">normal</item>

<item name="android:textAllCaps">false</item>

<item name="android:textSize">14sp</item>

<item name="android:letterSpacing">0</item>

</style>

TextAppearance.HMPlayerButton

<style name="TextAppearance.HMPlayerButton" parent="TextAppearance.MaterialComponents.Button">

<item name="android:textStyle">normal</item>

<item name="android:textAllCaps">false</item>

<item name="android:textSize">16sp</item>

<item name="android:letterSpacing">0</item>

</style>

TextAppearance.HMPlayerOverline

<style name="TextAppearance.HMPlayerOverline" parent="TextAppearance.MaterialComponents.Overline">

<item name="android:textStyle">normal</item>

<item name="android:textAllCaps">false</item>

<item name="android:textSize">10sp</item>

<item name="android:letterSpacing">0</item>

</style>

Color Styling

The table below lists the colors used in the app, for both day and night modes. You can change the colors by defining colors with the same name in your colors xml file(s)

Color Style Name

Day

Night

hmp_colorPrimary

#F6F8FA

#121212

hmp_colorSecondary

#FFFFFF

#000000

hmp_colorTertiary

#FFFFFF

#222222

hmp_colorBackground

#FFFFFF

#000000

hmp_colorSurface

#FFFFFF

#121212

hmp_colorHighlight

#D1D1D5

#3A3A3C







hmp_textPrimary

#303234

#FFFFFF

hmp_textSecondary

#86898C

#999999

hmp_tintPrimary

#FF7106

#FF7106

hmp_tintPrimaryContrast

#C93400

#C93400

hmp_tintTertiary

#D0D3D7

#606060

hmp_tintTertiaryContrast

#76767B

#919198

hmp_tintError

#DB3B3C

#DB3B3C

hmp_tintWarning

#EDAC46

#EDAC46

hmp_tintSuccess

#53D973

#53D973

hmp_tintLive

#AD3632

#AD3632

hmp_divider

#D1D1D6

#38383A







hmp_textPrimaryDisabled

#80000000

#80FFFFFF

hmp_textMedia

#FFFFFF

#FFFFFF

hmp_tintDisabled

#90929F

#90929F

hmp_tintDisabled2

#20000000

#20000000

hmp_tintAction

#000000

#FFFFFF

hmp_pollPrimary

set in your app

set in your app

hmp_pollPrimaryContrast

set in your app

set in your app

hmp_pollSecondary

#CCCDCE

#A7A8AA

hmp_pollPercentage

#E2E2E3

#484B50

Player Callback Interface

getPlatformToken

fun getPlatformToken(): String

The platform token is required by the SDK to authenticate any requests with the HotMic Server. The SDK expects the app to return the platform token through this callback. Note that the HotMic Server expects the token to be constructed in a certain format, please contact the HotMic team to learn more.

Params: None

Returns: Platform Token

onRequestBugReport

fun onRequestBugReport()

(OPTIONAL) User has requested to capture bug report. If the app is using any sort of bug capturing (like shake/instabug/etc), then trigger it

Params: None

onPlayerClosed

fun onPlayerClosed()

(OPTIONAL) Called after the player is closed. Player closure can be triggered by the sdk on certain events like stream over, error. Or it can be triggered by the app by calling HotMicPlayer.closePlayer

Params: None

getShareLink

fun getShareLink(context: Context, streamId: String): Observable<String>

(OPTIONAL) User has requested a share/invite message for a stream. The message would be then available to the user to share with others.

Params:

  • context
  • streamId – id of the stream for which the invite link is to be created

Returns: Observable with the message string

getFollowData (depreciated in version 1.7.0)

getFollowData has been replaced by getUserProfile.

fun getFollowData(uid: String): Observable<HMFollowData>?

(OPTIONAL) Return null or do not implement, if you do not support the follow feature. If implementing, then gather and return the follow information. This information includes,

  • Is the logged in user following the user identified by the passed uid
  • If the logged in user being followed by the specific user
  • How many followers does the specific user have
  • How many people does the specific user follow

Params:

  • uid – user id of the user whose follow information you need

Returns: An observable with the FollowData object. The observable will be invoked in the background by the implementation.

getUserProfile (new in version 1.7.0)

getUserProfile has replaced getFollowData, please use this moving forward.

(OPTIONAL) Return the user profile information, including meta data like name, profile pic. Return null or do not implement, if you do not want to provide information from your application. If implementing, then gather and return all information for the user profile, such as following information. This information includes,

  • Is the logged in user following the user identified by the passed uid
  • If the logged in user being followed by the specific user
  • How many followers does the specific user have
  • How many people does the specific user follow

Params:

  • uid - user id of the user whose profile info is needed
  • restriction - type of restriction on this account (if any), possible values are "none", "view_and_polls_only", "read_only". A null value on this can be interpreted as no restriction.
    • "view_and_polls_only" - user can view and answer polls, but cannot chat, join a stream, follow, tip, view a profile.
    • "read_only" - same as "view_and_polls_only", plus the user cannot answer polls
  • isHost - boolean - is the user id that of the host of this stream
  • isCohost - boolean - is the user one of the cohost in this stream
fun getUserProfile( uid: String, restriction: String?, isHost: Boolean, isCohost: Boolean ): Observable<HMProfile>?

Returns:

An observable with the HMProfile object. The observable will be invoked in the background by the implementation. Here is what the return looks like:

data class HMProfile( val name: String? = null, val displayName: String? = null, val profilePic: String? = null, val badge: String? = null, val bio: String? = null, val location: String? = null, val instagramHandle: String? = null, val tikTokHandle: String? = null, val twitchHandle: String? = null, val twitterHandle: String? = null, val youTubeHandle: String? = null, val followersCount: Int? = null, val followingCount: Int? = null, val isFollowingMe: Boolean? = null, val isFollowedByMe: Boolean? = null, val showFullProfileOption: Boolean? = null )

seeFullProfileTapped

If showFullProfileOption is returned to getUserProfile(), then there will be a button displayed to "See Full Profile". If a user taps that button, they trigger seeFullProfileTapped.

Params:

  • uid - user id of the user whose profile info is needed
  • restriction - type of restriction on this account (if any), possible values are "none", "view_and_polls_only", "read_only". A null value on this can be interpreted as no restriction.
    • "view_and_polls_only" - user can view and answer polls, but cannot chat, join a stream, follow, tip, view a profile.
    • "read_only" - same as "view_and_polls_only", plus the user cannot answer polls
  • isHost - boolean - is the user id that of the host of this stream
  • isCohost - boolean - is the user one of the cohost in this stream
fun seeFullProfileTapped( uid: String, restriction: String?, isHost: Boolean, isCohost: Boolean ): Boolean

Returns:

true if you want the profile panel shown by the sdk to be dismissed, false otherwise

followUser

fun followUser(user: HMUserBasic, shouldFollow: Boolean): Observable<Unit>?

(OPTIONAL) Follow/Unfollow the passed user. Required only if you app is having the follow-user feature

Params:

  • user – User to be followed/unfollowed
  • shouldFollow – true if to be followed, false otherwise

Returns: Observable

fun followUser(user: HMUserBasic, shouldFollow: Boolean): Observable<HMFollowData>?

The return for followUser is:

data class HMFollowData( val followersCount: Int?, val followingCount: Int?, val isFollowingMe: Boolean?, val isFollowedByMe: Boolean? )

onAdClick

fun onAdClick(streamId: String, adId: String)

(OPTIONAL) Called in the event of the user clicking on any advertisement banner on the player.

Params:

  • streamId – id of the stream being played
  • adId – id of the advertisement banner

Returns: Nothing

isBlockedByHost

fun isBlockedByHost(hostId: String): Observable<Boolean>?

(OPTIONAL) Return true in the observable if the host, identified by the host id has blocked the current logged in user from the stream. If true, then the user will not be able to send any chat message. Default implementation returns null, indicating no blocking

Params:

  • hostId – Uid of the host for the current stream being played

Returns: Observable with Boolean value, null will be treated as non-blocked

tipSkus

fun getTipSkus(): LiveData<List<AppSku>>

(OPTIONAL) Return the full list of billing products that is configured for tips in the app

Params: None

Returns: LiveData with the list of billings skus

makeTipPurchase

fun makeTipPurchase(activity: Activity,userId: String,hostId: String,streamId: String,streamType: String,message: String,anonymous: Boolean,appSkuDetail: AppSku)

(OPTIONAL) Make the Google billing purchase for when the user has opted to tip a certain amount in the stream

Params:

  • activity – Parent fragment activity of the player
  • userId – Unique id of the logged in user requesting the cameo
  • hostId – Unique id of the host of the stream being played
  • streamId – Unique id of the stream being played
  • streamType – type of the stream for analytic purposes (broadcast|watch_party|audio_room)
  • message – Message that the user has typed while giving the tip
  • anonymous – True if the user wants to stay anonymous
  • appSkuDetail – Billing product details which the user has agreed to pay for

Returns: Observable with the billing result

makeJoinGuestPurchase

fun makeJoinGuestPurchase(activity: Activity,userId: String,hostId: String,streamId: String,streamType: String,appSkuDetail: AppSku): Observable<AppBillingResult>

(OPTIONAL) Make the Google billing purchase for when the user has opted to pay for joining a stream as a guest (cameo)

Params:

  • activity – Parent fragment activity of the player
  • userId – Unique id of the logged in user requesting the cameo
  • hostId – Unique id of the host of the stream being played
  • streamId – Unique id of the stream being played
  • streamType – type of the stream for analytic purposes (broadcast|watch_party|audio_room)
  • appSkuDetail – Billing product details which the user has agreed to pay for

Returns: Observable with the billing result

fetchSkuDetails

fun fetchSkuDetails(skuId: String?): Flowable<List<AppSku>>

(OPTIONAL) Fetch the billing product details for the given sku id

Params:

  • skuId – id of the app billing product

Returns: FLowable where the list of matching billing products are returned. Ideally only one entry is expected and the sdk is only going to consider the first entry. Emit empty list if no match is found. And emit error exception in case of any error in fetching

Analytics Handler Interface

logEvent

fun logEvent(event: ANEvent, data: Map<String, Any>)

Log the event passed and the associated data using the app analytics

Params:

  • event – Event to be logged
  • data - Param data associated with the event logged

startEventTimer

fun startEventTimer(event: ANEvent)

This is mainly for timed events, where you want to record the duration of the event. This logs the start of an event. The end of the event will be triggered from the above logEvent

Params:

  • event – Event to be logged