阿里云视频点播上传,集成 Dcat admin

最近做的项目需要用到阿里云视频点播功能,管理后台使用 Dcat admin 开发,网上关于阿里云视频点播的案例几乎都是 JAVA 或者 其他后端,没有使用前端上传的,这里记录一下。

1. 首先配置好阿里云 RAM 账号,准备好 access_keysecret

「我奶奶看了都会」阿里云视频点播上传,集成 Dcat admin
配置配置管理阿里云视频点播权限

「我奶奶看了都会」阿里云视频点播上传,集成 Dcat admin

2. 安装阿里云的SDK

composer require alibabacloud/sdk

3. 创建分类与转码模板

我们要对上传的视频进行转码和压缩,这样可以节省流量,最好对视频做好分类,这样比较好管理。

转码模板

「我奶奶看了都会」阿里云视频点播上传,集成 Dcat admin

视频分类

「我奶奶看了都会」阿里云视频点播上传,集成 Dcat admin
记住这两个的 ID,后面会用到

4. 编写授权接口

由于我们是前端上传,需要先请求后端接口拿到授权。
路由注册

Route::get('/api/upload/video', [VideoHandler::class, 'createClient'])
use AlibabaCloud\Client\Exception\ClientException;
use AlibabaCloud\Client\Exception\ServerException;
use AlibabaCloud\Vod\Vod;
use Illuminate\Http\Request;

class VideoHandler{
public function createClient(Request $request)
    {
        try {
            $user_param = json_decode($request->post('extend'));  // 这里我使用了额外参数,用来定义上传文档归属的分类和转码模板
            $tempGroupId = $user_param->Extend->type == 'video' ? 'XXXX' : 'XXXX'; // 这里我需要判断前端要上传的是 视频还是音频从而选择不同的转码模板
            $cateId = $user_param->Extend->category;
            unset($user_param->Extend->type);
            unset($user_param->Extend->category);
            $videoRequest = Vod::v20170321()->createUploadVideo();
            $result = $videoRequest
                ->withFileName($request->post('filename')) // 文件名
                ->withTitle($request->post('name')) // 标题
                ->withTemplateGroupId($tempGroupId) // 转码模板
                ->withCateId($cateId)  // 类别ID
                ->withUserData(json_encode($user_param)) // 用户额外参数,用作回调使用
                ->request();
            return $result->toArray();
        } catch (ClientException $exception) {
            echo $exception->getMessage() . PHP_EOL;
        } catch (ServerException $exception) {
            echo $exception->getMessage() . PHP_EOL;
            echo $exception->getErrorCode() . PHP_EOL;
            echo $exception->getRequestId() . PHP_EOL;
            echo $exception->getErrorMessage() . PHP_EOL;
        }
    }
}

5. Dcat admin 前端发起请求

