package com.pugh.sockso.music.stream; import com.pugh.sockso.Constants; import com.pugh.sockso.Properties; import com.pugh.sockso.Utils; import com.pugh.sockso.music.Track; import com.pugh.sockso.music.encoders.Encoder; import com.pugh.sockso.web.Response; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import org.apache.log4j.Logger; /** * * @author Nathan Perrier */ public class ChunkedStream extends AbstractMusicStream { private static final Logger log = Logger.getLogger(ChunkedStream.class); private Encoder encoder; private Properties props; private static final byte[] CRLF = "\r\n".getBytes(); public ChunkedStream( final Track track, final Encoder encoder, final Properties props ) { super(track); this.encoder = encoder; this.props= props; } @Override public DataInputStream getAudioStream() throws IOException { final String ext = Utils.getExt(track.getPath()); final String bitrateStr = this.props.get(Constants.PROP_ENCODERS_PREFIX + ext + ".bitrate"); final int bitrate = bitrateStr.equals("") ? encoder.getDefaultBitrate() : Integer.valueOf(bitrateStr); return encoder.getAudioStream(this.track, bitrate); } @Override public void setHeaders( final Response response ) { super.setHeaders(response); response.addHeader("Transfer-Encoding", "chunked"); } public void sendAudioStream( final DataOutputStream client ) throws IOException { final DataInputStream audio = getAudioStream(); final ChunkedOutputStream out = new ChunkedOutputStream(client); final byte[] buffer = new byte[STREAM_BUFFER_SIZE]; int readBlock = STREAM_BUFFER_SIZE; long totalBytes = 0; try { int nextRead = -1; int bytesRead; while( (bytesRead = audio.read(buffer, 0, readBlock)) > 0 ) { out.write(buffer, 0, bytesRead); totalBytes += bytesRead; if ( totalBytes > nextRead ) { nextRead += STREAM_BUFFER_SIZE * 12; // print ~every 100 KB log.debug(String.format("Sent %2d%%", bytesRead)); } } } finally { Utils.close(audio); Utils.close(out); } } static class ChunkedOutputStream extends OutputStream { OutputStream output = null; public ChunkedOutputStream(OutputStream output) { this.output = output; } @Override public void write(int i) throws IOException { write(new byte[] { (byte) i }, 0, 1); } @Override public void write(byte[] b, int offset, int length) throws IOException { writeHeader(length); output.write(CRLF, 0, CRLF.length); output.write(b, offset, length); output.write(CRLF, 0, CRLF.length); } @Override public void flush() throws IOException { output.flush(); } @Override public void close() throws IOException { writeHeader(0); output.write(CRLF, 0, CRLF.length); output.write(CRLF, 0, CRLF.length); output.close(); } private void writeHeader( int length ) throws IOException { byte[] header = Integer.toHexString(length).getBytes(); output.write(header, 0, header.length); } } }