HTTP流服务器在Google chrome和firefox上表现良好,但无法流到MX Player网络流

时间:2018-09-11 15:41:06

标签: java android http server streaming

我试图在我的android设备中为我的局域网创建一个HTTP流服务器。我对this code进行了一些小修改,以使服务器正常工作。我的服务器可以与Google Chrome android和Firefox等浏览器完美配合。但是它无法在MX Player中流式传输。我已经测试了PC的纯JAVA版本。它适用于浏览器,但在那里使用VLC的效果不佳。几秒钟后,有些视频会在几分钟后叠加。谁能告诉我为什么它不能与媒体播放器一起使用?

我的服务器类:

public class AndroidHTTPStreamServer implements Runnable {

    private static final String TAG = "AndroidHTTPServer";
    private int SERVER_PORT = 0;
    private Thread thread;
    private boolean isRunning;
    private ServerSocket socket;
    private int port;

    public AndroidHTTPStreamServer() {

        // Create listening socket
        try {
            socket = new ServerSocket(SERVER_PORT/*, 0, InetAddress.getByAddress(new byte[] {127,0,0,1})*/);
            //socket.setSoTimeout(5000);
            port = socket.getLocalPort();
        } catch (UnknownHostException e) { // impossible
        } catch (IOException e) {
            Log.e(TAG, "IOException initializing server", e);
        }

    }

    public String getUrl(String path, String key, String iv) throws Exception {
        String url = String.format("http://127.0.0.1:%d%s?key=%s&iv=%s", port, path, URLEncoder.encode(key, "UTF-8"), iv);

        return url;
    }

    public void start() {
        thread = new Thread(this);
        thread.start();
    }

