在App开发中,实现付费视频功能是一个非常常见的需求,其中又会有很多用户希望能够提供30秒的试看时间,以便更好的决定是否进行购买。
下面我们来介绍一下实现这个功能的原理和方法。具体实现方式分为两种:客户端实现和服务器实现。
一、 客户端实现
1.客户端控制播放时间
客户端可以通过设置一个时间段,比如说30秒,然后在这个时间到达时暂停播放,等待用户操作去选择是否购买。
如下示例代码:
```
//设置一个30秒的定时器,时间到了暂停播放
CGRect videoViewFrame = CGRectMake(0, 0, self.view.frame.size.width, 200);
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithURL:[NSURL URLWithString:@"http://example.com/video.mp4"]];
AVPlayer *player = [[AVPlayer alloc] initWithPlayerItem: playerItem];
//设置定时器时间为30秒
[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(30, NSEC_PER_SEC) queue:nil usingBlock:^(CMTime time) {
//定时器时间到,暂停播放
[player pause];
}];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer: player];
playerLayer.frame = videoViewFrame;
[self.view.layer addSublayer: playerLayer];
[player play];
```
2.播放前缓存
在客户端播放视频之前,先缓存一定量的视频数据,同时控制播放进度和缓存进度的比例,当播放时间达到30秒时,则停止播放,并提示用户可以付费购买。此种方式需要做大量的本地缓存处理,对于流量比较高的视频来说显得有些低效。
如下示例代码:
```
//计算需要缓存的视频时长
NSUInteger duration = 30;
NSRange range = NSMakeRange(0, duration); //需要缓存的时长范围
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithURL: [NSURL URLWithString: @"http://example.com/video.mp4"]];
AVAsset *asset = playerItem.asset;
AVAssetResourceLoader *resourceLoader = asset.resourceLoader;
AVURLAsset *urlAsset = (AVURLAsset *)asset;
NSURLComponents *components = [NSURLComponents componentsWithURL: [urlAsset.URL absoluteURL] resolvingAgainstBaseURL: NO];
components.scheme = @"custom";
//获取缓存区域的数据并存储到本地文件中
AVAssetResourceLoader *resourceLoader = asset.resourceLoader;
[resourceLoader setDelegate: self queue: dispatch_get_main_queue()];
__weak typeof(self) weakSelf = self;
self.resourceLoaderDelegate = [ResourceLoaderDelegate new];
[self.resourceLoaderDelegate addCompletionHandler: ^BOOL(NSData *data, NSError *__autoreleasing *error) {
BOOL success = NO;
if (range.length > 0 && self.requestTask) {
if (resourceLoader.localCachePath.length > 0) {
success = YES;
}
}
return success;
} forRequestTask: self.requestTask];
//计算缓存时长
int length = 1;
NSURL *customSchemeURL = [components URL];
AVURLAsset *urlAsset = [[AVURLAsset alloc] initWithURL: customSchemeURL options: nil];
NSArray *keys = @[@"duration"];
[urlAsset loadValuesAsynchronouslyForKeys:keys completionHandler:^{
NSError *error = nil;
AVKeyValueStatus durationStatus = [urlAsset statusOfValueForKey:@"duration" error:&error];
switch (durationStatus) {
case AVKeyValueStatusLoaded:
dispatch_async(dispatch_get_global_queue(0, DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{
//1.计算需要缓存的数据字节
unsigned long long offset = range.location;
unsigned long long length = range.length;
//2.向服务端请求需要缓存的数据
NSData *requestData = [self sendRequestWithOffset: offset length: length];
if (requestData) {
//3.将请求来的数据存储到本地缓存文件中
BOOL result = [self writeData: requestData atOffset:offset];
if (result) {
//异步回调
dispatch_async(dispatch_get_main_queue(), ^{
[urlAsset.resourceLoader setDelegate:nil queue: NULL];
//创建播放器
weakSelf.playerItem = [[AVPlayerItem alloc] initWithURL: [NSURL URLWithString: @"http://example.com/video.mp4"]];
weakSelf.player = [AVPlayer playerWithPlayerItem:weakSelf.playerItem];
weakSelf.playerLayer = [AVPlayerLayer playerLayerWithPlayer:weakSelf.player];
weakSelf.playerLayer.frame = CGRectMake(0, 0, weakSelf.view.frame.size.width, 200);
[weakSelf.view.layer addSublayer: weakSelf.playerLayer];
[weakSelf.player play];
});
}
}
});
break;
default:
break;
}
}];
```
二、服务器实现
1.前端只需调用API
在服务器实现的方式中,前端只需调用服务端提供的API,即可在客户端以流的方式播放视频内容。在API中,可以设置视频内容试看的时间长度为30秒,超过30秒部分的内容需要付费才可以继续观看。
如下示例代码:
```
//前端根据用户ID和视频ID向API发起请求获取播放URL
NSString *userId = @"user-123456";
NSString *videoId = @"video-123456";
NSString *urlStr = [NSString stringWithFormat:@"api/v1/playUrl?userId=%@&videoId=%@", userId, videoId];
NSURLSession *session = [NSURLSession sharedSession];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlStr]];
[request setHTTPMethod:@"GET"];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(error) {
//请求出现错误,处理异常
}else {
//解析返回结果
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSString *playUrl = result[@"playUrl"];
//根据返回的URL展示视频播放器
AVPlayer *player = [[AVPlayer alloc] initWithURL:[NSURL URLWithString:playUrl]];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
playerLayer.frame = CGRectMake(0, 0, self.view.width, 200);
[self.view.layer addSublayer:playerLayer];
[player play];
}
}];
[task resume];
//api/v1/playUrl接口的实现,服务端根据用户ID和视频ID获取播放URL
public String getPlayUrl(String userId, String videoId) {
//1.根据用户ID和视频ID获取对应的视频信息
Video video = videoMapper.getVideoInfo(userId, videoId);
if(video == null) {
throw new BusinessException("该视频不存在!");
}
//2.检查该用户是否已经购买了该视频
boolean buyed = userService.checkUserBuyedVideo(userId, videoId);
if(buyed) {
//已购买,返回完整播放URL
return video.getUrl();
}else {
//未购买,返回试看URL(试看时间为30秒)
return video.getTrialUrl();
}
}
```
2.服务端自动截取前30秒
另外,服务端也可以通过自动截取视频前30秒,来实现视频试看功能。具体实现原理与上述两种方式类似。
如下示例代码:
```
//客户端请求服务端播放视频
NSString *userId = @"user-123456";
NSString *videoId = @"video-123456";
NSString *urlStr = [NSString stringWithFormat:@"api/v1/playUrl?userId=%@&videoId=%@", userId, videoId];
NSURLSession *session = [NSURLSession sharedSession];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlStr]];
[request setHTTPMethod:@"GET"];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if(error) {
//请求出现错误,处理异常
}else {
//解析返回结果
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
NSString *playUrl = result[@"playUrl"];
//根据返回的URL展示视频播放器
AVPlayer *player = [[AVPlayer alloc] initWithURL:[NSURL URLWithString:playUrl]];
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
playerLayer.frame = CGRectMake(0, 0, self.view.width, 200);
[self.view.layer addSublayer:playerLayer];
[player play];
}
}];
[task resume];
//api/v1/playUrl接口截取前30秒的实现
public String getPlayUrl(String userId, String videoId) {
//1.根据用户ID和视频ID获取对应的视频信息
Video video = videoMapper.getVideoInfo(userId, videoId);
if(video == null) {
throw new BusinessException("该视频不存在!");
}
//2.检查该用户是否已经购买了该视频
boolean buyed = userService.checkUserBuyedVideo(userId, videoId);
if(buyed) {
//已购买完整视频,返回完整播放URL
return video.getUrl();
}else {
//未购买,返回截取前30秒试看,并给出购买提示
//先获取完整视频URL
String fullVideoUrl = video.getUrl();
//调用接口截取视频前30秒,并获取新的URL
String trialUrl = videoService.cutVideoToUrl(fullVideoUrl, 0, 30);
return trialUrl;
}
}
//VideoService中截取视频前30秒的实现
public String cutVideoToUrl(String videoUrl, int startSecond, int endSecond) {
FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(videoUrl);
try {
grabber.start();
if(startSecond >= grabber.getLengthInTime()/1000000) {
throw new BusinessException("截取起始时间不能超过总时长!");
}
FFmpegFrameRecorder recorder = new FFmpegFrameRecorder("out.mp4", grabber.getImageWidth(), grabber.getImageHeight(), grabber.getAudioChannels());
recorder.setVideoCodec(avcodec.AV_CODEC_ID_MPEG4);
recorder.setFormat("mp4");
recorder.setFrameRate(grabber.getFrameRate());
recorder.setSampleRate(grabber.getSampleRate());
recorder.setAudioChannels(grabber.getAudioChannels());
recorder.start();
long start = startSecond * 1000000;
long end = endSecond * 1000000;
Frame frame = null;
while((frame = grabber.grab()) != null) {
if(frame.timestamp > start && frame.timestamp <= end) { //在截取的范围内
recorder.record(frame);
}
if(frame.timestamp > end) { //超出截取范围
break;
}
}
grabber.stop();
grabber.release();
recorder.stop();
recorder.release();
return "http://example.com/out.mp4";
} catch (Exception e) {
throw new BusinessException("截取视频失败!");
}
}
```
综上所述,客户端实现和服务端实现都可以实现给用户提供30秒试看的功能,实现的方式各不相同,具体实现方案应根据实际情况来选择。