mirror of https://github.com/EasyDarwin/EasyDarwin
pull & push ui done
parent
1a6cb3260a
commit
a7f5b1ce96
|
@ -60,7 +60,9 @@ func (h *APIHandler) Pushers(c *gin.Context) {
|
|||
}
|
||||
pushers = append(pushers, map[string]interface{}{
|
||||
"id": pusher.ID(),
|
||||
"path": rtsp,
|
||||
"url": rtsp,
|
||||
"path": pusher.Path(),
|
||||
"source": pusher.Source(),
|
||||
"transType": pusher.TransType(),
|
||||
"inBytes": pusher.InBytes(),
|
||||
"outBytes": pusher.OutBytes(),
|
||||
|
|
|
@ -3,9 +3,6 @@ package routers
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/EasyDarwin/EasyDarwin/rtsp"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/penggy/EasyGoLib/utils"
|
||||
"log"
|
||||
"math"
|
||||
"net/http"
|
||||
|
@ -16,12 +13,18 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/EasyDarwin/EasyDarwin/rtsp"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/penggy/EasyGoLib/utils"
|
||||
)
|
||||
|
||||
func (h *APIHandler) StreamStart(c *gin.Context) {
|
||||
type Form struct {
|
||||
URL string `form:"url" binding:"required"`
|
||||
IdleTimeout int `form:"idleTimeout"`
|
||||
URL string `form:"url" binding:"required"`
|
||||
CustomPath string `form:"customPath"`
|
||||
IdleTimeout int `form:"idleTimeout"`
|
||||
HeartbeatInterval int `form:"heartbeatInterval"`
|
||||
}
|
||||
var form Form
|
||||
err := c.Bind(&form)
|
||||
|
@ -29,8 +32,21 @@ func (h *APIHandler) StreamStart(c *gin.Context) {
|
|||
log.Printf("Pull to push err:%v", err)
|
||||
return
|
||||
}
|
||||
client := rtsp.NewRTSPClient(rtsp.GetServer(), form.URL, 0)
|
||||
client, err := rtsp.NewRTSPClient(rtsp.GetServer(), form.URL, int64(form.HeartbeatInterval)*1000)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
if form.CustomPath != "" && !strings.HasPrefix(form.CustomPath, "/") {
|
||||
form.CustomPath = "/" + form.CustomPath
|
||||
}
|
||||
client.CustomPath = form.CustomPath
|
||||
|
||||
pusher := rtsp.NewClientPusher(client)
|
||||
if rtsp.GetServer().GetPusher(pusher.Path()) != nil {
|
||||
c.AbortWithStatusJSON(http.StatusBadRequest, fmt.Sprintf("Path %s already exists", client.Path))
|
||||
return
|
||||
}
|
||||
err = client.Start(time.Duration(form.IdleTimeout) * time.Second)
|
||||
if err != nil {
|
||||
log.Printf("Pull stream err :%v", err)
|
||||
|
@ -57,7 +73,6 @@ func (h *APIHandler) StreamStop(c *gin.Context) {
|
|||
if v.ID() == form.ID {
|
||||
v.Stop()
|
||||
c.IndentedJSON(200, "OK")
|
||||
|
||||
log.Printf("Stop %v success ", v)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -55,6 +55,9 @@ func (pusher *Pusher) Path() string {
|
|||
if pusher.Session != nil {
|
||||
return pusher.Session.Path
|
||||
}
|
||||
if pusher.RTSPClient.CustomPath != "" {
|
||||
return pusher.RTSPClient.CustomPath
|
||||
}
|
||||
return pusher.RTSPClient.Path
|
||||
}
|
||||
|
||||
|
@ -136,6 +139,13 @@ func (pusher *Pusher) StartAt() time.Time {
|
|||
return pusher.RTSPClient.StartAt
|
||||
}
|
||||
|
||||
func (pusher *Pusher) Source() string {
|
||||
if pusher.Session != nil {
|
||||
return pusher.Session.URL
|
||||
}
|
||||
return pusher.RTSPClient.URL
|
||||
}
|
||||
|
||||
func NewClientPusher(client *RTSPClient) (pusher *Pusher) {
|
||||
pusher = &Pusher{
|
||||
RTSPClient: client,
|
||||
|
|
|
@ -13,6 +13,8 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/teris-io/shortid"
|
||||
|
||||
"github.com/penggy/EasyGoLib/utils"
|
||||
|
||||
"github.com/pixelbender/go-sdp/sdp"
|
||||
|
@ -24,6 +26,7 @@ type RTSPClient struct {
|
|||
Status string
|
||||
URL string
|
||||
Path string
|
||||
CustomPath string //custom path for pusher
|
||||
ID string
|
||||
Conn net.Conn
|
||||
AuthHeaders bool
|
||||
|
@ -56,24 +59,25 @@ func (client *RTSPClient) String() string {
|
|||
return fmt.Sprintf("client[%s]", client.URL)
|
||||
}
|
||||
|
||||
func NewRTSPClient(server *Server, rawUrl string, sendOptionMillis int64) *RTSPClient {
|
||||
func NewRTSPClient(server *Server, rawUrl string, sendOptionMillis int64) (client *RTSPClient, err error) {
|
||||
url, err := url.Parse(rawUrl)
|
||||
if err != nil {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
client := &RTSPClient{
|
||||
client = &RTSPClient{
|
||||
Server: server,
|
||||
Stoped: false,
|
||||
URL: rawUrl,
|
||||
ID: url.Path,
|
||||
ID: shortid.MustGenerate(),
|
||||
Path: url.Path,
|
||||
vRTPChannel: 0,
|
||||
vRTPControlChannel: 1,
|
||||
aRTPChannel: 2,
|
||||
aRTPControlChannel: 3,
|
||||
OptionIntervalMillis: sendOptionMillis,
|
||||
StartAt: time.Now(),
|
||||
}
|
||||
return client
|
||||
return
|
||||
}
|
||||
|
||||
func (client *RTSPClient) Start(timeout time.Duration) error {
|
||||
|
@ -197,7 +201,7 @@ func (client *RTSPClient) Start(timeout time.Duration) error {
|
|||
for !client.Stoped {
|
||||
if OptionIntervalMillis > 0 {
|
||||
elapse := time.Now().Sub(startTime)
|
||||
if elapse > time.Duration(OptionIntervalMillis*int64(time.Millisecond)) {
|
||||
if elapse > time.Duration(OptionIntervalMillis)*time.Millisecond {
|
||||
startTime = time.Now()
|
||||
headers := make(map[string]string)
|
||||
headers["Require"] = "implicit-play"
|
||||
|
|
|
@ -7,13 +7,27 @@
|
|||
<span class="help-block">{{errors.first('url')}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="['form-group', { 'has-error': errors.has('idleTimeout')}]">
|
||||
<label for="input-url" class="col-sm-3 control-label">空闲超时(秒)</label>
|
||||
<div :class="['form-group', { 'has-error': errors.has('customPath')}]">
|
||||
<label for="input-custom-path" class="col-sm-3 control-label">自定义路径</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" id="input-idle-timeout" class="form-control" name="idleTimeout" data-vv-as="空闲超时" v-model.trim="form.idleTimeout">
|
||||
<input type="text" id="input-custom-path" class="form-control" name="customPath" data-vv-as="自定义路径" v-model.trim="form.customPath" placeholder="/your/custom/path">
|
||||
<span class="help-block">{{errors.first('customPath')}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="['form-group', { 'has-error': errors.has('idleTimeout')}]">
|
||||
<label for="input-idle-timeout" class="col-sm-3 control-label">空闲超时(秒)</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" id="input-idle-timeout" class="form-control" name="idleTimeout" data-vv-as="空闲超时" v-validate="'numeric'" v-model.trim="form.idleTimeout" placeholder="默认使用系统配置">
|
||||
<span class="help-block">{{errors.first('idleTimeout')}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :class="['form-group', { 'has-error': errors.has('heartbeatInterval')}]">
|
||||
<label for="input-heartbeat-interval" class="col-sm-3 control-label">心跳间隔(秒)</label>
|
||||
<div class="col-sm-8">
|
||||
<input type="text" id="input-heartbeat-interval" class="form-control" name="heartbeatInterval" data-vv-as="心跳间隔" v-validate="'numeric'" v-model.trim="form.heartbeatInterval" placeholder="默认使用系统配置">
|
||||
<span class="help-block">{{errors.first('heartbeatInterval')}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</FormDlg>
|
||||
</template>
|
||||
|
||||
|
@ -36,7 +50,9 @@ export default {
|
|||
defForm() {
|
||||
return {
|
||||
url: '',
|
||||
idleTimeout: ''
|
||||
customPath: '',
|
||||
idleTimeout: '',
|
||||
heartbeatInterval: ''
|
||||
}
|
||||
},
|
||||
onHide() {
|
||||
|
|
|
@ -32,20 +32,23 @@
|
|||
<div class="box-body">
|
||||
<el-table :data="pushers" stripe class="view-list" :default-sort="{prop: 'startAt', order: 'descending'}" @sort-change="sortChange">
|
||||
<el-table-column prop="id" label="ID" min-width="120"></el-table-column>
|
||||
<el-table-column prop="path" label="播放地址" min-width="240" show-overflow-tooltip>
|
||||
<el-table-column label="播放地址" min-width="240" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span>
|
||||
<i class="fa fa-copy" role="button" v-clipboard="scope.row.path" title="点击拷贝" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
|
||||
{{scope.row.path}}
|
||||
<i class="fa fa-copy" role="button" v-clipboard="scope.row.url" title="点击拷贝" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
|
||||
{{scope.row.url}}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column prop="source" label="源地址" min-width="240" show-overflow-tooltip>
|
||||
<el-table-column label="源地址" min-width="240" show-overflow-tooltip>
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.source">{{scope.row.source}}</span>
|
||||
<span v-if="scope.row.source">
|
||||
<i class="fa fa-copy" role="button" v-clipboard="scope.row.source" title="点击拷贝" @success="$message({type:'success', message:'成功拷贝到粘贴板'})"></i>
|
||||
{{scope.row.source}}
|
||||
</span>
|
||||
<span v-else>-</span>
|
||||
</template>
|
||||
</el-table-column> -->
|
||||
</el-table-column>
|
||||
<el-table-column prop="transType" label="传输方式" min-width="100"></el-table-column>
|
||||
<el-table-column prop="inBytes" label="上行流量" min-width="120" :formatter="formatBytes" sortable="custom"></el-table-column>
|
||||
<el-table-column prop="outBytes" label="下行流量" min-width="120" :formatter="formatBytes" sortable="custom"></el-table-column>
|
||||
|
@ -54,7 +57,9 @@
|
|||
<el-table-column label="操作" min-width="120" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<div class="btn-group">
|
||||
<a role="button" class="btn btn-xs btn-danger" @click.prevent="stop(scope.row)" v-if="scope.row.source"><i class="fa fa-stop"></i> 停止</a>
|
||||
<a role="button" class="btn btn-xs btn-danger" @click.prevent="stop(scope.row)">
|
||||
<i class="fa fa-stop"></i> 停止
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
@ -97,7 +102,7 @@ export default {
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$refs["q"].focus();
|
||||
// this.$refs["q"].focus();
|
||||
this.timer = setInterval(() => {
|
||||
this.getPushers();
|
||||
}, 3000);
|
||||
|
@ -144,11 +149,13 @@ export default {
|
|||
return prettyBytes(val);
|
||||
},
|
||||
stop(row) {
|
||||
$.get("/api/v1/stream/stop", {
|
||||
streamID: row.id
|
||||
}).then(data => {
|
||||
this.getPushers();
|
||||
})
|
||||
this.$confirm(`确认停止 ${row.path} ?`, "提示").then(() => {
|
||||
$.get("/api/v1/stream/stop", {
|
||||
id: row.id
|
||||
}).then(data => {
|
||||
this.getPushers();
|
||||
})
|
||||
}).catch(() => {});
|
||||
}
|
||||
},
|
||||
beforeRouteEnter(to, from, next) {
|
||||
|
|
|
@ -19,7 +19,7 @@ define({
|
|||
"apidoc": "0.3.0",
|
||||
"generator": {
|
||||
"name": "apidoc",
|
||||
"time": "2018-11-24T02:46:15.810Z",
|
||||
"time": "2018-11-24T14:05:41.551Z",
|
||||
"url": "http://apidocjs.com",
|
||||
"version": "0.17.6"
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
"apidoc": "0.3.0",
|
||||
"generator": {
|
||||
"name": "apidoc",
|
||||
"time": "2018-11-24T02:46:15.810Z",
|
||||
"time": "2018-11-24T14:05:41.551Z",
|
||||
"url": "http://apidocjs.com",
|
||||
"version": "0.17.6"
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
<meta content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no" name="viewport">
|
||||
<script src="js/jquery-2.2.4.js"></script>
|
||||
<script src="js/easy-player-lib.min.js"></script>
|
||||
<link href="css/index.eba7eb98.css" rel="stylesheet"></head>
|
||||
<link href="css/index.09a1f19a.css" rel="stylesheet"></head>
|
||||
<body class="skin-green sidebar-mini">
|
||||
<div id="app"></div>
|
||||
|
||||
<script type="text/javascript" src="js/index.eba7eb98.js"></script></body>
|
||||
<script type="text/javascript" src="js/index.09a1f19a.js"></script></body>
|
||||
</html>
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue