Ver código fonte

视频批量上传和原视频修改

yuhongqi 3 semanas atrás
pai
commit
dcef449d16
2 arquivos alterados com 377 adições e 2 exclusões
  1. 18 0
      src/api/live/liveVideo.js
  2. 359 2
      src/views/live/liveVideo/index.vue

+ 18 - 0
src/api/live/liveVideo.js

@@ -74,4 +74,22 @@ export function batchUpdateCategory(data) {
     method: 'put',
     data: data
   })
+}
+
+// 更新视频URL并重置转码状态
+export function updateVideoUrl(data) {
+  return request({
+    url: '/live/liveVideo/updateVideoUrl',
+    method: 'put',
+    data: data
+  })
+}
+
+// 批量新增直播视频
+export function batchAddLiveVideo(data) {
+  return request({
+    url: '/live/liveVideo/batchAdd',
+    method: 'post',
+    data: data
+  })
 }

+ 359 - 2
src/views/live/liveVideo/index.vue

@@ -88,6 +88,15 @@
           @click="handleBatchUpdateCategory"
         >批量修改分类</el-button>
       </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="primary"
+          plain
+          icon="el-icon-upload"
+          size="mini"
+          @click="handleBatchUpload"
+        >批量上传视频</el-button>
+      </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
@@ -136,6 +145,12 @@
             icon="el-icon-edit"
             @click="handleUpdateCompanyIds(scope.row)"
           >修改公司范围</el-button>
+          <el-button
+            size="mini"
+            type="text"
+            icon="el-icon-upload"
+            @click="handleReuploadVideo(scope.row)"
+          >再次上传视频</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -323,13 +338,91 @@
         <el-button @click="cancelBatchUpdateCategory">取 消</el-button>
       </div>
     </el-dialog>
+
+    <!-- 批量上传视频对话框 -->
+    <el-dialog title="批量上传视频" :visible.sync="batchUploadDialog" width="900px" append-to-body>
+      <div style="margin-bottom: 15px;">
+        <el-upload
+          ref="batchUpload"
+          action="#"
+          :http-request="handleBatchUploadRequest"
+          accept=".mp4"
+          :multiple="true"
+          :auto-upload="false"
+          :on-change="handleBatchFileChange"
+          :on-remove="handleBatchFileRemove"
+          :file-list="batchUploadFileList"
+        >
+          <el-button slot="trigger" size="small" type="primary">选择视频文件</el-button>
+          <el-button style="margin-left: 10px;" size="small" type="success" @click="submitBatchUpload">开始上传</el-button>
+          <div slot="tip" class="el-upload__tip">只能上传mp4文件,且每个文件不超过1G,可同时选择多个文件</div>
+        </el-upload>
+      </div>
+      <div v-if="batchUploadVideos.length > 0" style="margin-top: 20px;">
+        <div v-for="(video, index) in batchUploadVideos" :key="index" style="margin-bottom: 20px; border: 1px solid #e4e7ed; padding: 15px; border-radius: 4px;">
+          <div style="margin-bottom: 10px; font-weight: bold;">{{ video.fileName }}</div>
+          <div class="progress-container" style="width:100% !important;">
+            <span class="progress-label">上传进度</span>
+            <el-progress
+              style="margin-top: 5px;"
+              class="progress"
+              :text-inside="true"
+              :stroke-width="18"
+              :percentage="video.progress"
+              :status="video.status"
+            ></el-progress>
+          </div>
+          <div v-if="video.status === 'success'" style="margin-top: 10px; color: #67c23a; font-size: 12px;">
+            上传成功
+          </div>
+          <div v-else-if="video.status === 'exception'" style="margin-top: 10px; color: #f56c6c; font-size: 12px;">
+            上传失败: {{ video.errorMsg || '未知错误' }}
+          </div>
+        </div>
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="saveBatchUploadVideos" :disabled="!canSaveBatchUpload">保 存</el-button>
+        <el-button @click="cancelBatchUpload">取 消</el-button>
+      </div>
+    </el-dialog>
+
+    <!-- 重新上传视频对话框 -->
+    <el-dialog title="重新上传视频" :visible.sync="reuploadVideoDialog" width="800px" append-to-body>
+      <el-form ref="reuploadVideoForm" :model="reuploadVideoForm" label-width="120px">
+        <el-form-item label="视频名称">
+          <el-input v-model="reuploadVideoForm.videoName" :disabled="true" />
+        </el-form-item>
+        <live-video-upload
+          :type="1"
+          :isPrivate="isPrivate"
+          :fileKey.sync="reuploadVideoForm.fileKey"
+          :fileSize.sync="reuploadVideoForm.fileSize"
+          :videoUrl.sync="reuploadVideoForm.videoUrl"
+          :fileName.sync="reuploadVideoForm.fileName"
+          :line_1.sync="reuploadVideoForm.lineOne"
+          :thumbnail.sync="reuploadVideoForm.thumbnail"
+          :uploadType.sync="reuploadVideoForm.uploadType"
+          :isTranscode.sync="reuploadVideoForm.isTranscode"
+          :transcodeFileKey.sync="reuploadVideoForm.transcodeFileKey"
+          @video-duration="handleReuploadVideoDuration"
+          @change="handleReuploadVideoChange"
+          ref="reuploadVideoUpload"
+          append-to-body
+        />
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitReuploadVideo">确 定</el-button>
+        <el-button @click="cancelReuploadVideo">取 消</el-button>
+      </div>
+    </el-dialog>
   </div>
 </template>
 
 <script>
