-
-
Notifications
You must be signed in to change notification settings - Fork 181
/
mjpeg_server.go
108 lines (101 loc) · 2.61 KB
/
mjpeg_server.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package screenshotr
import (
"bufio"
"bytes"
"fmt"
"github.com/danielpaulus/go-ios/ios"
log "github.com/sirupsen/logrus"
"image/jpeg"
"image/png"
"io"
"net/http"
"sync"
"time"
)
var consumers sync.Map
var conversionQueue = make(chan []byte, 20)
func StartStreamingServer(device ios.DeviceEntry, port string) error {
conn, err := New(device)
if err != nil {
return err
}
go startScreenshotting(conn)
go startConversionQueue()
http.HandleFunc("/", mjpegHandler)
location := fmt.Sprintf("0.0.0.0:%s", port)
log.WithFields(log.Fields{"host": "0.0.0.0", "port": port}).Infof("starting server, open your browser here: http://%s/", location)
return http.ListenAndServe(location, nil)
}
func startConversionQueue() {
var opt jpeg.Options
opt.Quality = 80
for {
pngBytes := <-conversionQueue
start := time.Now()
img, err := png.Decode(bytes.NewReader(pngBytes))
if err != nil {
log.Warnf("failed decoding png %v", err)
continue
}
var b bytes.Buffer
foo := bufio.NewWriter(&b)
err = jpeg.Encode(foo, img, &opt)
if err != nil {
log.Warnf("failed encoding jpg %v", err)
continue
}
elapsed := time.Since(start)
log.Debugf("conversion took %fs", elapsed.Seconds())
consumers.Range(func(key, value interface{}) bool {
c := value.(chan []byte)
go func() { c <- b.Bytes() }()
return true
})
}
}
func startScreenshotting(conn *Connection) {
for {
start := time.Now()
pngBytes, err := conn.TakeScreenshot()
if err != nil {
log.Fatal("screenshotr failed", err)
}
elapsed := time.Since(start)
log.Debugf("shot took %fs", elapsed.Seconds())
conversionQueue <- pngBytes
}
}
const mjpegFrameFooter = "\r\n\r\n"
const mjpegFrameHeader = "--BoundaryString\r\nContent-type: image/jpg\r\nContent-Length: %d\r\n\r\n"
func mjpegHandler(w http.ResponseWriter, r *http.Request) {
log.Infof("starting mjpeg stream for new client")
c := make(chan []byte)
consumers.Store(r, c)
w.Header().Add("Server", "go-ios-screenshotr-mjpeg-stream")
w.Header().Add("Connection", "Close")
w.Header().Add("Content-Type", "multipart/x-mixed-replace; boundary=--BoundaryString")
w.Header().Add("Max-Age", "0")
w.Header().Add("Expires", "0")
w.Header().Add("Cache-Control", "no-cache, private")
w.Header().Add("Pragma", "no-cache")
//io.WriteString(w, mjpegStreamHeader)
w.WriteHeader(200)
for {
jpg := <-c
_, err := io.WriteString(w, fmt.Sprintf(mjpegFrameHeader, len(jpg)))
if err != nil {
break
}
_, err = w.Write(jpg)
if err != nil {
break
}
_, err = io.WriteString(w, mjpegFrameFooter)
if err != nil {
break
}
}
consumers.Delete(r)
close(c)
log.Info("client disconnected")
}