我有一个问题,我已经问了很多次,但我觉得我现在离我们更近了一步,所以希望有人可以帮助我完成剩下的工作。
我以前的问题:
简单地说 - 我想创建一个应用程序:
第一部分相对容易,我已经做到了,但第二部分是令我不安的问题,以及我之前几次问过的问题。我想我已经取得了一些进展。
我想我需要在我的应用程序中使用ServerSocket
以某种方式在NAS和正在播放内容的应用程序之间建立桥梁。我想这可以使用Service
来完成。来自NAS设备的文件可以作为FileInputStream
进行访问。
市场上有很多应用程序(即ES File Explorer)能够在没有root权限的情况下执行此操作,所以我知道这是可能的 - 目前我还不知道如何。
我在使用上述某些应用程序时一直在研究Logcat,他们似乎都在创建本地服务器,然后从该服务器启动视频Intent
。如何实现这一目标?
答案 0 :(得分:20)
基本答案是使用SmbFileInputStream来获取 InputStream 你可能会使用它。
现在棘手的部分是如何将InputStream提供给其他应用程序。
一种可能的方法,即有多少应用程序将任何InputStream流式传输到设备上的其他应用程序,是使用 http: URL方案,并将您的流转换为http。 然后,可以处理http URL的应用程序可以打开并使用您的数据。
为此,你必须制作某种http服务器,这听起来很难,但实际上是可以实现的任务。好的开始是 nanohttpd 库,它只是一个java源,最初用于列出dirs中的文件,但您可以调整它以通过http流式传输InputStream。这就是我成功的原因。
您的网址将类似于http:// localhost:12345,其中12345是您的服务器侦听请求的端口。可以从ServerSocket.getLocalPort()获取此端口。然后将此URL提供给某个应用程序,您的服务器将等待连接并发送数据。
关于http流媒体的说明:一些应用程序(例如视频播放器),例如可搜索的http流(http Range标头)。既然你也可以获得SmbRandomAccessFile,你可以让你的小服务器提供文件中的任何数据部分。 Android的内置视频播放器需要这样的可搜索http流才能在视频文件中搜索,否则会出现“视频无法播放”的错误。您的服务器必须准备好处理断开连接和多个具有不同Range值的连接。
http服务器的基本任务:
祝好运实施。
编辑:
这是我的班级做的事情。它引用了一些不存在的文件类,对于你用文件类替换它应该是微不足道的。
/**
* 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;
}
}
答案 1 :(得分:1)
在Samsung S5(Android版本5.1.1)中,我遇到了范围请求从大于文件大小的值开始的问题,我通过设置status =“200 OK”解决了它,如下所示:
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";
}
剩余的标题留作了对流的新请求