-import { listLiveVideo, getLiveVideo, delLiveVideo, addLiveVideo, updateLiveVideo, exportLiveVideo, batchUpdateCategory } from "@/api/live/liveVideo";
+import { listLiveVideo, getLiveVideo, delLiveVideo, addLiveVideo, updateLiveVideo, exportLiveVideo, batchUpdateCategory, updateVideoUrl, batchAddLiveVideo } from "@/api/live/liveVideo";
 import { getCompanyList } from "@/api/company/company";
 import LiveVideoUpload from "@/components/LiveVideoUpload/index.vue";
+import { uploadObject } from "@/utils/cos.js";
 
 export default {
   name: "LiveVideo",
@@ -414,7 +507,29 @@ export default {
       // 所有公司选项(用于下拉选择)
       allCompanyOptions: [],
       // 公司选项加载状态
-      companyOptionsLoading: false
+      companyOptionsLoading: false,
+      // 是否显示批量上传对话框
+      batchUploadDialog: false,
+      // 批量上传视频数据
+      batchUploadVideos: [],
+      // 批量上传文件列表
+      batchUploadFileList: [],
+      // 是否显示重新上传视频对话框
+      reuploadVideoDialog: false,
+      // 重新上传视频表单
+      reuploadVideoForm: {
+        videoId: null,
+        videoName: '',
+        videoUrl: '',
+        fileKey: '',
+        fileSize: null,
+        fileName: '',
+        lineOne: '',
+        thumbnail: '',
+        uploadType: 1,
+        isTranscode: 0,
+        transcodeFileKey: null
+      }
     };
   },
   created() {
@@ -823,6 +938,237 @@ export default {
       }).catch(() => {
         this.msgError("批量修改失败");
       });
