跳到主要内容

融云 CDN 播放器

提示

融云 CDN 插件(RongRTCPlayer) 从 5.3.0 版本开始提供 CDN 播放器 RCRTCMediaPlayer

融云 CDN 插件 RongRTCPlayer 中提供了 CDN 流播放组件 RCRTCMediaPlayer,可用于播放第三方 CDN 直播流、获取 SEI 数据、或直接获取音视频帧数据用于自行处理。

  • 仅支持播放 CDN 直播流,不支持点播文件或网络点播资源。
  • 支持的 CDN 直播流协议:RTMP、HTTP-FLV、HLS
  • 仅支持播放 60fps 以下(不含)帧率的视频。

适用场景

融云 CDN 插件(RongRTCPlayer)直接在 RTC 房间内提供了 融云 CDN 服务的订阅、播放等处理逻辑,降低了 App 业务侧的接入成本。

CDN 流播放组件 RCRTCMediaPlayer 可直接输入 CDN 流地址进行播放,不依赖于用户加入 RTC 房,能有效降低 CDN 首屏打开延时,带来更优秀的用户体验。具体适用场景如下:

  • 您的 App 自行接入了第三方 CDN 服务,希望通过融云 CDN 播放器组件播放第三方拉流地址。
  • 您的 App 使用了融云 CDN 服务,按照融云指定的规则自行拼接生成推流、拉流地址后(详见融云 CDN 服务配置),通过融云客户端旁路推流服务端旁路推流接口将 CDN 推流地址提交给融云。配置成功后,可在融云 CDN 播放器中输入您自行生成的 CDN 拉流地址进行播放。该方式要求您将融云 CDN 的「推拉流模式」配置为自行生成推拉流地址模式。
  • 您的 App 使用了融云 CDN 服务,并直接使用融云服务端接口获取融云 CDN 拉流地址,您也可以通过融云 CDN 播放器组件进行播放。该方式要求您将融云 CDN 的「推拉流模式」配置为开播自动推流开播手动推流模式。

集成 CDN 播放器组件

RCRTCMediaPlayer 在融云 CDN 插件(RongRTCPlayer)中提供。融云 CDN 插件导入方法请参见导入 SDK

集成融云 CDN 插件后请注意导入头文件:

#import <RongRTCPlayer/RongRTCPlayer.h>`

使用 CDN 播放器组件

  1. 创建 RCRTCMediaPlayer 实例,同时设置播放事件 RCRTCMediaPlayerEventDelegate 的代理。注意,设置代理必须在加载资源(openWithUrl)调用之前。

    self.mediaPlayer = [RCRTCMediaPlayer alloc] init];
    self.mediaPlayer.delegate = self;
  2. 加载资源。资源缓冲完成后,不会自动播放。

    NSURL *url = [NSURL URLWithString:@"rtmps://......"];
    int res = [self.mediaPlayer openWithUrl:url];
    if (res != 0) {
    NSLog(@"openWithUrl failed:%@",@(res));
    }
  3. 播放资源。

    int res = [self.mediaPlayer play];
    if (res != 0) {
    NSLog(@"play failed:%@",@(res));
    }
  4. 获取渲染视图。如果在 openWithUrl 加载之前调用渲染视图,该接口返回为 nil

    提示

    当设置 dataHandle 代理时播放器不执行内部渲染,该接口返回为 nil

    UIView *videoView = [self.mediaPlayer videoView];
    [self.view addSubview:videoView];
  5. (可选)设置渲染模式。如不设置,默认为 RCRTCVideoFillMode.RCRTCVideoFillModeAspectFit,等比例填充黑边,直到一个维度到达区域边界。

    //详细见 RCRTCVideoFillMode 枚举
    int res = [self.mediaPlayer setRenderMode:RCRTCVideoFillModeAspectFit];
    if (res != 0) {
    NSLog(@"setRenderMode failed:%@",@(res));
    }
  6. 销毁当前播放资源。App 用户可能需要切换到另一个主播的 CDN 流,或者切换到不同分辨率的 CDN 流地址时,此时必须先调用 destroy,再加载另一个资源 openWithUrl

    int res = [self.mediaPlayer destroy];
    if (res != 0) {
    NSLog(@"destroy failed:%@", @(res));
    }

