Android advanced usage
Basic integration passes the playbackUrl returned by the SDK to Media3 or ExoPlayer. This page covers multiple playback sessions, authorization, and runtime controls.
Live and VOD integration policy
The SDK detects live/VOD automatically; both use the same quick integration. Player feedback is optional and helps P2P scheduling.
Live
Report the continuous playable buffer ahead of the current position to tune P2P prefetch. For example, position 60 s with buffer ending at 70 s means 10,000 ms. Live start position and target latency remain player settings.
session.updateBufferedDurationMs(bufferedDurationMs)
VOD
Also report playback position so the SDK can prefer already-connected peers with nearby progress and the requested segment. Update position immediately after a completed seek.
session.updateCurrentPositionMs(positionMs)
session.updateBufferedDurationMs(bufferedDurationMs)
Use non-negative milliseconds; do not count across buffer gaps. Send valid player state about once per second. Stop and drain updates before closing the session.
Complete player feedback example
Use io.meshstream.sdk.PlaybackSession and Media3 Player. isLive is application playback state.
fun syncPlaybackState(session: PlaybackSession, player: Player, isLive: Boolean) {
val positionMs = player.currentPosition
val bufferedEndMs = player.bufferedPosition
if (positionMs >= 0L && bufferedEndMs >= 0L) {
val bufferedMs = (bufferedEndMs - positionMs).coerceAtLeast(0L)
session.updateBufferedDurationMs(bufferedMs)
}
if (!isLive && positionMs >= 0L) {
session.updateCurrentPositionMs(positionMs)
}
}
Direct MP4 / MP3 playback
MP4 and MP3 are direct VOD files: the player loads a file URL rather than an HLS .m3u8 or DASH .mpd manifest. Use a stable contentId for the same video or audio asset. Set mimeType explicitly when the URL has no file extension.
val session = client.createPlaybackSession(
"https://cdn.example.com/video/trailer.mp4",
PlaybackOptions(
contentId = "trailer-2026",
mimeType = "mp4",
),
)
player.setMediaItem(MediaItem.fromUri(session.playbackUrl))
player.prepare()
player.play()
For MP3, change the URL and mimeType to .mp3 and "mp3", and use a Media3/ExoPlayer instance that supports audio playback. The media origin must support a single Range request and return a valid Content-Range. If it does not, the file still plays normally but acceleration is not enabled.
Per-playback configuration
One P2pClient can manage multiple independent sessions. Pass a stable contentId for dynamically signed URLs:
val session = client.createPlaybackSession(
signedUrl,
PlaybackOptions(
contentId = "movie-2026-001",
mimeType = "m3u8",
originHeaders = mapOf("Authorization" to "Bearer $originToken"),
),
)
player.setMediaItem(MediaItem.fromUri(session.playbackUrl))
player.prepare()
player.play()
originHeaders are used only when requesting your media origin and are never sent to other viewers. Close the old session before creating a new one when content changes.
Bitrate and quality selection remain managed by Media3/ExoPlayer; application code does not need to pass a representation identifier.
Preload and content switching
Explicit preload is an optional HLS/DASH warmup: it fetches the manifest and first media segment in the background, not the entire video. It may also start Tracker/Peer connections early, but a successful queue operation does not guarantee that peers or media are ready. An unplayed candidate session does not upload media to peers. MP4/MP3 direct files do not use this explicit preload path.
Keep the candidate session and reuse its playback URL when selected. Before closing a session, stop and drain its player-state updates and remove its event subscriptions; do not continue callbacks against a closed session. Preload does not prepare a decoder or render a frame.
The playback manager owns nullable currentSession and nextSession fields of type PlaybackSession?. Each player has its own sessions. Run these steps on the player thread.
Prepare candidate content
val candidate = client.createPlaybackSession(
nextUrl,
PlaybackOptions(contentId = nextContentId, mimeType = "m3u8"),
)
val previous = nextSession
nextSession = candidate
previous?.close()
val queued = candidate.preload()
queued == true means that warmup was queued, not completed. A false result does not by itself mean the playback session is unusable; you can still pass its URL to the player.
Switch to the candidate
Stop updates for the old session first, then stop and detach its media, close it, and promote the candidate:
val candidate = nextSession
if (candidate != null) {
player.stop()
player.clearMediaItems()
currentSession?.close()
currentSession = candidate
nextSession = null
player.setMediaItem(MediaItem.fromUri(candidate.playbackUrl))
player.prepare()
player.play()
}
Cancel candidate content
Close only the discarded candidate; the current session keeps playing:
nextSession?.close()
nextSession = null
Runtime controls
// Temporarily disable P2P for the current session; keep its playback URL.
session.setP2pEnabled(false)
session.setP2pEnabled(true)
Events and monitoring
client.addEventListener(object : P2pEventListener {
override fun onP2pReady(sessionId: Long) = log("P2P ready: $sessionId")
override fun onP2pUnavailable(sessionId: Long, reason: P2pUnavailableReason) =
log("P2P unavailable: $sessionId, $reason")
override fun onCdnFallback(sessionId: Long, bytes: Long) =
log("CDN fallback: $bytes bytes")
override fun onPeerCountChanged(sessionId: Long, peerCount: Int) =
updatePeerCount(sessionId, peerCount)
})
val stats = session.statistics()
val offload = stats.downloadOffloadRatio
onP2pUnavailable only means acceleration is temporarily unavailable. Do not stop the player because of this state.
Verify P2P delivery
- Start with one device: verify playback, seeking for VOD, and normal playback with P2P disabled.
- Run two devices on the same content with a consistent
contentId. For VOD, use overlapping playback positions; for live, play the same channel. Keep the media bytes and timeline consistent; do not reuse an ID for different assets or personalized manifests. - Check that player updates use milliseconds and continue after the manifest loads. At position 60 seconds with continuous buffer ending at 70 seconds, the buffer value is 10,000 ms; refresh VOD position after a completed seek.
- Observe changes in P2P downloaded bytes and offload ratio over a playback interval. A connected peer alone does not prove that media was downloaded through P2P. Results depend on overlapping available segments, network conditions, and platform policy.
- Disable P2P and verify that playback continues through CDN. Then stop state updates and event subscriptions before closing the session, and verify that switching content creates no calls against the closed session.
Network and platform policy
The SDK does not accept a client-selected region. The service resolves the SDK Key's tenant home region (homeRegion) and derives access-side locality (edgeRegion) from trusted request-network signals. This is not a data-residency setting. p2pEnabled, connection limits, and timeouts are local ceilings, while server policy has higher priority. In multi-center strict mode, 421 means the request reached the wrong center and 503 HOME_REGION_UNRESOLVED means the tenant's home region is unavailable. The SDK does not switch centers itself.
P2pConfig(
key = "your_sdk_key",
p2pEnabled = true,
maxPeerConnections = 12,
maxUploadPeers = 4,
maxDownloadPeers = 5,
requestTimeoutMs = 8_000,
)
Lifecycle and fallback
- Call
session.close()when playback ends, content changes, or the screen is destroyed. - No activation call is required after assigning the playback URL. A candidate session may use
preload()first; real transfer is reported automatically and peer upload waits for the player's first media request. - Call
P2pSdk.shutdown()when the application no longer uses the SDK. - When acceleration initialization or media delivery is temporarily unavailable, keep using the session
playbackUrl; do not replace the player URL solely because acceleration state changes. - Do not write the Key, origin token, or
originHeadersto logs.
Capability boundary
The SDK owns playback sessions and acceleration handling. Media3/ExoPlayer owns manifest parsing, ABR, decoding, rendering, and player buffering. The SDK does not replace the player or promise continuous background sharing.