Skip to content

P2P配置

实例化 P2pConfig

swift
let config = P2pConfig()
let config = P2pConfig()
objective-c
P2pConfig* config = [P2pConfig defaultConfiguration];
P2pConfig* config = [P2pConfig defaultConfiguration];

覆盖相应的字段:

字段类型默认值描述
trackerZoneTrackerZone.Europetracker服务器地址所在国家的枚举,分为Europe、HongKong、USA
debugBoolfalse是否打印日志
logLevelLogLevel.WARN打印日志的级别(VERBOSE, DEBUG, INFO, WARN, ERROR)
iceServers[IceServer][IceServer(url: "stun:stun.l.google.com:19302"), IceServer(url: "stun:global.stun.twilio.com:3478?transport=udp")]用于Stun服务器配置
announceString?niltracker服务器地址
diskCacheLimitUInt2000 * 1024 * 1024点播模式下P2P在磁盘缓存的最大数据量(设为0可以禁用磁盘缓存)
memoryCacheCountLimitUInt20P2P在内存缓存的最大文件数量
p2pEnabledBooltrue开启或关闭p2p engine
localPortHlsUInt0播放HLS流媒体本地代理服务器的端口号,默认随机端口
customLabelString?${platform}-$用户自定义的标签,可以在控制台查看分布图
maxPeerConnectionsInt25最大连接节点数量
useHttpRangeBooltrue在可能的情况下使用Http Range请求来补足p2p下载超时的剩余部分数据
useStrictHlsSegmentIdBoolfalse使用基于url的SegmentId,替代默认基于序列号的
httpHeadersHls[String : String]?nil设置请求ts和m3u8时的HTTP请求头
httpLoadTimeTimeInterval3.0P2P下载超时后留给HTTP下载的时间
sharePlaylistBoolfalse是否允许m3u8文件的P2P传输
hlsMediaFiles[String]["mp4", "fmp4", "ts", "m4s", "m4v"]允许进行P2P传输的所有媒体文件后缀
mediaFileSeparatorString"."媒体文件后缀分隔符
logPersistentBoolfalse是否将日志持久化到外部存储,默认路径是 Library/Caches/p2pengine.log
geoIpPreflightBooltrue向在线IP数据库请求ASN等信息,从而获得更准确的调度
playlistTimeOffsetTimeIntervalTimeInterval.nan仅在直播模式生效,在m3u8文件中插入 "#EXT-X-START:TIME-OFFSET=[timeOffset]",强制播放器从某个位置开始加载,其数值是在播放列表的偏移量,如果为负则从播放列表结尾往前偏移(单位:秒)

P2pEngine

初始化化 P2pEngine

swift
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    P2pEngine.setup(token: YOUR_TOKEN)
    return true
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    P2pEngine.setup(token: YOUR_TOKEN)
    return true
}
objective-c
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [P2pEngine setupWithToken:YOUR_TOKEN config:NULL];
    return YES;
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [P2pEngine setupWithToken:YOUR_TOKEN config:NULL];
    return YES;
}

其中 YOUR_TOKEN 是用于标识用户的字符串,请替换成从控制台获取的token。将原始播放地址(m3u8)传给 P2pEngine,从而获取本地播放地址:

swift
let parsedUrl = P2pEngine.shared.parseStreamUrl(ORIGINAL_URL)
let parsedUrl = P2pEngine.shared.parseStreamUrl(ORIGINAL_URL)
objective-c
NSString *parsedUrl = [[P2pEngine sharedInstance] parseStreamUrl:ORIGINAL_URL];
NSString *parsedUrl = [[P2pEngine sharedInstance] parseStreamUrl:ORIGINAL_URL];

切换源

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

swift
let newParsedURL = P2pEngine.shared.parseStreamUrl(NEW_ORIGINAL_URL)
let newParsedURL = P2pEngine.shared.parseStreamUrl(NEW_ORIGINAL_URL)
objective-c
NSString *newParsedURL = [[P2pEngine sharedInstance] parseStreamUrl:NEW_ORIGINAL_URL];
NSString *newParsedURL = [[P2pEngine sharedInstance] parseStreamUrl:NEW_ORIGINAL_URL];

停止 P2P

停止当前播放视频的P2P传输,但不停止本地代理:

swift
override func viewDidDisappear(_ animated: Bool) {
    P2pEngine.shared.stopP2p()
}
override func viewDidDisappear(_ animated: Bool) {
    P2pEngine.shared.stopP2p()
}
objective-c
- (void)viewDidDisappear:(BOOL)animated {
    [P2pEngine.sharedInstance stopP2p];
}
- (void)viewDidDisappear:(BOOL)animated {
    [P2pEngine.sharedInstance stopP2p];
}

运行时动态关闭P2P

在播放下一个媒体文件时才生效

swift
P2pEngine.shared.disableP2p()
P2pEngine.shared.disableP2p()
objective-c
[P2pEngine.sharedInstance disableP2p];
[P2pEngine.sharedInstance disableP2p];

运行时动态开启P2P

在播放下一个媒体文件时才生效

swift
P2pEngine.shared.enableP2p()
P2pEngine.shared.enableP2p()
objective-c
[P2pEngine.sharedInstance enableP2p];
[P2pEngine.sharedInstance enableP2p];

停止P2P和本地代理

停止当前播放视频的P2P传输和本地代理:

swift
P2pEngine.shared.shutdown()
P2pEngine.shared.shutdown()
objective-c
[P2pEngine.sharedInstance shutdown];
[P2pEngine.sharedInstance shutdown];

重启本地代理

swift
P2pEngine.shared.startLocalServer()
P2pEngine.shared.startLocalServer()
objective-c
[P2pEngine.sharedInstance startLocalServer];
[P2pEngine.sharedInstance startLocalServer];

P2P统计

通过 P2pStatisticsMonitor 来监听P2P下载信息:

swift
let monitor = P2pStatisticsMonitor()
P2pEngine.shared.p2pStatisticsMonitor = monitor
let monitor = P2pStatisticsMonitor()
P2pEngine.shared.p2pStatisticsMonitor = monitor
objective-c
P2pStatisticsMonitor* monitor = [[P2pStatisticsMonitor alloc] initWithQueue:dispatch_get_main_queue()];
P2pEngine.shared.p2pStatisticsMonitor = monitor;
P2pStatisticsMonitor* monitor = [[P2pStatisticsMonitor alloc] initWithQueue:dispatch_get_main_queue()];
P2pEngine.shared.p2pStatisticsMonitor = monitor;

在回调函数中获取 p2pDownloadedp2pUploadedhttpDownloadedpeersserverConnected 等:

swift
monitor.onPeers = { peers in
}
monitor.onP2pUploaded = { value in
}
monitor.onP2pDownloaded = { value, speed in
}
monitor.onHttpDownloaded = { value in
}
monitor.onServerConnected = { connected in
}
monitor.onPeers = { peers in
}
monitor.onP2pUploaded = { value in
}
monitor.onP2pDownloaded = { value, speed in
}
monitor.onHttpDownloaded = { value in
}
monitor.onServerConnected = { connected in
}
objective-c
monitor.onPeers = ^(NSArray<NSString *> * _Nonnull peers) {
};
monitor.onP2pUploaded = ^(NSInteger value) {
};
monitor.onP2pDownloaded = ^(NSInteger value, NSInteger speed) {
};
monitor.onHttpDownloaded = ^(NSInteger value) {
};
monitor.onServerConnected = ^(BOOL connected) {
};
monitor.onPeers = ^(NSArray<NSString *> * _Nonnull peers) {
};
monitor.onP2pUploaded = ^(NSInteger value) {
};
monitor.onP2pDownloaded = ^(NSInteger value, NSInteger speed) {
};
monitor.onHttpDownloaded = ^(NSInteger value) {
};
monitor.onServerConnected = ^(BOOL connected) {
};

WARNING

下载和上传数据量的单位是KB。

高级用法

回调播放器信息

在直播模式下,为了增强P2P效果,建议通过实现代理 PlayerInteractor 的方式,将从当前播放时间到缓冲前沿的时间间隔回调给p2p engine。

