You need to enable JavaScript to run this app.
导航
进阶功能
最近更新时间:2024.07.26 18:02:09首次发布时间:2022.01.21 18:42:59

本章节介绍了拉流 SDK 进阶功能的接入方式,支持的进阶功能包括但不限于 RTM 协议拉流、QUIC 协议拉流、使用 IP 地址拉流、使用主备流、多档位切换、自适应码率(ABR)拉流、截图、SEI、以及订阅视频和音频数据。您可以根据实际业务需求,借助拉流 SDK 实现更复杂的功能。

前提条件

注意事项

真机调试:由于 SDK 使用了大量 iOS 系统的音视频接口,这些接口在仿真模拟器下可能会出现异常,推荐您使用真机进行代码调试。

功能接入

本节为您详细介绍如何使用拉流 SDK 进阶功能的实现。包括但不限于 RTM 协议拉流、QUIC 协议拉流、DRM 加密流播放、使用 IP 地址拉流、使用主备流、多档位切换、自适应码率(ABR)拉流、截图、SEI、以及订阅视频和音频数据。

RTM 协议拉流

视频直播拉流 SDK 支持 RTM 协议拉流,该功能需要配合火山引擎视频直播服务使用。详细信息可参考超低延时直播介绍

接入准备

  • 确认已集成 1.37.1 及以上版本的互动版拉流 SDK。
  • 使用视频直播控制台的地址生成器,生成 RTM 和 FLV 拉流地址,其中,FLV 地址可作为 RTM 拉流失败时的自动降级地址。

接入说明

配置 RTM 拉流地址和 FLV 拉流地址进行播放。代码示例如下所示。

// 配置 RTM 地址
VeLivePlayerStream *playStreamRTM = [[VeLivePlayerStream alloc]init];
playStreamRTM.url = "https://pull.example.com/live/stream.sdp";
playStreamRTM.format = VeLivePlayerFormatRTM;

// 配置 FLV 地址
VeLivePlayerStream *playStreamFLV = [[VeLivePlayerStream alloc]init];
playStreamFLV.url = "https://pull.example.com/live/stream.flv";
playStreamFLV.format = VeLivePlayerFormatFLV;


// 创建 VeLivePlayerStreamData
 VeLivePlayerStreamData *streamData = [[VeLivePlayerStreamData alloc]init];
NSMutableArray <VeLivePlayerStream *> *mainStreams = [NSMutableArray arrayWithCapacity:1];

// 添加 RTM 流地址
[mainStreams addObject:playStreamRTM];

// 添加 FLV 流地址
[mainStreams addObject:playStreamFLV];

streamData.mainStream = mainStreams;

// 配置默认 format 和 protocol
streamData.defaultFormat = VeLivePlayerFormatRTM;
streamData.defaultProtocol = VeLivePlayerProtocolTLS;
    

// 配置播放源
[self.livePlayer setPlayStreamData:streamData];

// 开始播放
[self.livePlayer play];

QUIC 协议拉流

视频直播拉流 SDK 支持 QUIC 协议拉流,该功能需要配合火山引擎视频直播服务使用。

接入准备

  • 如果您使用的是在线集成方式,请在 subspecs 中添加 Quic,以启用 QUIC 功能。

    source 'https://github.com/volcengine/volcengine-specs.git' pod 'TTSDK', '1.37.1.8-premium', :subspecs => ['LivePull' 'Quic']
    
  • 使用视频直播控制台的地址生成器,生成 FLV 拉流地址。

接入说明

配置 QUIC 拉流地址进行播放。代码示例如下所示。

// 配置 FLV 地址
VeLivePlayerStream *playStreamFLV = [[VeLivePlayerStream alloc]init];
playStreamFLV.url = "https://pull.example.com/live/stream.flv";
playStreamFLV.format = VeLivePlayerFormatFLV;
playStreamFLV.protocol = VeLivePlayerProtocolQUIC;


// 创建 VeLivePlayerStreamData
 VeLivePlayerStreamData *streamData = [[VeLivePlayerStreamData alloc]init];
NSMutableArray <VeLivePlayerStream *> *mainStreams = [NSMutableArray arrayWithCapacity:1];

// 添加 FLV 流地址
[mainStreams addObject:playStreamFLV];

streamData.mainStream = mainStreams;

