Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -643,8 +643,10 @@
"group": "Mobile SDK",
"pages": [
"stream/mobile-sdk",
"stream/mobile-sdk-token-authentication"
]
"stream/android-sdk",
"stream/ios-sdk"
],
"expanded": true
},
"stream/expo-video",
"stream/embedding",
Expand Down
191 changes: 191 additions & 0 deletions stream/android-sdk.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
---
title: "Android SDK"
description: "Integrate Bunny Stream into your Android apps with the Kotlin SDK - playback, uploads, camera capture, and embed view token authentication."
tag: "Kotlin"
keywords: ["Android", "Kotlin", "Mobile", "SDK"]
---

The Bunny Stream Android SDK lets you quickly integrate the Bunny Stream player, uploads, and video management into your Android applications.

## Key Features

- **Complete API Integration:** Full support for the Bunny REST Stream API
- **Efficient Video Upload:** TUS protocol implementation for reliable, resumable uploads
- **Advanced Video Player:** Custom-built player with full Bunny CDN integration
- **Camera Upload Support:** Built-in capabilities for recording and uploading videos directly from the device camera
- **Type-Safe API:** Fully typed Kotlin API for compile-time safety
- **Background Processing:** Support for background uploads and downloads
- **Comprehensive Error Handling:** Detailed error information and recovery options

## What is the Bunny Stream Android SDK?

Bunny Stream is an Android library designed to seamlessly integrate Bunny's powerful video streaming capabilities into your Android applications. The library provides a robust set of tools for video management, playback, uploading, and camera-based video uploads, all through an intuitive **Kotlin API**.

<Card title="Android SDK" horizontal href="https://github.com/BunnyWay/bunny-stream-android" cta="Hosted on github">
[https://github.com/BunnyWay/bunny-stream-android](https://github.com/BunnyWay/bunny-stream-android)
</Card>

## Token authentication

When using the Android SDK, **two authentication layers exist**:

- **Embed View Token Authentication** — controls access to the video.
- **CDN Token Authentication** — protects delivery from Bunny CDN and is handled automatically by the SDK.

This section focuses on **Embed View Token Authentication**, which must be handled by a **customer-managed backend or Edge Script** containing custom business logic that decides whether a client is allowed to play back a video by returning an embed view token.

### Embed View Token Authentication

Embed View Token Authentication:

- Authorizes a viewer to play a specific video
- Is enforced at the **Stream API level**
- Is required for private or restricted videos

**Android SDK responsibility**

- The **customer backend generates the token**.
- The app requests the token and passes it to the `PlayVideo` call.
- The SDK uses the token for playback; **CDN token signing happens automatically**.

### Supported authentication methods

| Method | Android SDK |
| --- | --- |
| Embed View Token Authentication | Supported via customer backend |
| CDN Token Authentication | Automatic |
| Client-side token signing | Not supported |

<Warning>
The **Video Library API Key** must **never** be included in your Android app. It is a secret that only your backend or Edge Script should hold.
</Warning>

### Backend requirements

Your backend (or Edge Script) must:

- Securely store the **Video Library API Key**, as it serves as a secret that must not be stored in the mobile app.
- Authenticate the app user with your custom business logic.
- Generate the embed view token by following the [token authentication signing procedure](/docs/stream/token-authentication#signing-procedure) (the token security key is your Video Library API Key).
- Return `token` and `expires` values in the response:

```json
{
"token": "SIGNED_EMBED_VIEW_TOKEN",
"expires": 1710000000
}
```

<Note>
Tokens should be **short-lived** (1–5 minutes recommended), unless you have a specific use case that requires a longer expiration.
</Note>

### Edge Script example

Below is an example Edge Script that generates embed view tokens. Store `VIDEO_LIBRARY_API_KEY` as an **Edge Script Secret**.

```typescript
BunnySDK.net.http.serve(async (request: Request): Promise<Response> => {
const url = new URL(request.url);

const apiKey = BunnySDK.env.VIDEO_LIBRARY_API_KEY;
const videoId = url.searchParams.get("videoId");
const expires = Math.floor(Date.now() / 1000) + 300; // 5 minutes or adjust if needed

if (!videoId) {
return new Response(JSON.stringify({ error: "Missing videoId" }), {
status: 400,
headers: { "Content-Type": "application/json" },
});
}

/*
* ============================================================
* Custom authentication / authorization logic
* ------------------------------------------------------------
* Perform your business checks here:
* - Validate user identity (e.g. by using JWT, API key, or some other auth headers)
* - Verify entitlement to this video
* - Apply subscription / access rules
*
* Only generate a token if access is allowed.
* ============================================================
*/

// Example:
// if (!isUserAuthorized(request, videoId)) {
// return new Response(
// JSON.stringify({ error: "Unauthorized" }),
// { status: 403, headers: { "Content-Type": "application/json" } }
// );
// }

const token = generateEmbedViewToken(apiKey, videoId, expires);

return new Response(JSON.stringify({ token, expires }), {
headers: { "Content-Type": "application/json" },
});
});

/**
* Embed View Token generation (per Bunny Stream docs):
*
* Token data sequence:
* Video Library API Key + videoId + expires
*
* Steps:
* 1. Concatenate the values in the order above (no separators)
* 2. Generate HMAC-SHA256 using the Video Library API Key
* 3. Base64 encode: <signature>:<expires>
*/
function generateEmbedViewToken(
apiKey: string,
videoId: string,
expires: number,
): string {
// @ts-ignore - crypto is available in the Edge runtime
const crypto = require("crypto");

const data = apiKey + videoId + expires;
const signature = crypto
.createHmac("sha256", apiKey)
.update(data)
.digest("hex");

return Buffer.from(`${signature}:${expires}`).toString("base64");
}
```

### Android SDK usage

The `PlayVideo` call supports token parameters:

```kotlin
PlayVideo(
...
videoId = "abc123",
token = token,
expires = expires
...
)
```

**Flow**

<Steps>
<Step title="Request the embed token">
Call your backend or Edge Script from the app to request a token for the target `videoId`.
</Step>
<Step title="Receive the token">
Your backend returns `{ token, expires }`.
</Step>
<Step title="Play the video">
Pass the `token` and `expires` values to the `PlayVideo` call.
</Step>
</Steps>

## Important notes

- The **Video Library API Key** must **never** be included in mobile apps.
- Embed View Token Authentication is **required** if you don't want to publicly expose your videos.
- CDN Token Authentication is applied to CDN URLs automatically if it is turned on in the video library.
43 changes: 17 additions & 26 deletions stream/expo-video.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,36 +41,35 @@ Bunny Player supports features like Picture-in-Picture and background audio on t
}
}
```

</Step>
<Step title="Play a video">
Every processed Bunny Stream video has an HLS playlist at a predictable URL:

```
```text
https://{pullZone}/{videoId}/playlist.m3u8
```

