文章目录
- 阿里云视频点播文件上传-iOS
- 一、上传方式
- 方式一 上传地址加凭证上传
- 1、请求AppServer
- 2、在start的回调中设置上传地址和上传凭证
- 3、uploadAuth过期重新设置
- 4、上传图片和上传视频
- 方式二 STS方式上传
- 1、请求STS
- 2、初始化上传对象
- 3、回调设置
- 4、添加上传文件进入上传列表,支持视频文件和图片文件的上传
- 5、启动上传
- 6、回调处理
- 二、注意事项
- 三、错误和异常处理
- 1、` Undefined symbols for architecture x86_64 `
- 2、`[VODUploadClient initMultiUpload]_block_invoke`
- 3、`failed code = (null), error message = (null)`
- 4、`Undefined symbols:_CMTimeGetSeconds`
- 5、`Forbidden.RAM`
- 四、实现代码
- STS上传方式源码
- 上传地址+凭证方式源码
阿里云视频点播文件上传-iOS
写在前面:
使用前需先开通视频点播权限,见文档,否则Api访问无效,上传不上去的。
如果只是单纯的上传文件,而不是点播文件,使用简单上传就可以了,视频点播上传是针对点播服务的。刚开始不太清楚,搞了半天视频点播上传,发现Api访问无权限,后来才看到需要开通服务。
一、上传方式
1、上传地址加凭证
2、STS方式上传
方式一 上传地址加凭证上传
这是阿里官方推荐使用的上传方式
与STS方式流程基本一致,只有4点不同的地方
1、请求AppServer
请求AppServer的时候需要让server返回uploadAddress(上传地址)和uploadAuth(上传凭证)
2、在start的回调中设置上传地址和上传凭证
OnUploadStartedListener UploadStartedCallbackFunc = ^(UploadFileInfo* fileInfo) {
NSLog(@"upload upload started callback.");
// 设置上传地址 和 上传凭证
[weakSelf.uploader setUploadAuthAndAddress:fileInfo uploadAuth:`upload auth` uploadAddress:`upload address`];
};
3、uploadAuth过期重新设置
OnUploadTokenExpiredListener TokenExpiredCallbackFunc = ^{
NSLog(@"upload token expired callback.");
// token过期,设置新的上传凭证,继续上传
[weakSelf.uploader resumeWithAuth:`new upload auth`];
};
4、上传图片和上传视频
STS上传视频和地址,请求AppServer返回都是key,secreatKey,token和过期时间,就可以上传视频和图片资源。
上传地址+凭证的上传方式注意:
- 客户端上传视频:需要请求向AppServer发送请求,AppServer通过OpenApi向阿里云点播服务发送CreateUploadVideo请求。请求成功将返回上传地址,上传凭证以及VideoId,AppServer需要将结果返回给客户端。
- 客户端上传图片:需要请求向AppServer发送请求,AppServer通过OpenApi向阿里云点播服务发送CreateUploadImage请求。请求成功将返回上传地址,上传凭证以及ImageURL,AppServer需要将结果返回给客户端。
方式二 STS方式上传
不推荐使用,可能会被废弃
1、请求STS
向自己的AppServer请求相关的AccessKeyId、AccessKeySecret、SecurityToken等信息。
2、初始化上传对象
初始化VODUploadClient对象,并调用以下方法设置STS授权信息
- (BOOL) init:(NSString *)accessKeyId
accessKeySecret:(NSString *)accessKeySecret
secretToken:(NSString *)secretToken
expireTime:(NSString *)expireTime
listener:(VODUploadListener *) listener
STS方式调用init: accessKeySecret: secretToken: expireTime: listener:方法初始化,初始化参数即是第一步请求获取的临时STS凭证。
想不明白起名为什么要起init…,最新版的SDK里Api有所改动,这个方法改成了setAccessKeyId…。
3、回调设置
在初始化uploader对象的时候,顺便设置事件回调。
4、添加上传文件进入上传列表,支持视频文件和图片文件的上传
- (BOOL)addFile:(NSString *)filePath
vodInfo:(VodInfo *)vodInfo;
5、启动上传
- (BOOL)start;
6、回调处理
略…
二、注意事项
- 1.5.3版本的readme里面,推荐使用上传凭证的方式上传
- STS上传方式可能会被废弃
- 使用VODUpload.framework的话需要后台开通视频点播服务权限,否则接口访问受限
三、错误和异常处理
1、Undefined symbols for architecture x86_64
未添加依赖库,看Demo里有添加libresolv.tbd,libresolv.9.tbd、SystermConfigration.framework。
2、[VODUploadClient initMultiUpload]_block_invoke
初始化问题,未调用init方法设置STS授权信息。
3、failed code = (null), error message = (null)
报错如下
2019-10-21 11:09:02.791887+0800 UploadDemo[5606:1449184] upload started . 2019-10-21 11:09:02.802116+0800 UploadDemo[5606:1449184] failed code = (null), error message = (null) 2019-10-21 11:09:02.802214+0800 UploadDemo[5606:1449184] state 5
不知道怎么处理,更新最新的SDK吧,旧的SDK是有这个问题。
4、Undefined symbols:_CMTimeGetSeconds
报错如下
Undefined symbols for architecture arm64: "_CMTimeRangeGetEnd", referenced from: +[AVCVAssetInfo AVCVVideoDuration:] in VODUpload(AVCVAssetInfo.o) +[AVCVAssetInfo AVCVAudioDuration:] in VODUpload(AVCVAssetInfo.o) "_CMTimeGetSeconds", referenced from: +[AVCVAssetInfo AVCVDuration:] in VODUpload(AVCVAssetInfo.o) +[AVCVAssetInfo AVCVVideoDuration:] in VODUpload(AVCVAssetInfo.o) +[AVCVAssetInfo AVCVAudioDuration:] in VODUpload(AVCVAssetInfo.o) "_AVMediaTypeAudio", referenced from: +[AVCVAssetInfo AVCVAudioDuration:] in VODUpload(AVCVAssetInfo.o) "_AVMediaTypeVideo", referenced from: +[AVCVAssetInfo AVCVNaturalSize:] in VODUpload(AVCVAssetInfo.o) +[AVCVAssetInfo AVCVFrameRate:] in VODUpload(AVCVAssetInfo.o) +[AVCVAssetInfo AVCVBitrate:] in VODUpload(AVCVAssetInfo.o) +[AVCVAssetInfo AVCVVideoDuration:] in VODUpload(AVCVAssetInfo.o) "_OBJC_CLASS_$_AVURLAsset", referenced from: objc-class-ref in VODUpload(VODUploadClient.o) ld: symbol(s) not found for architecture arm64 clang: error: linker command failed with exit code 1 (use -v to see invocation)
官网上说要添加-Objc,force load,统统删掉,编译,OK了。
但是一初始化上传对象,编译,又报错了,什么情况呢?后来联系了阿里的技术服务和技术的童鞋。
原来是缺少了依赖库,也不用设置force load,-Objc什么的,需要添加的依赖库有:
libresolve.tbd
libresolve.9.tbd
SystermCongiguration.framework
MobileCoreService.framework
CoreMedia.ramework
AVFoundation.framework
这些官方文档上可是没有说的,就是对着Demo添加也添加不全的。
5、Forbidden.RAM
上传报错
failed code = Forbidden.RAM, error message = You are not authorized to operate this resource, or this API does not support RAM.
报这个错误,是没有权限使用上传Api,我是因为后台没有开通点播服务导致的。
四、实现代码
这个代码不是很合理,看看就行了。
STS上传方式源码
STS已经不被推荐使用了,这里直接跳过吧。
#import "VHVideoUploder.h"
#import <VODUpload/VODUploadClient.h>
#import "VHVideoUploadModel.h"
#import "VHWebService.h"
@interface VHVideoUploder ()
@property (nonatomic, strong) VODUploadClient *uploader;
@property (nonatomic, strong) VODUploadListener *listener;
@property (nonatomic, copy) NSString *userAccessToken;
@end
@implementation VHVideoUploder
/**
获取SDK版本号
*/
+ (NSString *)getSDKVersion {
return SDK_Version;
}
#pragma mark - init
- (VODUploadClient *)uploader {
if (!_uploader) {
_uploader = [[VODUploadClient alloc] init];
__weak typeof(self)weakSelf = self;
OnUploadFinishedListener finishedCallbackFunc = ^(UploadFileInfo* fileInfo, VodUploadResult* result) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@", [NSString stringWithFormat:@"upload success! %@", fileInfo.filePath]);
});
};
OnUploadFailedListener testFailedCallbackFunc = ^(UploadFileInfo* fileInfo, NSString *code, NSString* message){
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"failed code = %@, error message = %@", code, message);
NSLog(@"state %ld",(long)fileInfo.state);
});
};
OnUploadProgressListener testProgressCallbackFunc = ^(UploadFileInfo* fileInfo, long uploadedSize, long totalSize) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"progress uploadedSize : %li, totalSize : %li", uploadedSize, totalSize);
});
};
OnUploadTokenExpiredListener testTokenExpiredCallbackFunc = ^{
NSLog(@"token expired.");
// update sts token and call resumeWithToken
// OSS token过期,设置新的STS,继续上传
//[weakSelf.uploader resumeWithToken:`STS Key Id` accessKeySecret:`STS Key Secret` secretToken:`STS Secret Token` expireTime:`STS Expire Time`];
dispatch_async(dispatch_get_main_queue(), ^{
[VHVideoUploder requestUploadAuthKeyWithToekn:self.userAccessToken success:^(id _Nullable responseObject) {
[weakSelf.uploader resumeWithToken:responseObject[@"stsArr"][@"AccessKeyId"] accessKeySecret:responseObject[@"stsArr"][@"AccessKeySecret"] secretToken:responseObject[@"stsArr"][@"SecurityToken"] expireTime:responseObject[@"stsArr"][@"Expiration"] ];
} failure:^(NSError *error) {
}];
});
};
OnUploadRertyListener testRetryCallbackFunc = ^{
NSLog(@"manager: retry begin.");
};
OnUploadRertyResumeListener testRetryResumeCallbackFunc = ^{
NSLog(@"manager: retry begin.");
};
OnUploadStartedListener testUploadStartedCallbackFunc = ^(UploadFileInfo* fileInfo) {
NSLog(@"upload started .");
};
_listener = [[VODUploadListener alloc] init];
_listener.finish = finishedCallbackFunc;
_listener.failure = testFailedCallbackFunc;
_listener.progress = testProgressCallbackFunc;
_listener.expire = testTokenExpiredCallbackFunc;
_listener.retry = testRetryCallbackFunc;
_listener.retryResume = testRetryResumeCallbackFunc;
_listener.started = testUploadStartedCallbackFunc;
}
return _uploader;
}
#pragma mark - public
/**
添加文件
@param videoPath 视频文件路径
@param svideoInfo 短视频上传信息
*/
- (BOOL)addFileWithVideoPath:(NSString *)videoPath
svideoInfo:(VHVideoUploadModel *)svideoInfo
{
VodInfo *info = [[VodInfo alloc] init];
info.title = svideoInfo.name;
info.desc = svideoInfo.desc;
return [self.uploader addFile:videoPath vodInfo:info];
}
/**
开始上传
@param accessToken VH AccessToken
*/
- (void)startWithAccessToken:(NSString *)accessToken
{
self.userAccessToken = accessToken;
[VHVideoUploder requestUploadAuthKeyWithToekn:accessToken success:^(id _Nullable responseObject) {
[self.uploader setKeyId:responseObject[@"stsArr"][@"AccessKeyId"] accessKeySecret:responseObject[@"stsArr"][@"AccessKeySecret"] secretToken:responseObject[@"stsArr"][@"SecurityToken"] expireTime:responseObject[@"stsArr"][@"Expiration"] listener:self.listener];
[self.uploader start];
} failure:^(NSError *error) {
}];
}
/**
暂停上传
*/
- (void)pause
{
[self.uploader pause];
}
/**
恢复上传
*/
- (void)resume
{
[self.uploader resume];
}
#pragma mark - private
+ (void)requestUploadAuthKeyWithToekn:(NSString *)toekn
success:(nullable void (^)(id _Nullable responseObject))success
failure:(nullable void (^)(NSError *error))failure
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[@"access_token"] = toekn;
[VHWebService requestWithApi:UploadAuthKey param:dict onRequestFinished:^(id data) {
NSLog(@"request authKey response : %@",data);
if (success) {
success(data);
}
} onRequestFailed:^(NSError *error) {
NSLog(@"request authKey error : %@",error);
if (failure) {
failure(error);
}
}];
}
@end
这个代码测试了一下没跑通,OSS SDK报错信息如下:
failed code = Forbidden.RAM, error message = You are not authorized to operate this resource, or this API does not support RAM.
然后阿里技术的童鞋说推荐使用上传地址+凭证的方式上传。查了Api错误表,搜索‘API does not support RAM’关键字,会有解释,这个Api不能用了:
’未授权,或此 API 不支持访问控制。‘
安卓端也是报这个问题,‘临时用户访问无权限,该临时用户角色扮演指定授权策略,该授权策略无权限’。
上传地址+凭证方式源码
VHVideoUploader.m
//
// VHVideoUploader.m
// VHUploadFramework
//
// Created by vhall on 2019/10/15.
// Copyright © 2019 vhall. All rights reserved.
//
#import "VHVideoUploader.h"
#import <VODUpload/VODUploadClient.h>
#import "VHVideoUploadModel.h"
#import "VHWebService.h"
@interface VHVideoUploader ()
@property (nonatomic, strong) VODUploadClient *uploader;
@property (nonatomic, strong) VODUploadListener *listener;
@property (nonatomic, copy) NSString *userAccessToken;
@property (nonatomic, copy) NSString *uploadAuth;//上传凭证
@property (nonatomic, copy) NSString *uploadAddress;//上传地址
@end
@implementation VHVideoUploader
/**
获取SDK版本号
*/
+ (NSString *)getSDKVersion {
return SDK_Version;
}
#pragma mark - init
- (VODUploadClient *)uploader {
if (!_uploader) {
_uploader = [[VODUploadClient alloc] init];
__weak typeof(self)weakSelf = self;
OnUploadFinishedListener finishedCallbackFunc = ^(UploadFileInfo* fileInfo, VodUploadResult* result) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"%@", [NSString stringWithFormat:@"upload success! %@", fileInfo.filePath]);
[weakSelf finished:fileInfo result:result];
});
};
OnUploadFailedListener testFailedCallbackFunc = ^(UploadFileInfo* fileInfo, NSString *code, NSString* message){
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"failed code = %@, error message = %@", code, message);
NSLog(@"state %ld",(long)fileInfo.state);
[weakSelf failed:fileInfo code:code message:message];
});
};
OnUploadProgressListener testProgressCallbackFunc = ^(UploadFileInfo* fileInfo, long uploadedSize, long totalSize) {
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"progress uploadedSize : %li, totalSize : %li", uploadedSize, totalSize);
[weakSelf progress:fileInfo uploadSize:uploadedSize totalSize:totalSize];
});
};
OnUploadTokenExpiredListener testTokenExpiredCallbackFunc = ^{
NSLog(@"token expired.");
// upload auth过期,设置新的upload auth,继续上传
// [weakSelf.uploader resumeWithAuth:`new upload auth`];
dispatch_async(dispatch_get_main_queue(), ^{
[VHVideoUploader requestUploadAuthKeyWithToekn:self.userAccessToken success:^(id _Nullable responseObject) {
// weakSelf.uploadAuth =
// weakSelf.uploadAddress =
[weakSelf.uploader resumeWithAuth:weakSelf.uploadAuth];
} failure:^(NSError *error) {
}];
});
};
OnUploadRertyListener testRetryCallbackFunc = ^{
NSLog(@"manager: retry begin.");
};
OnUploadRertyResumeListener testRetryResumeCallbackFunc = ^{
NSLog(@"manager: retry begin.");
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf onResume];
});
};
OnUploadStartedListener testUploadStartedCallbackFunc = ^(UploadFileInfo* fileInfo) {
NSLog(@"upload started .");
// 设置上传地址 和 上传凭证
//[weakSelf.uploader setUploadAuthAndAddress:fileInfo uploadAuth:`upload auth` uploadAddress:`upload address`];
[weakSelf.uploader setUploadAuthAndAddress:fileInfo uploadAuth:weakSelf.uploadAuth uploadAddress:weakSelf.uploadAddress];
dispatch_async(dispatch_get_main_queue(), ^{
[weakSelf start:fileInfo];
});
};
_listener = [[VODUploadListener alloc] init];
_listener.finish = finishedCallbackFunc;
_listener.failure = testFailedCallbackFunc;
_listener.progress = testProgressCallbackFunc;
_listener.expire = testTokenExpiredCallbackFunc;
_listener.retry = testRetryCallbackFunc;
_listener.retryResume = testRetryResumeCallbackFunc;
_listener.started = testUploadStartedCallbackFunc;
}
return _uploader;
}
#pragma mark - public
/**
添加文件
@param videoPath 视频文件路径
@param model 短视频上传信息
*/
- (BOOL)addFileWithVideoPath:(NSString *)videoPath
svideoInfo:(VHVideoUploadModel *)model
{
return [self.uploader addFile:videoPath vodInfo:[self unPackVodInfo:model]];
}
/**
开始上传
@param accessToken VH AccessToken
*/
- (void)startWithAccessToken:(NSString *)accessToken
{
self.userAccessToken = accessToken;
[VHVideoUploader requestUploadAuthKeyWithToekn:accessToken success:^(id _Nullable responseObject) {
// self.uploadAuth =
// self.uploadAddress =
[self.uploader start];
} failure:^(NSError *error) {
}];
}
/**
暂停上传
*/
- (void)pause
{
[self.uploader pause];
}
/**
恢复上传
*/
- (void)resume
{
[self.uploader resume];
}
#pragma mark - private
+ (void)requestUploadAuthKeyWithToekn:(NSString *)toekn
success:(nullable void (^)(id _Nullable responseObject))success
failure:(nullable void (^)(NSError *error))failure
{
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dict[@"access_token"] = toekn;
[VHWebService requestWithApi:UploadAuthKey param:dict onRequestFinished:^(id data) {
NSLog(@"request authKey response : %@",data);
if (success) {
success(data);
}
} onRequestFailed:^(NSError *error) {
NSLog(@"request authKey error : %@",error);
if (failure) {
failure(error);
}
}];
}
- (VHUploadFileInfo *)packUploadInfo:(UploadFileInfo *)fileInfo {
VHUploadFileInfo *info = [[VHUploadFileInfo alloc] init];
info.filePath = fileInfo.filePath;
info.endpoint = fileInfo.endpoint;
info.bucket = fileInfo.bucket;
info.object = fileInfo.object;
info.state = (VHVODUploadFileStatus)fileInfo.state;
info.vodInfo = [self packUploadModel:fileInfo.vodInfo];
return info;
}
- (VHVideoUploadModel *)packUploadModel:(VodInfo *)vodInfo {
VHVideoUploadModel *info = [[VHVideoUploadModel alloc] init];
info.title = vodInfo.title;
info.tags = vodInfo.tags;
info.desc = vodInfo.desc;
info.cateId = vodInfo.cateId;
info.coverUrl = vodInfo.coverUrl;
info.userData = vodInfo.userData;
return info;
}
- (VodInfo *)unPackVodInfo:(VHVideoUploadModel *)model {
VodInfo *info = [[VodInfo alloc] init];
info.title = model.title;
info.tags = model.tags;
info.desc = model.desc;
info.cateId = model.cateId;
info.coverUrl = model.coverUrl;
info.userData = model.userData;
return info;
}
- (VHVodUploadResult *)packUploadResult:(VodUploadResult *)result
{
VHVodUploadResult *uploadResult = [[VHVodUploadResult alloc] init];
uploadResult.videoId = result.videoId;
uploadResult.imageUrl = result.imageUrl;
uploadResult.bucket = result.bucket;
uploadResult.endpoint = result.endpoint;
return uploadResult;
}
#pragma mark - 回调
- (void)start:(UploadFileInfo *)fileInfo {
if ([self.delegate respondsToSelector:@selector(uploader:didStarted:)]) {
[self.delegate uploader:self didStarted:[self packUploadInfo:fileInfo]];
}
}
- (void)failed:(UploadFileInfo *)fileInfo code:(NSString *)code message:(NSString *)message {
if ([self.delegate respondsToSelector:@selector(failed:code:message:)]) {
[self.delegate uploader:self didFailed:[self packUploadInfo:fileInfo] code:code message:message];
}
}
- (void)finished:(UploadFileInfo *)fileInfo result:(VodUploadResult *)reult {
if ([self.delegate respondsToSelector:@selector(uploader:didFinished:result:)]) {
[self.delegate uploader:self didFinished:[self packUploadInfo:fileInfo] result:[self packUploadResult:reult]];
}
}
- (void)onResume {
if ([self.delegate respondsToSelector:@selector(uploaderDidResume:)]) {
[self.delegate uploaderDidResume:self];
}
}
- (void)progress:(UploadFileInfo *)fileInfo uploadSize:(long)uploadedSize totalSize:(long)totalSize {
if ([self.delegate respondsToSelector:@selector(uploader:progressFile:uploadSize:totalSize:)]) {
[self.delegate uploader:self progressFile:[self packUploadInfo:fileInfo] uploadSize:uploadedSize totalSize:totalSize];
}
}
@end
VHVideoUploadModel.h
//
// VHVideoUploadModel.h
// VHUploadFramework
//
// Created by vhall on 2019/10/15.
// Copyright © 2019 vhall. All rights reserved.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef NS_ENUM(NSInteger, VHVODUploadFileStatus) {
VHVODUploadFileStatusReady,
VHVODUploadFileStatusUploading,
VHVODUploadFileStatusCanceled,
VHVODUploadFileStatusPaused,
VHVODUploadFileStatusSuccess,
VHVODUploadFileStatusFailure
};
@interface VHVideoUploadModel : NSObject
/**
标题
*/
@property (nonatomic, copy) NSString* title;
/**
标签
*/
@property (nonatomic, copy) NSString* tags;
/**
描述
*/
@property (nonatomic, copy) NSString* desc;
/**
分类id
*/
@property (nonatomic, strong) NSNumber* cateId;
/**
封面url
*/
@property (nonatomic, copy) NSString* coverUrl;
/**
设置自定义数据
*/
@property (nonatomic, copy) NSString* userData;
@end
@interface VHUploadFileInfo : NSObject
@property (nonatomic, copy) NSString* filePath;
@property (nonatomic, copy) NSString* endpoint;
@property (nonatomic, copy) NSString* bucket;
@property (nonatomic, copy) NSString* object;
@property (nonatomic, strong) VHVideoUploadModel* vodInfo;
@property VHVODUploadFileStatus state;
@end
@interface VHVodUploadResult: NSObject
@property (nonatomic, copy) NSString* videoId;
@property (nonatomic, copy) NSString* imageUrl;
@property (nonatomic, copy) NSString* bucket;
@property (nonatomic, copy) NSString* endpoint;
@end
NS_ASSUME_NONNULL_END
这种每次回调的时候都需要将数据包装,在progress回调方法中一直创建对象的方法不是很妥当,还需要优化。