阿里雲視訊點播上傳,整合 Dcat admin

MArtian發表於2021-10-20

最近做的專案需要用到阿里雲視訊點播功能,管理後臺使用 Dcat admin 開發,網上關於阿里雲視訊點播的案例幾乎都是 JAVA 或者 其他後端,沒有使用前端上傳的,這裡記錄一下。

1. 首先配置好阿里雲 RAM 賬號,準備好 access_keysecret

「我奶奶看了都會」阿里雲視訊點播上傳,整合 Dcat admin
配置配置管理阿里雲視訊點播許可權

「我奶奶看了都會」阿里雲視訊點播上傳,整合 Dcat admin

2. 安裝阿里雲的SDK

composer required 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 協議》,轉載必須註明作者和本文連結
我從未見過一個早起、勤奮、謹慎,誠實的人抱怨命運。

相關文章