4 package it
.lilik
.capturemjpeg
;
6 import it
.lilik
.capturemjpeg
.utils
.CircularBuffer
;
8 import java
.io
.BufferedInputStream
;
9 import java
.io
.ByteArrayInputStream
;
10 import java
.io
.ByteArrayOutputStream
;
11 import java
.io
.IOException
;
12 import java
.io
.InputStream
;
14 import org
.apache
.commons
.httpclient
.Header
;
15 import org
.apache
.commons
.httpclient
.HttpClient
;
16 import org
.apache
.commons
.httpclient
.HttpException
;
17 import org
.apache
.commons
.httpclient
.HttpMethod
;
18 import org
.apache
.commons
.httpclient
.UsernamePasswordCredentials
;
19 import org
.apache
.commons
.httpclient
.auth
.AuthScope
;
22 * This class produces JPEG images in <code>ByteArrayInputStream</code>
23 * getting them from a MJPEG stream.
25 * @author Alessio Caiazza
26 * @author Cosimo Cecchi
29 class AsyncProducer
extends Thread
{
31 /** timeout for HTTP request */
32 protected static final int HTTP_TIMEOUT
= 5000;
33 private static final int BYTES_TO_READ
= 128;
35 protected HttpClient client
;
37 protected HttpMethod method
;
38 /** used for stopping this thread */
39 protected boolean shouldStop
;
40 /** <code>true</code> if there are pending changes in <code>method</code> */
41 protected boolean isChangePending
;
42 /** circular buffer for images */
43 protected CircularBuffer buffer
;
48 * @return the shouldStop
50 public boolean getShouldStop() {
55 * It stops this thread when the current image if finished
56 * @param shouldStop the shouldStop to set
58 public void setShouldStop(boolean shouldStop
) {
59 this.shouldStop
= shouldStop
;
63 * It changes the URI.<br>
64 * A new connection will be performed after a complete
66 * @param method the method to set
68 public void setMethod(HttpMethod method
) {
69 synchronized (this.method
) {
71 this.isChangePending
= true;
72 this.method
.setFollowRedirects(true);
76 private void setCredential(String username
, String password
) {
77 UsernamePasswordCredentials creds
=
78 new UsernamePasswordCredentials(username
, password
);
79 client
.getState().setCredentials(AuthScope
.ANY
, creds
);
85 public AsyncProducer(HttpMethod method
) {
86 this(method
, null, null);
90 * This constructor provides support for HTTP AUTH
92 * @param method the MJPEG stream URI
93 * @param username HTTP AUTH username
94 * @param password HTTP AUTH password
96 public AsyncProducer(HttpMethod method
, String username
, String password
) {
98 this.shouldStop
= false;
99 buffer
= new CircularBuffer();
100 // create a singular HttpClient object
101 this.client
= new HttpClient();
103 // establish a connection within 5 seconds
104 this.client
.getHttpConnectionManager().getParams()
105 .setConnectionTimeout(HTTP_TIMEOUT
);
107 if (username
!= null && password
!= null ) {
108 this.setCredential(username
, password
);
110 setMethod(this.method
);
114 * @see java.lang.Runnable#run()
117 BufferedInputStream is
= null;
118 InputStream responseBody
= null;
119 String boundary
= "";
121 while (!this.shouldStop
) {
123 if (this.isChangePending
) {
124 synchronized (this.method
) {
125 // establish the connection
127 this.client
.executeMethod(this.method
);
128 responseBody
= this.method
.getResponseBodyAsStream();
129 is
= new BufferedInputStream (responseBody
);
130 } catch (HttpException e
) {
131 System
.err
.println("Http error connecting to '" + this.method
.getPath() + "'");
132 System
.err
.println(e
.getMessage());
134 } catch (IOException e
) {
135 System
.err
.println("Unable to connect to '" + this.method
.getPath() + "'");
138 // automagically guess the boundary
139 Header contentType
= this.method
.getResponseHeader("Content-Type");
140 String contentTypeS
= contentType
.toString();
141 int startIndex
= contentTypeS
.indexOf("boundary=");
142 int endIndex
= contentTypeS
.indexOf(';', startIndex
);
143 if (endIndex
== -1) //boundaty is the last option
144 endIndex
= contentTypeS
.length();
145 boundary
= contentTypeS
.substring(startIndex
+ 9, endIndex
);
147 this.isChangePending
= false;
149 } //end if(isChangePending)
151 /* Now flip flop to parse the images. We look for the JPEG magic MIME identifier,
152 * which is composed by the first two bites set to 0xff and 0xd8. We stop when we find
153 * another "--$boundary" string.
157 byte tmp
[] = new byte[BYTES_TO_READ
];
158 ByteArrayOutputStream os
= new ByteArrayOutputStream();
159 char delimiter
[] = (boundary
).toCharArray();
160 byte magicMIME
[] = {(byte) 0xff,
162 byte partialMatch
[] = new byte[delimiter
.length
];
165 boolean foundStart
= false;
167 /* This first cycle ensures we start parsing the stream on the first
168 * magic MIME identifier.
174 if ((byte) data == magicMIME[0]) {
180 if (foundStart && ((byte) data) == magicMIME[1]) {
188 /* This cycle splits the stream into several ByteArrayInputStreams containing
189 * the real image, and pushs them to our internal buffer.
193 boolean lookinForMagicMIME
= true;
194 boolean imageComplete
;
195 imageComplete
= false;
196 while (!imageComplete
&& (bytesRead
= is
.read(tmp
)) != -1) {
197 for (int i
= 0; i
< bytesRead
; i
++) {
198 if (lookinForMagicMIME
) {
199 if (tmp
[i
] == magicMIME
[last
]) {
200 partialMatch
[last
] = tmp
[i
];
204 os
.write(partialMatch
, 0, 2);
206 lookinForMagicMIME
= false;
211 }else { //lookinForBoundary
212 if (tmp
[i
] == delimiter
[last
]) {
213 partialMatch
[last
] = tmp
[i
];
216 if (last
== delimiter
.length
) {
217 this.buffer
.push(new ByteArrayInputStream(os
.toByteArray()));
218 lookinForMagicMIME
= true;
220 imageComplete
= true;
223 } else if (last
> 0) {
224 partialMatch
[last
++] = tmp
[i
];
225 os
.write(partialMatch
, 0 , last
);
235 }catch (IOException e
) {
236 // TODO: handle exception
243 * Return the first image available as {@link java.io.ByteArrayInputStream}.
244 * @return the first image available
246 public ByteArrayInputStream
pop() {
247 return this.buffer
.pop();
251 * Return <code>true</code> if there is at least one image available into
252 * the internal buffer.
254 * @return the availability status
256 public boolean isImageAvailable() {
257 return !this.buffer
.isEmpty();