exp.upspin.io@v0.0.0-20230625230448-5076e5b595ec/cmd/camclient/main.go (about) 1 // Copyright 2017 The Upspin Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Command camclient serves a video stream generated by 6 // exp.upspin.io/cmd/camserver over HTTP as a Motion JPEG stream. 7 package main // import "exp.upspin.io/cmd/camclient" 8 9 import ( 10 "flag" 11 "fmt" 12 "mime/multipart" 13 "net/http" 14 "net/textproto" 15 "os" 16 "sync" 17 "time" 18 19 "upspin.io/client" 20 "upspin.io/client/clientutil" 21 "upspin.io/config" 22 "upspin.io/flags" 23 "upspin.io/log" 24 "upspin.io/transports" 25 "upspin.io/upspin" 26 ) 27 28 func main() { 29 flag.Usage = func() { 30 fmt.Fprintln(os.Stderr, "usage: camclient [flags] <Upspin path>") 31 flag.PrintDefaults() 32 } 33 34 flags.Parse(flags.Client, "http") 35 36 if flag.NArg() != 1 { 37 fmt.Fprintln(os.Stderr, "camclient: need exactly one path name argument") 38 flag.Usage() 39 os.Exit(2) 40 } 41 name := upspin.PathName(flag.Arg(0)) 42 43 cfg, err := config.FromFile(flags.Config) 44 if err != nil { 45 log.Fatal(err) 46 } 47 transports.Init(cfg) 48 49 h, err := newHandler(cfg, name) 50 if err != nil { 51 log.Fatal(err) 52 } 53 http.Handle("/", h) 54 log.Fatal(http.ListenAndServe(flags.HTTPAddr, nil)) 55 } 56 57 // handler is an http.Handler that serves a Motion JPEG of a camserver stream. 58 type handler struct { 59 mu sync.Mutex 60 update *sync.Cond 61 frame []byte 62 } 63 64 // newHandler initializes a handler that streams the provided camserver path 65 // with the given config. The handler only watches and fetches each frame once 66 // regardless of the number of concurrent viewers. 67 func newHandler(cfg upspin.Config, name upspin.PathName) (http.Handler, error) { 68 h := &handler{} 69 h.update = sync.NewCond(&h.mu) 70 71 c := client.New(cfg) 72 dir, err := c.DirServer(name) 73 if err != nil { 74 return nil, err 75 } 76 entries := make(chan *upspin.DirEntry) 77 go func() { 78 for e := range entries { 79 // Read the latest frame. 80 frame, err := clientutil.ReadAll(cfg, e) 81 if err != nil { 82 log.Error.Print(err) 83 continue 84 } 85 // Share it with viewers. 86 h.mu.Lock() 87 h.frame = frame 88 h.mu.Unlock() 89 h.update.Broadcast() 90 } 91 }() 92 go func() { 93 var ( 94 fetched, skipped int 95 lastUpdate = time.Now() 96 done chan struct{} 97 ) 98 for { 99 if done != nil { 100 close(done) 101 } 102 done = make(chan struct{}) 103 events, err := dir.Watch(name, 0, done) 104 if err != nil { 105 log.Error.Print(err) 106 time.Sleep(5 * time.Second) 107 continue 108 } 109 for e := range events { 110 if e.Error != nil { 111 log.Error.Print(e.Error) 112 break 113 } 114 // Do a non-blocking send here so that we skip 115 // this frame if we're still fetching an old 116 // frame, to prevent us from failling behind. 117 select { 118 case entries <- e.Entry: 119 log.Debug.Printf("fetching frame %d", e.Entry.Sequence) 120 fetched++ 121 default: 122 log.Debug.Printf("skipped frame %d", e.Entry.Sequence) 123 skipped++ 124 } 125 if d := time.Since(lastUpdate); d > 10*time.Second { 126 sec := float64(d) / float64(time.Second) 127 fps := float64(fetched) / sec 128 sps := float64(skipped) / sec 129 log.Info.Printf("frames per second: %.3g fetched, %.3g skipped", fps, sps) 130 fetched, skipped, lastUpdate = 0, 0, time.Now() 131 } 132 } 133 } 134 }() 135 136 return h, nil 137 } 138 139 func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 140 // Motion JPEG is a mulitpart MIME-encoded series of JPEG images. 141 mw := multipart.NewWriter(w) 142 w.Header().Set("Content-Type", "multipart/x-mixed-replace;boundary="+mw.Boundary()) 143 partHeader := textproto.MIMEHeader{"Content-Type": {"image/jpeg"}} 144 for { 145 // Wait for a new frame to become available. 146 h.mu.Lock() 147 h.update.Wait() 148 frame := h.frame 149 h.mu.Unlock() 150 // Write that frame as a new MIME part. 151 w, err := mw.CreatePart(partHeader) 152 if err != nil { 153 log.Println(err) 154 return 155 } 156 _, err = w.Write(frame) 157 if err != nil { 158 log.Println(err) 159 return 160 } 161 } 162 }