// 配置默认 format 和 protocol
streamData.defaultFormat = VeLivePlayerFormatFLV;
streamData.defaultProtocol = VeLivePlayerProtocolQUIC; // 协议为 QUIC
    

// 配置播放源
[self.livePlayer setPlayStreamData:streamData];

// 开始播放
[self.livePlayer play];

说明

拉流 SDK 默认开启了 QUIC 协议拉流的失败降级策略,无需额外配置。如果 QUIC 协议拉流失败,SDK 将自动降级为 TCP 协议拉流。

DRM 加密流播放

播放器支持 DRM 加密流播放。

  1. 参考最佳实践- 直播 DRM 加密获取以下信息:

    • 获取 M3U8 格式的 DRM 加密流播放地址;
    • 获取 DRM 授权许可文件;
    • 获取 FairPlay 证书文件。
  2. 使用 initWithType 方法初始化播放器,示例代码如下所示。

    self.livePlayer = [[TVLManager alloc] initWithType:VeLivePlayerTypeSystem];
    

    说明

    使用 initWithType 方法初始化播放器后,将不支持以下功能接入。

    • 使用除 M3U8 格式外的其他播放地址;
    • 使用主备流
    • 自适应码率拉流
    • 视频截图
    • 视频镜像
    • 自定义音视频渲染
    • 拉流超分
    • SEI 信息回调
  3. 配置加密流播放地址。

    NSString *playUrl = "http://pull.example.com/live/stream.m3u8";
    [self.livePlayer setPlayUrl:playUrl];
    
  4. 注册 observer 后,通过以下回调方法返回 Fairplay 证书文件和 DRM 授权许可 URL。

    - (NSData *)getDrmResourceLoaderCertificateData:(TVLManager *)player { 
        // 传入 Fairplay 证书的路径
        NSString *filePath = [[NSBundle mainBundle] pathForResource:@"testfairplay" ofType:@"cer"]; 
        NSData *certificate = [NSData dataWithContentsOfFile:filePath]; 
        return certificate; 
    } 
     
    - (NSString *)getDrmResourceLoaderLicenseUrl:(TVLManager *)player { 
        // 返回 DRM 许可证书地址 URL 
        return @"https://drmtest/license"; 
    }
    

使用 IP 地址拉流

播放器支持通过设置播放域名的 IP 地址进行拉流,以降低播放首帧时间。

接入准备

请先获取直播播放地址并解析出 IP 地址。

接入说明

使用播放器的 setUrlHostIP 接口将 IP 地址与域名关联起来,播放器将直接根据 IP 地址进行拉流播放。代码示例如下所示。

// 配置播放 URL
[self.livePlayer setPlayUrl:@"https://pull.example.com/live/stream.flv"];

// 添加 IP 地址和域名关联
NSMutableArray<NSString *> *ipList = [[NSMutableArray alloc] initWithCapacity:1];
[ipList addObject:@"xxx.xxx.xxx.xxx"];  // IP 地址

NSMutableDictionary* ipHost = [[NSMutableDictionary alloc] initWithCapacity:1];
[ipHost setObject:ipList forKey:@"pull.example.com"];

// 配置 IP 地址
[self.livePlayer setUrlHostIP:ipHost];

// 开始播放
[self.livePlayer play];  
    

使用主备流

主备流主要用于直播间容灾,通过配置主备两路直播流地址,在推流和分发环节使用主备流进行直播。播放器在接入时配置主备两路拉流地址,当主路地址拉流失败或者播放出错时,播放器会自动切换到备路地址进行播放。同样地,当备路地址播放出错时,播放器会切换回主路地址进行播放。主备地址可以来自同一直播服务商,也可以来自不同的直播服务商。

接入准备

获取主流地址和备流地址,如果使用火山引擎视频直播服务,您可通过视频直播控制台的地址生成器,生成主备拉流地址。

接入说明

  1. 配置主备流地址进行播放。代码示例如下所示。
// 配置主流地址
VeLivePlayerStream *playStreamMain = [[VeLivePlayerStream alloc]init];
playStreamMain.url = @"https://pull.example.com/live/主.flv";
playStreamMain.type = VeLivePlayerStreamTypeMain; // 流类型配置为主路
    
