Ver Fonte

feat: rts

lvkun há 3 anos atrás
pai
commit
0009849850

+ 2 - 2
config/proxy.ts

@@ -6,8 +6,8 @@
 module.exports = {
   dev: {
     '/rts-api': {
-      target: 'http://ugknfjq1tyqbshsh3.neiwangyun.net',
-      // target: 'http://124.222.113.37:8080',
+      // target: 'http://ugknfjq1tyqbshsh3.neiwangyun.net',
+      target: 'http://124.222.113.37:8080',
       changeOrigin: true,
       pathRewrite: { '^/rts-api': '' }
     },

+ 4 - 0
public/index.html

@@ -11,7 +11,11 @@
     <noscript>
       <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
     </noscript>
+
     <div id="app"></div>
     <!-- built files will be auto injected -->
   </body>
+  <!-- <script src="https://cdnjs.cloudflare.com/ajax/libs/flv.js/1.6.2/flv.min.js"></script>\ -->
+  <!-- <script src="https://cdn.bootcdn.net/ajax/libs/flv.js/1.6.2/flv.js"></script> -->
+  <script src="https://cdn.bootcdn.net/ajax/libs/flv.js/1.6.2/flv.min.js"></script>
 </html>

+ 6 - 5
src/api/rts/stream.ts

@@ -42,7 +42,7 @@ export const stopPull = (url: string) => {
   })
 }
 
