001    package com.github.sarxos.webcam;
002    
003    import java.awt.image.BufferedImage;
004    import java.io.BufferedOutputStream;
005    import java.io.BufferedReader;
006    import java.io.ByteArrayOutputStream;
007    import java.io.IOException;
008    import java.io.InputStreamReader;
009    import java.net.ServerSocket;
010    import java.net.Socket;
011    import java.net.SocketException;
012    import java.util.concurrent.Executor;
013    import java.util.concurrent.Executors;
014    import java.util.concurrent.ThreadFactory;
015    import java.util.concurrent.atomic.AtomicBoolean;
016    
017    import javax.imageio.ImageIO;
018    
019    import org.slf4j.Logger;
020    import org.slf4j.LoggerFactory;
021    
022    
023    public class WebcamStreamer implements ThreadFactory, WebcamListener {
024    
025            private static final Logger LOG = LoggerFactory.getLogger(WebcamStreamer.class);
026    
027            private static final String BOUNDARY = "mjpegframe";
028    
029            private class Acceptor implements Runnable {
030    
031                    @Override
032                    public void run() {
033                            try {
034                                    ServerSocket server = new ServerSocket(port);
035                                    while (true) {
036                                            executor.execute(new Connection(server.accept()));
037                                    }
038                            } catch (Exception e) {
039                                    LOG.error("Cannot accept socket connection", e);
040                            }
041                    }
042            }
043    
044            private class Reader implements Runnable {
045    
046                    @Override
047                    public void run() {
048                            while (webcam.isOpen()) {
049    
050                                    image = webcam.getImage();
051    
052                                    if (image != null) {
053                                            synchronized (lock) {
054                                                    lock.notifyAll();
055                                            }
056                                    }
057    
058                                    try {
059                                            Thread.sleep(getDelay());
060                                    } catch (InterruptedException e) {
061                                            LOG.error("Nasty interrupted exception", e);
062                                    }
063                            }
064                    }
065            }
066    
067            private class Connection implements Runnable {
068    
069                    private static final String CRLF = "\r\n";
070    
071                    private Socket socket = null;
072    
073                    public Connection(Socket socket) {
074                            this.socket = socket;
075                    }
076    
077                    @Override
078                    public void run() {
079    
080                            BufferedReader br = null;
081                            BufferedOutputStream bos = null;
082                            ByteArrayOutputStream baos = new ByteArrayOutputStream();
083    
084                            try {
085    
086                                    br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
087                                    bos = new BufferedOutputStream(socket.getOutputStream());
088    
089                                    try {
090    
091                                            while (webcam.isOpen()) {
092    
093                                                    if (socket.isInputShutdown()) {
094                                                            break;
095                                                    }
096                                                    if (socket.isClosed()) {
097                                                            break;
098                                                    }
099    
100                                                    String line = br.readLine();
101                                                    if (line == null || line.isEmpty()) {
102                                                            bos.write(("--" + BOUNDARY + "--" + CRLF).getBytes());
103                                                            LOG.info("Breaking");
104                                                            break;
105                                                    }
106    
107                                                    if (line.startsWith("GET")) {
108    
109                                                            socket.setSoTimeout(0);
110                                                            socket.setKeepAlive(true);
111    
112                                                            getImage();
113    
114                                                            StringBuilder sb = new StringBuilder();
115                                                            sb.append("HTTP/1.0 200 OK").append(CRLF);
116                                                            sb.append("Connection: keep-alive").append(CRLF);
117                                                            sb.append("Cache-Control: no-cache").append(CRLF);
118                                                            sb.append("Cache-Control: private").append(CRLF);
119                                                            sb.append("Pragma: no-cache").append(CRLF);
120                                                            sb.append("Content-type: multipart/x-mixed-replace; boundary=--").append(BOUNDARY).append(CRLF);
121                                                            sb.append(CRLF);
122    
123                                                            bos.write(sb.toString().getBytes());
124    
125                                                            do {
126    
127                                                                    if (socket.isInputShutdown()) {
128                                                                            break;
129                                                                    }
130                                                                    if (socket.isClosed()) {
131                                                                            break;
132                                                                    }
133    
134                                                                    baos.reset();
135    
136                                                                    ImageIO.write(getImage(), "JPG", baos);
137    
138                                                                    sb.delete(0, sb.length());
139                                                                    sb.append("--").append(BOUNDARY).append(CRLF);
140                                                                    sb.append("Content-type: image/jpeg").append(CRLF);
141                                                                    sb.append("Content-Length: ").append(baos.size()).append(CRLF);
142                                                                    sb.append(CRLF);
143    
144                                                                    bos.write(sb.toString().getBytes());
145                                                                    bos.write(baos.toByteArray());
146                                                                    bos.write(CRLF.getBytes());
147    
148                                                                    try {
149                                                                            bos.flush();
150                                                                    } catch (SocketException e) {
151                                                                            if (LOG.isTraceEnabled()) {
152                                                                                    LOG.error("Socket exception", e);
153                                                                            }
154                                                                    }
155    
156                                                                    Thread.sleep(getDelay());
157    
158                                                            } while (webcam.isOpen());
159                                                    }
160                                            }
161                                    } catch (Exception e) {
162    
163                                            String message = e.getMessage();
164                                            if (message != null && message.startsWith("Software caused connection abort")) {
165                                                    LOG.info("User closed stream");
166                                                    return;
167                                            }
168    
169                                            LOG.error("Error", e);
170    
171                                            bos.write("HTTP/1.0 501 Internal Server Error\r\n\r\n\r\n".getBytes());
172                                    }
173    
174                            } catch (IOException e) {
175                                    LOG.error("I/O exception", e);
176                            } finally {
177                                    if (br != null) {
178                                            try {
179                                                    br.close();
180                                            } catch (IOException e) {
181                                                    LOG.error("Cannot close buffered reader", e);
182                                            }
183                                    }
184                                    if (bos != null) {
185                                            try {
186                                                    bos.close();
187                                            } catch (IOException e) {
188                                                    LOG.error("Cannot close data output stream", e);
189                                            }
190                                    }
191                            }
192                    }
193            }
194    
195            private Webcam webcam = null;
196            private double fps = 0;
197            private BufferedImage image = null;
198            private Object lock = new Object();
199            private int number = 0;
200            private int port = 0;
201            private Executor executor = Executors.newCachedThreadPool(this);
202            private AtomicBoolean open = new AtomicBoolean(false);
203            private AtomicBoolean initialized = new AtomicBoolean(false);
204    
205            public WebcamStreamer(int port, Webcam webcam, double fps, boolean start) {
206    
207                    if (webcam == null) {
208                            throw new IllegalArgumentException("Webcam for streaming cannot be null");
209                    }
210    
211                    this.port = port;
212                    this.webcam = webcam;
213                    this.fps = fps;
214    
215                    if (start) {
216                            start();
217                    }
218            }
219    
220            private long getDelay() {
221                    return (long) (1000 / fps);
222            }
223    
224            private BufferedImage getImage() {
225                    if (image == null) {
226                            synchronized (lock) {
227                                    try {
228                                            lock.wait();
229                                    } catch (InterruptedException e) {
230                                            LOG.error("Nasty interrupted exception", e);
231                                    }
232                            }
233                    }
234                    return image;
235            }
236    
237            public void start() {
238                    if (open.compareAndSet(false, true)) {
239                            webcam.addWebcamListener(this);
240                            webcam.open();
241                    }
242            }
243    
244            @Override
245            public Thread newThread(Runnable r) {
246                    Thread thread = new Thread(r, String.format("streamer-thread-%s", number++));
247                    thread.setDaemon(true);
248                    return thread;
249            }
250    
251            @Override
252            public void webcamOpen(WebcamEvent we) {
253                    if (initialized.compareAndSet(false, true)) {
254                            executor.execute(new Acceptor());
255                            executor.execute(new Reader());
256                    }
257            }
258    
259            @Override
260            public void webcamClosed(WebcamEvent we) {
261                    // TODO: shutdown executor?
262            }
263    
264            @Override
265            public void webcamDisposed(WebcamEvent we) {
266            }
267    
268            @Override
269            public void webcamImageObtained(WebcamEvent we) {
270            }
271    
272            public static void main(String[] args) throws InterruptedException {
273                    new WebcamStreamer(8081, Webcam.getDefault(), 0.5, true);
274                    do {
275                            Thread.sleep(1000);
276                    } while (true);
277            }
278    }