Platform Player
Platform Player
This page covers additional player features and configurations for the Quickplay React Native Player SDK.
Subtitles and Closed Captions
Subtitles
To enable subtitles, set the TrackVariantTypeValue to 'TEXT':
// Retrieve the available subtitle tracks
let subtitleTracks = await player.getAvailableTrackVariants('TEXT');
// Send the selected subtitle track to the player
let selectedTrack = subtitleTracks[selectedIndex];
await player.setPreferredTrackVariant('TEXT', selectedTrack);
// If the user disables the subtitles
await player.setPreferredTrackVariant('TEXT', undefined);
// Retrieve the selected track for given variantType
let selectedSubtitleTrack = await player.getSelectedTrack('TEXT');
Closed Captions
To enable closed captions, set the TrackVariantTypeValue to 'CLOSED CAPTION':
// Retrieve the available closed caption tracks
let closedCaptions = await player.getAvailableTrackVariants('CLOSED CAPTION');
if (closedCaptions.length > 0) {
// show CC button
}
// Select a closed caption track
let selectedCaption = closedCaptions[0];
await player.setPreferredTrackVariant('CLOSED CAPTION', selectedCaption);
// If the user disables the closed captions
await player.setPreferredTrackVariant('CLOSED CAPTION', undefined);
// Retrieve the selected track
let selectedSubtitleTrack = await player.getSelectedTrack('CLOSED CAPTION');
Note: If the device's accessibility settings are enabled to automatically show subtitles/CC, the device will pre-select the track. When playback starts, check which TEXT and CLOSED CAPTION tracks are selected and update the UI accordingly.
Subtitle Positioning
Adjust the subtitle position between 0 (top of screen) and 100 (bottom of screen):
await player.setSubtitlePosition(subtitlePosition: SubtitlePosition);
Note: Typically used to raise the subtitle position above controls when they become visible.
Preferred Subtitle / Audio Language
To select subtitle or audio language at playback start, use preferredSubtitleLanguage and preferredAudioLanguage in PlayerConfig. Supports ISO 639-1 (two-letter) and ISO 639-2/3 (three-letter) language codes:
let playerConfig: PlayerConfig = {
preferredSubtitleLanguage: 'fr',
preferredAudioLanguage: 'en',
};
let player = await createPlayer(playerConfig);
Track Availability Callback
The trackAvailabilityChanged listener notifies applications when new tracks become available during playback:
async onTrackAvailabilityChanged(): Promise<void> {}
SMPTE-TT Subtitles
SMPTE-TT is a bitmap/image-based subtitle format embedded in the stream as XML via ID3 timed metadata, or delivered as a sidecar XML file.
Enable SMPTE-TT Subtitle Rendering
Note: This is a resource-intensive operation — enable it only for channels that support SMPTE-TT subtitles.
XML via ID3 Event
const playerPreference: PlayerPreference = {
enableAdditionalSubtitle: enableSubtitleMediaTrackType ? ['SMPTE-TT'] : undefined,
};
player.setPlayerPreference(playerPreference);
XML via SideCar Load
const textTracks = [
{
endpoint: 'https://example.com/subtitles_eng.ttml',
languageCode: 'en',
},
{
endpoint: 'https://example.com/subtitles_chi.ttml',
languageCode: 'chi',
},
];
let playerConfig: PlayerConfig = {
smpteTimedTextTracks: textTracks,
};
let player = await createPlayer(playerConfig);
Subtitle Selection Behavior
| Preference | Available Tracks | Displayed |
|---|---|---|
'SMPTE_ID3' | 'SMPTE_ID3' | 'SMPTE_ID3' |
'SMPTE_SIDECAR' | 'SMPTE_SIDECAR' | 'SMPTE_SIDECAR' |
'SMPTE_SIDECAR, SMPTE_ID3' | 'SMPTE_SIDECAR, SMPTE_ID3' | 'SMPTE_ID3' |
undefined | Native supported | Native supported |
undefined / No preference | 'SMPTE_ID3' | No tracks display |
Limitations
- Not supported on tvOS.
- Not supported during AirPlay or Picture-in-Picture playback.
- SMPTE-TT tracks become available only after playback starts.
- Multi-language SMPTE-TT subtitles are not supported on iOS 16 and below (ID3 timed metadata not reported).
Audio Description Support
Audio description tracks provide spoken narration of visual elements for accessibility.
Detecting Audio Description Tracks
const audioTracks = await player.getAvailableTrackVariants('AUDIO');
audioTracks.map((track: TrackVariantInfo, index: number) => {
if (track.role?.includes('DESCRIBES_VIDEO')) {
console.log(`Audio description track found: ${track.displayName}`);
}
});
Audio Track Role Types
| Role | Description |
|---|---|
MAIN | Primary audio track |
DESCRIBES_VIDEO | Audio description track for accessibility |
NONE | No specific role assigned |
Selecting Audio Description Tracks
const audioTracks = await player.getAvailableTrackVariants('AUDIO');
const audioDescriptionTrack = audioTracks.find(track =>
track.role?.includes('DESCRIBES_VIDEO')
);
if (audioDescriptionTrack) {
await player.selectTrackVariant(audioDescriptionTrack);
}
Low Latency
iOS
Set preferredInitialForwardBufferDuration and preferredForwardBufferDurationAfterRebuffer for minimal delay:
let playerPreference: PlayerPreference = {
preferredInitialForwardBufferDuration: 1,
preferredForwardBufferDurationAfterRebuffer: 1,
};
player.setPlayerPreference(playerPreference);
Android
let playbackProperties: PlaybackProperties = {
preferredMinBufferDurationMs: 3000,
preferredMaxBufferDurationMs: 15000,
preferredInitialBufferDurationMs: 1500,
preferredBufferDurationAfterRebufferMs: 3000,
preferredTargetLiveOffsetIncrementOnRebufferMs: 0,
};
let playerConfig: PlayerConfig = {
playbackProperties: playbackProperties,
};
let player = await createPlayer(playerConfig);
Dolby Support
By default on Android, playback uses Dolby Atmos if both the device and content support it.
To switch between audio codecs:
// Switch to Dolby Digital Plus
player.setPreferredMimeType('AUDIO', ['audio/eac3']);
// Switch to stereo
player.setPreferredMimeType('AUDIO', ['audio/mp4']);
// Switch to Dolby Atmos
player.setPreferredMimeType('AUDIO', ['audio/eac3-joc']);
Security
Device Security Status
import { deviceInformation } from '@quickplay/rn-qp-nxg-player';
const { isSecure, failureReason } = await deviceInformation.securityStatus();
| Property | Type | Description |
|---|---|---|
isSecure | boolean | Whether the device is secure |
failureReason | string? | Reason why the device is insecure (if applicable) |
Protect Playback from Insecure Devices
let playerConfig: PlayerConfig = {
securityConfig: {
allowPlaybackOnInsecureDevice: false,
},
};
let player = await createPlayer(playerConfig);
Enable Additional Security Checks
let playerConfig: PlayerConfig = {
securityConfig: {
allowPlaybackOnInsecureDevice: false,
additionalSecurityChecksAndroid: [
SecurityChecksAndroid.CHECK_FOR_ROOT_APPS,
SecurityChecksAndroid.CHECK_FOR_ROOT_CLOCK_APPS,
],
additionalSecurityChecksIOS: [
SecurityChecksIOS.PROXY_CHECK,
SecurityChecksIOS.INTEGRITY_CHECK,
],
bundleIdIOS: 'com.org.app', // Required for INTEGRITY_CHECK
},
};
Android Security Checks
| Constant | Value | Description |
|---|---|---|
CHECK_FOR_ROOT_APPS | 1 | Checks for root management apps |
CHECK_FOR_DANGEROUS_APPS | 2 | Checks for dangerous apps |
CHECK_FOR_ROOT_CLOCK_APPS | 3 | Checks for root clock apps |
CHECK_FOR_WRITABLE_PATHS | 4 | Checks if restricted paths are writable |
CHECK_FOR_MAGISK_BINARY | 5 | Checks for Magisk binary |
CHECK_FOR_DANGEROUS_PROPS | 6 | Checks for dangerous system properties |
CHECK_FOR_ROOT_NATIVE | 7 | Native root checks |
iOS Security Checks
| Constant | Value | Description |
|---|---|---|
DEBUGGER_CHECK | 1 | Detects if the app is being debugged |
EMULATOR_CHECK | 2 | Detects if running on an emulator |
PROXY_CHECK | 3 | Detects HTTP proxy in iOS Settings |
INTEGRITY_CHECK | 4 | Detects if the app has been tampered with |
Advanced Security Check (iOS only)
import { deviceInformation, SecurityChecksIOS } from '@quickplay/rn-qp-nxg-player';
const additionalChecks = [
SecurityChecksIOS.DEBUGGER_CHECK,
SecurityChecksIOS.EMULATOR_CHECK,
];
const result = await deviceInformation.securityStatusWithChecks(additionalChecks);
console.log('Is Secure:', result.isSecure);
console.log('Failure Reason:', result.failureReason);
License Management
Support for License Refresh
License Refresh is needed when configured with CSAI. The player auto-detects LA_URL expiry and renews it before acquiring the license.
Note: Works on Android only; integration does not need to change for iOS.
- Initialize
contentAuthorizerwithlicenseRefreshEndPointURL:
const contentAuthConfig = {
licenseRefreshEndPointURL: 'https://license-refresh.api.<tenant>.firstlight.ai',
};
await contentAuthorizer.initWithConfig(contentAuthConfig);
- Pass mandatory variables in
playerConfig:
let contentAuthToken = await contentAuthorizer.authorizePlayback(platformAsset);
let playerConfig: PlayerConfig = {
contentID: string,
drmLicenseURL: contentAuthToken.licenseURL,
drmLicenseRefreshToken: contentAuthToken.licenseRefreshToken,
};
let player = await createPlayer(playerConfig);
Playback Customization
Optimizing OAuth Token Refresh
By default, the SDK no longer disposes the auth token when the app goes to background. To opt back into the old behavior:
import { platformAuthorizer } from '@quickplay/rn-qp-nxg-player';
let disposeAuthTokenOnBackground = true; // Android only
await platformAuthorizer.initWithConfig(config, disposeAuthTokenOnBackground);
Playback Auto Recovery
preferredMaxPlaybackRecoveryRetryCount configures the maximum recovery attempts (default: 3).
Auto Recovery Callbacks
onPlaybackRecoveryStatus(playbackRecoveryStatus: PlaybackRecoveryStatus): void {
const {
error,
recoveryStatus,
currentRetryAttempt,
totalRetryAttempts,
recoveryAttemptsDurationMs,
reportingEventPayload, // Must be reported for analytics
} = playbackRecoveryStatus;
}
PlaybackRecoveryStatus Properties
| Property | Type | Description |
|---|---|---|
error | PlatformError | Error encountered during recovery |
recoveryStatus | RecoveryStatus | Success or Failure |
currentRetryAttempt | number | Number of recovery attempts made |
totalRetryAttempts | number | Total configured retry attempts |
recoveryAttemptsDurationMs | number[] | Duration (ms) for each attempt |
reportingEventPayload | string | JSON payload for analytics reporting |
Setting Recoverable Errors
const recoverablePlayerErrors: Array<string> = [
'400208', // MEDIA_PLAYBACK_FAILURE_ERROR
'40020D', // INVALID_LIVE_MEDIA_SEGMENT_ERROR
'40020E', // NETWORK_CONNECTION_FAILURE_ERROR
'40020F', // NETWORK_END_POINT_UNREACHABLE_ERROR
];
let playerConfig: PlayerConfig = {
playbackProperties: { recoverablePlayerErrors },
};
let player = await createPlayer(playerConfig);
Setting Initial Network Bandwidth
let playbackProperty = {
preferredInitialNetworkBandwidth: number,
preferredMinDurationForQualityIncreaseMs: number,
preferredMaxDurationForQualityDecreaseMs: number,
preferredMinDurationToRetainAfterDiscardMs: number,
preferredBandwidthFraction: number,
preferredMaxPlaybackRecoveryRetryCount: number,
};
let playerConfig: PlayerConfig = {
playbackProperties: playbackProperty,
};
let player = await createPlayer(playerConfig);
Note: These configurations work only on Android; integration does not need to change for iOS.
Network Stack
Set a preferred NetworkStack for the player (Android only):
let playerConfig: PlayerConfig = {
networkStack: 'CRONET_GPS',
};
let player = await createPlayer(playerConfig);
| Enum Value | Protocols | APK size impact | Notes |
|---|---|---|---|
BUILT_IN (default) | HTTP (varies by device) | None | Implementation varies by device |
OK_HTTP | HTTP, HTTP/2 | Small (<1 MB) | Requires Kotlin runtime |
CRONET_GPS | HTTP, HTTP/2, HTTP/3 (QUIC) | Small (<100 KB) | Requires Google Play Services |
Required dependencies:
// OK_HTTP
implementation 'com.google.android.exoplayer:extension-okhttp:2.X.X'
// CRONET_GPS
implementation 'com.google.android.exoplayer:extension-cronet:2.X.X'
Media Metadata Support
const mediaMetadataRecord: Record<AndroidMediaMetadataKeys, string> = {
TITLE: '<Title of the Asset>',
DISPLAY_TITLE: '<Display Title of the Asset>',
// ...
};
let playerConfig: PlayerConfig = {
mediaMetadata: mediaMetadataRecord,
};
let player = await createPlayer(playerConfig);
Thumbnail Extended Configuration
Note: Android only.
| Parameter | Type | Default | Description |
|---|---|---|---|
isPrefetchRequired | Boolean | true | Pre-loads sprites and caches thumbnails |
shouldDeleteFromDiskOnExit | Boolean | true | Deletes cached thumbnails when playback exits |
maxConcurrentThumbnailDownloads | Integer | Available processors | Max concurrent thumbnail downloads |
const thumbnailExtendedConfig = {
isPrefetchRequired: true,
shouldDeleteFromDiskOnExit: true,
maxConcurrentThumbnailDownloads: 2,
};
let playerConfig: PlayerConfig = {
thumbnailExtendedConfig,
};
let player = await createPlayer(playerConfig);
Device Information & Capabilities
Audio / Video Codecs
Note: Android only — returns
undefinedon iOS.
let audioCodecs = await deviceInformation.supportedAudioCodecs();
let videoCodecs = await deviceInformation.supportedVideoCodecs();
Device Capability
let deviceCapability = await deviceInformation.getDeviceCapability();
// 'HIGH_END' | 'MID_RANGE' | 'LOW_END'
Maximum Secure Decoders
let maxDecoderInstances = await deviceInformation.getMaxSecuredDecoderInstances();
let hevcCodecName = maxDecoderInstances['video/hevc'].name;
let avcCodecName = maxDecoderInstances['video/avc'].name;
CPU Core Count
const cpuCoreCount = await deviceInformation.getTotalCPUCoreCount();
Supported GPU Family (iOS)
const supportedGPUFamilies = await deviceInformation.getAllSupportedGPUFamily();
Total RAM
const totalRam = await deviceInformation.getTotalRamInGB();
Universal Search (iOS / tvOS)
Manual Reporting Using MPNowPlayingInfoCenter
-
Remote commands are registered automatically when the player is created and unregistered when it is deinitialized.
-
Set constant metadata after player creation:
const playingInfo: PlayingInfo = {
title: 'Interstellar',
contentIdentifier: '550e8400-e29b-41d4-a716-446655440000',
};
setNowPlayingInfo(playingInfo);
- Enable automatic position/rate updates:
const playerPreference: PlayerPreference = {
assignRemoteActions: true,
};
player.setPlayerPreference(playerPreference);
tvOS Compatibility
Some older Apple TV HD models do not recognize metadata from MPNowPlayingSession. Use the shared MPNowPlayingInfoCenter instead:
const playerPreference: PlayerPreference = {
useSharedNowPlayingInfoCenter: true,
};
player.setPlayerPreference(playerPreference);
QPShorts Integration
import { ShortsPlayer, Video } from '@quickplay/rn-qp-nxg-player';
const videos: Video[] = [
{
id: '8f1d0955-4423-4856-bc59-8a378abd7db9',
title: '',
description: 'Sample short',
contentType: 'video',
contentUrls: [
'https://example.com/video_1024.MP4',
'https://example.com/video_512.MP4',
],
likes: 1,
shares: 4,
views: 170,
comments: 23,
user: {
id: '16fabb52-3b32-4eb4-ad30-c16c6b8c1c08',
userName: 'sampleUser',
fullName: 'Sample User',
followers: 0,
},
},
];
const shorts = await ShortsPlayer.play(videos);
Syndication SSO
- Initialize
contentAuthorizerwithsyndicationSSOEndPointURL:
import { ContentAuthConfig, contentAuthorizer } from '@quickplay/rn-qp-nxg-player';
const contentAuthConfig = {
syndicationSSOEndPointURL: 'https://sso.example.com',
};
await contentAuthorizer.initWithConfig(contentAuthConfig);
- Call
authorizePartnerPlaybackwith aPartnerPlatformAsset(contentId,contentTypeId,catalogTypeare mandatory):
let partnerAuthorizationData = await contentAuthorizer.authorizePartnerPlayback(platformAsset);
The response contains:
{
contentId: string; // same content ID as request
deeplinkUrl: string; // playback deep link
deeplinkToken: string; // authentication token
}