Find your Pull Zone hostname under **Stream > API** in the bunny.net dashboard.
Find your Pull Zone hostname under **Stream \> API** in the bunny.net dashboard.

Pass the HLS URL to `useVideoPlayer` and render a `VideoView`:

```tsx
import { useVideoPlayer, VideoView } from "expo-video";
import { useWindowDimensions } from "react-native";

const PULL_ZONE = "vz-abc123-456.b-cdn.net";
const VIDEO_ID = "your-video-guid";

export default function VideoScreen() {
const { width } = useWindowDimensions();

const player = useVideoPlayer(
`https://${PULL_ZONE}/${VIDEO_ID}/playlist.m3u8`,
(p) => {
p.loop = false;
}
);

return (
<VideoView
player={player}
Expand All @@ -84,27 +83,24 @@ Bunny Player supports features like Picture-in-Picture and background audio on t
```

That's it. The player handles adaptive bitrate selection automatically, choosing the best quality for the device's connection.

</Step>
</Steps>

<Note>
A complete working example with a video list screen, detail screen, chapter
navigation, and caption picker is available at
[bunny-stream-expo](https://github.com/jamie-at-bunny/bunny-stream-expo).
A complete working example with a video list screen, detail screen, chapter navigation, and caption picker is available at [bunny-stream-expo](https://github.com/jamie-at-bunny/bunny-stream-expo).
</Note>

## URL structure

Every processed video is accessible via predictable URLs built from your Pull Zone hostname and the video's GUID:

| Resource | URL pattern |
| ---------------- | -------------------------------------------------- |
| HLS playlist | `https://{pullZone}/{videoId}/playlist.m3u8` |
| Thumbnail | `https://{pullZone}/{videoId}/{thumbnailFileName}` |
| Animated preview | `https://{pullZone}/{videoId}/preview.webp` |
| MP4 fallback | `https://{pullZone}/{videoId}/play_{height}p.mp4` |
| Captions | `https://{pullZone}/{videoId}/captions/{lang}.vtt` |
| Resource | URL pattern |
| --- | --- |
| HLS playlist | `https://{pullZone}/{videoId}/playlist.m3u8` |
| Thumbnail | `https://{pullZone}/{videoId}/{thumbnailFileName}` |
| Animated preview | `https://{pullZone}/{videoId}/preview.webp` |
| MP4 fallback | `https://{pullZone}/{videoId}/play_{height}p.mp4` |
| Captions | `https://{pullZone}/{videoId}/captions/{lang}.vtt` |

## Fetching video metadata

Expand All @@ -125,8 +121,7 @@ async function getVideo(videoId: string) {
```

<Warning>
The `AccessKey` is a secret. In a production app, proxy API calls through your
backend so the key never reaches the client.
The `AccessKey` is a secret. In a production app, proxy API calls through your backend so the key never reaches the client.
</Warning>

The response includes fields you can pass directly to the player:
Expand Down Expand Up @@ -228,11 +223,7 @@ const selectTrack = (track: SubtitleTrack | null) => {
If your library has [MediaCage Enterprise DRM](/stream/quickstart-mediacage-enterprise) enabled, `expo-video` can request play licenses directly from Bunny's license servers using the `drm` source option.

<Info>
The license endpoints apply the same referrer protection and token
authentication as the player embed view. If either is enabled in your library
settings, include the corresponding headers or query parameters in your
requests. See [Embedded view token auth](/stream/token-authentication) for
details.
The license endpoints apply the same referrer protection and token authentication as the player embed view. If either is enabled in your library settings, include the corresponding headers or query parameters in your requests. See [Embedded view token auth](/stream/token-authentication) for details.
</Info>

On iOS, use FairPlay with Bunny's certificate and license endpoints:
Expand Down Expand Up @@ -262,4 +253,4 @@ const source = {
licenseServer: `https://video.bunnycdn.com/WidevineLicense/${LIBRARY_ID}/${videoId}`,
},
};
```
```
Loading