Skip to main content
OlamOTT
Cross-Platform Video Apps

Building an SDK for Smart TV and Cross-Platform Video Apps

A practical architecture for exposing video, live-event, playback, highlight, and content capabilities across web, mobile, native, and Smart TV applications.

Reading time
10 min read
Published date
Published 2026-07-20
SDK ArchitectureSmart TVCross-Platform VideoPlaybackGraphQLDeveloper Experience

An SDK is a contract, not repackaged application code

A video platform may already have applications, API clients, player integrations, and reusable components. It is tempting to collect that code into a package and call it an SDK.

The harder test comes later. A separately developed host application upgrades on its own schedule, owns its own navigation and authentication, uses a different player stack, and may run for years on an installed Smart TV. Internal application code can change whenever its owners coordinate a release. A public SDK contract cannot.

That is the core difference: a production SDK is a stable boundary between a video platform and applications the platform does not control. It exposes capabilities while hiding backend topology, transport details, state libraries, and device-specific mechanics.

Decide what kind of integration you are offering

“SDK” can describe several different products, and confusing them creates mismatched expectations.

  • A UI library supplies visual building blocks but usually leaves data, playback, and workflows to the host.
  • A player library owns media-session behavior around a playback engine but not catalog, highlights, or live-event discovery.
  • A domain SDK exposes video concepts such as content, events, playback sessions, timelines, and highlights without dictating presentation.
  • An embeddable application experience delivers a complete flow or surface, including UI, state, and navigation inside a defined boundary.

These models can coexist, but they should be named and versioned separately. A headless domain SDK with optional UI is often the most flexible foundation. A fully embeddable experience favors consistency, while a player-only integration fits hosts that own the rest of the video domain.

Draw the ownership line before designing methods

The SDK should own the meaning and behavior of the capabilities it exposes: mapping backend data into stable domain models, coordinating playback setup, normalizing failures, managing its internal state, and emitting documented lifecycle and analytics events.

The host should normally own top-level routing, login UI, credential storage, global navigation, branding, accessibility policy, consent decisions, and the surrounding application lifecycle. On television, the host should also own the overall focus graph and decide how Back exits the SDK surface.

Some responsibilities are negotiated boundaries. The SDK may render a content rail while the host chooses its placement and theme. It may manage focus within that rail but must return focus to a host-provided target when it closes. Write these rules down.

Use a headless domain core and explicit adapters

The most durable shape is a platform-neutral core surrounded by adapters. The core models content, live events, highlights, playback intent, authorization state, and normalized outcomes. It should avoid direct dependencies on browser globals, native views, remote-control APIs, and specific playback engines. Platform access should instead arrive through explicit interfaces.

Host application
  └─ Public SDK contract
       ├─ Video-domain core
       │    ├─ Content, live events, highlights
       │    ├─ Playback orchestration
       │    └─ State, errors, analytics intents
       ├─ Data and authentication ports
       ├─ Platform adapters
       │    ├─ Web / React Native / Android / iOS
       │    └─ Tizen / webOS / VIDAA
       │         ├─ Device lifecycle
       │         └─ Focus and remote input
       └─ Playback adapters
            └─ Browser / native / Smart TV engines

Web and Smart TV web runtimes may share substantial TypeScript, but that should be an outcome rather than a mandate. React Native, Android, and iOS artifacts may share generated contracts while using native playback and lifecycle implementations. Tizen, webOS, and VIDAA adapters can share domain behavior yet preserve different key maps, media APIs, runtime constraints, and packaging rules.

The goal is shared semantics, not one universal codebase. Forced reuse moves platform conditionals into the core until the supposedly neutral layer understands every device.

Keep the public API small and lifecycle-aware

Every exported type and method becomes a compatibility promise. Prefer a few domain operations over exporting transport clients, cache objects, reducers, or player instances.

type VideoSdkOptions = {
	auth: TokenProvider;
	platform: PlatformAdapter;
	playback: PlaybackAdapter;
	analytics?: AnalyticsSink;
};

interface VideoSdk {
	initialize(options: VideoSdkOptions): Promise<void>;
	getContent(id: string): Promise<Content>;
	listLiveEvents(input?: LiveEventFilter): Promise<LiveEvent[]>;
	getHighlights(eventId: string): Promise<Highlight[]>;
	createPlayback(input: PlaybackIntent): Promise<PlaybackSession>;
	destroy(): Promise<void>;
}

The playback adapter is injected separately from device and navigation behavior. A third-party host integration can therefore stay small while choosing the playback implementation for its platform:

const sdk = createVideoSdk();
await sdk.initialize({ auth: hostTokenProvider, platform, playback });

const content = await sdk.getContent(contentId);
const session = await sdk.createPlayback({ contentId: content.id });

await playback.load(session);
await playback.play();

Initialization should validate capabilities, register necessary listeners, and establish readiness. Destruction should remove listeners, stop owned media work, release resources, and make repeated cleanup safe. Partial initialization also needs cleanup, especially on memory-limited televisions.

Public models should describe the video domain rather than mirror backend responses. That allows the backend and adapters to evolve without forcing every host to change.

Let the host provide authentication

The SDK needs credentials to request protected content, but it should not own the host’s login flow or token storage. Instead, accept a narrow token abstraction.

interface TokenProvider {
	getAccessToken(input: { forceRefresh: boolean }): Promise<string | null>;
}

The SDK can request a token and, after an authorization failure, request a refreshed token through the same host-provided interface. The host remains responsible for refresh-token storage and the actual refresh flow, whether authentication uses device linking, account selection, native storage, or a web session. The contract defines missing tokens, failed refresh, and account changes.

This boundary avoids duplicating identity UI and keeps credentials out of SDK-controlled persistence. It also lets the host coordinate authentication across the whole application instead of allowing an embedded surface to create a second account state.

