@dolby-optiview/ads-sdk-react-native
v1.0.0
Published
OptiView Ads SDK for React Native - bridges the native Android/iOS/tvOS SDKs
Readme
@dolby-optiview/ads-sdk-react-native
React Native support for the OptiView Ads SDK, built by bridging the native
Android/iOS/tvOS SDKs: a typed JS facade + TurboModule delegate to the native
OptiViewAds orchestrator, which owns the PlayerAdapter, overlay rendering,
and IMA.
RN app (JS)
└─ @dolby-optiview/ads-sdk-react-native typed facade: config, startSession(), events,
│ muted/volume, skip/click controls,
│ diagnostics, interceptors
└─ TurboModule (bridge)
└─ native OptiViewAds orchestrator (ads-sdk / OptiViewAdsSDK)
├─ PlayerAdapter wraps the *native* player instance
├─ OverlayAdRenderer native view above the player view
└─ GamStreamManager / VastAdManager (native IMA)Key properties:
- Native-thread scheduling — the RN JS thread is paused/throttled in PiP and background modes, so no scheduling, break punch-in, cue matching, or ad-progress logic depends on it. Events are buffered natively while JS is paused and delivered on resume.
- Playback-anchored cutoff — Android and iOS/tvOS own the cutoff timer natively;
RN Web uses the Web SDK. Loading does not consume the effective-duration plus
adBreakCutSafetyMarginSecbudget. Both native bridges preserve a custom margin, including zero. Startup remains bounded independently, and resumed playback does not reset the cutoff. No JS timer drives this behavior. - API parity with web — the config is the web
OptiViewAdsConfigminus its DOM/function members, and events reuseOptiViewAdsEventMapverbatim. - New architecture only (RN ≥ 0.76): TurboModule + Fabric overlay host.
- Host players:
react-native-theoplayerandreact-native-video, both resolved through the native view tag (nativeHandle; use the typeduseReactVideoHandle()helper forreact-native-video).
Installation
Install the beta from npm:
npm install @dolby-optiview/ads-sdk-react-native@nextFor reproducible native builds, replace @next with an exact published version
and use --save-exact. Native artifacts must match the installed wrapper
version. Android automatically includes the THEOplayer integration when the
:react-native-theoplayer Gradle project is autolinked; otherwise it builds
without THEOplayer. iOS/tvOS can also install either host without the other.
Keep the supported RN new-architecture toolchain (and matching
react-native-tvos for Apple TV).
The minimum react-native-theoplayer version is 11.7.0 on Android and Web for the experimental PlayerFacade API. Android uses normal Gradle dependency resolution and typed API calls, without package-version or source-file probes. Web also assumes a supported wrapper is installed, without runtime version checks. iOS/tvOS may use older wrappers: they do not use PlayerFacade. The optional npm peer range therefore remains unrestricted.
If an existing .npmrc routes @dolby-optiview to another registry, configure
that scope to use https://registry.npmjs.org/ for this consumer. No override is
needed when the scope already resolves from public npm.
Android
Add the native repositories to the React Native projects' Gradle configuration, retaining Google and Maven Central:
// android/build.gradle
allprojects {
repositories {
maven { url "https://maven.theoplayer.com/releases" }
// Also required for beta/prerelease SDK versions:
maven {
url "https://maven.theoplayer.com/snapshots"
content { includeGroup "com.dolby.optiview" }
}
google()
mavenCentral()
}
}React Native's Gradle plugin may ignore settings-level-only repositories;
ensure the repositories are available to the RN projects.
The bridge selects the exact Maven version from the installed wrapper's
package.json; npm does not contain the native binaries. Do not add
snapshotsOnly() filtering: a -beta.N version is not a -SNAPSHOT version.
Keep Java 17 and enable core library desugaring in android/app/build.gradle:
android {
compileOptions { coreLibraryDesugaringEnabled true }
}
dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.5'
}Restrict the snapshots repository to the OptiView group as shown above, so THEOplayer's version ranges do not select nightly native SDK builds.
For react-native-video, also set this in the host app's android/gradle.properties:
RNVideo_useExoplayerIMA=trueHost selection follows Android autolinking at build time. If the
:react-native-theoplayer Gradle project is present, its typed integration is
compiled and linked. Otherwise THEOplayer-specific sources and dependencies are
excluded. This checks dependency presence only, not versions, files or methods.
No SDK host-selection flag or JS API change is needed. If both players are
linked, the supplied player view determines the binding; their Media3 dependencies
must still be compatible.
The IMA setting uses the real integration instead of react-native-video's stub classes, which otherwise conflict with the IMA SDK supplied by the OptiView runtime.
iOS / tvOS
Add both sources to each platform's Podfile, alongside its normal React Native
autolinking setup, then run bundle exec pod install in that native project:
source 'https://github.com/THEOplayer/cocoapods-specs.git'
source 'https://cdn.cocoapods.org/'The Specs repository is publicly readable over HTTPS, but is not CocoaPods trunk. Keep one platform per Podfile and use compatible Xcode/React Native tooling.
Explicit THEOplayer header workaround
Unpatched react-native-theoplayer 11.5.0/11.6.0 imports a header absent from
React Native 0.87.0 and React Native tvOS 0.87.0-0. For these Apple combinations,
you may explicitly opt into this narrow workaround while waiting for an
upstream wrapper fix:
After installing dependencies, open
node_modules/react-native-theoplayer/ios/Theoplayer-Bridging-Header.h.Remove only this unused import, retaining all other imports:
#import <React/RCTRootContentView.h>Persist that one-line change through your app's dependency-patch process so it is reapplied after reinstalls, then run
bundle exec pod installand build.
Do not apply this recipe to other versions without checking compatibility. The Ads SDK never applies it automatically or modifies published SDK artifacts.
Native dependency selection
The package autolinks its bundled OptiViewAdsReactNative.podspec and defaults
to exact-version OptiViewAdsRuntime and OptiViewAdsReactBridge pods.
Core/SDK arrive transitively; OptiViewAdsAdapterTHEOplayer is added at the
same version only when the host includes react-native-theoplayer. The wrapper
itself is installed from node_modules, not the remote Specs repo.
| Environment at pod install | OptiView native dependencies |
| --- | --- |
| OPTIVIEW_ADS_NATIVE_PODS unset or 1 (default) | CocoaPods, pinned exactly to the wrapper version |
| OPTIVIEW_ADS_NATIVE_PODS=0 | Opt out; the host supplies the native dependencies itself |
On react-native-web, use the package's browser entry and the web SDK's normal player setup; Maven/CocoaPods configuration is only for native targets. See the SDK integration guide.
Managed sessions and React ownership
OptiViewAdsSession owns one fresh connector and one asynchronous startup.
It exposes ready, connector, destroyed, getSnapshot() and subscribe().
destroy() waits for pending startup and returns the same cleanup promise on
repeated calls. Startup failures automatically clean up the connector and remain
observable as status: 'failed' with the original error.
For an ordinary player, let the hook own the session. Memoize the factory; changing
its identity explicitly replaces the session, after the previous cleanup finishes.
Return null while the player is not ready. The factory runs after React commits,
not during render, and StrictMode effect replay does not create a second session.
import { useCallback } from 'react';
import {
createOptiViewAds,
OptiViewAdsSession,
useOptiViewAds,
} from '@dolby-optiview/ads-sdk-react-native';
const createSession = useCallback(() => player
? new OptiViewAdsSession(createOptiViewAds(player, sdkConfig), sessionConfig)
: null, [player, sdkConfig, sessionConfig]);
const ads = useOptiViewAds({ ownership: 'hook', createSession });For floating playback, retain the session in your playback owner, outside the UI component that unmounts. Observe it from any new host:
const ads = useOptiViewAds({ ownership: 'external', session: retainedSession });External mode only subscribes to lifecycle state. Unmounting or switching the
observed session never destroys it; the playback owner must call
await retainedSession.destroy() before releasing the actual native player.
This does not preserve or reparent the native player for you. Do not use a React
view tag as persistent identity: a restored host may have a new tag while the
underlying player remains the same. Retain the session object instead.
Both modes return { session, connector, status, error }. Status is idle without
a session, then starting, active, failed or destroyed. Destruction is
requested immediately; await destroy() to know native cleanup has finished.
Use the managed object's lifecycle rather than directly calling startSession,
endSession or destroy on its connector. SDK events, diagnostics, controls and
updateAssetParameters / updateAssetParameterMacros remain available through
ads.connector; changing targeting does not require recreating the session.
The hook does no debug polling and imports no player library or application store.
The same API is exported from the native and web entries.
THEOplayer binding and optional debug state
useTHEOplayerAdsBinding works with the native and web entries. It enables
usePlayerFacade, forwards host callbacks, waits for a native handle (and the
web player element), and returns a memoized createSession factory. It does not
create or destroy sessions itself.
const binding = useTHEOplayerAdsBinding({
config: playerConfig,
adsConfig: sdkConfig,
sessionConfig,
enabled: adsEnabled,
onPlayerReady,
onPlayerDestroy,
});
const ads = useOptiViewAds({ ownership: 'hook', createSession: binding.createSession });
const debug = useOptiViewAdsDebug({
connector: ads.connector,
status: ads.status,
enabled: debugEnabled,
maxEvents: 50,
});
return <THEOplayerView config={binding.playerConfig}
onPlayerReady={binding.playerHandlers.onPlayerReady}
onPlayerDestroy={binding.playerHandlers.onPlayerDestroy} />;Import both new hooks from @dolby-optiview/ads-sdk-react-native. The binding's
enabled defaults to true; missing adsConfig or sessionConfig keeps its
factory idle. Equivalent configuration data, including reordered object keys,
keeps the same factory. Changed configuration, session data, player, enabled
state, or targeting-macro callback identity changes it. Memoize macro callbacks;
use connector targeting-update methods for changes that must not restart playback.
The bridge-safe config plus session macro callbacks are supported, not DOM-based
web SDK configuration. Configurations must be acyclic.
External owners can use createSession, configKey, and sessionKey to retain
and compare sessions. Keys are opaque in-process values, may contain configuration
data, and must not be logged or persisted. The owner decides whether to replace a
retained session. After restoring a retained player, call
binding.playerHandlers.onPlayerRestored(player); this updates the binding and
forwards the optional onPlayerRestored callback. It is not a THEOplayerView prop.
The hook never retains native views, transfers player IDs, or chooses floating policy.
useOptiViewAdsDebug is player-independent and opt-in by calling it. enabled
defaults to true; false removes its subscriptions and timer. It returns events,
details, clearEvents, and exportDiagnostics. History contains only IDs,
local receipt timestamps and event names, newest first, without progress ticks or
raw event payloads. maxEvents defaults to 50, is clamped to 0–1000, and changing
it clears history and metadata. details includes current ad IDs, channel,
SDK version, break status and presentation state. These details are for local
inspection, not a redacted telemetry payload.
Polling runs once per second only when status is active (the default), never
overlaps requests, and ignores late results after cleanup. Metadata and history
reset on connector change or disabling. No polling runs in useOptiViewAds
itself. clearEvents resets history only. exportDiagnostics remains an explicit
on-demand operation even with automatic observation disabled; it returns null
without a connector and propagates export failures to the caller. Neither debug
observation nor its cleanup controls playback or destroys sessions.
Usage
import { createOptiViewAds } from '@dolby-optiview/ads-sdk-react-native';
const ads = createOptiViewAds({ nativeHandle: viewTag }, config); // config: OptiViewAdsReactNativeConfig
await ads.startSession({ channelId: 'my-channel' });
ads.addEventListener('adbreakbegin', (e) => console.log(e.break.id));
const skipped = await ads.skipAd(); // false until the manifest skipOffset is reached
const clickThrough = await ads.clickAd(); // URL or null; the app decides whether to navigate
ads.setManifestRequestInterceptor(async (request) => ({ ...request }), { timeoutMs: 1500 });
await ads.destroy();For react-native-theoplayer, pass the player received by onPlayerReady
directly on Android, iOS, tvOS, and react-native-web:
const ads = createOptiViewAds(player, config);The browser entry unwraps player.nativeHandle, creates the THEOplayer web
adapter, and uses the browser player's element as the overlay container.
No app-side adapter import or handle cast is needed. Create the connector only
after the player is mounted; an invalid handle or missing element throws.
Destroying the connector releases its adapter listeners without destroying the
host player. Native targets still resolve a positive integer view tag.
On web, use the package's browser export condition, or explicitly import
createOptiViewAds from @dolby-optiview/ads-sdk-react-native/web. The web SDK
runs in-process, without a bridge or TurboModule. The existing one-argument
createOptiViewAds(webConfig) remains supported for custom adapters and overlay
containers; their lifecycle remains the caller's responsibility.
react-native-video continues to use the native bridge only.
THEOplayer Android ad events
Enable usePlayerFacade: true before player creation with
react-native-theoplayer 11.7.0 or newer. The native integration registers with
com.theoplayer.integration.Integration, supplies its own Ads API, and forwards
ad lifecycle and playback events without routing timing through JavaScript.
Android ads and breaks use GOOGLE_IMA for CSAI reporting compatibility because
Bitmovin treats CUSTOM as SSAI and the native API has no generic CSAI kind.
customIntegration: optiview-ads preserves provider identity. OptiView still owns
playback; this metadata does not activate THEOplayer's native IMA integration.
single and lshape_ad breaks own the player clock and playback state throughout
creative gaps. Active-break adtimeupdate values become player timeupdate
unchanged, including whole-pod GAM time. Raw content playback events are hidden;
source changes and destruction remain visible. SDK background pauses and seeks
are suppressed through their eventual completion. Other formats retain content
playback controls. Audio and skip commands delegate to the SDK.
The SDK scheduler reads the raw content engine, never the facade ad clock. Session end resets integration state; destruction unregisters it. The Android THEOplayer integration is included only when its Gradle project is autolinked; apps without that project automatically use the video-only build. Combining native players still requires compatible Media3 dependencies. Native ads must finish before registration.
THEOplayer web ad events
With react-native-theoplayer 11.7.0 or newer, enable
usePlayerFacade: true in the player configuration before onPlayerReady.
The shared factory automatically registers AdsSDKIntegration: Dolby break/ad
lifecycle, quartiles, ad errors and clicks become THEOplayer ad events, identified
by integration: 'csai' with customData.provider: 'optiview-ads'. Web clicks use adclicked; the wrapper must
support forwarding that event. Analytics must observe the facade, not its raw
contentPlayer.
The facade owns the clock throughout single and lshape_ad breaks. It starts
at zero and retains the last SDK sample across creative gaps and terminal callbacks.
Valid adtimeupdate samples for the active break update the clock and forward
player timeupdate unchanged. GAM samples use whole-pod time, including slate,
not time relative to each creative. Other formats leave the content clock alone.
The SDK itself always uses the underlying content player so its scheduler never
reads the facade's ad clock. No skip, impression or completion reason is inferred.
Session end clears unfinished break state; destruction also unregisters the
integration. Plain browser players remain supported without this event bridge.
The integration supplies an Ads API object with its own subscriptions, current
state, empty scheduled lists and skip() delegation. A compatible facade keeps
existing listeners attached across registration and removal. Ad playing, waiting
and sampled progress events represent full-surface ad playback; paused stays
false, including buffering. Public play/pause are consumed during these ads;
pausing the ad itself is not supported. Non-content-replacing formats keep
content playback state and controls.
With catch-all facade interception, single and lshape_ad breaks suppress raw
content playback events until break end; sourcechange and destroy remain visible.
SDK-owned content seeks, including deferred resume writes, keep their seeking /
seeked events hidden through completion after the break. Public seeking stays
false during takeover and pending background seeks. Viewer seeks through the facade
or connector after handoff remain visible. Raw SDK listeners are never filtered;
older facade builds retain limited per-event filtering.
Facade mute/volume, including changes during content, use the SDK's effective audio owner and existing handoff synchronization, including double-box audio focus. Duplicate audio notifications are absorbed. The sampled clock follows the SDK ad asset or GAM pod; a continuous combined content-and-ad timeline is not implemented. Direct Ads scheduling and target-position break skipping are unsupported.
THEOplayer iOS and tvOS ad events
When react-native-theoplayer is installed, the native bridge automatically
registers the Dolby Ads integration on the resolved THEOplayer instance. Ad
lifecycle, quartiles, errors, playing, waiting and sampled progress are exposed
through THEOplayer's native player and Ads APIs without crossing the JavaScript
thread.
For content-replacing breaks, THEOplayer reports the SDK's sampled ad-local time while the backing content is paused. The internal content pause and resume seek sequence are not exposed to player API consumers. The integration is removed before the native Ads SDK instance is destroyed. Other host players do not install this THEOplayer-specific integration.
A persistent Dolby-owned view controller owns the overlay for the connector's lifetime. When react-native-theoplayer reparents its React container for fullscreen, or a native host moves it into a floating-player controller, overlay reanchoring moves the Dolby controller before its view. GAM and VAST IMA retain an owner that still matches the ad view hierarchy.
The native ad models currently identify as Google IMA for compatibility with analytics collectors that classify this integration kind as client-side. The underlying GAM/IMA ad metadata remains available on the translated ad object.
API reference
Every exported function, type and connector method carries TypeScript
documentation, visible in the editor and rendered as an HTML API reference that
is published with each release next to the web SDK's reference. The connector
methods are grouped by task (lifecycle, playback and UI, events, diagnostics),
the same way the web OptiViewAds class is.
Supported platforms
- Android — native bridge with
react-native-theoplayerorreact-native-video(Media3/ExoPlayer) as the host player. - iOS and tvOS — native bridge with the same host players; tvOS through
react-native-tvos. - react-native-web — the browser entry over the web SDK and its adapters.
Platform notes
- Anvato break signaling over DASH (
ptsSource: 'anvatoCue'): the config value bridges verbatim on all native RN platforms. On RN Android the native Media3/ExoPlayer and THEOplayer adapters deliver Anvato DASH emsg cues (both the directurn:anvato:es1:052016scheme and the standardized ID3-in-emsg carriage). On RN iOS/tvOS playback goes through AVFoundation, which has no DASH engine — Anvato signaling there arrives as HLS ID3GEOBframes, which are already supported; a DASH source simply cannot be played on those platforms.
