Skip to content

Commit ab1e87d

Browse files
committed
Add option to start the recording directly.
Resolves #2.
1 parent 43fcbc6 commit ab1e87d

File tree

5 files changed

+95
-45
lines changed

5 files changed

+95
-45
lines changed

README.md

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,30 @@
11
# MJPEG Server
2+
23
MJPEG Server implements
34
[MJPEG over HTTP](https://en.wikipedia.org/wiki/Motion_JPEG#M-JPEG_over_HTTP)
4-
using [FFmpeg](https://ffmpeg.org/) or any other input source capable of
5-
piping a multipart JPEG stream to stdout.
5+
using [FFmpeg](https://ffmpeg.org/) or any other input source capable of piping
6+
a multipart JPEG stream to stdout.
67

78
Its primary use case is providing screen recordings for remote Webdriver tests,
89
e.g. for [blueimp/wdio](https://github.com/blueimp/wdio).
910

10-
It is resource efficient and only starts the screen recording process if there
11-
is at least one HTTP client connected to the server, stopping the recording when
12-
there are no more open client connections.
11+
It is resource efficient and by default only starts the screen recording process
12+
if there is at least one HTTP client connected to the server, stopping the
13+
recording when there are no more open client connections.
1314

1415
- [Installation](#installation)
1516
- [Usage](#usage)
16-
* [Options](#options)
17-
* [Screencast](#screencast)
18-
+ [Linux](#linux)
19-
+ [MacOS](#macos)
20-
+ [Windows](#windows)
17+
- [Options](#options)
18+
- [Screencast](#screencast)
19+
- [Linux](#linux)
20+
- [MacOS](#macos)
21+
- [Windows](#windows)
2122
- [License](#license)
2223
- [Attributions](#attributions)
2324
- [Author](#author)
2425

2526
## Installation
27+
2628
The MJPEG Server binary can be downloaded for Linux, MacOS and Windows from the
2729
[releases](https://github.com/blueimp/mjpeg-server/releases) page or built from
2830
source via [go get](https://golang.org/cmd/go/):
@@ -35,6 +37,7 @@ The screencast examples also require [FFmpeg](https://ffmpeg.org/) to be
3537
installed.
3638

3739
## Usage
40+
3841
By default, `mjpeg-server` listens on port `9000` on all interfaces and starts
3942
the given recording command when the first HTTP client connects:
4043

@@ -67,6 +70,7 @@ go run main.go -a 127.0.0.1:9000 -- go run mpjpeg/main.go -- gopher.jpg
6770
It simply streams the provided JPEG images in an endless loop.
6871

6972
### Options
73+
7074
Available MJPEG Server options can be listed the following way:
7175

7276
```sh
@@ -79,18 +83,24 @@ Usage of mjpeg-server:
7983
TCP listen address (default ":9000")
8084
-b string
8185
Multipart boundary (default "ffmpeg")
86+
-d Start command directly
8287
-p string
8388
URL path (default "/")
8489
-v Output version and exit
8590
```
8691

92+
The `-d` option starts the given recording command directly on initialization of
93+
the MJPEG server and keeps it running independently of the number of connected
94+
HTTP clients, until the MJPEG server process is stopped.
95+
8796
### Screencast
8897

8998
#### Linux
99+
90100
Start `mjpeg-server` using the
91-
[x11grab](https://www.ffmpeg.org/ffmpeg-devices.html#x11grab) device,
92-
selecting the X11 display via `-i :DISPLAY` and the matching screen resolution
93-
via `-video_size WIDTHxHEIGHT` option:
101+
[x11grab](https://www.ffmpeg.org/ffmpeg-devices.html#x11grab) device, selecting
102+
the X11 display via `-i :DISPLAY` and the matching screen resolution via
103+
`-video_size WIDTHxHEIGHT` option:
94104

95105
```sh
96106
mjpeg-server -- ffmpeg \
@@ -108,10 +118,12 @@ mjpeg-server -- ffmpeg \
108118
-
109119
```
110120

111-
#### MacOS
121+
#### MacOS
122+
112123
List the available
113-
[avfoundation](https://www.ffmpeg.org/ffmpeg-devices.html#avfoundation)
114-
input devices:
124+
[avfoundation](https://www.ffmpeg.org/ffmpeg-devices.html#avfoundation) input
125+
devices:
126+
115127
```sh
116128
ffmpeg -f avfoundation -list_devices true -i -
117129
```
@@ -136,9 +148,10 @@ mjpeg-server -- ffmpeg \
136148
```
137149

138150
#### Windows
151+
139152
On Windows, we can use the built-in
140-
[gdigrab](https://ffmpeg.org/ffmpeg-devices.html#gdigrab)
141-
input device to capture the whole desktop.
153+
[gdigrab](https://ffmpeg.org/ffmpeg-devices.html#gdigrab) input device to
154+
capture the whole desktop.
142155

143156
Start `MJPEGServer` using the following command in a Powershell console:
144157

@@ -158,9 +171,11 @@ MJPEGServer -- ffmpeg `
158171
```
159172

160173
## License
174+
161175
Released under the [MIT license](https://opensource.org/licenses/MIT).
162176

163177
## Attributions
178+
164179
The [Gopher](gopher.jpg) image used for the tests was designed by
165180
[Renee French](https://reneefrench.blogspot.com/).
166181
The design is licensed under the
@@ -169,4 +184,5 @@ The design is licensed under the
169184
![Gopher](gopher.jpg)
170185

171186
## Author
187+
172188
[Sebastian Tschan](https://blueimp.net/)

internal/registry/registry.go

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type logEntry struct {
2828
type registry struct {
2929
command string
3030
args []string
31+
directStart bool
3132
clients multi.MapWriter
3233
counter uint64
3334
stopRecording context.CancelFunc
@@ -54,6 +55,14 @@ func log(id string, registered bool, numClients int) {
5455
fmt.Println(string(b))
5556
}
5657

58+
func (t *registry) startRecording() {
59+
t.stopRecording, t.waitForStop = startRecording(
60+
t.command,
61+
t.args,
62+
t.clients,
63+
)
64+
}
65+
5766
// GenerateID returns an auto-incrementing ID.
5867
func (t *registry) GenerateID() string {
5968
return strconv.FormatUint(atomic.AddUint64(&t.counter, 1), 10)
@@ -63,13 +72,9 @@ func (t *registry) GenerateID() string {
6372
// It returns the new number of clients in the Registry.
6473
func (t *registry) Add(id string, w io.Writer) (num int) {
6574
num = t.clients.Add(w)
66-
if num == 1 {
75+
if num == 1 && !t.directStart {
6776
// First client added, start the recording.
68-
t.stopRecording, t.waitForStop = startRecording(
69-
t.command,
70-
t.args,
71-
t.clients,
72-
)
77+
t.startRecording()
7378
}
7479
log(id, true, num)
7580
return
@@ -79,7 +84,7 @@ func (t *registry) Add(id string, w io.Writer) (num int) {
7984
// It returns the new number of clients in the Registry.
8085
func (t *registry) Remove(id string, w io.Writer) (num int) {
8186
num = t.clients.Remove(w)
82-
if num == 0 {
87+
if num == 0 && !t.directStart {
8388
// Last client removed, stop the recording.
8489
t.stopRecording()
8590
}
@@ -88,13 +93,18 @@ func (t *registry) Remove(id string, w io.Writer) (num int) {
8893
}
8994

9095
// New creates a new Registry.
91-
func New(command string, args []string) Registry {
92-
return &registry{
96+
func New(command string, args []string, directStart bool) Registry {
97+
reg := &registry{
9398
command,
9499
args,
100+
directStart,
95101
multi.NewMapWriter(),
96102
0,
97103
nil,
98104
nil,
99105
}
106+
if directStart {
107+
reg.startRecording()
108+
}
109+
return reg
100110
}

internal/registry/registry_test.go

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ func outputHelper(fn func()) (stdout []byte, stderr []byte) {
4646
}
4747

4848
func TestNew(t *testing.T) {
49-
reg := New("go", []string{"version"})
49+
reg := New("go", []string{"version"}, false)
5050
if reg == nil {
5151
t.Error("Unexpected: nil")
5252
}
@@ -57,7 +57,7 @@ func TestNew(t *testing.T) {
5757
}
5858

5959
func TestGenerateID(t *testing.T) {
60-
reg := New("go", []string{"version"})
60+
reg := New("go", []string{"version"}, false)
6161
id := reg.GenerateID()
6262
if id != "1" {
6363
t.Errorf("Unexpected generated ID: %s. Expected: %s", id, "1")
@@ -76,7 +76,7 @@ func TestAdd(t *testing.T) {
7676
started = 0
7777
stopped = 0
7878
startRecording = startRecordingHelper
79-
reg := New("go", []string{"version"})
79+
reg := New("go", []string{"version"}, false)
8080
if started != 0 {
8181
t.Errorf("Unexpected started recordings: %d. Expected: %d", started, 0)
8282
}
@@ -150,7 +150,7 @@ func TestRemove(t *testing.T) {
150150
started = 0
151151
stopped = 0
152152
startRecording = startRecordingHelper
153-
reg := New("go", []string{"version"})
153+
reg := New("go", []string{"version"}, false)
154154
var (
155155
buffer1 bytes.Buffer
156156
buffer2 bytes.Buffer
@@ -227,3 +227,26 @@ func TestRemove(t *testing.T) {
227227
)
228228
}
229229
}
230+
231+
func TestNewWithDirectStart(t *testing.T) {
232+
started = 0
233+
stopped = 0
234+
startRecording = startRecordingHelper
235+
reg := New("go", []string{"version"}, true)
236+
if started != 1 {
237+
t.Errorf("Unexpected started recordings: %d. Expected: %d", started, 1)
238+
}
239+
var buffer1 bytes.Buffer
240+
outputHelper(func() {
241+
reg.Add("1", &buffer1)
242+
})
243+
if started != 1 {
244+
t.Errorf("Unexpected started recordings: %d. Expected: %d", started, 1)
245+
}
246+
outputHelper(func() {
247+
reg.Remove("1", &buffer1)
248+
})
249+
if stopped != 0 {
250+
t.Errorf("Unexpected stopped recordings: %d. Expected: %d", stopped, 0)
251+
}
252+
}

main.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ import (
1414
var (
1515
// Version provides the program version information.
1616
// It is provided at build time via -ldflags="-X main.Version=VERSION".
17-
Version = "dev"
18-
showVersion = flag.Bool("v", false, "Output version and exit")
19-
addr = flag.String("a", ":9000", "TCP listen address")
20-
urlPath = flag.String("p", "/", "URL path")
21-
boundary = flag.String("b", "ffmpeg", "Multipart boundary")
22-
command string
23-
args []string
24-
reg registry.Registry
17+
Version = "dev"
18+
showVersion = flag.Bool("v", false, "Output version and exit")
19+
directStart = flag.Bool("d", false, "Start command directly")
20+
addr = flag.String("a", ":9000", "TCP listen address")
21+
urlPath = flag.String("p", "/", "URL path")
22+
boundary = flag.String("b", "ffmpeg", "Multipart boundary")
23+
command string
24+
args []string
25+
reg registry.Registry
2526
)
2627

2728
func setHeaders(header http.Header) {
@@ -76,6 +77,6 @@ func main() {
7677
fmt.Println(Version)
7778
os.Exit(0)
7879
}
79-
reg = registry.New(command, args)
80+
reg = registry.New(command, args, *directStart)
8081
log.Fatalln(http.ListenAndServe(*addr, http.HandlerFunc(requestHandler)))
8182
}

main_internal_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
)
1212

1313
func TestRequestHandler(t *testing.T) {
14-
reg = registry.New(command, args)
14+
reg = registry.New(command, args, false)
1515
rec := httptest.NewRecorder()
1616
ctx, cancel := context.WithCancel(context.Background())
1717
req := httptest.NewRequest(
@@ -61,7 +61,7 @@ func TestRequestHandler(t *testing.T) {
6161
}
6262

6363
func TestRequestHandlerWithInvalidMethod(t *testing.T) {
64-
reg = registry.New(command, args)
64+
reg = registry.New(command, args, false)
6565
rec := httptest.NewRecorder()
6666
req := httptest.NewRequest(
6767
"POST",
@@ -79,7 +79,7 @@ func TestRequestHandlerWithInvalidMethod(t *testing.T) {
7979
}
8080

8181
func TestRequestHandlerWithInvalidPath(t *testing.T) {
82-
reg = registry.New(command, args)
82+
reg = registry.New(command, args, false)
8383
rec := httptest.NewRecorder()
8484
req := httptest.NewRequest(
8585
"GET",
@@ -97,7 +97,7 @@ func TestRequestHandlerWithInvalidPath(t *testing.T) {
9797
}
9898

9999
func TestRequestHandlerWithCustomPath(t *testing.T) {
100-
reg = registry.New(command, args)
100+
reg = registry.New(command, args, false)
101101
*urlPath = "/banana"
102102
rec := httptest.NewRecorder()
103103
ctx, cancel := context.WithCancel(context.Background())
@@ -136,7 +136,7 @@ func TestRequestHandlerWithCustomPath(t *testing.T) {
136136
}
137137

138138
func TestRequestHandlerWithCustomBoundary(t *testing.T) {
139-
reg = registry.New(command, args)
139+
reg = registry.New(command, args, false)
140140
*boundary = "banana"
141141
rec := httptest.NewRecorder()
142142
ctx, cancel := context.WithCancel(context.Background())

0 commit comments

Comments
 (0)