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) // VODモードで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アクセラレーションを即座に停止し、リソースを解放します。通常はアプリ終了時にのみ呼び出せば十分です。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;
}
});VODモードでは、動画の長さが比較的大きい場合があり、ノードのマッチング時に再生時刻が近いノードを優先的にマッチングできれば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に特殊なタグを設定することで、プレイヤーを強制的にリストの先頭位置から読み込ませることができ、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();特定のセグメントファイルを除外する
特定のセグメントファイル、例えばSSAI(Server Side Ad Insertion)によって生成されたユーザー固有のセグメントをP2Pに参加させたくない場合があります。このような場合、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の例外情報を監視する
ネットワーク、サーバー、アルゴリズムのバグなどの理由により、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
}
});