likes
comments
collection
share

React项目 对接阿里云视频点播功能

作者站长头像
站长
· 阅读数 31

前言

背景

公司的产品,需要课程模块。需求时间紧,实现难度和工作量大。因此,决定选择市面上较为成熟的解决方案。经过调研后,决定使用阿里云的视频点播功能。

写文目的

在对接阿里云视频点播服务的过程中,踩了不少坑。网上相关资料不多,记录一下,供个人学习和复盘。

基本介绍

阿里云视频点播(VoD)是集音视频上传、自动化转码处理、媒体资源管理、分发加速于一体的全链路音视频点播服务。借助灵活、可伸缩的存储、处理及内容分发服务,帮助企业和开发者快速搭建安全、弹性、高可定制的点播平台和应用,提供端到端的完整解决方案。

点播的应用场景包括但不限于音视频网站,短视频,在线教育,广电传媒。

正文

上传SDK

本次上传采用的客户端上传至点播存储,即使用JavaScript上传SDK将媒体文件上传。

本文只针对当前实现方案(STS).其他方案略过,详情移步官方文档

前提条件

    这部分主要是开通视频点播服务,上传相关系统配置(启用目标存储地域的存储地址配置回调),基于安全考虑,可以创建RAM用户并授予相关权限,通过密钥访问服务

    JavaScript上传SDK目前兼容的浏览器是:

 IE 10及以上版本
 Edge
 主流版本的Chrome/Firefox/Safari
 主流版本的Android/iOS/WindowsPhone系统默认浏览器

    本次实现的方式是通过STS临时授权的方式来访问服务的,因此也需要设置相关权限

    tips:权限配置的时候,不能漏权限,不然访问阿里云接口的时候会报403Forbidden

React项目 对接阿里云视频点播功能

上传文件

    使用JavaScript上传文件的基本操作步骤如下:

       1、引入JavaScript上传SDK

    首先得下载JavaScript脚本,也就是SDK。官方文档中,还有JqueryVue的Demo。我参照的是Vue的Demo。

    Demo有几个问题,参考的时候得注意一下:

- 运行npm run dev命令,会报错。NODE_ENV不是内部或外部命令,也不是可运行的程序。
  这是Window系统下才会出现的错误。
  解决方案是安装across-env:npm install cross-env –-save-dev
  然后在package.json文件script脚本中的NODE_ENV=xxxxxxx前面加上cross-env
- Demo展示了2种上传方式的例子。但是在STS的例子中,上传和token超时重新上传的逻辑,与上传凭证和地址的实现方式一样
  这是错误的,直接参照会走点弯路。一切以官方文档为主

React项目 对接阿里云视频点播功能

     引入方式有两种:

     1. 在HTML中通过script引用(官方推荐)

<!-- IE需要es6-promise,目前支持到IE10 --> 
<script src="../lib/es6-promise.min.js">
</script> <script src="../lib/aliyun-oss-sdk6.17.1.min.js">
</script> <script src="../aliyun-vod-upload-sdk1.5.4.min.js"></script>

     2. 模块化引用

     手动将OSS模块的内容赋值给Window

    tips:已经通过方式1引用的,不需要再使用方式2

import OSS from '../lib/aliyun-upload-sdk/lib/aliyun-oss-sdk-6.17.1.min' 
window.OSS = OSS; 
import '../lib/aliyun-upload-sdk/aliyun-upload-sdk-1.5.4.min'

       2、请求STS临时Token

    JavaScript上传SDK时,需要从AppServer中获取上传凭证。这一步需要后端部署相关服务(STS服务部署方法)。部署好以后,前端直接通过接口请求,拿到相关凭证即可。

       3、初始化上传实例

    初始化上传实例,STS方式

    tips:上传地址和凭证方式,移步官方文档

