最近做的项目需要用到阿里云视频点播功能,管理后台使用 Dcat admin 开发,网上关于阿里云视频点播的案例几乎都是 JAVA 或者 其他后端,没有使用前端上传的,这里记录一下。
1. 首先配置好阿里云 RAM 账号,准备好 access_key
和 secret
配置配置管理阿里云视频点播权限
2. 安装阿里云的SDK
composer require alibabacloud/sdk
3. 创建分类与转码模板
我们要对上传的视频进行转码和压缩,这样可以节省流量,最好对视频做好分类,这样比较好管理。
转码模板
视频分类
记住这两个的 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. 转码回调
这里的连接地址必须是 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')); | |
}} | |
... | |
} |
水平有限,就只能做到这种程度了,不会 VUE
,JQUERY
将就看。