安卓的ServerSocket编程JCIFS流文件 [英] Android ServerSocket programming with jCIFS streaming files

查看:498
本文介绍了安卓的ServerSocket编程JCIFS流文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个有点问题,我一直在问关于它好几次,但我觉得我是一步步接近了,所以希望有人能帮助我休息。

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.

我的previous问题:

My previous questions:

  • 连接到NAS设备从Android的
  • <一个href="http://stackoverflow.com/questions/8030769/how-to-open-files-in-android-with-default-viewer-using-jcifs">How使用JCIFS
  • 在Android开放文件的默认浏览器
  • Connect to NAS device from Android
  • How to open files in Android with default viewer using jCIFS

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

  1. 可以连接到使用JCIFS NAS设备
  2. 能够发射在默认浏览器的文件 - 即在视频播放器的视频

第一部分是比较容易的,我已经做了,但第二部分是什么困扰着我和我以前问过约了几次。我想我虽然取得了一些进展。

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.

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

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.

有很多市场上的应用程序(如 ES文件浏览器)是能够做到这一点没有root权限,所以我知道这是可能的 - 在那一刻我只是不知道如何

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.

我一直在寻找的logcat同时使用前面提到的一些应用程序,他们似乎都创建一个本地服务器,然后启动视频意图从该服务器。如何才能实现这一目标?

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?

推荐答案

基本答案是使用SmbFileInputStream获得的InputStream 您可能使用此。

Basic answer is to use SmbFileInputStream to get InputStream You probably use this.

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

Now the tricky part is how to offer InputStream to other apps.

一个可行的方法,有多少应用程序提供任何的InputStream设备上的其他应用程序的数据流,是使用 HTTP: URL方案,并TUNEL你流过的HTTP。 然后,应用程序也可以处理HTTP网址可以打开和使用您的数据。

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.

有关这一点,你必须做出某种HTTP服务器,这听起来很难,但实际上是可以实现的任务。佳源下手为 nanohttpd 库,就是一个java源,原本用于在显示目录列表的文件,但你可以修改它流你的InputStream通过HTTP。这就是我所做的成功。

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.

你的URL看起来类似于http://本地主机:12345,其中12345是在其服务器侦听请求的端口。该端口可以从ServerSocket.getLocalPort获得()。然后给这个URL的一些应用程序,服务器等待连接,并发送数据。

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.

有关HTTP的注意事项流:一些应用程序(如视频播放器),如可查找HTTP流(HTTP范围头)。既然你也可以得到SmbRandomAccessFile,你可以让你的小服务器提供文件任何部分的数据。 Android的内置的视频播放器都需要这样可查找HTTP流为了允许在视频文件,否则,它给视频无法播放的错误。你的服务器必须准备好处理断开连接,并连接多个不同的取值范围。

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.

HTTP服务器的基本任务:

Basic tasks of http server:

  1. 创建的ServerSocket
  2. 创建线程等待连接(插座接受的ServerSocket.accept =()),一个线程可能是好的,因为你处理单个客户端的时间
  3. 在读取HTTP请求(socket.getInputStream()),主要检查GET方法和范围头)
  4. 发送标题,主要内容类型,内容长度,接受,范围,内容,范围标题
  5. 在发送实际的二进制数据,这是InputStream的(文件)的普通复制到的OutputStream(插座)
  6. 在手柄断开连接,错误,异常

祝你好运实施。

编辑:

下面是我的课,做的事情。它引用一些非present类文件,它应该是微不足道的,你通过你的class文件替换。

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;
   }
}

这篇关于安卓的ServerSocket编程JCIFS流文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