I am trying to write an app that will log the output of an arduino Due to a text file on the phone or tablet. The output rate is 1kHz. I have based my app on the Blueserial code (https://github.com/plastygrove/BlueSerial). The bluetooth connection gets established properly with the arduino bluetooth module, the commands are sent and received properly and everything seems to work just fine. However, the file that I am saving the data to is missing blocks of data, usually around 200ms worth every so often (I have a millisecond timestamp included in my data), resulting in corrupted data. I have been trying to figure out the source of the problem and I think it might be related to the gc but at this point I am at a loss. This is the code that writes my data to the file:
private class ReadInput implements Runnable {
private boolean bStop = false;
private Thread t;
public ReadInput() {
t = new Thread(this, "Input Thread");
t.start();
}
public boolean isRunning() {
return t.isAlive();
}
public void run() {
InputStream inputStream;
try {
inputStream = mBTSocket.getInputStream();
BufferedInputStream bis = new BufferedInputStream(inputStream);
while (!bStop) {
byte[] buffer = new byte[250];
int bytes = 0;
if (bis.available() > 0) {
bytes = bis.read(buffer);
strInput = new String(buffer, 0, bytes);
sb.append(strInput);
int endOfLineIndex = sb.indexOf("\r\n"); // determine the end-of-line
pw.print(strInput); // print buffer to the file buffer
pw.flush(); // flush buffer and force write to media
if (endOfLineIndex > 0) { // if end-of-line,
String sbprint = sb.substring(0, endOfLineIndex); // extract string
sb.delete(0, sb.length()); // and clear the string
pw.print(strInput); // write buffer to file buffer
pw.flush(); // force writing to file
pw.close(); // close print writer
try {
f.close(); // close file output stream
} catch (IOException e) {
e.printStackTrace();
}
}
sb.delete(0, sb.length()); strInput = "";
}
//Thread.sleep(100);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void stop() {
bStop = true;
}
}
and this is my file outputsteam and printwriter declarations:
String strInput = null;
static PrintWriter pw = null;
static FileOutputStream f = null;
private StringBuilder sb = new StringBuilder();
The data I am sending is formatted as so:
24.330,-58,5,119,460\n
24.331,-86,25,-105,460\n
24.332,66,41,-145,460\n
24.333,90,-23,-85,4622,-7,119,460\n
24.524,6,-95,107,461\n
24.525,10,-7,-173,461\n
24.526,-22,33,103,461\n
and in this example you can see where it skipped some data. Thank you for helping out!
There appears to be code missing or you have some extra logic.
For example the StringBuilder doesn't appear to be doing anything,
bStop is never set/cleared. But you are always printing out the incoming data
via strInput.
The end of line handling also looks off, specifically this:
String sbprint = sb.substring(0, endOfLineIndex); // extract string
sb.delete(0, sb.length());
You extract the string and then delete the whole buffer sb.delete(0,sb.length())
same at the bottom of the loop.
Related
What I have :
HC-05 Bluetooth module
Arduino promini
BPM sensors
Android studio 3.0
What I have done :
Connect the Arduino, sensors, and HC-05 bluetooth using Serial
Send the data from Arduino script
Recieve and get the data using 3rd party apps from playstore
What I wanted to do :
Get the data from arduino's to be showed in the android text view, using native coded apps (not the one from the store) using android studio.
My current code :
Arduino script :
loop(){
Serial.println("*"+String(BPM)+"#"); //BPM from sensor
delay(200)
}
And some code for connecting and pairing using Android Bluetooth Adapter, which I can't showed here full because still hard coded, but the point is :
I made a handler, which recieves incoming inputs from the serial, and at least showing them on Toast component, but still haven't managed to make it work.
h = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case RECIEVE_MESSAGE:
Toast.makeText(getApplicationContext(),"Data in",Toast.LENGTH_SHORT
).show();// if receive massage
byte[] readBuf = (byte[]) msg.obj;
String strIncom = new String(readBuf, 0, msg.arg1); // create string from bytes array
sb.append(strIncom); // append string
int endOfLineIndex = sb.indexOf("\r\n"); // determine the end-of-line
if (endOfLineIndex > 0) { // if end-of-line,
String sbprint = sb.substring(0, endOfLineIndex); // extract string
sb.delete(0, sb.length()); // and clear
sensorView0.setText("Data from Arduino: " + sbprint); // update TextView
btnOff.setEnabled(true);
btnOn.setEnabled(true);
}
//Log.d(TAG, "...String:"+ sb.toString() + "Byte:" + msg.arg1 + "...");
break;
}
};
};
I also have a class :
private class ConnectedThread extends Thread {
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams, using temp objects because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) { }
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
byte[] buffer = new byte[256]; // buffer store for the stream
int bytes; // bytes returned from read()
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
Toast.makeText(getApplicationContext(),"Masuk ke handler private class",Toast.LENGTH_SHORT
).show();
// Read from the InputStream
bytes = mmInStream.read(buffer); // Get number of bytes and message in "buffer"
h.obtainMessage(RECIEVE_MESSAGE, bytes, -1, buffer).sendToTarget(); // Send to message queue Handler
} catch (IOException e) {
break;
}
}
}
}
Which I think calls for the handler to read the message, but haven't managed to make it work.
I wanted to at least show the message to the Toast, or TextView, but haven't got a clear answer this last 3 weeks.
So anyone who can help will be appreciated.
So, I used this Android sample as a guide to make a bluetooth connection without any kind of validation (This app will have a very restrict userbase and will not be available to download at the store).
I was able to transfer string just fine, and it works like a charm. My problem is when trying to transfer images.
I have one activity that sends the byte[] of the image to the bluetooth service and a handler on the other activity that recieves the message and do wharever with the said message.
The thing is, because of the size of the buffer the handler receives parts of the original byte[]. What I'm tryng to do is to merge all the parts in one byte and save it.
This is the loop that I do inside my handler:
byte[] result = new byte[originalByteSize];
byte[] readBuf = (byte[]) msg.obj;
if (cont < byteTimes){
if (result == null) {
result = appendData(readBuf,readBuf);
} else {
result = appendData(result,readBuf);
}
} else {
new SavePhotoTask(cont).execute(result);
}
This is the appendData function
protected byte[] appendData(byte[] firstObject,byte[] secondObject){
ByteArrayOutputStream outputStream = new ByteArrayOutputStream( );
try {
if (firstObject!=null && firstObject.length!=0)
outputStream.write(firstObject);
if (secondObject!=null && secondObject.length!=0)
outputStream.write(secondObject);
} catch (IOException e) {
e.printStackTrace();
}
return outputStream.toByteArray();
}
And here is where I write the file:
public class SavePhotoTask extends AsyncTask<byte[], String, String> {
int counter = 0;
public SavePhotoTask(int cont){
this.counter = cont;
}
#Override
protected String doInBackground(byte[]... jpeg) {
File photo = new File(Environment.getExternalStorageDirectory(), counter + "_photo.jpg");
if (photo.exists()) {
photo.delete();
}
try {
FileOutputStream fos = new FileOutputStream(photo.getPath());
fos.write(jpeg[0]);
fos.close();
} catch (java.io.IOException e) {
Log.e("PictureDemo", "Exception in photoCallback", e);
}
return (null);
}
What I needed is just a tip in the right direction, thanks.
I solved my problem with this answer
The problem was in the way I was writing and reading the stream.
public static void writeItem(OutputStream out, String s) throws IOException
{
// Get the array of bytes for the string item:
byte[] bs = s.getBytes(); // as bytes
// Encapsulate by sending first the total length on 4 bytes :
// - bits 7..0 of length
out.write(bs.length); // modulo 256 done by write method
// - bits 15..8 of length
out.write(bs.length>>>8); // modulo 256 done by write method
// - bits 23..16 of length
out.write(bs.length>>>16); // modulo 256 done by write method
// - bits 31..24 of length
out.write(bs.length>>>24); // modulo 256 done by write method
// Write the array content now:
out.write(bs); // Send the bytes
out.flush();
}
public static String readItem(InputStream in) throws IOException
{
// first, read the total length on 4 bytes
// - if first byte is missing, end of stream reached
int len = in.read(); // 1 byte
if (len<0) throw new IOException("end of stream");
// - the other 3 bytes of length are mandatory
for(int i=1;i<4;i++) // need 3 more bytes:
{
int n = in.read();
if (n<0) throw new IOException("partial data");
len |= n << (i<<3); // shift by 8,16,24
}
// Create the array to receive len bytes:
byte[] bs = new byte[len];
// Read the len bytes into the created array
int ofs = 0;
while (len>0) // while there is some byte to read
{
int n = in.read(bs, ofs, len); // number of bytes actually read
if (n<0) throw new IOException("partial data");
ofs += n; // update offset
len -= n; // update remaining number of bytes to read
}
// Transform bytes into String item:
return new String(bs);
}
I'm trying to build an application that reads information sent on a bluetooth service using rfcomm.
The device is an hardness tester (HT-6510A), unfortunalty specs about the device data format can't be found I'm faced with a strange problem, I've to understand how to read these information.
01-11 17:47:28.940 11862-13447/joinstore.it.testhardness V/result: ��S
01-11 17:47:29.581 11862-13447/joinstore.it.testhardness V/result: ��S
01-11 17:47:30.211 11862-13447/joinstore.it.testhardness V/result: ��S
01-11 17:47:30.872 11862-13447/joinstore.it.testhardness V/result: ��S
01-11 17:47:31.513 11862-13447/joinstore.it.testhardness V/result: ��S
01-11 17:47:32.143 11862-13447/joinstore.it.testhardness V/result: ��T
01-11 17:47:32.794 11862-13447/joinstore.it.testhardness V/result: ��T
This is the data I receive from the device, I don't think there's something wrong with the implementation tha simply uses this thread after stabilizing a rfcomm connection.
//After connection, handle data transfer
private class ConnectedThread extends Thread {
private final BluetoothSocket mmSocket;
private final InputStream mmInStream;
private final OutputStream mmOutStream;
public ConnectedThread(BluetoothSocket socket) {
mmSocket = socket;
InputStream tmpIn = null;
OutputStream tmpOut = null;
// Get the input and output streams, using temp objects because
// member streams are final
try {
tmpIn = socket.getInputStream();
tmpOut = socket.getOutputStream();
} catch (IOException e) {
}
mmInStream = tmpIn;
mmOutStream = tmpOut;
}
public void run() {
// readAndPublishRaw();
readAndPublishString();
Log.v("result", "Reading data ended.");
setStatusText(-1);
}
void readAndPublishRaw(){
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
Log.v("result", "Start reading...");
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI activity
Log.v("result", bytes + "");
} catch (IOException e) {
break;
}
}
}
void readAndPublishString(){
//String method, not useful in this case?
try {
BufferedReader r = new BufferedReader(new InputStreamReader(mmInStream));
StringBuilder total = new StringBuilder();
String line;
Log.v("result", "Start reading...");
while ((line = r.readLine()) != null) {
total.append(line);
Log.v("result", line);
}
Log.v("result", total.toString());
//TODO publish read string to the view
} catch (Exception e) {
//
try {
mmSocket.close();
}catch (Exception ex){}
Log.v(TAG, "exception reading data from service");
}
}
/* Call this from the main activity to send data to the remote device */
public void write(byte[] bytes) {
try {
mmOutStream.write(bytes);
} catch (IOException e) {
}
}
/* Call this from the main activity to shutdown the connection */
public void cancel() {
try {
mmSocket.close();
setStatusText(-1);
} catch (IOException e) {
}
}
}
Can you guys give me any information about how to correctly parse this raw data? I think I should have a stream of float values, but instead I've just this random stuff.
Suggestion on how to get some usable log output first:
void readAndPublishRaw(){
byte[] buffer = new byte[1024]; // buffer store for the stream
int bytes; // bytes returned from read()
Log.v("result", "Start reading...");
// Keep listening to the InputStream until an exception occurs
while (true) {
try {
// Read from the InputStream
bytes = mmInStream.read(buffer);
// Send the obtained bytes to the UI activity
final StringBuilder sb = new StringBuilder();
sb.append("Received ").append(bytes).append(" bytes: ");
for ( int i = 0; i < bytes; i++ ) {
sb.append( Integer.toHexString(((int)buffer[i]) & 0xff) ).append(", ");
}
Log.v("result", sb.toString());
} catch (IOException e) {
break;
}
}
}
The next step should be to calibrate the data, i.e. make note of which input/display value yields what raw data. From there, you may or may not be able to infer the actual encoding.
Your device may or may not include other information besides the actual measurement in the data, e.g. to indicate a low battery. That would have to be factored out to get raw measurement values.
If one data value comprises more than one byte the byte-order (little- or big-endian) needs to be determined.
Often floating point data of small devices is represented in a fixed point representation. Sometimes, esp. if negative numbers are needed too, with an offset added, so that
realValue = rawValue * a + c
If you find out a and c you're good. Hence, once you can relate only two different realValues and corresponding rawValues from the calibration done above, you have enough data to solve the equation for a and c.
Devices with a little more "punch", embedded linux devices for instance, may also use regular IEEE floating point data. - Not that small embedded devices cannot use IEEE, but floating point is often more than is required and comes at the price of higher complexity (memory & CPU) for the floating point emulation code.
I have been using InputStream.read( byte[] b, int off, int len ) method to read in data, but now have run into a timeout problem. I am sometimes expecting timeouts from reading, and should have the program adjust itself accordingly after a timeout. I have tried to implement a Thread but I really know nothing about Threads and cannot get it to work. I also want to add that this thread is being initialized within another thread. I'm not sure what the implications of this are but it may cause a problem.
My initial code had worked for the majority of times I need to read, but whenever I'm expecting a timeout, my program freezes at the read() call and never times out. When I implemented this new code, the times when my initial code worked now time out. I use Thread.wait(500) which I assume is 500 milliseconds, but I cannot find any Javadocs including the wait() function. Here and Here.
Other posts relating to this: 1, 2, 3.
I have also looked into declaring a timeout for the BluetoothSocket, but I cannot find it anywhere in the documentation.
Here is what my initial code looks like:
public void run(int length) throws IOException {
buffer = new byte[1024];
try {
bytes = mmInStream.read(buffer, 0, length);
mHandler.obtainMessage(MainMenu.MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
Message msg = mHandler.obtainMessage(MainMenu.MESSAGE_TOAST);
Bundle bundle = new Bundle();
bundle.putString( TOAST, "Device has disconnected from the Bluetooth Module." );
msg.setData(bundle);
mHandler.sendMessage(msg);
connectionLost();
BluetoothService.this.start();
}
This is what I have tried to implement:
public void run(int length) throws IOException {
buffer = new byte[1024];
length1 = length;
Thread myThread = new Thread(new Runnable() {
public void run() {
try {
bytes = mmInStream.read( buffer, 0, length1 );
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
synchronized (myThread) {
myThread.start();
try {
myThread.wait(500);
if(myThread.isAlive()) {
mmInStream.close();
Log.i( "InStream", "Timeout exceeded!");
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
myThread.run();
mHandler.obtainMessage(MainMenu.MESSAGE_READ, bytes, -1, buffer)
.sendToTarget();
} catch (IOException e) {
Message msg = mHandler.obtainMessage(MainMenu.MESSAGE_TOAST);
Bundle bundle = new Bundle();
bundle.putString( TOAST, "Device has disconnected from the Bluetooth Module." );
msg.setData(bundle);
mHandler.sendMessage(msg);
connectionLost();
BluetoothService.this.start();
}
EDIT:
So I'm trying to recreate
buffer = new byte[1024];
bytes = mmInStream.read(buffer, 0, length);
I have done the following:
Scanner scan = new Scanner(new InputStreamReader(mmInStream));
String readIn;
try {
readIn = scan.next();
bytes = 5; // I tried with or without this, since I do not think it matters...
buffer = readIn.getBytes( Charset.forName( "US-ASCII" ) );
}
Later in my code I make this call....Sorry edit again, the buf=read( 5 ) call goes to what is shown above.
byte[] buf = buffer;
write( a );
buf = read( 5 );
Log.i(TAG, "Before buf[5]" );
try {
buf[5] = '\0';
} catch( NullPointerException e ) {
return false;
}
When I use the original method, It passes this buf[5] call fine. But when I use the new method, it gives me an IndexOutOfBoundsException at that spot. Am I missing something? The expected input should be CMD\r\n
The bluetooth chat example is really poor in this respect, you should use an input scanner instead of mmInStream.read. Here's what I use and it works reasonably well...
For your use case you skip the entire buffer and byte and write and read (no need to use any of those when you are using a scanner and inputstreamreader as those handle that stuff for you)... in other words the below code takes care of all that for you. I changed the delimiter for you to CRLF. What the code below does is you send a string and it writes it and then reads. If you don't need to send anything to the remote device, just start at scan = new Scanner. Each time a line is read and it ends with \r\n it will store it in the string instring.
So if you want to send "a", you would write
String readIn = beginListenForData("a");
The a will be sent under the mmOutStream and then the scanner will read the mmInStream and collect all the characters, then once it sees a CRLF it will return the characters it read and return them in your readIn string. Make sense?
private String beginListenForData(String msg0) {
msg0 += "\r"; //this adds a return character to the string, you can omit this if you just send an a and the remote device understands what that means.
String instring = "";
try {
mmOutStream.write(msg0.getBytes());
} catch (IOException ex) {
stop();
}
scan = new Scanner(new InputStreamReader(mmInStream));
scan.useDelimiter(Pattern.compile("[\\r\\n]+"));
instring = scan.next();
scan = null;
return instring;
}
I've got a bit of an issue and I've been asking regarding it quite a few times, but I think I'm one step closer now, so hopefully someone can help me with the rest.
My previous questions:
Connect to NAS device from Android
How to open files in Android with default viewer using jCIFS
Put simply - I want to create an application that:
Can connect to a NAS device using jCIFS
Is capable of launching files in the default viewer - i.e. a video in the video player
The first part is relatively easy and I've already done that, but the second part is what's troubling me and what I've asked about a few times before. I think I've made some progress though.
I think I need to use a ServerSocket in my application to somehow create a bridge between the NAS and the application that's playing the content. I'm thinking this could be done using a Service. The files from the NAS device can be accessed as a FileInputStream.
There are plenty of applications on Market (i.e. ES File Explorer) that are capable of doing this without root access, so I know it's possible - at the moment I just don't know how.
I've been looking at Logcat while using some of the aforementioned applications, and they all seem to be creating a local server and then launch a video Intent from that server. How can this be achieved?
Basic answer is to use SmbFileInputStream to get InputStream You probably use this.
Now the tricky part is how to offer InputStream to other apps.
One possible approach, how many apps provide streaming of any InputStream to other apps on device, is to use http: URL scheme, and tunel your stream over http.
Then apps that can handle http URLs can open and use your data.
For this you have to make some kind of http server, which sounds difficult, but actually is achievable task. Good source to start with is nanohttpd library which is just one java source, originally used to list files in dirs, but you can adapt it to stream your InputStream over http. That's what I did with success.
Your url would look like http:// localhost:12345 where 12345 is port on which your server listens for requests. This port may be obtained from ServerSocket.getLocalPort(). Then give this URL to some app and your server waits for connection and sends data.
A note about http streaming: some apps (e.g. video players) like seekable http streams (http Range header). Since you can get also SmbRandomAccessFile, you can make your tiny server to provide any part of data in file. Android's built-in video player needs such seekable http stream in order to allow seeking in video file, otherwise it gives "Video can't be played" error. Your server must be ready to handle disconnects and multiple connects with different Range values.
Basic tasks of http server:
create ServerSocket
create Thread waiting for connection (Socket accept = serverSocket.accept()), one thread may be ok since you'd handle single client at a time
read http request (socket.getInputStream()), mainly check GET method and Range header)
send headers, mainly Content-Type, Content-Length, Accept-Ranges, Content-Range headers
send actual binary data, which is plain copying of InputStream (file) to OutputStream (socket)
handle disconnects, errors, exceptions
Good luck in implementation.
EDIT:
Here's my class that does the thing. It references some non-present classes for file, which should be trivial for you to replace by your file class.
/**
* This is simple HTTP local server for streaming InputStream to apps which are capable to read data from url.
* Random access input stream is optionally supported, depending if file can be opened in this mode.
*/
public class StreamOverHttp{
private static final boolean debug = false;
private final Browser.FileEntry file;
private final String fileMimeType;
private final ServerSocket serverSocket;
private Thread mainThread;
/**
* Some HTTP response status codes
*/
private static final String
HTTP_BADREQUEST = "400 Bad Request",
HTTP_416 = "416 Range not satisfiable",
HTTP_INTERNALERROR = "500 Internal Server Error";
public StreamOverHttp(Browser.FileEntry f, String forceMimeType) throws IOException{
file = f;
fileMimeType = forceMimeType!=null ? forceMimeType : file.mimeType;
serverSocket = new ServerSocket(0);
mainThread = new Thread(new Runnable(){
#Override
public void run(){
try{
while(true) {
Socket accept = serverSocket.accept();
new HttpSession(accept);
}
}catch(IOException e){
e.printStackTrace();
}
}
});
mainThread.setName("Stream over HTTP");
mainThread.setDaemon(true);
mainThread.start();
}
private class HttpSession implements Runnable{
private boolean canSeek;
private InputStream is;
private final Socket socket;
HttpSession(Socket s){
socket = s;
BrowserUtils.LOGRUN("Stream over localhost: serving request on "+s.getInetAddress());
Thread t = new Thread(this, "Http response");
t.setDaemon(true);
t.start();
}
#Override
public void run(){
try{
openInputStream();
handleResponse(socket);
}catch(IOException e){
e.printStackTrace();
}finally {
if(is!=null) {
try{
is.close();
}catch(IOException e){
e.printStackTrace();
}
}
}
}
private void openInputStream() throws IOException{
// openRandomAccessInputStream must return RandomAccessInputStream if file is ssekable, null otherwise
is = openRandomAccessInputStream(file);
if(is!=null)
canSeek = true;
else
is = openInputStream(file, 0);
}
private void handleResponse(Socket socket){
try{
InputStream inS = socket.getInputStream();
if(inS == null)
return;
byte[] buf = new byte[8192];
int rlen = inS.read(buf, 0, buf.length);
if(rlen <= 0)
return;
// Create a BufferedReader for parsing the header.
ByteArrayInputStream hbis = new ByteArrayInputStream(buf, 0, rlen);
BufferedReader hin = new BufferedReader(new InputStreamReader(hbis));
Properties pre = new Properties();
// Decode the header into params and header java properties
if(!decodeHeader(socket, hin, pre))
return;
String range = pre.getProperty("range");
Properties headers = new Properties();
if(file.fileSize!=-1)
headers.put("Content-Length", String.valueOf(file.fileSize));
headers.put("Accept-Ranges", canSeek ? "bytes" : "none");
int sendCount;
String status;
if(range==null || !canSeek) {
status = "200 OK";
sendCount = (int)file.fileSize;
}else {
if(!range.startsWith("bytes=")){
sendError(socket, HTTP_416, null);
return;
}
if(debug)
BrowserUtils.LOGRUN(range);
range = range.substring(6);
long startFrom = 0, endAt = -1;
int minus = range.indexOf('-');
if(minus > 0){
try{
String startR = range.substring(0, minus);
startFrom = Long.parseLong(startR);
String endR = range.substring(minus + 1);
endAt = Long.parseLong(endR);
}catch(NumberFormatException nfe){
}
}
if(startFrom >= file.fileSize){
sendError(socket, HTTP_416, null);
inS.close();
return;
}
if(endAt < 0)
endAt = file.fileSize - 1;
sendCount = (int)(endAt - startFrom + 1);
if(sendCount < 0)
sendCount = 0;
status = "206 Partial Content";
((RandomAccessInputStream)is).seek(startFrom);
headers.put("Content-Length", "" + sendCount);
String rangeSpec = "bytes " + startFrom + "-" + endAt + "/" + file.fileSize;
headers.put("Content-Range", rangeSpec);
}
sendResponse(socket, status, fileMimeType, headers, is, sendCount, buf, null);
inS.close();
if(debug)
BrowserUtils.LOGRUN("Http stream finished");
}catch(IOException ioe){
if(debug)
ioe.printStackTrace();
try{
sendError(socket, HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
}catch(Throwable t){
}
}catch(InterruptedException ie){
// thrown by sendError, ignore and exit the thread
if(debug)
ie.printStackTrace();
}
}
private boolean decodeHeader(Socket socket, BufferedReader in, Properties pre) throws InterruptedException{
try{
// Read the request line
String inLine = in.readLine();
if(inLine == null)
return false;
StringTokenizer st = new StringTokenizer(inLine);
if(!st.hasMoreTokens())
sendError(socket, HTTP_BADREQUEST, "Syntax error");
String method = st.nextToken();
if(!method.equals("GET"))
return false;
if(!st.hasMoreTokens())
sendError(socket, HTTP_BADREQUEST, "Missing URI");
while(true) {
String line = in.readLine();
if(line==null)
break;
// if(debug && line.length()>0) BrowserUtils.LOGRUN(line);
int p = line.indexOf(':');
if(p<0)
continue;
final String atr = line.substring(0, p).trim().toLowerCase();
final String val = line.substring(p + 1).trim();
pre.put(atr, val);
}
}catch(IOException ioe){
sendError(socket, HTTP_INTERNALERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
}
return true;
}
}
/**
* #param fileName is display name appended to Uri, not really used (may be null), but client may display it as file name.
* #return Uri where this stream listens and servers.
*/
public Uri getUri(String fileName){
int port = serverSocket.getLocalPort();
String url = "http://localhost:"+port;
if(fileName!=null)
url += '/'+URLEncoder.encode(fileName);
return Uri.parse(url);
}
public void close(){
BrowserUtils.LOGRUN("Closing stream over http");
try{
serverSocket.close();
mainThread.join();
}catch(Exception e){
e.printStackTrace();
}
}
/**
* Returns an error message as a HTTP response and
* throws InterruptedException to stop further request processing.
*/
private static void sendError(Socket socket, String status, String msg) throws InterruptedException{
sendResponse(socket, status, "text/plain", null, null, 0, null, msg);
throw new InterruptedException();
}
private static void copyStream(InputStream in, OutputStream out, byte[] tmpBuf, long maxSize) throws IOException{
while(maxSize>0){
int count = (int)Math.min(maxSize, tmpBuf.length);
count = in.read(tmpBuf, 0, count);
if(count<0)
break;
out.write(tmpBuf, 0, count);
maxSize -= count;
}
}
/**
* Sends given response to the socket, and closes the socket.
*/
private static void sendResponse(Socket socket, String status, String mimeType, Properties header, InputStream isInput, int sendCount, byte[] buf, String errMsg){
try{
OutputStream out = socket.getOutputStream();
PrintWriter pw = new PrintWriter(out);
{
String retLine = "HTTP/1.0 " + status + " \r\n";
pw.print(retLine);
}
if(mimeType!=null) {
String mT = "Content-Type: " + mimeType + "\r\n";
pw.print(mT);
}
if(header != null){
Enumeration<?> e = header.keys();
while(e.hasMoreElements()){
String key = (String)e.nextElement();
String value = header.getProperty(key);
String l = key + ": " + value + "\r\n";
// if(debug) BrowserUtils.LOGRUN(l);
pw.print(l);
}
}
pw.print("\r\n");
pw.flush();
if(isInput!=null)
copyStream(isInput, out, buf, sendCount);
else if(errMsg!=null) {
pw.print(errMsg);
pw.flush();
}
out.flush();
out.close();
}catch(IOException e){
if(debug)
BrowserUtils.LOGRUN(e.getMessage());
}finally {
try{
socket.close();
}catch(Throwable t){
}
}
}
}
/**
* Seekable InputStream.
* Abstract, you must add implementation for your purpose.
*/
abstract class RandomAccessInputStream extends InputStream{
/**
* #return total length of stream (file)
*/
abstract long length();
/**
* Seek within stream for next read-ing.
*/
abstract void seek(long offset) throws IOException;
#Override
public int read() throws IOException{
byte[] b = new byte[1];
read(b);
return b[0]&0xff;
}
}
In Samsung S5 (Android version 5.1.1), I faced a problem of range request starting from a value greater than the file size and I solved it by setting status = "200 OK" as below:
if (startFrom >= contentLength) {
// when you receive a request from MediaPlayer that does not contain Range in the HTTP header , then it is requesting a new stream
// https://code.google.com/p/android/issues/detail?id=3031
status = "200 OK";
}
The remaining headers were left as a fresh request for the stream