# Integrate the Genuin Android SDK

Add Genuin embeds, placements, single sign-on, deep links, and push notifications to an Android app with SDK 2.1.5.

The Genuin Android SDK renders your social experience inside your own app: your brand's communities, groups, and video feeds. You add one or more of four modules, initialize the SDK with your API key, and drop a view or fragment where the content goes. Everything the view shows, from which community to the carousel or grid layout, is configured in Brand Control Center and identified by an embed ID or placement ID.

| Module | Artifact | Size | Adds | Requires |
| --- | --- | --- | --- | --- |
| Core | com.begenuin:core:2.1.5 | 3.4 MB | Embeds, placements, sign-in, deep links, push notification handling, the video player | — |
| UI | com.begenuin:ui:2.1.5 | 4.5 MB | Comments, search, notifications screen, community and group detail screens, deep-link destinations | Core |
| Camera | com.begenuin:camera:2.1.5 | 48 MB | Recording and uploading video posts | Core, UI |
| AI | com.begenuin:ai:2.1.5 | 6.5 MB | AI-assisted creation of communities and groups | Core, UI, Camera |

Sizes are the published AAR files; the Camera module is large because it bundles the video-processing toolchain. Add only the modules you use, and keep them on the same version.

## Requirements

| | Minimum |
| --- | --- |
| Android | API 24 (Android 7.0). Every module declares minSdkVersion 24. |
| Android Gradle Plugin | 8.6.0 |
| Language | Kotlin or Java. The SDK ships with Kotlin 1.9.22 and exposes Java-callable methods through GenuinSDK.INSTANCE. |
| Orientation | Portrait |
| Form factor | Phones; the SDK isn't laid out for tablets or TV |
| UI toolkit | Views, or Jetpack Compose through AndroidView |

You also need an **API key** for your brand and at least one **embed ID** or **placement ID**, both from Brand Control Center. The key is the same one the Web and iOS SDKs use.

## Add the SDK

### Add the repositories

The SDK is published to Maven Central. Core also pulls in the Pangle ad SDK, served from ByteDance's repository, and two libraries hosted on JitPack, so declare all three in your project-level settings:

```kotlin title="settings.gradle.kts"
dependencyResolutionManagement {
  repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
  repositories {
    google()
    mavenCentral()
    maven { setUrl("https://jitpack.io") }
    maven { setUrl("https://artifact.bytedance.com/repository/pangle") }
  }
}
```

```groovy title="build.gradle (project)"
allprojects {
  repositories {
    google()
    mavenCentral()
    maven { url "https://jitpack.io" }
    maven { url "https://artifact.bytedance.com/repository/pangle" }
  }
}
```

Without the Pangle repository, Gradle fails to resolve `com.pangle.global:pag-sdk` even if you never show an ad.

### Add the dependencies

Declare one version and apply it to every module you include. Modules depend on each other in a fixed chain (`ai` → `camera` → `ui` → `core`), so an app that uses the AI module declares all four:

```kotlin title="build.gradle.kts (app)"
val genuinVersion = "2.1.5"
implementation("com.begenuin:core:$genuinVersion")
implementation("com.begenuin:ui:$genuinVersion")      // comments, search, notifications, detail screens
implementation("com.begenuin:camera:$genuinVersion")  // recording and upload
implementation("com.begenuin:ai:$genuinVersion")      // AI-assisted community and group creation
```

```groovy title="build.gradle (app)"
def genuinVersion = "2.1.5"
implementation "com.begenuin:core:$genuinVersion"
implementation "com.begenuin:ui:$genuinVersion"
implementation "com.begenuin:camera:$genuinVersion"
implementation "com.begenuin:ai:$genuinVersion"
```

Mixing versions across modules fails at runtime, not at build time, so pin them to one variable.

The SDK brings its own video stack (`androidx.media3` 1.7.1 with ExoPlayer, HLS, and IMA), Glide, Retrofit, Lottie, and the RudderStack analytics client. If your app already uses any of these, Gradle resolves to the higher version.

### Declare permissions

Core declares `INTERNET`, `ACCESS_NETWORK_STATE`, `VIBRATE`, and `WAKE_LOCK` in its own manifest, and Android merges them into yours. Add the rest in your `AndroidManifest.xml` only for the features you use:

