mirror of
https://github.com/gen2brain/cam2ip.git
synced 2026-07-02 21:28:09 +00:00
Add --lazy to open the camera only while clients are connected, issue #21
This commit is contained in:
@@ -65,6 +65,8 @@ Usage: cam2ip [<flags>]
|
||||
Bind address [CAM2IP_BIND_ADDR] (default ":56000")
|
||||
--htpasswd-file
|
||||
Path to htpasswd file, if empty auth is disabled [CAM2IP_HTPASSWD_FILE] (default "")
|
||||
--lazy
|
||||
Open the camera only while clients are connected [CAM2IP_LAZY] (default "false")
|
||||
--list-devices
|
||||
List available cameras and exit (default "false")
|
||||
--version
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"go.senan.xyz/flagconf"
|
||||
|
||||
"github.com/gen2brain/cam2ip/camera"
|
||||
"github.com/gen2brain/cam2ip/handlers"
|
||||
"github.com/gen2brain/cam2ip/server"
|
||||
)
|
||||
|
||||
@@ -57,6 +58,7 @@ func main() {
|
||||
flag.StringVar(&srv.TimeFormat, "time-format", "2006-01-02 15:04:05", "Time format [CAM2IP_TIME_FORMAT]")
|
||||
flag.StringVar(&srv.Bind, "bind-addr", ":56000", "Bind address [CAM2IP_BIND_ADDR]")
|
||||
flag.StringVar(&srv.Htpasswd, "htpasswd-file", "", "Path to htpasswd file, if empty auth is disabled [CAM2IP_HTPASSWD_FILE]")
|
||||
flag.BoolVar(&srv.Lazy, "lazy", false, "Open the camera only while clients are connected [CAM2IP_LAZY]")
|
||||
|
||||
var showVersion bool
|
||||
flag.BoolVar(&showVersion, "version", false, "Print version and exit")
|
||||
@@ -69,7 +71,7 @@ func main() {
|
||||
|
||||
stderr("%s %s [<flags>]\n", colorize(color, colorBold, "Usage:"), name)
|
||||
order := []string{"index", "device", "delay", "width", "height", "quality", "rotate", "flip", "no-webgl",
|
||||
"timestamp", "time-format", "bind-addr", "htpasswd-file", "list-devices", "version"}
|
||||
"timestamp", "time-format", "bind-addr", "htpasswd-file", "lazy", "list-devices", "version"}
|
||||
|
||||
for _, name := range order {
|
||||
f := flag.Lookup(name)
|
||||
@@ -121,7 +123,7 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
cam, err := camera.New(camera.Options{
|
||||
opts := camera.Options{
|
||||
Index: srv.Index,
|
||||
Rotate: srv.Rotate,
|
||||
Flip: srv.Flip,
|
||||
@@ -129,26 +131,37 @@ func main() {
|
||||
Height: srv.Height,
|
||||
Timestamp: srv.Timestamp,
|
||||
TimeFormat: srv.TimeFormat,
|
||||
})
|
||||
if err != nil {
|
||||
stderr("%s\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
srv.Reader = cam
|
||||
|
||||
defer srv.Reader.Close()
|
||||
|
||||
info := cam.Info()
|
||||
desc := fmt.Sprintf("%dx%d %s", info.Width, info.Height, info.Format)
|
||||
if dn := deviceName(srv.Index); dn != "" {
|
||||
desc = dn + ", " + desc
|
||||
srv.Open = func() (handlers.ImageReader, error) {
|
||||
return camera.New(opts)
|
||||
}
|
||||
|
||||
stderr("%s %s [%s] listening on %s\n", name, version, desc, srv.Bind)
|
||||
if srv.Lazy {
|
||||
stderr("%s %s [lazy] listening on %s\n", name, version, srv.Bind)
|
||||
} else {
|
||||
cam, err := camera.New(opts)
|
||||
if err != nil {
|
||||
stderr("%s\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = srv.ListenAndServe()
|
||||
if err != nil {
|
||||
defer cam.Close()
|
||||
|
||||
srv.Open = func() (handlers.ImageReader, error) {
|
||||
return cam, nil
|
||||
}
|
||||
|
||||
info := cam.Info()
|
||||
desc := fmt.Sprintf("%dx%d %s", info.Width, info.Height, info.Format)
|
||||
if dn := deviceName(srv.Index); dn != "" {
|
||||
desc = dn + ", " + desc
|
||||
}
|
||||
|
||||
stderr("%s %s [%s] listening on %s\n", name, version, desc, srv.Bind)
|
||||
}
|
||||
|
||||
if err := srv.ListenAndServe(); err != nil {
|
||||
stderr("%s\n", err.Error())
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -13,25 +13,33 @@ const errorBackoff = 100 * time.Millisecond
|
||||
|
||||
// Stream captures frames in a single loop and broadcasts the encoded JPEG to all subscribers.
|
||||
type Stream struct {
|
||||
reader ImageReader
|
||||
open func() (ImageReader, error)
|
||||
lazy bool
|
||||
delay int
|
||||
quality int
|
||||
|
||||
reader ImageReader
|
||||
|
||||
mu sync.Mutex
|
||||
cond *sync.Cond
|
||||
subs map[chan []byte]struct{}
|
||||
}
|
||||
|
||||
// NewStream returns a new Stream and starts its capture loop.
|
||||
func NewStream(reader ImageReader, delay, quality int) *Stream {
|
||||
// NewStream returns a new Stream and starts its capture loop. open acquires the camera: once up front, or while clients are connected when lazy.
|
||||
func NewStream(open func() (ImageReader, error), delay, quality int, lazy bool) *Stream {
|
||||
s := &Stream{
|
||||
reader: reader,
|
||||
open: open,
|
||||
lazy: lazy,
|
||||
delay: delay,
|
||||
quality: quality,
|
||||
subs: make(map[chan []byte]struct{}),
|
||||
}
|
||||
s.cond = sync.NewCond(&s.mu)
|
||||
|
||||
if !lazy {
|
||||
s.reader, _ = open()
|
||||
}
|
||||
|
||||
go s.capture()
|
||||
|
||||
return s
|
||||
@@ -42,10 +50,27 @@ func (s *Stream) capture() {
|
||||
for {
|
||||
s.mu.Lock()
|
||||
for len(s.subs) == 0 {
|
||||
if s.lazy && s.reader != nil {
|
||||
s.reader.Close()
|
||||
s.reader = nil
|
||||
}
|
||||
|
||||
s.cond.Wait()
|
||||
}
|
||||
s.mu.Unlock()
|
||||
|
||||
if s.reader == nil {
|
||||
reader, err := s.open()
|
||||
if err != nil {
|
||||
log.Printf("stream: open: %v", err)
|
||||
time.Sleep(errorBackoff)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
s.reader = reader
|
||||
}
|
||||
|
||||
img, err := s.reader.Read()
|
||||
if err != nil {
|
||||
log.Printf("stream: read: %v", err)
|
||||
|
||||
@@ -36,7 +36,8 @@ type Server struct {
|
||||
Bind string
|
||||
Htpasswd string
|
||||
|
||||
Reader handlers.ImageReader
|
||||
Lazy bool
|
||||
Open func() (handlers.ImageReader, error)
|
||||
}
|
||||
|
||||
// NewServer returns new Server.
|
||||
@@ -54,7 +55,7 @@ func (s *Server) ListenAndServe() error {
|
||||
basic = auth.NewBasicAuthenticator(realm, auth.HtpasswdFileProvider(s.Htpasswd))
|
||||
}
|
||||
|
||||
stream := handlers.NewStream(s.Reader, s.Delay, s.Quality)
|
||||
stream := handlers.NewStream(s.Open, s.Delay, s.Quality, s.Lazy)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user