swift
class ViewController: UIViewController {
...
P2pEngine.shared.playerInteractor = self
...
}
extension ViewController : PlayerInteractor {
    func onBufferedDuration() -> TimeInterval {
        let currentTime = CMTimeGetSeconds(self.avplayer.currentTime())
        var bufferedDuration: Double = 0.0
        let timeRanges = self.avplayer.currentItem!.loadedTimeRanges
        for value in timeRanges {
            let timeRange = value.timeRangeValue
            let start = CMTimeGetSeconds(timeRange.start)
            let end = start + CMTimeGetSeconds(timeRange.duration);
            if (currentTime >= start && currentTime <= end) {
                bufferedDuration = end - currentTime;
                break;
            }
        }
        return bufferedDuration;
    }
}
class ViewController: UIViewController {
...
P2pEngine.shared.playerInteractor = self
...
}
extension ViewController : PlayerInteractor {
    func onBufferedDuration() -> TimeInterval {
        let currentTime = CMTimeGetSeconds(self.avplayer.currentTime())
        var bufferedDuration: Double = 0.0
        let timeRanges = self.avplayer.currentItem!.loadedTimeRanges
        for value in timeRanges {
            let timeRange = value.timeRangeValue
            let start = CMTimeGetSeconds(timeRange.start)
            let end = start + CMTimeGetSeconds(timeRange.duration);
            if (currentTime >= start && currentTime <= end) {
                bufferedDuration = end - currentTime;
                break;
            }
        }
        return bufferedDuration;
    }
}
objective-c
@interface ViewController () <PlayerInteractor>
...
P2pEngine.shared.playerInteractor = self;
...
#pragma mark - **************** PlayerInteractor ****************
-(NSTimeInterval)onBufferedDuration {
    NSTimeInterval currentTime = CMTimeGetSeconds([self.player currentTime]);
    NSTimeInterval bufferedDuration = 0;
    for (NSValue *value in [[self.player currentItem] loadedTimeRanges]) {
        CMTimeRange timeRange = [value CMTimeRangeValue];
        NSTimeInterval start = CMTimeGetSeconds(timeRange.start);
        NSTimeInterval end = start + CMTimeGetSeconds(timeRange.duration);
        if (currentTime >= start && currentTime <= end) {
            bufferedDuration = end - currentTime;
            break;
        }
    }
    return bufferedDuration;
}
@interface ViewController () <PlayerInteractor>
...
P2pEngine.shared.playerInteractor = self;
...
#pragma mark - **************** PlayerInteractor ****************
-(NSTimeInterval)onBufferedDuration {
    NSTimeInterval currentTime = CMTimeGetSeconds([self.player currentTime]);
    NSTimeInterval bufferedDuration = 0;
    for (NSValue *value in [[self.player currentItem] loadedTimeRanges]) {
        CMTimeRange timeRange = [value CMTimeRangeValue];
        NSTimeInterval start = CMTimeGetSeconds(timeRange.start);
        NSTimeInterval end = start + CMTimeGetSeconds(timeRange.duration);
        if (currentTime >= start && currentTime <= end) {
            bufferedDuration = end - currentTime;
            break;
        }
    }
    return bufferedDuration;
}

在点播模式下,一个视频的时长可能比较大,如果能在节点匹配的时候优先匹配播放时间接近的节点则有助于P2P效果的提升,要实现这个功能需要在SDK层面获取播放器的当前播放时间:

swift
class ViewController: UIViewController {
...
P2pEngine.shared.playerInteractor = self
...
}
extension ViewController : PlayerInteractor {
    func onCurrentPosition() -> TimeInterval {
        return CMTimeGetSeconds(self.avplayer.currentTime())
    }
}
class ViewController: UIViewController {
...
P2pEngine.shared.playerInteractor = self
...
}
extension ViewController : PlayerInteractor {
    func onCurrentPosition() -> TimeInterval {
        return CMTimeGetSeconds(self.avplayer.currentTime())
    }
}
objective-c
@interface ViewController () <PlayerInteractor>
...
P2pEngine.shared.playerInteractor = self;
...
- (NSTimeInterval)onCurrentPosition {
    return CMTimeGetSeconds([self.player currentTime]);
}
@interface ViewController () <PlayerInteractor>
...
P2pEngine.shared.playerInteractor = self;
...
- (NSTimeInterval)onCurrentPosition {
    return CMTimeGetSeconds([self.player currentTime]);
}

解决动态m3u8路径问题

某些流媒体提供商的m3u8是动态生成的,不同节点的m3u8地址不一样,例如example.com/clientId1/streamId.m3u8和example.com/clientId2/streamId.m3u8, 而本插件默认使用m3u8地址(去掉查询参数)作为channelId。这时候就要构造一个共同的chanelId,使实际观看同一直播/视频的节点处在相同频道中。