// 配置备流地址
VeLivePlayerStream *playStreamBackup = [[VeLivePlayerStream alloc]init];
playStreamBackup.url = @"https://pull.example.com/live/备.flv";
playStreamBackup.type = VeLivePlayerStreamTypeBackup; // 流类型配置为备路
    
// 创建播放源
VeLivePlayerStreamData *streamData = [[VeLivePlayerStreamData alloc]init];
    
// 开启主备切换
streamData.enableMainBackupSwitch = YES;
    
// 添加主流
NSMutableArray <VeLivePlayerStream *> *mainStreams = [NSMutableArray arrayWithCapacity:1];
[mainStreams addObject:playStreamMain];
streamData.mainStream = mainStreams;
    
// 添加备流
NSMutableArray <VeLivePlayerStream *> *backupStreams = [NSMutableArray arrayWithCapacity:1];
[backupStreams addObject:playStreamBackup];
streamData.backupStream = backupStreams;
    
// 配置播放源
[self.livePlayer setPlayStreamData:streamData];
    
// 开始播放
[self.livePlayer play];

  1. 当播放器内部发生主备切换时,会通过 VeLivePlayerObserver 的回调接口 onMainBackupSwitch 进行通知。代码示例如下所示。
- (void)onMainBackupSwitch:(TVLManager *)player streamType:(VeLivePlayerStreamType)streamType error:(VeLivePlayerError *)error {
    // 主备流切换通知
}

多档位切换

播放器支持配置多档位的直播拉流地址,通过接口可以实现多档位直播流的切换。

接入准备

获取源流和各档位转码流拉流地址。如您使用了火山引擎视频直播服务,请先登录控制台完成转码配置,再获取拉流地址,地址方法如下所示。

本文的接入说明以下列拉流地址为例。

档位拉流地址
源流(Orgin)https://pull.example.com/live/123456.flv
超清(UHD)https://pull.example.com/live/123456_uhd.flv
高清(HD)https://pull.example.com/live/123456_hd.flv
标清(SD)https://pull.example.com/live/123456_sd.flv
低清(LD)https://pull.example.com/live/123456_ld.flv

接入说明

  1. 配置多档位地址进行播放。代码示例如下所示。
// 配置多档位流地址
VeLivePlayerStream *playStreamOrgin = [[VeLivePlayerStream alloc]init];
playStreamOrgin.url = @"https://pull.example.com/live/123456.flv";
playStreamOrgin.resolution = VeLivePlayerResolutionOrigin; // 原始档位
    
VeLivePlayerStream *playStreamUHD = [[VeLivePlayerStream alloc]init];
playStreamUHD.url = @"https://pull.example.com/live/123456_uhd.flv";
playStreamUHD.resolution = VeLivePlayerResolutionUHD; // UHD 档位
    
VeLivePlayerStream *playStreamHD = [[VeLivePlayerStream alloc]init];
playStreamHD.url = @"https://pull.example.com/live/123456_hd.flv";
playStreamHD.resolution = VeLivePlayerResolutionHD; // HD 档位
    
VeLivePlayerStream *playStreamSD = [[VeLivePlayerStream alloc]init];
playStreamSD.url = @"https://pull.example.com/live/123456_sd.flv";
playStreamSD.resolution = VeLivePlayerResolutionSD; // SD 档位
    
VeLivePlayerStream *playStreamLD = [[VeLivePlayerStream alloc]init];
playStreamLD.url = @"https://pull.example.com/live/123456_ld.flv";
playStreamLD.resolution = VeLivePlayerResolutionLD; // LD 档位
    
// 创建播放源
VeLivePlayerStreamData *streamData = [[VeLivePlayerStreamData alloc]init];
    
// 添加多档位流
NSMutableArray <VeLivePlayerStream *> *mainStreams = [NSMutableArray arrayWithCapacity:1];
[mainStreams addObject:playStreamOrgin];
[mainStreams addObject:playStreamUHD];
[mainStreams addObject:playStreamHD];
[mainStreams addObject:playStreamSD];
[mainStreams addObject:playStreamLD];
streamData.mainStream = mainStreams;
    
// 配置默认启播档位
streamData.defaultResolution = VeLivePlayerResolutionOrigin;
    
// 配置播放源
[self.livePlayer setPlayStreamData:streamData];
    
// 开始播放
[self.livePlayer play];

  1. 通过调用播放器的 switchResolution 接口可以实现多个档位之间的手动切换。代码示例如下所示。
