464 lines
26 KiB
PHP
464 lines
26 KiB
PHP
|
<?php include ("./link/session.php") ?>
|
||
|
<!doctype html>
|
||
|
<html lang="uft-8">
|
||
|
<head>
|
||
|
<?php include ("./public/head.inc") ?>
|
||
|
<link href="assets/plugins/timepicker/css/bootstrap-timepicker.min.css" rel="stylesheet">
|
||
|
</head>
|
||
|
<body>
|
||
|
<?php include ("./public/menu.inc") ?>
|
||
|
<div data-simplebar>
|
||
|
<main class="page-content push" id="app" v-cloak>
|
||
|
<div class="row">
|
||
|
<div class="col-lg-7 lp-equal-height-container">
|
||
|
<div class="card lp-equal-height-item">
|
||
|
<div class="card-header bg-transparent">
|
||
|
<div class="p-2 mb-0 d-flex align-items-end">
|
||
|
<cn>视频预览</cn>
|
||
|
<en>Preview</en>
|
||
|
<small>
|
||
|
<cn>推流后可见</cn>
|
||
|
<en>visible when pushing</en>
|
||
|
</small>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="card-body d-flex">
|
||
|
<div class="row flex-grow-1 lp-align-center">
|
||
|
<div class="col-lg-12">
|
||
|
<h5-player :url="playUrl" :codec="playerCodec" :audio="true" :canplay="hadPlayed"></h5-player>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="col-lg-5 lp-equal-height-container">
|
||
|
<div class="lp-equal-height-item d-flex flex-column">
|
||
|
<div class="row flex-grow-1 pb-2">
|
||
|
<div class="col-lg-12">
|
||
|
<div class="card h-100 d-flex flex-column">
|
||
|
<div class="card-header bg-transparent">
|
||
|
<div class="p-2 mb-0 d-flex align-items-end">
|
||
|
<cn>基本设置</cn>
|
||
|
<en>Basic config</en>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="card-body">
|
||
|
<div class="d-flex flex-column justify-content-between h-100" v-if="Object.keys(pushConf).length > 0">
|
||
|
<div class="row">
|
||
|
<div class="col-lg-4 lp-align-right pe-4">
|
||
|
<label>
|
||
|
<cn>视频源</cn>
|
||
|
<en>Video source</en>
|
||
|
</label>
|
||
|
</div>
|
||
|
<div class="col-lg-6">
|
||
|
<select class="form-select" v-model="pushConf.srcV" @change="onChangeSrcV">
|
||
|
<option v-for="(item,index) in handleEnableConf" :value="item.id">{{item.name}}</option>
|
||
|
</select>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="row">
|
||
|
<div class="col-lg-4 lp-align-right pe-4">
|
||
|
<label>
|
||
|
<cn>音频源</cn>
|
||
|
<en>Audio source</en>
|
||
|
</label>
|
||
|
</div>
|
||
|
<div class="col-lg-6">
|
||
|
<select class="form-select" v-model="pushConf.srcA">
|
||
|
<option value="-1" cn="无" en="None" v-language-option></option>
|
||
|
<option v-for="(item,index) in handleEnableConf" :value="item.id">{{item.name}}</option>
|
||
|
</select>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="row">
|
||
|
<div class="col-lg-4 lp-align-right pe-4">
|
||
|
<label>
|
||
|
<cn>码流</cn>
|
||
|
<en>Stream</en>
|
||
|
</label>
|
||
|
</div>
|
||
|
<div class="col-lg-6">
|
||
|
<select class="form-select" v-model="pushConf.srcV_chn">
|
||
|
<option value="main" cn="主码流" en="Main Stream" v-language-option></option>
|
||
|
<option v-if="defaultSubEnable" value="sub" cn="辅码流" en="Sub Stream" v-language-option></option>
|
||
|
<option v-else value="sub" cn="辅码流(未启用)" en="Sub Stream(not enable)" v-language-option></option>
|
||
|
</select>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="row">
|
||
|
<div class="col-lg-4 lp-align-right pe-4">
|
||
|
<label>
|
||
|
<cn>定时开启</cn>
|
||
|
<en>start time</en>
|
||
|
</label>
|
||
|
</div>
|
||
|
<div class="col-lg-3">
|
||
|
<select class="form-select" v-model="pushCron.start.day">
|
||
|
<option cn="从不" en="never" value="x" v-language-option></option>
|
||
|
<option cn="每天" en="everyday" value="*" v-language-option></option>
|
||
|
<option cn="每周一" en="monday" value="1" v-language-option></option>
|
||
|
<option cn="每周二" en="tuesday" value="2" v-language-option></option>
|
||
|
<option cn="每周三" en="wednesday" value="3" v-language-option></option>
|
||
|
<option cn="每周四" en="thursday" value="4" v-language-option></option>
|
||
|
<option cn="每周五" en="friday" value="5" v-language-option></option>
|
||
|
<option cn="每周六" en="saturday" value="6" v-language-option></option>
|
||
|
<option cn="每周日" en="sunday" value="0" v-language-option></option>
|
||
|
</select>
|
||
|
</div>
|
||
|
<div class="col-lg-3">
|
||
|
<time-picker v-model="pushCron.start.time"></time-picker>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="row">
|
||
|
<div class="col-lg-4 lp-align-right pe-4">
|
||
|
<label>
|
||
|
<cn>定时结束</cn>
|
||
|
<en>stop time</en>
|
||
|
</label>
|
||
|
</div>
|
||
|
<div class="col-lg-3">
|
||
|
<select class="form-select" v-model="pushCron.stop.day">
|
||
|
<option cn="从不" en="never" value="x" v-language-option></option>
|
||
|
<option cn="每天" en="everyday" value="*" v-language-option></option>
|
||
|
<option cn="每周一" en="monday" value="1" v-language-option></option>
|
||
|
<option cn="每周二" en="tuesday" value="2" v-language-option></option>
|
||
|
<option cn="每周三" en="wednesday" value="3" v-language-option></option>
|
||
|
<option cn="每周四" en="thursday" value="4" v-language-option></option>
|
||
|
<option cn="每周五" en="friday" value="5" v-language-option></option>
|
||
|
<option cn="每周六" en="saturday" value="6" v-language-option></option>
|
||
|
<option cn="每周日" en="sunday" value="0" v-language-option></option>
|
||
|
</select>
|
||
|
</div>
|
||
|
<div class="col-lg-3">
|
||
|
<time-picker v-model="pushCron.stop.time"></time-picker>
|
||
|
</div>
|
||
|
</div>
|
||
|
<!-- <div class="hr-container">-->
|
||
|
<!-- <hr>-->
|
||
|
<!-- <span class="hr-text">OR</span>-->
|
||
|
<!-- </div>-->
|
||
|
<div class="row">
|
||
|
<div class="col-lg-4 lp-align-right pe-4">
|
||
|
<label>
|
||
|
<cn>开机启动</cn>
|
||
|
<en>auto push</en>
|
||
|
</label>
|
||
|
</div>
|
||
|
<div class="col-lg-6">
|
||
|
<select class="form-select" v-model="pushConf.autorun">
|
||
|
<option cn="关闭" en="OFF" value="false" v-language-option></option>
|
||
|
<option cn="开启" en="ON" value="true" v-language-option></option>
|
||
|
</select>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="row mt-2">
|
||
|
<div class="col-lg-12 text-center">
|
||
|
<button type="button" class="btn border-3 btn-primary px-5" @click="savePushConf">
|
||
|
<cn>保存</cn>
|
||
|
<en>Save</en>
|
||
|
</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="row flex-grow-0">
|
||
|
<div class="col-lg-12">
|
||
|
<div class="card">
|
||
|
<div class="card-body">
|
||
|
<div class="push-bar">
|
||
|
<div class="row">
|
||
|
<div class="col-4 text-center" style="line-height: 34px;">
|
||
|
<strong>{{pushTimeCount}}</strong>
|
||
|
</div>
|
||
|
<div class="col-7 d-flex align-items-start">
|
||
|
<button type="button" :class="['btn border-3',{'btn-primary':!pushState.pushing},{'btn-default disabled':pushState.pushing}]" @click="onPushStart">
|
||
|
<i class="fa-solid fa-video me-1"></i>
|
||
|
<cn>推流</cn>
|
||
|
<en>Push</en>
|
||
|
</button>
|
||
|
<button type="button" :class="['btn border-3 ms-1',{'btn-primary':pushState.pushing},{'btn-default disabled':!pushState.pushing}]" @click="onPushStop">
|
||
|
<i class="fa-solid fa-stop me-1"></i>
|
||
|
<cn>停止</cn>
|
||
|
<en>Stop</en>
|
||
|
</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="row">
|
||
|
<div class="col-lg-12">
|
||
|
<div class="card">
|
||
|
<div class="card-header bg-transparent">
|
||
|
<div class="p-2 mb-0 d-flex align-items-end">
|
||
|
<cn>推流设置</cn>
|
||
|
<en>Push config</en>
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="card-body">
|
||
|
<div class="row">
|
||
|
<div class="col-lg-2 text-center">
|
||
|
<cn>描述</cn>
|
||
|
<en>Description</en>
|
||
|
</div>
|
||
|
<div class="col-lg-5 text-center">
|
||
|
<cn>推流地址</cn>
|
||
|
<en>Push Url</en>
|
||
|
</div>
|
||
|
<div class="col-lg-2 text-center">
|
||
|
<cn>兼容性</cn>
|
||
|
<en>Compatible</en>
|
||
|
</div>
|
||
|
<div class="col-lg-1 text-center">
|
||
|
<cn>启用</cn>
|
||
|
<en>Enable</en>
|
||
|
</div>
|
||
|
<div class="col-lg-1 text-center">
|
||
|
<cn>操作</cn>
|
||
|
<en>Option</en>
|
||
|
</div>
|
||
|
<div class="col-lg-1 text-center">
|
||
|
<cn>速度</cn>
|
||
|
<en>Speed</en>
|
||
|
</div>
|
||
|
</div>
|
||
|
<hr class="my-3">
|
||
|
<div class="row" v-if="Object.keys(pushConf).length > 0" v-for="(item,index) in pushConf.url">
|
||
|
<div class="co-lg-12">
|
||
|
<div class="row">
|
||
|
<div class="col-lg-2">
|
||
|
<input type="text" class="form-control" v-model.trim.lazy="item.des">
|
||
|
</div>
|
||
|
<div class="col-lg-5">
|
||
|
<input type="text" class="form-control" v-model.trim.lazy="item.path">
|
||
|
</div>
|
||
|
<div class="col-lg-2">
|
||
|
<select class="form-select" v-model="item.flvflags">
|
||
|
<option cn="标准" en="normal" value="" v-language-option></option>
|
||
|
<option value="ext_header">enhanced-rtmp</option>
|
||
|
</select>
|
||
|
</div>
|
||
|
<div class="col-lg-1 lp-align-center">
|
||
|
<bs-switch v-model="item.enable"></bs-switch>
|
||
|
</div>
|
||
|
<div class="col-lg-1 text-center">
|
||
|
<button type="button" class="btn border-3 btn-primary" @click="delPushUrl(index)">
|
||
|
<cn>移除</cn>
|
||
|
<en>delete</en>
|
||
|
</button>
|
||
|
</div>
|
||
|
<div class="col-lg-1 text-center">
|
||
|
{{getPushSpeed(index)}} kb/s
|
||
|
</div>
|
||
|
</div>
|
||
|
<hr class="my-3">
|
||
|
</div>
|
||
|
</div>
|
||
|
<div class="row">
|
||
|
<div class="col-lg-12 text-center">
|
||
|
<button type="button" class="btn border-3 btn-primary px-5" @click="addPushUrl">
|
||
|
<cn>添加</cn>
|
||
|
<en>Add</en>
|
||
|
</button>
|
||
|
<button type="button" class="btn border-3 btn-primary px-5 ms-2" @click="savePushConf">
|
||
|
<cn>保存</cn>
|
||
|
<en>Save</en>
|
||
|
</button>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</div>
|
||
|
</main>
|
||
|
</div>
|
||
|
<?php include ("./public/foot.inc") ?>
|
||
|
<script type="module">
|
||
|
import { rpc,func,alertMsg } from "./assets/js/lp.utils.js";
|
||
|
import { useDefaultConf,usePushConf } from "./assets/js/vue.hooks.js";
|
||
|
import { ignoreCustomElementPlugin,bootstrapSwitchComponent,h5PlayerComponent,timepickerComponent,languageOptionDirective } from "./assets/js/vue.helper.js"
|
||
|
import vue from "./assets/js/vue.build.js";
|
||
|
|
||
|
const {createApp,ref,reactive,watchEffect,computed,onMounted} = vue;
|
||
|
const app = createApp({
|
||
|
directives: {
|
||
|
"language-option": languageOptionDirective
|
||
|
},
|
||
|
components:{
|
||
|
"bs-switch" : bootstrapSwitchComponent,
|
||
|
"h5-player": h5PlayerComponent,
|
||
|
"time-picker": timepickerComponent
|
||
|
},
|
||
|
setup(props,context) {
|
||
|
|
||
|
const { defaultConf } = useDefaultConf();
|
||
|
const { pushConf,updatePushConf } = usePushConf();
|
||
|
|
||
|
const state = {
|
||
|
playerCodec:ref("h264"),
|
||
|
defaultSubEnable:ref(null),
|
||
|
hadPlayed:ref(false),
|
||
|
playUrl:ref('http://'+window.location.host+'/flv?app=live&stream=preview'),
|
||
|
pushTimeCount:ref("00:00:00"),
|
||
|
updateTime:0,
|
||
|
pushCron: reactive({
|
||
|
start:{
|
||
|
day:"x",
|
||
|
time:"00:00"
|
||
|
},
|
||
|
stop: {
|
||
|
day:"x",
|
||
|
time:"00:00"
|
||
|
}
|
||
|
}),
|
||
|
pushState:reactive({
|
||
|
duration:0,
|
||
|
pushing:false,
|
||
|
speed:[]
|
||
|
}),
|
||
|
}
|
||
|
|
||
|
const unwatch = watchEffect(()=>{
|
||
|
if(Object.keys(pushConf).length > 0) {
|
||
|
defaultConf.forEach(item => {
|
||
|
if(item.id === pushConf.srcV) {
|
||
|
if(pushConf.srcV_chn === "sub" && item.enable2)
|
||
|
state.playerCodec.value = item.encv2.codec;
|
||
|
else
|
||
|
state.playerCodec.value = item.encv.codec;
|
||
|
}
|
||
|
});
|
||
|
unwatch();
|
||
|
}
|
||
|
})
|
||
|
|
||
|
const handleEnableConf = computed(()=>{
|
||
|
return defaultConf.filter((item,index) => {
|
||
|
if(item.enable && state.defaultSubEnable.value === null)
|
||
|
state.defaultSubEnable.value = item.enable2;
|
||
|
return !!item.enable;
|
||
|
})
|
||
|
});
|
||
|
|
||
|
const handlePushCrontab = () => {
|
||
|
func("/system/getPushCrontab").then(result => {
|
||
|
const keys = Object.keys(result.data);
|
||
|
keys.forEach(key => {
|
||
|
const value = result.data[key];
|
||
|
if(value === null)
|
||
|
return;
|
||
|
let list = value.split(" ");
|
||
|
if(list.length === 8) {
|
||
|
state.pushCron[key].day = list[4];
|
||
|
state.pushCron[key].time = list[1]+":"+list[0];
|
||
|
}
|
||
|
});
|
||
|
})
|
||
|
}
|
||
|
|
||
|
const onChangeSrcV = () => {
|
||
|
defaultConf.forEach(item => {
|
||
|
if(item.id === pushConf.srcV)
|
||
|
state.defaultSubEnable.value = item.enable2;
|
||
|
return true;
|
||
|
})
|
||
|
}
|
||
|
|
||
|
const handlePushState = () => {
|
||
|
rpc("push.getState").then(data => {
|
||
|
Object.assign(state.pushState, data);
|
||
|
state.updateTime = new Date().getTime() / 1000;
|
||
|
state.hadPlayed.value = state.pushState.pushing;
|
||
|
});
|
||
|
setTimeout(handlePushState,2000);
|
||
|
}
|
||
|
|
||
|
const getPushSpeed = index => {
|
||
|
return state.pushState.speed[index];
|
||
|
}
|
||
|
|
||
|
const handlePushTimeCount = () => {
|
||
|
if (state.pushState.pushing) {
|
||
|
const fix = num => {
|
||
|
if ( num < 10 )
|
||
|
return '0' + num;
|
||
|
else
|
||
|
return num;
|
||
|
}
|
||
|
let now = new Date();
|
||
|
let diff = now.getTime() / 1000 - state.updateTime + state.pushState.duration/1000;
|
||
|
let h = Math.floor(diff / 3600);
|
||
|
let m = Math.floor( diff % 3600 / 60 );
|
||
|
let s = Math.floor( diff % 60 );
|
||
|
state.pushTimeCount.value = "[" + fix(h) + ":" + fix( m ) + ":" + fix( s ) + "]"
|
||
|
} else {
|
||
|
state.pushTimeCount.value = "[--:--:--]";
|
||
|
}
|
||
|
setTimeout(handlePushTimeCount,1000);
|
||
|
}
|
||
|
|
||
|
const onPushStart = () => {
|
||
|
rpc("push.start").then(()=>{
|
||
|
handlePushState();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
const onPushStop = () => {
|
||
|
rpc("push.stop").then(()=>{
|
||
|
handlePushState();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
const addPushUrl = () => {
|
||
|
pushConf.url.push({
|
||
|
"des": "platform "+ (pushConf.url.length+1),
|
||
|
"enable": false,
|
||
|
"path": ""
|
||
|
})
|
||
|
}
|
||
|
|
||
|
const delPushUrl = (index) => {
|
||
|
pushConf.url.splice(index,1);
|
||
|
}
|
||
|
|
||
|
const savePushConf = () => {
|
||
|
defaultConf.forEach(item => {
|
||
|
if(item.id === pushConf.srcV) {
|
||
|
if(pushConf.srcV_chn === "sub" && item.enable2)
|
||
|
state.playerCodec.value = item.encv2.codec;
|
||
|
else
|
||
|
state.playerCodec.value = item.encv.codec;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
updatePushConf().then(()=>{
|
||
|
func("/system/setPushCrontab",state.pushCron).then(data => {
|
||
|
if(data.status === "success")
|
||
|
alertMsg('<cn>保存设置成功</cn><en>Save config success!</en>', 'success');
|
||
|
});
|
||
|
})
|
||
|
}
|
||
|
|
||
|
onMounted(() => {
|
||
|
handlePushCrontab();
|
||
|
handlePushState();
|
||
|
handlePushTimeCount();
|
||
|
})
|
||
|
|
||
|
return {...state,pushConf,onChangeSrcV,onPushStart,onPushStop,addPushUrl,delPushUrl,handleEnableConf,getPushSpeed,savePushConf}
|
||
|
}
|
||
|
});
|
||
|
app.use(ignoreCustomElementPlugin);
|
||
|
app.mount('#app');
|
||
|
</script>
|
||
|
</body>
|
||
|
</html>
|