2013-04-06 21:47:24 +02:00
|
|
|
package com.thejoshwa.ultrasonic.androidapp.util;
|
2013-02-10 09:13:20 +01:00
|
|
|
|
|
|
|
import java.io.BufferedOutputStream;
|
|
|
|
import java.io.BufferedReader;
|
|
|
|
import java.io.File;
|
|
|
|
import java.io.FileInputStream;
|
|
|
|
import java.io.IOException;
|
|
|
|
import java.io.InputStream;
|
|
|
|
import java.io.InputStreamReader;
|
|
|
|
import java.io.OutputStream;
|
|
|
|
import java.io.UnsupportedEncodingException;
|
|
|
|
import java.net.InetAddress;
|
|
|
|
import java.net.ServerSocket;
|
|
|
|
import java.net.Socket;
|
|
|
|
import java.net.SocketException;
|
|
|
|
import java.net.SocketTimeoutException;
|
|
|
|
import java.net.URLDecoder;
|
|
|
|
import java.net.UnknownHostException;
|
|
|
|
import java.util.StringTokenizer;
|
|
|
|
|
|
|
|
import org.apache.http.HttpRequest;
|
|
|
|
import org.apache.http.message.BasicHttpRequest;
|
|
|
|
|
2013-05-16 09:59:55 +02:00
|
|
|
import com.thejoshwa.ultrasonic.androidapp.domain.MusicDirectory;
|
2013-04-06 21:47:24 +02:00
|
|
|
import com.thejoshwa.ultrasonic.androidapp.service.DownloadFile;
|
|
|
|
import com.thejoshwa.ultrasonic.androidapp.service.DownloadService;
|
2013-05-16 09:59:55 +02:00
|
|
|
|
2013-02-10 09:13:20 +01:00
|
|
|
import android.util.Log;
|
|
|
|
|
|
|
|
public class StreamProxy implements Runnable {
|
|
|
|
private static final String TAG = StreamProxy.class.getSimpleName();
|
|
|
|
|
|
|
|
private Thread thread;
|
|
|
|
private boolean isRunning;
|
|
|
|
private ServerSocket socket;
|
|
|
|
private int port;
|
2013-05-16 09:59:55 +02:00
|
|
|
private DownloadService downloadService;
|
2013-02-10 09:13:20 +01:00
|
|
|
|
2013-05-16 09:59:55 +02:00
|
|
|
public StreamProxy(DownloadService downloadService) {
|
2013-02-10 09:13:20 +01:00
|
|
|
|
|
|
|
// Create listening socket
|
|
|
|
try {
|
|
|
|
socket = new ServerSocket(0, 0, InetAddress.getByAddress(new byte[] { 127, 0, 0, 1 }));
|
|
|
|
socket.setSoTimeout(5000);
|
|
|
|
port = socket.getLocalPort();
|
2013-05-16 09:59:55 +02:00
|
|
|
this.downloadService = downloadService;
|
2013-02-10 09:13:20 +01:00
|
|
|
} catch (UnknownHostException e) { // impossible
|
|
|
|
} catch (IOException e) {
|
|
|
|
Log.e(TAG, "IOException initializing server", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public int getPort() {
|
|
|
|
return port;
|
|
|
|
}
|
|
|
|
|
|
|
|
public void start() {
|
|
|
|
thread = new Thread(this);
|
|
|
|
thread.start();
|
|
|
|
}
|
|
|
|
|
|
|
|
public void stop() {
|
|
|
|
isRunning = false;
|
|
|
|
thread.interrupt();
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void run() {
|
|
|
|
isRunning = true;
|
|
|
|
while (isRunning) {
|
|
|
|
try {
|
|
|
|
Socket client = socket.accept();
|
|
|
|
if (client == null) {
|
|
|
|
continue;
|
|
|
|
}
|
2013-05-16 09:59:55 +02:00
|
|
|
Log.i(TAG, "client connected");
|
2013-02-10 09:13:20 +01:00
|
|
|
|
|
|
|
StreamToMediaPlayerTask task = new StreamToMediaPlayerTask(client);
|
|
|
|
if (task.processRequest()) {
|
2013-05-16 09:59:55 +02:00
|
|
|
new Thread(task).start();
|
2013-02-10 09:13:20 +01:00
|
|
|
}
|
2013-05-16 09:59:55 +02:00
|
|
|
|
2013-02-10 09:13:20 +01:00
|
|
|
} catch (SocketTimeoutException e) {
|
|
|
|
// Do nothing
|
|
|
|
} catch (IOException e) {
|
|
|
|
Log.e(TAG, "Error connecting to client", e);
|
|
|
|
}
|
|
|
|
}
|
2013-05-16 09:59:55 +02:00
|
|
|
Log.i(TAG, "Proxy interrupted. Shutting down.");
|
2013-02-10 09:13:20 +01:00
|
|
|
}
|
|
|
|
|
2013-05-16 09:59:55 +02:00
|
|
|
private class StreamToMediaPlayerTask implements Runnable {
|
|
|
|
|
|
|
|
String localPath;
|
2013-02-10 09:13:20 +01:00
|
|
|
Socket client;
|
|
|
|
int cbSkip;
|
|
|
|
|
|
|
|
public StreamToMediaPlayerTask(Socket client) {
|
|
|
|
this.client = client;
|
|
|
|
}
|
2013-05-16 09:59:55 +02:00
|
|
|
|
2013-02-10 09:13:20 +01:00
|
|
|
private HttpRequest readRequest() {
|
|
|
|
HttpRequest request = null;
|
|
|
|
InputStream is;
|
|
|
|
String firstLine;
|
|
|
|
try {
|
|
|
|
is = client.getInputStream();
|
|
|
|
BufferedReader reader = new BufferedReader(new InputStreamReader(is), 8192);
|
|
|
|
firstLine = reader.readLine();
|
|
|
|
} catch (IOException e) {
|
|
|
|
Log.e(TAG, "Error parsing request", e);
|
|
|
|
return request;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (firstLine == null) {
|
|
|
|
Log.i(TAG, "Proxy client closed connection without a request.");
|
|
|
|
return request;
|
|
|
|
}
|
|
|
|
|
|
|
|
StringTokenizer st = new StringTokenizer(firstLine);
|
|
|
|
String method = st.nextToken();
|
|
|
|
String uri = st.nextToken();
|
|
|
|
String realUri = uri.substring(1);
|
2013-05-16 09:59:55 +02:00
|
|
|
Log.i(TAG, realUri);
|
2013-02-10 09:13:20 +01:00
|
|
|
request = new BasicHttpRequest(method, realUri);
|
|
|
|
return request;
|
|
|
|
}
|
|
|
|
|
|
|
|
public boolean processRequest() {
|
|
|
|
HttpRequest request = readRequest();
|
|
|
|
if (request == null) {
|
|
|
|
return false;
|
|
|
|
}
|
2013-05-16 09:59:55 +02:00
|
|
|
|
|
|
|
// Read HTTP headers
|
|
|
|
Log.i(TAG, "Processing request");
|
2013-02-11 05:30:46 +01:00
|
|
|
|
2013-05-16 09:59:55 +02:00
|
|
|
try {
|
|
|
|
localPath = URLDecoder.decode(request.getRequestLine().getUri(), Constants.UTF_8);
|
|
|
|
} catch (UnsupportedEncodingException e) {
|
|
|
|
Log.e(TAG, "Unsupported encoding", e);
|
2013-02-10 09:13:20 +01:00
|
|
|
return false;
|
|
|
|
}
|
2013-05-16 09:59:55 +02:00
|
|
|
|
|
|
|
Log.i(TAG, "Processing request for file " + localPath);
|
|
|
|
File file = new File(localPath);
|
|
|
|
if (!file.exists()) {
|
|
|
|
Log.e(TAG, "File " + localPath + " does not exist");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-02-10 09:13:20 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2013-05-16 09:59:55 +02:00
|
|
|
public void run() {
|
|
|
|
Log.i(TAG, "Streaming song in background");
|
|
|
|
DownloadFile downloadFile = downloadService.getCurrentPlaying();
|
|
|
|
MusicDirectory.Entry song = downloadFile.getSong();
|
|
|
|
|
|
|
|
// Create HTTP header
|
|
|
|
String headers = "HTTP/1.0 200 OK\r\n";
|
|
|
|
headers += "Content-Type: " + "application/octet-stream" + "\r\n";
|
2013-07-17 10:59:58 +02:00
|
|
|
|
|
|
|
Integer contentLength = downloadFile.getContentLength();
|
|
|
|
long fileSize;
|
|
|
|
|
|
|
|
if (contentLength == null) {
|
|
|
|
fileSize = downloadFile.getBitRate() * ((song.getDuration() != null) ? song.getDuration() : 0) * 1000 / 8;
|
|
|
|
} else {
|
|
|
|
fileSize = contentLength;
|
|
|
|
headers += "Content-Length: " + fileSize + "\r\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
Log.i(TAG, "Streaming fileSize: " + fileSize);
|
2013-05-16 09:59:55 +02:00
|
|
|
|
|
|
|
headers += "Connection: close\r\n";
|
|
|
|
headers += "\r\n";
|
|
|
|
|
|
|
|
long cbToSend = fileSize - cbSkip;
|
|
|
|
OutputStream output = null;
|
|
|
|
byte[] buff = new byte[64 * 1024];
|
|
|
|
try {
|
|
|
|
output = new BufferedOutputStream(client.getOutputStream(), 32*1024);
|
|
|
|
output.write(headers.getBytes());
|
|
|
|
|
|
|
|
if(!downloadFile.isWorkDone()) {
|
|
|
|
// Loop as long as there's stuff to send
|
|
|
|
while (isRunning && !client.isClosed()) {
|
|
|
|
|
|
|
|
// See if there's more to send
|
|
|
|
File file = new File(localPath);
|
|
|
|
int cbSentThisBatch = 0;
|
|
|
|
if (file.exists()) {
|
|
|
|
FileInputStream input = new FileInputStream(file);
|
|
|
|
input.skip(cbSkip);
|
|
|
|
int cbToSendThisBatch = input.available();
|
|
|
|
while (cbToSendThisBatch > 0) {
|
|
|
|
int cbToRead = Math.min(cbToSendThisBatch, buff.length);
|
|
|
|
int cbRead = input.read(buff, 0, cbToRead);
|
|
|
|
if (cbRead == -1) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
cbToSendThisBatch -= cbRead;
|
|
|
|
cbToSend -= cbRead;
|
|
|
|
output.write(buff, 0, cbRead);
|
|
|
|
output.flush();
|
|
|
|
cbSkip += cbRead;
|
|
|
|
cbSentThisBatch += cbRead;
|
|
|
|
}
|
|
|
|
input.close();
|
|
|
|
}
|
2013-02-11 05:30:46 +01:00
|
|
|
|
2013-05-16 09:59:55 +02:00
|
|
|
// Done regardless of whether or not it thinks it is
|
|
|
|
if(downloadFile.isWorkDone() && cbSkip >= file.length()) {
|
2013-02-11 05:30:46 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2013-05-16 09:59:55 +02:00
|
|
|
// If we did nothing this batch, block for a second
|
|
|
|
if (cbSentThisBatch == 0) {
|
|
|
|
Log.d(TAG, "Blocking until more data appears (" + cbToSend + ")");
|
|
|
|
Thread.sleep(1000);
|
2013-02-11 05:30:46 +01:00
|
|
|
}
|
|
|
|
}
|
2013-05-16 09:59:55 +02:00
|
|
|
} else {
|
|
|
|
Log.w(TAG, "Requesting data for completely downloaded file");
|
2013-02-11 05:30:46 +01:00
|
|
|
}
|
2013-05-16 09:59:55 +02:00
|
|
|
}
|
|
|
|
catch (SocketException socketException) {
|
|
|
|
Log.e(TAG, "SocketException() thrown, proxy client has probably closed. This can exit harmlessly");
|
|
|
|
}
|
|
|
|
catch (Exception e) {
|
|
|
|
Log.e(TAG, "Exception thrown from streaming task:");
|
|
|
|
Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Cleanup
|
|
|
|
try {
|
|
|
|
if (output != null) {
|
|
|
|
output.close();
|
|
|
|
}
|
|
|
|
client.close();
|
|
|
|
}
|
|
|
|
catch (IOException e) {
|
|
|
|
Log.e(TAG, "IOException while cleaning up streaming task:");
|
|
|
|
Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
|
|
|
|
}
|
|
|
|
}
|
2013-02-10 09:13:20 +01:00
|
|
|
}
|
|
|
|
}
|