/*- ******************************************************************************* * Copyright (c) 2011, 2015 Diamond Light Source Ltd. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Matthew Gerring - initial API and implementation and/or initial documentation *******************************************************************************/ package org.eclipse.dawnsci.remotedataset.client.streamer; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import org.eclipse.dawnsci.remotedataset.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; abstract class AbstractStreamer<T> implements IStreamer<T>, Runnable { protected static final Logger logger = LoggerFactory.getLogger(AbstractStreamer.class); private BlockingQueue<T> queue; private InputStream in; private long sleepTime; private long droppedImages = 0; private long receivedImages = 0; private boolean isFinished; private String delimiter; protected URLConnection init(URL url, long sleepTime, int cacheSize) throws Exception { URLConnection conn = url.openConnection(); conn.setDoInput(true); conn.setDoOutput(true); conn.setUseCaches(false); String contentType = conn.getContentType(); if (!contentType.startsWith(Constants.MCONTENT_TYPE)) throw new Exception("getImages() may only be used with "+Constants.MCONTENT_TYPE+", not "+contentType); this.delimiter = contentType.split("boundary=")[1]; this.queue = new LinkedBlockingQueue<T>(cacheSize); // TODO How many images can be in the queue? this.in = new BufferedInputStream(conn.getInputStream()); this.sleepTime = sleepTime; return conn; } public void run() { isFinished = false; try { final StringBuilder buf = new StringBuilder(); int c = -1; boolean foundImage = false; while(!isFinished && (c=in.read())> -1 ) { buf.append((char)c); if (buf.length()>0 && buf.charAt(buf.length()-1) == '\n') { // Line found final String line = buf.toString().trim(); if (line.equals("--"+delimiter)) { // We found a new image foundImage = true; } if (foundImage && line.startsWith("Content-Length: ")) { int clength = Integer.parseInt(line.split("\\:")[1].trim()); T image = readImage(in, clength); if (image == null || isFinished) return; if (queue.remainingCapacity()<1) { Object gone = queue.poll(); // Goodbye if (gone!=null) { droppedImages+=1; logger.trace("We dropped an image of size "+clength+" bytes when reading an MJPG Stream"); } } queue.add(image); foundImage = false; Thread.sleep(sleepTime); // We don't want to use all the CPU! } buf.delete(0, buf.length()); continue; } } } catch (Exception ne) { setFinished(true); logger.error("Cannot read input stream in "+getClass().getSimpleName(), ne); } finally { try { in.close(); } catch (Exception ne) { logger.error("Cannot close connection!", ne); } // Ensure there is capacity to add the queue end object if (queue.remainingCapacity() < 1) { Object gone = queue.poll(); // Goodbye if (gone != null) { droppedImages += 1; logger.trace("We dropped an image when closing an MJPG Stream"); } } // Cannot have null, instead add tiny empty image queue.add(getQueueEndObject()); } } private T readImage(InputStream in, int clength) throws Exception { int c= -1; // Scoot down until no more new lines (this looses first character of JPG) while((c=in.read())> -1) { if (c=='\r') continue; if (c=='\n') continue; break; } byte[] imageBytes = new byte[clength + 1]; imageBytes[0] = (byte)c; // We took one int offset = 1; int numRead = 0; while (!isFinished && offset < imageBytes.length && (numRead=in.read(imageBytes, offset, imageBytes.length-offset)) >= 0) { offset += numRead; } if (isFinished) return null; ByteArrayInputStream bais = new ByteArrayInputStream(imageBytes); return getFromStream(bais); } protected abstract T getFromStream(ByteArrayInputStream bais) throws Exception; /** * Blocks until image added. Once null is added, we are done. * @return Image or null when finished. * * @throws InterruptedException */ public T take() throws InterruptedException { T bi = queue.take(); // Might get interrupted if (bi == getQueueEndObject()) { setFinished(true); return null; } receivedImages++; return bi; } protected abstract T getQueueEndObject(); public long getDroppedImageCount() { return droppedImages; } public long getReceivedImageCount() { return receivedImages; } public void start() { Thread thread = new Thread(this); thread.setPriority(Thread.MIN_PRIORITY); thread.setDaemon(true); thread.setName("MJPG Streamer"); thread.start(); } /** * Call to tell the streamer to stop adding images to its queue. * @param b */ public void setFinished(boolean b) { this.isFinished = b; } }