package se.sics.gvod.ls.http; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.net.URLConnection; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; import org.slf4j.LoggerFactory; import se.sics.gvod.ls.system.PieceHandler; import se.sics.gvod.video.msgs.Piece; /** * Provides various functionalities for reading HTTP (Live) Streaming data and * converting it to Pieces. * * @author Niklas Wahlén <nwahlen@kth.se> * @see Piece * @see <a href="http://en.wikipedia.org/wiki/HTTP_Live_Streaming">Wikipedia: * HTTP Live Streaming</a> * @see <a href="https://developer.apple.com/resources/http-streaming/">HTTP * Live Streaming Resources - Apple Developer</a> */ public class HTTPStreamingClient implements Runnable { private final org.slf4j.Logger logger = LoggerFactory.getLogger(HTTPStreamingClient.class); private List<Piece> pieces; private int piecesRead; private ByteBuffer buffer; private InputStream in; private AtomicBoolean reading; public HTTPStreamingClient(URL url) throws IOException { in = getHTTPStream(url); init(); } public HTTPStreamingClient(File file) throws IOException { in = new FileInputStream(file); init(); } private void init() { pieces = Collections.synchronizedList(new ArrayList<Piece>()); piecesRead = 0; buffer = ByteBuffer.allocate(2 * Piece.PIECE_DATA_SIZE); reading = new AtomicBoolean(false); } /** * Starts downloading the stream and saving it as Pieces. * * @throws IOException if the connection to the streaming server fails. */ @Override public void run() { try { reading.set(true); byte[] data = new byte[Piece.PIECE_DATA_SIZE]; int nRead; while ((nRead = in.read(data, 0, data.length)) != -1) { logger.debug("Read " + nRead + " bytes."); buffer.put(data, 0, nRead); if (buffer.position() >= Piece.PIECE_DATA_SIZE) { buffer.flip(); buffer.get(data, 0, Piece.PIECE_DATA_SIZE); buffer.compact(); pieces.add(new Piece(piecesRead, data)); piecesRead++; } } if (buffer.position() > 0 || buffer.limit() < buffer.capacity()) { // TODO: currently only handling position() < 0 // (only a matter of flipping/not flipping) int position = buffer.position(); // Create padding for last piece buffer.flip(); buffer.get(data, 0, position); buffer.compact(); logger.debug(this.getClass().getSimpleName() + ": Writing padding code starting at " + piecesRead + "[" + position + "]"); System.arraycopy(Piece.PADDING_CODE, 0, data, position, Piece.PADDING_CODE.length); Arrays.fill(data, position + Piece.PADDING_CODE.length, data.length, (byte) 1); pieces.add(new Piece(piecesRead, data)); piecesRead++; } in.close(); } catch (IOException ex) { Logger.getLogger(HTTPStreamingClient.class.getName()).log(Level.SEVERE, null, ex); } finally { reading.set(false); } } /** * Returns the downloaded * <code>Piece</code> with the lowest ID and removes it from the streaming * client instance. * * @return The * <code>Piece</code> with the lowest ID of the remaining pieces. Returns * <code>null</code> if there are currently no Pieces available. */ public Piece getNextPiece() { if (hasNextPiece()) { return pieces.remove(0); } else { return null; } } /** * Indicates if there are any new Pieces downloaded. * * @return * <code>true</code> if there are any new downloaded Pieces ready. */ public boolean hasNextPiece() { return !pieces.isEmpty(); } private InputStream getHTTPStream(URL url) throws IOException { URLConnection streamConnection = url.openConnection(); logger.info(this.getClass().getSimpleName() + ": Waiting for stream connection ..."); while (true) { boolean connected = true; try { streamConnection.connect(); } catch (IOException ex) { connected = false; } if (connected) { logger.info(this.getClass().getSimpleName() + ": Connected."); break; } } return streamConnection.getInputStream(); } public boolean isReading() { return reading.get(); } public static void main(String args[]) { List<Piece> pieces = new ArrayList<Piece>(); try { //HTTPStreamingClient client = new HTTPStreamingClient("http://127.0.0.1/~niklas/source.mp4"); HTTPStreamingClient client = new HTTPStreamingClient(new URL("http://127.0.0.1:8080")); client.run(); while (client.hasNextPiece()) { Piece p = client.getNextPiece(); pieces.add(p); } System.out.println(HTTPStreamingClient.class.getSimpleName() + ": Read " + pieces.size() + " pieces."); PieceHandler.writePieceData("stream.mp4", pieces); } catch (IOException ex) { Logger.getLogger(HTTPStreamingClient.class.getName()).log(Level.SEVERE, null, ex); } } }