[self.livePlayer switchResolution:VeLivePlayerResolutionUHD]; // 切换到 UHD 档位

  1. 当档位切换成功时,会通过 VeLivePlayerObserver 的回调接口 onResolutionSwitch 进行回调。代码示例如下所示。
- (void)onResolutionSwitch:(TVLManager *)player resolution:(VeLivePlayerResolution)resolution error:(VeLivePlayerError *)error reason:(VeLivePlayerResolutionSwitchReason)reason  {
	// 档位切换回调
}

自适应码率(ABR)拉流

自适应码率(Adaptive Bit-Rate,简称 ABR)是一种流媒体传输技术,通过一系列算法策略,动态切换不同档位媒体流,以达到适应网络带宽变化,防止观众在观看直播过程产生卡顿,提升播放质量和观看体验。

注意

ABR 功能只适用于 FLV 格式流。

接入准备

获取源流和各档位转码流拉流地址。如您使用了火山引擎视频直播服务,请先登录控制台完成转码配置,再获取拉流地址,地址方法如下所示。

本文的接入说明以下列拉流地址为例。

档位说明拉流地址码率(kbps)
源流(Orgin)https://pull.example.com/live/123456.flv2500
超清(UHD)https://pull.example.com/live/123456_uhd.flv2500
高清(HD)https://pull.example.com/live/123456_hd.flv1000
标清(SD)https://pull.example.com/live/123456_sd.flv800
低清(LD)https://pull.example.com/live/123456_ld.flv500

注意

ABR 功能需要配置多档位拉流地址时,SDK 中每个档位的编码码率需要与转码配置时填写的编码码率一致,配置方法可参考转码配置文档。

接入说明

  1. 配置多档位地址进行播放。代码示例如下所示。
// 配置多档位流地址
VeLivePlayerStream *playStreamOrgin = [[VeLivePlayerStream alloc]init];
playStreamOrgin.url = @"https://pull.example.com/live/123456.flv";
playStreamOrgin.resolution = VeLivePlayerResolutionOrigin; // 原始档位
playStreamOrgin.bitrate = 2500;
    
VeLivePlayerStream *playStreamUHD = [[VeLivePlayerStream alloc]init];
playStreamUHD.url = @"https://pull.example.com/live/123456_uhd.flv";
playStreamUHD.resolution = VeLivePlayerResolutionUHD; // UHD 档位
playStreamUHD.bitrate = 2500;
    
VeLivePlayerStream *playStreamHD = [[VeLivePlayerStream alloc]init];
playStreamHD.url = @"https://pull.example.com/live/123456_hd.flv";
playStreamHD.resolution = VeLivePlayerResolutionHD; // HD 档位
playStreamHD.bitrate = 1000;
    
VeLivePlayerStream *playStreamSD = [[VeLivePlayerStream alloc]init];
playStreamSD.url = @"https://pull.example.com/live/123456_sd.flv";
playStreamSD.resolution = VeLivePlayerResolutionSD; // SD 档位
playStreamSD.bitrate = 800;
    
VeLivePlayerStream *playStreamLD = [[VeLivePlayerStream alloc]init];
playStreamLD.url = @"https://pull.example.com/live/123456_ld.flv";
playStreamLD.resolution = VeLivePlayerResolutionLD; // LD 档位
playStreamLD.bitrate = 500;
    
// 创建播放源
VeLivePlayerStreamData *streamData = [[VeLivePlayerStreamData alloc]init];
    
// 添加多档位流
NSMutableArray <VeLivePlayerStream *> *mainStreams = [NSMutableArray arrayWithCapacity:1];
[mainStreams addObject:playStreamOrgin];
[mainStreams addObject:playStreamUHD];
[mainStreams addObject:playStreamHD];
[mainStreams addObject:playStreamSD];
[mainStreams addObject:playStreamLD];
streamData.mainStream = mainStreams;
    
// 配置默认启播档位
streamData.defaultResolution = VeLivePlayerResolutionOrigin;

// 开启 ABR
streamData.enableABR = YES;
    
// 配置播放源
[self.livePlayer setPlayStreamData:streamData];
    
// 开始播放
[self.livePlayer play];

  1. ABR 档位自动切换时,会通过 VeLivePlayerObserver 的接口 onResolutionSwitch 进行回调。代码示例如下所示。
