diff --git a/kvmapp/system/update-nanokvm.py b/kvmapp/system/update-nanokvm.py
index af0e490..2a2f15f 100755
--- a/kvmapp/system/update-nanokvm.py
+++ b/kvmapp/system/update-nanokvm.py
@@ -46,32 +46,6 @@ def download_firmware() -> None:
print("Completed downloading firmware.")
-def download_lib():
- print("download lib...")
-
- device_key = read("/device_key")
-
- url = f"https://maixvision.sipeed.com/api/v1/nanokvm/encryption?uid={device_key}"
- headers = {"token": "MaixVision2024"}
- response = requests.get(url, headers=headers, stream=True)
-
- if response.status_code != 200:
- raise Exception(f"download lib failed, status: {response.status_code}")
-
- content_type = response.headers.get("content-type")
- if content_type != "application/octet-stream":
- raise Exception(f"download lib failed, content_type: {content_type}")
-
- lib_file = f"{temporary}/libmaixcam_lib.so"
- with open(lib_file, "wb") as f:
- f.write(response.content)
-
- lib_dir = f"{temporary}/latest/kvm_system/dl_lib/"
- shutil.copy(lib_file, lib_dir)
-
- print("download lib done")
-
-
def update() -> None:
backup_dir = pathlib.Path("/root/old")
firmware_dir = pathlib.Path("/kvmapp")
@@ -94,7 +68,7 @@ def change_permissions() -> None:
os.chmod(file_path, 0o755)
print("change permissions done")
-
+
def main() -> None:
try:
@@ -105,7 +79,6 @@ def main() -> None:
mkdir()
download_firmware()
- # download_lib()
update()
change_permissions()
diff --git a/server/common/cgo.go b/server/common/cgo.go
index 6289d30..0a040fd 100644
--- a/server/common/cgo.go
+++ b/server/common/cgo.go
@@ -20,9 +20,7 @@ var (
kvmVisionOnce sync.Once
)
-type KvmVision struct {
- mutex sync.Mutex
-}
+type KvmVision struct{}
func GetKvmVision() *KvmVision {
kvmVisionOnce.Do(func() {
@@ -44,9 +42,6 @@ func GetKvmVision() *KvmVision {
}
func (k *KvmVision) ReadMjpeg(width uint16, height uint16, quality uint16) (data []byte, result int) {
- k.mutex.Lock()
- defer k.mutex.Unlock()
-
var (
kvmData *C.uint8_t
dataSize C.uint32_t
@@ -72,10 +67,7 @@ func (k *KvmVision) ReadMjpeg(width uint16, height uint16, quality uint16) (data
return
}
-func (k *KvmVision) ReadH264(width uint16, height uint16, bitRate uint16) (data []byte, sps []byte, pps []byte, result int) {
- k.mutex.Lock()
- defer k.mutex.Unlock()
-
+func (k *KvmVision) ReadH264(width uint16, height uint16, bitRate uint16) (data []byte, result int) {
var (
kvmData *C.uint8_t
dataSize C.uint32_t
@@ -97,48 +89,10 @@ func (k *KvmVision) ReadH264(width uint16, height uint16, bitRate uint16) (data
data = C.GoBytes(unsafe.Pointer(kvmData), C.int(dataSize))
- if result == 3 {
- sps, _ = k.ReadH264SPS()
- pps, _ = k.ReadH264PPS()
- }
-
log.Debugf("read kvm image: %v", result)
return
}
-func (k *KvmVision) ReadH264SPS() ([]byte, int) {
- var (
- kvmData *C.uint8_t
- dataSize C.uint32_t
- )
-
- result := int(C.kvmv_get_sps_frame(&kvmData, &dataSize))
- if result < 0 {
- log.Errorf("failed to read sps: %v", result)
- return nil, result
- }
-
- data := C.GoBytes(unsafe.Pointer(kvmData), C.int(dataSize))
-
- return data, result
-}
-
-func (k *KvmVision) ReadH264PPS() ([]byte, int) {
- var (
- kvmData *C.uint8_t
- dataSize C.uint32_t
- )
-
- result := int(C.kvmv_get_pps_frame(&kvmData, &dataSize))
- if result < 0 {
- log.Errorf("failed to read pps: %v", result)
- return nil, result
- }
-
- data := C.GoBytes(unsafe.Pointer(kvmData), C.int(dataSize))
- return data, result
-}
-
func (k *KvmVision) SetHDMI(enable bool) int {
hdmiEnable := C.uint8_t(0)
if enable {
@@ -154,10 +108,17 @@ func (k *KvmVision) SetHDMI(enable bool) int {
return result
}
-func (k *KvmVision) Close() {
- k.mutex.Lock()
- defer k.mutex.Unlock()
+func (k *KvmVision) SetGop(gop uint8) {
+ _gop := C.uint8_t(gop)
+ C.set_h264_gop(_gop)
+}
+func (k *KvmVision) SetFrameDetect(frame uint8) {
+ _frame := C.uint8_t(frame)
+ C.set_frame_detact(_frame)
+}
+
+func (k *KvmVision) Close() {
C.kvmv_deinit()
log.Debugf("stop kvm vision...")
}
diff --git a/server/common/screen.go b/server/common/screen.go
index 8059bd5..8ed486a 100644
--- a/server/common/screen.go
+++ b/server/common/screen.go
@@ -8,6 +8,7 @@ type Screen struct {
FPS int
Quality uint16
BitRate uint16
+ GOP uint8
}
var (
@@ -46,6 +47,7 @@ func GetScreen() *Screen {
Quality: 80,
FPS: 30,
BitRate: 3000,
+ GOP: 30,
}
})
@@ -70,6 +72,9 @@ func SetScreen(key string, value int) {
case "fps":
screen.FPS = validateFPS(value)
+
+ case "gop":
+ screen.GOP = uint8(value)
}
}
diff --git a/server/dl_lib/libkvm.so b/server/dl_lib/libkvm.so
index 256f24a..bf5bbbe 100644
Binary files a/server/dl_lib/libkvm.so and b/server/dl_lib/libkvm.so differ
diff --git a/server/go.mod b/server/go.mod
index b123016..58ffbce 100644
--- a/server/go.mod
+++ b/server/go.mod
@@ -1,6 +1,8 @@
module NanoKVM-Server
-go 1.22.1
+go 1.23.0
+
+toolchain go1.23.2
require (
github.com/creack/pty v1.1.24
@@ -15,7 +17,7 @@ require (
github.com/sirupsen/logrus v1.9.3
github.com/spf13/viper v1.19.0
github.com/unrolled/secure v1.15.0
- golang.org/x/crypto v0.31.0
+ golang.org/x/crypto v0.36.0
gopkg.in/yaml.v3 v3.0.1
)
@@ -71,9 +73,9 @@ require (
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
- golang.org/x/net v0.33.0 // indirect
- golang.org/x/sys v0.28.0 // indirect
- golang.org/x/text v0.21.0 // indirect
+ golang.org/x/net v0.37.0 // indirect
+ golang.org/x/sys v0.31.0 // indirect
+ golang.org/x/text v0.23.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
)
diff --git a/server/go.sum b/server/go.sum
index 271cc14..61ff70e 100644
--- a/server/go.sum
+++ b/server/go.sum
@@ -160,17 +160,25 @@ golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
+golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
+golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
+golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
+golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
+golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
+golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
+golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/server/include/kvm_vision.h b/server/include/kvm_vision.h
index cacd036..4f7af4f 100644
--- a/server/include/kvm_vision.h
+++ b/server/include/kvm_vision.h
@@ -50,20 +50,20 @@ void set_venc_auto_recyc(uint8_t _enable);
-5: Retrieving image, please wait
-4: Modifying image resolution, please wait
-3: img buffer full
- -2: VENC Errorl
+ -2: VENC Error
-1: No images were acquired
0: Acquire MJPEG encoded images
- 1: Acquire H264 encoded images(SPS)
- 2: Acquire H264 encoded images(PPS)
+ 1: Acquire H264 encoded images(SPS)[Deprecated]
+ 2: Acquire H264 encoded images(PPS)[Deprecated]
3: Acquire H264 encoded images(I)
4: Acquire H264 encoded images(P)
+ 5: IMG not changed
**********************************************************************************/
int kvmv_read_img(uint16_t _width, uint16_t _height, uint8_t _type, uint16_t _qlty, uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size);
-int kvmv_get_sps_frame(uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size);
-int kvmv_get_pps_frame(uint8_t** _pp_kvm_data, uint32_t* _p_kvmv_data_size);
int free_kvmv_data(uint8_t ** _pp_kvm_data);
void free_all_kvmv_data();
void set_h264_gop(uint8_t _gop);
+void set_frame_detact(uint8_t _frame_detact);
void kvmv_deinit();
uint8_t kvmv_hdmi_control(uint8_t _en);
@@ -71,4 +71,4 @@ uint8_t kvmv_hdmi_control(uint8_t _en);
}
#endif
-#endif // KVM_VISION_H_
\ No newline at end of file
+#endif // KVM_VISION_H_
diff --git a/server/logger/formatter.go b/server/logger/formatter.go
index a995d1a..b1dca36 100644
--- a/server/logger/formatter.go
+++ b/server/logger/formatter.go
@@ -22,7 +22,7 @@ func (f *formatter) Format(entry *logrus.Entry) ([]byte, error) {
buffer = &bytes.Buffer{}
}
- now := entry.Time.Format("2006-01-02 15:04:05")
+ now := entry.Time.Format("2006-01-02 15:04:05.000")
if entry.HasCaller() {
fileName := filepath.Base(entry.Caller.File)
diff --git a/server/proto/stream.go b/server/proto/stream.go
index 52687db..f0bb202 100644
--- a/server/proto/stream.go
+++ b/server/proto/stream.go
@@ -1,9 +1,9 @@
package proto
-type GetFrameDetectRsp struct {
- Enabled bool `json:"enabled"`
+type UpdateFrameDetectReq struct {
+ Enabled bool `validate:"omitempty"`
}
-type UpdateFrameDetectRsp struct {
- Enabled bool `json:"enabled"`
+type StopFrameDetectReq struct {
+ Duration int `validate:"omitempty"`
}
diff --git a/server/router/stream.go b/server/router/stream.go
index a59308e..bcc13d3 100644
--- a/server/router/stream.go
+++ b/server/router/stream.go
@@ -12,8 +12,7 @@ func streamRouter(r *gin.Engine) {
api := r.Group("/api").Use(middleware.CheckToken())
api.GET("/stream/mjpeg", mjpeg.Connect) // mjpeg stream
- api.GET("/stream/mjpeg/detect", mjpeg.GetFrameDetect) // get frame detect state
- api.POST("/stream/mjpeg/detect", mjpeg.UpdateFrameDetect) // update frame detect state
+ api.POST("/stream/mjpeg/detect", mjpeg.UpdateFrameDetect) // update frame detect
api.POST("/stream/mjpeg/detect/stop", mjpeg.StopFrameDetect) // temporary stop frame detect
api.GET("/stream/h264", h264.Connect) // h264 stream
diff --git a/server/service/stream/h264/client.go b/server/service/stream/h264/client.go
index 4714ac6..1db5d5b 100644
--- a/server/service/stream/h264/client.go
+++ b/server/service/stream/h264/client.go
@@ -2,6 +2,7 @@ package h264
import (
"encoding/json"
+ "sync"
"github.com/gorilla/websocket"
"github.com/pion/webrtc/v4"
@@ -9,8 +10,9 @@ import (
)
type Client struct {
- ws *websocket.Conn
- pc *webrtc.PeerConnection
+ ws *websocket.Conn
+ pc *webrtc.PeerConnection
+ mutex sync.Mutex
}
type Message struct {
@@ -36,9 +38,7 @@ func (c *Client) addTrack() {
return
}
- mutex.Lock()
trackMap[c.ws] = videoTrack
- mutex.Unlock()
}
// register callback events
@@ -61,13 +61,9 @@ func (c *Client) register() {
// ICE connection state has changed
c.pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
if state == webrtc.ICEConnectionStateConnected && !isSending {
- mutex.Lock()
- if !isSending {
- // start sending h264 data
- go send()
- isSending = true
- }
- mutex.Unlock()
+ // start sending h264 data
+ go send()
+ isSending = true
}
log.Debugf("ice connection state has changed to %s", state.String())
@@ -81,13 +77,11 @@ func (c *Client) readMessage() {
for {
_, raw, err := c.ws.ReadMessage()
if err != nil {
- mutex.Lock()
delete(trackMap, c.ws)
- if len(trackMap) == 0 && isSending {
+ if isSending && len(trackMap) == 0 {
// stop sending when all websocket connections are closed
- exitSig <- true
+ isSending = false
}
- mutex.Unlock()
log.Debugf("failed to read message: %s", err)
return
@@ -144,9 +138,6 @@ func (c *Client) readMessage() {
return
}
- case "heartbeat":
- _ = c.sendMessage("heartbeat", "")
-
default:
log.Debugf("unhandled message event: %s", message.Event)
}
@@ -155,8 +146,8 @@ func (c *Client) readMessage() {
// send websocket message
func (c *Client) sendMessage(event string, data string) error {
- mutex.RLock()
- defer mutex.RUnlock()
+ c.mutex.Lock()
+ defer c.mutex.Unlock()
message := &Message{
Event: event,
diff --git a/server/service/stream/h264/h264.go b/server/service/stream/h264/h264.go
index cd9b691..67358de 100644
--- a/server/service/stream/h264/h264.go
+++ b/server/service/stream/h264/h264.go
@@ -4,6 +4,7 @@ import (
"NanoKVM-Server/config"
"net/http"
"sync"
+ "time"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
@@ -18,9 +19,7 @@ var (
},
}
trackMap = make(map[*websocket.Conn]*webrtc.TrackLocalStaticSample)
- mutex = sync.RWMutex{}
isSending = false
- exitSig = make(chan bool, 1)
)
func Connect(c *gin.Context) {
@@ -35,6 +34,9 @@ func Connect(c *gin.Context) {
log.Debugf("h264 websocket disconnected")
}()
+ var zeroTime time.Time
+ _ = wsConn.SetReadDeadline(zeroTime)
+
conf := config.GetInstance()
var iceServers []webrtc.ICEServer
@@ -67,8 +69,9 @@ func Connect(c *gin.Context) {
}()
client := &Client{
- ws: wsConn,
- pc: peerConn,
+ ws: wsConn,
+ pc: peerConn,
+ mutex: sync.Mutex{},
}
client.addTrack()
diff --git a/server/service/stream/h264/sender.go b/server/service/stream/h264/sender.go
index da16c9c..2510c16 100644
--- a/server/service/stream/h264/sender.go
+++ b/server/service/stream/h264/sender.go
@@ -2,7 +2,6 @@ package h264
import (
"NanoKVM-Server/common"
- "NanoKVM-Server/service/stream"
"time"
"github.com/pion/webrtc/v4/pkg/media"
@@ -18,63 +17,45 @@ func send() {
defer ticker.Stop()
vision := common.GetKvmVision()
- for {
- select {
- case <-ticker.C:
- height := screen.Height
- width, ok := common.ResolutionMap[height]
- if !ok {
- width = 0
- height = 0
- }
-
- bitRate := screen.BitRate
- if _, ok := common.BitRateMap[bitRate]; !ok {
- bitRate = 3000
- }
-
- data, sps, pps, result := vision.ReadH264(width, height, bitRate)
- if result < 0 {
- continue
- }
-
- if result == 3 {
- writeSample(sps, duration)
- writeSample(pps, duration)
- }
- writeSample(data, duration)
-
- stream.GetFrameRateCounter().Update()
-
- if screen.FPS != fps {
- fps = screen.FPS
- duration = time.Second / time.Duration(fps)
- ticker.Reset(duration)
- }
-
- case <-exitSig:
- mutex.Lock()
- isSending = false
- mutex.Unlock()
+ for range ticker.C {
+ if !isSending && len(trackMap) == 0 {
return
}
- }
-}
-func writeSample(data []byte, duration time.Duration) {
- sample := media.Sample{
- Data: data,
- Duration: duration,
- }
+ height := screen.Height
+ width, ok := common.ResolutionMap[height]
+ if !ok {
+ width = 0
+ height = 0
+ }
- mutex.RLock()
- defer mutex.RUnlock()
+ bitRate := screen.BitRate
+ if _, ok := common.BitRateMap[bitRate]; !ok {
+ bitRate = 3000
+ }
- for _, track := range trackMap {
- if err := track.WriteSample(sample); err != nil {
- log.Errorf("failed to send h264 data: %s", err)
+ data, result := vision.ReadH264(width, height, bitRate)
+ if result < 0 {
+ continue
+ }
+
+ sample := media.Sample{
+ Data: data,
+ Duration: duration,
+ }
+
+ for _, track := range trackMap {
+ if err := track.WriteSample(sample); err != nil {
+ log.Errorf("failed to send h264 data: %s", err)
+ }
+ }
+
+ log.Debugf("send h264 data: %d", len(data))
+
+ if screen.FPS != fps {
+ fps = screen.FPS
+ duration = time.Second / time.Duration(fps)
+ ticker.Reset(duration)
}
}
-
- log.Debugf("send h264 data: %d", len(data))
}
diff --git a/server/service/stream/mjpeg/frame-detect.go b/server/service/stream/mjpeg/frame-detect.go
index fa89358..ac2d8df 100644
--- a/server/service/stream/mjpeg/frame-detect.go
+++ b/server/service/stream/mjpeg/frame-detect.go
@@ -1,132 +1,55 @@
package mjpeg
import (
- "errors"
- "os"
+ "NanoKVM-Server/common"
+ "NanoKVM-Server/proto"
"time"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
-
- "NanoKVM-Server/proto"
)
-const (
- frameDetect = "/etc/kvm/frame_detact"
- frameDetectTmp = "/etc/kvm/frame_detact_tmp"
-)
-
-type UpdateFrameDetectRsp struct {
- Enabled bool `json:"enabled"`
-}
-
-func GetFrameDetect(c *gin.Context) {
- var rsp proto.Response
-
- isEnabled, err := isFrameDetectEnabled()
- if err != nil {
- rsp.ErrRsp(c, -1, "unknown frame detect status")
- return
- }
-
- rsp.OkRspWithData(c, &proto.GetFrameDetectRsp{
- Enabled: isEnabled,
- })
- log.Debugf("get frame detect success, enabled: %t", isEnabled)
-}
+const FrameDetectInterval uint8 = 60
func UpdateFrameDetect(c *gin.Context) {
+ var req proto.UpdateFrameDetectReq
var rsp proto.Response
- isEnabled, err := isFrameDetectEnabled()
- if err != nil {
- rsp.ErrRsp(c, -1, "unknown frame status")
+ if err := proto.ParseFormRequest(c, &req); err != nil {
+ rsp.ErrRsp(c, -1, "invalid parameters")
return
}
- if isEnabled {
- _ = os.Remove(frameDetect)
- _ = os.Remove(frameDetectTmp)
- } else {
- file, err2 := os.OpenFile(frameDetect, os.O_CREATE|os.O_TRUNC, 0o644)
- if err2 != nil {
- rsp.ErrRsp(c, -3, "enable frame detect failed")
- return
- }
- defer func() {
- _ = file.Close()
- }()
+ var frame uint8 = 0
+ if req.Enabled {
+ frame = FrameDetectInterval
}
- isEnabled, err = isFrameDetectEnabled()
- if err != nil {
- rsp.ErrRsp(c, -4, "unknown frame status")
- return
- }
+ common.GetKvmVision().SetFrameDetect(frame)
- rsp.OkRspWithData(c, &UpdateFrameDetectRsp{
- Enabled: isEnabled,
- })
- log.Debugf("update frame detect success, enabled: %t", isEnabled)
+ rsp.OkRsp(c)
+ log.Debugf("update frame detect: %t", req.Enabled)
}
func StopFrameDetect(c *gin.Context) {
+ var req proto.StopFrameDetectReq
var rsp proto.Response
- exist, err := isFileExist(frameDetect)
- if err != nil {
- rsp.ErrRsp(c, -1, "unknown frame status")
+ if err := proto.ParseFormRequest(c, &req); err != nil {
+ rsp.ErrRsp(c, -1, "invalid parameters")
return
}
- if !exist {
- rsp.OkRsp(c)
- return
+ duration := 10 * time.Second
+ if req.Duration > 0 {
+ duration = time.Duration(req.Duration) * time.Second
}
- err = os.Rename(frameDetect, frameDetectTmp)
- if err != nil {
- rsp.ErrRsp(c, -2, "stop operation failed")
- return
- }
+ vision := common.GetKvmVision()
- go func() {
- time.Sleep(20 * time.Second)
- _ = os.Rename(frameDetectTmp, frameDetect)
- log.Debug("frame detect started")
- }()
+ vision.SetFrameDetect(0)
+ time.Sleep(duration)
+ vision.SetFrameDetect(FrameDetectInterval)
rsp.OkRsp(c)
- log.Debug("frame detect stopped")
-}
-
-func isFrameDetectEnabled() (bool, error) {
- exist, err := isFileExist(frameDetect)
- if err != nil {
- return false, err
- }
-
- if exist {
- return true, nil
- }
-
- exist, err = isFileExist(frameDetectTmp)
- if err != nil {
- return false, err
- }
-
- return exist, nil
-}
-
-func isFileExist(name string) (bool, error) {
- _, err := os.Stat(name)
- if err != nil {
- if errors.Is(err, os.ErrNotExist) {
- return false, nil
- }
-
- return false, nil
- }
-
- return true, nil
}
diff --git a/server/service/stream/mjpeg/mjpeg.go b/server/service/stream/mjpeg/mjpeg.go
index 32ba210..5f77e8d 100644
--- a/server/service/stream/mjpeg/mjpeg.go
+++ b/server/service/stream/mjpeg/mjpeg.go
@@ -11,8 +11,8 @@ import (
)
var (
- chanMap = make(map[*gin.Context]chan struct{})
- mutex = sync.RWMutex{}
+ chanMap = make(map[*gin.Context]bool)
+ mutex = sync.Mutex{}
exitSig = make(chan bool, 1)
)
@@ -23,13 +23,13 @@ func Connect(c *gin.Context) {
c.Header("Pragma", "no-cache")
mutex.Lock()
- chanMap[c] = make(chan struct{}, 1)
+ chanMap[c] = true
if len(chanMap) == 1 {
go send()
}
mutex.Unlock()
- <-chanMap[c]
+ <-c.Request.Context().Done()
mutex.Lock()
delete(chanMap, c)
@@ -63,14 +63,13 @@ func send() {
}
data, result := vision.ReadMjpeg(width, height, quality)
- if result < 0 {
+ if result < 0 || result == 5 {
continue
}
- for c, ch := range chanMap {
+ for c := range chanMap {
if err := write(c, data); err != nil {
log.Debugf("failed to write mjpeg data: %s", err)
- close(ch)
}
}
log.Debugf("send mjpeg data: %d", len(data))
diff --git a/server/service/vm/screen.go b/server/service/vm/screen.go
index a42a395..a5b4c39 100644
--- a/server/service/vm/screen.go
+++ b/server/service/vm/screen.go
@@ -4,6 +4,7 @@ import (
"NanoKVM-Server/common"
"fmt"
"os"
+ "strconv"
"github.com/gin-gonic/gin"
log "github.com/sirupsen/logrus"
@@ -22,30 +23,34 @@ func (s *Service) SetScreen(c *gin.Context) {
var req proto.SetScreenReq
var rsp proto.Response
- if err := proto.ParseFormRequest(c, &req); err != nil {
+ err := proto.ParseFormRequest(c, &req)
+ if err != nil {
rsp.ErrRsp(c, -1, "invalid arguments")
return
}
- file, ok := screenFileMap[req.Type]
- if !ok {
- rsp.ErrRsp(c, -2, "invalid arguments")
- return
- }
-
- data := fmt.Sprintf("%d", req.Value)
- if req.Type == "type" {
+ switch req.Type {
+ case "type":
+ data := "h264"
if req.Value == 0 {
data = "mjpeg"
- } else {
- data = "h264"
}
+ err = writeScreen("type", data)
+
+ case "gop":
+ gop := 30
+ if req.Value >= 1 && req.Value <= 100 {
+ gop = req.Value
+ }
+ common.GetKvmVision().SetGop(uint8(gop))
+
+ default:
+ data := strconv.Itoa(req.Value)
+ err = writeScreen(req.Type, data)
}
- err := os.WriteFile(file, []byte(data), 0o666)
if err != nil {
- log.Errorf("write kvm %s failed: %s", file, err)
- rsp.ErrRsp(c, -3, "update screen failed")
+ rsp.ErrRsp(c, -2, "update screen failed")
return
}
@@ -54,3 +59,18 @@ func (s *Service) SetScreen(c *gin.Context) {
log.Debugf("update screen: %+v", req)
rsp.OkRsp(c)
}
+
+func writeScreen(key string, value string) error {
+ file, ok := screenFileMap[key]
+ if !ok {
+ return fmt.Errorf("invalid argument %s", key)
+ }
+
+ err := os.WriteFile(file, []byte(value), 0o666)
+ if err != nil {
+ log.Errorf("write kvm %s failed: %s", file, err)
+ return err
+ }
+
+ return nil
+}
diff --git a/web/src/api/stream.ts b/web/src/api/stream.ts
index 132fb55..227e55f 100644
--- a/web/src/api/stream.ts
+++ b/web/src/api/stream.ts
@@ -1,16 +1,17 @@
import { http } from '@/lib/http.ts';
-// get whether the frame detect is enabled
-export function getFrameDetect() {
- return http.get('/api/stream/mjpeg/detect');
-}
-
// enable/disable frame detect
-export function updateFrameDetect() {
- return http.post('/api/stream/mjpeg/detect');
+export function updateFrameDetect(enabled: boolean) {
+ const data = {
+ enabled
+ };
+ return http.post('/api/stream/mjpeg/detect', data);
}
// pause frame detect for a while (prevent a black screen when opening the page for the first time)
-export function stopFrameDetect() {
- return http.post('/api/stream/mjpeg/detect/stop');
+export function stopFrameDetect(duration: number) {
+ const data = {
+ duration
+ };
+ return http.post('/api/stream/mjpeg/detect/stop', data);
}
diff --git a/web/src/lib/localstorage.ts b/web/src/lib/localstorage.ts
index d59c011..07cbc1e 100644
--- a/web/src/lib/localstorage.ts
+++ b/web/src/lib/localstorage.ts
@@ -5,6 +5,8 @@ const VIDEO_MODE_KEY = 'nano-kvm-vide-mode';
const WEB_RESOLUTION_KEY = 'nano-kvm-web-resolution';
const FPS_KEY = 'nano-kvm-fps';
const QUALITY_KEY = 'nano-kvm-quality';
+const GOP_KEY = 'nano-kvm-gop';
+const FRAME_DETECT_KEY = 'nano-kvm-frame-detect';
const MOUSE_STYLE_KEY = 'nano-kvm-mouse-style';
const MOUSE_MODE_KEY = 'nano-kvm-mouse-mode';
const SKIP_UPDATE_KEY = 'nano-kvm-check-update';
@@ -92,6 +94,24 @@ export function setQuality(quality: number) {
localStorage.setItem(QUALITY_KEY, String(quality));
}
+export function getGop() {
+ const gop = localStorage.getItem(GOP_KEY);
+ return gop ? Number(gop) : null;
+}
+
+export function setGop(gop: number) {
+ localStorage.setItem(GOP_KEY, String(gop));
+}
+
+export function getFrameDetect(): boolean {
+ const enabled = localStorage.getItem(FRAME_DETECT_KEY);
+ return enabled ? Boolean(enabled) : true;
+}
+
+export function setFrameDetect(enabled: boolean) {
+ localStorage.setItem(FRAME_DETECT_KEY, String(enabled));
+}
+
export function getMouseStyle() {
return localStorage.getItem(MOUSE_STYLE_KEY);
}
diff --git a/web/src/pages/desktop/menu/screen/frame-detect.tsx b/web/src/pages/desktop/menu/screen/frame-detect.tsx
index a750e21..f640817 100644
--- a/web/src/pages/desktop/menu/screen/frame-detect.tsx
+++ b/web/src/pages/desktop/menu/screen/frame-detect.tsx
@@ -1,31 +1,39 @@
import { useEffect, useState } from 'react';
import { Tooltip } from 'antd';
+import clsx from 'clsx';
import { LoaderCircleIcon, Tally4Icon, Tally5Icon } from 'lucide-react';
import { useTranslation } from 'react-i18next';
-import { getFrameDetect, updateFrameDetect } from '@/api/stream.ts';
+import * as api from '@/api/stream.ts';
+import * as ls from '@/lib/localstorage.ts';
export const FrameDetect = () => {
const { t } = useTranslation();
- const [isEnabled, setIsEnabled] = useState(false);
+
const [isLoading, setIsLoading] = useState(false);
+ const [isEnabled, setIsEnabled] = useState(false);
useEffect(() => {
- getFrameDetect().then((rsp) => {
- if (rsp.code === 0) {
- setIsEnabled(rsp.data.enabled);
- }
- });
+ const enabled = ls.getFrameDetect();
+ if (enabled) {
+ setIsEnabled(true);
+ } else {
+ api.updateFrameDetect(false);
+ }
}, []);
function update() {
if (isLoading) return;
setIsLoading(true);
- updateFrameDetect()
+ const enabled = !isEnabled;
+
+ api
+ .updateFrameDetect(enabled)
.then((rsp) => {
if (rsp.code === 0) {
- setIsEnabled(rsp.data.enabled);
+ setIsEnabled(enabled);
+ ls.setFrameDetect(enabled);
}
})
.finally(() => {
@@ -41,18 +49,20 @@ export const FrameDetect = () => {
>
{isLoading ? (