腾讯云视频点播-web上传视频

环境:debian 13\php8.3\thinkphp8.1; 把上传后的视频及封面数据保存到mysql8.4中;

表:tp_video结构

sql 复制代码
CREATE TABLE `tp_video` (
    `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',

    `file_id` varchar(32) NOT NULL COMMENT '腾讯云VOD FileId',

    `video_url` varchar(1000) NOT NULL DEFAULT '' COMMENT '视频地址',
    `video_verify_content` varchar(1000) NOT NULL DEFAULT '' COMMENT '视频校验内容',

    `cover_url` varchar(1000) NOT NULL DEFAULT '' COMMENT '封面地址',
    `cover_verify_content` varchar(1000) NOT NULL DEFAULT '' COMMENT '封面校验内容',

    `status` tinyint unsigned NOT NULL DEFAULT '1' COMMENT '状态:0禁用 1正常',

    `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
    `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',

    PRIMARY KEY (`id`),
    UNIQUE KEY `uk_file_id` (`file_id`)
) ENGINE=InnoDB
DEFAULT CHARSET=utf8mb4
COLLATE=utf8mb4_0900_ai_ci
COMMENT='腾讯云点播视频表';

客户端代码:

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>QCloud VIDEO UGC UPLOAD SDK</title>
  <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet">
  <style type="text/css">
    .text-danger {
      color: red;
    }

    .control-label {
      text-align: left !important;
    }

    #resultBox {
      width: 100%;
      height: 300px;
      border: 1px solid #888;
      padding: 5px;
      overflow: auto;
      margin-bottom: 20px;
    }

    .uploaderMsgBox {
      width: 100%;
      border-bottom: 1px solid #888;
    }

    .cancel-upload {
      text-decoration: none;
      cursor: pointer;
    }
  </style>
</head>

<body>
  <div id="content">
    <div class="container">
      <h1>UGC-Uploader</h1>
    </div>
  </div>
  <div class="container" id="main-area">
    <div class="row" style="padding:10px;">
      <p>
        1.示例中的签名是直接从demo后台获取签名。该 demo 对应的点播账号已开启防盗链功能,此视频分发 URL 仅限 2 个独立 IP 进行访问,有效期为 10 分钟。<br>
        2.示例1点击"直接上传视频"按钮即可上传视频。<br>
        3.示例2点击"添加视频"添加视频文件,点击"添加封面"添加封面文件,然后点击"上传视频和封面"按钮即可上传视频和封面。<br>
        4.取消上传为取消上传中的视频,上传成功的视频不能取消上传。
      </p>
    </div>

    <form ref="vExample">
      <input type="file" style="display:none;" ref="vExampleFile" @change="vExampleUpload" />
    </form>
    <div class="row" style="padding:10px;">
      <h4>示例1:直接上传视频</h4>
      <a href="javascript:void(0);" class="btn btn-default" @click="vExampleAdd">直接上传视频</a>
    </div>

    <form ref="vcExample">
      <input type="file" style="display:none;" ref="vcExampleVideo" @change="setVcExampleVideoName()" />
      <input type="file" style="display:none;" ref="vcExampleCover" @change="setVcExampleCoverName()" />
    </form>
    <div class="row" style="padding:10px;">
      <h4>示例2:上传视频和封面</h4>
      <a href="javascript:void(0);" class="btn btn-default" @click="vcExampleAddVideo">{{vcExampleVideoName || '添加视频'}}</a>
      <a href="javascript:void(0);" class="btn btn-default" @click="vcExampleAddCover">{{vcExampleCoverName || '添加封面'}}</a>
      <a href="javascript:void(0);" class="btn btn-default" @click="vcExampleAddUpload">上传视频和封面</a>
    </div>

    <form ref="cExample">
      <input type="file" style="display:none;" ref="cExampleCover" @change="cExampleUpload" />
    </form>
    
    <div class="row" id="resultBox">
      <!-- 上传信息组件	 -->
      <div class="uploaderMsgBox" v-for="uploaderInfo in uploaderInfos">
        <div v-if="uploaderInfo.videoInfo">
          视频名称:{{uploaderInfo.videoInfo.name + '.' + uploaderInfo.videoInfo.type}};
          上传进度:{{Math.floor(uploaderInfo.progress * 100) + '%'}};
          fileId:{{uploaderInfo.fileId}};
          上传结果:{{uploaderInfo.isVideoUploadCancel ? '已取消' : uploaderInfo.isVideoUploadSuccess ? '上传成功' : '上传中'}};
          <br>
          地址:{{uploaderInfo.videoUrl}};
          <a href="javascript:void(0);" class="cancel-upload" v-if="!uploaderInfo.isVideoUploadSuccess && !uploaderInfo.isVideoUploadCancel" @click="uploaderInfo.cancel()">取消上传</a><br>
        </div>

        <div v-if="uploaderInfo.coverInfo">
          封面名称:{{uploaderInfo.coverInfo.name}};
          上传进度:{{Math.floor(uploaderInfo.coverProgress * 100) + '%'}};
          上传结果:{{uploaderInfo.isCoverUploadSuccess ? '上传成功' : '上传中'}};
          <br>
          地址:{{uploaderInfo.coverUrl}};
          <br>
        </div>
      </div>
    </div>

  </div>
  <script src="//cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.js"></script>
  <script src="//cdn-go.cn/cdn/vod-js-sdk-v6/latest/vod-js-sdk-v6.js"></script>

  <script type="text/javascript">
  ;(function () {

  /**
   * 计算签名
   */
  function getSignature() {
    return axios.post('/api/tencent/index/').then(function (response) {
      return response.data.signature;
    })
  }

  /**
   * 保存腾讯云上传结果到自己的数据库
   */
  function saveUploadResult(doneResult) {
    return axios.post('/api/tencent/saveVideo/', doneResult)
  }

  var app = new Vue({
    el: '#main-area',

    data: {
      uploaderInfos: [],

      vcExampleVideoName: '',
      vcExampleCoverName: '',

      cExampleFileId: '',
    },

    created: function () {
      this.tcVod = new TcVod.default({
        getSignature: getSignature
      })
    },

    methods: {

      /**
       * 示例1
       * 添加视频
       */
      vExampleAdd: function () {
        this.$refs.vExampleFile.click()
      },

      /**
       * 示例1
       * 直接上传视频
       */
      vExampleUpload: function () {

        var self = this;

        var mediaFile = this.$refs.vExampleFile.files[0];

        if (!mediaFile) {
          return;
        }

        var uploader = this.tcVod.upload({
          mediaFile: mediaFile,
        });

        var uploaderInfo = {
          videoInfo: uploader.videoInfo,
          isVideoUploadSuccess: false,
          isVideoUploadCancel: false,
          progress: 0,
          fileId: '',
          videoUrl: '',
          cancel: function () {
            uploaderInfo.isVideoUploadCancel = true;
            uploader.cancel();
          },
        };

        /**
         * 上传进度
         */
        uploader.on('media_progress', function (info) {
          uploaderInfo.progress = info.percent;
        });

        /**
         * 视频文件上传完成
         */
        uploader.on('media_upload', function (info) {
          uploaderInfo.isVideoUploadSuccess = true;
        });

        console.log(uploader, 'uploader');
        this.uploaderInfos.push(uploaderInfo);
        /**
         * 腾讯云整个上传流程完成
         */
        uploader.done().then(function (doneResult) {
            console.log('doneResult', doneResult);
            /**
             * 原有功能保留
             */
            uploaderInfo.fileId = doneResult.fileId;
            uploaderInfo.videoUrl = doneResult.video.url;
            /**
             * 保存到自己的 MySQL
             */
            return saveUploadResult(doneResult);

          }).then(function (response) {
            console.log('数据库保存结果:', response.data);
            if (response.data.code !== 0) {
              console.error('数据库保存失败:', response.data.msg);
            }
            /**
             * 原有重置功能保留
             */
            self.$refs.vExample.reset();
          }).catch(function (error) {
            console.error('视频上传或数据库保存失败:', error);
          });
      },


      /**
       * 示例2
       * 选择视频
       */
      setVcExampleVideoName: function () {
        var file = this.$refs.vcExampleVideo.files[0];
        if (file) {
          this.vcExampleVideoName = file.name;
        }
      },


      /**
       * 示例2
       * 选择封面
       */
      setVcExampleCoverName: function () {
        var file = this.$refs.vcExampleCover.files[0];
        if (file) {
          this.vcExampleCoverName = file.name;
        }
      },


      /**
       * 示例2
       * 添加视频
       */
      vcExampleAddVideo: function () {
        this.$refs.vcExampleVideo.click()
      },


      /**
       * 示例2
       * 添加封面
       */
      vcExampleAddCover: function () {
        this.$refs.vcExampleCover.click()
      },


      /**
       * 示例2
       * 上传视频 + 封面
       */
      vcExampleAddUpload: function () {

        var self = this;

        var mediaFile = this.$refs.vcExampleVideo.files[0];
        var coverFile = this.$refs.vcExampleCover.files[0];

        if (!mediaFile) {
          console.error('请选择视频文件');
          return;
        }

        if (!coverFile) {
          console.error('请选择封面文件');
          return;
        }

        var uploader = this.tcVod.upload({
          mediaFile: mediaFile,
          coverFile: coverFile,
        });

        var uploaderInfo = {
          videoInfo: uploader.videoInfo,
          coverInfo: uploader.coverInfo,
          isVideoUploadSuccess: false,
          isVideoUploadCancel: false,
          isCoverUploadSuccess: false,
          progress: 0,
          coverProgress: 0,
          fileId: '',
          videoUrl: '',
          coverUrl: '',
          cancel: function () {
            uploaderInfo.isVideoUploadCancel = true;
            uploader.cancel();
          },
        };

        /**
         * 视频上传进度
         */
        uploader.on('media_progress', function (info) {
          uploaderInfo.progress = info.percent;
        });

        /**
         * 视频文件上传完成
         */
        uploader.on('media_upload', function (info) {
          uploaderInfo.isVideoUploadSuccess = true;
        });

        /**
         * 封面上传进度
         */
        uploader.on('cover_progress', function (info) {
          uploaderInfo.coverProgress = info.percent;
        });

        /**
         * 封面文件上传完成
         */
        uploader.on('cover_upload', function (info) {
          uploaderInfo.isCoverUploadSuccess = true;
        });

        console.log(uploader, 'uploader');

        this.uploaderInfos.push(uploaderInfo);

        /**
         * 腾讯云上传完成
         */
        uploader.done().then(function (doneResult) {
            console.log('doneResult', doneResult);
            /**
             * 原有功能全部保留
             */
            uploaderInfo.fileId = doneResult.fileId;

            if (doneResult.video) {
              uploaderInfo.videoUrl = doneResult.video.url;
            }

            if (doneResult.cover) {
              uploaderInfo.coverUrl = doneResult.cover.url;
            }

            /**
             * 把腾讯云返回结果保存数据库
             */
            return saveUploadResult(doneResult);

          }).then(function (response) {
            console.log('数据库保存结果:', response.data);
            if (response.data.code !== 0) {
              console.error('数据库保存失败:', response.data.msg);
            }
            /**
             * 原有重置功能保留
             */
            self.$refs.vcExample.reset();
            self.vcExampleVideoName = '';
            self.vcExampleCoverName = '';
          }).catch(function (error) {
            console.error('上传或数据库保存失败:', error);
          });
      },


      /**
       * 示例3
       * 添加封面
       */
      cExampleAddCover: function () {
        this.$refs.cExampleCover.click()
      },


      /**
       * 示例3
       * 为已经存在的 fileId 上传封面
       */
      cExampleUpload: function () {

        var self = this;

        var coverFile = this.$refs.cExampleCover.files[0];

        if (!coverFile) {
          console.error('请选择封面');
          return;
        }

        if (!this.cExampleFileId) {
          console.error('请输入 fileId');
          return;
        }

        var uploader = this.tcVod.upload({fileId: this.cExampleFileId, coverFile: coverFile});

        var uploaderInfo = {
          coverInfo: uploader.coverInfo,
          isCoverUploadSuccess: false,
          coverProgress: 0,
          coverUrl: '',
          cancel: function () {
            uploader.cancel();
          },
        };

        /**
         * 封面上传进度
         */
        uploader.on(
          'cover_progress',
          function (info) {
            uploaderInfo.coverProgress = info.percent;
          }
        );

        /**
         * 封面上传成功
         */
        uploader.on(
          'cover_upload',
          function (info) {
            uploaderInfo.isCoverUploadSuccess = true;
          }
        );

        console.log(uploader, 'uploader');

        this.uploaderInfos.push(uploaderInfo);

        uploader.done().then(function (doneResult) {
            console.log('doneResult',doneResult);
            if (doneResult.cover) {
              uploaderInfo.coverUrl = doneResult.cover.url;
            }
            /**
             * 注意:
             * 单独上传封面返回的数据结构
             * 可能没有 video。
             *
             * 我们补上 fileId,
             * 方便 TP8 更新数据库。
             */
            doneResult.fileId = self.cExampleFileId;
            /**
             * 保存/更新数据库
             */
            return saveUploadResult(doneResult);
          }).then(function (response) {
            console.log('封面数据库更新结果:', response.data);
            self.$refs.cExample.reset();
          }).catch(function (error) {
            console.error('封面上传或保存失败:', error);
          });
      },
    },
  });
})();
</script>
</body>
</html>

后端上传签名:

php 复制代码
<?php
declare (strict_types = 1);

namespace app\api\controller;

use think\response\Json;
use think\response\View;

class Tencent
{
    /**
     * 显示资源列表
     *
     * @return \think\Response
     */
    public function index(): Json
    {
        // 确定 App 的云 API 密钥
        $secret_id = "你的ID";
        $secret_key = "你的key";
        
        // 确定签名的当前时间和失效时间
        $current = time();
        $expired = $current + 86400;  // 签名有效期:1天
        
        // 向参数列表填入参数
        $arg_list = array(
            "secretId" => $secret_id,
            "currentTimeStamp" => $current,
            "expireTime" => $expired,
            "random" => rand());
        
        // 计算签名
        $original = http_build_query($arg_list);
        $signature = base64_encode(hash_hmac('SHA1', $original, $secret_key, true).$original);
        
        return json(['signature' => $signature]);
    }
    
    public function uploadVideo(): View
    {
        return view('/tencent-upload');
    }

    public function saveVideo(): Json
    {
        if (!request()->isPost()) {
            return json(['code' => 1, 'msg'  => '非法请求']);
        }
    
        try {
            $data = request()->post();
            if (empty($data)) {
                $data = json_decode(request()->getContent(), true);
            }
    
            if (!is_array($data)) {
                return json(['code' => 1, 'msg'  => 'JSON数据格式错误']);
            }
    
    
            $fileId = trim((string) ($data['fileId'] ?? ''));
    
            if ($fileId === '') {
                return json(['code' => 1, 'msg'  => 'fileId不能为空']);
            }
            
            $saveData = [
                'video_url' => (string)($data['video']['url'] ?? ''),
                'video_verify_content' => (string)($data['video']['verify_content'] ?? ''),
                'cover_url' => (string)($data['cover']['url'] ?? ''),
                'cover_verify_content' => (string)($data['cover']['verify_content'] ?? ''),
                'status' => 1,
                'updated_at' => date('Y-m-d H:i:s'),
            ];
            /*
             * 查询 fileId
             */
            $id = Db::name('video')->where('file_id', $fileId)->value('id');
    
    
            if ($id) {
                /*
                 * 已存在 → 更新
                 */
                Db::name('video')->where('id', $id)->update($saveData);
            } else {
                /*
                 * 不存在 → 新增
                 */
                $saveData['file_id'] = $fileId;
                $saveData['created_at'] = date('Y-m-d H:i:s');
                $id = Db::name('video')->insertGetId($saveData);
            }
            
            return json([
                'code' => 0,
                'msg'  => '保存成功',
                'data' => [
                    'id'        => $id,
                    'file_id'   => $fileId,
                    'video_url' => $saveData['video_url'],
                    'cover_url' => $saveData['cover_url'],
                ]
            ]);
    
        } catch (\Throwable $e) {
            return json(['code' => 1, 'msg'  => '保存失败:' . $e->getMessage()]);
        }
    }
}
相关推荐
API快乐传递者1 小时前
1688 跨境电商 API 接口实战指南:从寻源到代采的全链路技术方案
java·前端·数据库
马可家的菠萝1 小时前
收藏不是终点:一个真正有用的个人知识库,至少要完成“收集 → 理解 → 行动”
前端·后端·架构
Kari112 小时前
AgentOps 七大闭环 vs 传统 Agent 开发:腾讯云 ADP CSP 授权服务商 JOTO 解析生产级落地的本质差异
大数据·人工智能·腾讯云
明月_清风2 小时前
看完 DSH 文档后,我总结了这 7 个关键点
前端·后端·deepseek
qq_452396232 小时前
第八篇:《Monorepo 实战:Turborepo 与 pnpm workspace 驱动的大型项目协作》
前端
数据狐(Datafox)2 小时前
1688.item_search工程实战:1688商品列表API分页采集与B2B货源数据分析落地
java·前端·数据分析
铁皮饭盒2 小时前
浏览器AI肯定离不开onnxruntime, 微软开源
前端·javascript·后端
vivo互联网技术2 小时前
vivo 广告小游戏:从"写代码"到"说需求"
前端·人工智能·游戏开发