var uploader = new AliyunUpload.Vod({ 
    userId: "122",  //userID,必填,您可以使用阿里云账号访问账号中心(https://account.console.aliyun.com/),即可查看账号ID
    region:"cn-shanghai",  //上传到视频点播的地域,默认值为'cn-shanghai', //eu-central-1,ap-southeast-1
    partSize: 1048576,  //分片大小默认1 MB,不能小于100 KB(100*1024) 
    parallel: 5,  //并行上传分片个数,默认5 
    retryCount: 3, //网络原因失败时,重新上传次数,默认为3 
    retryDuration: 2,  //网络原因失败时,重新上传间隔时间,默认为2秒
    //添加文件成功
    addFileSuccess: function (uploadInfo) {
        console.log('addFileSuccess: ' + uploadInfo.name)
    },
    //开始上传 
    onUploadstarted: function (uploadInfo) {
        var stsUrl = 'stsUrl'; 
        // 以下请求实现为示例,用于演示设置凭证 
        // 获取 accessKeyId, accessKeySecret,secretToken 
        fetch(stsUrl).then((data) => { 
           const {AccessKeyId,AccessKeySecret,SecretToken} = data
           uploader.setSTSToken(uploadInfo, AccessKeyId, AccessKeySecret, SecretToken); 
        }).catch((err)=>{
           console.log('getToken errInfo: ' + err)
        })
    },
    //文件上传成功 
    onUploadSucceed: function (uploadInfo) { 
        console.log('onUploadSucceed: ' + uploadInfo.name)
    },
    //文件上传失败 
    onUploadFailed: function (uploadInfo, code, message) {
        console.log('onUploadSucceed: ' + uploadInfo.name + 'code: ' +code + 'message: ' + message)
    }, 
    //文件上传进度,单位:字节 
    onUploadProgress: function (uploadInfo, totalSize, loadedPercent) { 
        console.log('onUploadProgress: ' + uploadInfo.name + 'fileSize: ' + totalSize + 'percent: ' + Math.ceil(progress * 100) + '%'
    },
    //上传凭证或STS token超时 
    onUploadTokenExpired: function (uploadInfo) { 
        var stsUrl = 'stsUrl'; 
        // 以下请求实现为示例,用于演示设置凭证 
        // 获取 accessKeyId, accessKeySecret,secretToken 
        fetch(stsUrl).then((data) => { 
           const {AccessKeyId,AccessKeySecret,SecretToken} = data
           uploader.resumeUploadWithSTSToken(uploadInfo, AccessKeyId, AccessKeySecret, SecretToken); 
        }).catch((err)=>{
           console.log('getToken errInfo: ' + err)
        })
    }, 
    //全部文件上传结束 
    onUploadEnd:function(uploadInfo){
        console.log('onUploadEnd: ' + uploadInfo.name)
    } 
  });

注意事项

1.使用了TypeScript的项目,会在初始化实例的时候报错:找不到名称“AliyunUpload”。出现报错的原因是因为上传SDK是直接通过脚本引入的。解决方案是忽略即可,//@ts-ignore

2.userId是必填项,可以前往阿里云账号访问中心,查看账号ID。

注意:账号ID得是主账号的ID

3.上传文件过大时可能在上传过程中sts token就会失效, Token失效后,会自动触发onUploadTokenExpired回调函数,需要调用resumeUploadWithSTSToken方法.设置新的STS继续上传。

       4、根据上传的文件类型构造上传参数

    1.选择上传文件

render(){
    return (
        <div className="upload">
            <input type="file" onChange={(e)=>fileChange(e.target.file[0])} />
        </div>
    )
}

    2.将选择的文件添加到上传列表中

fileChange = (file) =>{
   uploader.addFile(file,null,null,null,'{"Vod":{}}');
}

    参数说明

参数名称是否必填类型参数描述
fileFile需要上传的音视频文件。
endpointString想要上传到的endpoint,传入null则由服务端决定。
bucketString想要上传到的bucket,传入null则由服务端决定。
objectString想要上传到的object,传入null则由服务端决定。
paramsDataString当您使用STS方式上传时,可以通过paramData设置音频信息。paramData是一个JSON对象字符串,第一级的Vod是必须的。请在Vod下添加paramData支持的属性。更多信息,请参见获取音视频上传地址和凭证

    tips: paramData只有在STS方式上传时需要在SDK指定,如果是上传地址和凭证方式,则无需在SDK里指定paramData参数。

注意事项

    参数file的类型是File。官网案例中使用的是原生input标签来上传的,得到的file的类型是File

    我们在实际开发中,不用局限于原生标签,也可以直接使用第三方组件库中的组件,例如Ant DesignUpload组件来进行文件上传。

    需要注意的点是,Upload组件onChange方法回调中的file属性是普通对象,beforeUploadcustomRequest方法回调中的fileFile对象

如果在将文件添加到上传列表的步骤中,添加的file为普通对象。那么会报非法调用的错误。

React项目 对接阿里云视频点播功能

       5、开始上传

    1.调用startUpload()方法开始上传

  uploader.startUpload();

    2.文件开始上传后,onUploadProgress回调开始同步上传进度。

    3.文件上传成功后,onUploadSucceed回调会返回上传结果。

   tips: 目前客户端仅支持音视频文件上传。

队列管理与上传控制

    对于上传中或者上传完成的文件,可以通过对应的API进行管理。阿里云支持的API有以下几种

       listFiles()

    获取上传的文件列表,返回值为通过addFile添加的文件列表。其中的file属性为对于的File类型的文件,可以通过遍历获取到需要操作的文件的index

  const list = uploader.listFiles(); 
  for (let i=0; i<list.length; i++) 
  { 
    console.log("file:" + list[i].file.name); 
  }

       deleteFile()

    删除上传文件

  uploader.deleteFile(index);//需要删除的文件index,对应listFiles接口返回列表中元素的索引

       cancelFile()

    取消单个文件上传

   tips: cancelFile成功后会在控制台打印oss is cancel as error。这是SDK为了避免已上传的部分分片文件占用存储空间(如果占用就会产生存储费用)做的处理。

  uploader.cancelFile(index);

       resumeFile()

    恢复单个文件上传

  uploader.resumeFile(index);

       cleanList()

    清理上传文件列表

uploader.cleanList();

       stopUpload()

    停止上传

   tips: stopUpload要确保文件正在上传,有文件上传进度时才能工作。

 uploader.stopUpload();

上传凭证

    设置STS Token

    用于通过STS方式进行上传时,设置上传地址和上传凭证方法。一般在onUploadstarted回调里调用,此回调的参数包含uploadInfo的值。

 const {AccessKeyId,AccessKeySecret,SecretToken} = data
 uploader.setSTSToken(uploadInfo, AccessKeyId, AccessKeySecret, SecretToken);

   参数说明

参数名称参数描述
uploadInfo将onUploadstarted回调中的第一个参数进行透传
accessKeyIdCreateSecurityToken接口返回
accessKeySecretCreateSecurityToken接口返回
secretTokenCreateSecurityToken接口返回

    上传STS Token失效后恢复上传

    一般在onUploadTokenExpired回调中调用,用于通过STS方式进行上传时上传凭证过期后更新上传凭证。

  const {AccessKeyId,AccessKeySecret,SecretToken} = data
  uploader.resumeUploadWithSTSToken(uploadInfo, AccessKeyId, AccessKeySecret, SecretToken); 

高级功能-断点续传

    在上传过程中,由于某种原因(例如:页面被关闭、浏览器崩溃等)没有上传完成,下次选择同一个文件上传时, SDK会从上次完成的位置继续上传,并在onUploadstarted回调中获取上传凭证。使用上传地址和凭证方式上传时,用户可以根据回调返回的videoId的值,调用视频点播的接口获取断点信息。示例如下:

onUploadstarted: function (uploadInfo) { 
  if (上传地址和凭证方式) { 
    if(!uploadInfo.videoId){  //这个文件没有上传异常 
      //实际环境中调用视频点播的获取上传凭证接口 
      uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress,videoId); 
    } 
    else{   //如果videoId有值,根据videoId刷新上传凭证 
      //实际环境中调用视频点播的刷新上传凭证接口,获取凭证    
      uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress); 
    } 
  }
  else(STS方式) { 
    //实际环境中调用获取STS接口,获取STS的值 
    uploader.setSTSToken(uploadInfo, accessKeyId, accessKeySecret,secretToken); 
  } 
}

    获取断点信息:

 uploader.getCheckpoint(file);