+    },
+    /** 打开批量上传视频对话框 */
+    handleBatchUpload() {
+      this.batchUploadFileList = [];
+      this.batchUploadVideos = [];
+      this.batchUploadDialog = true;
+    },
+    /** 取消批量上传 */
+    cancelBatchUpload() {
+      this.batchUploadDialog = false;
+      this.batchUploadFileList = [];
+      this.batchUploadVideos = [];
+      if (this.$refs.batchUpload) {
+        this.$refs.batchUpload.clearFiles();
+      }
+    },
+    /** 批量文件变化 */
+    handleBatchFileChange(file, fileList) {
+      const MAX_SIZE = 1024 * 1024 * 1024;
+      if (file.raw && file.raw.size > MAX_SIZE) {
+        this.$message.error(`${file.name} 文件大小不能超过1G`);
+        // 从文件列表中移除
+        const index = fileList.findIndex(f => f.uid === file.uid);
+        if (index > -1) {
+          fileList.splice(index, 1);
+        }
+        return;
+      }
+      this.batchUploadFileList = fileList;
+
+      // 初始化上传数据
+      const index = this.batchUploadVideos.length;
+      this.batchUploadVideos.push({
+        file: file.raw,
+        fileName: file.name,
+        fileSize: file.size,
+        videoUrl: '',
+        fileKey: '',
+        duration: 0,
+        progress: 0,
+        status: '',
+        errorMsg: ''
+      });
+
+      // 获取视频时长
+      this.getBatchVideoDuration(file.raw, index);
+    },
+    /** 批量文件移除 */
+    handleBatchFileRemove(file, fileList) {
+      this.batchUploadFileList = fileList;
+      const index = this.batchUploadVideos.findIndex(v => v.fileName === file.name);
+      if (index > -1) {
+        this.batchUploadVideos.splice(index, 1);
+      }
+    },
+    /** 获取批量视频时长 */
+    getBatchVideoDuration(file, index) {
+      const video = document.createElement("video");
+      video.preload = "metadata";
+      video.onloadedmetadata = () => {
+        window.URL.revokeObjectURL(video.src);
+        const duration = parseInt(video.duration.toFixed(2));
+        if (this.batchUploadVideos[index]) {
+          this.batchUploadVideos[index].duration = duration;
+        }
+      };
+      video.src = URL.createObjectURL(file);
+    },
+    /** 批量上传请求处理 */
+    handleBatchUploadRequest(options) {
+      // 这个方法不会被调用,因为我们使用auto-upload=false
+    },
+    /** 开始批量上传 */
+    async submitBatchUpload() {
+      if (this.batchUploadVideos.length === 0) {
+        this.msgWarning("请先选择视频文件");
+        return;
+      }
+
+      // 重置所有视频状态
+      this.batchUploadVideos.forEach(video => {
+        video.progress = 0;
+        video.status = '';
+        video.errorMsg = '';
+      });
+
+      // 逐个上传视频
+      for (let i = 0; i < this.batchUploadVideos.length; i++) {
+        const video = this.batchUploadVideos[i];
+        try {
+          video.status = '';
+          video.progress = 0;
+
+          // 上传到COS
+          const updateProgress = (progressData) => {
+            video.progress = Math.round(progressData.percent * 100);
+          };
+
+          const data = await uploadObject(video.file, updateProgress, 1);
+
+          // 构建视频URL
+          let line_1 = `${process.env.VUE_APP_VIDEO_LINE_1}${data.urlPath}`;
+          line_1 = line_1.replace(/\.mp4$/, '.m3u8');
+
+          video.videoUrl = line_1;
+          video.fileKey = data.urlPath.substring(1);
+          video.progress = 100;
+          video.status = 'success';
+        } catch (error) {
+          console.error(`视频 ${video.fileName} 上传失败:`, error);
+          video.status = 'exception';
+          video.errorMsg = error.message || '上传失败';
+        }
+      }
+    },
+    /** 计算是否可以保存 */
+    canSaveBatchUpload() {
+      if (this.batchUploadVideos.length === 0) {
+        return false;
+      }
+      // 检查是否所有视频都已上传成功
+      return this.batchUploadVideos.every(video => video.status === 'success' && video.videoUrl);
+    },
+    /** 保存批量上传的视频 */
+    saveBatchUploadVideos() {
+      if (!this.canSaveBatchUpload) {
+        this.msgWarning("请等待所有视频上传完成");
+        return;
+      }
+
+      // 构建保存数据
+      const videoList = this.batchUploadVideos.map(video => {
+        return {
+          videoUrl: video.videoUrl,
+          videoName: video.fileName.replace(/\.[^/.]+$/, ""), // 去掉扩展名
+          videoType: -1, // 视频库
+          liveId: -1,
+          fileSize: video.fileSize,
+          duration: video.duration,
+          finishStatus: 0
+        };
+      });
+
+      // 调用批量新增接口
+      batchAddLiveVideo(videoList).then(response => {
+        if (response.code === 200) {
+          this.msgSuccess("批量保存成功");
+          this.batchUploadDialog = false;
+          this.batchUploadFileList = [];
+          this.batchUploadVideos = [];
+          this.getList();
+        } else {
+          this.msgError(response.msg || "批量保存失败");
+        }
+      }).catch(() => {
+        this.msgError("批量保存失败");
+      });
+    },
+    /** 打开重新上传视频对话框 */
+    handleReuploadVideo(row) {
+      this.reuploadVideoForm = {
+        videoId: row.videoId,
+        videoName: row.videoName || '',
+        videoUrl: '',
+        fileKey: '',
+        fileSize: null,
+        fileName: '',
+        lineOne: '',
+        thumbnail: '',
+        uploadType: 1,
+        isTranscode: 0,
+        transcodeFileKey: null,
+        duration: 0
+      };
+      this.reuploadVideoDialog = true;
+      // 重置上传组件
+      this.$nextTick(() => {
+        if (this.$refs.reuploadVideoUpload) {
+          this.$refs.reuploadVideoUpload.reset();
+        }
+      });
+    },
+    /** 取消重新上传视频 */
+    cancelReuploadVideo() {
+      this.reuploadVideoDialog = false;
+      this.reuploadVideoForm = {
+        videoId: null,
+        videoName: '',
+        videoUrl: '',
+        fileKey: '',
+        fileSize: null,
+        fileName: '',
+        lineOne: '',
+        thumbnail: '',
+        uploadType: 1,
+        isTranscode: 0,
+        transcodeFileKey: null,
+        duration: 0
+      };
+    },
+    /** 重新上传视频时长回调 */
+    handleReuploadVideoDuration(duration) {
+      this.reuploadVideoForm.duration = duration;
+    },
+    /** 重新上传视频URL变化回调 */
+    handleReuploadVideoChange(videoUrl) {
+      this.reuploadVideoForm.videoUrl = videoUrl;
+    },
+    /** 提交重新上传视频 */
+    submitReuploadVideo() {
+      if (!this.reuploadVideoForm.videoUrl) {
+        this.msgError("请上传视频");
+        return;
+      }
+
+      const videoUrl = this.reuploadVideoForm.videoUrl.replace('.mp4', '.m3u8');
+      updateVideoUrl({
+        videoId: this.reuploadVideoForm.videoId,
+        videoUrl: videoUrl
+      }).then(response => {
+        if (response.code === 200) {
+          this.msgSuccess("上传成功");
+          this.reuploadVideoDialog = false;
+          this.getList();
+          this.cancelReuploadVideo();
+        } else {
+          this.msgError(response.msg || "上传失败");
+        }
+      }).catch(() => {
+        this.msgError("上传失败");
+      });
     }
   }
 };
@@ -834,4 +1180,15 @@ export default {
   height: 100%;
   object-fit: cover;
 }
+
+.progress-container {
+  margin-bottom: 5px;
+}
+
+.progress-label {
+  display: block;
+  font-weight: bold;
+  font-size: 13px;
+  color: #303331;
+}
 </style>