|
|
@@ -80,12 +80,12 @@
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="startedAt" :label="t('启动时间')" width="160" align="center">
|
|
|
<template #default="{ row }">
|
|
|
- {{ formatDateTime(row.startedAt) }}
|
|
|
+ {{ formatTime(row.startedAt) }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column prop="stoppedAt" :label="t('关闭时间')" width="160" align="center">
|
|
|
<template #default="{ row }">
|
|
|
- {{ formatDateTime(row.stoppedAt) }}
|
|
|
+ {{ formatTime(row.stoppedAt) }}
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column :label="t('操作')" align="center" fixed="right">
|
|
|
@@ -171,7 +171,7 @@
|
|
|
<CodeEditor
|
|
|
v-model="form.commandTemplate"
|
|
|
language="bash"
|
|
|
- height="200px"
|
|
|
+ height="400px"
|
|
|
placeholder="#!/bin/bash # FFmpeg 命令模板,可使用 {RTSP_URL} 等变量"
|
|
|
/>
|
|
|
</div>
|
|
|
@@ -197,9 +197,18 @@
|
|
|
<el-tag v-if="playbackInfo.isLive" type="success" size="small">{{ t('直播中') }}</el-tag>
|
|
|
<el-tag v-else type="info" size="small">{{ t('离线') }}</el-tag>
|
|
|
</div>
|
|
|
- <el-button type="danger" size="small" @click="drawerVisible = false">
|
|
|
+ <!-- <el-button type="danger" size="small" @click="drawerVisible = false">
|
|
|
<Icon icon="mdi:close" width="16" height="16" />
|
|
|
{{ t('关闭') }}
|
|
|
+ </el-button> -->
|
|
|
+ <el-button
|
|
|
+ v-if="currentMediaStream && currentMediaStream.status === '1'"
|
|
|
+ type="danger"
|
|
|
+ size="small"
|
|
|
+ :loading="streamStopping"
|
|
|
+ @click="handleStopStreamFromPlayer"
|
|
|
+ >
|
|
|
+ {{ t('停止推流') }}
|
|
|
</el-button>
|
|
|
</div>
|
|
|
<div class="player-container">
|
|
|
@@ -240,6 +249,8 @@
|
|
|
:inactive-text="t('有声')"
|
|
|
style="margin-left: 16px"
|
|
|
/>
|
|
|
+ <el-divider direction="vertical" />
|
|
|
+ <!-- 停止推流按钮 -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
@@ -442,8 +453,8 @@ import {
|
|
|
Close
|
|
|
} from '@element-plus/icons-vue'
|
|
|
import { Icon } from '@iconify/vue'
|
|
|
-import dayjs from 'dayjs'
|
|
|
import { useI18n } from 'vue-i18n'
|
|
|
+import { formatTime } from '@/utils/dayjs'
|
|
|
import { listLiveStreams, addLiveStream, updateLiveStream, deleteLiveStream } from '@/api/live-stream'
|
|
|
import { listAllLssNodes } from '@/api/lss'
|
|
|
import { adminListCameras } from '@/api/camera'
|
|
|
@@ -457,12 +468,6 @@ const { t } = useI18n({ useScope: 'global' })
|
|
|
const route = useRoute()
|
|
|
const router = useRouter()
|
|
|
|
|
|
-// 格式化时间
|
|
|
-function formatDateTime(dateStr: string | undefined): string {
|
|
|
- if (!dateStr) return '-'
|
|
|
- return dayjs(dateStr).format('YYYY-MM-DD HH:mm:ss')
|
|
|
-}
|
|
|
-
|
|
|
const loading = ref(false)
|
|
|
const submitLoading = ref(false)
|
|
|
const streamList = ref<LiveStreamDTO[]>([])
|
|
|
@@ -479,6 +484,7 @@ const commandUpdateLoading = ref(false)
|
|
|
const activeDrawerTab = ref<'edit' | 'play'>('edit')
|
|
|
const currentMediaStream = ref<LiveStreamDTO | null>(null)
|
|
|
const streamStarting = ref(false)
|
|
|
+const streamStopping = ref(false)
|
|
|
const playerRef = ref<InstanceType<typeof VideoPlayer>>()
|
|
|
const playbackInfo = ref<{
|
|
|
videoId: string
|
|
|
@@ -912,6 +918,41 @@ async function handleStartStreamFromPlayer() {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// 从播放器窗口停止推流
|
|
|
+async function handleStopStreamFromPlayer() {
|
|
|
+ if (!currentMediaStream.value) return
|
|
|
+
|
|
|
+ try {
|
|
|
+ await ElMessageBox.confirm(t('确定要停止该推流任务吗?'), t('提示'), {
|
|
|
+ type: 'warning',
|
|
|
+ confirmButtonText: t('确定'),
|
|
|
+ cancelButtonText: t('取消')
|
|
|
+ })
|
|
|
+
|
|
|
+ streamStopping.value = true
|
|
|
+ const res = await stopStreamTask({
|
|
|
+ taskId: currentMediaStream.value.taskStreamSn,
|
|
|
+ lssId: currentMediaStream.value.lssId
|
|
|
+ })
|
|
|
+ if (res.success) {
|
|
|
+ ElMessage.success(t('推流任务已停止'))
|
|
|
+ // 更新当前流的状态
|
|
|
+ currentMediaStream.value.status = '0'
|
|
|
+ playbackInfo.value.isLive = false
|
|
|
+ getList()
|
|
|
+ } else {
|
|
|
+ ElMessage.error(res.errMessage || t('停止失败'))
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ if (error !== 'cancel') {
|
|
|
+ console.error('停止推流失败', error)
|
|
|
+ ElMessage.error(t('停止推流失败'))
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ streamStopping.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// 停止推流
|
|
|
async function handleStopStream(row: LiveStreamDTO) {
|
|
|
try {
|
|
|
@@ -1332,7 +1373,7 @@ onMounted(async () => {
|
|
|
}
|
|
|
|
|
|
.play-content {
|
|
|
- background-color: #f5f7fa;
|
|
|
+ //background-color: #f5f7fa;
|
|
|
}
|
|
|
|
|
|
.drawer-header {
|
|
|
@@ -1406,8 +1447,8 @@ onMounted(async () => {
|
|
|
.media-drawer-content {
|
|
|
display: flex;
|
|
|
height: 100%;
|
|
|
- padding: 16px;
|
|
|
- gap: 16px;
|
|
|
+ padding: 16px 0;
|
|
|
+ gap: 8px;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
|
|
|
@@ -1417,18 +1458,17 @@ onMounted(async () => {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
min-width: 0;
|
|
|
- background-color: #fff;
|
|
|
- border-radius: 8px;
|
|
|
+ background-color: #e1e1e1;
|
|
|
+ // border-radius: 0px;
|
|
|
overflow: hidden;
|
|
|
- box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
|
+ // box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
|
|
|
|
|
|
.video-header {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
padding: 12px 16px;
|
|
|
- background-color: #fff;
|
|
|
- border-bottom: 1px solid #e5e7eb;
|
|
|
+ background-color: #f5f7fa;
|
|
|
|
|
|
.header-left {
|
|
|
display: flex;
|
|
|
@@ -1485,8 +1525,7 @@ onMounted(async () => {
|
|
|
align-items: center;
|
|
|
gap: 8px;
|
|
|
padding: 12px 16px;
|
|
|
- background-color: #fff;
|
|
|
- border-top: 1px solid #e5e7eb;
|
|
|
+ background: #e5e7eb;
|
|
|
}
|
|
|
}
|
|
|
|