- (void)onResolutionSwitch:(TVLManager *)player resolution:(VeLivePlayerResolution)resolution error:(VeLivePlayerError *)error reason:(VeLivePlayerResolutionSwitchReason)reason  {
	// 档位切换回调
}

截图

截图功能允许您在播放器中截取当前直播画面并生成一张图片。

接入说明

  1. 通过调用播放器的 snapshot 接口可以实现截图功能。截图仅在播放器播放成功后可以生效。代码示例如下所示。
int result = [self.livePlayer snapshot];
  1. 截图成功后,会通过播放器 VeLivePlayerObserver 的回调接口 onSnapshotComplete 进行回调,同时将截取的图片数据传递给回调方法。代码示例如下所示。
- (void)onSnapshotComplete:(TVLManager *)player image:(UIImage *)image  {
	// 截图成功回调,参数 image 为截图的图片数据
}

SEI

SEI(Supplemental Enhancement Information)是直播流中的附加信息,它可以用于传递自定义的数据或元数据。

接入说明

  1. 在调用 play 方法之前,您可以通过配置播放器的 enableSei 属性来开启 SEI 信息的接收功能。代码示例如下所示。
// 创建播放器初始化配置
VeLivePlayerConfiguration *config = [[VeLivePlayerConfiguration alloc]init];

// 打开 SEI 开关
config.enableSei = YES;
        
// 初始化配置
[self.livePlayer setConfig:config];
  1. 当直播流中有 SEI 信息时,播放器会通过 VeLivePlayerObserver 的回调接口 onReceiveSeiMessage 进行回调,并将接收到的 SEI 信息传递给回调方法。代码示例如下所示。
- (void)onReceiveSeiMessage:(TVLManager *)player message:(NSString*)message  {
	// SEI 回调
}

订阅视频数据

通过订阅解码后的视频数据,您可以获取视频帧数据并进行自定义的处理和渲染操作。播放器支持多种视频帧数据的回调,并提供了相应的参数和格式选项。

接入准备

视频帧像素格式和视频数据封装格式的枚举和含义如下表所示。

格式类型枚举说明

视频帧像素格式
VeLivePlayerPixelFormat

VeLivePlayerPixelFormatNV12

NV12

VeLivePlayerPixelFormatI420I420

视频数据封装格式
VeLivePlayerVideoBufferType

VeLivePlayerVideoBufferTypePixelBuffer

CVPixelBufferRef

VeLivePlayerVideoBufferTypeSampleBufferCVSampleBufferRef
VeLivePlayerVideoBufferTypeNSDataNSData

配置订阅视频数据前,请您根据需要选择格式组合。格式组合需要遵循下表中提供的 VeLivePlayerPixelFormat 和 VeLivePlayerVideoBufferType 的对应关系。

像素格式/封装格式VeLivePlayerPixelFormatNV12VeLivePlayerPixelFormatI420
VeLivePlayerVideoBufferTypePixelBuffer√(性能最优)×
VeLivePlayerVideoBufferTypeSampleBuffer×
VeLivePlayerVideoBufferTypeNSData

接入说明

  1. 您可以调用播放器的 enableVideoFrameObserver 接口开启或关闭视频帧数据订阅功能。接口参数说明如下表所示。
参数类型说明
enableBOOL是否开启视频帧回调。
pixelFormatVeLivePlayerPixelFormat视频帧像素格式。
bufferTypeVeLivePlayerVideoBufferType视频数据封装格式。

代码示例如下所示。

// 
[self.livePlayer enableVideoFrameObserver:YES pixelFormat:VeLivePlayerPixelFormatNV12 bufferType:VeLivePlayerVideoBufferTypePixelBuffer];
  1. 解码后的视频帧数据会通过播放器 VeLivePlayerObserver 的回调接口 onRenderVideoFrame 进行回调。代码示例如下所示。
- (void)onRenderVideoFrame:(TVLManager *)player videoFrame:(VeLivePlayerVideoFrame *)videoFrame  {
	// 视频帧数据回调
}

VeLivePlayerVideoFrame 参数说明如下表所示。

