最近做的專案需要用到阿里雲視訊點播功能,管理後臺使用 Dcat admin 開發,網上關於阿里雲視訊點播的案例幾乎都是 JAVA 或者 其他後端,沒有使用前端上傳的,這裡記錄一下。
1. 首先配置好阿里雲 RAM 賬號,準備好 access_key
和 secret
配置配置管理阿里雲視訊點播許可權
2. 安裝阿里雲的SDK
composer required 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
將就看。
本作品採用《CC 協議》,轉載必須註明作者和本文連結