添加ZLM样例代码
This commit is contained in:
parent
024c01e248
commit
de81b78cdc
91
src/App.vue
91
src/App.vue
|
|
@ -1,18 +1,107 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
import WebSocketDemo from './components/WebSocketDemo.vue'
|
import WebSocketDemo from './components/WebSocketDemo.vue'
|
||||||
|
import Zlm from './components/Zlm.vue'
|
||||||
|
import Wvp from './components/Wvp.vue'
|
||||||
|
|
||||||
|
type TabType = 'websocket' | 'zlm' | 'wvp'
|
||||||
|
|
||||||
|
const activeTab = ref<TabType>('websocket')
|
||||||
|
|
||||||
|
const switchTab = (tab: TabType) => {
|
||||||
|
activeTab.value = tab
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<WebSocketDemo />
|
<div class="tabs">
|
||||||
|
<button
|
||||||
|
:class="{ active: activeTab === 'websocket' }"
|
||||||
|
@click="switchTab('websocket')"
|
||||||
|
>
|
||||||
|
WebSocket Demo
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
:class="{ active: activeTab === 'zlm' }"
|
||||||
|
@click="switchTab('zlm')"
|
||||||
|
>
|
||||||
|
ZLM
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
:class="{ active: activeTab === 'wvp' }"
|
||||||
|
@click="switchTab('wvp')"
|
||||||
|
>
|
||||||
|
WVP
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-content">
|
||||||
|
<WebSocketDemo v-if="activeTab === 'websocket'" />
|
||||||
|
<Zlm v-if="activeTab === 'zlm'" />
|
||||||
|
<Wvp v-if="activeTab === 'wvp'" />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-bottom: 2px solid #e0e0e0;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
color: #666;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs button:hover {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs button.active {
|
||||||
|
background-color: #2196f3;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-content {
|
||||||
|
animation: fadeIn 0.3s ease;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(10px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="wvp-container">
|
||||||
|
<h2>WVP 管理</h2>
|
||||||
|
<div class="content">
|
||||||
|
<p>WVP 视频平台管理界面</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.wvp-container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content p {
|
||||||
|
color: #666;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,889 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
const GATEWAY_URL = 'http://127.0.0.1:8080'
|
||||||
|
|
||||||
|
// getSnap 接口参数
|
||||||
|
const snapUrl = ref('rtsp://127.0.0.1:10002/live/prod')
|
||||||
|
const timeoutSec = ref(10)
|
||||||
|
const expireSec = ref(1)
|
||||||
|
const snapLoading = ref(false)
|
||||||
|
const snapError = ref('')
|
||||||
|
const snapshotImage = ref('')
|
||||||
|
|
||||||
|
const getSnapshot = async () => {
|
||||||
|
snapLoading.value = true
|
||||||
|
snapError.value = ''
|
||||||
|
snapshotImage.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
url: snapUrl.value,
|
||||||
|
timeout_sec: timeoutSec.value.toString(),
|
||||||
|
expire_sec: expireSec.value.toString()
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await fetch(`${GATEWAY_URL}/zlm/index/api/getSnap?${params}`)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await response.blob()
|
||||||
|
snapshotImage.value = URL.createObjectURL(blob)
|
||||||
|
} catch (error) {
|
||||||
|
snapError.value = error instanceof Error ? error.message : '获取快照失败'
|
||||||
|
} finally {
|
||||||
|
snapLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const downloadSnapshot = () => {
|
||||||
|
if (snapshotImage.value) {
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = snapshotImage.value
|
||||||
|
a.download = 'snapshot.jpg'
|
||||||
|
a.click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// startRecord 接口参数
|
||||||
|
const recordType = ref(1)
|
||||||
|
const recordVhost = ref('__defaultVhost__')
|
||||||
|
const recordApp = ref('live')
|
||||||
|
const recordStream = ref('prod')
|
||||||
|
const maxSecond = ref(86400)
|
||||||
|
const recordLoading = ref(false)
|
||||||
|
const recordError = ref('')
|
||||||
|
const recordResult = ref('')
|
||||||
|
|
||||||
|
const startRecord = async () => {
|
||||||
|
recordLoading.value = true
|
||||||
|
recordError.value = ''
|
||||||
|
recordResult.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
type: recordType.value.toString(),
|
||||||
|
vhost: recordVhost.value,
|
||||||
|
app: recordApp.value,
|
||||||
|
stream: recordStream.value,
|
||||||
|
max_second: maxSecond.value.toString()
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await fetch(`${GATEWAY_URL}/zlm/index/api/startRecord?${params}`)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
recordResult.value = JSON.stringify(data, null, 2)
|
||||||
|
} catch (error) {
|
||||||
|
recordError.value = error instanceof Error ? error.message : '开始录制失败'
|
||||||
|
} finally {
|
||||||
|
recordLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// isRecording 接口参数
|
||||||
|
const checkType = ref(1)
|
||||||
|
const checkVhost = ref('__defaultVhost__')
|
||||||
|
const checkApp = ref('live')
|
||||||
|
const checkStream = ref('prod')
|
||||||
|
const checkLoading = ref(false)
|
||||||
|
const checkError = ref('')
|
||||||
|
const checkResult = ref('')
|
||||||
|
|
||||||
|
const checkRecording = async () => {
|
||||||
|
checkLoading.value = true
|
||||||
|
checkError.value = ''
|
||||||
|
checkResult.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
type: checkType.value.toString(),
|
||||||
|
vhost: checkVhost.value,
|
||||||
|
app: checkApp.value,
|
||||||
|
stream: checkStream.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await fetch(`${GATEWAY_URL}/zlm/index/api/isRecording?${params}`)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
checkResult.value = JSON.stringify(data, null, 2)
|
||||||
|
} catch (error) {
|
||||||
|
checkError.value = error instanceof Error ? error.message : '检查录制状态失败'
|
||||||
|
} finally {
|
||||||
|
checkLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMp4RecordFile 接口参数
|
||||||
|
const mp4Vhost = ref('__defaultVhost__')
|
||||||
|
const mp4App = ref('live')
|
||||||
|
const mp4Stream = ref('prod')
|
||||||
|
const mp4Period = ref('2025-12-09')
|
||||||
|
const mp4Loading = ref(false)
|
||||||
|
const mp4Error = ref('')
|
||||||
|
const mp4Result = ref('')
|
||||||
|
|
||||||
|
const getMp4RecordFile = async () => {
|
||||||
|
mp4Loading.value = true
|
||||||
|
mp4Error.value = ''
|
||||||
|
mp4Result.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
vhost: mp4Vhost.value,
|
||||||
|
app: mp4App.value,
|
||||||
|
stream: mp4Stream.value,
|
||||||
|
period: mp4Period.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await fetch(`${GATEWAY_URL}/zlm/index/api/getMp4RecordFile?${params}`)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
mp4Result.value = JSON.stringify(data, null, 2)
|
||||||
|
} catch (error) {
|
||||||
|
mp4Error.value = error instanceof Error ? error.message : '获取录制文件列表失败'
|
||||||
|
} finally {
|
||||||
|
mp4Loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// stopRecord 接口参数
|
||||||
|
const stopType = ref(1)
|
||||||
|
const stopVhost = ref('__defaultVhost__')
|
||||||
|
const stopApp = ref('live')
|
||||||
|
const stopStream = ref('prod')
|
||||||
|
const stopLoading = ref(false)
|
||||||
|
const stopError = ref('')
|
||||||
|
const stopResult = ref('')
|
||||||
|
|
||||||
|
const stopRecord = async () => {
|
||||||
|
stopLoading.value = true
|
||||||
|
stopError.value = ''
|
||||||
|
stopResult.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
type: stopType.value.toString(),
|
||||||
|
vhost: stopVhost.value,
|
||||||
|
app: stopApp.value,
|
||||||
|
stream: stopStream.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await fetch(`${GATEWAY_URL}/zlm/index/api/stopRecord?${params}`)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
stopResult.value = JSON.stringify(data, null, 2)
|
||||||
|
} catch (error) {
|
||||||
|
stopError.value = error instanceof Error ? error.message : '停止录制失败'
|
||||||
|
} finally {
|
||||||
|
stopLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// downloadFile 接口参数
|
||||||
|
const downloadFilePath = ref('/opt/media/bin/www/record/live/prod/2025-12-09/2025-12-09-16-49-44-0.mp4')
|
||||||
|
const downloadLoading = ref(false)
|
||||||
|
const downloadError = ref('')
|
||||||
|
const downloadSuccess = ref('')
|
||||||
|
|
||||||
|
const downloadFile = async () => {
|
||||||
|
downloadLoading.value = true
|
||||||
|
downloadError.value = ''
|
||||||
|
downloadSuccess.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
file_path: downloadFilePath.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await fetch(`${GATEWAY_URL}/zlm/index/api/downloadFile?${params}`)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await response.blob()
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = url
|
||||||
|
|
||||||
|
// 从文件路径中提取文件名
|
||||||
|
const fileName = downloadFilePath.value.split('/').pop() || 'recording.mp4'
|
||||||
|
a.download = fileName
|
||||||
|
a.click()
|
||||||
|
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
downloadSuccess.value = `文件 ${fileName} 下载成功`
|
||||||
|
} catch (error) {
|
||||||
|
downloadError.value = error instanceof Error ? error.message : '下载文件失败'
|
||||||
|
} finally {
|
||||||
|
downloadLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// deleteRecordDirectory 接口参数
|
||||||
|
const deleteVhost = ref('__defaultVhost__')
|
||||||
|
const deleteApp = ref('live')
|
||||||
|
const deleteStream = ref('prod')
|
||||||
|
const deletePeriod = ref('2025-12-09')
|
||||||
|
const deleteLoading = ref(false)
|
||||||
|
const deleteError = ref('')
|
||||||
|
const deleteResult = ref('')
|
||||||
|
|
||||||
|
const deleteRecordDirectory = async () => {
|
||||||
|
deleteLoading.value = true
|
||||||
|
deleteError.value = ''
|
||||||
|
deleteResult.value = ''
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
vhost: deleteVhost.value,
|
||||||
|
app: deleteApp.value,
|
||||||
|
stream: deleteStream.value,
|
||||||
|
period: deletePeriod.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const response = await fetch(`${GATEWAY_URL}/zlm/index/api/deleteRecordDirectory?${params}`)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json()
|
||||||
|
deleteResult.value = JSON.stringify(data, null, 2)
|
||||||
|
} catch (error) {
|
||||||
|
deleteError.value = error instanceof Error ? error.message : '删除录制目录失败'
|
||||||
|
} finally {
|
||||||
|
deleteLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="zlm-container">
|
||||||
|
<h2>ZLM 流媒体服务器管理</h2>
|
||||||
|
|
||||||
|
<div class="api-section">
|
||||||
|
<h3>1. 获取流快照 (getSnap)</h3>
|
||||||
|
<div class="api-description">
|
||||||
|
<p>通过网关调用: <code>GET /zlm/index/api/getSnap</code></p>
|
||||||
|
<p>实际转发到: <code>http://114.67.89.4:8778/index/api/getSnap</code></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>RTSP URL:</label>
|
||||||
|
<input
|
||||||
|
v-model="snapUrl"
|
||||||
|
type="text"
|
||||||
|
placeholder="rtsp://127.0.0.1:10002/live/prod"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>超时时间 (秒):</label>
|
||||||
|
<input
|
||||||
|
v-model.number="timeoutSec"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="60"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>过期时间 (秒):</label>
|
||||||
|
<input
|
||||||
|
v-model.number="expireSec"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
max="3600"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button @click="getSnapshot" :disabled="snapLoading">
|
||||||
|
{{ snapLoading ? '获取中...' : '获取快照' }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="snapshotImage"
|
||||||
|
@click="downloadSnapshot"
|
||||||
|
class="secondary"
|
||||||
|
>
|
||||||
|
下载快照
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="snapError" class="error">
|
||||||
|
错误: {{ snapError }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="snapshotImage" class="snapshot-preview">
|
||||||
|
<h4>快照预览:</h4>
|
||||||
|
<img :src="snapshotImage" alt="Stream Snapshot" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="api-section">
|
||||||
|
<h3>2. 开始录制 (startRecord)</h3>
|
||||||
|
<div class="api-description">
|
||||||
|
<p>通过网关调用: <code>GET /zlm/index/api/startRecord</code></p>
|
||||||
|
<p>实际转发到: <code>http://114.67.89.4:8778/index/api/startRecord</code></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>类型 (type):</label>
|
||||||
|
<input
|
||||||
|
v-model.number="recordType"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>虚拟主机 (vhost):</label>
|
||||||
|
<input
|
||||||
|
v-model="recordVhost"
|
||||||
|
type="text"
|
||||||
|
placeholder="__defaultVhost__"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>应用名 (app):</label>
|
||||||
|
<input
|
||||||
|
v-model="recordApp"
|
||||||
|
type="text"
|
||||||
|
placeholder="live"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>流ID (stream):</label>
|
||||||
|
<input
|
||||||
|
v-model="recordStream"
|
||||||
|
type="text"
|
||||||
|
placeholder="prod"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>最大录制时长 (秒):</label>
|
||||||
|
<input
|
||||||
|
v-model.number="maxSecond"
|
||||||
|
type="number"
|
||||||
|
min="1"
|
||||||
|
placeholder="86400"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button @click="startRecord" :disabled="recordLoading">
|
||||||
|
{{ recordLoading ? '录制中...' : '开始录制' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="recordError" class="error">
|
||||||
|
错误: {{ recordError }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="recordResult" class="result-preview">
|
||||||
|
<h4>响应结果:</h4>
|
||||||
|
<pre>{{ recordResult }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="api-section">
|
||||||
|
<h3>3. 检查录制状态 (isRecording)</h3>
|
||||||
|
<div class="api-description">
|
||||||
|
<p>通过网关调用: <code>GET /zlm/index/api/isRecording</code></p>
|
||||||
|
<p>实际转发到: <code>http://114.67.89.4:8778/index/api/isRecording</code></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>类型 (type):</label>
|
||||||
|
<input
|
||||||
|
v-model.number="checkType"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>虚拟主机 (vhost):</label>
|
||||||
|
<input
|
||||||
|
v-model="checkVhost"
|
||||||
|
type="text"
|
||||||
|
placeholder="__defaultVhost__"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>应用名 (app):</label>
|
||||||
|
<input
|
||||||
|
v-model="checkApp"
|
||||||
|
type="text"
|
||||||
|
placeholder="live"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>流ID (stream):</label>
|
||||||
|
<input
|
||||||
|
v-model="checkStream"
|
||||||
|
type="text"
|
||||||
|
placeholder="prod"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button @click="checkRecording" :disabled="checkLoading">
|
||||||
|
{{ checkLoading ? '检查中...' : '检查录制状态' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="checkError" class="error">
|
||||||
|
错误: {{ checkError }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="checkResult" class="result-preview">
|
||||||
|
<h4>响应结果:</h4>
|
||||||
|
<pre>{{ checkResult }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="api-section">
|
||||||
|
<h3>4. 获取MP4录制文件列表 (getMp4RecordFile)</h3>
|
||||||
|
<div class="api-description">
|
||||||
|
<p>通过网关调用: <code>GET /zlm/index/api/getMp4RecordFile</code></p>
|
||||||
|
<p>实际转发到: <code>http://114.67.89.4:8778/index/api/getMp4RecordFile</code></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>虚拟主机 (vhost):</label>
|
||||||
|
<input
|
||||||
|
v-model="mp4Vhost"
|
||||||
|
type="text"
|
||||||
|
placeholder="__defaultVhost__"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>应用名 (app):</label>
|
||||||
|
<input
|
||||||
|
v-model="mp4App"
|
||||||
|
type="text"
|
||||||
|
placeholder="live"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>流ID (stream):</label>
|
||||||
|
<input
|
||||||
|
v-model="mp4Stream"
|
||||||
|
type="text"
|
||||||
|
placeholder="prod"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>日期 (period):</label>
|
||||||
|
<input
|
||||||
|
v-model="mp4Period"
|
||||||
|
type="date"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button @click="getMp4RecordFile" :disabled="mp4Loading">
|
||||||
|
{{ mp4Loading ? '查询中...' : '获取录制文件' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="mp4Error" class="error">
|
||||||
|
错误: {{ mp4Error }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="mp4Result" class="result-preview">
|
||||||
|
<h4>响应结果:</h4>
|
||||||
|
<pre>{{ mp4Result }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="api-section">
|
||||||
|
<h3>5. 停止录制 (stopRecord)</h3>
|
||||||
|
<div class="api-description">
|
||||||
|
<p>通过网关调用: <code>GET /zlm/index/api/stopRecord</code></p>
|
||||||
|
<p>实际转发到: <code>http://114.67.89.4:8778/index/api/stopRecord</code></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>类型 (type):</label>
|
||||||
|
<input
|
||||||
|
v-model.number="stopType"
|
||||||
|
type="number"
|
||||||
|
min="0"
|
||||||
|
max="1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>虚拟主机 (vhost):</label>
|
||||||
|
<input
|
||||||
|
v-model="stopVhost"
|
||||||
|
type="text"
|
||||||
|
placeholder="__defaultVhost__"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>应用名 (app):</label>
|
||||||
|
<input
|
||||||
|
v-model="stopApp"
|
||||||
|
type="text"
|
||||||
|
placeholder="live"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>流ID (stream):</label>
|
||||||
|
<input
|
||||||
|
v-model="stopStream"
|
||||||
|
type="text"
|
||||||
|
placeholder="prod"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button @click="stopRecord" :disabled="stopLoading" class="danger">
|
||||||
|
{{ stopLoading ? '停止中...' : '停止录制' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="stopError" class="error">
|
||||||
|
错误: {{ stopError }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="stopResult" class="result-preview">
|
||||||
|
<h4>响应结果:</h4>
|
||||||
|
<pre>{{ stopResult }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="api-section">
|
||||||
|
<h3>6. 下载录制文件 (downloadFile)</h3>
|
||||||
|
<div class="api-description">
|
||||||
|
<p>通过网关调用: <code>GET /zlm/index/api/downloadFile</code></p>
|
||||||
|
<p>实际转发到: <code>http://114.67.89.4:8778/index/api/downloadFile</code></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>文件路径 (file_path):</label>
|
||||||
|
<input
|
||||||
|
v-model="downloadFilePath"
|
||||||
|
type="text"
|
||||||
|
placeholder="/opt/media/bin/www/record/live/prod/2025-12-09/2025-12-09-16-49-44-0.mp4"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button @click="downloadFile" :disabled="downloadLoading" class="secondary">
|
||||||
|
{{ downloadLoading ? '下载中...' : '下载文件' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="downloadError" class="error">
|
||||||
|
错误: {{ downloadError }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="downloadSuccess" class="success">
|
||||||
|
{{ downloadSuccess }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="api-section">
|
||||||
|
<h3>7. 删除录制目录 (deleteRecordDirectory)</h3>
|
||||||
|
<div class="api-description">
|
||||||
|
<p>通过网关调用: <code>GET /zlm/index/api/deleteRecordDirectory</code></p>
|
||||||
|
<p>实际转发到: <code>http://114.67.89.4:8778/index/api/deleteRecordDirectory</code></p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>虚拟主机 (vhost):</label>
|
||||||
|
<input
|
||||||
|
v-model="deleteVhost"
|
||||||
|
type="text"
|
||||||
|
placeholder="__defaultVhost__"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>应用名 (app):</label>
|
||||||
|
<input
|
||||||
|
v-model="deleteApp"
|
||||||
|
type="text"
|
||||||
|
placeholder="live"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>流ID (stream):</label>
|
||||||
|
<input
|
||||||
|
v-model="deleteStream"
|
||||||
|
type="text"
|
||||||
|
placeholder="prod"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>日期 (period):</label>
|
||||||
|
<input
|
||||||
|
v-model="deletePeriod"
|
||||||
|
type="date"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-group">
|
||||||
|
<button @click="deleteRecordDirectory" :disabled="deleteLoading" class="danger">
|
||||||
|
{{ deleteLoading ? '删除中...' : '删除录制目录' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="deleteError" class="error">
|
||||||
|
错误: {{ deleteError }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="deleteResult" class="result-preview">
|
||||||
|
<h4>响应结果:</h4>
|
||||||
|
<pre>{{ deleteResult }}</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.zlm-container {
|
||||||
|
max-width: 1000px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-section {
|
||||||
|
background-color: #fff;
|
||||||
|
border: 1px solid #e0e0e0;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-section h3 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
color: #2196f3;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-description {
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-description p {
|
||||||
|
margin: 5px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.api-description code {
|
||||||
|
background-color: #e0e0e0;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #d32f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 12px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #2196f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #2196f3;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover:not(:disabled) {
|
||||||
|
background-color: #1976d2;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:disabled {
|
||||||
|
background-color: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.secondary {
|
||||||
|
background-color: #4caf50;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.secondary:hover:not(:disabled) {
|
||||||
|
background-color: #45a049;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.danger {
|
||||||
|
background-color: #f44336;
|
||||||
|
}
|
||||||
|
|
||||||
|
button.danger:hover:not(:disabled) {
|
||||||
|
background-color: #d32f2f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: #ffebee;
|
||||||
|
color: #c62828;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.success {
|
||||||
|
margin-top: 15px;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: #e8f5e9;
|
||||||
|
color: #2e7d32;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snapshot-preview {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snapshot-preview h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.snapshot-preview img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-preview {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 15px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-preview h4 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-preview pre {
|
||||||
|
background-color: #2d2d2d;
|
||||||
|
color: #f8f8f2;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow-x: auto;
|
||||||
|
font-family: 'Courier New', monospace;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Loading…
Reference in New Issue