| Permission | Add it for | Module |
| --- | --- | --- |
| android.permission.ACCESS_WIFI_STATE | Network-aware playback | Core |
| android.permission.READ_CONTACTS | Letting users add contacts as community and group members | UI |
| android.permission.POST_NOTIFICATIONS | Push notifications on Android 13 and later | UI |
| android.permission.CAMERA, android.permission.RECORD_AUDIO, plus <uses-feature android:name="android.hardware.camera" android:required="false" /> | Recording video posts | Camera |
| android.permission.READ_MEDIA_VIDEO (API 33+) and android.permission.READ_EXTERNAL_STORAGE with android:maxSdkVersion="32" | Uploading videos from the gallery | Camera |
| android.permission.ACCESS_COARSE_LOCATION, android.permission.ACCESS_FINE_LOCATION | AI-created communities tailored to the user's location | AI |
| com.google.android.gms.permission.AD_ID | Monetization. See Enable monetization. | Core |

## Initialize the SDK

Call `initSDK` once, in your `Application` subclass, before any Genuin view is created:

```kotlin title="YourApplication.kt"
class YourApplication : Application() {
  override fun onCreate() {
    super.onCreate()
    GenuinSDK.initSDK(this, "<<YOUR_API_KEY>>")                          // production
    // GenuinSDK.initSDK(this, "<<YOUR_API_KEY>>", genuinEnv = GenuinEnv.QA)  // QA environment
  }
}
```

`genuinEnv` defaults to `GenuinEnv.PROD`. Use `GenuinEnv.QA` only with a key issued for the QA environment; keys aren't shared between environments. Calling `initSDK` from an Activity works too, but views created before the call fail to load.

> **Note** Don't ship a build that points at QA. The two environments have separate brands, users, and content.

### Replace the loading animation

The SDK shows a Lottie animation while content loads. To use your own, add a Lottie file named exactly `loader_mix.json` to `res/raw/`; a resource with that name overrides the SDK's.

## Show an embed

An embed is a feed of videos from a community, group, or your whole brand. It's configured in Brand Control Center and rendered in one of four layouts: carousel, grid, feed, or dynamic. The layout comes from the embed's configuration, so the same code shows whatever the embed is set to.

For a carousel or grid inside your own screen, use `GenuinEmbedView`. For a full-screen feed or a standard wall that takes over a screen, use `FeedEmbedFragment`.

### Add a GenuinEmbedView

Give the view a fixed height. A container with `wrap_content` height renders nothing.

```xml title="activity_embed.xml"
<LinearLayout
    android:id="@+id/llCarouselContainer"
    android:layout_width="match_parent"
    android:layout_height="300dp">
  <com.begenuin.core.ui.customview.embed.GenuinEmbedView
      android:id="@+id/genuinEmbedView"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:interTileSpacing="12dp"
      app:leftInset="12dp"
      app:topInset="12dp"
      app:rightInset="12dp"
      app:bottomInset="12dp"
      app:tileCornerRadius="12dp" />
</LinearLayout>
```

Then configure and load it. The setters return the view, so they chain:

```kotlin title="EmbedActivity.kt"
val params = hashMapOf<String, Any>(              // optional: pre-fills sign-up for SSO users
  "name" to "John Doe",
  "email" to "john.doe@example.com",
  "nickname" to "john",
  "mobile" to "{{PHONE_NUMBER}}"
)
val contextualParams = hashMapOf<String, Any>(    // optional: tailors the feed to the screen and viewer
  "page_context" to "{{PAGE_CONTEXT}}",
  "geo" to JSONObject().put("lat", 40.7128).put("long", -74.0060).toString()
)

genuinEmbedView
  .setEmbedParams(
    embedId = "{{EMBED_ID}}",
    uniqueId = "{{UNIQUE_ID}}",               // optional
    interactionDeepLink = "{{DEEP_LINK}}",    // optional
    isDirectDeepLinkEnabled = false,
    isShowProfileEnabled = false
  )
  .setActivity(this)
  .setSSOToken("{{SSO_TOKEN}}")               // optional: signs the viewer in
  .setParams(params)
  .setContextualParams(contextualParams)
  .setInterTileSpacing(24)                    // pixels
  .setInsets(left = 24, top = 24, right = 24, bottom = 24)   // pixels
  .setTileCornerRadius(24)                    // pixels
  .load()
```

