背景
最近游戏社区在适配鸿蒙系统,需要基于鸿蒙版Flutter并适配原生接口,大部分基础Flutter插件官方已完成鸿蒙化改造,而像视频上传使用到的腾讯云点播(VOD)目前尚未支持,为了能正常使用该功能,需要分析源码、集成鸿蒙版COS SDK自己实现。
代码实现
oh-package.json5添加COS依赖:
"@tencentcloud/cos":"1.1.4"
新增插件类 FlutterOhosCloudVodUploadSdkPlugin
,主要是处理视频上传请求:
onMethodCall(call: MethodCall, result: MethodResult): void {
let method: string = call.method;
try {
switch (method) {
case VideoUploadConstant.METHOD_UPLOAD_VIDEO:
const sign: string = call.argument(VideoUploadConstant.PARAM_SIGN);
const filePath: string = call.argument(VideoUploadConstant.PARAM_SRC_PATH);
const fileName: string = call.argument(VideoUploadConstant.PARAM_FILE_NAME);
const coverPath: string = call.argument(VideoUploadConstant.PARAM_COVER);
const taskId: string = call.argument(VideoUploadConstant.PARAM_TASK_ID);
this.mgr?.uploadFile(sign, filePath, fileName, coverPath, taskId);
break;
default:
break;
}
} catch (err) {
// 异常处理
}
}
实际调用的核心上传方法:
async uploadFile(sign: string, filePath: string, fileName: string, coverPath: string, taskId: string) {
.... // 参数校验等
let data: ApplyUploadUGCData | null = await this.getApplyUploadUGCData(sign, filePath, fileName, coverPath);
if (data != null) {
this.initCosService(this.context, data);
this.uploadVideo(data, sign, filePath, coverPath, taskId);
}
}
其中几个关键步骤:
(1)getApplyUploadUGCData:请求上传票据等数据,请求参数包括签名、视频和封面图的文件名和大小
private async getApplyUploadUGCData(sign: string, filePath: string,
fileName: string, coverPath: string): Promise<ApplyUploadUGCData | null> {
let data: ApplyUploadUGCData | null = null;
try {
const options: http.HttpRequestOptions = {
method: http.RequestMethod.POST,
header: {
'Content-Type': 'application/json',
},
extraData: {
signature: sign,
videoName: fileName,
videoType: fileType,
videoSize: fileSize,
coverName: coverName,
coverType: coverType,
coverSize: coverSize,
},
};
const url =
`https://vod2.qcloud.com/v3/index.php?Action=ApplyUploadUGC`;
const httpRequest = http.createHttp();
const response = await httpRequest.request(url, options);
if (response.responseCode === http.ResponseCode.OK) {
const reply: ApplyUploadUGCReply = JSON.parse(response.result.toString());
if (reply.code == 0) {
data = reply.data;
}
}
httpRequest.destroy();
} catch (error) {
// 异常处理
}
return data;
}
(2)initCosService:初始化临时密钥和VOD COS服务
private initCosService(context: common.Context, data: ApplyUploadUGCData) {
const credential: QCloudCredential = new QCloudCredential();
credential.secretID = data.tempCertificate.secretId;
credential.secretKey = data.tempCertificate.secretKey;
credential.token = data.tempCertificate.token;
credential.expirationDate = new Date(data.tempCertificate.expiredTime * 1000);
this.credential = credential;
const config = new CosXmlServiceConfig(data.storageRegionV5);
config.retrySleep = 5 * 1000;
this.service = new CosXmlBaseService(context, config);
}
(3)getMultipartUploadId:获取视频分片上传id
private async getMultipartUploadId(service: CosXmlBaseService, credential: QCloudCredential, bucket: string,
cosPath: string): Promise<string | null> {
let uploadId: string | null = null;
try {
const putRequest = new InitMultipartUploadRequest(bucket, cosPath);
putRequest.credential = credential;
let multipart = await service.initMultipartUpload(putRequest);
uploadId = multipart.initMultipartUpload?.uploadId ?? null;
} catch (e) {
// 异常处理
}
return uploadId;
}
(4)uploadVideo:视频上传到VOD COS,主要参数有
- 存储桶名称:
${data.storageBucket}-${data.storageAppId}
- 对象在存储桶中的位置:
data.video.storagePath.substring(1)
private async uploadVideo(data: ApplyUploadUGCData, sign: string, filePath: string, coverPath: string,
taskId: string) {
.... // 参数校验等
const putRequest = new PutObjectRequest(bucket, cosPath, filePath);
putRequest.credential = this.credential;
let uploadId = await this.getMultipartUploadId(this.service, this.credential, bucket, cosPath);
if (uploadId == null) {
return;
}
const task: UploadTask = this.service.upload(putRequest, uploadId, config);
task.onProgress = (progress: HttpProgress) => {
this.sendProgressResult(progress.complete, progress.target);
};
task.onResult = {
onSuccess: async (request, result) => {
if (await VideoUploadUtils.hasCover(coverPath)) {
this.uploadCover(data, sign, coverPath, taskId);
} else {
const uploadData = await this.getCommitUploadUGC(data, sign);
if (uploadData != null) {
this.sendSuccessResult(uploadData.video.url, taskId, uploadData.cover.url);
}
}
},
onFail: (request, error) => {
// 上传失败处理
}
}
task.start();
}
(5)uploadCover:封面图上传到VOD COS,比视频上传类似且更简单
private uploadCover(data: ApplyUploadUGCData, sign: string, coverPath: string, taskId: string) {
.... // 参数校验等
const putRequest = new PutObjectRequest(bucket, cosPath, coverPath);
putRequest.credential = this.credential;
const task: UploadTask = this.service.upload(putRequest, taskId);
task.onResult = {
onSuccess: async (request, result) => {
const uploadData = await this.getCommitUploadUGC(data, sign);
if (uploadData != null) {
this.sendSuccessResult(uploadData.video.url, taskId, uploadData.cover.url);
}
},
onFail: (request, error) => {
// 上传失败处理
}
}
task.start();
}
(6)getCommitUploadUGC:请求视频和封面图链接,请求参数包括签名、视频id
private async getCommitUploadUGC(data: ApplyUploadUGCData, sign: string): Promise<CommitUploadUGCCData | null> {
let uploadData: CommitUploadUGCCData | null = null;
try {
const options: http.HttpRequestOptions = {
method: http.RequestMethod.POST,
header: {
'Content-Type': 'application/json',
},
extraData: {
signature: sign,
vodSessionKey: data.vodSessionKey,
},
};
const url =
`https://vod2.qcloud.com/v3/index.php?Action=CommitUploadUGC`;
const httpRequest = http.createHttp();
const response = await httpRequest.request(url, options);
if (response.responseCode === http.ResponseCode.OK) {
const reply: CommitUploadUGCReply = JSON.parse(response.result.toString());
if (reply.code == 0) {
uploadData = reply.data;
}
}
httpRequest.destroy();
} catch (error) {
// 异常处理
}
return uploadData;
}
最后在Flutter 插件pubspec.yaml
声明鸿蒙接口:
plugin:
platforms:
android:
package: com.sirius.cloud_vod_upload_sdk
pluginClass: FlutterCloudVodUploadSdkPlugin
ios:
pluginClass: TencentFlutterCloudVodUploadSdkPlugin
ohos:
pluginClass: FlutterOhosCloudVodUploadSdkPlugin
代码地址:https://github.com/minmin1123/flutter_cloud_vod_upload_sdk
使用说明
参考官方Flutter 上传 SDK先接入到Flutter项目中:
(1)将前面的源码复制到项目中,并在pubspec.yaml
中引入,比如:
flutter_cloud_vod_upload_sdk:
path: ../flutter_cloud_vod_upload_sdk
(2)申请上传签名:参考官方指引
(3)创建任务UploadTask
并上传,任务参数:
-
taskId
:任务唯一id -
signature
:上传签名 -
fileName
:视频文件名 -
filePath
:视频本地路径 -
coverPath
:封面本地路径
static Future<UploadTask> uploadVideo(
UploadTaskController controller,
String taskId,
String signature,
String fileName,
String filePath,
String coverPath, {
ValueChanged<String>? onStart,
UploadProgressCallBack? onProgress,
ValueChanged<UploadTaskCompleteInfo>? onSuccess,
ValueChanged<UploadTaskCompleteInfo>? onFail,
}) async {
var task = UploadTask(
taskId: taskId,
signature: signature,
fileName: fileName,
filePath: filePath,
coverPath: coverPath,
onStart: onStart,
onProgress: onProgress,
onFail: onFail,
onSuccess: onSuccess,
);
controller.addTask(task);
return task;
}
(4)上传结果回调在UploadTaskCompleteInfo
,包括:
-
videoId
:视频文件id -
videoURL
:视频存储地址 -
coverURL
:封面存储地址 -
retCode
:错误码 -
descMsg
:错误描述信息
总结
如此实现了一个简单鸿蒙版VOD Flutter插件,但时间原因并没有把源码中所有功能都还原,后续有时间会持续优化:
- 预上传
- 大文件分块上传
- 断点续传
- QUIC