API & Config
P2P Configuration
A P2pConfig instance can be obtained via its builder. The parameters below show the default values:
val config = P2pConfig.Builder()
.logEnabled(false) // Enable or disable log
.logLevel(LogLevel.WARN) // Print log level
.trackerZone(TrackerZone.Europe) // The country enum for the tracker server address(Europe, HongKong, USA).
.downloadTimeout(15000, TimeUnit.MILLISECONDS) // TS file download timeout by HTTP
.localPortHls(0) // The port for local http server of HLS(Use random port by default)
.localPortDash(0) // The port for local http server of DASH(Use random port by default)
.diskCacheLimit(2000*1024*1024) // The max size of binary data that can be stored in the disk cache for VOD(Set to 0 will disable disk cache)
.memoryCacheCountLimit(15) // The max count of ts files that can be stored in the memory cache
.p2pEnabled(true) // Enable or disable p2p engine
.withTag(null) // Add a custom label to every different user session, which in turn will provide you the ability to have more meaningful analysis of the data gathered
.webRTCConfig(null) // Providing options to configure WebRTC connections
.maxPeerConnections(25) // Max peer connections at the same time
.startFromSegmentOffset(3) // The segment offset that start to connect to tracker server
.useHttpRange(true) // Use HTTP ranges requests where it is possible. Allows to continue (and not start over) aborted P2P downloads over HTTP
.useStrictHlsSegmentId(false) // Use segment url based segment id instead of sequence number based one
.httpHeadersForHls(null) // Set HTTP Headers while requesting ts and m3u8.
.httpHeadersForDash(null) // Set HTTP Headers while requesting Dash files.
.sharePlaylist(false) // Allow the P2P transmission of m3u8 file.
.prefetchOnly(false) // Only use prefetch strategy in p2p downloading(Only for HLS).
.logPersistent(false) // Save logs to the file({Environment.getExternalStorageDirectory()}/logger/).
.insertTimeOffsetTag(null) // Insert "#ext-x-start: time-offset = [timeOffset]" in m3u8 file to force the player to start loading from the first ts of playlist, where [timeOffset] is the offset in seconds to start playing the video, only works on live mode
.p2pProtocolVersion(P2pProtocolVersion.V8) // The version of P2P protocol, only have the same protocol version as another platform can both interconnect with each other
.dashMediaFiles(
arrayListOf("mp4", "fmp4", "webm", "m4s", "m4v")) // The supported media file type of DASH.
.build()
P2pEngine
Instantiate the P2pEngine singleton:
- kotlin
- java
P2pEngine.init(context, token, config)
P2pEngine.init(context, token, config);
Parameter description:
| param | type | required | description |
|---|---|---|---|
| context | Context | Yes | The Application Context instance is recommended. |
| token | String | Yes | Token assigned by CDNBye. |
| config | P2pConfig | No | Custom configuration. |
Switch Stream URL
When switching to a new stream URL, pass the URL through the P2pEngine instance before handing it to the player:
- kotlin
- java
val parsedUrl = P2pEngine.instance.parseStreamUrl(url)
String parsedUrl = P2pEngine.getInstance().parseStreamUrl(url);
P2pEngine API
P2pEngine.version
Current SDK version.
P2pEngine.instance
Returns the singleton instance of P2pEngine.
engine.parseStreamUrl(url: String)
Converts the original playback URL (m3u8/mpd) into a local proxy server address.
engine.parseStreamUrl(url: String, videoId: String)
Passes a video ID, used together with the original playback URL to generate the channel ID.
engine.parseStreamUrl(url: String, videoId: String, mimeType: MimeType)
If your URI doesn't end with .m3u8 or .mpd, pass MimeType.APPLICATION_M3U8 or MimeType.APPLICATION_MPD as the third parameter of parseStreamUrl to explicitly specify the content type.
engine.isConnected
Checks whether the SDK is connected to the CDNBye backend.
engine.stopP2p()
Once playback finishes, stop the P2P streaming session you created earlier. Calling this method completes any ongoing tasks and releases the associated resources.
engine.restartP2p()
Resumes P2P streaming after it has been stopped.
engine.peerId
Returns the peer ID of this engine.
engine.setHttpHeadersForHls(headers: Map<String, String>?)
Dynamically sets the HTTP headers used when requesting ts and m3u8 files.
engine.setHttpHeadersForDash(headers: Map<String, String>?)
Dynamically sets the HTTP headers used when requesting DASH files.
engine.notifyPlaybackStalled()
Notifies the SDK that playback has stalled.
engine.disableP2p()
Disables P2P dynamically at runtime; the change takes effect starting with the next media file played.
engine.enableP2p()
Enables P2P dynamically at runtime; the change takes effect starting with the next media file played.
engine.shutdown()
Stops P2P streaming and shuts down the proxy server.
P2P Statistics
Register a P2pStatisticsListener observer to monitor download statistics:
- kotlin
- java
engine.addP2pStatisticsListener(object : P2pStatisticsListener {
override fun onHttpDownloaded(value: Int) {
}
override fun onP2pDownloaded(value: Int, speed: Int) {
}
override fun onP2pUploaded(value: Int, speed: Int) {
}
override fun onPeers(peers: List<String>) {
}
override fun onServerConnected(connected: Boolean) {
}
})
engine.addP2pStatisticsListener(new P2pStatisticsListener() {
@Override
public void onHttpDownloaded(int value) {
}
@Override
public void onP2pDownloaded(int value, int speed) {
}
@Override
public void onP2pUploaded(int value, int speed) {
}
@Override
public void onPeers(@NonNull List<String> peers) {
}
@Override
public void onServerConnected(boolean connected) {
}
});
Download and upload volumes are measured in KB. Download speed is measured in KB/s.
Advanced Usage
Callback Player Stats
For live streaming, to improve performance, we recommend informing the P2P engine of the duration between the current playback position and the end of the buffered interval. To do so, use the setPlayerInteractor callback.
- kotlin
- java
P2pEngine.instance?.setPlayerInteractor(object : PlayerInteractor() {
override fun onBufferedDuration(): Long {
return if (player != null) {
// Exoplayer in milliseconds
player!!.bufferedPosition - player!!.currentPosition
} else {
-1
}
}
})
P2pEngine.getInstance().setPlayerInteractor(new PlayerInteractor() {
public long onBufferedDuration() {
// Exoplayer in milliseconds
if (play != null) {
return player.getBufferedPosition() - player.getCurrentPosition();
}
return -1;
}
});
For VOD, video duration can be significant. Matching peers with similar playback positions helps improve P2P performance, so we recommend informing the P2P engine of the player's current playback time:
- kotlin
- java
P2pEngine.instance?.setPlayerInteractor(object : PlayerInteractor() {
override fun onCurrentPosition(): Long {
// Exoplayer in milliseconds
return player?.currentPosition ?: -1
}
})
P2pEngine.getInstance().setPlayerInteractor(new PlayerInteractor() {
public long onCurrentPosition() {
// Exoplayer in milliseconds
if (play != null) {
return player.getCurrentPosition();
}
return -1;
}
});
Dynamic Url Support
The channelId is an identifier used by our backend to match peers watching the same content. It's an optional parameter — by default, videoId is generated from the content URL by stripping the protocol and any query parameters. However, if a unique URL is generated for each viewer (for example, when a security token is embedded in the URL path), players will appear to be watching different content, which prevents the P2P plugin from working efficiently. In this case, an explicit videoId is required.
- kotlin
- java
val videoId = extractVideoIdFromUrl(urlString) // extractVideoIdFromUrl is a function defined by yourself, you just need to extract video id from url
val parsedUrl = P2pEngine.instance?.parseStreamUrl(urlString, videoId)
String videoId = extractVideoIdFromUrl(urlString); // extractVideoIdFromUrl is a function defined by yourself, you just need to extract video id from url
String parsedUrl = P2pEngine.getInstance().parseStreamUrl(urlString, videoId);
To interconnect with another platform, ensure both sides use the same token and channelId.
StrictSegmentId Mode
You can use a URL-based segment ID instead of a sequence-number-based one:
- kotlin
- java
P2pEngine.instance?.setHlsSegmentIdGenerator(StrictHlsSegmentIdGenerator())
P2pEngine.getInstance().setHlsSegmentIdGenerator(new StrictHlsSegmentIdGenerator());
Setup HTTP headers
Some HTTP requests require additional header information, such as User-Agent, for anti-leech protection or analytics purposes. This can be configured via setHttpHeaders :
- kotlin
- java
val headers = mapOf("User-Agent" to "XXX")
P2pEngine.instance?.setHttpHeadersForHls(headers)
P2pEngine.instance?.setHttpHeadersForDash(headers)
Map headers = new HashMap();
headers.put("User-Agent", "XXX");
engine.setHttpHeadersForHls(headers);
engine.setHttpHeadersForDash(headers);
Specify the Preferred Point in the Video to Start Playback.
The SDK can insert the EXT-X-START tag to start a live stream from a specific point in the playlist. Note that this may introduce additional stream delay:
- kotlin
- java
val config = P2pConfig.Builder()
.insertTimeOffsetTag(0.0)
.build()
P2pConfig config = new P2pConfig.Builder()
.insertTimeOffsetTag(0.0)
.build();
Report Player Rebuffering
You can report player rebuffering events to the SDK, then view the average rebuffer ratio in the SwarmCloud dashboard:
- kotlin
- java
// Take exoplayer as example
player?.addListener(object : Player.Listener {
var isDetecting = false
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
if (playbackState == 2) { // STATE_BUFFERING
if (isDetecting) return
isDetecting = true
Timer().schedule(object : TimerTask() {
override fun run() {
runOnUiThread {
isDetecting = false
if (!player!!.isPlaying) {
P2pEngine.instance!!.notifyPlaybackStalled()
}
}
}
}, 8000)
}
}
})
// Take exoplayer as example
player.addListener(new Player.Listener() {
Boolean isDetecting = false;
@Override
public void onPlaybackStateChanged(int playbackState) {
if (playbackState == 2) { // STATE_BUFFERING
if (isDetecting) return;
isDetecting = true;
new Timer().schedule(new TimerTask() {
@Override
public void run() {
runOnUiThread(new Runnable() {
@Override
public void run() {
isDetecting = false;
if (!player.isPlaying()) {
P2pEngine.getInstance().notifyPlaybackStalled();
}
}
});
}
}, 8000);
}
}
});
Intercept m3u8/mpd
The SDK parses the contents of m3u8/mpd files after they are downloaded. If you use encrypted m3u8/mpd files, use the interceptor to intercept them and return the standard, decrypted file:
- kotlin
- java
P2pEngine.instance?.setHlsInterceptor(object : HlsInterceptor() {
override fun interceptPlaylist(text: ByteArray, url: String): ByteArray {
return handlePlaylist(text, url);
}
})
P2pEngine.instance?.setDashInterceptor(object : DashInterceptor() {
override fun interceptPlaylist(text: ByteArray, url: String): ByteArray {
return handlePlaylist(text, url)
}
})
P2pEngine.getInstance().setHlsInterceptor(new HlsInterceptor() {
@Override
public byte[] interceptPlaylist(byte[] text, String url) {
return handlePlaylist(text, url);
}
});
P2pEngine.getInstance().setDashInterceptor(new DashInterceptor() {
@Override
public byte[] interceptPlaylist(byte[] text, String url) {
return handlePlaylist(text, url);
}
});
Support Media Files without Suffixes
Some media segment files may not end with ".ts" or any other recognizable suffix. You can use the following hook function to determine whether such a URL points to a media file:
- kotlin
- java
P2pEngine.instance?.setHlsInterceptor(object : HlsInterceptor() {
override fun isMediaSegment(url: String): Boolean {
return true
}
})
P2pEngine.getInstance().setHlsInterceptor(new HlsInterceptor() {
@Override
public boolean isMediaSegment(@NonNull String url) {
return true;
}
});
Pass Your Customized OkHttpClient
- kotlin
- java
val httpClient = OkHttpClient.Builder()
.addInterceptor(YourInterceptor())
.build()
val config = P2pConfig.Builder().okHttpClient(httpClient).build()
OkHttpClient httpClient = new OkHttpClient.Builder()
.addInterceptor(new YourInterceptor())
.build();
P2pConfig config = new P2pConfig.Builder()
.okHttpClient(httpClient)
.build();
Bypass User-specific Segments
Sometimes certain ts segments should not be shared — for example, user-specific segments generated by SSAI (Server-Side Ad Insertion). In this case, use the segmentBypass function to exclude them from sharing:
- kotlin
- java
P2pEngine.instance?.setHlsInterceptor(object : HlsInterceptor() {
override fun shouldBypassSegment(url: String): Boolean {
return isSSAISegment(url)
}
})
P2pEngine.getInstance().setHlsInterceptor(new HlsInterceptor() {
@Override
public boolean shouldBypassSegment(@NonNull String url) {
return isSSAISegment(url);
}
});
Listen to SDK Exception
The SDK may throw exceptions due to network issues, server errors, algorithm bugs, or other causes. You can listen for these exceptions using registerExceptionListener :
- kotlin
- java
P2pEngine.instance?.registerExceptionListener(object : EngineExceptionListener {
override fun onTrackerException(e: EngineException) {
// Tracker Exception
}
override fun onSignalException(e: EngineException) {
// Signal Server Exception
}
override fun onSchedulerException(e: EngineException) {
// Scheduler Exception
}
override fun onOtherException(e: EngineException) {
// Other Exception
}
})
P2pEngine.getInstance().registerExceptionListener(new EngineExceptionListener() {
@Override
public void onTrackerException(EngineException e) {
// Tracker Exception
}
@Override
public void onSignalException(EngineException e) {
// Signal Server Exception
}
@Override
public void onSchedulerException(EngineException e) {
// Scheduler Exception
}
@Override
public void onOtherException(EngineException e) {
// Other Exception
}
});