-export const createPull = (params: {streamPath: string, target: string, save: number}) => {
+export const createRtspPull = (params: {streamPath: string, target: string, save: number}) => {
   return request<RTS.STREAM.Detail[]>({
     url: '/rtsp/api/pull',
     method: 'GET',
@@ -72,7 +72,7 @@ export const stopPush = (url: string) => {
   })
 }
 
-export const createPush = (params: {streamPath: string, target: string, save: number}) => {
+export const createRtspPush = (params: {streamPath: string, target: string, save: number}) => {
   return request<RTS.PullStream.Detail[]>({
     url: '/rtsp/api/push',
     method: 'GET',
@@ -107,7 +107,7 @@ export const getRecording = () => {
 /** 开始存储 */
 export const postRecord = (params: {type: 'mp4' | 'ts' | 'flv', streamPath: string }) => {
   return request<RTS.STREAM.Detail[]>({
-    url: '/record/api/start?type=flv&streamPath=live/rtc',
+    url: '/record/api/start',
     method: 'GET',
     params
   })
@@ -122,9 +122,10 @@ export const stopRecord = (id: string) => {
 }
 
 /** 录制播放 */
-export const playRecord = (streamPath: 'flv'| 'mp4' | 'm3u8' | 'h264'| 'h265') => {
+// url: `/record/${id}.${format}`,
+export const playRecord = (id: string, format: 'flv'| 'mp4' | 'm3u8' | 'h264'| 'h265') => {
   return request<RTS.STREAM.Detail[]>({
-    url: `/record/.${streamPath}`,
+    url: `/record/${id}`,
     method: 'GET'
   })
 }

+ 49 - 0
src/components/VideoPlayer/index.tsx

@@ -0,0 +1,49 @@
+import { defineComponent, onMounted, onUnmounted, reactive, ref } from 'vue'
+
+/**
+ * @description flv视频播放组件
+ *  关闭时需要销毁组件来停止 视频的 播放
+ */
+export const VideoPlayerTsx = defineComponent({
+  name: 'video-player-tsx',
+  props: {
+    videoUrl: {
+      type: String,
+      default: '/rts-api/hdl/live/test.flv'
+    }
+  },
+  setup (props, ctx) {
+    const videoPlay = ref()
+    const videoElementRef = ref()
+
+    const mediaDataSource = reactive({
+      type: 'flv',
+      url: props.videoUrl
+    })
+
+    const createPlayer = () => {
+      videoPlay.value = window.flvjs.createPlayer(mediaDataSource, {
+        enableWorker: false,
+        lazyLoadMaxDuration: 3 * 60,
+        seekType: 'range'
+      })
+
+      videoPlay.value.attachMediaElement(document.getElementById('videoPlay'))
+      videoPlay.value.load()
+    }
+
+    onMounted(() => {
+      createPlayer()
+    })
+
+    onUnmounted(() => {
+      videoPlay.value.destroy()
+    })
+
+    return () => (
+      <video ref={videoElementRef.value} id='videoPlay' class="centeredVideo" controls autoplay>
+          Your browser is too old which doesn't support HTML5 video.
+      </video>
+    )
+  }
+})

+ 6 - 0
src/components/VideoPlayer/typing.d.ts

@@ -0,0 +1,6 @@
+export {}
+declare global {
+  interface Window {
+    flvjs: any
+  }
+}

+ 30 - 5
src/controller/rts/rts.ts

@@ -1,4 +1,4 @@
-import { closeStream, createPull, createPush, getConfig, getPullList, getPushList, getRecording, getRecordList, getStreams, getSummary, stopPull, stopPush, updateConfig } from '@/api/rts/stream'
+import { closeStream, createRtmpPull, createRtmpPush, createRtspPull, createRtspPush, getConfig, getPullList, getPushList, getRecording, getRecordList, getStreams, getSummary, playRecord, postRecord, stopPull, stopPush, stopRecord, updateConfig } from '@/api/rts/stream'
 import { message } from 'ant-design-vue'
 
 export class RtsController {
@@ -29,8 +29,12 @@ export class RtsController {
     message.success('停止拉流成功')
   }
 
-  static async createPull (params: {streamPath: string, target: string, save: number}) {
-    await createPull(params)
+  static async createPull (params: {streamPath: string, target: string, save: number, name: 'RTSP' | 'RTMP'}) {
+    if (params.name === 'RTMP') {
+      await createRtmpPull(params)
+    } else {
+      await createRtspPull(params)
+    }
     message.success('新增成功')
   }
 
@@ -40,10 +44,15 @@ export class RtsController {
 
   static async stopPush (remoteurl: string) {
     await stopPush(remoteurl)
+    message.success('停止推流成功')
   }
 
-  static async createPush (params: {streamPath: string, target: string, save: number}) {
-    await createPush(params)
+  static async createPush (params: {streamPath: string, target: string, save: number, name: 'RTSP' | 'RTMP'}) {
+    if (params.name === 'RTSP') {
+      await createRtspPush(params)
+    } else {
+      await createRtmpPush(params)
+    }
     message.success('新增成功')
   }
 
@@ -55,6 +64,22 @@ export class RtsController {
     return await getRecording()
   }
 
+  /** 开始录制 */
+  static async recordVideo (params: {type: 'mp4' | 'ts' | 'flv', streamPath: string}) {
+    await postRecord(params)
+    message.success('录制成功')
+  }
+
+  static async playRecord (id: string, format: 'flv'| 'mp4' | 'm3u8' | 'h264'| 'h265') {
+    await playRecord(id, format)
+    message.success('操作成功')
+  }
+
+  static async stopRecord (id: string) {
+    await stopRecord(id)
+    message.success('停止录制成功')
+  }
+
   static async protocol (name: 'RTSP' | 'RTMP' | 'HLS' | 'HDL' | 'GB28181') {
     return await getConfig(name)
   }

+ 7 - 0
src/global.d.ts

@@ -0,0 +1,7 @@
+// declare global {
+//   interface Window {
+//       flv: any;
+//   }
+// }
+
+declare const window: Window & typeof globalThis & {flv: any}

+ 2 - 0
src/hooks/index.ts

@@ -1,3 +1,5 @@
 export { useEmitter, useScheduler, useSchedulerOnce } from './effect'
 
 export { useId } from './state'
+
+export { useFlvUrl } from './state'

+ 3 - 0
src/hooks/state.ts

@@ -3,3 +3,6 @@
  * useId 函数生成一个随机字符串,用作 ID。
  */
 export const useId = () => Math.random().toString(16).slice(2)
+
+/** 获取flv播放地址 */
+export const useFlvUrl = (streamPath: string) => `/rts-api/hdl/${streamPath}.flv`

+ 0 - 2
src/layout/components/Sidebar/index.vue

@@ -50,8 +50,6 @@ const emitter = useEmitter()
 // 刷新后也需要打开openKeys
 
 emitter.on(Emitter.NAVBAR, () => {
-  console.log('Emitter.NAVBAR', appRouter.router.sider.selectPath)
-
   selectedKeys2.value = [appRouter.router.sider.selectPath]
 })
 

+ 0 - 8
src/layout/navbar.vue

@@ -48,8 +48,6 @@ const appRouter = useAppRouter()
 const selectedKeys = ref<string[]>()
 
 const changeRouter = (path: string) => {
-  console.log('changeRouter:', path)
-
   selectedKeys.value = [path]
   appRouter.changeNavbar(path, '')
 }
@@ -57,8 +55,6 @@ const changeRouter = (path: string) => {
 const hasCurrentRourte = (children: ROUTER.RoutesProps[]): boolean => {
   let r = false
   children.forEach(item => {
-    console.log(item.path, route.path)
-
     if (item.path === route.path) {
       r = true
     }
@@ -77,11 +73,7 @@ const hasCurrentRourte = (children: ROUTER.RoutesProps[]): boolean => {
 
 onMounted(() => {
   routes.forEach(item => {
-    console.log(hasCurrentRourte(item.children))
-
     if (hasCurrentRourte(item.children)) {
-      console.log('item.path:', item.path)
-
       selectedKeys.value = [item.path]
       appRouter.changeNavbar(item.path, 'init')
       appRouter.changeSiderRoute()

+ 70 - 2
src/pages/rts/monitor/index.vue

@@ -1,12 +1,78 @@
 <template>
-  <a-card>
-    <a-alert message="Success Text" type="success" />
+   <a-row :gutter="[8, 8]" >
+      <a-col :xs="24" :md="24" :lg="12" >
+        <a-alert :message=" 'Address: ' + state.summary.Address" type="info" />
+      </a-col>
+      <a-col :xs="24" :md="24" :lg="12" >
+        <a-alert :message=" 'CPUUsage:' + state.summary.CPUUsage" type="info" />
+      </a-col>
+    </a-row>
+  <a-card style="margin-top: 20px;" >
+    <a-row :gutter="[12, 12]" >
+      <a-col  :xs="24" :md="24" :lg="12">
+        <a-spin :spinning="state.loading" >
+          <a-descriptions title="Memory" bordered :column="2">
+            <a-descriptions-item label="Total">{{state.summary.Memory?.Total}}</a-descriptions-item>
+            <a-descriptions-item label="Usage">{{state.summary.Memory?.Usage}}</a-descriptions-item>
+            <a-descriptions-item label="Used">{{state.summary.Memory?.Used}}</a-descriptions-item>
+            <a-descriptions-item label="Free">{{state.summary.Memory?.Free}}</a-descriptions-item>
+          </a-descriptions>
+        </a-spin>
+      </a-col>
+      <a-col :xs="24" :md="24" :lg="12">
+        <a-spin :spinning="state.loading" >
+          <a-descriptions title="HardDisk" bordered :column="2">
+            <a-descriptions-item label="Total">{{state.summary.HardDisk?.Total}}</a-descriptions-item>
+            <a-descriptions-item label="Usage">{{state.summary.HardDisk?.Usage}}</a-descriptions-item>
+            <a-descriptions-item label="Used">{{state.summary.HardDisk?.Used}}</a-descriptions-item>
+            <a-descriptions-item label="Free">{{state.summary.HardDisk?.Free}}</a-descriptions-item>
+          </a-descriptions>
+        </a-spin>
+      </a-col>
+      <a-col :xs="24" :md="24" :lg="24">
+        <a-spin :spinning="state.loading" >
+          <a-descriptions title="NetWork" bordered :column="2">
+
+          </a-descriptions>
+          <a-table
+              :columns="columns"
+              :data-source="state.summary.NetWork"
+              :pagination="false"
+            >
+
+            </a-table>
+        </a-spin>
+      </a-col>
+    </a-row>
   </a-card>
 </template>
 <script lang="ts" setup >
 import { RtsController } from '@/controller/rts'
 import { onMounted, reactive } from 'vue'
 
+const columns = [
+  {
+    title: '名称',
+    dataIndex: 'Name'
+  },
+  {
+    title: 'Receive',
+    dataIndex: 'Receive'
+  },
+  {
+    title: 'Sent',
+    dataIndex: 'Sent'
+  },
+  {
+    title: 'ReceiveSpeed',
+    dataIndex: 'ReceiveSpeedame'
+  },
+  {
+    title: 'SentSpeed',
+    dataIndex: 'SentSpeed'
+  }
+]
+
 const state = reactive<{
   loading: boolean,
   summary: Partial<RTS.SUMMARY.Detail>
@@ -16,7 +82,9 @@ const state = reactive<{
 })
 
 const getSummary = async () => {
+  state.loading = true
   const { data } = await RtsController.summary()
+  state.loading = false
   state.summary = data
 }
 

+ 17 - 7
src/pages/rts/pull/index.vue

@@ -12,9 +12,12 @@
       :loading="state.loading"
     >
       <template #bodyCell="{column, record}" >
+        <template v-if="column.key === 'StartTime'" >
+          {{dayjs(record.StartTime).format('YYYY/MM/DD HH:mm:ss')}}
+        </template>
         <template v-if="column.key === 'action'"  >
           <a-space>
-            <a @click="stopPull(record.remoteurl)" >停止</a>
+            <a @click="stopPull(record.RemoteURL)" >停止</a>
           </a-space>
         </template>
       </template>
@@ -27,9 +30,9 @@
     @cancel="state.visible = false "
     @ok="ok"
   >
-    <a-form :label-col="{span: 4}" >
+    <a-form :label-col="{span: 5}" >
       <a-form-item label="拉流类型" >
-        <a-select v-model:value="bodyParamsState.target" >
+        <a-select v-model:value="bodyParamsState.name" >
           <a-select-option
             v-for="item in pullList"
             :key="item.key"
@@ -39,7 +42,10 @@
           </a-select-option>
         </a-select>
       </a-form-item>
-      <a-form-item label="拉流地址" v-bind='validateInfos.streamPath' >
+      <a-form-item label="拉流地址" v-bind='validateInfos.target' >
+        <a-input v-model:value="bodyParamsState.target" > </a-input>
+      </a-form-item>
+      <a-form-item label="视频流标识" v-bind='validateInfos.streamPath' >
         <a-input v-model:value="bodyParamsState.streamPath" > </a-input>
       </a-form-item>
     </a-form>
@@ -49,6 +55,7 @@
 import { RtsController } from '@/controller/rts'
 import { onMounted, reactive } from 'vue'
 import { Form } from 'ant-design-vue'
+import dayjs from 'dayjs'
 
 const columns = [
   {
@@ -77,7 +84,8 @@ const columns = [
   },
   {
     title: '开始时间',
-    dataIndex: 'StartTime'
+    dataIndex: 'StartTime',
+    key: 'StartTime'
   },
   {
     title: '操作',
@@ -100,7 +108,8 @@ const pullList = [
 ]
 
 const bodyParamsState = reactive({
-  target: 'RTSP',
+  name: 'RTSP',
+  target: '',
   streamPath: '',
   save: 2
 })
@@ -118,7 +127,8 @@ const state = reactive<{
 })
 
 const { resetFields, validate, validateInfos } = useForm(bodyParamsState, reactive({
-  streamPath: [{ required: 'true', message: '请填写流地址' }]
+  streamPath: [{ required: 'true', message: '请填写视频流标识' }],
+  target: [{ required: 'true', message: '请填写流地址' }]
 }))
 
 const openModal = () => {

+ 22 - 11
src/pages/rts/push/index.vue

@@ -12,9 +12,12 @@
       :loading="state.loading"
     >
       <template #bodyCell="{column, record}" >
+        <template v-if="column.key === 'StartTime'" >
+          {{dayjs(record.StartTime).format('YYYY/MM/DD HH:mm:ss')}}
+        </template>
         <template v-if="column.key === 'action'"  >
           <a-space>
-            <a @click="stopPush(record.remoteurl)" >停止</a>
+            <a @click="stopPush(record.RemoteURL)" >停止</a>
           </a-space>
         </template>
       </template>
@@ -27,9 +30,9 @@
     @cancel="state.visible = false "
     @ok="ok"
   >
-    <a-form :label-col="{span: 4}" >
+    <a-form :label-col="{span: 5}" >
       <a-form-item label="推流类型" >
-        <a-select v-model:value="bodyParamsState.target" >
+        <a-select v-model:value="bodyParamsState.name" >
           <a-select-option
             v-for="item in pullList"
             :key="item.key"
@@ -39,7 +42,10 @@
           </a-select-option>
         </a-select>
       </a-form-item>
-      <a-form-item label="推流地址" v-bind='validateInfos.streamPath' >
+      <a-form-item label="推流地址" v-bind='validateInfos.target' >
+        <a-input v-model:value="bodyParamsState.target" > </a-input>
+      </a-form-item>
+      <a-form-item label="视频流标识" v-bind='validateInfos.streamPath' >
         <a-input v-model:value="bodyParamsState.streamPath" > </a-input>
       </a-form-item>
     </a-form>
@@ -49,6 +55,7 @@
 import { RtsController } from '@/controller/rts'
 import { onMounted, reactive } from 'vue'
 import { Form } from 'ant-design-vue'
+import dayjs from 'dayjs'
 
 const columns = [
   {
@@ -77,7 +84,8 @@ const columns = [
   },
   {
     title: '开始时间',
-    dataIndex: 'StartTime'
+    dataIndex: 'StartTime',
+    key: 'StartTime'
   },
   {
     title: '操作',
@@ -100,7 +108,8 @@ const pullList = [
 ]
 
 const bodyParamsState = reactive({
-  target: 'RTSP',
+  name: 'RTSP',
+  target: '',
   streamPath: '',
   save: 2
 })
@@ -118,7 +127,8 @@ const state = reactive<{
 })
 
 const { resetFields, validate, validateInfos } = useForm(bodyParamsState, reactive({
-  streamPath: [{ required: 'true', message: '请填写流地址' }]
+  streamPath: [{ required: 'true', message: '请填写流地址' }],
+  target: [{ required: 'true', message: '请填写流地址' }]
 }))
 
 const openModal = () => {
@@ -129,21 +139,22 @@ const ok = () => {
   validate().then(async () => {
     await RtsController.createPush(bodyParamsState)
     state.visible = false
-    getPullList()
+    getPushList()
   })
 }
 
 const stopPush = async (remoteurl: string) => {
-  await RtsController.stopPull(remoteurl)
+  await RtsController.stopPush(remoteurl)
+  getPushList()
 }
 
-const getPullList = async () => {
+const getPushList = async () => {
   const { data } = await RtsController.listPush()
   state.dataSource = data
 }
 
 onMounted(() => {
-  getPullList()
+  getPushList()
 })
 
 </script>

+ 120 - 8
src/pages/rts/record/index.vue

@@ -1,17 +1,35 @@
 <template>
-
   <a-card
     title="存储列表"
   >
+  <a-row justify="space-between" >
+    <a-col>
+      <a-select v-model:value="recordState.type" style="width: 170px;" >
+          <a-select-option
+            v-for="item in typeList"
+            :key="item"
+            :value="item"
+          >
+            {{item}}
+          </a-select-option>
+        </a-select>
+    </a-col>
+    <a-col>
+      <a-button type="primary" @click="openRecordModal">开始存储</a-button>
+    </a-col>
+  </a-row>
   <a-table
-      :column="recordColumns"
-      :data-source="state.recordingDataSource"
+      style="margin-top: 20px;"
+      :columns="recordColumns"
+      :data-source="state.recordDataSource"
       :loading="state.loading"
+      :pagination="false"
     >
     <template #bodyCell="{column, record}" >
         <template v-if="column.key === 'action'"  >
           <a-space>
-            <a @click="openModal(record)" >详情</a>
+            <!-- <a @click="openModal(record)" >详情</a> -->
+            <a @click="recordPlay(record)" >录制播放</a>
           </a-space>
         </template>
       </template>
@@ -22,14 +40,15 @@
     style="margin-top: 20px;"
   >
     <a-table
-      :column="columns"
+      :columns="columns"
       :data-source="state.recordingDataSource"
       :loading="state.loading"
+      :pagination="false"
     >
     <template #bodyCell="{column, record}" >
         <template v-if="column.key === 'action'"  >
           <a-space>
-            <a @click="openModal(record)" >详情</a>
+            <a @click="stopRcord(record)" >停止录制</a>
           </a-space>
         </template>
       </template>
@@ -37,17 +56,56 @@
   </a-card>
 
   <modal-pro
+    style="width: 700px;"
     label="视频流详情"
     :visible="state.visible"
     @cancel="state.visible = false"
     @ok="state.visible = false"
+    destroyOnClose
   >
+    <video-player-tsx :video-url="`/rts-api/record/${state.recordDetail.Path}`" />
+  </modal-pro>
 
+  <modal-pro
+    label="录制视频"
+    :visible="state.recordVisible"
+    @cancel="state.recordVisible = false"
+    @ok="recordVideo"
+  >
+    <a-form :label-col="{span: 4}"  >
+      <a-form-item label="类型" >
+        <a-select v-model:value="recordState.type" >
+          <a-select-option
+            v-for="item in typeList"
+            :key="item"
+            :value="item"
+          >
+            {{item}}
+          </a-select-option>
+        </a-select>
+      </a-form-item>
+      <a-form-item label="视频流标识" >
+        <a-select v-model:value="recordState.streamPath" >
+          <a-select-option
+            v-for="item in state.streams"
+            :key="item.Path"
+            :value="item.Path"
+          >
+            {{item.Path}}
+          </a-select-option>
+        </a-select>
+      </a-form-item>
+    </a-form>
   </modal-pro>
 </template>
 <script lang="ts" setup >
 import { RtsController } from '@/controller/rts'
 import { onMounted, reactive } from 'vue'
+import { Form, message } from 'ant-design-vue'
+import { VideoPlayerTsx } from '@/components/VideoPlayer/index'
+import { getRecording } from '@/api/rts/stream'
+
+const useForm = Form.useForm
 
 const recordColumns = [
   {
@@ -61,6 +119,11 @@ const recordColumns = [
   {
     title: '时长',
     dataIndex: 'Duration'
+  },
+  {
+    title: '操作',
+    dataIndex: 'action',
+    key: 'action'
   }
 ]
 
@@ -88,25 +151,74 @@ const columns = [
   }
 ]
 
+const typeList = ['mp4', 'ts', 'flv']
+
+const recordState = reactive({
+  type: 'flv',
+  streamPath: 'app/name'
+})
+
 const state = reactive({
   loading: false,
   visible: false,
+  recordVisible: false,
   recordingDataSource: [],
-  detail: {}
+  recordDataSource: [],
+  recordDetail: {},
+  detail: {},
+  streams: [],
+  type: 'flv',
+  videoUrl: ''
 })
 
+const stopRcord = async (record) => {
+  await RtsController.stopRecord(record.id)
+  getRecording()
+}
+
+const recordVideo = async () => {
+  if (!recordState.streamPath) {
+    message.warn('请填写标识流地址')
+    return
+  }
+
+  await RtsController.recordVideo(recordState)
+  state.recordVisible = false
+}
+
+const recordPlay = async (record) => {
+  await RtsController.playRecord(record.Path, 'flv')
+  getRecordingList()
+}
+
+const openRecordModal = () => {
+  state.recordVisible = true
+}
+
 const openModal = (record: RTS.STREAM.Detail) => {
   state.detail = record
   state.visible = false
 }
 
+const getStreamList = async () => {
+  const { data } = await RtsController.getStreams()
+  state.streams = data
+}
+
+const getRecordList = async () => {
+  const { data } = await RtsController.listRecord(state.type)
+  state.recordDataSource = data
+}
+
 const getRecordingList = async () => {
   const { data } = await RtsController.listRecording()
-  state.recordingDataSource = state
+  state.recordingDataSource = data
 }
 
 onMounted(() => {
   getRecordingList()
+  getStreamList()
+  getRecordList()
 })
 </script>
 

+ 16 - 6
src/pages/rts/stream/index.vue

@@ -7,13 +7,13 @@
       :loading="state.loading"
       :columns="columns"
       :data-source="state.dataSource"
+      :pagination="false"
     >
       <template #bodyCell="{column, record}" >
         <template v-if="column.key === 'action'"  >
           <a-space>
-            <a @click="openModal(record)" >详情</a>
-            <a>编辑</a>
-            <a @click="closeSteram(record.streamPath)" >关闭视频流</a>
+            <a @click="openLive(record)" >实时播放</a>
+            <a @click="closeSteram(record.Path)" >关闭视频流</a>
           </a-space>
         </template>
       </template>
@@ -21,18 +21,22 @@
   </a-card>
 
   <modal-pro
+    style="width: 700px;"
     label="视频流详情"
     :visible="state.visible"
     @cancel="state.visible = false"
     @ok="state.visible = false"
+    destroyOnClose
   >
-
+    <video-player-tsx :video-url="useFlvUrl(state.detail.Path!)" />
   </modal-pro>
 </template>
 
 <script lang="ts" setup >
 import { RtsController } from '@/controller/rts'
 import { onMounted, reactive } from 'vue'
+import { VideoPlayerTsx } from '@/components/VideoPlayer/index'
+import { useFlvUrl } from '@/hooks/index'
 
 const columns = [
   {
@@ -92,13 +96,19 @@ const state = reactive<{
   visible: false
 })
 
+const openLive = (record: RTS.STREAM.Detail) => {
+  state.visible = true
+  state.detail = record
+}
+
 const openModal = (record: RTS.STREAM.Detail) => {
   state.detail = record
   state.visible = false
 }
 
-const closeSteram = (streamPath: string) => {
-  RtsController.cloeStreams(streamPath)
+const closeSteram = async (streamPath: string) => {
+  await RtsController.cloeStreams(streamPath)
+  getStreamList()
 }
 
 const getStreamList = async () => {

Diff do ficheiro suprimidas por serem muito extensas
+ 11 - 0
src/utils/flv.min.js


Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff