Flutter|云点播插件适配鸿蒙(简单版)

背景

最近游戏社区在适配鸿蒙系统,需要基于鸿蒙版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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容