a-zlm/www/webassist/index.html

2989 lines
173 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="icon" type="image/x-icon" href="./assets/favicon.ico">
<!-- import CSS -->
<link rel="stylesheet" href="./assets/elementui/index.css">
<!-- import JS -->
<script src="./js/utils.js"></script>
</head>
<body>
<div id="app">
<el-container>
<el-header>
<el-row>
<el-col :xs="23" :sm="23" :md="23" :lg="23" :xl="23">
<el-menu :default-active="selectIndex" class="el-menu-demo" mode="horizontal"
@select="handleSelect" background-color="#545c64" text-color="#fff"
active-text-color="#ffd04b">
<el-menu-item index="1" disable> <template slot="title">
<el-image style="height: 100%" src="./assets/logo.png" fit="fill"></el-image>
</template>
</el-menu-item>
<el-menu-item index="2">WebRTC测试</el-menu-item>
<el-menu-item index="3">拉流代理</el-menu-item>
<el-menu-item index="4">推流代理</el-menu-item>
<el-menu-item index="5">FFmpeg推拉流</el-menu-item>
<el-menu-item index="6">Rtp服务</el-menu-item>
<el-menu-item index="7">服务器配置</el-menu-item>
</el-menu>
</el-col>
<el-col :xs="1" :sm="1" :md="1" :lg="1" :xl="1" v-show="selectIndex==='7'">
<el-popconfirm confirm-button-text='好的' cancel-button-text='不用了' icon="el-icon-info"
icon-color="red" title="这只有Daemon方式才能重启否则是直接关闭确定重启吗" @confirm="restartServer">
<el-button type="danger" plain slot="reference" icon="el-icon-refresh-right">重启</el-button>
</el-popconfirm>
</el-col>
</el-row>
</el-header>
<el-main>
<div v-show="selectIndex==='1'">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
<div style="height: 300px; background-color: whitesmoke; border-radius: 20px;">
<div style="height: 100%; width: 100%;" ref="chart1">
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
<div style="height: 300px; background-color: whitesmoke; border-radius: 20px;">
<div style="height: 100%; width: 100%;" ref="chart2">
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="8" :xl="8">
<div style="height: 300px; background-color: whitesmoke;border-radius: 20px;">
<div style="height: 100%; width: 100%;" ref="chart3">
</div>
</div>
</el-col>
</el-row>
<el-divider><i class="el-icon-s-data"></i></el-divider>
<div style="width: 100%;text-align: right;">
<el-checkbox label="自动刷新" v-model="autorefresh_medialist" ></el-checkbox>
<el-button type="primary" icon="el-icon-refresh" @click="getMediaList">刷新</el-button>
</div>
<el-table :data="medialist" empty-text="暂无数据">
<!-- <el-table-column prop="vhost" label="主机名"> </el-table-column> -->
<el-table-column sortable prop="app" label="应用名"> </el-table-column>
<el-table-column prop="stream" label="流id"> </el-table-column>
<el-table-column sortable prop="schema" label="协议" :filters="schemaFilters" :filter-method="filterSchema"> </el-table-column>
<el-table-column prop="createStamp" label="创建时间">
<template #default="scope">
{{scope.row.createStamp | secFormat}}
</template>
</el-table-column>
<el-table-column prop="aliveSecond" label="时长(s)">
</el-table-column>
<el-table-column prop="bytesSpeed" label="码率">
<template #default="scope">
{{scope.row.bytesSpeed | bitsSpeed}}/s
</template>
</el-table-column>
<el-table-column label="录制">
<template #default="scope">
<el-tag v-if="scope.row.isRecordingHLS">录制HLS</el-tag>
<el-tag v-if="scope.row.isRecordingMP4">录制Mp4</el-tag>
</template>
</el-table-column>
<!--
<el-table-column prop="isRecordingHLS" label="是否录制HLS">
<template #default="scope">
<el-tag v-if="scope.row.isRecordingHLS" type="success" effect="dark">是</el-tag>
<el-tag v-else type="danger" effect="dark">否</el-tag>
</template>
</el-table-column>
<el-table-column prop="isRecordingMP4" label="是否录制MP4">
<template #default="scope">
<el-tag v-if="scope.row.isRecordingMP4" type="success" effect="dark">是</el-tag>
<el-tag v-else type="danger" effect="dark">否</el-tag>
</template>
</el-table-column>
<el-table-column prop="originType" label="源类型">
<template #default="scope">
<el-tag v-if="scope.row.originType==0">unknown</el-tag>
<el-tag v-else-if="scope.row.originType==1">rtmp_push</el-tag>
<el-tag v-else-if="scope.row.originType==2">rtsp_push</el-tag>
<el-tag v-else-if="scope.row.originType==3">rtp_push</el-tag>
<el-tag v-else-if="scope.row.originType==4">pull</el-tag>
<el-tag v-else-if="scope.row.originType==5">ffmpeg_pull</el-tag>
<el-tag v-else-if="scope.row.originType==6">mp4_vod</el-tag>
<el-tag v-else-if="scope.row.originType==7">device_chn</el-tag>
<el-tag v-else-if="scope.row.originType==8">rtc_push</el-tag>
</template>
</el-table-column>
-->
<el-table-column prop="originTypeStr" label="类型"> </el-table-column>
<el-table-column prop="originUrl" label="源地址"> </el-table-column>
<!--
<el-table-column prop="readerCount" label="本协议观看人数"> </el-table-column>
<el-table-column sortable prop="totalReaderCount" label="观看总人数"> </el-table-column>
-->
<el-table-column sortable prop="totalReaderCount" label="观看人数">
<template slot-scope="scope">
<el-button @click="getPlayerList(scope.row)">{{scope.row.readerCount}}/{{scope.row.totalReaderCount}}</el-button>
</template>
</el-table-column>
<el-table-column label="网络信息">
<el-table-column prop="originSock.identifier" label="identifier"> </el-table-column>
<el-table-column label="local_ip">
<template slot-scope="scope">{{scope.row.originSock.local_ip}}:{{scope.row.originSock.local_port}}</template>
</el-table-column>
<el-table-column label="peer_ip">
<template slot-scope="scope">{{scope.row.originSock.peer_ip}}:{{scope.row.originSock.peer_port}}</template>
</el-table-column>
<!--
<el-table-column prop="originSock.local_ip" label="local_ip"> </el-table-column>
<el-table-column prop="originSock.local_port" label="local_port"> </el-table-column>
<el-table-column prop="originSock.peer_ip" label="peer_ip"> </el-table-column>
<el-table-column prop="originSock.peer_port" label="peer_port"> </el-table-column>
-->
</el-table-column>
<el-table-column type="expand" label="媒体轨道">
<template slot-scope="scope">
<el-table v-if="scope.row.tracks.length > 0" :data="scope.row.tracks">
<!--
<el-table-column prop="codec_type" label="编码类型">
</el-table-column>
<el-table-column prop="codec_id" label="编码类型ID">
</el-table-column>
-->
<el-table-column prop="codec_id_name" label="编码">
<template #default="scope">
<el-tag type="success">{{ scope.row.codec_id_name}}</el-tag>
</template>
</el-table-column>
<el-table-column prop="frames" label="接收帧数">
</el-table-column>
<el-table-column prop="duration" label="时长(s)">
<template #default="scope">{{ scope.row.duration/1000}}</template>
</el-table-column>
<el-table-column prop="ready" label="已就绪">
<template #default="scope">
<el-tag v-if="scope.row.ready" type="success" effect="dark"></el-tag>
<el-tag v-else type="danger" effect="dark"></el-tag>
</template>
</el-table-column>
<el-table-column label="编码属性">
<template #default="scope">
<el-tag v-if="scope.row.codec_type==1">{{scope.row.channels}}x{{scope.row.sample_rate}}x{{scope.row.sample_bit}}</el-tag>
<el-tag v-else>{{scope.row.width}}x{{scope.row.height}}@{{scope.row.fps}}</el-tag>
</template>
</el-table-column>
<el-table-column prop="key_frames" label="关键帧数">
</el-table-column>
<el-table-column prop="gop_interval_ms" label="gop间隔时间(s)">
</el-table-column>
<el-table-column prop="gop_size" label="gop大小">
</el-table-column>
</el-table>
<el-empty description="无tracks" v-else></el-empty>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" type="danger"
@click="close_stream(scope.$index, scope.row)">关闭</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog :title="playerTitle"
width="60%" top="2rem"
:close-on-click-modal="true"
:visible.sync="showPlayerDialog">
<div id="shared" style="margin-top: 1rem;">
<el-table :data="playerlist" empty-text="暂无播放器">
<el-table-column prop="identifier" label="id"></el-table-column>
<el-table-column prop="local_ip" label="本机ip"></el-table-column>
<el-table-column prop="local_port" label="本机端口"></el-table-column>
<el-table-column prop="peer_ip" label="对端ip"></el-table-column>
<el-table-column prop="peer_port" label="对端端口"></el-table-column>
<el-table-column prop="typeid" label="类型"></el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" type="danger"
@click="kick_session(scope.row.identifier)">断开</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-dialog>
<el-divider><i class="el-icon-connection"></i></el-divider>
<div style="width: 100%;text-align: right;">
<el-checkbox label="自动刷新" v-model="autorefresh_session" ></el-checkbox>
<el-button type="primary" icon="el-icon-refresh" @click="getAllSession">刷新</el-button>
</div>
<el-table :data="sessionlist" empty-text="暂无连接数据">
<el-table-column prop="id" label="id"></el-table-column>
<el-table-column prop="local_ip" label="本机ip"></el-table-column>
<el-table-column prop="local_port" label="本机端口"></el-table-column>
<el-table-column prop="peer_ip" label="对端ip"></el-table-column>
<el-table-column prop="peer_port" label="对端端口"></el-table-column>
<el-table-column prop="typeid" label="类型"></el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" type="danger"
@click="kick_session(scope.row.id)">断开</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div v-show="selectIndex==='2'">
<el-row :gutter="20">
<el-col :span="24">
<el-row :gutter="10" style="height: 600px;display: flex; margin-bottom: 10px;">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<video ref='video' controls autoplay class="videobox">
Your browser is too old which doesn't support HTML5 video.
</video>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<video ref='selfVideo' controls autoplay class="videobox">
Your browser is too old which doesn't support HTML5 video.
</video>
</el-col>
</el-row>
</el-col>
<el-col :span="12" :offset="6">
<el-form v-model="webrtc" label-width="160px">
<el-form-item label="url">
<el-input v-model="webrtc.zlmsdpUrl"></el-input>
</el-form-item>
<el-form-item label="">
<el-checkbox label="simulcast" v-model="webrtc.simulcast"></el-checkbox>
<el-checkbox label="useCamera" v-model="webrtc.useCamera"></el-checkbox>
<el-checkbox label="audioEnable" v-model="webrtc.audioEnable"></el-checkbox>
<el-checkbox label="videoEnable" v-model="webrtc.videoEnable"></el-checkbox>
</el-form-item>
<el-form-item label="method">
<el-radio-group v-model="webrtc.type">
<el-radio label="echo"></el-radio>
<el-radio label="push"></el-radio>
<el-radio label="play"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="resolution">
<el-select v-model="webrtc.resolution" placeholder="请选择分辨率" @change="$forceUpdate()"
style="width: 100%;">
<el-option v-for="(opt,index) in resolution_opt" :key="index" :label="opt.text"
:value="index"></el-option>
</el-select>
</el-form-item>
<el-form-item label="datachannel">
<el-switch v-model="webrtc.usedatachannel"></el-switch>
</el-form-item>
<el-form-item label="debug日志">
<el-switch v-model="webrtc.debug"></el-switch>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="startwebrtc">开始(start)</el-button>
<el-button type="danger" @click="stopwebrtc">停止(stop)</el-button>
</el-form-item>
<el-form-item label="msgrecv">
<el-input type="textarea" v-model="webrtc.msgrecv"></el-input>
</el-form-item>
<el-form-item label="msgsend">
<el-input type="text" v-model="webrtc.msgsend">
<template slot="append">
<el-button type="success" @click="sendata">发送数据</el-button>
</template>
</el-input>
</el-form-item>
<el-form-item>
<el-button type="info" @click="closedata">关闭(close datachannel)</el-button>
</el-form-item>
</el-form>
</el-col>
</el-row>
</div>
<div v-if="selectIndex==='3'" style="display: flex;">
<div style="border-radius: 4px; background-color: #545c6421;margin: 10px; flex:1">
<el-form style="margin: 10px;">
<!-- <el-form-item label="api操作密钥" label-width="180px">
<el-input v-model="secret" disabled></el-input>
</el-form-item> -->
<el-form-item label="虚拟主机" label-width="180px">
<el-input v-model="addStreamProxy_params.vhost"></el-input>
</el-form-item>
<el-form-item label="应用名" label-width="180px">
<el-input v-model="addStreamProxy_params.app"></el-input>
</el-form-item>
<el-form-item label="流id" label-width="180px">
<el-input v-model="addStreamProxy_params.stream"></el-input>
</el-form-item>
<el-form-item label="拉流地址" label-width="180px">
<el-input v-model="addStreamProxy_params.url"
placeholder="例如rtmp://live.hkstv.hk.lxdns.com/live/hks2"></el-input>
</el-form-item>
<el-form-item label="" label-width="180px">
<el-row style="text-align: left;">
<el-col :span="8">
<el-checkbox
v-model="addStreamProxy_params.enable_hls">转hls-mpeg</el-checkbox>
</el-col>
<el-col :span="8">
<el-checkbox
v-model="addStreamProxy_params.enable_hls_fmp4">转hls-fmp4</el-checkbox>
</el-col>
<el-col :span="8">
<el-checkbox
v-model="addStreamProxy_params.enable_mp4">mp4录制</el-checkbox>
</el-col>
<el-col :span="8">
<el-checkbox
v-model="addStreamProxy_params.enable_rtsp">转rtsp</el-checkbox>
</el-col>
<el-col :span="8">
<el-checkbox
v-model="addStreamProxy_params.enable_rtmp">转rtmp/flv</el-checkbox>
</el-col>
<el-col :span="8">
<el-checkbox
v-model="addStreamProxy_params.enable_ts">转http-ts/ws-ts</el-checkbox>
</el-col>
<el-col :span="8">
<el-checkbox
v-model="addStreamProxy_params.enable_fmp4">转http-fmp4/ws-fmp4</el-checkbox>
</el-col>
<el-col :span="8">
<el-checkbox
v-model="addStreamProxy_params.hls_demand">按需生成hls</el-checkbox>
</el-col>
<el-col :span="8">
<el-checkbox
v-model="addStreamProxy_params.rtsp_demand">按需生成rtsp</el-checkbox>
</el-col>
<el-col :span="8">
<el-checkbox
v-model="addStreamProxy_params.rtmp_demand">按需生成rtmp</el-checkbox>
</el-col>
<el-col :span="8">
<el-checkbox
v-model="addStreamProxy_params.ts_demand">按需生成ts</el-checkbox>
</el-col>
<el-col :span="8">
<el-checkbox
v-model="addStreamProxy_params.fmp4_demand">按需生成fmp4</el-checkbox>
</el-col>
<el-col :span="8">
<el-checkbox
v-model="addStreamProxy_params.enable_audio">允许音频</el-checkbox>
</el-col>
<el-col :span="8">
<el-checkbox
v-model="addStreamProxy_params.add_mute_audio">添加静音音轨</el-checkbox>
</el-col>
<el-col :span="16">
<el-checkbox
v-model="addStreamProxy_params.mp4_as_player">MP4录制当作观看者参与播放人数计数</el-checkbox>
</el-col>
<el-col :span="16">
<el-checkbox
v-model="addStreamProxy_params.auto_close">无人观看自动关闭流(不触发无人观看hook)</el-checkbox>
</el-col>
</el-row>
</el-form-item>
<el-form-item label="拉流方式" label-width="180px">
<el-select v-model="addStreamProxy_params.rtp_type" style="width: 100%;">
<el-option label="TCP" value="0"> </el-option>
<el-option label="UDP" value="1"> </el-option>
<el-option label="组播" value="2"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="超时时间" label-width="180px">
<el-input v-model="addStreamProxy_params.timeout_sec"></el-input>
</el-form-item>
<el-form-item label="重试次数" label-width="180px">
<el-input v-model="addStreamProxy_params.retry_count" placeholder="默认为-1无限重试"></el-input>
</el-form-item>
<el-form-item label="时间戳覆盖" label-width="180px">
<el-select v-model="addStreamProxy_params.modify_stamp" style="width: 100%;">
<el-option label="不开启" value=""> </el-option>
<el-option label="绝对时间戳" value="0"> </el-option>
<el-option label="系统时间戳" value="1"> </el-option>
<el-option label="相对时间戳" value="2"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="mp4录像保存目录" label-width="180px">
<el-input v-model="addStreamProxy_params.mp4_save_path"
placeholder="mp4录像保存目录置空使用默认"></el-input>
</el-form-item>
<el-form-item label="mp4切片大小(s)" label-width="180px">
<el-input v-model="addStreamProxy_params.mp4_max_second"
placeholder="mp4录像切片大小(s)"></el-input>
</el-form-item>
<el-form-item label="hls文件保存目录" label-width="180px">
<el-input v-model="addStreamProxy_params.hls_save_path"
placeholder="hls文件保存目录置空使用默认"></el-input>
</el-form-item>
<el-form-item label="">
<el-input type="textarea" :rows="5" v-model="apiurl" readonly></el-input>
</el-form-item>
</el-form>
</div>
<div style="border-radius: 4px; background-color: #545c6421;margin: 10px; flex:1">
<el-form style="margin: 10px;">
<div style="margin-bottom: 10px;width: 100%;text-align: left;">
<el-button type="primary" @click="addStreamProxy">增加</el-button>
<el-button type="primary" icon="el-icon-refresh" @click="listStreamProxy">刷新</el-button>
</div>
<el-table :data="streamProxyList" empty-text="暂无拉流代理">
<!-- <el-table-column sortable prop="key" label="key"> </el-table-column> -->
<el-table-column sortable prop="url" label="url"> </el-table-column>
<el-table-column sortable prop="src.app" label="app"> </el-table-column>
<el-table-column sortable prop="src.stream" label="stream"> </el-table-column>
<el-table-column sortable prop="status" label="状态"> </el-table-column>
<el-table-column sortable prop="liveSecs" label="时长(s)"> </el-table-column>
<el-table-column sortable prop="rePullCount" label="重试次数"> </el-table-column>
<el-table-column sortable prop="totalReaderCount" label="观看人数"> </el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" type="danger"
@click="delStreamProxy(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</el-form>
</div>
</div>
<div v-if="selectIndex==='4'">
<el-form>
<!-- <el-form-item label="api操作密钥" label-width="180px">
<el-input v-model="secret" disabled></el-input>
</el-form-item> -->
<el-form-item label="虚拟主机" label-width="180px">
<el-input v-model="addStreamPusherProxy_params.vhost"></el-input>
</el-form-item>
<el-form-item label="" label-width="180px">
<el-select v-model="addStreamPusherProxy_params.schema" style="width: 100%;">
<el-option label="RTSP" value="rtsp"> </el-option>
<el-option label="RTMP" value="rtmp"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="应用名" label-width="180px">
<el-input v-model="addStreamPusherProxy_params.app"></el-input>
</el-form-item>
<el-form-item label="流id" label-width="180px">
<el-input v-model="addStreamPusherProxy_params.stream"></el-input>
</el-form-item>
<el-form-item label="转推地址" label-width="180px">
<el-input v-model="addStreamPusherProxy_params.dst_url"
placeholder="例如rtmp://live.hkstv.hk.lxdns.com/live/hks2"></el-input>
</el-form-item>
<el-form-item label="推流方式" label-width="180px">
<el-select v-model="addStreamPusherProxy_params.rtp_type" style="width: 100%;">
<el-option label="TCP" value="0"> </el-option>
<el-option label="UDP" value="1"> </el-option>
</el-select>
</el-form-item>
<el-form-item label="超时时间" label-width="180px">
<el-input v-model="addStreamPusherProxy_params.timeout_sec"></el-input>
</el-form-item>
<el-form-item label="重试次数" label-width="180px">
<el-input v-model="addStreamPusherProxy_params.retry_count"></el-input>
</el-form-item>
<el-form-item label="">
<!-- <el-input type="textarea" :rows="5" v-model="addStreamPusherProxy_apiurl" readonly></el-input> -->
<div style="width: 100%;text-align: right;">
<el-button type="primary" @click="addStreamPusherProxy">增加</el-button>
<el-button type="primary" icon="el-icon-refresh" @click="listStreamPusherProxy">刷新</el-button>
</div>
</el-form-item>
</el-form>
<el-table :data="pusherProxyList" empty-text="暂无推流代理">
<!-- <el-table-column sortable prop="key" label="key"> </el-table-column> -->
<el-table-column sortable prop="url" label="url"> </el-table-column>
<el-table-column sortable prop="src.app" label="app"> </el-table-column>
<el-table-column sortable prop="src.stream" label="stream"> </el-table-column>
<el-table-column sortable prop="status" label="状态"> </el-table-column>
<el-table-column sortable prop="liveSecs" label="时长(s)"> </el-table-column>
<el-table-column sortable prop="rePublishCount" label="重试次数"> </el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" type="danger"
@click="delStreamPusherProxy(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div v-show="selectIndex==='5'">
<el-form>
<el-form-item label="源地址" label-width="180px">
<el-input v-model="addFFmpeg_param.src_url"></el-input>
</el-form-item>
<el-form-item label="目的地址" label-width="180px">
<el-input v-model="addFFmpeg_param.dst_url"></el-input>
</el-form-item>
<el-form-item label="命令模板" label-width="180px">
<el-input v-model="addFFmpeg_param.ffmpeg_cmd_key"></el-input>
</el-form-item>
<el-form-item label="" label-width="180px">
<el-checkbox v-model="addFFmpeg_param.enable_mp4">MP4录制</el-checkbox>
<el-checkbox v-model="addFFmpeg_param.enable_hls">HLS录制</el-checkbox>
</el-form-item>
<el-form-item label="超时ms" label-width="180px">
<el-input v-model="addFFmpeg_param.timeout_ms"></el-input>
</el-form-item>
<el-form-item label="">
<el-button type="primary" @click="addFFmpegSource">增加</el-button>
<el-button type="primary" icon="el-icon-refresh" @click="listFFmpegSource">刷新</el-button>
</el-form-item>
</el-form>
<el-table :data="ffmpeglist" empty-text="暂无数据">
<el-table-column sortable prop="src_url" label="源地址"> </el-table-column>
<el-table-column sortable prop="dst_url" label="目的地址"> </el-table-column>
<el-table-column sortable prop="ffmpeg_cmd_key" label="命令模板"></el-table-column>
<el-table-column sortable prop="cmd" label="命令行"> </el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" type="danger"
@click="delFFmpegSource(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div v-show="selectIndex==='6'">
<el-form>
<el-form-item label="虚拟主机" label-width="180px">
<el-input v-model="addRtpServer_param.vhost"></el-input>
</el-form-item>
<el-form-item label="应用名" label-width="180px">
<el-input v-model="addRtpServer_param.app"></el-input>
</el-form-item>
<el-form-item label="流id" label-width="180px">
<el-input v-model="addRtpServer_param.stream_id"></el-input>
</el-form-item>
<el-form-item label="tcp模式" label-width="180px">
<el-select v-model="addRtpServer_param.tcp_mode" style="width: 100%;">
<el-option v-for="(val,key) in typeTcpMode" :label="val" :value="key"></el-option>
</el-select>
</el-form-item>
<el-form-item label="流过滤" label-width="180px">
<el-select v-model="addRtpServer_param.only_track" style="width: 100%;">
<el-option v-for="(val,key) in typeOnlyTrack" :label="val" :value="key"></el-option>
</el-select>
</el-form-item>
<el-form-item label="本地ip" label-width="180px">
<el-input v-model="addRtpServer_param.local_ip"></el-input>
</el-form-item>
<el-form-item label="端口" label-width="180px">
<el-input v-model="addRtpServer_param.port">
<template #append>
<el-checkbox v-model="addRtpServer_param.re_use_port" label="端口复用"/>
</template>
</el-input>
</el-form-item>
<el-form-item label="SSRC" label-width="180px">
<el-input v-model="addRtpServer_param.ssrc"></el-input>
</el-form-item>
<el-form-item label="">
<el-button type="primary" @click="openRtpServer">增加</el-button>
<el-button type="primary" icon="el-icon-refresh" @click="listRtpServer">刷新</el-button>
</el-form-item>
</el-form>
<el-table :data="rtpserverlist" empty-text="暂无数据">
<el-table-column sortable prop="vhost" label="vhost"> </el-table-column>
<el-table-column sortable prop="app" label="app"> </el-table-column>
<el-table-column sortable prop="stream_id" label="stream"> </el-table-column>
<el-table-column sortable prop="port" label="端口"></el-table-column>
<el-table-column sortable prop="tcp_mode" label="tcp模式">
<template slot-scope="scope">
{{typeTcpMode[scope.row.tcp_mode]}}
</template>
</el-table-column>
<el-table-column sortable prop="only_track" label="流过滤">
<template slot-scope="scope">
{{typeOnlyTrack[scope.row.only_track]}}
</template>
</el-table-column>
<el-table-column label="ssrc" width="160">
<template slot-scope="scope">
<el-input v-model="scope.row.ssrc" class="input-with-select">
<template #append>
<el-button icon="el-icon-refresh" @click="updateRtpServerSSRC(scope.row)"></el-button>
</template>
</el-input>
</template>
</el-table-column>
<el-table-column prop="dst_url" label="目的地址" width="280">
<template slot-scope="scope">
<div style="display: flex;">
<el-input placeholder="ip" v-model="scope.row.dst_url" style="flex:4;"></el-input>
<el-input placeholder="port" v-model="scope.row.dst_port" style="flex:2"></el-input>
<el-button size="small" @click="connectRtpServer(scope.$index, scope.row)">连接</el-button>
</div>
</template>
</el-table-column>
<el-table-column label="操作">
<template slot-scope="scope">
<el-button size="mini" type="danger"
@click="closeRtpServer(scope.$index, scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<div v-if="selectIndex==='7'">
<el-tabs tab-position="left" style="height: 100%;">
<el-tab-pane label="通用设置">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="24" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
check_nvidia_dev
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.general.check_nvidia_dev" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
启用虚拟主机
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.general.enableVhost" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
服务器唯一id
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.general.mediaServerId"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
启用FFMPEG日志
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.general.enable_ffmpeg_log" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
流超时时间(ms)
</div>
<div class="conf_item_value">
<el-input type="number"
v-model="serverConfig.general.maxStreamWaitMS"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
合并写缓存(ms)
</div>
<div class="conf_item_value">
<el-input type="number"
v-model="serverConfig.general.mergeWriteMS"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
断连删除
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.general.resetWhenRePlay" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
无人观看流的释放超时(ms)
</div>
<div class="conf_item_value">
<el-input type="number"
v-model="serverConfig.general.streamNoneReaderDelayMS"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
未就绪缓存帧数
</div>
<div class="conf_item_value">
<el-input type="number"
v-model="serverConfig.general.unready_frame_cache"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
加流超时(ms)
</div>
<div class="conf_item_value">
<el-input type="number"
v-model="serverConfig.general.wait_add_track_ms"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
流就绪超时(ms)
</div>
<div class="conf_item_value">
<el-input type="number"
v-model="serverConfig.general.wait_track_ready_ms"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
on_flow_report事件阈值
</div>
<div class="conf_item_value">
<el-input type="number"
v-model="serverConfig.general.flowThreshold"></el-input>
</div>
</div>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="FFMPEG设置">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
ffmpeg路径
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.ffmpeg.bin"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
拉流再推流命令模板
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.ffmpeg.cmd"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
日志路径
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.ffmpeg.log"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
自动重启时间
</div>
<div class="conf_item_value">
<el-input type="number"
v-model="serverConfig.ffmpeg.restart_sec"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
截图模板
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.ffmpeg.snap"></el-input>
</div>
</div>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="API设置">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
调试
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.api.apiDebug" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
密钥
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.api.secret"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
截图路径
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.api.snapRoot"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
缺省截图
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.api.defaultSnap"></el-input>
</div>
</div>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="Hook设置">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
启用hook事件
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.hook.enable" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item" v-if="serverConfig.hook.enable=='1'">
<div class="conf_item_label">
alive心跳间隔(s)
</div>
<div class="conf_item_value">
<el-input type="number"
v-model="serverConfig.hook.alive_interval"></el-input>
</div>
</div>
<div class="conf_item" v-if="serverConfig.hook.enable=='1'">
<div class="conf_item_label">
Hook请求超时(s)
</div>
<div class="conf_item_value">
<el-input type="number"
v-model="serverConfig.hook.timeoutSec"></el-input>
</div>
</div>
<div class="conf_item" v-if="serverConfig.hook.enable=='1'">
<div class="conf_item_label">
失败重试次数
</div>
<div class="conf_item_value">
<el-input type="number" v-model="serverConfig.hook.retry"></el-input>
</div>
</div>
<div class="conf_item" v-if="serverConfig.hook.enable=='1'">
<div class="conf_item_label">
失败重试延时(s)
</div>
<div class="conf_item_value">
<el-input type="number"
v-model="serverConfig.hook.retry_delay"></el-input>
</div>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12"
v-if="serverConfig.hook.enable=='1'">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
保活上报
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.hook.on_server_keepalive"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
流量事件
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.hook.on_flow_report"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
http文件鉴权事件
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.hook.on_http_access"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
播放鉴权事件
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.hook.on_play"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
推流鉴权事件
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.hook.on_publish"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
mp4切片录好事件
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.hook.on_record_mp4"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
ts切片录好事件
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.hook.on_record_ts"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
rtp服务超时事件
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.hook.on_rtp_server_timeout"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
rtsp播放鉴权事件
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.hook.on_rtsp_auth"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
rtsp专属鉴权事件
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.hook.on_rtsp_realm"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
发送rtp被动关闭
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.hook.on_send_rtp_stopped"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
服务启动事件
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.hook.on_server_started"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
服务关闭事件
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.hook.on_server_exited"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
telnet调试鉴权事件
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.hook.on_shell_login"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
流注册或注销事件
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.hook.on_stream_changed"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
无人观看流事件
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.hook.on_stream_none_reader"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
播放未找到流事件
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.hook.on_stream_not_found"></el-input>
</div>
</div>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="HLS设置">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
广播hls切片完成
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.hls.broadcastRecordTs" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
删除延时(s)
</div>
<div class="conf_item_value">
<el-input type="number"
v-model="serverConfig.hls.deleteDelaySec"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
文件缓存大小
</div>
<div class="conf_item_value">
<el-input type="number"
v-model="serverConfig.hls.fileBufSize"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
切片时长(s)
</div>
<div class="conf_item_value">
<el-input type="number" v-model="serverConfig.hls.segDur"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
切片个数
</div>
<div class="conf_item_value">
<el-input type="number" v-model="serverConfig.hls.segNum"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
预留切片数
</div>
<div class="conf_item_value">
<el-input type="number" v-model="serverConfig.hls.segRetain"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
保留hls文件
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.hls.segKeep" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="cluster设置">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
源站拉流url模板
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.cluster.origin_url"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
溯源总超时
</div>
<div class="conf_item_value">
<el-input type="number"
v-model="serverConfig.cluster.timeout_sec"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
溯源失败重试次数
</div>
<div class="conf_item_value">
<el-input type="number"
v-model="serverConfig.cluster.retry_count"></el-input>
</div>
</div>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="HTTP">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
端口
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.http.port"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
SSL端口
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.http.sslport"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
编码
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.http.charSet" placeholder="请选择" style="width: 100%;">
<el-option label="UTF-8" value="utf-8"></el-option>
<el-option label="GB2312" value="gb2312"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
超时时间
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.http.keepAliveSecond"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
最大请求大小(字节)
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.http.maxReqSize"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
404网页内容
</div>
<div class="conf_item_value">
<el-input type="textarea"
v-model="serverConfig.http.notFound"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
根目录
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.http.rootPath"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
发送缓存大小
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.http.sendBufSize"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
禁止使用mmap缓存的文件后缀
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.http.forbidCacheSuffix"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
forwarded_ip_header
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.http.forwarded_ip_header"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
显示目录列表
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.record.dirMenu" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
虚拟路径
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.http.virtualPath"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
允许跨域请求
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.record.allow_cross_domains" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
Ip白名单
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.http.allow_ip_range"></el-input>
</div>
</div>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="protocol设置">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
开启音频
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.protocol.enable_audio" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
添加静音音轨
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.protocol.add_mute_audio" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
<el-tooltip effect="dark" placement="bottom-start">
<div slot="content">
推流断开后可以在超时时间内重新连接上继续推流,这样播放器会接着播放。
<br />置0关闭此特性(推流断开会导致立即断开播放器)
<br />此参数不应大于播放器超时时间;单位ms
</div>
<span>继续推流时间(ms)</span>
</el-tooltip>
</div>
<div class="conf_item_value">
<el-input type="number"
v-model="serverConfig.protocol.continue_push_ms"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
将mp4录制当做观看者
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.protocol.mp4_as_player" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
mp4切片大小(s)
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.protocol.mp4_max_second"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
mp4录像保存路径
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.protocol.mp4_save_path"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
hls录像保存路径
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.protocol.hls_save_path"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
开启帧级时间戳覆盖
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.protocol.modify_stamp"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
无人观看时,直接关闭
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.protocol.auto_close" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
启用 http-fmp4/ws-fmp4
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.protocol.enable_fmp4" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
启用 hls
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.protocol.enable_hls" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
启用MP4录制
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.protocol.enable_mp4" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
启用 rtmp/flv
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.protocol.enable_rtmp" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
启用 rtsp
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.protocol.enable_rtsp" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
启用 http-ts/ws-ts
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.protocol.enable_ts" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
按需生成 http[s]-fmp4、ws[s]-fmp4
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.protocol.fmp4_demand" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
按需生成 hls
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.protocol.hls_demand" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
按需生成 rtmp[s]、http[s]-flv、ws[s]-flv
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.protocol.rtmp_demand" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
按需生成 rtsp[s]
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.protocol.rtsp_demand" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
按需生成 http[s]-ts
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.protocol.ts_demand" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="组播设置">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
组播udp ttl
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.multicast.udpTTL"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
rtp组播起始地址
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.multicast.addrMin"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
rtp组播截止地址
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.multicast.addrMax"></el-input>
</div>
</div>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="RECORD">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
应用名
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.record.appName"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
fastStart
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.record.fastStart" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
文件缓存大小
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.record.fileBufSize"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
保存路径
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.record.filePath"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
循环播放
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.record.fileRepeat" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
fileSecond
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.record.fileSecond"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
MP4点播每次流化数据量
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.record.sampleMS"></el-input>
</div>
</div>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="RTC">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
公网ip
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.rtc.externIP"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
UDP端口
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.rtc.port"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
TCP端口
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.rtc.tcpPort"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
rtc超时
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.rtc.timeoutSec"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
remb比特率
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.rtc.rembBitRate"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
音频候选编码
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.rtc.preferredCodecA"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
视频候选编码
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.rtc.preferredCodecV"></el-input>
</div>
</div>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="RTMP">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
端口
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.rtmp.port"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
SSL端口
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.rtmp.sslport"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
握手超时
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.rtmp.handshakeSecond"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
心跳超时(s)
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.rtmp.keepAliveSecond"></el-input>
</div>
</div>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="RTSP">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
端口
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.rtsp.port"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
SSL端口
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.rtsp.sslport"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
低延迟模式
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.rtsp.lowLatency" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
心跳超时(s)
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.rtsp.keepAliveSecond"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
握手超时(s)
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.rtsp.handshakeSecond"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
直接代理模式
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.rtsp.directProxy" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
rtsp专有鉴权
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.rtsp.authBasic"></el-input>
</div>
</div>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="RTP">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
音频mtu大小
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.rtp.audioMtuSize"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
视频mtu大小
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.rtp.videoMtuSize"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
低延迟开关
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.rtp.lowLatency" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
rtp包长度限制
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.rtp.rtpMaxSize"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
采用stap-a模式
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.rtp.h264_stap_a" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
</div>
</el-col>
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
调试输出目录(包括rtp/ps/h264)
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.rtp_proxy.dumpDir"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
udp和tcp代理服务端口
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.rtp_proxy.port"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
端口范围
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.rtp_proxy.port_range"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
超时时间
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.rtp_proxy.timeoutSec"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
开启gop缓存优化
</div>
<div class="conf_item_value">
<el-select v-model="serverConfig.rtp_proxy.gop_cache" placeholder="请选择" style="width: 100%;">
<el-option v-for="(val,key) in yesNo" :label="val" :value="key"></el-option>
</el-select>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
g711a载荷类型
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.rtp_proxy.g711a_pt"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
g711u载荷类型
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.rtp_proxy.g711u_pt"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
h264载荷类型
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.rtp_proxy.h264_pt"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
h265载荷类型
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.rtp_proxy.h265_pt"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
opus载荷类型
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.rtp_proxy.opus_pt"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
ps载荷类型
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.rtp_proxy.ps_pt"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
ts载荷类型
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.rtp_proxy.ts_pt"></el-input>
</div>
</div>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="SHELL">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
端口
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.shell.port"></el-input>
</div>
</div>
<div class="conf_item" v-if="serverConfig.shell.port!='0'">
<div class="conf_item_label">
最大请求大小
</div>
<div class="conf_item_value">
<el-input type="text"
v-model="serverConfig.shell.maxReqSize"></el-input>
</div>
</div>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="SRT">
<el-row :gutter="20">
<el-col :xs="24" :sm="24" :md="12" :lg="12" :xl="12">
<div class="confs">
<div class="conf_item">
<div class="conf_item_label">
延迟估算参数
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.srt.latencyMul"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
包缓存大小
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.srt.pktBufSize"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
端口
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.srt.port"></el-input>
</div>
</div>
<div class="conf_item">
<div class="conf_item_label">
会话超时(s)
</div>
<div class="conf_item_value">
<el-input type="text" v-model="serverConfig.srt.timeoutSec"></el-input>
</div>
</div>
</div>
</el-col>
</el-row>
</el-tab-pane>
</el-tabs>
<el-form ref="form" :model="serverConfig" label-width="180px">
<el-form-item>
<el-button type="primary" @click="onSubmit">保存更新</el-button>
<el-button>取消</el-button>
</el-form-item>
</el-form>
</div>
</el-main>
<el-footer>
<span>分支:{{ versioninfo.branchName }}</span>
<span>构建时间: {{ versioninfo.buildTime }}</span>
<span>版本: {{ versioninfo.commitHash }}</span>
</el-footer>
</el-container>
</div>
</body>
<script src="./js/vue.min.js"></script>
<script src="./assets/elementui/index.js"></script>
<script src="./js/axios.min.js"></script>
<script src="./js/qs.js"></script>
<script src="./js/echarts.min.js"></script>
<script src="./js/ZLMRTCClient.js"></script>
<script>
new Vue({
el: '#app',
beforeMount() {
var urlParams = new URLSearchParams(window.location.search);
var secret = urlParams.get('secret');
console.log('secret值为:', secret);
if (secret) this.secret = secret;
else this.secret = "";
var host = urlParams.get('host');
console.log('host值为:', host);
if (host) this.host = host;
else this.host = "";
if (this.secret) {
//获取版本和配置信息
this.version();
this.getServerConfig();
this.getApiList();
}
//获取支持的分辨率
this.resolution_opt = []
ZLMRTCClient.GetAllScanResolution().forEach((r, i) => {
this.resolution_opt.push({
text: r.label + "(" + r.width + "x" + r.height + ")",
value: r
})
})
this.webrtc.zlmsdpUrl = document.location.protocol + "//" + window.location.host + "/index/api/webrtc?app=live&stream=test&type=play"
},
mounted() {
if (this.secret == "") {
this.$message({
message: '在url路径中添加正确的secret参数',
type: 'error'
});
return
}
else {
this.updatetimer = setInterval(this.update_status, 1000)
}
this.chart1 = echarts.init(this.$refs.chart1);
this.chart2 = echarts.init(this.$refs.chart2);
this.chart3 = echarts.init(this.$refs.chart3);
this.getMediaList();
this.getAllSession();
},
data: function () {
return {
host: "",
secret: "",
selectIndex: '1',
yesNo:{
0:'否',
1:'是',
},
typeTcpMode:{
0:"无",
1:"被动",
2:"主动"
},
typeOnlyTrack:{
0:"全部",
1:"只音频",
2:"只视频",
},
getMediaList_queryparams: {
schema: null,
vhost: "__defaultVhost__",
app: null,
stream: null
},
addStreamProxy_params: {
vhost: "__defaultVhost__",
app: "live",
stream: "test",
url: "",
retry_count: -1,
rtp_type: "0",
timeout_sec: 5,
enable_hls: false,
enable_hls_fmp4: false,
enable_mp4: false,
enable_rtsp: true,
enable_rtmp: true,
enable_ts: true,
enable_fmp4: true,
hls_demand: false,
rtsp_demand: false,
rtmp_demand: false,
ts_demand: false,
fmp4_demand: false,
enable_audio: true,
add_mute_audio: true,
mp4_save_path: "",
mp4_max_second: 10,
mp4_as_player: false,
hls_save_path: "",
modify_stamp: "",
auto_close: false
},
addStreamPusherProxy_params: {
vhost: "__defaultVhost__",
app: "live",
stream: "test",
schema: "rtsp",
dst_url: "",
retry_count: -1,
rtp_type: "0",
timeout_sec: 5,
},
versioninfo: {
},
updatetimer: null,
autorefresh_medialist: false,
autorefresh_session: false,
serverConfig: null,
serverConfig_bak: null,
chart1: null,
chart2: null,
chart3: null,
threadsLoad: [],
workThreadsLoad: [],
statistic: null,
medialist: [],
ffmpeglist: [],
addFFmpeg_param: {
src_url: "",
dst_url: "",
ffmpeg_cmd_key: "ffmpeg.cmd",
timeout_ms: 10000,
enable_hls: false,
enable_mp4: false,
},
rtpserverlist: [],
addRtpServer_param:{
vhost: "__defaultVhost__",
app: "rtp",
stream_id: "test",
tcp_mode: "1",
only_track: "0",
local_ip: '',
re_use_port: true,
port: 0,
ssrc: 0,
},
pusherProxyList: [],
streamProxyList: [],
playerlist: [],
playerTitle: "PlayerList",
showPlayerDialog: false,
sessionlist: [],
schemaFilters:[
{text:'rtsp', value: 'rtsp'},
{text:'rtmp', value: 'rtmp'},
{text:'ts', value: 'ts'},
{text:'hls', value: 'hls'},
{text:'fmp4', value: 'fmp4'},
],
webrtc: {
debug: true,
zlmsdpUrl: "",
type: "play",
recvOnly: true,
resolution: "",
usedatachannel: false,
simulcast: false,
useCamera: true,
audioEnable: true,
videoEnable: true,
msgsend: "",
msgrecv: "",
},
resolution_opt: [],
webrtc_player: null,
getAllSession_queryparams: {
local_port: null,
peer_ip: null
}
}
},
filters: {
bitsSpeed: function (val) {
return byteString(val * 8);
},
msFormat: function(val) {
var time = new Date(val);
return time.toLocaleString();
},
secFormat: function(val) {
var time = new Date(val * 1000);
return time.toLocaleString();
}
},
computed: {
apiurl: function () {
const qs_str = Qs.stringify(this.addStreamProxy_params, {
filter: (key, value) => {
if (value === "" || value === null || value === undefined) {
return undefined; // 返回 undefined 将排除该属性
}
return value;
}
})
console.log(qs_str)
return `/index/api/addStreamProxy?secret=${this.secret}&${qs_str}`
},
addStreamPusherProxy_apiurl: function () {
const qs_str = Qs.stringify(this.addStreamPusherProxy_params, {
filter: (key, value) => {
if (value === "" || value === null || value === undefined) {
return undefined; // 返回 undefined 将排除该属性
}
return value;
}
})
console.log(qs_str)
return `/index/api/addStreamPusherProxy?secret=${this.secret}&${qs_str}`
},
addFFmpegSource_apiurl: function () {
const qs_str = Qs.stringify(this.addFFmpeg_param, {
filter: (key, value) => {
if (value === "" || value === null || value === undefined) {
return undefined; // 返回 undefined 将排除该属性
}
return value;
}
})
console.log(qs_str)
return `/index/api/addFFmpegSource?secret=${this.secret}&${qs_str}`
},
},
watch: {
'webrtc.type'(newv, oldv) {
var url = new URL(this.webrtc.zlmsdpUrl)
url.searchParams.set("type", newv)
this.webrtc.zlmsdpUrl = url.toString()
if (newv == "play") {
this.webrtc.recvOnly = true;
} else if (newv == "echo") {
this.webrtc.recvOnly = false;
} else {
this.webrtc.recvOnly = false;
}
}
},
methods: {
filterSchema(value, row) {
return row.schema === value;
},
startwebrtc() {
if (this.webrtc.resolution == "") {
this.$message({
message: '请选择分辨率(please set resolution)',
type: 'warning'
});
return
}
this.webrtc_player = new ZLMRTCClient.Endpoint(
{
element: this.$refs.video,// video 标签
debug: this.webrtc.debug,// 是否打印日志
zlmsdpUrl: this.webrtc.zlmsdpUrl,//流地址
simulcast: this.webrtc.simulcast,
useCamera: this.webrtc.useCamera,
audioEnable: this.webrtc.audioEnable,
videoEnable: this.webrtc.videoEnable,
recvOnly: this.webrtc.recvOnly,
resolution: {
w: this.resolution_opt[this.webrtc.resolution].value.width,
h: this.resolution_opt[this.webrtc.resolution].value.height
},
usedatachannel: this.webrtc.usedatachannel,
});
this.webrtc_player.on(ZLMRTCClient.Events.WEBRTC_ICE_CANDIDATE_ERROR, (e) => {// ICE 协商出错
console.log('ICE 协商出错')
this.$message({
message: 'ICE 协商出错',
type: 'warning'
});
});
this.webrtc_player.on(ZLMRTCClient.Events.WEBRTC_ON_REMOTE_STREAMS, (e) => {//获取到了远端流,可以播放
this.$message({
message: '播放成功',
type: 'success'
});
console.log('播放成功', e.streams)
});
this.webrtc_player.on(ZLMRTCClient.Events.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, (e) => {// offer anwser 交换失败
console.log('offer anwser 交换失败', e)
this.$message({
message: `offer anwser 交换失败:${e.msg}`,
type: 'error'
});
this.stopwebrtc();
});
this.webrtc_player.on(ZLMRTCClient.Events.WEBRTC_ON_LOCAL_STREAM, (s) => {// 获取到了本地流
this.$refs.selfVideo.srcObject = s;
this.$refs.selfVideo.muted = true;
});
this.webrtc_player.on(ZLMRTCClient.Events.CAPTURE_STREAM_FAILED, (s) => {// 获取本地流失败
this.$message({
message: '获取本地流失败',
type: 'error'
});
console.log('获取本地流失败')
});
this.webrtc_player.on(ZLMRTCClient.Events.WEBRTC_ON_CONNECTION_STATE_CHANGE, (state) => {// RTC 状态变化 ,详情参考 https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionState
console.log('当前状态==>', state)
});
this.webrtc_player.on(ZLMRTCClient.Events.WEBRTC_ON_DATA_CHANNEL_OPEN, (event) => {
console.log('rtc datachannel 打开 :', event)
this.$message({
message: 'rtc datachannel 打开',
type: 'info'
});
});
this.webrtc_player.on(ZLMRTCClient.Events.WEBRTC_ON_DATA_CHANNEL_MSG, (event) => {
console.log('rtc datachannel 消息 :', event.data)
this.webrtc.msgrecv = event.data
});
this.webrtc_player.on(ZLMRTCClient.Events.WEBRTC_ON_DATA_CHANNEL_ERR, (event) => {
console.log('rtc datachannel 错误 :', event)
this.$message({
message: 'rtc datachannel 错误 ',
type: 'error'
});
});
this.webrtc_player.on(ZLMRTCClient.Events.WEBRTC_ON_DATA_CHANNEL_CLOSE, (event) => {
console.log('rtc datachannel 关闭 :', event)
this.$message({
message: 'rtc datachannel 关闭 ',
type: 'warning'
});
});
},
stopwebrtc() {
if (this.webrtc_player) {
this.webrtc_player.close();
this.webrtc_player = null;
if (this.$refs.video) {
this.$refs.video.srcObject = null;
this.$refs.video.load();
}
this.$refs.selfVideo.srcObject = null;
this.$refs.selfVideo.load();
}
},
sendata() {
console.log(`发送数据:${this.webrtc.msgsend}`)
if (this.webrtc_player) {
this.webrtc_player.sendMsg(this.webrtc.msgsend)
}
},
closedata() {
console.log(`关闭数据通道`)
if (this.webrtc_player) {
this.webrtc_player.closeDataChannel()
this.$message({
message: '关闭数据通道',
type: 'success'
});
}
},
onSubmit() {
this.setServerConfig()
},
handleSelect(key, keyPath) {
if (this.updatetimer) {
clearInterval(this.updatetimer);
this.updatetimer = null;
}
if (key) this.selectIndex = key;
if (this.selectIndex == '7') {
this.getServerConfig();
}
else if (this.selectIndex == '3') {
this.listStreamProxy();
}
else if (this.selectIndex == '4') {
this.listStreamPusherProxy();
}
else if (this.selectIndex == '5') {
this.listFFmpegSource();
}
else if (this.selectIndex == '6') {
this.listRtpServer();
}
else if (this.selectIndex == '2') {
var ishttps = 'https:' == document.location.protocol ? true : false
var isLocal = ("file:" == document.location.protocol) || (document.location.host == "127.0.0.1") || (document.location.host == "localhost");
if (!ishttps && !isLocal) {
this.$message({
message: '本demo需要在https的网站访问 ,如果你要推流的话(this demo must access in site of https if you want push stream)',
type: 'warning'
});
}
}
else if (this.selectIndex == '1') {
this.chart1 = echarts.init(this.$refs.chart1);
this.chart2 = echarts.init(this.$refs.chart2);
this.chart3 = echarts.init(this.$refs.chart3);
this.updatetimer = setInterval(this.update_status, 1000)
}
console.log(key, keyPath);
},
async performRequest(url, successMessage, errorMessage, callback) {
try {
const response = await axios.get(url);
if (response.status === 200) {
if (response.data.code === 0) {
if (successMessage) {
this.$message({
message: successMessage,
type: 'success',
});
}
if (callback) {
callback(response);
}
} else {
this.$message({
message: errorMessage || response.data.msg,
type: 'error',
});
}
}
else {
this.$message({
message: `response.status:${response.status}`,
type: 'error',
});
}
} catch (err) {
// 错误处理代码
}
},
addStreamProxy() {
const url = `${this.host}${this.apiurl}`;
this.performRequest(url, '添加成功!', '', () => this.listStreamProxy());
},
addStreamPusherProxy() {
const url = `${this.host}${this.addStreamPusherProxy_apiurl}`;
this.performRequest(url, '添加成功!', '', () => this.listStreamPusherProxy());
},
delStreamProxy(index, row) {
console.log(index, row);
const url = `${this.host}/index/api/delStreamProxy?secret=${this.secret}&key=${row.key}`;
this.performRequest(url, '删除成功!', '', () => this.listStreamProxy());
},
delStreamPusherProxy(index, row) {
console.log(index, row);
const url = `${this.host}/index/api/delStreamPusherProxy?secret=${this.secret}&key=${row.key}`;
this.performRequest(url, '删除成功!', '', () => this.listStreamPusherProxy());
},
listStreamProxy() {
const url = `${this.host}/index/api/listStreamProxy?secret=${this.secret}`;
this.performRequest(url, "", "", resp => {
if (resp.data.data) this.streamProxyList = resp.data.data;
else this.streamProxyList = [];
})
},
listStreamPusherProxy() {
const url = `${this.host}/index/api/listStreamPusherProxy?secret=${this.secret}`;
this.performRequest(url, "", "", resp => {
if (resp.data.data) this.pusherProxyList = resp.data.data;
else this.pusherProxyList = [];
})
},
close_stream(index, row) {
console.log(index, row);
const data = {
secret: this.secret,
schema: row.schema,
vhost: row.vhost,
app: row.app,
stream: row.stream,
force: true
}
const url = `${this.host}/index/api/close_streams?${Qs.stringify(data)}`;
this.performRequest(url, '关闭成功!', '', () => this.getMediaList());
},
kick_session(id) {
const url = `${this.host}/index/api/kick_session?secret=${this.secret}&id=${id}`;
this.performRequest(url, '关闭成功!', '', () => this.getAllSession());
},
getApiList() {
const url = `${this.host}/index/api/getApiList?secret=${this.secret}`;
this.performRequest(url, '', '', (response) => {
console.log(response.data.data);
});
},
getPlayerList(row) {
const url = `${this.host}/index/api/getMediaPlayerList?secret=${this.secret}&schema=${row.schema}&vhost=${row.vhost}&app=${row.app}&stream=${row.stream}`;
console.log(url);
this.performRequest(url, '', '', (resp) => {
console.log(resp.data.data);
if (resp.data.data) this.playerlist = resp.data.data;
else this.playerlist = [];
if (this.playerlist.length) {
this.playerTitle = `${row.app}\\${row.stream}s PlayerList`;
this.showPlayerDialog = true;
}
});
},
getServerConfig() {
const url = `${this.host}/index/api/getServerConfig?secret=${this.secret}`;
this.performRequest(url, '', '', (response) => {
var data = {}
var serverConfig = response.data.data[0]
console.log(serverConfig);
const serverConfigKeys = Object.keys(serverConfig);
for (const key of serverConfigKeys) {
const [category, subKey] = key.split('.');
if (!data[category]) {
data[category] = {};
}
data[category][subKey] = serverConfig[key];
}
this.serverConfig = data;
this.serverConfig_bak = JSON.parse(JSON.stringify(data));
});
},
setServerConfig() {
var confs = findDifferentProperties(this.serverConfig_bak, this.serverConfig)
console.log(confs)
const flattened = flattenObject(confs);
const queryString = Object.keys(flattened).map(key => `${key}=${flattened[key]}`).join('&');
if (queryString == "") {
this.$message({
message: '没有修改任何参数',
type: 'info'
});
return
}
const update_counts = ((queryString.match(/&/g) || []).length + 1)
const postdata = { ...{ secret: this.secret }, ...flattened }
axios.post(`${this.host}/index/api/setServerConfig`, postdata).then(resp => {
if (resp.status == 200) {
if (resp.data.code === 0) {
if (update_counts == resp.data.changed) {
this.$message({
message: `保存成功!共修改${resp.data.changed}个参数`,
type: 'success'
});
}
else {
this.$message({
message: `存在参数修改失败,共修改${resp.data.changed}个参数,请检查后重试!`,
type: 'error'
});
}
this.getServerConfig()
}
}
}).catch(err => {
console.log(err)
})
},
getThreadsLoad() {
const url = `${this.host}/index/api/getThreadsLoad?secret=${this.secret}`;
this.performRequest(url, "", "", resp => {
this.threadsLoad = resp.data.data;
var delays = this.threadsLoad.map(item => item.delay);
var loads = this.threadsLoad.map(item => item.load);
var max_delay = Math.max(...delays);
var y_max = max_delay>100?max_delay*1.2:100;
this.chart3.setOption({
title: {
text: 'ThreadsLoad'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
}
},
legend: {
data: ['延迟', '负载']
},
xAxis: [
{
type: 'category',
data: Array.from({ length: this.threadsLoad.length }, (_, i) => i + 1),
axisPointer: {
type: 'shadow'
}
}
],
yAxis: [
{
type: 'value',
name: '延迟',
min: 0,
max: y_max,
axisLabel: {
formatter: '{value} ms'
}
},
{
type: 'value',
name: '负载',
min: 0,
max: 100,
axisLabel: {
formatter: '{value} %'
}
}
],
series: [
{
name: '延迟',
type: 'line',
tooltip: {
valueFormatter: function (value) {
return value + ' ms';
}
},
data: delays
},
{
name: '负载',
type: 'bar',
yAxisIndex: 1,
tooltip: {
valueFormatter: function (value) {
return value + ' %';
}
},
data: loads
}
]
})
})
},
getWorkThreadsLoad() {
const url = `${this.host}/index/api/getWorkThreadsLoad?secret=${this.secret}`;
this.performRequest(url, "", "", resp => {
this.workThreadsLoad = resp.data.data;
var delays = this.workThreadsLoad.map(item => item.delay);
var loads = this.workThreadsLoad.map(item => item.load);
var max_delay = Math.max(delays);
var y_max = max_delay>100?max_delay:100;
this.chart2.setOption({
title: {
text: 'WorkThreadsLoad'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
}
},
legend: {
data: ['延迟', '负载']
},
xAxis: [
{
type: 'category',
data: Array.from({ length: this.workThreadsLoad.length }, (_, i) => i + 1),
axisPointer: {
type: 'shadow'
}
}
],
yAxis: [
{
type: 'value',
name: '延迟',
min: 0,
max: y_max,
axisLabel: {
formatter: '{value} ms'
}
},
{
type: 'value',
name: '负载',
min: 0,
max: 100,
axisLabel: {
formatter: '{value} %'
}
}
],
series: [
{
name: '延迟',
type: 'line',
tooltip: {
valueFormatter: function (value) {
return value + ' ms';
}
},
data: delays
},
{
name: '负载',
type: 'bar',
yAxisIndex: 1,
tooltip: {
valueFormatter: function (value) {
return value + ' %';
}
},
data: loads
}
]
})
})
},
getStatistic() {
const url = `${this.host}/index/api/getStatistic?secret=${this.secret}`;
this.performRequest(url, "", "", resp => {
this.statistic = resp.data.data;
this.chart1.setOption({
title: {
text: 'Statistic'
},
tooltip: {},
legend: {},
xAxis: {
data: Object.keys(this.statistic),
axisLabel: {
rotate: 45 // 设置X轴刻度标签旋转角度为45度
}
},
yAxis: {},
series: [{
type: 'bar',
data: Object.values(this.statistic)
}]
})
})
},
restartServer() {
const url = `${this.host}/index/api/restartServer?secret=${this.secret}`;
this.performRequest(url, "", "", resp => {
this.$message({
message: "服务器将在一秒后重启",
type: 'success'
});
})
},
version() {
const url = `${this.host}/index/api/version?secret=${this.secret}`;
this.performRequest(url, "", "", resp => {
this.versioninfo = resp.data.data;
console.log(this.versioninfo)
})
},
getMediaList() {
const url = `${this.host}/index/api/getMediaList?secret=${this.secret}&` + Qs.stringify(this.getMediaList_queryparams);
this.performRequest(url, "", "", resp => {
if (resp.data.data) this.medialist = resp.data.data;
else this.medialist = [];
})
},
listRtpServer() {
const url = `${this.host}/index/api/listRtpServer?secret=${this.secret}`;
this.performRequest(url, "", "", resp => {
if (resp.data.data) this.rtpserverlist = resp.data.data;
else this.rtpserverlist = [];
})
},
openRtpServer() {
const url = `${this.host}/index/api/openRtpServer?secret=${this.secret}&` + Qs.stringify(this.addRtpServer_param);
this.performRequest(url, '添加成功!', '', () => this.listRtpServer());
},
closeRtpServer(index, row) {
console.log(index, row);
const url = `${this.host}/index/api/closeRtpServer?secret=${this.secret}&stream_id=${row.stream_id}&vhost=${row.vhost}&app=${row.app}`;
this.performRequest(url, '删除成功!', '', () => this.listRtpServer());
},
connectRtpServer(index, row) {
console.log(index, row);
const url = `${this.host}/index/api/connectRtpServer?secret=${this.secret}&stream_id=${row.stream_id}&vhost=${row.vhost}&app=${row.app}&dst_url=${row.dst_url}&dst_port=${row.dst_port}`;
console.log(url);
this.performRequest(url, '连接成功!', '', () => this.listRtpServer());
},
updateRtpServerSSRC(row) {
const url = `${this.host}/index/api/updateRtpServerSSRC?secret=${this.secret}&stream_id=${row.stream_id}&vhost=${row.vhost}&app=${row.app}&ssrc=${row.ssrc}`;
console.log(url);
this.performRequest(url, '更新成功!', '', () => this.listRtpServer());
},
listFFmpegSource() {
const url = `${this.host}/index/api/listFFmpegSource?secret=${this.secret}`;
this.performRequest(url, "", "", resp => {
if (resp.data.data) this.ffmpeglist = resp.data.data;
else this.ffmpeglist = [];
})
},
delFFmpegSource(index, row) {
console.log(index, row);
const url = `${this.host}/index/api/delFFmpegSource?secret=${this.secret}&key=${row.key}`;
this.performRequest(url, '删除成功!', '', () => this.listFFmpegSource());
},
addFFmpegSource() {
const url = `${this.host}${this.addFFmpegSource_apiurl}`;
this.performRequest(url, '添加成功!', '', () => this.listFFmpegSource());
},
getAllSession() {
const url = `${this.host}/index/api/getAllSession?secret=${this.secret}&` + Qs.stringify(this.getAllSession_queryparams);
this.performRequest(url, "", "", resp => {
this.sessionlist = resp.data.data;
})
},
update_status() {
this.getThreadsLoad();
this.getWorkThreadsLoad();
this.getStatistic();
if (this.autorefresh_medialist)
this.getMediaList();
if (this.autorefresh_session)
this.getAllSession();
},
},
})
</script>
<style>
.el-header,
.el-footer {
background-color: #545c64;
color: #333;
text-align: center;
line-height: 60px;
}
.el-main {
background-color: #E9EEF3;
color: #333;
text-align: center;
/* line-height: 260px; */
}
body>.el-container {
margin-bottom: 40px;
}
.videobox {
width: 100%;
height: 100%;
}
.confs {
min-height: 50px;
border-radius: 4px;
background-color: #545c6421;
margin-bottom: 20px;
display: flex;
flex-direction: column;
}
.confs>.conf_item {
margin: 10px;
display: flex;
flex-direction: row;
justify-content: space-between;
line-height: 2em;
}
.confs>.conf_item>.conf_item_label {}
.confs>.conf_item>.conf_item_value {
width: 70%;
}
.input-with-select .el-input-group__prepend {
background-color: var(--el-fill-color-blank);
}
</style>
</html>