12

我有一个问题,我已经问了很多次了,但我想我现在离我更近了一步,所以希望有人能帮助我解决剩下的问题。

我之前的问题:

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

  1. 可以使用 jCIFS 连接到 NAS 设备
  2. 能够在默认查看器中启动文件 - 即视频播放器中的视频

第一部分相对容易,我已经完成了,但第二部分是困扰我的问题以及我之前曾问过几次的问题。不过我想我已经取得了一些进展。

我想我需要ServerSocket在我的应用程序中使用 a 以某种方式在 NAS 和正在播放内容的应用程序之间建立一座桥梁。我认为这可以使用Service. NAS 设备中的文件可以作为FileInputStream.

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

我的想法的插图

我在使用上述一些应用程序时一直在查看 Logcat,它们似乎都在创建本地服务器,然后Intent从该服务器启动视频。如何做到这一点?

4

2 回答 2

20

基本答案是使用 SmbFileInputStream 来获取InputStream你可能会使用这个。

现在棘手的部分是如何将 InputStream 提供给其他应用程序。

一种可能的方法是,有多少应用程序将任何 InputStream 流式传输到设备上的其他应用程序,是使用http: URL 方案,并通过 http 调谐您的流。然后可以处理 http URL 的应用程序可以打开和使用您的数据。

为此,您必须制作某种 http 服务器,这听起来很困难,但实际上是可以完成的任务。好的来源是nanohttpd库,它只是一个 java 源,最初用于列出 dirs 中的文件,但您可以调整它以通过 http 流式传输 InputStream。这就是我成功的做法。

您的 url 看起来像 http://localhost:12345 其中 12345 是您的服务器侦听请求的端口。该端口可以从 ServerSocket.getLocalPort() 中获得。然后将此 URL 提供给某个应用程序,您的服务器将等待连接并发送数据。

关于 http 流的说明:一些应用程序(例如视频播放器)喜欢可搜索的 http 流(http Range 标头)。由于您还可以获得 SmbRandomAccessFile,因此您可以让您的小型服务器提供文件中的任何部分数据。Android 的内置视频播放器需要这种可搜索的 http 流才能允许在视频文件中搜索,否则会出现“视频无法播放”错误。您的服务器必须准备好处理具有不同 Range 值的断开连接和多次连接。

http服务器的基本任务:

  1. 创建服务器套接字
  2. 创建等待连接的线程(Socket accept = serverSocket.accept()),一个线程可能没问题,因为您一次只处理一个客户端
  3. 读取http请求(socket.getInputStream()),主要检查GET方法和Range头)
  4. 发送 headers,主要是 Content-Type、Content-Length、Accept-Ranges、Content-Range headers
  5. 发送实际的二进制数据,即 InputStream(文件)到 OutputStream(套接字)的普通复制
  6. 处理断开连接、错误、异常

祝执行顺利。

编辑:

这是我的班级。它为文件引用了一些不存在的类,对于您来说替换为文件类应该是微不足道的。

/**
 * 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;
   }
}
于 2012-02-01T12:59:42.160 回答
1

在三星 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";
}

剩余的标头作为对流的新请求留下

于 2016-04-07T19:18:55.893 回答