Hide GraphQL behind domain methods

GraphQL can work well as an internal data layer for a video SDK. Typed operations can retrieve screen-relevant content, compose live-event and entitlement data, and select smaller response shapes for constrained platforms. Different artifacts may use different operations while returning the same public Content or PlaybackSession model.

Raw queries should not be the SDK contract. They couple hosts to schema names, pagination, error shapes, and backend evolution. Keep operations, caching, and response mapping behind domain methods.

The same principle applies to internal state. An SDK can use Redux, Zustand, Apollo Client, a native store, or a small state machine internally, but host applications should not have to install, configure, or share any of them. Export snapshots, events, or documented callbacks—not an internal store.

Treat playback as a replaceable capability

Playback is not one implementation across browsers, native mobile, and Smart TVs. Engines differ in source attachment, DRM integration, tracks, event timing, live behavior, teardown, and error reporting. Put those differences behind a playback port.

interface PlaybackAdapter {
	load(session: PlaybackSession): Promise<void>;
	play(): Promise<void>;
	pause(): Promise<void>;
	seekTo(seconds: number): Promise<void>;
	subscribe(listener: (event: PlaybackEvent) => void): () => void;
	destroy(): Promise<void>;
}

A browser adapter might wrap a media element and a JavaScript playback engine. Native artifacts can use platform media frameworks. Smart TV adapters may need different engines by device family or model year. The core should depend on normalized events such as ready, playing, stalled, ended, and failed, while adapters preserve diagnostic details for observability.

Avoid reducing every failure to “player error.” Normalize errors into stable categories—authentication, authorization, content unavailable, network, playback, unsupported capability, and internal failure—then attach safe diagnostic fields such as platform, adapter version, playback phase, and correlation identifier.

Make television navigation a boundary of its own

Focus is not a cosmetic detail on Smart TVs. The SDK must define where focus enters an optional UI surface, how directional input moves within it, what happens while items load or disappear, and where focus returns when the surface closes.

The host should remain the authority for top-level navigation. The SDK can own focus inside its boundary and report that Back was handled, but it should not silently replace the host’s route stack or terminate the application. A useful integration contract includes an initial focus target, a host callback for unhandled Back, and a restoration target captured before entry.

Remote input also needs protection against repeated key events. Playback actions, live-event selection, and modal confirmation should be idempotent or guarded while pending. Test focus after data refresh, application suspension, playback exit, errors, and modal dismissal—not only on a static happy-path screen.

Choose delivery models deliberately

A headless SDK provides maximum control and the smallest presentation commitment. Optional UI packages can add reviewed rails, cards, highlight lists, or playback controls without making them mandatory. A fully embeddable experience can own a complete discovery-to-playback flow when the host accepts stronger styling and navigation constraints.

Those models should build on the same domain contracts where practical, but they do not need one artifact. Web packages, React Native modules, Android libraries, iOS frameworks, and Smart TV bundles have different toolchains and release constraints. Publish separate artifacts with an explicit compatibility matrix. Sharing schemas, domain types, event names, and contract tests is often more valuable than maximizing shared source code.

Analytics and observability belong in the contract

The SDK should emit documented product and QoE events without assuming the host’s analytics vendor. A host-provided sink can receive normalized events for content impressions, live-event selection, playback intent, startup, stalls, recovery, completion, and failure.

Separate operational telemetry from business analytics even when they share identifiers. Define event names, timestamps, session identifiers, privacy rules, ordering, and duplicate prevention. The host must know what the SDK emits automatically.

Logs and errors should preserve enough context to connect a host report to adapter and backend evidence without including tokens or sensitive payloads. Observability is part of supportability, not an optional debugging add-on.

Version for applications that may not upgrade

Semantic versioning is necessary but insufficient. A major version can announce a breaking change; it cannot make an installed television application update. Backend behavior, GraphQL fields, playback authorization, and analytics ingestion may need to remain compatible with old SDK generations for years.

Maintain a support matrix across SDK version, platform version, device generation, backend contract, and playback adapter. Add new public fields compatibly, give deprecations a documented replacement and removal window, and avoid changing the meaning of an existing event or error code. When removal is unavoidable, coordinate backend capability detection and a realistic end-of-support policy.

Compatibility is easier when the SDK reports its version and capabilities during initialization. The platform can then choose supported response shapes or reject an obsolete combination clearly instead of failing deep in playback.

Test contracts and devices, not only functions

Unit tests should cover domain mapping, state transitions, error normalization, and lifecycle cleanup. Contract tests should verify that each artifact implements the same public behavior. Adapter tests should exercise platform APIs and player-event translation. Integration tests should run sample hosts through authentication, content retrieval, live events, highlights, playback, and teardown.

For Smart TVs, simulators and browser tests are only part of the evidence. Use real devices across supported model years to validate startup, memory, resume behavior, playback, remote repeat, Back handling, focus restoration, and long sessions. Add compatibility tests against supported backend and artifact versions, and keep known device exceptions visible in the support matrix.

Documentation is part of the SDK product

A technically sound SDK still fails if an external team cannot integrate or diagnose it. Ship an API reference, lifecycle guide, authentication contract, error catalog, analytics schema, support matrix, and release notes. Provide sample host applications covering cleanup, failed authentication, playback exit, and focus restoration.

Migration guides should explain behavioral changes and deprecated paths with before-and-after examples. A reference integration should run in continuous validation so documentation cannot drift indefinitely from the released contract.

Final takeaway

The architectural goal is not maximum abstraction. It is a boundary that remains understandable as backends, players, devices, and independently released host applications change. A small public API, headless domain core, replaceable adapters, explicit ownership, and real-device evidence make that boundary durable enough to be trusted.