To create the view in code instead of XML, construct `GenuinEmbedView(context)`, add it to a container with a fixed height, and configure it the same way.

### Embed parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| embedId | String | Required | The embed to load, from Brand Control Center. |
| uniqueId | String | none | Set a different value for each instance when you show the same embed on several screens, or more than once on one screen. |
| interactionDeepLink | String | none | A URL to open instead of the SDK's own flow when the viewer interacts in the full-screen view. The URL is validated; an invalid URL makes the redirect fail silently. |
| isDirectDeepLinkEnabled | Boolean | false | When true, interactions open the specific video in your white-labelled app and interactionDeepLink is ignored. |
| isShowProfileEnabled | Boolean | false | When true, signed-in viewers see their profile picture in the full-screen view, with access to account settings and sign-out. |

### User parameters (`setParams`)

Pass these only together with an SSO token. They pre-fill the viewer's Genuin profile when their account is created.

| Key | Type | Description |
| --- | --- | --- |
| name | String | Display name |
| email | String | Email address |
| mobile | String | Phone number |
| nickname | String | Used as the viewer's nickname if it's free; otherwise the SDK generates one |
| profile_image | String | URL of a profile image to show in the SDK |

### Contextual parameters (`setContextualParams`)

| Key | Type | Description |
| --- | --- | --- |
| page_context | String | What the current screen is about. The feed is ranked toward it. |
| geo | JSON string | {"lat": <double>, "long": <double>} to rank by location |

