背景
最近游戲社區(qū)在適配鴻蒙系統(tǒng),需要基于鴻蒙版Flutter并適配原生接口,大部分基礎Flutter插件官方已完成鴻蒙化改造,而像視頻上傳使用到的騰訊云點播(VOD)目前尚未支持,為了能正常使用該功能,需要分析源碼、集成鴻蒙版COS SDK自己實現(xiàn)。
代碼實現(xiàn)
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)創(chuàng)建任務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:錯誤描述信息
總結
如此實現(xiàn)了一個簡單鴻蒙版VOD Flutter插件,但時間原因并沒有把源碼中所有功能都還原,后續(xù)有時間會持續(xù)優(yōu)化:
- 預上傳
- 大文件分塊上傳
- 斷點續(xù)傳
- QUIC