上传SDk功能封装

    在上文上传SDK这块主要是沿着官方文档的思路,详细讲解了一下,JavaScript上传SDK的完整步骤、实现以及在实际开发过程中会遇见的各种坑。

基础封装

    我在开发的过程中,最开始也确实是按照这个步骤去开发,并封装了一个上传组件。

export const VideoUploadOss = forwardRef((props,ref) =>{
    const {
      accept,  //接受的文件类型
      disabled, //是否禁用上传功能
      listType, //上传列表的内建样式
    } = props
    
    const uploadSucessd = () =>{
        console.log('文件上传成功')
    }
    
    const uploader = useMemo(()=>{
        let currentUploader = new AliyunUpload.Vod({ 
            userId: "122", 
            region:"cn-shanghai",  
            partSize: 1048576,  
            parallel: 5, 
            retryCount: 3, 
            retryDuration: 2,  
            //添加文件成功
            addFileSuccess: function (uploadInfo) {
                console.log('addFileSuccess: ' + uploadInfo.name)
            },
            //开始上传 
            onUploadstarted: function (uploadInfo) {
                var stsUrl = 'stsUrl'; 
                fetch(stsUrl).then((data) => { 
                   const {AccessKeyId,AccessKeySecret,SecretToken} = data
                   currentUploader.setSTSToken(uploadInfo, AccessKeyId, AccessKeySecret, SecretToken); 
                }).catch((err)=>{
                   console.log('getToken errInfo: ' + err)
                })
            },
            //文件上传成功 
            onUploadSucceed: function (uploadInfo) { 
                console.log('onUploadSucceed: ' + uploadInfo.name)
            },
            //文件上传失败 
            onUploadFailed: function (uploadInfo, code, message) {
                console.log('onUploadSucceed: ' + uploadInfo.name + 'code: ' +code + 'message: ' + message)
            }, 
            //文件上传进度,单位:字节 
            onUploadProgress: function (uploadInfo, totalSize, loadedPercent) { 
                console.log('onUploadProgress: ' + uploadInfo.name + 'fileSize: ' + totalSize + 'percent: ' + Math.ceil(progress * 100) + '%'
            },
            //上传凭证或STS token超时 
            onUploadTokenExpired: function (uploadInfo) { 
                var stsUrl = 'stsUrl';  
                fetch(stsUrl).then((data) => { 
                   const {AccessKeyId,AccessKeySecret,SecretToken} = data
                   currentUploader.resumeUploadWithSTSToken(uploadInfo, AccessKeyId, AccessKeySecret, SecretToken); 
                }).catch((err)=>{
                   console.log('getToken errInfo: ' + err)
                })
            }, 
            //全部文件上传结束 
            onUploadEnd:function(uploadInfo){
                console.log('onUploadEnd: ' + uploadInfo.name)
            } 
          })
          return currentUploader
    },[])
    
    const fileChange = (file) =>{
      if(!file){
         console.log("请选择文件")
         return false
      }
      const userData = '{"Vod":{}}';
      uploader.addFile(file, nullnullnull, userData);
    }
    
    useImperativeHandle(ref,() => (
        startUpload: () => {
          if(uploader){
             uploader.startUpload()
          }
        }
    ))
    
    render(){
      return (
        <Upload
          beforeUpload={(file) => fileChange(file)}
          accept={accept}
          disabled={disabled}
          listType={listType}
          maxCount={1}
        >
        <Button icon={<UploadOutlined />}>{btnName}</Button>
       </Upload>
      )
    }
})

    但是在封装完成,并实际使用的过程中,发现了几个问题。

    1.组件不受控 从代码中可以看到,当外部组件通过ref.current.startUpload()调用实例上传文件后,实际的上传过程都在实例内部实现。暴露给我们的只有回调函数,通过在回调函数调用的时候,才能得到文件上传的结果。这个很明显,不是我们想要的效果,我们不知道什么时候能拿到回调,也就无法进行下一步的逻辑操作。特别是它与表单数据一起时。这个问题更加凸显了。

    2.多视频上传官方文档中,有关于一个页面同时上传多个文件的说明,原文是可通过创建多个上传实例来实现。一个上传实例对应一个上传文件,一个上传实例不能同时上传多个文件。如果继续使用当前封装的组件,意味着,得动态的创建多个ref来操控多个上传组件,同时,依然面临着组件不受控的问题。此外,如果除了上传文件操作,还有其他对文件有关的操作(例如,拖拽)产生的时候。当前组件更加无法满足业务要求了。

    因此,如何将创建实例文件添加解耦,就至关重要了

解耦封装

    如何解耦封装的核心在于将文件上传操作实例创建以及添加文件到实例上传列表操作分离。也就是说,上文中的创建实例部分得完全拆分出来了。另外就是,拆出来后的部分得受控,我们能够完全的控制,上传的节点,上传结束的节点,以及在这个过程中的相关操作。

    关于异步操作的受控问题,不出意外就得借助于Promise,async/await了。

       抽离实例

    将实例独立的抽离出来,通过Promise来重新封装一下

    此外,实例创建操作添加文件到实例上传列表操作,本来是相互独立的操作,原本的逻辑是在上传文件操作的同时,将添加文件到实例上传列表操作完成。现在为了解耦,则改为在创建实例的同时,将文件添加到实例上传列表。具体操作如下:

const createUploader = ({file,title},progressCallBack,uploadListCallBack) =>{
    if(!file) return Promise.reject(null)
    return new Promise((resolve,reject) =>{
        let uploader = new AliyunUpload.Vod({
            userId: "122", 
            region:"cn-shanghai",  
            partSize: 1048576,  
            parallel: 5, 
            retryCount: 3, 
            retryDuration: 2,  
            //添加文件成功
            addFileSuccess: function (uploadInfo) {
                console.log('addFileSuccess: ' + uploadInfo.name)
                //创建完实例,并添加文件后直接上传
                uploader.startUpload()
            },
            //开始上传 
            onUploadstarted: function (uploadInfo) {
                var stsUrl = 'stsUrl'; 
                fetch(stsUrl).then((data) => { 
                   const {AccessKeyId,AccessKeySecret,SecretToken} = data
                   uploader.setSTSToken(uploadInfo, AccessKeyId, AccessKeySecret, SecretToken); 
                }).catch((err)=>{
                   console.log('getToken errInfo: ' + err)
                })
            },
            //文件上传成功 
            onUploadSucceed: function (uploadInfo) { 
                console.log('onUploadSucceed: ' + uploadInfo.name)
                 resolve({uploadInfo})
            },
            //文件上传失败 
            onUploadFailed: function (uploadInfo, code, message) {
                console.log('onUploadSucceed: ' + uploadInfo.name + 'code: ' +code + 'message: ' + message)
                reject({uploadInfo,code,message})
            }, 
            //文件上传进度,单位:字节 
            onUploadProgress: function (uploadInfo, totalSize, loadedPercent) { 
                console.log('onUploadProgress: ' + uploadInfo.name + 'fileSize: ' + totalSize + 'percent: ' + Math.ceil(progress * 100) + '%'
                progressCallBack(`${uploadInfo.file.name}上传进度${progressPercent}%`)
            },
            //上传凭证或STS token超时 
            onUploadTokenExpired: function (uploadInfo) { 
                var stsUrl = 'stsUrl';  
                fetch(stsUrl).then((data) => { 
                   const {AccessKeyId,AccessKeySecret,SecretToken} = data
                   uploader.resumeUploadWithSTSToken(uploadInfo, AccessKeyId, AccessKeySecret, SecretToken); 
                }).catch((err)=>{
                   console.log('getToken errInfo: ' + err)
                })
            }, 
            //全部文件上传结束 
            onUploadEnd:function(uploadInfo){
                console.log('onUploadEnd: ' + uploadInfo.name)
            } 
        })
        /**
         *推荐将文件的标题一起传进来,这样阿里云后台就能看见上传的视频名字展示
         *如果用这个写法 "{"Vod":{}}"
         *视频上传后,后台标题展示均为undefined
        **/
        let useData = `{"Vod":{"Title":"${title}"}}`;
        setTimeout(()=>{
            if(uploader){
               uploadListCallBack((n)=>n.concat(uploader))
               uploader.addFile(file,null,null,null,useData);
            }
        },1000)
    })
}

tips

 1. title属性,推荐传入,可以自己单独传,也可以用file.name

 2. 文件添加后,在添加文件成功的回调addFileSuccess中,直接开始调用实例uploaderstartUpload()方法上传文件

 3. 阿里云点播实例的创建不是同步的,是异步的。如果直接调用addFile(),会报错。因此,此处通过setTimeout方法异步调用addFile()方法。

 4. 在文件上传成功的onUploadSucceed()回调函数中,通过resolve()将回调的数据传递出去。在文件上传失败的onUploadFailed()回调函数中,通过reject()将回调的数据传递出去。

 5. 在实例创建完成后,可以通过父组件的回调函数uploadListCallBack()将实例传递出去,方便后续其他操控操作。可以在文件上传进度回调函数onUploadProgress()中,使用父组件的回调函数progressCallBack()实时的同步文件上传进度。

       使用实例

    实例封装完成后,就可以开始使用实例了。

    1. 文件上传

const submit = async() =>{
  try{
      /**
        *校验逻辑+部分数据处理逻辑
      **/
      xxxxx

      /**
        *上传文件逻辑
      **/
      for(let i = 0; i < filesList.length; i += 1){
         const result = await createUploader(filesList[i],uploadListCallBack,progressCallBack)
      }

      /**
        *数据处理逻辑
      **/
      xxxxx
  }catch(err){
     cosole.log(err)
  }
  
}

    2. 文件停止上传,清除上传文件列表

const cacelUpload = async() =>{
   try{
     for(let i = 0; i < uploadList.length; i += 1){
       await uploadList[i].stopUpload()
       await uploadList[i].cleanList()
     }
   }catch(err){
     cosole.log(err)
   }
   
}

tips

 1. 使用async/await的时候,记得使用try/catch兜底,进行异常捕获。

 2. 当有文件进度的时候,可以用stopUpload()来停止上传。适用于上传到一般不想上传的情况。创建实例时,uploadListCallBack()获取的实例,就用于类似的地方。

 3. 当前上传的进度,卡住不动的原因,与网速和网络堵塞有关。上传速度慢,说明当前网速较慢,耐心等待即可。上传进度持续卡住不动,原因可能与网络堵塞有关,可以关掉网页,重新打开上传。

 出现场景:如果在第二点,上传到一半,未停止上传操作,直接关掉上传页面.打开上传页面,再次上传的时候,会存在堵塞情况。

播放器

    阿里云Web播放器SDK支持HTML5Flash两种播放模式     显而易见,本次采用的是H5播放模式,Flash作为曾经的互联网弄潮儿,已经被淘汰了。

快速接入

     引入js文件

<head> 
  <link rel="stylesheet" href="https://g.alicdn.com/de/prismplayer/2.15.2/skins/default/aliplayer-min.css" /> //(必须)H5模式播放器,需引用此css文件。 
  <script charset="utf-8" type="text/javascript" src="https://g.alicdn.com/de/prismplayer/2.15.2/aliplayer-h5-min.js"></script> //(必须)引入H5模式的js文件。 
</head>

     提供挂载元素

<body> 
  <div id="J_prismPlayer"></div> 
</body>

     实例化播放器

    Web播放器SDK支持5种点播播放方式,包括:URL播放Vid+PlayAuth播放(推荐)STS播放MPS播放加密播放

    本次使用了Vid+PlayAuth播放(推荐)STS播放

    Vid+PlayAuth播放(推荐)

    使用VID+PlayAuth播放方式播放点播视频,需要将播放器的vid属性设置为音视频ID,将playauth属性设置为音视频播放凭证。音视频ID可以在音视频上传完成后通过控制台(路径:媒资库 > 音/视频。)或服务端接口(SearchMedia )获取。音视频播放凭证可以调用获取音视频播放凭证接口获取。建议您集成点播服务端SDK来获取音视频播放凭证,免去自签名的麻烦。调用接口获取音视频播放凭证的示例请参见开发者门户 。

    推荐视频点播采用此播放方式。相比STS播放方式,PlayAuth播放方式在易用性和安全性上更有优势,对比详情请参见凭证方式与STS方式对比

   官网示例

var player = new Aliplayer({ 
    id: 'J_prismPlayer', 
    width: '100%', 
    vid : '<your video ID>',//必选参数。音视频ID可以在音视频上传完成后通过控制台(路径:媒资库 > 音/视频。)或服务端接口(SearchMedia )获取。示例:1e067a2831b641db90d570b6480f****。 
    playauth : '<your PlayAuth>',//必选参数。音视频播放凭证。
    },function(player){ 
    console.log('The player is created.') 
});

   STS播放

    使用STS播放方式播放点播视频是指用STS临时凭证而非点播音视频播放凭证播放。STS临时Token需要提前获取,获取方式请参见获取STS临时Token 。播放时需要将播放器的securityToken属性设置为STS临时Token,同时设置为STS临时Token生成的临时AK对(accessKeyIdaccessKeySecret)。

   官网示例

 var player = new Aliplayer({ 
     id: 'J_prismPlayer', width: '100%', 
     vid : '<your video ID>',//必选参数。音视频ID可以在音视频上传完成后通过控制台(路径:媒资库 > 音/视频。)或服务端接口(SearchMedia )获取。示例:1e067a2831b641db90d570b6480f****。 
     accessKeyId: '<your AccessKey ID>',//必选参数。生成STS临时Token时返回。
     securityToken: '<your STS token>',//必选参数。视频播放的临时凭证。凭证需要提前生成。生成方式请参考创建角色并进行STS临时授权。
     accessKeySecret: '<your AccessKey Secret>',//必选参数。生成STS临时Token时返回。 
     region: '<region of your video>', // 必选参数。媒体资源所在的地域标识。如cn-shanghai、eu-central-1, ap-southeast-1等。 
   },function(player){ 
     console.log('The player is created.') 
   }
 );

功能介绍

    这部分主要介绍一下,基本功能。以及实际项目中,会用到的功能。其他更详细的功能介绍,移步官网文档

     接口调用规则

    在创建播放器实例的回调函数中调用

//H5播放模式 
var player = new Aliplayer({},function(player) { 
    player.play(); 
});

     控制播放

   从指定时间开始播放

//time为指定的时间。单位: 秒。
player.seek(time)

   暂停播放

player.pause()

     获取播放信息

   获取当前播放进度

//接口返回的时间单位为秒。 
player.getCurrentTime()

   获取播放时长

player.getDuration()

     监听播放状态

   指监听播放器的状态。由getStatus接口实现。返回值包括:

   init:初始化

   ready:准备

   loading:加载中

   play:播放

   pause:暂停

   playing:正在播放

   waiting:等待缓冲

   error:错误

   ended:结束

player.getStatus()

     设置音量

tips

由于video.volume在iOS 和一些Android系统中是可读属性,阿里云Web播放器提供的音量调节方法getVolumesetVolume在iOS系统和部分Android系统会失效。

   音量调节

//设置音量大小。volume的值为0~1之间的实数。
player.setVolume(0) 
//获取音量信息。 
player.getVolume()

   静音设置

player.mute()

     倍速播放

tips

仅H5模式支持倍速播放。Web播放器SDK默认的UI自带倍速功能,用户观看时可通过界面选择倍速。如果自定义UI,可通过setSpeed接口实现倍速功能。

//设置倍速。以下示例表示设置为2倍速。 
player.setSpeed(2)

     自动播放

    由于浏览器自身的限制,在Web播放器SDK中无法通过设置autoplay属性或者调用play()方法实现自动播放。只有视频静音才可以实现自动播放或者通过用户行为手动触发播放(例如:初始化后,手动调用setVolume方法对视频进行静音处理)。

tips

桌面端浏览器有以下限制:

Safari浏览器:macOS High Sierra Safari 11及以上版本限制自动播放。

Chrome浏览器:Chrome 55及以上版本限制自动播放。

移动端浏览器不排除部分浏览器和WebView允许自动播放,Android系统中较为常见 以H5模式下的URL播放方式为例:

//初始化后,手动对视频进行静音处理 
var player = new Aliplayer({ 
    "id": "J_prismPlayer", 
    //您的播放地址。示例:example.aliyundoc.com/video/****.mp4 
    "source":"<your URL>", 
    "width": "100%", 
    "height": "500px", 
    "autoplay": true, 
    "preload": true, 
    "controlBarVisibility": "hover", 
    "useH5Prism": true 
    }, function (player) { 
    player.mute() 
    //[或者] player.setVolume(0) 
});

     自定义播放器外观和控件

    Web播放器SDK支持自定义播放器外观(播放器皮肤)、控件(播放器控制栏UI、报错UI等)是否显示以及显示的位置。

    播放栏控制UI

    通过修改skinLayout属性定制组件是否显示及显示位置。官网在配置skinLayout属性这一块写的太模糊了,文档不够详细。我直接在代码里注释一下对应组件的用途。

skinLayout:[ 
    //大播放器按钮
    { name: "bigPlayButton", align: "blabs", x: 30, y: 80}, 
    //缓冲时出现的loading图标
    { name: "H5Loading", align: "cc"}, 
    //错误UI
    { name: "errorDisplay", align: "tlabs", x: 0, y: 0}, 
    //信息提示
    { name: "infoDisplay"},
    //提示信息。播放器控件鼠标移上去会显示
    { name:"tooltip", align:"blabs",x: 0, y: 56}, 
    //缩略图
    { name: "thumbnail"}, 
    //控制栏
    { name: "controlBar", align: "blabs", x: 0, y: 0, 
      children: [ 
        //进度条
        {name: "progress", align: "blabs", x: 0, y: 44}, 
        //播放按钮
        {name: "playButton", align: "tl", x: 15, y: 12}, 
        //播放时间
        {name: "timeDisplay", align: "tl", x: 10, y: 7}, 
        //全屏按钮
        {name: "fullScreenButton", align: "tr", x: 10, y: 12}, 
        //字幕
        {name:"subtitle", align:"tr",x:15, y:12}, 
        //设置
        {name:"setting", align:"tr",x:15, y:12}, 
        //音量
        {name: "volume", align: "tr", x: 5, y: 10} 
      ] 
    } 
]

React项目 对接阿里云视频点播功能

插件组件

    这部分主要介绍一下如何引入组件以及项目中常用的2个组件记忆播放,试看的引入。

     引入组件

    1. 引入Web播放器SDK组件。

   tips: 这个也是属于有点拉跨的点,没有CDN资源,只能本地引入。然后下载链接好歹直接能下载文件啊,这个还得去github上自己拷贝代码。下载链接

<scripttype="text/javascript"charset="utf-8"src="/aliplayercomponents-1.0.9.min.js"></script>

    2. Web播放器SDK挂载组件。

var player = new Aliplayer({ 
    id: "J_prismPlayer", 
    source: "//player.alicdn.com/video/editor.mp4", 
    components: [{ 
        name: 'xxxComponent', 
        type: AliPlayerComponent.xxxComponent 
    }] 
    }, function (player) { 
});

    组件也可以单独使用,不过需要自己打包后,再引入,详情移步官网,这里就不详细介绍了

     记忆播放

    自动记忆用户上一次播放的视频位置,记忆播放组件分两种,一种点击播放(会提示用户上次播放的位置,用户可以点击seek),另一种是自动播放(自动seek到上次播放位置)

   tips:记忆播放组件使用localStorage记录播放位置,不支持localStorage的浏览器将不生效。

    引入当前组件, 播放器配置中添加如下代码:

components: [{
  name: 'MemoryPlayComponent',
  type: AliPlayerComponent.MemoryPlayComponent,
  /* Set the first parameter to true to enable auto play. The default is false. */
  args: [true,getTime,saveTime]
}]

    该组件接收3个参数:

    autoPlay,getTime,saveTime 返回的时间的单位都是秒

  • autoPlayBoolean, 可选参数, 默认为 false。是否启用自动播放。
  • getTime: 函数, 获取视频结束时间函数, 可选参数方法。不传则保存到localStorage。
  • saveTime: 函数, 自定义起播时间函数, 可选参数方法。不传则默认从localStorage获取起播时间。
const saveTime = function (memoryVideo,currentTime) {
	console.log(memoryVideo, currentTime)   
}

    saveTime 的参数就是一个视频标识对应一个结束时间

  • memoryVideoString, 必填参数。视频标识。
  • currentTimenumber, 必填参数。视频结束时间。
const getTime = function (memoryVideo) {
	/* return返回的是自定义起播时间  */
	return 20
}

    getTime 的参数就是一个视频标识

  • memoryVideoString, 必填参数。视频标识,可用作查询此视频播放到了哪里。

     试看组件

    用于用户试看, 试看结束后提示用户, 观看完整版的条件

    引入当前组件, 播放器配置中添加如下代码:

components: [{
  name: 'PreviewVodComponent',
  type: AliPlayerComponent.PreviewVodComponent,
  /**
   * 试看组件共有三个参数: previewDuration, previewEndHtml, previewBarHtml
   * previewDuration: 试看时长, 单位为秒, 如果不需要开启试看的话, 设置为0, 当开启会员或购买之后设置为0!
   * previewEndHtml: 试看结束之后, 出现在播放器中间的 Dom 字符串或者父节点的 '#' + id, 默认为 null
   * previewBarHtml: 播放器左下角, 提示试看时长之后的 Dom 字符串或者父节点的 '#' + id, 同样可自定义样式, 默认为 null
   * tips: previewEndHtml, previewBarHtml 可以像例子中的那样, <script type="text/template" id="endPreviewTemplate">  script 标签中插入html
   * tips: previewEndHtml, previewBarHtml 也可以直接使用Dom字符串, 推荐使用 es6 的模板字符串可以很方便地插入Dom字符串
   */
  args: [previewDuration, previewEndHtml, previewBarHtml]
}]

    该组件接收三个参数:

    previewDuration, previewEndHtml, previewBarHtml

  • previewDurationNumber 类型, 试看时长(单位为秒, 设置为 0 表示可以完整观看)
  • previewEndHtml, 试看结束后显示在播放器中间的 DOM 字符串, 可选参数, 默认为 null, 可以两种方式设置 previewEndHtml
/**
 * 方式一, script 标签设置 type="text/template", 并设置一个id
 * 在 script 中写入需要插入到 previewEndHtml 的 html
 * previewEndHtml 传入 script 的 id
 */

// html
<script type="text/template" id="endPreviewTemplate">
  <div>previewEndHtml 插入的元素</div>
</script>

// 试看组件参数配置, previewEndHtml 传入 '#endPreviewTemplate'
args: [previewDuration, '#endPreviewTemplate', previewBarHtml]

/**
 * 方式二, 直接传入 DOM 字符串
 */

// 试看组件参数配置, 使用 es6 模板语法, 传入字符串
args: [previewDuration, `<div>previewEndHtml 插入的元素</div>`, previewBarHtml]
  • previewBarHtml, 显示在播放器左下角的 DOM 字符串, 可选参数, 默认为 null, 和 previewEndHtml 的设置一样有两种方式设置 previewBarHtml

   自定义的 DOM 字符串都可以添加自定义事件

tips

 Vid+PlayAuth播放STS播放的对比

 这两种播放方式都有同一个问题:

 遮罩层的层级问题

 - 试看遮罩层的层级问题,会造成窗口滑轮向下滚动的时候,遮罩层盖住了其他元素并浮在上方,层级解决方案是通过修改css样式解决.

 在实践Vid+PlayAuth播放的过程中,单纯的前端引入该组件,实现的效果是不尽人意的。除了上述的问题外,还有这个问题(偶现):

 - 通过删除DOM节点class="preview-vod-component",将遮挡层去掉,点击进度条,播放器可以正常的播放视频了

 进度条解决方案得参照官方的点播试看实践文档

播放器组件封装

    本次组件封装,会将试看组件和记忆播放组件一起封装进去。可以自己配置是否使用对应组件。

       封装播放器组件

//STS方式,将playauth替换为OssToken相关信息
const H5Video = (props) => {
  const {
    getPlayer, //获取播放器实例
    bPreView = true//是否试看
    previewTime = 20//试看时间
    bMemory = true//是否记忆播放
    playauth, //音视频播放凭证
    vid,  //音视频ID
  } = props

  const [player, setPlayer] = useState(null)

  //memoryVideo 为当前视频的vid
  const getTime = (memoryVideo) => {
    //返回自定义起播的时间
    return localStorage.getItem(memoryVideo)
  }

  const saveTime = (memoryVideo: string, currentTime: string) => {
    /**
      * memoryVideo: String, 必填参数。视频标识。
      *currentTime: number, 必填参数。视频结束时间。
     */
    localStorage.setItem(memoryVideo, currentTime)
  }

  
  //记忆播放组件
  const memoryPlayComponent = useMemo(() => {
    const autoPlay = false
    return {
      name'MemoryPlayComponent', 
      typeAliPlayerComponent.MemoryPlayComponent,
      args: [autoPlay, getTime, saveTime],
    }
  }, [])

  //试看组件
  const preViewComponent = useMemo(() => {
    const previewEndHtml = `<div class="vip_limit_content">
      <div class="vip_limit_wrap">
        <p class="title">试看时间已过,购买课程立马观看</p>
        <div class="line"></div>
        <div class="vip_limit_button_box">
          <a class="vip_limit_btn">购买课程</a>
        </div>
        <div class="vip_limit_close">
          <img className="bg" src="./images/close.png" alt="" />
        </div>
        <div class="vip_limit_tips">
          关闭浮层,重新试看
        </div>
      </div>
    </div>`

  const previewBarHtml = `<a href="https://www.aliyun.com/product/vod" class="vip-join">
      开通会员
    </a> 观看完整视频`
    return {
      name'PreviewVodComponent', 
      typeAliPlayerComponent.PreviewVodComponent,
      args: [previewTime, previewEndHtml, previewBarHtml],
    }
  }, [previewTime])

  //自定义控件  如果不需要额外的自定义控件,可以不引入
  const skinLayout = useMemo(() => {
    return [
      { name'bigPlayButton'align'blabs'x30y80 }, //大播放按钮
      { name'H5Loading'align'cc' }, //h5加载动画
      { name'errorDisplay'align'tlabs'x0y0 }, //错误提示
      { name'infoDisplay' }, //信息提示
      { name'tooltip'align'blabs'x0y56 }, //按钮提示
      { name'thumbnail' }, //缩略图
      {
        name'controlBar'//控制条
        align'blabs',
        x0,
        y0,
        children: [
          { name'progress'align'blabs'x0y44 }, //进度条
          { name'playButton'align'tl'x15y12 }, //播放按钮
          { name'timeDisplay'align'tl'x10y7 }, //显示时间
          { name'fullScreenButton'align'tr'x10y12 }, //全屏按钮
          { name'subtitle'align'tr'x15y12 }, //字幕
          { name'setting'align'tr'x15y12 }, //设置
          { name'volume'align'tr'x5y10 }, //音量
          // { name: 'snapshot', align: 'tr', x: 10, y: 12 }, //截图
        ],
      },
    ]
  }, [])

  
 const defaultProps = useMemo(() => {
    const components = []
    if (bMemory) {
      components.push(memoryPlayComponent)
    }
    if (bPreView) {
      components.push(preViewComponent)
    }
    /**
      *STS方式,将playauth属性,更换为以下几个属性
      *accessKeyId, //必选参数。生成STS临时Token时返回。
      *securityToken, //必选参数。视频播放的临时凭证。凭证需要提前生成。生成方式请参考创建角色并进行STS临时授权。
      *accessKeySecret, //必选参数。生成STS临时Token时返回。
      *region: 'cn-shanghai', // 必选参数。媒体资源所在的地域标识。如cn-shanghai、eu-central-1, ap-southeast-1等。
     **/
    return {
      id'J_prismPlayer',
      width'100%',
      height'100%',
      autoplaytrue// 自动播放
      preloadtrue//播放器自动加载
      useH5Prism'h5'//指定使用H5播放器
      cover''//视频封面
      controlBarVisibility'hover'//控制面板的实现,默认hover  移动到播放器区域
      vid, //必选参数。音视频ID可以在音视频上传完成后通过控制台(路径:媒资库 > 音/视频。)
      playauth : '<your PlayAuth>',//必选参数。音视频播放凭证。
      components,
      skinLayout,
    }
  }, [bPreView, preViewComponent, bMemory, memoryPlayComponent, skinLayout, vid,playauth])

  
  useEffect(() => {
    if (!player) {
      setPlayer(
        new Aliplayer(defaultProps, (player: any) => {
          getPlayer(player)
          //获取试看结束的关闭按钮节点,为其绑定关闭事件
          const close_btn = document.querySelector('.vip_limit_close')
          if (close_btn) {
            close_btn.addEventListener('click'function () {
              console.log('关闭浮窗')
              player.getComponent('PreviewVodComponent').closePreviewLayer()
              //关闭浮窗后,重新试看
              player.replay()
            })
          }
        })
      )
    }
  }, [])

  
  useEffect(() => {
    return () => {
      if (player) {
        //获取当前的播放进度
        const currentTime = player.getCurrentTime()
        //将当前播放进度记录
        saveTime('b8d07720ac3471eda3ad0764a3fc0102', currentTime)
        //销毁实例
        player.dispose()
      }
    }
  }, [player])

  return <div id="J_prismPlayer"></div>
}

       使用播放器组件

const Father = () =>{
    const getPlayer = (player) =>{
      if(player){
         //设置静音  用于自动播放
         player.setVolume(0)
      }else{
         console.log('视频加载失败')
      }  
    }
    
    const playerInfo = {
        /**
          *播放器相关信息
        **/
    }
    
    return (
        <div>
            <H5Video getPlayer={getPlayer} {...playerInfo}/>
        </div>
    }
}

总结

    至此,关于阿里云视频点播功能的SDK上传视频播放的全链路功能介绍及实现介绍完毕。

    说实话,这个功能刚给我的时候,我压力还挺大的。毕竟时间紧张,我还有其他功能未完成。这个属于突然插入的需求。另外就是这方面的功能以前从未接触过。对着文档来实现功能以及和后端联调都很耗费大量时间和精力。

    前前后后,包括调试阿里云接口上传,参照文档实现对应功能,与官方的人沟通开发过程中产生的各种问题,与后端联调。中间穿插着,继续完成自身未完成的需求,总共大概花了快2周的时间。

    但是,总的来说,这次的功能需求既是挑战也是机遇吧。很好的锻炼了我的抗压能力,沟通能力,技术能力。

    福兮祸所伏,祸兮福所倚