使用jCIFS流文件进行Android ServerSocket编程

时间:2012-01-30 00:32:36

标签: android serversocket smb jcifs

我有一个问题,我已经问了很多次,但我觉得我现在离我们更近了一步,所以希望有人可以帮助我完成剩下的工作。

我以前的问题:

简单地说 - 我想创建一个应用程序:

  1. 可以使用jCIFS连接到NAS设备
  2. 能够在默认查看器中启动文件 - 即视频播放器中的视频
  3. 第一部分相对容易,我已经做到了,但第二部分是令我不安的问题,以及我之前几次问过的问题。我想我已经取得了一些进展。

    我想我需要在我的应用程序中使用ServerSocket以某种方式在NAS和正在播放内容的应用程序之间建立桥梁。我想这可以使用Service来完成。来自NAS设备的文件可以作为FileInputStream进行访问。

    市场上有很多应用程序(即ES File Explorer)能够在没有root权限的情况下执行此操作,所以我知道这是可能的 - 目前我还不知道如何。

    Illustration of my idea

    我在使用上述某些应用程序时一直在研究Logcat,他们似乎都在创建本地服务器,然后从该服务器启动视频Intent。如何实现这一目标?

2 个答案:

答案 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服务器的基本任务:

  1. 创建ServerSocket
  2. 创建线程等待连接(Socket accept = serverSocket.accept()),一个线程可能没问题,因为你一次只处理一个客户端
  3. 读取http请求(socket.getInputStream()),主要检查GET方法和Range标头)
  4. 发送标头,主要是内容类型,内容长度,接受范围,内容范围标头
  5. 发送实际二进制数据,即将InputStream(文件)简单复制到OutputStream(套接字)
  6. 处理断开连接,错误,异常
  7. 祝好运实施。

    编辑:

    这是我的班级做的事情。它引用了一些不存在的文件类,对于你用文件类替换它应该是微不足道的。

    /**
     * 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";
}

剩余的标题留作了对流的新请求