iOS 录制的的视频是mov格式 如果直接上传,安卓端获取无法播放,因为安卓和pc都不支持mov
所以作为iOS端,需要视频转码,转成MP4格式并且压缩上传
iOS端苹果已经提供了视频压缩以及转换格式的系统类AVAssetExportSession 可以用一下方法实现视频压缩转码
1,先把视频写入本地 自己设置本地路径 ,本地路径最好是时间戳 (精确到毫秒)加时间唯一标示
这样做防止路径重名 ,并且添加后缀 .mp4
//保存至沙盒路径
NSString *pathDocuments = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *videoPath = [NSString stringWithFormat:@“%@/%@”, pathDocuments,.MP4];
把视频转为二进数据制流
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"www.baidu.com"]];
把数据流写入本地沙河
[data writeToFile: videoPath atomically:NO];
//转码配置
AVURLAsset *asset = [AVURLAsset URLAssetWithURL: [NSURL fileURLWithPath: videoPath] options:nil];
AVAssetExportSession *exportSession= [[AVAssetExportSession alloc] initWithAsset:asset presetName:AVAssetExportPresetMediumQuality];
exportSession.shouldOptimizeForNetworkUse = YES;
exportSession.outputURL = [NSURL fileURLWithPath:model.sandboxPath];
//AVFileTypeMPEG4 文件输出类型,可以更改,是枚举类型,官方有提供,更改该值也可以改变视频的压缩比例
exportSession.outputFileType = AVFileTypeMPEG4;
[exportSession exportAsynchronouslyWithCompletionHandler:^{
int exportStatus = exportSession.status;
NSLog(@"%d",exportStatus);
switch (exportStatus)
{
case AVAssetExportSessionStatusFailed:
{
NSLog(@"视频转码失败”);
NSError *exportError = exportSession.error;
NSLog (@"AVAssetExportSessionStatusFailed: %@", exportError);
break;
}
case AVAssetExportSessionStatusCompleted:
{
NSLog(@"视频转码成功");
在这个地方上传视频
[self UploadVideo:outputURL]; //注意上传成功后,要清掉本地视频
}
}
}];
-(void)UploadVideo:(NSURL*)URL{
NSData*data = [NSDatadataWithContentsOfURL:URL];
MKNetworkEngine*engine = [[MKNetworkEnginealloc]initWithHostName:@"www.ylhuakai.com"customHeaderFields:nil];
NSMutableDictionary*dic = [[NSMutableDictionaryalloc]init];
NSString*updateURL;
updateURL =@"/alflower/Data/sendupdate";
[dicsetValue:[NSStringstringWithFormat:@"%@",User_id]forKey:@"openid"];
[dicsetValue:[NSStringstringWithFormat:@"%@",[self.webobjectForKey:@"web_id"]]forKey:@"web_id"];
[dicsetValue:[NSStringstringWithFormat:@"%i",insertnumber]forKey:@"number"];
[dicsetValue:[NSStringstringWithFormat:@"%i",insertType]forKey:@"type"];
MKNetworkOperation*op = [engineoperationWithPath:updateURLparams:dichttpMethod:@"POST"];
[opaddData:dataforKey:@"video"mimeType:@"video/mpeg"fileName:@"aa.mp4"];
[opaddCompletionHandler:^(MKNetworkOperation*operation) {
NSLog(@"[operation responseData]-->>%@", [operationresponseString]);
NSData*data = [operationresponseData];
NSDictionary*resweiboDict = [NSJSONSerializationJSONObjectWithData:dataoptions:NSJSONReadingAllowFragmentserror:nil];
NSString*status = [[resweiboDictobjectForKey:@"status"]stringValue];
NSLog(@"addfriendlist status is %@", status);
NSString*info = [resweiboDictobjectForKey:@"info"];
NSLog(@"addfriendlist info is %@", info);
// [MyTools showTipsWithView:nil message:info];
// [SVProgressHUD showErrorWithStatus:info];
if([statusisEqualToString:@"1"])
{
[[NSNotificationCenterdefaultCenter]postNotificationName:@"refreshwebpages"object:niluserInfo:nil];
[[NSFileManagerdefaultManager]removeItemAtPath:[URL path]error:nil];//上传之后就删除,以免占用手机硬盘空间;
}else
{
//[SVProgressHUD showErrorWithStatus:dic[@"info"]];
}
// [[NSNotificationCenter defaultCenter] postNotificationName:@"StoryData" object:nil userInfo:nil];
}errorHandler:^(MKNetworkOperation*errorOp, NSError* err) {
NSLog(@"MKNetwork request error : %@", [errlocalizedDescription]);
}];
[engineenqueueOperation:op];
}
2视频播放
实现边下载边播放的方法
<1>,使用AVPlayer的方法开启下载服务
AVURLAsset*urlAsset=[[AVURLAssetalloc]initWithURL:url options:nil];
AVPlayerItem*item=[AVPlayerItemplayerItemWithAsset:urlAsset];
[self.avPlayer replaceCurrentItemWithPlayerItem:item];
[self addObserverToPlayerItem:item];
视频加载状态通过KVO监听AVPlayerItem的status属性来获得。当AVPlayerItem的status属性为AVPlayerStatusReadyToPlay时,表明视频加载完成。
视频缓冲状态同样,通过KVO监听AVPlayerItem的loadedTimeRanges属性来获得。视频每缓冲一部分这个属性数据就会被更新,当loadedTimeRanges的值改变时可以获得本次缓冲加载的视频范围,包含起始时间、本次加载时长),这样一来就可以实时获得缓冲情况。
播放进度状态通过AVPlayer的- (id)addPeriodicTimeObserverForInterval:(CMTime)interval queue:(dispatch_queue_t)queue usingBlock:(void (^)(CMTime time))block方法获得播放进度,这个方法会在设定的时间间隔内定时更新播放进度。
<2>AVAssetResourceLoader
当视频源为mp4、mkv这种单一格式时,且播放器使用了AVPlayer,用该方案较容易。其大概原理是将AVPlayer的URL请求协议替换成自定义(系统无法识别)协议,这样就会触发AVAssetResourceLoader的协议–手动干预此次请求过程,这样也变可以拿到完整的请求数据,从而实现缓存。
有些可能是使用NSURLSession。NSURLConnection和NSURLSession的区别在这里不做讨论,不论是哪种方式,会发现一些共同点:如果按系统默认的实现,对于部分文件可以完美的实现边下边播,而对于某些文件可能必须等完全下载好后才能播放,这与AVPlayer底层的缓存机制有关。
我们无法对AVPlayer的缓存策略进行修改,但是可以通过干预其http请求从而曲线改变AVPlayer的缓存策略。采用的方式就是分段请求:给http请求设置Range,每次请求指定的部分,数据请求完成后抛给播放器播放,这样就可以完美的实现边下边播。
首先设置一个Range为0~1的请求,这个请求的目的只是为了让服务器响应,并且通过httpResponse里的Content Range拿到视频的大小,从而对接下来的分段请求进行分割。接下来就会发现会反复调用AVAssetResourceLoader的协议,同时会有许多loadingRequest进行请求,这每一个请求便对应着一个数据块,根据loadingRequest的requestedOffset以及当前视频下载的进度就可以计算出还有多少数据没有下载完,当数据下载完成时,便完成了本次缓存。
注意
一定注意不要通过请求是否完成的这个方法来判断缓存是否完成,准备来说是当请求完成的回调方法触发时并不能代表缓存成功,因为有多个请求发出,所以需要在请求完成时校验拿到的数据量是否等于第一次请求服务器返回的数据量。
通过自定义scheme来创建avplayer,并给AVURLAsset指定代理(SUPlayer对象)
AVURLAsset * asset = [AVURLAsset URLAssetWithURL:[self.url customSchemeURL] options:nil];
[asset.resourceLoader setDelegate:self.resourceLoader queue:dispatch_get_main_queue()];//指定代理
self.currentItem = [AVPlayerItem playerItemWithAsset:asset];
self.player = [AVPlayer playerWithPlayerItem:self.currentItem];
给AVURLAsset指定代理后实现代理方法,通过代理方法实现边下载边播放
- (BOOL)resourceLoader:(AVAssetResourceLoader *)resourceLoader shouldWaitForLoadingOfRequestedResource:(AVAssetResourceLoadingRequest *)loadingRequest {
[self addLoadingRequest:loadingRequest];
return YES;
}
- (void)resourceLoader:(AVAssetResourceLoader *)resourceLoader didCancelLoadingRequest:(AVAssetResourceLoadingRequest *)loadingRequest {
[self removeLoadingRequest:loadingRequest];
}
对loadingRequest的处理(addLoadingRequest方法)
(1)将其加入到requestList中
[self.requestList addObject:loadingRequest];
(2)如果还没开始下载,则开始请求数据,否则静待数据的下载
[self newTaskWithLoadingRequest:loadingRequest cache:YES];
(3)如果是seek之后的loadingRequest,判断请求开始的位置,如果已经缓冲到,则直接读取数据
if (loadingRequest.dataRequest.requestedOffset >= self.requestTask.requestOffset &&
loadingRequest.dataRequest.requestedOffset <= self.requestTask.requestOffset + self.requestTask.cacheLength) {
[self processRequestList];
}
(4)如果没有缓存重新下载
if (self.seekRequired) {
[self newTaskWithLoadingRequest:loadingRequest cache:NO];
}