添加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">
|
||||
import { ref } from '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>
|
||||
|
||||
<template>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
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>
|
||||
|
|
|
|||
|
|
@ -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