播放控制

  • 在播放过程中可暂停。暂停后调用 play 方法可播放资源。

    int res = [self.mediaPlayer pause];
    if (res != 0) {
    NSLog(@"pause failed:%@",@(res));
    }
  • 设置播放音量。如不设置,使用默认值 100。

    ///播放器支持设置播放音量,以及取值当前音量,取值范围 [0,100],默认值 100。
    int res = [self.mediaPlayer setVolume:100];
    if (res != 0) {
    NSLog(@"setVolume failed:%@",@(res));
    }
  • 获取当前音量

    float volume = [self.mediaPlayer volume];

获取流信息数据

在加载资源后可以获取流信息数据 RCRTCMediaStreamInfo,例如音视频的帧率、码率、视频尺寸、音频采样率等。

NSArray *infos = [self.mediaPlayer getMediaStreamInfos];
NSLog(@"infos:%@", infos);

播放事件回调

通过设置 RCRTCMediaPlayerEventDelegate 代理,接收播放相关的事件。

  • 播放状态变更回调

    ///当播放器的状态发生转变时,会触发以下回调。
    - (void)onPlayer:(RCRTCMediaPlayer *)player didChangedState:(RCRTCPlaybackState)state {
    NSString *desc = nil;
    switch (state) {
    case RCRTCPlaybackStateIdle: {
    desc = @"didChangedState: Idle";
    }
    break;
    case RCRTCPlaybackStateLoading: {
    desc = @"didChangedState: Loading";
    }
    break;
    case RCRTCPlaybackStatePlayAble: {
    desc = @"didChangedState: PlayAble";
    }
    break;
    case RCRTCPlaybackStatePlaying: {
    desc = @"didChangedState: Playing";
    }
    break;
    case RCRTCPlaybackStatePausing: {
    desc = @"didChangedState: Pausing";
    }
    break;
    case RCRTCPlaybackStateError: {
    desc = @"didChangedState: Error";
    }
    break;
    default:
    break;
    }
    if (desc.length) {
    NSLog(@"%@",desc);
    }
    }
  • 视频分辨率变更通知

    提示

    接收到视频首帧时,会通知上层当前帧分辨率,后续在推流分辨率发生变化时,也会通过该回调通知到上层。开发者可以通过此通知来适当调整当前视图尺寸。

    - (void)onPlayer:(RCRTCMediaPlayer *)player didChangedVideoSize:(CGSize)size {
    NSLog(@"%@",[NSString stringWithFormat:@"didChangedVideoSize:%@",NSStringFromCGSize(size)]);
    }
  • 音频首帧渲染

    - (void)onFirstAudioFrameRenderWithPlayer:(RCRTCMediaPlayer *)player {
    NSLog(@"onFirstAudioFrameRenderWithPlayer");
    }
  • 视频首帧渲染

    - (void)onFirstVideoFrameRenderWithPlayer:(RCRTCMediaPlayer *)player {
    NSLog(@"onFirstVideoFrameRenderWithPlayer");
    }
  • 获取 SEI 数据

    提示
    • 播放器支持解析拉流资源的媒体增强补充信息(SEI)在接收到 SEI 数据时,会通过以下回调通知到上层。回调的频次和 SEI 帧率一致。
    • 配合融云的旁路推流,使用 RCRTCMediaPlayer 拉流时,默认会回调拉流房间内多个视频流的合流布局信息。回调频次以及数据结构与 RCRTCRoomEventDelegatedidReceiveLiveStreamSEI 方法相同。
    - (void)onPlayer:(RCRTCMediaPlayer *)player handleSEIData:(NSString *)data {
    NSLog(@"handleSEIData:%@",data);
    }
  • 发生错误通知

    提示

    当播放器在播放的过程中出现播放错误时,通过以下回调通知上层。开发者可以适当添加日志记录方便错误排查。

    - (void)onPlayer:(RCRTCMediaPlayer *)player didOccurError:(NSInteger)errCode {
    NSLog(@"errCode:%@", @(errCode));
    }

音视频数据回调

播放器提供音视频帧数据回调,需要用户在 openWithUrl 调用前设置 RCRTCMediaPlayerDataHandledataHandle 代理。调用播放时,播放器会上抛音视频帧。

获取视频帧数据

设置 dataHandle 代理时后,CDN 播放器不执行内部视频渲染。App 可自行处理视频渲染。

