P2P設定
用建造者模式實例化 P2pConfig,以下的參數是預設值:
val config = P2pConfig.Builder()
.logEnabled(false) // 是否列印日誌
.logLevel(LogLevel.WARN) // 列印日誌的等級
.trackerZone(TrackerZone.Europe) // tracker伺服器位址所在國家的列舉,分為China、Europe、HongKong、USA
.downloadTimeout(15000, TimeUnit.MILLISECONDS) // HTTP下載ts檔案逾時時間
.localPortHls(0) // HLS本地代理伺服器的連接埠號(預設隨機連接埠)
.localPortDash(0) // DASH本地代理伺服器的連接埠號(預設隨機連接埠)
.diskCacheLimit(2000*1024*1024) // 隨選模式下P2P在磁碟快取的最大資料量(設為0可以停用磁碟快取)
.memoryCacheCountLimit(15) // P2P在記憶體快取的最大資料量,用ts檔案個數表示
.p2pEnabled(true) // 開啟或關閉p2p engine
.withTag(null) // 使用者自訂的標籤,可以在控制台查看分布圖
.webRTCConfig(null) // 透過webRTCConfig來修改WebRTC預設設定
.maxPeerConnections(25) // 最大連線節點數量
.startFromSegmentOffset(3) // 開始請求tracker服務的segment偏移量
.useHttpRange(true) // 在可能的情況下使用Http Range請求來補足p2p下載逾時的剩餘部分資料
.useStrictHlsSegmentId(false) // 使用基於url的SegmentId,替代預設基於序號的
.httpHeadersForHls(null) // 設定請求ts和m3u8時的HTTP請求標頭
.httpHeadersForDash(null) // 設定請求Dash檔案時的HTTP請求標頭
.sharePlaylist(false) // 是否允許m3u8檔案的P2P傳輸
.prefetchOnly(false) // HLS模式下只採用預先載入的方式進行P2P下載
.logPersistent(false) // 是否將日誌持久化到外部儲存空間(Environment.getExternalStorageDirectory()路徑下的logger資料夾)
.insertTimeOffsetTag(null) // 僅在直播模式生效,在m3u8檔案中插入 "#EXT-X-START:TIME-OFFSET=[timeOffset]",強制播放器從某個位置開始載入,其中 [timeOffset] 是在播放清單的偏移量,如果為負則從播放清單結尾往前偏移(單位:秒)
.p2pProtocolVersion(P2pProtocolVersion.V8) // P2P 協定的版本號,與其他平台互通的前提是 P2P 協定版本號相同
.dashMediaFiles(
arrayListOf("mp4", "fmp4", "webm", "m4s", "m4v")) // 支援的DASH媒體檔案副檔名
.build()P2pEngine
實例化P2pEngine,取得一個全域單例:
P2pEngine.init(context, token, config)P2pEngine.init(context, token, config);參數說明:
| 參數 | 型別 | 是否必要 | 說明 |
|---|---|---|---|
| context | Context | 是 | 建議使用Application 的 Context 物件。 |
| token | String | 是 | CDNBye分配的token。 |
| config | P2pConfig | 否 | 自訂設定。 |
切換來源
當播放器切換到新的播放位址時,只需要將新的播放位址傳給 P2pEngine,從而取得新的本地播放位址:
val parsedUrl = P2pEngine.instance.parseStreamUrl(url)String parsedUrl = P2pEngine.getInstance().parseStreamUrl(url);P2pEngine API
P2pEngine.version
目前外掛程式的版本號。
P2pEngine.instance
取得 P2pEngine 的單例。
engine.parseStreamUrl(url: String)
將原始播放位址(m3u8/mpd)轉換成本地代理伺服器的位址。
engine.parseStreamUrl(url: String, videoId: String)
除了原始播放位址(m3u8/mpd),同時傳入videoId用以建構channelId。
engine.parseStreamUrl(url: String, videoId: String, mimeType: MimeType)
除了原始播放位址(m3u8/mpd)以及videoId,同時傳入mimeType(目前支援MimeType.APPLICATION_M3U8和MimeType.APPLICATION_MPD),用於無「.m3u8」或「.mpd」副檔名的url。
engine.isConnected
是否已與CDNBye後台建立連線。
engine.stopP2p()
立即停止P2P加速並釋放資源,一般只需要在退出APP的時候呼叫即可。SDK採用「延遲釋放」的策略,只有在重新啟動p2p的時候才釋放資源。對於效能較差的裝置,起播耗時可能比較明顯,建議在影片播放之前提前呼叫 engine.stopP2p() 。
engine.restartP2p()
重新啟動P2P加速服務,一般不需要呼叫。
engine.peerId
取得對等連線的id。
engine.setHttpHeadersForHls(headers: Map<String, String>?)
動態設定請求ts和m3u8時的HTTP請求標頭。
engine.setHttpHeadersForDash(headers: Map<String, String>?)
動態設定請求Dash檔案時的HTTP請求標頭。
engine.notifyPlaybackStalled()
當有卡頓發生時通知SDK。
engine.disableP2p()
執行時動態關閉P2P,在播放下一個媒體檔案時才生效。
engine.enableP2p()
執行時動態開啟P2P,在播放下一個媒體檔案時才生效。
engine.shutdown()
停止P2P並關閉代理伺服器。
P2P統計
透過 P2pStatisticsListener 來監聽P2P下載資訊:
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) {
}
});WARNING
下載和上傳資料量的單位是KB,下載速度的單位是KB/s。
進階用法
回呼播放器資訊
在直播模式下,為了增強P2P效果並提高播放流暢度,建議透過 setPlayerInteractor,將從目前播放時間到緩衝前沿的時間間隔回呼給p2p engine。
P2pEngine.instance?.setPlayerInteractor(object : PlayerInteractor() {
override fun onBufferedDuration(): Long {
return if (player != null) {
// Exoplayer 單位:毫秒
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;
}
});在隨選模式下,一部影片的時長可能比較大,如果能在節點配對的時候優先配對播放時間接近的節點則有助於P2P效果的提升,要實現這個功能需要在SDK層面取得播放器的目前播放時間:
P2pEngine.instance?.setPlayerInteractor(object : PlayerInteractor() {
override fun onCurrentPosition(): Long {
// Exoplayer 單位:毫秒
return player?.currentPosition ?: -1
}
})P2pEngine.getInstance().setPlayerInteractor(new PlayerInteractor() {
public long onCurrentPosition() {
// Exoplayer 單位:毫秒
if (play != null) {
return player.getCurrentPosition();
}
return -1;
}
});解決動態url路徑問題
某些CDN提供商提供的url是動態產生的,不同節點的位址不一樣,例如example.com/clientId1/streamId.m3u8和example.com/clientId2/streamId.m3u8,而本外掛程式預設使用url(去掉查詢參數)作為channelId。這時候就要建構一個共同的channelId,使實際觀看同一直播/影片(下載同一檔案)的節點處在相同頻道中。建構channelId方法如下:
val videoId = extractVideoIdFromUrl(urlString) // extractVideoIdFromUrl 需要自己定義,可以擷取url中的影片ID作為結果回傳
val parsedUrl = P2pEngine.instance?.parseStreamUrl(urlString, videoId)String videoId = extractVideoIdFromUrl(urlString); // extractVideoIdFromUrl 需要自己定義,可以擷取url中的影片ID作為結果回傳
String parsedUrl = P2pEngine.getInstance().parseStreamUrl(urlString, videoId);WARNING
如果要與其他平台互通,則必須確保兩者擁有相同的 token 和 channelId 。
嚴格切片ID模式
使用基於url的SegmentId,替代預設基於序號的:
P2pEngine.instance?.setHlsSegmentIdGenerator(StrictHlsSegmentIdGenerator())P2pEngine.getInstance().setHlsSegmentIdGenerator(new StrictHlsSegmentIdGenerator());設定HTTP請求標頭
出於防盜鏈或統計的需求,有些HTTP請求需要加上 User-Agent 等標頭資訊,可以透過 setHttpHeaders 進行設定:
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);設定播放時間偏移量
透過在m3u8設定一個特殊的tag,可以強制播放器從清單開始位置載入,從而提升P2P效果,但同時會增加延遲,需要權衡考慮。
val config = P2pConfig.Builder()
.insertTimeOffsetTag(0.0)
.build()P2pConfig config = new P2pConfig.Builder()
.insertTimeOffsetTag(0.0)
.build();播放器卡頓統計
SwarmCloud 控制台可以監測用戶端的平均卡頓率,只需要在播放器卡頓發生時回報給SDK即可,以 Exoplayer 為例:
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()
}
}
}
}, 7000)
}
}
})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);
}
}
});攔截m3u8或mpd檔案
由於 SDK 需要解析m3u8/mpd的內容,如果您用了加密的m3u8/mpd,則需要使用攔截器攔截並回傳標準的m3u8/mpd檔案:
P2pEngine.instance?.setHlsInterceptor(object : HlsInterceptor() {
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);
}
});支援無副檔名的媒體檔案
有些特殊的檔案不是以「.ts」結尾,也沒有任何其他副檔名,此時可以透過hook函式判斷其是否是媒體檔案:
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;
}
});傳入自訂 OkHttpClient
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();排除某些特殊的切片檔案
某些情況下我們不想讓某些切片檔案參與P2P,比如SSAI(Server Side Ad Insertion)產生的特定於使用者的切片,這個時候可以利用 segmentBypass 這個函式來進行過濾:
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);
}
});監聽SDK異常資訊
由於網路、伺服器和演算法bug等原因,SDK可能會出現異常,可以透過 registerExceptionListener 方法來監聽異常:
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
}
});