API & Config
P2P Configuration
A P2pConfig can be obtained via its builder, the parameters below is 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(30_000, 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
.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.
.isSetTopBox(false) // Set it as true if SDK is running on set-top box
.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/).
.geoIpPreflight(true) // Make a preflight request to online Geo IP database provider to get ASN.
.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 P2pEngine,which is a singleton:
- kotlin
- java
P2pEngine.init(context, token, config)
P2pEngine.init(context, token, config);
Explanation:
param | type | required | description |
---|---|---|---|
context | Context | Yes | The instance of Application Context is recommended. |
token | String | Yes | Token assigned by CDNBye. |
config | P2pConfig | No | Custom configuration. |
Switch Stream URL
When Switching to a new stream URL, before passing new stream url to the player, pass that URL through P2pEngine instance:
- kotlin
- java
val parsedUrl = P2pEngine.instance.parseStreamUrl(url)
String parsedUrl = P2pEngine.getInstance().parseStreamUrl(url);
P2pEngine API
P2pEngine.version
Current SDK version.
P2pEngine.instance
Get the singleton of P2pEngine.
engine.parseStreamUrl(url: String)
Convert original playback address (m3u8/mpd) to the address of the local proxy server.
engine.parseStreamUrl(url: String, videoId: String)
Pass video ID for making channel ID in addition to original playback address.
engine.parseStreamUrl(url: String, videoId: String, mimeType: MimeType)
If your URI doesn’t end with .m3u8/.mpd, you can pass MimeType.APPLICATION_M3U8 or MimeType.APPLICATION_MPD to the third parameter of parseStreamUrl to explicitly indicate the type of the content.
engine.isConnected
Check if connected with CDNBye backend.
engine.stopP2p()
Once the video is done playing, you have to stop the P2P streaming you created earlier. Calling this method will finish the ongoing tasks and release the resources.
engine.restartP2p()
Resume P2P if it has been stopped.
engine.peerId
Get the peer ID of this engine.
engine.setHttpHeadersForHls(headers: Map<String, String>?)
Set HTTP Headers while requesting ts and m3u8 dynamically.
engine.setHttpHeadersForDash(headers: Map<String, String>?)
Set HTTP Headers while requesting Dash files dynamically.
engine.notifyPlaybackStalled()
Notify SDK the player stalled.
engine.disableP2p()
P2P will be disabled dynamically at runtime, it will not take effect until the next media file is played.
engine.enableP2p()
P2P will be enabled dynamically at runtime, it will not take effect until the next media file is played.
engine.shutdown()
Stop P2P and shut down the proxy server.
P2P Statistics
Add an observer P2pStatisticsListener to observe downloading 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) {
}
});
The unit of download and upload is KB. The unit of download speed is KB/s.
Advanced Usage
Callback Player Stats
On Live streaming, to improve performance, we recommend telling p2p engine the duration from the playback time to the end of the buffered interval. In order to do so, you need to use callback setPlayerInteractor .
- 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;
}
});
On the VOD mode, the duration of a video may be large. If we can match peers with similar playback time, it will help to improve the P2P performance, so we recommend to tell p2p engine the current playback time of player:
- 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 that are watching the same content. It is an optional parameter, and by default, we generate videoId from the content URL by removing any query parameters and protocol from it. However, if specific urls are generated for every viewer (it may be the case if a token is used in the path of the url for security reasons), then the players will think they are not watching the same content, preventing the p2p plugin from being efficient. In this situation, videoIDs are needed.
- 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);
Interconnect with other platform should ensure that both have the same token and channelId.
StrictSegmentId Mode
You can use segment url based segment id instead of sequence number based one:
- kotlin
- java
P2pEngine.instance?.setHlsSegmentIdGenerator(StrictHlsSegmentIdGenerator())
P2pEngine.getInstance().setHlsSegmentIdGenerator(new StrictHlsSegmentIdGenerator());
Setup HTTP headers
Some HTTP requests need to add header information such as User-Agent for Anti-Leech or statistical requirements. It can be set 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 playing a live stream from the start point in time of playlist, however, it may bring more delay to the stream:
- kotlin
- java
val config = P2pConfig.Builder()
.insertTimeOffsetTag(0.0)
.build()
P2pConfig config = new P2pConfig.Builder()
.insertTimeOffsetTag(0.0)
.build();
Report Player Rebuffering
You may want to report the player rebuffering event to the sdk, then get average rebuffer ratio from 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 will parse the contents of m3u8/mpd when downloaded, if you use encrypted m3u8/mpd, you need to use the interceptor to intercept and return the standard m3u8/mpd 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 special files do not end with ".ts" or any other suffixes. You can use the hook function to determine whether they are media files:
- 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 we don't want some ts files to be shared, such as user-specific ts generated by SSAI (Server Side Ad Insertion). At this time, we can use the segmentBypass function to bypass it:
- 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
Due to network, server, algorithm bugs and other reasons, the SDK may have exceptions. You can listen to the exceptions by using the 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
}
});