protected function form()
    {
        return Form::make(new AudiovisualAnimation(), function (Form $form) {   
            $form->html(view('uploads.upload-auth',
                [
                    'type' => 'video', // 媒体文件类型
                    'category' => 'xxxx', // 上面创建的视频分类ID 
                ]),
                '视频文件1')->required();
            $form->hidden('video1');  // 这个表单必须要有,我们要把上面的 Html 处理后的回调赋值到这个 `video1` 
    }

上传视图

// uploads.upload-auth
<div class="upload">
    <div class="file">
        <div>
            <input type="file" class="media-file" onchange="uploadInitial(this, '{{$type}}', '{{$category}}')">
        </div>
        <div class="upload-type">
            <label class="status"></label>
            <div class="upload-progress progress progress-bar-primary pull-left" style="display:none; width: 35%; margin-top: 10px;">
                <div class="progress-bar progress-bar-striped active" style="line-height: 18px; width: 0;">0%</div>
            </div>
        </div>
    </div>
</div>

相关JS,代码都是现成的,拿过去用就好使,如果有特殊需要自己去看 API 文档

//兼容IE11
if (!FileReader.prototype.readAsBinaryString) {
    FileReader.prototype.readAsBinaryString = function (fileData) {
        var binary = "";
        var pt = this;
        var reader = new FileReader();
        reader.onload = function (e) {
            var bytes = new Uint8Array(reader.result);
            var length = bytes.byteLength;
            for (var i = 0; i < length; i++) {
                binary += String.fromCharCode(bytes[i]);
            }
            //pt.result  - readonly so assign binary
            pt.content = binary;
            pt.onload()
        }
        reader.readAsArrayBuffer(fileData);
    }
}
var uploader;

/**
 * 创建一个上传对象
 * 使用 UploadAuth 上传方式
 */
function createUploader(fileEle, extend) {
    let status = fileEle.parent().next('.upload-type').children('.status')
    let progressEle = fileEle.parent().next('.upload-type').children('.progress');
    let uploader = new AliyunUpload.Vod({
        timeout: 60000,
        partSize: 1048576,
        parallel: 5,
        retryCount: 3,
        retryDuration: 2,
        userId: '1308118194043180',
        // 添加文件成功
        addFileSuccess: function (uploadInfo) {
            status.text('添加文件成功, 等待上传...')
            progressEle.show()
            console.log("addFileSuccess: " + uploadInfo.file.name)
        },
        // 开始上传
        onUploadstarted: function (uploadInfo) { 
        // 地址和凭证接口(https://help.aliyun.com/document_detail/55407.html)
            let createUrl = '/api/upload/video'  // 授权接口地址
            $.ajax({
                type: 'post',
                url: createUrl,
                headers: {
                    "Access-Control-Allow-Origin": "*"
                },
                data: {
                    'filename': uploadInfo.file.name,
                    'name': uploadInfo.file.name,
                    'extend': extend,
                },
                success: function (data) {
                    console.log(data)
                    let uploadAuth = data.UploadAuth
                    let uploadAddress = data.UploadAddress
                    let videoId = data.VideoId
                    uploader.setUploadAuthAndAddress(uploadInfo, uploadAuth, uploadAddress, videoId)
                }
            })
            status.text('上传状态: 文件开始上传...')
            console.log("onUploadStarted:" + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object)
        },
        // 文件上传成功
        onUploadSucceed: function (uploadInfo) {
            console.log("onUploadSucceed: " + uploadInfo.file.name + ", endpoint:" + uploadInfo.endpoint + ", bucket:" + uploadInfo.bucket + ", object:" + uploadInfo.object)
            fileEle.parents('.form-group').next('input[type="hidden"]').val(uploadInfo.videoId)
            status.text('上传状态: 文件上传成功!')
            progressEle.hide();
        },
        // 文件上传失败
        onUploadFailed: function (uploadInfo, code, message) {
            console.log("onUploadFailed: file:" + uploadInfo.file.name + ",code:" + code + ", message:" + message)
            status.text('上传状态: 文件上传失败!')
        }, 
        // 文件上传进度,单位:字节, 可以在这个函数中拿到上传进度并显示在页面上
        onUploadProgress: function (uploadInfo, totalSize, progress) {
            // console.log("onUploadProgress:file:" + uploadInfo.file.name + ", fileSize:" + totalSize + ", percent:" + Math.ceil(progress * 100) + "%")
            let progressPercent = Math.ceil(progress * 100) + '%';
            // console.log(progressPercent)
            progressEle.children('.progress-bar').text(progressPercent)
            progressEle.children('.progress-bar').width(progressPercent)
            status.text('上传状态: 文件上传中...')
        }, 
        // 全部文件上传结束
        onUploadEnd: function (uploadInfo) {
            status.css('color', '#21b978').text('上传状态: 文件上传完毕!')
        }
    })
    return uploader
}

$(document).on('pjax:end', function() {
    $('.upload').each(function(){
        if ($(this).parents('.form-group').next("input[type='hidden']").val()) {
            $(this).children('.file').children('.upload-type').children('.status').css('color', '#cc0000').text('文件已上传,重新上传会替换现有文件')
        }
    })
}).trigger('pjax:end');

function uploadInitial(e, ...param) {
    let _this = $(e);
    let file = e.files[0]
    console.log(e)
    console.log(e.files)
    if (!file) {
        alert("请先选择需要上传的文件!")
        return
    }
    let userData = '{"Extend":{' +
        '"type":"' + param['0'] +
        '","category":"' + param['1'] +
        '"}}';
    uploader = createUploader(_this, userData)
    uploader.addFile(file)
    uploader.startUpload()
    _this.next('.authUpload').attr('disabled', false)
}

一些样式

.container {
    width: 1200px;
    margin: 0 auto;
}

.input-control {
    margin: 5px 0;
}

.input-control label {
    font-size: 14px;
    color: #333;
    width: 30%;
    text-align: right;
    display: inline-block;
    vertical-align: middle;
    margin-right: 10px;
}

.input-control input {
    width: 30%;
    height: 30px;
    padding: 0 5px;
}

.progress {
    font-size: 14px;
}

.progress i {
    font-style: normal;
}

.upload-type {
    color: #666;
    font-size: 12px;
    padding: 10px 0;
}
.upload{
    padding-top: 5px;
}
.upload-type button {
    margin: 0 10px 0 20px;
}

.status {
    font-size: 12px;
    display: block;
}

.info {
    font-size: 14px;
    padding-left: 30px;
}

到这上传就结束了,此时 Dcat admin 表单的 video1 的值为阿里云视频点播的 RequestId 直接保存就行了,但是现在还不能播放,我们还要去处理转码后的回调。


6. 转码回调

「我奶奶看了都会」阿里云视频点播上传,集成 Dcat admin

这里的连接地址必须是 http 的, https 不兼容,建议开启回调鉴权。

7. 编写回调接口

class VideoHandler
{
...
/**
 * 视频转码完成回调
  */
    public function callback(Request $request)
    {
    // 回调鉴权
      if(md5(config('video.callback_url') .'|'. $request->header('X-VOD-TIMESTAMP') .'|'. config('video.key')) == $request->header('X-VOD-SIGNATURE') && $request->post('Status') == 'success')
     {  
     // 把回调播放地址先扔进缓存里,然后下次要播放的时候直接用记录的 RequestId 去缓存中找相对应的播放地址,然后删除缓存,替换 RequestId 为播放地址。我是这么处理的,具体看你的业务逻辑
     Cache::set($request->post('VideoId'), $request->post('FileUrl'));
     }}
...
}

水平有限,就只能做到这种程度了,不会 VUEJQUERY 将就看。

本作品采用《CC 协议》,转载必须注明作者和本文链接
悲观者永远正确,乐观者永远前行。
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 5

感谢分享

2年前 评论
running8

实用,感谢

2年前 评论

composer require alibabacloud/sdk 写错了

1年前 评论
MArtian (楼主) 1年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!