参数类型说明
bufferTypeVeLivePlayerVideoBufferType视频帧数据封装格式
pixelFormatVeLivePlayerPixelFormat视频帧像素格式
widthNSInteger视频帧的宽度,单位为 px
heightNSInteger视频帧的高度,单位为 px
ptsint64_t视频帧的渲染时间戳,单位为 ms
pixelBufferCVPixelBufferRefbufferTypeVeLivePlayerVideoBufferTypePixelBuffer 时的视频数据
sampleBufferCMSampleBufferRefbufferTypeVeLivePlayerVideoBufferTypeSampleBuffer 时的视频数据
dataNSData*bufferTypeVeLivePlayerVideoBufferTypeNSData 时的视频数据

订阅音频数据

通过订阅解码后的音频数据,您可以获取音频帧数据并进行自定义的处理和渲染操作。

播放器支持音频帧数据帧回调格式为 PCM Float32 类型,数据封装格式为 NSData。

VeLivePlayerAudioBufferType音频数据封装格式
VeLivePlayerAudioBufferTypeNSDataNSData 格式

接入说明

  1. 您可以调用播放器的 enableAudioFrameObserver 接口开启或关闭音频帧数据订阅功能。接口参数说明如下表所示。
参数类型说明
enableBOOL是否开启音频帧回调。
enableRenderingBOOL是否开启播放器渲染。当不需要播放器内部进行音频播放时,可以将该参数值配置为 NO 。

代码示例如下所示。

// 订阅音频帧数据,打开播放器内部音频渲染
[self.livePlayer enableAudioFrameObserver:YES enableRendering:YES];
  1. 解码后的视频帧数据会通过播放器 VeLivePlayerObserver 的 onRenderAudioFrame 接口进行回调。代码示例如下所示。
- (void)onRenderAudioFrame:(TVLManager *)player audioFrame:(VeLivePlayerAudioFrame *)audioFrame  {
	// 音频帧数据回调
}

VeLivePlayerAudioFrame 参数说明如下表所示。

参数数据类型说明
bufferTypeVeLivePlayerAudioBufferType音频帧数据封装格式
sampleRateint音频采样率,单位为 Hz
channelsint声道数
bitDepthint音频位深度
ptsCMTime音频渲染时间戳,单位为 ms
dataNSData*PCM 音频数据
samplesint音频采样点个数

拉流超分

超分,即超分辨率技术(Super-Resolution, SR)是指从观测到的低分辨率图像重建出相应的高分辨率图像的过程,移动端实时超分,是指利用算法技术在端上对低分辨率的帧进行实时重建,产生高分辨率的帧显示在屏幕上,从而改善视频内容的细节与对比度,全面提升视频的播放清晰度和观看体验的优化手段。

接入说明

  1. 联系技术支持开通拉流超分功能。
  2. 调用播放器的 setEnableSuperResolution:(BOOL)enable 方法开启或者关闭拉流超分功能,默认状态为关闭。
参数类型说明
enableBOOL是否开启超分功能。
  1. 开启失败后,会通过 VeLivePlayerObserver 中的 onStreamFailedOpenSuperResolution 进行回调。代理方法签名如下所示。
- (void)onStreamFailedOpenSuperResolution:(TVLManager *_Nonnull)player error:(VeLivePlayerError *_Nullable)error;

如果开启超分失败,可能有以下几种原因。

  • 流的实际分辨率大于超分支持的最高分辨率。
  • 流的帧率大于超分支持的最高帧率。
  • 当前机型不支持超分。

域名解析

直播拉流 SDK 支持调用火山引擎 HTTPDNS 服务对拉流地址的预解析,以此来降低播放首帧时长。

接入说明

  1. 开通云解析 DNS 服务

  2. 登录移动解析 HTTPDNS 控制台

  3. 左侧导航栏选择实例信息,记录您的鉴权密钥,包含 SERVICE ID 和 SECRET KEY。

  4. 在拉流客户端调用 setVeHttpDNSAuth 方法配置调用 HTTPDNS 服务需要使用的鉴权密钥,代码示例如下所示。

    [VeLiveCommon setVeHttpDNSAuth:@"http_dns_serviceID" key:@"http_dns_key"];
    
  5. 调用 prefetchDomains 方法进行域名解析,解析成功后的 IP 地址会自动设置给播放器,代码示例如下所示。

    [VeLiveCommon prefetchDomains:@[@"xxx.pull.com", @"yyy.pull.com"]];