Skip to content

P2P配置

用建造者模式实例化 P2pConfig,以下的参数是默认值:

kotlin
val config = P2pConfig.Builder()
.logEnabled(false)                                  // 是否打印日志
.logLevel(LogLevel.WARN)                            // 打印日志的级别
.trackerZone(TrackerZone.Europe)                    // tracker服务器地址所在国家的枚举,分为Europe、HongKong、USA
.downloadTimeout(30_000, 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)                             // 最大连接节点数量
.useHttpRange(true)                                 // 在可能的情况下使用Http Range请求来补足p2p下载超时的剩余部分数据
.useStrictHlsSegmentId(false)                       // 使用基于url的SegmentId,替代默认基于序列号的
.httpHeadersForHls(null)                            // 设置请求ts和m3u8时的HTTP请求头
.httpHeadersForDash(null)                           // 设置请求Dash文件时的HTTP请求头
.isSetTopBox(false)                                 // 如果运行于机顶盒请设置成true
.httpLoadTime(3_000)                                // P2P下载超时后留给HTTP下载的时间
.sharePlaylist(false)                               // 是否允许m3u8文件的P2P传输
.logPersistent(false)                               // 是否将日志持久化到外部存储(Environment.getExternalStorageDirectory()路径下的logger文件夹)
.geoIpPreflight(true)                               // 向在线IP数据库请求ASN等信息,从而获得更准确的调度
.insertTimeOffsetTag(null)                          // 仅在直播模式生效,在m3u8文件中插入 "#EXT-X-START:TIME-OFFSET=[timeOffset]",强制播放器从某个位置开始加载,其中 [timeOffset] 是在播放列表的偏移量,如果为负则从播放列表结尾往前偏移(单位:秒)
.p2pProtocolVersion(P2pProtocolVersion.V8)          // P2P 协议的版本号,与其他平台互通的前提是 P2P 协议版本号相同
.mediaFileSeparator(".")                            // 媒体文件后缀分隔符
.hlsMediaFiles(
    arrayListOf("mp4", "fmp4", "ts","m4s","m4v"))            // 支持的HLS媒体文件后缀
.dashMediaFiles(
    arrayListOf("mp4", "fmp4", "webm", "m4s", "m4v"))       // 支持的DASH媒体文件后缀
.build()
val config = P2pConfig.Builder()
.logEnabled(false)                                  // 是否打印日志
.logLevel(LogLevel.WARN)                            // 打印日志的级别
.trackerZone(TrackerZone.Europe)                    // tracker服务器地址所在国家的枚举,分为Europe、HongKong、USA
.downloadTimeout(30_000, 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)                             // 最大连接节点数量
.useHttpRange(true)                                 // 在可能的情况下使用Http Range请求来补足p2p下载超时的剩余部分数据
.useStrictHlsSegmentId(false)                       // 使用基于url的SegmentId,替代默认基于序列号的
.httpHeadersForHls(null)                            // 设置请求ts和m3u8时的HTTP请求头
.httpHeadersForDash(null)                           // 设置请求Dash文件时的HTTP请求头
.isSetTopBox(false)                                 // 如果运行于机顶盒请设置成true
.httpLoadTime(3_000)                                // P2P下载超时后留给HTTP下载的时间
.sharePlaylist(false)                               // 是否允许m3u8文件的P2P传输
.logPersistent(false)                               // 是否将日志持久化到外部存储(Environment.getExternalStorageDirectory()路径下的logger文件夹)
.geoIpPreflight(true)                               // 向在线IP数据库请求ASN等信息,从而获得更准确的调度
.insertTimeOffsetTag(null)                          // 仅在直播模式生效,在m3u8文件中插入 "#EXT-X-START:TIME-OFFSET=[timeOffset]",强制播放器从某个位置开始加载,其中 [timeOffset] 是在播放列表的偏移量,如果为负则从播放列表结尾往前偏移(单位:秒)
.p2pProtocolVersion(P2pProtocolVersion.V8)          // P2P 协议的版本号,与其他平台互通的前提是 P2P 协议版本号相同
.mediaFileSeparator(".")                            // 媒体文件后缀分隔符
.hlsMediaFiles(
    arrayListOf("mp4", "fmp4", "ts","m4s","m4v"))            // 支持的HLS媒体文件后缀
.dashMediaFiles(
    arrayListOf("mp4", "fmp4", "webm", "m4s", "m4v"))       // 支持的DASH媒体文件后缀
.build()

P2pEngine

实例化P2pEngine,获得一个全局单例:

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

参数说明:

参数类型是否必须说明
contextContext建议使用Application 的 Context 对象。
tokenStringCDNBye分配的token。
configP2pConfig自定义配置。

切换源

当播放器切换到新的播放地址时,只需要将新的播放地址传给 P2pEngine,从而获取新的本地播放地址:

kotlin
val parsedUrl = P2pEngine.instance.parseStreamUrl(url)
val parsedUrl = P2pEngine.instance.parseStreamUrl(url)
java
String parsedUrl = P2pEngine.getInstance().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下载信息:

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) {
    }
})
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) {

    }
});
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
        }
    }
})
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;
    }
});
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
    }
})
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;
    }
});
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。这时候就要构造一个共同的chanelId,使实际观看同一直播/视频(下载同一文件)的节点处在相同频道中。构造channelId方法如下:

kotlin
val videoId = extractVideoIdFromUrl(urlString)     // extractVideoIdFromUrl 需要自己定义,可以抽取url中的视频ID作为结果返回
val parsedUrl = P2pEngine.instance?.parseStreamUrl(urlString, videoId)
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);
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())
P2pEngine.instance?.setHlsSegmentIdGenerator(StrictHlsSegmentIdGenerator())
java
P2pEngine.getInstance().setHlsSegmentIdGenerator(new StrictHlsSegmentIdGenerator());
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)
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);
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()
val config = P2pConfig.Builder()
        .insertTimeOffsetTag(0.0)
        .build()
java
P2pConfig config = new P2pConfig.Builder()
        .insertTimeOffsetTag(0.0)
        .build();
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)
        }
    }
})
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);
        }
    }
});
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);
    }
})
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);
    }
});
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
    }
})
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;
    }
});
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()
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();
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)
    }
})
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);
    }
});
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
    }
})
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
    }
});
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号