    public void stop() {
        isRunning = false;
        thread.interrupt();
        try {
            thread.join(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        Looper.prepare();
        isRunning = true;
        while (isRunning) {
            try {
                Socket client = socket.accept();
                if (client == null) {
                    continue;
                }
                Log.d(TAG, "client connected");

                StreamToMediaPlayerTask task = new StreamToMediaPlayerTask(client);
                if (task.processRequest()) {
                    task.execute();
                }

            } catch (SocketTimeoutException e) {
                // Do nothing
            } catch (IOException e) {
                Log.e(TAG, "Error connecting to client", e);
            }
        }
        Log.d(TAG, "Proxy interrupted. Shutting down.");
    }




    private class StreamToMediaPlayerTask {

        Socket client;
        long cbSkip;
        private Properties parameters;
        private Properties request;
        private Properties requestHeaders;
        private String filePath;

        public StreamToMediaPlayerTask(Socket client) {
            this.client = client;
        }

        public boolean processRequest() throws IOException {
            // Read HTTP headers
            InputStream is = client.getInputStream();
            final int bufferSize = 8192;
            byte[] buffer = new byte[bufferSize];
            int splitByte = 0;
            int readLength = 0;
            {
                int read = is.read(buffer, 0, bufferSize);
                while (read > 0) {
                    readLength += read;
                    splitByte = findHeaderEnd(buffer, readLength);
                    if (splitByte > 0)
                        break;
                    read = is.read(buffer, readLength, bufferSize - readLength);
                }
            }

            // Create a BufferedReader for parsing the header.
            ByteArrayInputStream hbis = new ByteArrayInputStream(buffer, 0, readLength);
            BufferedReader hin = new BufferedReader(new InputStreamReader(hbis));
            request = new Properties();
            parameters = new Properties();
            requestHeaders = new Properties();

            try {
                decodeHeader(hin, request, parameters, requestHeaders);
            } catch (InterruptedException e1) {
                Log.e(TAG, "Exception: " + e1.getMessage());
                e1.printStackTrace();
            }
            for (Map.Entry<Object, Object> e : requestHeaders.entrySet()) {
                Log.i(TAG, "Header: " + e.getKey() + " : " + e.getValue());
            }

            String range = requestHeaders.getProperty("range");
            if (range != null) {
                Log.i(TAG, "range is: " + range);
                range = range.substring(6);
                int charPos = range.indexOf('-');
                if (charPos > 0) {
                    range = range.substring(0, charPos);
                }
                cbSkip = Long.parseLong(range);
                Log.i(TAG, "range found!! " + cbSkip);
            }

            if(!request.get("method").equals("GET")) {
                Log.e(TAG, "Only GET is supported");
                return false;
            }

            filePath = request.getProperty("uri");

            return true;
        }

        protected void execute() {
            ExternalResourceDataSource dataSource = new ExternalResourceDataSource(new File(filePath), parameters.getProperty("key"), parameters.getProperty("iv"));
            long fileSize = dataSource.getContentLength();

            String headers = "";
            if (cbSkip > 0) {// It is a seek or skip request if there's a Range
                // header
                headers += "HTTP/1.1 206 Partial Content\r\n";
                headers += "Content-Type: " + dataSource.getContentType() + "\r\n";
                headers += "Accept-Ranges: bytes\r\n";
                headers += "Content-Length: " + (fileSize - cbSkip) + "\r\n";
                headers += "Content-Range: bytes " + cbSkip + "-" + (fileSize - 1) + "/" + fileSize + "\r\n";
                headers += "Connection: Keep-Alive\r\n";
                headers += "\r\n";
            } else {
                headers += "HTTP/1.1 200 OK\r\n";
                headers += "Content-Type: " + dataSource.getContentType() + "\r\n";
                headers += "Accept-Ranges: bytes\r\n";
                headers += "Content-Length: " + fileSize + "\r\n";
                headers += "Connection: Keep-Alive\r\n";
                headers += "\r\n";
            }

            Log.i(TAG, "headers: " + headers);

            OutputStream output = null;
            byte[] buff = new byte[64 * 1024];
            try {
                output = new BufferedOutputStream(client.getOutputStream(), 32 * 1024);
                output.write(headers.getBytes());
                InputStream data = dataSource.getInputStream();

                dataSource.skipFully(data, cbSkip);//try to skip as much as possible

                // Loop as long as there's stuff to send and client has not closed
                int cbRead;
                while (!client.isClosed() && (cbRead = data.read(buff, 0, buff.length)) != -1) {
                    output.write(buff, 0, cbRead);
                }
            }
            catch (SocketException socketException) {
                Log.e(TAG, "SocketException() thrown, proxy client has probably closed. This can exit harmlessly");
            }
            catch (Exception e) {
                Log.e(TAG, "Exception thrown from streaming task:");
                Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
                //Crashlytics.logException(e);
            }

            // Cleanup
            try {
                if (output != null) {
                    output.close();
                }
                client.close();
            }
            catch (IOException e) {
                Log.e(TAG, "IOException while cleaning up streaming task:");
                Log.e(TAG, e.getClass().getName() + " : " + e.getLocalizedMessage());
                e.printStackTrace();
            }
        }

        /**
         * Find byte index separating header from body. It must be the last byte of
         * the first two sequential new lines.
         **/
        private int findHeaderEnd(final byte[] buf, int rlen) {
            int splitbyte = 0;
            while (splitbyte + 3 < rlen) {
                if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n'
                        && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n')
                    return splitbyte + 4;
                splitbyte++;
            }
            return 0;
        }


        /**
         * Decodes the sent headers and loads the data into java Properties' key -
         * value pairs
         **/
        private void decodeHeader(BufferedReader in, Properties pre,
                                  Properties parms, Properties header) throws InterruptedException {
            try {
                // Read the request line
                String inLine = in.readLine();
                if (inLine == null)
                    return;
                StringTokenizer st = new StringTokenizer(inLine);
                if (!st.hasMoreTokens())
                    Log.e(TAG,
                            "BAD REQUEST: Syntax error. Usage: GET /example/file.html");

                String method = st.nextToken();
                pre.put("method", method);

                if (!st.hasMoreTokens())
                    Log.e(TAG,
                            "BAD REQUEST: Missing URI. Usage: GET /example/file.html");

                String uri = st.nextToken();

                // Decode parameters from the URI
                int qmi = uri.indexOf('?');
                if (qmi >= 0) {
                    decodeParms(uri.substring(qmi + 1), parms);
                    uri = decodePercent(uri.substring(0, qmi));
                } else
                    uri = decodePercent(uri);

                // If there's another token, it's protocol version,
                // followed by HTTP headers. Ignore version but parse headers.
                // NOTE: this now forces header names lowercase since they are
                // case insensitive and vary by client.
                if (st.hasMoreTokens()) {
                    String line = in.readLine();
                    while (line != null && line.trim().length() > 0) {
                        int p = line.indexOf(':');
                        if (p >= 0)
                            header.put(line.substring(0, p).trim().toLowerCase(),
                                    line.substring(p + 1).trim());
                        line = in.readLine();
                    }
                }

                pre.put("uri", uri);
            } catch (IOException ioe) {
                Log.e(TAG,
                        "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
            }
        }

        /**
         * Decodes parameters in percent-encoded URI-format ( e.g.
         * "name=Jack%20Daniels&pass=Single%20Malt" ) and adds them to given
         * Properties. NOTE: this doesn't support multiple identical keys due to the
         * simplicity of Properties -- if you need multiples, you might want to
         * replace the Properties with a Hashtable of Vectors or such.
         */
        private void decodeParms(String parms, Properties p)
                throws InterruptedException {
            if (parms == null)
                return;

            StringTokenizer st = new StringTokenizer(parms, "&");
            while (st.hasMoreTokens()) {
                String e = st.nextToken();
                int sep = e.indexOf('=');
                if (sep >= 0)
                    p.put(decodePercent(e.substring(0, sep)).trim(),
                            decodePercent(e.substring(sep + 1)));
            }
        }

        /**
         * Decodes the percent encoding scheme. <br/>
         * For example: "an+example%20string" -> "an example string"
         */
        private String decodePercent(String str) throws InterruptedException {
            try {
                StringBuffer sb = new StringBuffer();
                for (int i = 0; i < str.length(); i++) {
                    char c = str.charAt(i);
                    switch (c) {
                        case '+':
                            sb.append(' ');
                            break;
                        case '%':
                            sb.append((char) Integer.parseInt(
                                    str.substring(i + 1, i + 3), 16));
                            i += 2;
                            break;
                        default:
                            sb.append(c);
                            break;
                    }
                }
                return sb.toString();
            } catch (Exception e) {
                Log.e(TAG, "BAD REQUEST: Bad percent-encoding.");
                return null;
            }
        }


    }



    /**
     * provides meta-data and access to a stream for resources on SD card.
     */
    protected class ExternalResourceDataSource {

        private final String mKey;
        private final String mIV;
        private final File mFileResource;
        long contentLength;

        public ExternalResourceDataSource(File resource, String key, String iv) {
            //it is your duty to ensure that the file exists
            mFileResource = new File(Environment.getExternalStorageDirectory(),resource.getPath());
            mKey = key;
            mIV = iv;
            contentLength = mFileResource.length();
            Log.i(TAG, "path: " + mFileResource.getPath() + ", exists: " + mFileResource.exists() + ", length: " + contentLength);
        }

        /**
         * Discards {@code n} bytes of data from the input stream. This method
         * will block until the full amount has been skipped. Does not close the
         * stream.
         *
         * @param in the input stream to read from
         * @param n the number of bytes to skip
         * @throws EOFException if this stream reaches the end before skipping all
         *     the bytes
         * @throws IOException if an I/O error occurs, or the stream does not
         *     support skipping
         */
        public void skipFully(InputStream in, long n) throws IOException {
            while (n > 0) {
                long amt = in.skip(n);
                if (amt == 0) {
                    // Force a blocking read to avoid infinite loop
                    if (in.read() == -1) {
                        throw new EOFException();
                    }
                    n--;
                } else {
                    n -= amt;
                }
            }
        }


        /**
         * Returns a MIME-compatible content type (e.g. "text/html") for the
         * resource. This method must be implemented.
         *
         * @return A MIME content type.
         */
        public String getContentType() {
            String MIME=null;
            String ext=getFileExt(mFileResource.getName());
            if(ext.equals("mp3"))return "audio/mpeg";
            else if(ext.equals("mp4"))return "video/mp4";
            else if(ext.equals("mkv"))return "video/x-matroska";
            else if(ext.equals("amr"))return "audio/AMR";
            else if(ext.equals("flv"))return "video/x-flv";

            return "audio/mpeg";
        }

        public String getFileExt(String fileName){
            String extension = "";

            int i = fileName.lastIndexOf('.');
            if (i > 0) {
                extension = fileName.substring(i + 1);
            }
            return extension;
        }

        /**
         * Returns the length of resource in bytes.
         *
         * By default this returns -1, which causes no content-type header to be
         * sent to the client. This would make sense for a stream content of
         * unknown or undefined length. If your resource has a defined length
         * you should override this method and return that.
         *
         * @return The length of the resource in bytes.
         */
        public long getContentLength() {
            return contentLength;
        }

        public InputStream getInputStream() {
            try {
                return new  FileInputStream(mFileResource);
            } catch (Exception e) {
                Log.e(TAG, "failed to create input stream", e);
            }

            return null;
        }
    }
}

0 个答案:

没有答案