Skip to content

P2P設定

用建造者模式實例化 P2pConfig,以下的參數是預設值:

kotlin
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,取得一個全域單例:

kotlin
P2pEngine.init(context, token, config)
java
P2pEngine.init(context, token, config);

參數說明:

參數型別是否必要說明
contextContext建議使用Application 的 Context 物件。
tokenStringCDNBye分配的token。
configP2pConfig自訂設定。

切換來源

當播放器切換到新的播放位址時,只需要將新的播放位址傳給 P2pEngine,從而取得新的本地播放位址:

kotlin
val parsedUrl = P2pEngine.instance.parseStreamUrl(url)
java
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下載資訊:

kotlin
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) {
    }
})
java
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。

kotlin
P2pEngine.instance?.setPlayerInteractor(object : PlayerInteractor() {
    override fun onBufferedDuration(): Long {
        return if (player != null) {
            // Exoplayer 單位:毫秒
            player!!.bufferedPosition - player!!.currentPosition
        } else {
            -1
        }
    }
})
java
P2pEngine.getInstance().setPlayerInteractor(new PlayerInteractor() {
    public long onBufferedDuration() {
        // Exoplayer in milliseconds
        if (play != null) {
            return player.getBufferedPosition() - player.getCurrentPosition();
        }
        return -1;
    }
});

在隨選模式下,一部影片的時長可能比較大,如果能在節點配對的時候優先配對播放時間接近的節點則有助於P2P效果的提升,要實現這個功能需要在SDK層面取得播放器的目前播放時間:

kotlin
P2pEngine.instance?.setPlayerInteractor(object : PlayerInteractor() {
    override fun onCurrentPosition(): Long {
        // Exoplayer 單位:毫秒
        return player?.currentPosition ?: -1
    }
})
java
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方法如下:

kotlin
val videoId = extractVideoIdFromUrl(urlString)     // extractVideoIdFromUrl 需要自己定義,可以擷取url中的影片ID作為結果回傳
val parsedUrl = P2pEngine.instance?.parseStreamUrl(urlString, videoId)
java
String videoId = extractVideoIdFromUrl(urlString);     // extractVideoIdFromUrl 需要自己定義,可以擷取url中的影片ID作為結果回傳
String parsedUrl = P2pEngine.getInstance().parseStreamUrl(urlString, videoId);

WARNING

如果要與其他平台互通,則必須確保兩者擁有相同的 token 和 channelId 。

嚴格切片ID模式

使用基於url的SegmentId,替代預設基於序號的:

kotlin
P2pEngine.instance?.setHlsSegmentIdGenerator(StrictHlsSegmentIdGenerator())
java
P2pEngine.getInstance().setHlsSegmentIdGenerator(new StrictHlsSegmentIdGenerator());

設定HTTP請求標頭

出於防盜鏈或統計的需求,有些HTTP請求需要加上 User-Agent 等標頭資訊,可以透過 setHttpHeaders 進行設定:

kotlin
val headers = mapOf("User-Agent" to "XXX")
P2pEngine.instance?.setHttpHeadersForHls(headers)
P2pEngine.instance?.setHttpHeadersForDash(headers)
java
Map headers = new HashMap();
headers.put("User-Agent", "XXX");
engine.setHttpHeadersForHls(headers);
engine.setHttpHeadersForDash(headers);

設定播放時間偏移量

透過在m3u8設定一個特殊的tag,可以強制播放器從清單開始位置載入,從而提升P2P效果,但同時會增加延遲,需要權衡考慮。

kotlin
val config = P2pConfig.Builder()
        .insertTimeOffsetTag(0.0)
        .build()
java
P2pConfig config = new P2pConfig.Builder()
        .insertTimeOffsetTag(0.0)
        .build();

播放器卡頓統計

SwarmCloud 控制台可以監測用戶端的平均卡頓率,只需要在播放器卡頓發生時回報給SDK即可,以 Exoplayer 為例:

kotlin
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)
        }
    }
})
java
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檔案:

kotlin
P2pEngine.instance?.setHlsInterceptor(object : HlsInterceptor() {
    override fun interceptPlaylist(text: ByteArray, url: String): ByteArray {
        return handlePlaylist(text, url);
    }
})
java
P2pEngine.getInstance().setHlsInterceptor(new HlsInterceptor() {
    @Override
    public byte[] interceptPlaylist(byte[] text, String url) {
        return handlePlaylist(text, url);
    }
});

支援無副檔名的媒體檔案

有些特殊的檔案不是以「.ts」結尾,也沒有任何其他副檔名,此時可以透過hook函式判斷其是否是媒體檔案:

kotlin
P2pEngine.instance?.setHlsInterceptor(object : HlsInterceptor() {
    override fun isMediaSegment(url: String): Boolean {
        return true
    }
})
java
P2pEngine.getInstance().setHlsInterceptor(new HlsInterceptor() {
    @Override
    public boolean isMediaSegment(@NonNull String url) {
        return true;
    }
});

傳入自訂 OkHttpClient

kotlin
val httpClient = OkHttpClient.Builder()
    .addInterceptor(YourInterceptor())
    .build()
val config = P2pConfig.Builder().okHttpClient(httpClient).build()
java
OkHttpClient httpClient = new OkHttpClient.Builder()
                .addInterceptor(new YourInterceptor())
                .build();
P2pConfig config = new P2pConfig.Builder()
        .okHttpClient(httpClient)
        .build();

排除某些特殊的切片檔案

某些情況下我們不想讓某些切片檔案參與P2P,比如SSAI(Server Side Ad Insertion)產生的特定於使用者的切片,這個時候可以利用 segmentBypass 這個函式來進行過濾:

kotlin
P2pEngine.instance?.setHlsInterceptor(object : HlsInterceptor() {
    override fun shouldBypassSegment(url: String): Boolean {
        return isSSAISegment(url)
    }
})
java
P2pEngine.getInstance().setHlsInterceptor(new HlsInterceptor() {
    @Override
    public boolean shouldBypassSegment(@NonNull String url) {
        return isSSAISegment(url);
    }
});

監聽SDK異常資訊

由於網路、伺服器和演算法bug等原因,SDK可能會出現異常,可以透過 registerExceptionListener 方法來監聽異常:

kotlin
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
    }
})
java
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
    }
});

粤ICP备18075581号