swift
let videoId = extractVideoIdFromUrl(url: orginalUrl)                      // extractVideoIdFromUrl 需要自己定义,可以抽取url中的视频ID作为结果返回
let parsedUrl = P2pEngine.shared.parseStreamUrl(orginalUrl, videoId: videoId)
let videoId = extractVideoIdFromUrl(url: orginalUrl)                      // extractVideoIdFromUrl 需要自己定义,可以抽取url中的视频ID作为结果返回
let parsedUrl = P2pEngine.shared.parseStreamUrl(orginalUrl, videoId: videoId)
objective-c
NSString *videoId = [Utils extractVideoIdFromUrl:originalUrl];            // extractVideoIdFromUrl 需要自己定义,可以抽取url中的视频ID作为结果返回
NSURL *parsedUrl = [[P2pEngine sharedInstance] parseStreamUrl:originalUrl videoId:videoId];
NSString *videoId = [Utils extractVideoIdFromUrl:originalUrl];            // extractVideoIdFromUrl 需要自己定义,可以抽取url中的视频ID作为结果返回
NSURL *parsedUrl = [[P2pEngine sharedInstance] parseStreamUrl:originalUrl videoId:videoId];

设置HTTP请求头

出于防盗链或者统计的需求,有些HTTP请求需要加上 User-Agent 等头信息,可以通过 httpHeaders 进行设置:

swift
p2pConfig.httpHeadersHls = ["User-Agent": "XXX"]
p2pConfig.httpHeadersHls = ["User-Agent": "XXX"]
objective-c
p2pConfig.httpHeadersHls = @{@"User-Agent":@"XXX"};
p2pConfig.httpHeadersHls = @{@"User-Agent":@"XXX"};

设置播放时间偏移量

通过在m3u8设置一个特殊的tag,可以强制播放器从列表开始位置加载,从而提升P2P效果,但同时会增加延迟,需要权衡考虑。

swift
p2pConfig.playlistTimeOffset = 0.0
p2pConfig.playlistTimeOffset = 0.0
objective-c
p2pConfig.playlistTimeOffset = 0.0;
p2pConfig.playlistTimeOffset = 0.0;

支持 AirPlay 模式

SwarmCloud暂不支持 AirPlay ,但是可以通过只在不进行 AirPlay 的时候使用本地代理来避开限制:

objective-c
if (/* player is in AirPlay mode */) {
    // set URL to original version
   [player replaceCurrentItemWithPlayerItem:[AVPlayerItem playerItemWithURL:original]];
} else {
    // airplay stopped - set URL to result of parseStreamURL
   [player replaceCurrentItemWithPlayerItem:[AVPlayerItem playerItemWithURL:rewritten]];
}
if (/* player is in AirPlay mode */) {
    // set URL to original version
   [player replaceCurrentItemWithPlayerItem:[AVPlayerItem playerItemWithURL:original]];
} else {
    // airplay stopped - set URL to result of parseStreamURL
   [player replaceCurrentItemWithPlayerItem:[AVPlayerItem playerItemWithURL:rewritten]];
}

扩展支持的HLS媒体文件

默认只支持".ts"等为后缀的常见文件类型,如果需要支持类似".image"的文件类型,需要做以下配置:

swift
let config = P2pConfig()
config.hlsMediaFiles = ["ts", "image"]
P2pEngine.shared.setup(token: YOUR_TOKEN, config: config)
let config = P2pConfig()
config.hlsMediaFiles = ["ts", "image"]
P2pEngine.shared.setup(token: YOUR_TOKEN, config: config)
objective-c
P2pConfig *config = [P2pConfig defaultConfiguration];
config.hlsMediaFiles = @[@"ts", @"image"];
[[P2pEngine sharedInstance] setupWithToken:YOUR_TOKEN config:config];
P2pConfig *config = [P2pConfig defaultConfiguration];
config.hlsMediaFiles = @[@"ts", @"image"];
[[P2pEngine sharedInstance] setupWithToken:YOUR_TOKEN config:config];

一般HLS媒体文件以".xx"结尾,但也有例外情况,比如其他任意字符结尾,为了让本地代理能识别出这些扩展,需要做以下配置:

swift
class ViewController: UIViewController {
...
P2pEngine.shared.hlsInterceptor = self
...
}
extension ViewController : HlsInterceptor {
    func isMediaSegment(url: String) -> Bool {
        return mediaFileRegex.match(url)
    }
}
class ViewController: UIViewController {
...
P2pEngine.shared.hlsInterceptor = self
...
}
extension ViewController : HlsInterceptor {
    func isMediaSegment(url: String) -> Bool {
        return mediaFileRegex.match(url)
    }
}
objective-c
@interface ViewController () <HlsInterceptor>
...
P2pEngine.shared.hlsInterceptor = self;
...
#pragma mark - **************** HlsInterceptor ****************
- (BOOL)isMediaSegmentWithUrl:(NSString *)url {
    return [mediaFileRegex match:url];
}
@interface ViewController () <HlsInterceptor>
...
P2pEngine.shared.hlsInterceptor = self;
...
#pragma mark - **************** HlsInterceptor ****************
- (BOOL)isMediaSegmentWithUrl:(NSString *)url {
    return [mediaFileRegex match:url];
}