Placements accept a longer list. See [Contextual parameters for placements](#contextual-parameters-for-placements).

### Layout setters

| Setter | XML attribute | Default |
| --- | --- | --- |
| setInterTileSpacing(px) | app:interTileSpacing | 8 dp |
| setInsets(left, top, right, bottom) | app:leftInset, app:topInset, app:rightInset, app:bottomInset | left 16 dp, top 8 dp, right 16 dp, bottom 0 dp |
| setTileCornerRadius(px) | app:tileCornerRadius | 8 dp |

XML attributes take dp. The programmatic setters take pixels, so convert with your display density.

![Three phone screens showing a video carousel, annotated to show what setTileCornerRadius, setInsets, and setInterTileSpacing control: the rounding of each tile, the space between the carousel and the screen edges, and the gap between tiles.](https://cdn.sanity.io/images/lnrtvsvr/development/9869f7e8e41cd16faf0ce800f466c11fc8ee18be-1449x687.jpg)
*Tile corner radius, insets, and inter-tile spacing on a carousel embed.*

Corner radius, insets, and tile spacing on a carousel embed.

### Control playback with the lifecycle

Videos autoplay. Pause them when the screen leaves the foreground and release the player when it's destroyed, or playback continues in the background:

```kotlin title="Lifecycle"
override fun onResume() { super.onResume(); genuinEmbedView.play() }
override fun onPause() { genuinEmbedView.pause(); super.onPause() }
override fun onDestroy() { genuinEmbedView.release(); super.onDestroy() }
```

### Load asynchronously

`load()` shows the view immediately with a shimmer while data arrives. To keep the view hidden until the feed is ready, use `loadAsync`:

```kotlin title="Load asynchronously"
genuinEmbedView.loadAsync { success ->
  genuinEmbedView.visibility = if (success) View.VISIBLE else View.GONE
}
```

`success` is `false` when the embed doesn't exist, the key is wrong, or there's no network.

### Show a full-screen feed or standard wall

Both use `FeedEmbedFragment`; the embed's configuration decides which layout appears. Add a `FrameLayout` that fills the screen and insert the fragment:

```kotlin title="FullEmbedActivity.kt"
import com.begenuin.core.ui.fragment.FeedEmbedFragment

val fragment = FeedEmbedFragment.newInstance(
  embedId = "{{EMBED_ID}}",
  uniqueId = "{{UNIQUE_ID}}",               // optional
  interactionDeepLink = "{{DEEP_LINK}}",    // optional
  isDirectDeepLinkEnabled = false,
  ssoToken = "{{SSO_TOKEN}}",               // optional
  params = params,                          // optional
  contextualParams = contextualParams,      // optional
  isShowProfileEnabled = false
)
supportFragmentManager.beginTransaction()
  .add(R.id.frameLayout, fragment)
  .addToBackStack("GenuinFeed")
  .commit()
```

The parameters mean the same as on `GenuinEmbedView`. The fragment manages its own playback lifecycle.

### Use Jetpack Compose

Wrap the view in `AndroidView`. The view needs an `Activity`, so resolve it from `LocalContext`:

```kotlin title="Compose wrapper"
@Composable
fun GenuinCarousel(embedId: String, modifier: Modifier = Modifier) {
  val activity = LocalContext.current as? Activity
    ?: throw IllegalStateException("GenuinEmbedView needs an Activity context")
  AndroidView(
    modifier = modifier,            // give it a height, e.g. Modifier.fillMaxWidth().height(400.dp)
    factory = { context ->
      GenuinEmbedView(context)
        .setEmbedParams(embedId)
        .setActivity(activity)
        .load()
    }
  )
}
```

For a full-screen feed in Compose, host `FeedEmbedFragment` in a `FragmentContainerView` inside `AndroidView`, and add the fragment only when `findFragmentById` returns null so recomposition doesn't add it twice.

## Show a placement

A placement is an embed whose content is chosen at render time from the context you pass. That context is the page, the viewer's location and segment, and the communities or users to draw from. It needs a placement ID and a style ID, both from Brand Control Center. It renders through `GenuinPlacementView`, which has the same setters and lifecycle as `GenuinEmbedView`.

```kotlin title="Show a placement"
genuinPlacementView
  .setPlacementParams(
    placementId = "{{PLACEMENT_ID}}",
    styleId = "{{STYLE_ID}}",
    uniqueId = "{{UNIQUE_ID}}",               // optional
    interactionDeepLink = "{{DEEP_LINK}}",    // optional
    isDirectDeepLinkEnabled = false
  )
  .setActivity(this)
  .setSSOToken("{{SSO_TOKEN}}")               // optional
  .setParams(params)                          // optional
  .setContextualParams(contextualParams)
  .load()
```

The XML element is `com.begenuin.core.ui.customview.embed.GenuinPlacementView`, with the same layout attributes as the embed view.

### Contextual parameters for placements

Every key is optional. JSON values are passed as strings (`JSONObject.toString()`); lists as `JSONArray`.

| Key | Type | Description |
| --- | --- | --- |
| page_context | String | What the current screen is about |
| previous_page_context | String | What the previous screen was about |
| user_context | String | Anything else you know about the viewer's intent |
| url | String | The web URL this screen corresponds to |
| time | Number | A Unix timestamp to rank against scheduled content |
| geo | JSON | lat, long (doubles), and radius_limit |
| place | JSON | country (code), state, city, zipcode |
| user_segments | JSON | age, min_age, max_age, segment, gender (M, F, or O), race |
| user_interest | JSON array of strings | Interests to rank toward |
| brands_ids | JSON array | Restrict to these brands |
| community_ids | JSON array | Restrict to these communities |
| loop_ids | JSON array | Restrict to these groups |
| posted_by_user_ids | JSON array | Restrict to posts by these users |

The `_ids` keys scope the feed; the others only change its order. Which signals a placement honours is set per placement under **Grow** > **Placement** > **Implement** in Brand Control Center.

## Sign users in

Viewers can use embeds without an account. To let them post, comment, or follow as themselves, sign them in with single sign-on. Your server issues an **SSO token** for the user, and the SDK creates or matches their Genuin account from it. Genuin's own login screens never appear.

### Sign in with an SSO token

Call `ssoLogin` once your app has authenticated the user. Pass the same user parameters you'd pass to an embed; they're used the first time the account is created.

```kotlin title="Sign in"
GenuinSDK.ssoLogin(
  context = this,
  ssoToken = "{{SSO_TOKEN}}",
  params = params                       // optional
) { success ->
  // success is false when the token is invalid or expired
}
```

```java title="Sign in (Java)"
// Java
GenuinSDK.INSTANCE.ssoLogin(context, "{{SSO_TOKEN}}", params, success -> {
  return Unit.INSTANCE;
});
```

You can also set the token on an individual view with `setSSOToken`. `ssoLogin` signs the user in for every view in the app.

Sign the user out of the SDK when they sign out of your app:

```kotlin title="Sign out"
GenuinSDK.ssoLogout(context = this) { success -> }
```

### Let the SDK ask your app to sign in

If the viewer isn't signed in and taps an action that needs an account, the SDK calls back into your app instead of showing its own login. Register the callback once, run your login flow in it, and finish with `ssoLogin`:

```kotlin title="GenuinInterface"
GenuinSDK.registerInterface(object : GenuinInterface {
  override fun onLogin(activity: Activity) {
    // Start your app's sign-in flow. When it completes:
    // GenuinSDK.ssoLogin(activity, "{{SSO_TOKEN}}")
  }
})
```

Without a registered interface, the SDK uses its own sign-in screens.

For how tokens are issued and validated, see [Choose how members sign in to the Genuin SDK](https://begenuin.com/developers/developer-resources/sdk/user-authentication-mechanisms-in-genuin-sdk) and [How Auto Login works](https://begenuin.com/developers/developer-resources/sdk/genuin-auto-login).

## Handle deep links

Links to Genuin content, such as a shared video or an invitation, use your white-label domain. To open them in your app instead of the browser:

1. [White-label your social experience](https://begenuin.com/developers/browse-by-products/onsite/how-can-you-white-label-your-community-under-your-own-domain) so links use your domain.
2. Set up [Android App Links](https://developer.android.com/studio/write/app-link-indexing) for that domain in your app.
3. Send your Genuin contact your package name and the SHA-256 fingerprint of every certificate that signs your app, including the Google Play app signing key. Genuin serves `assetlinks.json` on your white-labelled domain from your brand's configuration, so you don't host it. [Handle Genuin deep links in your app](https://begenuin.com/developers/developer-resources/sdk/handle-genuin-deep-links-in-your-app) shows how to check it.
4. When your app receives a link, hand it to the SDK. You can do this as soon as the intent arrives, or after your home screen has loaded if you want it to sit behind your own UI:

```kotlin title="Handle a link"
GenuinSDK.handleDeepLink(context = this, deeplinkData = intent)
```

Android has no check method like iOS's `willHandleDeepLink`. Pass only links on your Genuin domain, because the SDK drops a link it doesn't recognize instead of opening the browser.

### Intercept link-outs

Videos can carry link-outs to product pages. When a link-out points at a URL your app handles, the SDK gives you the intent instead of opening the browser:

```kotlin title="Link-out interceptor"
GenuinSDK.registerLinkOutInterceptor(object : GenuinLinkOutInterceptor {
  override fun onLinkOutIntercept(intent: Intent, activity: Activity) {
    // Route the intent inside your app
  }
})
```

## Handle push notifications

Genuin sends notifications for replies, mentions, and community activity through Firebase Cloud Messaging (FCM). Your app owns the FCM integration, and the SDK registers the token and builds the notifications it recognizes. Genuin also needs your Firebase project's credentials to send anything; [Set up push notifications in the Genuin SDK](https://begenuin.com/developers/get-started/developers/genuin-sdk/creation-n-editing-tools/how-do-push-notifications-work-with-the-genuin-sdk) covers that step, the payload, and every notification type.

1. Create the app in the [Firebase console](https://firebase.google.com/docs/android/setup), add `google-services.json` to your project, and add the [FCM client SDK](https://firebase.google.com/docs/cloud-messaging/android/client).
2. Declare `POST_NOTIFICATIONS` and request it at runtime on Android 13 and later.
3. Register the FCM token with the SDK, on first launch and whenever it changes:

```kotlin title="Register the FCM token"
FirebaseMessaging.getInstance().token.addOnCompleteListener { task ->
  if (task.isSuccessful) GenuinSDK.registerFCMToken(this, task.result)
}
```

4. In your `FirebaseMessagingService`, let the SDK handle its own messages while the app is in the foreground, and re-register on token refresh:

```kotlin title="MyFirebaseMessagingService.kt"
class MyFirebaseMessagingService : FirebaseMessagingService() {
  override fun onMessageReceived(remoteMessage: RemoteMessage) {
    val data = remoteMessage.data
    if (GenuinSDK.willHandleForegroundNotification(data)) {
      GenuinSDK.handleForegroundNotifications(
        context = this,
        data = data,
        message = remoteMessage.notification?.body ?: "",
        title = remoteMessage.notification?.title,
        smallNotificationIcon = R.drawable.ic_notification
      )
    } else {
      // Your app's other notifications
    }
  }

  override fun onNewToken(token: String) {
    GenuinSDK.registerFCMToken(this, token)
  }
}
```

5. When the user taps a notification while the app is in the background or closed, Android delivers the payload to your launcher activity. Pass it on there:

```kotlin title="Notification tap"
if (GenuinSDK.willHandleNotification(intent)) {
  GenuinSDK.handleBackgroundNotifications(context = this, intent = intent)
}
```

`willHandleForegroundNotification` and `willHandleNotification` return `false` for notifications that aren't the SDK's, so your own handling is unaffected.

## Listen to SDK callbacks

Register these on `GenuinSDK` to react to what happens inside Genuin views:

| Callback | Interface | Fires when |
| --- | --- | --- |
| registerEventCallback | GenuinEventCallback.onEventCallback(name, properties) | The SDK records an analytics event, for forwarding to your own analytics |
| registerPlayerCallback | GenuinPlayerCallback (onPlay, onPause, onError, onAudioStateChanged, onFullScreenOpen, onFullScreenClose, …) | Playback or full-screen state changes, for pausing your own media or adjusting your UI |
| registerInteractionCallback / unregisterInteractionCallback | GenuinInteractionCallback.onInteraction(data, activity, continue) | The viewer interacts with a post; call continue to let the SDK proceed |
| registerInterface | GenuinInterface.onLogin(activity) | The viewer needs to sign in. See Sign users in. |
| registerLinkOutInterceptor | GenuinLinkOutInterceptor.onLinkOutIntercept(intent, activity) | A link-out targets a URL your app handles |

The SDK requests audio focus when a video plays with sound. Call `GenuinSDK.abandonAudioFocus()` if your app needs it back, and `GenuinSDK.releasePlayerCache()` to free the player's disk cache.

## Enable monetization

If your brand runs ads in embeds, Google Play requires the app to declare its use of the advertising ID. Add the permission:

```xml title="AndroidManifest.xml"
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />
```

Then declare it in Google Play Console: open the app, go to **Policy and programs** > **App content**, and find **Advertising ID**. Click **Manage**, answer **Yes** to using the advertising ID, complete the questionnaire, and submit. Builds without the declaration are rejected at review.

## Limitations

- **One version for all modules**: `core`, `ui`, `camera`, and `ai` must be the same version. Mismatches fail at runtime.
- **Minimum API 24**: Forcing a lower `minSdk` with `tools:overrideLibrary` lets the app build, but SDK features fail on devices below API 24.
- **Fixed-height containers**: Embed and placement views render nothing inside a `wrap_content` height.
- **Activity context**: Views need an `Activity`, set with `setActivity`; an application context doesn't work.
- **Portrait only**: The SDK doesn't lay out for landscape, tablets, or TV.
- **Playback follows your lifecycle**: Call `play`, `pause`, and `release` from the host screen, or audio continues in the background.
- **Deep links need the domain setup**: `handleDeepLink` only resolves links on a white-labelled domain with a valid `assetlinks.json`.
- **Camera module size**: Adding `camera` grows the app by about 48 MB before compression.

## See also

- [Integrate the Genuin iOS SDK](https://begenuin.com/developers/developer-resources/sdk/developer/genuin-sdk/native-compatibility/ios/how-to-integrate-and-power-your-brand-community-in-ios-apps)
- [Integrate the Genuin SDK with Flutter apps](https://begenuin.com/developers/developer-resources/sdk/how-do-you-integrate-the-genuin-flutter-sdk-into-your-application)
- [Integrate the React Native SDK](https://begenuin.com/developers/developer-resources/sdk/how-do-you-integrate-the-genuin-react-native-sdk-into-your-application)
- [Generate an embed code in Brand Control Center](https://begenuin.com/developers/browse-by-products/onsite/how-do-brands-generate-embed-codes-from-brand-control-center)
- [Set up push notifications in the Genuin SDK](https://begenuin.com/developers/get-started/developers/genuin-sdk/creation-n-editing-tools/how-do-push-notifications-work-with-the-genuin-sdk)
- [Customize the Genuin video player](https://begenuin.com/developers/browse-by-products/control/how-does-a-brand-customize-the-genuin-video-player-controls)
- [Handle Genuin deep links in your app](https://begenuin.com/developers/developer-resources/sdk/handle-genuin-deep-links-in-your-app)
