/*
* Copyright 2013-2016 Cel Skeggs, 2013 Andrew Merrill.
*
* This file is part of the CCRE, the Common Chicken Runtime Engine.
*
* The CCRE is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option) any
* later version.
*
* The CCRE is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the CCRE. If not, see <http://www.gnu.org/licenses/>.
*/
package ccre.viewer;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.EOFException;
import java.io.IOException;
import javax.imageio.ImageIO;
import ccre.net.ClientSocket;
import ccre.net.Network;
class WebcamReader implements Closeable {
// @formatter:off
private static final String requestString =
"GET /mjpg/video.mjpg HTTP/1.1\n" +
"User-Agent: HTTPStreamClient\n" +
"Connection: Keep-Alive\n" +
"Cache-Control: no-cache\n" +
"Authorization: Basic RlJDOkZSQw==\n\n";
// @formatter:on
private final ClientSocket socket;
private final BufferedInputStream source;
private final BufferedOutputStream sink;
private final ByteArrayOutputStream lineBuf = new ByteArrayOutputStream();
private final String boundary;
public WebcamReader(String address, int timeoutMillis) throws IOException {
this.socket = Network.connectDynPort(address, 80);
try {
socket.setSocketTimeout(timeoutMillis);
source = new BufferedInputStream(socket.openInputStream());
sink = new BufferedOutputStream(socket.openOutputStream());
sink.write(requestString.getBytes("UTF-8"));
sink.flush();
assertLine("HTTP/1.0 200 OK");
this.boundary = readHeaderFromHeaders("Content-Type").split("boundary=", 2)[1];
} catch (Throwable thr) {
try {
this.socket.close();
} catch (Throwable thr2) {
thr.addSuppressed(thr2);
}
throw thr;
}
}
private String readHeaderFromHeaders(String headerName) throws IOException {
headerName = headerName + ": ";
String value = null;
while (true) {
String line = readLine();
if (line.isEmpty()) {
break;
}
if (line.startsWith(headerName)) {
if (value != null) {
throw new IOException("Multiple instances of header: " + headerName);
}
value = line.substring(headerName.length());
}
}
if (value == null) {
throw new IOException("Cannot find header: " + headerName);
}
return value;
}
private String readLine() throws IOException, EOFException {
lineBuf.reset();
int last = -1;
while (true) {
int b = source.read();
if (b == -1) {
throw new EOFException();
}
if (b == '\n' && last == '\r') {
byte[] array = lineBuf.toByteArray();
return new String(array, 0, array.length - 1, "UTF-8");
}
lineBuf.write(b);
last = b;
}
}
private void assertLine(String string) throws IOException {
String other = readLine();
if (!string.equals(other)) {
throw new IOException("Line mismatch: expected " + string + " but got " + other);
}
}
private byte[] bytes;
private byte[] getByteArray(int size) {
if (bytes == null || bytes.length < size) {
bytes = new byte[size + (size >> 1)];
}
return bytes;
}
public BufferedImage readNext() throws IOException {
readThroughBoundary();
String contentLengthString = readHeaderFromHeaders("Content-Length");
int contentLength;
try {
contentLength = Integer.parseInt(contentLengthString);
} catch (NumberFormatException nfe) {
throw new IOException("Invalid content length string: |" + contentLengthString + "|");
}
if (contentLength < 1 || contentLength > 200000) {
throw new IOException("Invalid content length: " + contentLength);
}
byte[] bytes = getByteArray(contentLength);
readExact(bytes, contentLength);
return ImageIO.read(new ByteArrayInputStream(bytes, 0, contentLength));
}
private void readExact(byte[] out, int bytes) throws IOException {
int index = 0;
while (index < out.length) {
int read = source.read(out, index, out.length - index);
if (read == -1) {
throw new EOFException();
}
index += read;
}
}
private void readThroughBoundary() throws IOException {
String header;
do {
header = readLine();
} while (header.isEmpty());
if (!header.endsWith(boundary)) {
throw new IOException("Boundary not found in: " + header);
}
}
@Override
public void close() throws IOException {
this.socket.close();
}
}