屏蔽某些特殊的切片文件

某些情况下我们不想让某些切片文件参与P2P,比如SSAI(Server Side Ad Insertion)产生的特定于用户的切片,这个时候可以利用 segmentBypass 这个函数来进行过滤:

swift
class ViewController: UIViewController {
...
P2pEngine.shared.hlsInterceptor = self
...
}
extension ViewController : HlsInterceptor {
    func shouldBypassSegment(url: String) -> Bool {
        return isSSAISegment(url)
    }
}
class ViewController: UIViewController {
...
P2pEngine.shared.hlsInterceptor = self
...
}
extension ViewController : HlsInterceptor {
    func shouldBypassSegment(url: String) -> Bool {
        return isSSAISegment(url)
    }
}
objective-c
@interface ViewController () <HlsInterceptor>
...
P2pEngine.shared.hlsInterceptor = self;
...
#pragma mark - **************** HlsInterceptor ****************
- (BOOL)shouldBypassSegment:(NSString *)url {
    return [Utils isSSAISegment:url];
}
@interface ViewController () <HlsInterceptor>
...
P2pEngine.shared.hlsInterceptor = self;
...
#pragma mark - **************** HlsInterceptor ****************
- (BOOL)shouldBypassSegment:(NSString *)url {
    return [Utils isSSAISegment:url];
}

拦截 m3u8 文件

由于 SDK 需要解析 m3u8 的内容,如果您用了加密的 m3u8 ,则需要使用拦截器拦截并返回标准的 m3u8 文件:

swift
class ViewController: UIViewController {
...
P2pEngine.shared.hlsInterceptor = self
...
}
extension ViewController : HlsInterceptor {
    func interceptPlaylist(data: Data, url: String) -> Data {
        return handlePlaylist(data, url)
    }
}
class ViewController: UIViewController {
...
P2pEngine.shared.hlsInterceptor = self
...
}
extension ViewController : HlsInterceptor {
    func interceptPlaylist(data: Data, url: String) -> Data {
        return handlePlaylist(data, url)
    }
}
objective-c
@interface ViewController () <HlsInterceptor>
...
P2pEngine.shared.hlsInterceptor = self;
...
#pragma mark - **************** HlsInterceptor ****************
- (NSData *)interceptPlaylistWithData:(NSData *)data url:(NSString *)url {
    return [Utils handlePlaylistWithData:data url:url];
}
@interface ViewController () <HlsInterceptor>
...
P2pEngine.shared.hlsInterceptor = self;
...
#pragma mark - **************** HlsInterceptor ****************
- (NSData *)interceptPlaylistWithData:(NSData *)data url:(NSString *)url {
    return [Utils handlePlaylistWithData:data url:url];
}

播放器卡顿统计

SwarmCloud 控制台可以监测客户端的平均卡顿率,只需要在播放器卡顿发生时上报给SDK即可:

swift
NotificationCenter.default.addObserver(self, selector: #selector(handleItemPlayBackStall), name: NSNotification.Name.AVPlayerItemPlaybackStalled, object: player.currentItem)

@objc func handleItemPlayBackStall() {
    P2pEngine.shared.notifyPlaybackStalled()
}
NotificationCenter.default.addObserver(self, selector: #selector(handleItemPlayBackStall), name: NSNotification.Name.AVPlayerItemPlaybackStalled, object: player.currentItem)

@objc func handleItemPlayBackStall() {
    P2pEngine.shared.notifyPlaybackStalled()
}
objective-c
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(handleItemPlayBackStall) name:AVPlayerItemPlaybackStalledNotification object:self.playerVC.player.currentItem];

- (void)handleItemPlayBackStall {
    [P2pEngine.sharedInstance notifyPlaybackStalled];
}
[NSNotificationCenter.defaultCenter addObserver:self selector:@selector(handleItemPlayBackStall) name:AVPlayerItemPlaybackStalledNotification object:self.playerVC.player.currentItem];

- (void)handleItemPlayBackStall {
    [P2pEngine.sharedInstance notifyPlaybackStalled];
}

粤ICP备18075581号