Skip to content

Commit 8660f03

Browse files
authored
Add mjpeg server to screenshotr (danielpaulus#213)
start a simple mjpeg server by running `ios screenshot --stream` on 0.0.0.0:3333. If you provide the port argument like so: `ios screenshot --stream --port=8082` the server will run on your specified port. Then watch a slow stream in your browser. This is only a fun feature or to use in debugging if all else fails. Screenshotr is too slow to be really useable on Retina/High Res devices because it sends huge PNG files. Those reach memory bandwith limits on the device. Also it is too unstable to be used in production scenarios. For useable mjpeg streams, use WebDriverAgent's built in server. On older devices, this works quite well though :-D
1 parent ac87f10 commit 8660f03

File tree

2 files changed

+121
-2
lines changed

2 files changed

+121
-2
lines changed

ios/screenshotr/mjpeg_server.go

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

main.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ Usage:
6666
ios image mount [--path=<imagepath>] [options]
6767
ios image auto [--basedir=<where_dev_images_are_stored>] [options]
6868
ios syslog [options]
69-
ios screenshot [options] [--output=<outfile>]
69+
ios screenshot [options] [--output=<outfile>] [--stream] [--port=<port>]
7070
ios instruments notifications [options]
7171
ios crash ls [<pattern>] [options]
7272
ios crash cp <srcpattern> <target> [options]
@@ -132,7 +132,8 @@ The commands work as following:
132132
> You can specify a dir where images should be cached.
133133
> The default is the current dir.
134134
ios syslog [options] Prints a device's log output
135-
ios screenshot [options] [--output=<outfile>] Takes a screenshot and writes it to the current dir or to <outfile>
135+
ios screenshot [options] [--output=<outfile>] [--stream] [--port=<port>] Takes a screenshot and writes it to the current dir or to <outfile> If --stream is supplied it
136+
> starts an mjpeg server at 0.0.0.0:3333. Use --port to set another port.
136137
ios instruments notifications [options] Listen to application state notifications
137138
ios crash ls [<pattern>] [options] run "ios crash ls" to get all crashreports in a list,
138139
> or use a pattern like 'ios crash ls "*ips*"' to filter
@@ -377,7 +378,17 @@ The commands work as following:
377378

378379
b, _ = arguments.Bool("screenshot")
379380
if b {
381+
stream, _ := arguments.Bool("--stream")
382+
port, _ := arguments.String("--port")
380383
path, _ := arguments.String("--output")
384+
if stream {
385+
if port == "" {
386+
port = "3333"
387+
}
388+
err := screenshotr.StartStreamingServer(device, port)
389+
exitIfError("failed starting mjpeg", err)
390+
return
391+
}
381392
saveScreenshot(device, path)
382393
return
383394
}

0 commit comments

Comments
 (0)