Jelajahi Sumber

bugfix:
1、替换前端播放器,解决无法播放avi视频文件
2、解决ffmpeg无法正常压缩视频问题

lihao16 5 bulan lalu
induk
melakukan
c5b557304b

+ 3 - 3
smsb-modules/smsb-source/src/main/java/com/inspur/source/service/impl/SmsbMinioDataServiceImpl.java

@@ -89,10 +89,10 @@ public class SmsbMinioDataServiceImpl implements ISmsbMinioDataService {
         String scaleFilter;
         if (height > width) {
             // 竖屏视频:高度压缩至480,宽度等比缩放
-            scaleFilter = String.format("scale=-2:480");
+            scaleFilter = "scale=-2:480";
         } else {
             // 横屏视频:宽度压缩至480,高度等比缩放
-            scaleFilter = String.format("scale=480:-2");
+            scaleFilter = "scale=480:-2";
         }
         return scaleFilter;
     }
@@ -938,7 +938,7 @@ public class SmsbMinioDataServiceImpl implements ISmsbMinioDataService {
                                    "-b:v " + codeRate + "k -maxrate " + codeRate + "k";
             // 4. 构建FFmpeg命令
             String ffmpegCommand = String.format(
-                "ffmpeg -i %s -vf \"%s\" %s -c:a copy %s",
+                "ffmpeg -i %s -vf %s %s -c:a copy %s",
                 inputPath, resolutionFilter, bitrateFilter, tempOutPath
             );
             log.info("compressVideo execute ffmpeg cmd : " + ffmpegCommand);

+ 3 - 0
smsb-plus-ui/package.json

@@ -20,6 +20,7 @@
   "dependencies": {
     "@element-plus/icons-vue": "2.3.1",
     "@highlightjs/vue-plugin": "2.1.0",
+    "@videojs-player/vue": "^1.0.0",
     "@vueup/vue-quill": "1.2.0",
     "@vueuse/core": "10.9.0",
     "animate.css": "4.1.1",
@@ -43,6 +44,8 @@
     "pinia": "2.1.7",
     "screenfull": "6.0.2",
     "sortablejs": "^1.15.6",
+    "video.js": "^7.20.3",
+    "videojs-flash": "^2.2.1",
     "vue": "3.4.34",
     "vue-cropper": "1.1.1",
     "vue-i18n": "9.10.2",

+ 174 - 0
smsb-plus-ui/src/components/VideoPlayer/index.vue

@@ -0,0 +1,174 @@
+<template>
+  <div class="video-player-container">
+    <video
+      ref="videoRef"
+      class="video-js vjs-big-play-centered"
+      playsinline
+      webkit-playsinline
+    ></video>
+  </div>
+</template>
+
+<script lang="ts">
+import { defineComponent, onMounted, onBeforeUnmount, ref, watch } from 'vue'
+import videojs from 'video.js'
+import 'video.js/dist/video-js.css'
+// 使用声明文件后这样引入
+import videojsFlash from 'videojs-flash'
+videojs.registerPlugin('flash', videojsFlash)
+
+interface VideoPlayerProps {
+  url: string
+  options?: videojs.PlayerOptions
+}
+
+export default defineComponent({
+  name: 'VideoPlayer',
+  props: {
+    url: {
+      type: String,
+      required: true
+    },
+    options: {
+      type: Object as () => videojs.PlayerOptions,
+      default: () => ({})
+    }
+  },
+  setup(props: VideoPlayerProps) {
+    const videoRef = ref<HTMLVideoElement | null>(null)
+    const player = ref<videojs.Player | null>(null)
+
+    // 初始化播放器
+    const initPlayer = () => {
+      if (!videoRef.value) return
+
+      // 合并默认选项和传入的选项
+      const mergedOptions: videojs.PlayerOptions = {
+        controls: true,
+        fluid: true,
+        preload: 'auto',
+        techOrder: ['html5', 'flash'], // 优先使用HTML5,回退到Flash
+        ...props.options
+      }
+
+      // 创建 video.js 播放器实例
+      player.value = videojs(videoRef.value, mergedOptions, () => {
+        console.log('Player is ready')
+      })
+
+      // 根据 URL 后缀确定视频类型
+      setupSource()
+    }
+
+    // 设置视频源
+    const setupSource = () => {
+      if (!player.value) return
+
+      const url = props.url.toLowerCase()
+
+      if (isRTMPStream(url)) {
+        // RTMP 流媒体
+        setupRTMPPlayer()
+      } else {
+        // 普通视频文件 (MP4, AVI 等)
+        player.value.src({
+          src: props.url,
+          type: getVideoType(props.url)
+        })
+      }
+    }
+
+    // 判断是否是RTMP流
+    const isRTMPStream = (url: string): boolean => {
+      return url.startsWith('rtmp://') ||
+        url.startsWith('rtmps://') ||
+        url.includes('rtmp.stream') ||
+        url.includes('rtmp/live')
+    }
+
+    // 设置RTMP播放器
+    const setupRTMPPlayer = () => {
+      if (!player.value) return
+
+      // 使用Flash技术播放RTMP
+      player.value.src({
+        src: props.url,
+        type: 'rtmp/mp4',
+        withCredentials: false
+      })
+
+      // 备选方案:如果Flash不可用,可以尝试HLS转换
+      player.value.ready(() => {
+        player.value!.on('error', () => {
+          console.warn('RTMP playback failed, trying HLS fallback')
+          tryHLSFallback()
+        })
+      })
+    }
+
+    // 尝试HLS回退方案
+    const tryHLSFallback = () => {
+      if (!player.value) return
+
+      // 这里假设你的RTMP流有对应的HLS流
+      const hlsUrl = props.url
+        .replace('rtmp://', 'http://')
+        .replace('rtmps://', 'https://')
+        .replace('/live/', '/hls/') + '.m3u8'
+
+      player.value.src({
+        src: hlsUrl,
+        type: 'application/x-mpegURL'
+      })
+    }
+
+    // 根据 URL 获取视频类型
+    const getVideoType = (url: string): string => {
+      const lowerUrl = url.toLowerCase()
+      if (lowerUrl.endsWith('.mp4')) {
+        return 'video/mp4'
+      } else if (lowerUrl.endsWith('.avi')) {
+        return 'video/x-msvideo'
+      }
+      // 默认类型,让浏览器自己判断
+      return ''
+    }
+
+    // 监听 URL 变化
+    watch(
+      () => props.url,
+      () => {
+        setupSource()
+      }
+    )
+
+    onMounted(() => {
+      initPlayer()
+    })
+
+    onBeforeUnmount(() => {
+      // 清理 video.js 播放器
+      if (player.value) {
+        player.value.dispose()
+        player.value = null
+      }
+    })
+
+    return {
+      videoRef
+    }
+  }
+})
+</script>
+
+<style scoped>
+.video-player-container {
+  width: 100%;
+  height: 100%;
+}
+
+.video-js {
+  width: 100%;
+  height: 100%;
+}
+</style>

+ 7 - 24
smsb-plus-ui/src/views/smsb/device/index.vue

@@ -222,7 +222,7 @@
       </template>
     </el-dialog>
     <!--设备详情弹窗-->
-    <el-dialog v-model="viewDialog.visible" :title="viewDialog.title" width="1000px" style="height: 680px"
+    <el-dialog v-model="viewDialog.visible" :title="viewDialog.title" width="1050px" style="height: 680px"
                append-to-body>
       <el-tabs v-model="activeName" style="height: 500px" @tab-click="handleClickTab">
         <el-tab-pane label="设备详情" name="info">
@@ -377,7 +377,8 @@
     <el-dialog v-model="watchDialog.visible" :title="watchDialog.title" width="900px" append-to-body
       @closed="onDialogClosed">
       <div v-if="watchDialog.visible" style="width: 100%; height: 500px">
-        <video ref="flvPlayerRef" style="width: 100%; height: 100%" controls></video>
+<!--        <video ref="flvPlayerRef" style="width: 100%; height: 100%" controls></video>-->
+        <VideoPlayer :url="watchDialog.url" />
       </div>
       <template #footer>
         <div class="dialog-footer">
@@ -493,7 +494,7 @@ import {
   stopDevicePowerSchedule,
   updateDevicePowerSchedule
 } from "@/api/smsb/device/device_power_schedule";
-
+import VideoPlayer from '../../../components/VideoPlayer/index.vue'
 const screenshotStore = storeToRefs(useScreenshotStore());
 const screenshotImageUrl = ref<string>();
 const alarmList = ref<DeviceErrorRecordVO[]>([]);
@@ -586,7 +587,8 @@ const flvPlayer = ref<flvjs.Player | null>(null);
 const flvPlayerRef = ref<HTMLVideoElement | null>(null);
 const watchDialog = reactive<DialogOption>({
   visible: false,
-  title: ''
+  title: '',
+  url: ''
 });
 const shotDialog = reactive<DialogOption>({
   visible: false,
@@ -963,26 +965,7 @@ const startMonitor = async (row?: DeviceVO) => {
       destroyPlayer();
       watchDialog.visible = true;
       watchDialog.title = '回采画面';
-      // 确保DOM已更新
-      await nextTick();
-      if (flvPlayerRef.value && res.data.viewUrl) {
-        flvPlayer.value = flvjs.createPlayer({
-          type: 'flv',
-          url: res.data.viewUrl
-        });
-        // 错误处理
-        /*flvPlayer.value.on(flvjs.Events.ERROR, (errType, errDetail) => {
-          console.error('播放错误:', errType, errDetail);
-          proxy?.$modal.msgError('视频播放失败,请检查流地址');
-          destroyPlayer();
-        });*/
-        flvPlayer.value.attachMediaElement(flvPlayerRef.value);
-        flvPlayer.value.load();
-        // 处理浏览器自动播放策略
-        flvPlayer.value.play().catch(() => {
-          // proxy?.$modal.msgWarning('请点击视频播放按钮以开始播放');
-        });
-      }
+      watchDialog.url = res.data.viewUrl;
     } else {
       proxy?.$modal.msgError('开启推流失败!');
     }

+ 7 - 6
smsb-plus-ui/src/views/smsb/minioData/index.vue

@@ -110,7 +110,7 @@
                 <!-- 视频类型 -->
                 <!-- <image-preview :src="scope.row.screenshot" style="width: 40px; height: 40px; cursor: pointer" -->
                 <!-- @click="viewVideo(scope.row.url)" /> -->
-                <el-icon class="VideoPlay" @click="viewVideo(scope.row.fileUrl)" size="40" style="cursor: pointer">
+                <el-icon class="VideoPlay" @click="viewVideo(scope.row.screenshot)" size="40" style="cursor: pointer">
                   <VideoPlay />
                 </el-icon>
               </div>
@@ -222,8 +222,8 @@
     </el-dialog>
 
     <!-- 用于展示播放的视频 -->
-    <el-dialog v-model="videoDialogVisible" v-if="videoDialogVisible">
-      <video width="100%" controls :src="videoUrl"></video>
+    <el-dialog v-model="videoDialogVisible" v-if="videoDialogVisible" title="视频预览">
+      <VideoPlayer :url="videoUrl" />
     </el-dialog>
 
     <!-- 引用情况弹窗 -->
@@ -238,12 +238,12 @@
                 <dict-tag :options="smsb_item_type" :value="scope.row.itemType" />
               </template>
             </el-table-column>
-            <el-table-column label="分屏" align="center" prop="splitScreen" width="100">
+<!--            <el-table-column label="分屏" align="center" prop="splitScreen" width="100">
               <template #default="scope">
-                <span v-if="scope.row.splitScreen == 0"> --- </span>
+                <span v-if="scope.row.splitScreen == 0"> -&#45;&#45; </span>
                 <dict-tag v-else :options="smsb_split_screen" :value="scope.row.splitScreen" />
               </template>
-            </el-table-column>
+            </el-table-column>-->
             <el-table-column label="资源数量" align="center" prop="sourceNum" width="100" />
             <el-table-column label="创建人" align="left" prop="createUser" width="120" :show-overflow-tooltip="true" />
             <el-table-column label="创建时间" align="left" prop="createTime" width="160" />
@@ -265,6 +265,7 @@ import {listMinioTransRecord} from '@/api/smsb/source/transRecord';
 import {MinioTransRecordQuery, MinioTransRecordVO} from '@/api/smsb/source/transRecord_type';
 import {ItemQuery, ItemVO} from '@/api/smsb/source/item_type';
 import {itemListByFileId} from '@/api/smsb/source/item';
+import VideoPlayer from '../../../components/VideoPlayer/index.vue'
 /** 提交按钮 */
 // 通过ref访问SmsbFileUploader实例
 import {ref as vueRef} from 'vue';