- (void)onPlayer:(RCRTCMediaPlayer *)player handleVideoFrame:(RCRTCMediaPlayerVideoFrame *)videoFrame;

RCRTCMediaPlayerVideoFrame 参数说明:

参数类型说明
pixelFormatTypeOSType当前视频帧编码格式 NV12 或者 i420
widthint视频的宽度(px)
heightint视频的高度(px)
yStrideint对 YUV 数据,表示 Y 缓冲区的行跨度
uStrideint对 YUV 数据,表示 U 缓冲区的行跨度
vStrideint对 YUV 数据,表示 V 缓冲区的行跨度
yBufferuint8_t *对 YUV 数据,表示 Y 缓冲区的指针
uBufferuint8_t *对 YUV 数据,表示 U 缓冲区的指针
vBufferuint8_t *对 YUV 数据,表示 V 缓冲区的指针
- (void)onPlayer:(RCRTCMediaPlayer *)player handleVideoFrame:(RCRTCMediaPlayerVideoFrame *)videoFrame {
// NV12
if (videoFrame.pixelFormatType == kCVPixelFormatType_420YpCbCr8BiPlanarFullRange) {
CVReturn result = noErr;
NSDictionary *pixelAttributes = @{(id)kCVPixelBufferIOSurfacePropertiesKey: @{}};
CVPixelBufferRef pixelBufferRef;
result = CVPixelBufferCreate(kCFAllocatorDefault,
videoFrame.width,
videoFrame.height,
kCVPixelFormatType_420YpCbCr8BiPlanarFullRange,
(__bridge CFDictionaryRef)pixelAttributes,
&pixelBufferRef);
if (result != noErr || !pixelBufferRef) {
NSLog(@"CVPixelBufferCreate failed:%@",@(result));
return;
}

{
CVPixelBufferLockBaseAddress(pixelBufferRef, 0);

void *yDestPlane = CVPixelBufferGetBaseAddressOfPlane(pixelBufferRef, 0);
void *uvDestPlane = CVPixelBufferGetBaseAddressOfPlane(pixelBufferRef, 1);
size_t heightY = CVPixelBufferGetHeightOfPlane(pixelBufferRef, 0);
size_t heightUV = CVPixelBufferGetHeightOfPlane(pixelBufferRef, 1);
size_t dstStrideY = CVPixelBufferGetBytesPerRowOfPlane(pixelBufferRef, 0);
size_t dstStrideUV = CVPixelBufferGetBytesPerRowOfPlane(pixelBufferRef, 1);
if (videoFrame.yBuffer) {
memcpy(yDestPlane, videoFrame.yBuffer, dstStrideY * heightY);
}
if (videoFrame.uBuffer) {
memcpy(uvDestPlane,videoFrame.uBuffer, dstStrideUV * heightUV);
}
CVPixelBufferUnlockBaseAddress(pixelBufferRef, 0);
}
// pixelBufferRef to do something

CVBufferRelease(pixelBufferRef);
}
}

获取音频帧数据

如需自行处理音频播放,可将 CDN 播放器的音量置为 0。

- (void)onPlayer:(RCRTCMediaPlayer *)player handleAudioFrame:(RCRTCMediaPlayerAudioFrame *)audioFrame;

RCRTCMediaPlayerAudioFrame 参数说明:

参数类型说明
sampleRateHzUInt32采样率
channelsUInt32声道数量(如果是立体声,数据是交叉的)
samplesPerChannelUInt32每个声道的采样点数
bytesPerSampleUInt32每个采样点的字节数: 对于 PCM 来说,一般使用 16 bit,即两个字节
buffervoid*缓存区数据起始地址
bufferSizeUInt32缓存区数据大小 bufferSize = samplesPerChannel × channels × bytesPerSample
renderTimeMsUInt64外部音频帧的渲染时间戳
- (void)onPlayer:(RCRTCMediaPlayer *)player handleAudioFrame:(RCRTCMediaPlayerAudioFrame *)audioFrame {
AudioBuffer buffer;
buffer.mData = audioFrame.buffer;
buffer.mDataByteSize = audioFrame.bufferSize;
buffer.mNumberChannels = audioFrame.channels;
AudioBufferList abl;
abl.mNumberBuffers = 1;
abl.mBuffers[0] = buffer;
// AudioBufferList to do something
}