This repository is a reference sample — a minimal, runnable app that shows how to play Gumlet DRM-protected streams with react-native-video on iOS (FairPlay) and Android (Widevine).
Use it to understand the integration patterns, test your Gumlet DRM setup, and copy the relevant code into your own React Native application.
This repo is:
- A working demo with platform-specific form fields for pasting stream and licence values
- A starting point for custom native player integrations
This repo is not:
- A published npm package or production-ready player SDK
- A substitute for
@gumlet/react-native-embed-player(Gumlet's WebView-based embed player)
| File | Purpose |
|---|---|
App.tsx |
react-native-video source and drm configuration per platform |
utils/signWidevineLicense.ts |
Gumlet licence-proxy HMAC signing (Android demo only) |
flowchart LR
subgraph sample [This sample app]
AppTsx[App.tsx]
SignUtil[signWidevineLicense.ts]
end
subgraph prod [Your production app]
YourUI[Your player UI]
YourAPI[Your backend API]
end
AppTsx -->|copy drm config| YourUI
SignUtil -->|move to backend| YourAPI
YourAPI -->|token + expires| YourUI
Before starting, set up your local development environment according to the React Native Environment Setup Guide.
| Requirement | Notes |
|---|---|
React Native 0.76+ (as pinned in package.json) |
Match or test against your own RN version |
react-native-video ^6.7 |
Sample uses the v6 DRMType / source.drm API — v7 has a different DRM plugin API |
| Physical iOS device | FairPlay does not work on the iOS Simulator (Apple limitation) |
| Physical Android device (recommended) | Widevine may work on emulators, but real-device testing is strongly advised |
| Gumlet account with DRM enabled | FairPlay credentials uploaded; asset transcoded with DRM keys |
npm install
# or
yarn installcd ios
pod install
cd ..iOS (use a physical device for DRM):
npm run ios
# or
yarn iosAndroid:
npm run android
# or
yarn android- Launch the app — platform-specific fields appear automatically.
- Paste your Gumlet stream and licence values (see Configuration below).
- Tap Play Video — the app builds a
sourceobject and mounts<Video />. - Tap Stop Video — clears
sourceand unmounts the player.
The text inputs are demo-only. In production, fetch stream URLs and signed licence URLs from your backend at playback time.
| Field | URL pattern |
|---|---|
| iOS stream | HLS manifest, e.g. https://video.gumlet.io/<collection>/<asset>/main.m3u8 |
| iOS FairPlay licence | https://fairplay.gumlet.com/licence/<ORG_ID>/<ASSET_ID> |
| iOS FairPlay certificate | https://fairplay.gumlet.com/certificate/<ORG_ID> |
| Android stream | DASH manifest, e.g. https://video.gumlet.io/<collection>/<asset>/main.mpd |
| Android Widevine licence proxy | https://widevine.gumlet.com/licence/<ORG_ID>/<ASSET_ID> |
From the Gumlet DRM Dashboard, obtain:
licence_url_sign_secret— Base64 HMAC key used to sign both FairPlay and Widevine licence URLs- Staging vs production hosts (some orgs use
widevine-stage.gumlet.cominstead ofwidevine.gumlet.com)
Replace <ORG_ID> with your organisation ID and <ASSET_ID> with the Gumlet video asset ID.
To play your DRM streams in the demo, fill in the following fields in the app interface.
| Field | Description |
|---|---|
| HLS URL | The .m3u8 stream URL for the video |
| FairPlay licence URL | Licence server URL including ?token=...&expires=... (the sample does not sign this at runtime — paste a pre-signed URL) |
| FairPlay certificate URL | Org-level certificate endpoint (https://fairplay.gumlet.com/certificate/<ORG_ID>) |
The sample uses a custom getLicense hook (App.tsx) that:
- POSTs JSON
{ spc: "<base64 SPC>" }to the licence URL - Expects a JSON response with a
ckcfield - Returns
ckctoreact-native-video
| Field | Description |
|---|---|
| MPD URL | The MPEG-DASH .mpd stream URL. Widevine is not supported on HLS .m3u8 in standard Android environments — use DASH. |
| Widevine licence proxy URL | Base licence URL without query params. Format: https://widevine.gumlet.com/licence/<ORG_ID>/<ASSET_ID>. The app appends token and expires. |
| Widevine proxy secret (Base64) | Your org's licence_url_sign_secret from the Gumlet DRM Dashboard |
| Token lifetime (seconds) | Validity window for the signature token (default: 30 seconds) |
Android playback also requires source.type = 'mpd' — see App.tsx.
Gumlet requires each licence request to be authenticated with an HMAC-SHA1 signature. The canonical algorithm (used by Gumlet's embed player and API) is:
expires = round(now + lifetime_ms) // milliseconds since epoch
stringToSign = "/<ORG_ID>/<ASSET_ID>?expires=<expires>"
token = HMAC-SHA1(Base64_decode(secret), stringToSign) as hex
licenceUrl = "<base_licence_url>?token=<token>&expires=<expires>"
Reference implementation: utils/signWidevineLicense.ts.
Steps in detail:
- Extract the path — strip protocol, host, and
/licence/prefix to get<ORG_ID>/<ASSET_ID> - Calculate expiration —
now + token_lifetimein milliseconds - Build the message —
/<ORG_ID>/<ASSET_ID>?expires=<expiration_timestamp> - HMAC-SHA1 — sign with the Base64-decoded
licence_url_sign_secret - Append parameters —
?token=<hex_signature>&expires=<expiration_timestamp>
| Platform | Signing in this sample | Production expectation |
|---|---|---|
| Android | Client-side at Play tap (demo convenience) | Backend signs before playback |
| iOS | User pastes a pre-signed licence URL | Backend signs using the same algorithm |
The same licence_url_sign_secret and algorithm apply to both FairPlay and Widevine licence URLs.
iOS source object — from App.tsx:
{
uri: hlsUrl,
drm: {
type: DRMType.FAIRPLAY,
licenseServer: signedFairplayLicenceUrl,
certificateUrl: fairplayCertUrl,
getLicense: (spcString, _contentId, licenseUrl) =>
fetch(licenseUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ spc: spcString }),
})
.then(r => r.json())
.then(r => r.ckc),
},
}Android source object:
{
uri: mpdUrl,
type: 'mpd',
drm: {
type: DRMType.WIDEVINE,
licenseServer: signedWidevineLicenceUrl,
},
}You can also port the signing logic from utils/signWidevineLicense.ts — but see the production notes below.
- Remove demo TextInputs — fetch the stream URL and signed licence URL (or signing inputs) from your API at playback time.
- Move signing to your backend — never ship
licence_url_sign_secretin the mobile app. The sample signs client-side only for convenience. In production, your API returns{ token, expires }and the client appends them to the licence URL. - Sign FairPlay licence URLs server-side — same HMAC algorithm as Widevine; the iOS sample currently expects you to provide the full signed URL.
- Handle token expiry — the default 30-second lifetime is for demo use. Re-sign or re-fetch from your API before licence requests fail on long sessions, seeks, or rebuffers.
- Playback URL signing is separate — if your collection has
signed_urlprotection enabled, the stream URL (.m3u8/.mpd) needs its own HMAC signature usingsigned_url_secret(a different algorithm: pathname + expiry in seconds). This sample does not demonstrate stream URL signing. - Error handling — replace
Alert.alertwith your app's error UX; log licence HTTP status codes for debugging. react-native-videoversion — this sample targets v6. v7 uses the@react-native-video/drmplugin with a different API; adapt accordingly if upgrading.
- iOS = HLS + FairPlay; Android = DASH + Widevine. Do not use
.m3u8for Widevine on Android. - Android must set
source.type = 'mpd'. - FairPlay requires a real iOS device — the simulator will fail.
expiresis in milliseconds (not seconds).- The secret is Base64-encoded before HMAC.
- The Widevine proxy URL must have no existing query params before signing.
- The signing string is
/<ORG_ID>/<ASSET_ID>?expires=...— no host, no/licence/prefix in the HMAC input. - iOS and Android share the same
licence_url_sign_secret.
- Gumlet expects request body
{ spc: "..." }and response{ ckc: "..." }. - 403 or 415 errors usually mean invalid Content-Type, missing
token/expireson the licence URL, or an expired signature.
- Do not commit secrets or hardcode org credentials in your app.
- Client-side signing in this sample is not a security model — extract the algorithm and implement it on your server.
- The internal Xcode project is still named
FairplayDRMExample— cosmetic only; safe to rename when forking. crypto-jsis used because React Native lacks Node'scryptomodule. Your backend should use nativecryptoor OpenSSL.
- Use Reactotron (
ReactotronConfig.js) to inspect licence network requests during development. - Test with a DRM-enabled asset that has completed transcoding.
| Symptom | Likely cause |
|---|---|
| iOS playback fails immediately | Testing on simulator; invalid or expired licence URL; missing certificate URL |
| Android "Failed to sign" alert | Invalid Base64 secret or malformed proxy URL |
| 403 on licence request | Wrong HMAC, expired expires, or secret rotated in dashboard |
| Video loads but black screen / decode error | Wrong stream format (HLS on Android); unsigned playback URL when signed_url is enabled |
getLicense error on iOS |
Licence URL missing token/expires; server returned non-JSON or missing ckc |
If you do not need direct react-native-video integration, consider @gumlet/react-native-embed-player:
- WebView-based Gumlet embed player for React Native
- Accepts
drm_tokenandexpiresfrom your backend as props (see its README) - Gumlet-hosted player UI, controls, and embed features out of the box
Choose the embed player if you want a hosted player experience with minimal native video code.
Choose this sample if you need a custom native player, offline hooks, or full control over the video surface.
- react-native-video — v6 source-based DRM configuration
- React Native environment setup
- Gumlet — dashboard and documentation