Jelajahi Sumber

Replace gradle depo to java files

cjl_macbook 6 tahun lalu
induk
melakukan
686b42d695
40 mengubah file dengan 4256 tambahan dan 3 penghapusan
  1. 0 3
      android/build.gradle
  2. 121 0
      android/src/main/java/tv/danmaku/ijk/media/player/AbstractMediaPlayer.java
  3. 437 0
      android/src/main/java/tv/danmaku/ijk/media/player/AndroidMediaPlayer.java
  4. 215 0
      android/src/main/java/tv/danmaku/ijk/media/player/IMediaPlayer.java
  5. 28 0
      android/src/main/java/tv/danmaku/ijk/media/player/ISurfaceTextureHolder.java
  6. 24 0
      android/src/main/java/tv/danmaku/ijk/media/player/ISurfaceTextureHost.java
  7. 23 0
      android/src/main/java/tv/danmaku/ijk/media/player/IjkLibLoader.java
  8. 293 0
      android/src/main/java/tv/danmaku/ijk/media/player/IjkMediaCodecInfo.java
  9. 401 0
      android/src/main/java/tv/danmaku/ijk/media/player/IjkMediaMeta.java
  10. 1291 0
      android/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java
  11. 39 0
      android/src/main/java/tv/danmaku/ijk/media/player/IjkTimedText.java
  12. 30 0
      android/src/main/java/tv/danmaku/ijk/media/player/MediaInfo.java
  13. 339 0
      android/src/main/java/tv/danmaku/ijk/media/player/MediaPlayerProxy.java
  14. 100 0
      android/src/main/java/tv/danmaku/ijk/media/player/TextureMediaPlayer.java
  15. 32 0
      android/src/main/java/tv/danmaku/ijk/media/player/annotations/AccessedByNative.java
  16. 36 0
      android/src/main/java/tv/danmaku/ijk/media/player/annotations/CalledByNative.java
  17. 22 0
      android/src/main/java/tv/danmaku/ijk/media/player/exceptions/IjkMediaException.java
  18. 5 0
      android/src/main/java/tv/danmaku/ijk/media/player/ffmpeg/FFmpegApi.java
  19. 63 0
      android/src/main/java/tv/danmaku/ijk/media/player/misc/AndroidMediaFormat.java
  20. 109 0
      android/src/main/java/tv/danmaku/ijk/media/player/misc/AndroidTrackInfo.java
  21. 28 0
      android/src/main/java/tv/danmaku/ijk/media/player/misc/IAndroidIO.java
  22. 29 0
      android/src/main/java/tv/danmaku/ijk/media/player/misc/IMediaDataSource.java
  23. 31 0
      android/src/main/java/tv/danmaku/ijk/media/player/misc/IMediaFormat.java
  24. 35 0
      android/src/main/java/tv/danmaku/ijk/media/player/misc/ITrackInfo.java
  25. 259 0
      android/src/main/java/tv/danmaku/ijk/media/player/misc/IjkMediaFormat.java
  26. 99 0
      android/src/main/java/tv/danmaku/ijk/media/player/misc/IjkTrackInfo.java
  27. 143 0
      android/src/main/java/tv/danmaku/ijk/media/player/pragma/DebugLog.java
  28. 24 0
      android/src/main/java/tv/danmaku/ijk/media/player/pragma/Pragma.java
  29. TEMPAT SAMPAH
      android/src/main/libs/arm64-v8a/libijkffmpeg.so
  30. TEMPAT SAMPAH
      android/src/main/libs/arm64-v8a/libijkplayer.so
  31. TEMPAT SAMPAH
      android/src/main/libs/arm64-v8a/libijksdl.so
  32. TEMPAT SAMPAH
      android/src/main/libs/armeabi-v7a/libijkffmpeg.so
  33. TEMPAT SAMPAH
      android/src/main/libs/armeabi-v7a/libijkplayer.so
  34. TEMPAT SAMPAH
      android/src/main/libs/armeabi-v7a/libijksdl.so
  35. TEMPAT SAMPAH
      android/src/main/libs/x86/libijkffmpeg.so
  36. TEMPAT SAMPAH
      android/src/main/libs/x86/libijkplayer.so
  37. TEMPAT SAMPAH
      android/src/main/libs/x86/libijksdl.so
  38. TEMPAT SAMPAH
      android/src/main/libs/x86_64/libijkffmpeg.so
  39. TEMPAT SAMPAH
      android/src/main/libs/x86_64/libijkplayer.so
  40. TEMPAT SAMPAH
      android/src/main/libs/x86_64/libijksdl.so

+ 0 - 3
android/build.gradle

@@ -41,10 +41,7 @@ android {
 
 
 dependencies {
-    api 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8'
-//    api 'tv.danmaku.ijk.media:ijkplayer-armv7a:0.8.8'
     implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
-//    api 'tv.danmaku.ijk.media:ijkplayer-exo:0.8.8'
 }
 repositories {
     mavenCentral()

+ 121 - 0
android/src/main/java/tv/danmaku/ijk/media/player/AbstractMediaPlayer.java

@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2013-2014 Bilibili
+ * Copyright (C) 2013-2014 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player;
+
+import tv.danmaku.ijk.media.player.misc.IMediaDataSource;
+
+@SuppressWarnings("WeakerAccess")
+public abstract class AbstractMediaPlayer implements IMediaPlayer {
+    private OnPreparedListener mOnPreparedListener;
+    private OnCompletionListener mOnCompletionListener;
+    private OnBufferingUpdateListener mOnBufferingUpdateListener;
+    private OnSeekCompleteListener mOnSeekCompleteListener;
+    private OnVideoSizeChangedListener mOnVideoSizeChangedListener;
+    private OnErrorListener mOnErrorListener;
+    private OnInfoListener mOnInfoListener;
+    private OnTimedTextListener mOnTimedTextListener;
+
+    public final void setOnPreparedListener(OnPreparedListener listener) {
+        mOnPreparedListener = listener;
+    }
+
+    public final void setOnCompletionListener(OnCompletionListener listener) {
+        mOnCompletionListener = listener;
+    }
+
+    public final void setOnBufferingUpdateListener(
+            OnBufferingUpdateListener listener) {
+        mOnBufferingUpdateListener = listener;
+    }
+
+    public final void setOnSeekCompleteListener(OnSeekCompleteListener listener) {
+        mOnSeekCompleteListener = listener;
+    }
+
+    public final void setOnVideoSizeChangedListener(
+            OnVideoSizeChangedListener listener) {
+        mOnVideoSizeChangedListener = listener;
+    }
+
+    public final void setOnErrorListener(OnErrorListener listener) {
+        mOnErrorListener = listener;
+    }
+
+    public final void setOnInfoListener(OnInfoListener listener) {
+        mOnInfoListener = listener;
+    }
+
+    public final void setOnTimedTextListener(OnTimedTextListener listener) {
+        mOnTimedTextListener = listener;
+    }
+
+    public void resetListeners() {
+        mOnPreparedListener = null;
+        mOnBufferingUpdateListener = null;
+        mOnCompletionListener = null;
+        mOnSeekCompleteListener = null;
+        mOnVideoSizeChangedListener = null;
+        mOnErrorListener = null;
+        mOnInfoListener = null;
+        mOnTimedTextListener = null;
+    }
+
+    protected final void notifyOnPrepared() {
+        if (mOnPreparedListener != null)
+            mOnPreparedListener.onPrepared(this);
+    }
+
+    protected final void notifyOnCompletion() {
+        if (mOnCompletionListener != null)
+            mOnCompletionListener.onCompletion(this);
+    }
+
+    protected final void notifyOnBufferingUpdate(int percent) {
+        if (mOnBufferingUpdateListener != null)
+            mOnBufferingUpdateListener.onBufferingUpdate(this, percent);
+    }
+
+    protected final void notifyOnSeekComplete() {
+        if (mOnSeekCompleteListener != null)
+            mOnSeekCompleteListener.onSeekComplete(this);
+    }
+
+    protected final void notifyOnVideoSizeChanged(int width, int height,
+                                                  int sarNum, int sarDen) {
+        if (mOnVideoSizeChangedListener != null)
+            mOnVideoSizeChangedListener.onVideoSizeChanged(this, width, height,
+                    sarNum, sarDen);
+    }
+
+    protected final boolean notifyOnError(int what, int extra) {
+        return mOnErrorListener != null && mOnErrorListener.onError(this, what, extra);
+    }
+
+    protected final boolean notifyOnInfo(int what, int extra) {
+        return mOnInfoListener != null && mOnInfoListener.onInfo(this, what, extra);
+    }
+
+    protected final void notifyOnTimedText(IjkTimedText text) {
+        if (mOnTimedTextListener != null)
+            mOnTimedTextListener.onTimedText(this, text);
+    }
+
+    public void setDataSource(IMediaDataSource mediaDataSource) {
+        throw new UnsupportedOperationException();
+    }
+}

+ 437 - 0
android/src/main/java/tv/danmaku/ijk/media/player/AndroidMediaPlayer.java

@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2006 Bilibili
+ * Copyright (C) 2006 The Android Open Source Project
+ * Copyright (C) 2013 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.MediaDataSource;
+import android.media.MediaPlayer;
+import android.media.TimedText;
+import android.net.Uri;
+import android.os.Build;
+import android.text.TextUtils;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.util.Map;
+
+import tv.danmaku.ijk.media.player.misc.AndroidTrackInfo;
+import tv.danmaku.ijk.media.player.misc.IMediaDataSource;
+import tv.danmaku.ijk.media.player.misc.ITrackInfo;
+import tv.danmaku.ijk.media.player.pragma.DebugLog;
+
+public class AndroidMediaPlayer extends AbstractMediaPlayer {
+    private final MediaPlayer mInternalMediaPlayer;
+    private final AndroidMediaPlayerListenerHolder mInternalListenerAdapter;
+    private String mDataSource;
+    private MediaDataSource mMediaDataSource;
+
+    private final Object mInitLock = new Object();
+    private boolean mIsReleased;
+
+    private static MediaInfo sMediaInfo;
+
+    public AndroidMediaPlayer() {
+        synchronized (mInitLock) {
+            mInternalMediaPlayer = new MediaPlayer();
+        }
+        mInternalMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
+        mInternalListenerAdapter = new AndroidMediaPlayerListenerHolder(this);
+        attachInternalListeners();
+    }
+
+    public MediaPlayer getInternalMediaPlayer() {
+        return mInternalMediaPlayer;
+    }
+
+    @Override
+    public void setDisplay(SurfaceHolder sh) {
+        synchronized (mInitLock) {
+            if (!mIsReleased) {
+                mInternalMediaPlayer.setDisplay(sh);
+            }
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+    @Override
+    public void setSurface(Surface surface) {
+        mInternalMediaPlayer.setSurface(surface);
+    }
+
+    @Override
+    public void setDataSource(Context context, Uri uri)
+            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+        mInternalMediaPlayer.setDataSource(context, uri);
+    }
+
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+    @Override
+    public void setDataSource(Context context, Uri uri, Map<String, String> headers)
+            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+        mInternalMediaPlayer.setDataSource(context, uri, headers);
+    }
+
+    @Override
+    public void setDataSource(FileDescriptor fd)
+            throws IOException, IllegalArgumentException, IllegalStateException {
+        mInternalMediaPlayer.setDataSource(fd);
+    }
+
+    @Override
+    public void setDataSource(String path) throws IOException,
+            IllegalArgumentException, SecurityException, IllegalStateException {
+        mDataSource = path;
+
+        Uri uri = Uri.parse(path);
+        String scheme = uri.getScheme();
+        if (!TextUtils.isEmpty(scheme) && scheme.equalsIgnoreCase("file")) {
+            mInternalMediaPlayer.setDataSource(uri.getPath());
+        } else {
+            mInternalMediaPlayer.setDataSource(path);
+        }
+    }
+
+    @TargetApi(Build.VERSION_CODES.M)
+    @Override
+    public void setDataSource(IMediaDataSource mediaDataSource) {
+        releaseMediaDataSource();
+
+        mMediaDataSource = new MediaDataSourceProxy(mediaDataSource);
+        mInternalMediaPlayer.setDataSource(mMediaDataSource);
+    }
+
+    @TargetApi(Build.VERSION_CODES.M)
+    private static class MediaDataSourceProxy extends MediaDataSource {
+        private final IMediaDataSource mMediaDataSource;
+
+        public MediaDataSourceProxy(IMediaDataSource mediaDataSource) {
+            mMediaDataSource = mediaDataSource;
+        }
+
+        @Override
+        public int readAt(long position, byte[] buffer, int offset, int size) throws IOException {
+            return mMediaDataSource.readAt(position, buffer, offset, size);
+        }
+
+        @Override
+        public long getSize() throws IOException {
+            return mMediaDataSource.getSize();
+        }
+
+        @Override
+        public void close() throws IOException {
+            mMediaDataSource.close();
+        }
+    }
+
+    @Override
+    public String getDataSource() {
+        return mDataSource;
+    }
+
+    private void releaseMediaDataSource() {
+        if (mMediaDataSource != null) {
+            try {
+                mMediaDataSource.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            mMediaDataSource = null;
+        }
+    }
+
+    @Override
+    public void prepareAsync() throws IllegalStateException {
+        mInternalMediaPlayer.prepareAsync();
+    }
+
+    @Override
+    public void start() throws IllegalStateException {
+        mInternalMediaPlayer.start();
+    }
+
+    @Override
+    public void stop() throws IllegalStateException {
+        mInternalMediaPlayer.stop();
+    }
+
+    @Override
+    public void pause() throws IllegalStateException {
+        mInternalMediaPlayer.pause();
+    }
+
+    @Override
+    public void setScreenOnWhilePlaying(boolean screenOn) {
+        mInternalMediaPlayer.setScreenOnWhilePlaying(screenOn);
+    }
+
+    @Override
+    public ITrackInfo[] getTrackInfo() {
+        return AndroidTrackInfo.fromMediaPlayer(mInternalMediaPlayer);
+    }
+
+    @Override
+    public int getVideoWidth() {
+        return mInternalMediaPlayer.getVideoWidth();
+    }
+
+    @Override
+    public int getVideoHeight() {
+        return mInternalMediaPlayer.getVideoHeight();
+    }
+
+    @Override
+    public int getVideoSarNum() {
+        return 1;
+    }
+
+    @Override
+    public int getVideoSarDen() {
+        return 1;
+    }
+
+    @Override
+    public boolean isPlaying() {
+        try {
+            return mInternalMediaPlayer.isPlaying();
+        } catch (IllegalStateException e) {
+            DebugLog.printStackTrace(e);
+            return false;
+        }
+    }
+
+    @Override
+    public void seekTo(long msec) throws IllegalStateException {
+        mInternalMediaPlayer.seekTo((int) msec);
+    }
+
+    @Override
+    public long getCurrentPosition() {
+        try {
+            return mInternalMediaPlayer.getCurrentPosition();
+        } catch (IllegalStateException e) {
+            DebugLog.printStackTrace(e);
+            return 0;
+        }
+    }
+
+    @Override
+    public long getDuration() {
+        try {
+            return mInternalMediaPlayer.getDuration();
+        } catch (IllegalStateException e) {
+            DebugLog.printStackTrace(e);
+            return 0;
+        }
+    }
+
+    @Override
+    public void release() {
+        mIsReleased = true;
+        mInternalMediaPlayer.release();
+        releaseMediaDataSource();
+        resetListeners();
+        attachInternalListeners();
+    }
+
+    @Override
+    public void reset() {
+        try {
+            mInternalMediaPlayer.reset();
+        } catch (IllegalStateException e) {
+            DebugLog.printStackTrace(e);
+        }
+        releaseMediaDataSource();
+        resetListeners();
+        attachInternalListeners();
+    }
+
+    @Override
+    public void setLooping(boolean looping) {
+        mInternalMediaPlayer.setLooping(looping);
+    }
+
+    @Override
+    public boolean isLooping() {
+        return mInternalMediaPlayer.isLooping();
+    }
+
+    @Override
+    public void setVolume(float leftVolume, float rightVolume) {
+        mInternalMediaPlayer.setVolume(leftVolume, rightVolume);
+    }
+
+    @Override
+    public int getAudioSessionId() {
+        return mInternalMediaPlayer.getAudioSessionId();
+    }
+
+    @Override
+    public MediaInfo getMediaInfo() {
+        if (sMediaInfo == null) {
+            MediaInfo module = new MediaInfo();
+
+            module.mVideoDecoder = "android";
+            module.mVideoDecoderImpl = "HW";
+
+            module.mAudioDecoder = "android";
+            module.mAudioDecoderImpl = "HW";
+
+            sMediaInfo = module;
+        }
+
+        return sMediaInfo;
+    }
+
+    @Override
+    public void setLogEnabled(boolean enable) {
+    }
+
+    @Override
+    public boolean isPlayable() {
+        return true;
+    }
+
+    /*--------------------
+     * misc
+     */
+    @Override
+    public void setWakeMode(Context context, int mode) {
+        mInternalMediaPlayer.setWakeMode(context, mode);
+    }
+
+    @Override
+    public void setAudioStreamType(int streamtype) {
+        mInternalMediaPlayer.setAudioStreamType(streamtype);
+    }
+
+    @Override
+    public void setKeepInBackground(boolean keepInBackground) {
+    }
+
+    /*--------------------
+     * Listeners adapter
+     */
+    private void attachInternalListeners() {
+        mInternalMediaPlayer.setOnPreparedListener(mInternalListenerAdapter);
+        mInternalMediaPlayer
+                .setOnBufferingUpdateListener(mInternalListenerAdapter);
+        mInternalMediaPlayer.setOnCompletionListener(mInternalListenerAdapter);
+        mInternalMediaPlayer
+                .setOnSeekCompleteListener(mInternalListenerAdapter);
+        mInternalMediaPlayer
+                .setOnVideoSizeChangedListener(mInternalListenerAdapter);
+        mInternalMediaPlayer.setOnErrorListener(mInternalListenerAdapter);
+        mInternalMediaPlayer.setOnInfoListener(mInternalListenerAdapter);
+        mInternalMediaPlayer.setOnTimedTextListener(mInternalListenerAdapter);
+    }
+
+    private class AndroidMediaPlayerListenerHolder implements
+            MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener,
+            MediaPlayer.OnBufferingUpdateListener,
+            MediaPlayer.OnSeekCompleteListener,
+            MediaPlayer.OnVideoSizeChangedListener,
+            MediaPlayer.OnErrorListener, MediaPlayer.OnInfoListener,
+            MediaPlayer.OnTimedTextListener {
+        public final WeakReference<AndroidMediaPlayer> mWeakMediaPlayer;
+
+        public AndroidMediaPlayerListenerHolder(AndroidMediaPlayer mp) {
+            mWeakMediaPlayer = new WeakReference<AndroidMediaPlayer>(mp);
+        }
+
+        @Override
+        public boolean onInfo(MediaPlayer mp, int what, int extra) {
+            AndroidMediaPlayer self = mWeakMediaPlayer.get();
+            return self != null && notifyOnInfo(what, extra);
+
+        }
+
+        @Override
+        public boolean onError(MediaPlayer mp, int what, int extra) {
+            AndroidMediaPlayer self = mWeakMediaPlayer.get();
+            return self != null && notifyOnError(what, extra);
+
+        }
+
+        @Override
+        public void onVideoSizeChanged(MediaPlayer mp, int width, int height) {
+            AndroidMediaPlayer self = mWeakMediaPlayer.get();
+            if (self == null)
+                return;
+
+            notifyOnVideoSizeChanged(width, height, 1, 1);
+        }
+
+        @Override
+        public void onSeekComplete(MediaPlayer mp) {
+            AndroidMediaPlayer self = mWeakMediaPlayer.get();
+            if (self == null)
+                return;
+
+            notifyOnSeekComplete();
+        }
+
+        @Override
+        public void onBufferingUpdate(MediaPlayer mp, int percent) {
+            AndroidMediaPlayer self = mWeakMediaPlayer.get();
+            if (self == null)
+                return;
+
+            notifyOnBufferingUpdate(percent);
+        }
+
+        @Override
+        public void onCompletion(MediaPlayer mp) {
+            AndroidMediaPlayer self = mWeakMediaPlayer.get();
+            if (self == null)
+                return;
+
+            notifyOnCompletion();
+        }
+
+        @Override
+        public void onPrepared(MediaPlayer mp) {
+            AndroidMediaPlayer self = mWeakMediaPlayer.get();
+            if (self == null)
+                return;
+
+            notifyOnPrepared();
+        }
+
+        @Override
+        public void onTimedText(MediaPlayer mp, TimedText text) {
+            AndroidMediaPlayer self = mWeakMediaPlayer.get();
+            if (self == null)
+                return;
+
+            IjkTimedText ijkText = null;
+
+            if (text != null) {
+                ijkText = new IjkTimedText(text.getBounds(), text.getText());
+            }
+
+            notifyOnTimedText(ijkText);
+        }
+    }
+}

+ 215 - 0
android/src/main/java/tv/danmaku/ijk/media/player/IMediaPlayer.java

@@ -0,0 +1,215 @@
+/*
+ * Copyright (C) 2013-2014 Bilibili
+ * Copyright (C) 2013-2014 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Build;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.Map;
+
+import tv.danmaku.ijk.media.player.misc.IMediaDataSource;
+import tv.danmaku.ijk.media.player.misc.ITrackInfo;
+
+public interface IMediaPlayer {
+    /*
+     * Do not change these values without updating their counterparts in native
+     */
+    int MEDIA_INFO_UNKNOWN = 1;
+    int MEDIA_INFO_STARTED_AS_NEXT = 2;
+    int MEDIA_INFO_VIDEO_RENDERING_START = 3;
+    int MEDIA_INFO_VIDEO_TRACK_LAGGING = 700;
+    int MEDIA_INFO_BUFFERING_START = 701;
+    int MEDIA_INFO_BUFFERING_END = 702;
+    int MEDIA_INFO_NETWORK_BANDWIDTH = 703;
+    int MEDIA_INFO_BAD_INTERLEAVING = 800;
+    int MEDIA_INFO_NOT_SEEKABLE = 801;
+    int MEDIA_INFO_METADATA_UPDATE = 802;
+    int MEDIA_INFO_TIMED_TEXT_ERROR = 900;
+    int MEDIA_INFO_UNSUPPORTED_SUBTITLE = 901;
+    int MEDIA_INFO_SUBTITLE_TIMED_OUT = 902;
+
+    int MEDIA_INFO_VIDEO_ROTATION_CHANGED = 10001;
+    int MEDIA_INFO_AUDIO_RENDERING_START  = 10002;
+    int MEDIA_INFO_AUDIO_DECODED_START    = 10003;
+    int MEDIA_INFO_VIDEO_DECODED_START    = 10004;
+    int MEDIA_INFO_OPEN_INPUT             = 10005;
+    int MEDIA_INFO_FIND_STREAM_INFO       = 10006;
+    int MEDIA_INFO_COMPONENT_OPEN         = 10007;
+    int MEDIA_INFO_VIDEO_SEEK_RENDERING_START = 10008;
+    int MEDIA_INFO_AUDIO_SEEK_RENDERING_START = 10009;
+    int MEDIA_INFO_MEDIA_ACCURATE_SEEK_COMPLETE = 10100;
+
+    int MEDIA_ERROR_UNKNOWN = 1;
+    int MEDIA_ERROR_SERVER_DIED = 100;
+    int MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK = 200;
+    int MEDIA_ERROR_IO = -1004;
+    int MEDIA_ERROR_MALFORMED = -1007;
+    int MEDIA_ERROR_UNSUPPORTED = -1010;
+    int MEDIA_ERROR_TIMED_OUT = -110;
+
+    void setDisplay(SurfaceHolder sh);
+
+    void setDataSource(Context context, Uri uri)
+            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
+
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+    void setDataSource(Context context, Uri uri, Map<String, String> headers)
+            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
+
+    void setDataSource(FileDescriptor fd)
+            throws IOException, IllegalArgumentException, IllegalStateException;
+
+    void setDataSource(String path)
+            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
+
+    String getDataSource();
+
+    void prepareAsync() throws IllegalStateException;
+
+    void start() throws IllegalStateException;
+
+    void stop() throws IllegalStateException;
+
+    void pause() throws IllegalStateException;
+
+    void setScreenOnWhilePlaying(boolean screenOn);
+
+    int getVideoWidth();
+
+    int getVideoHeight();
+
+    boolean isPlaying();
+
+    void seekTo(long msec) throws IllegalStateException;
+
+    long getCurrentPosition();
+
+    long getDuration();
+
+    void release();
+
+    void reset();
+
+    void setVolume(float leftVolume, float rightVolume);
+
+    int getAudioSessionId();
+
+    MediaInfo getMediaInfo();
+
+    @SuppressWarnings("EmptyMethod")
+    @Deprecated
+    void setLogEnabled(boolean enable);
+
+    @Deprecated
+    boolean isPlayable();
+
+    void setOnPreparedListener(OnPreparedListener listener);
+
+    void setOnCompletionListener(OnCompletionListener listener);
+
+    void setOnBufferingUpdateListener(
+            OnBufferingUpdateListener listener);
+
+    void setOnSeekCompleteListener(
+            OnSeekCompleteListener listener);
+
+    void setOnVideoSizeChangedListener(
+            OnVideoSizeChangedListener listener);
+
+    void setOnErrorListener(OnErrorListener listener);
+
+    void setOnInfoListener(OnInfoListener listener);
+
+    void setOnTimedTextListener(OnTimedTextListener listener);
+
+    /*--------------------
+     * Listeners
+     */
+    interface OnPreparedListener {
+        void onPrepared(IMediaPlayer mp);
+    }
+
+    interface OnCompletionListener {
+        void onCompletion(IMediaPlayer mp);
+    }
+
+    interface OnBufferingUpdateListener {
+        void onBufferingUpdate(IMediaPlayer mp, int percent);
+    }
+
+    interface OnSeekCompleteListener {
+        void onSeekComplete(IMediaPlayer mp);
+    }
+
+    interface OnVideoSizeChangedListener {
+        void onVideoSizeChanged(IMediaPlayer mp, int width, int height,
+                                int sar_num, int sar_den);
+    }
+
+    interface OnErrorListener {
+        boolean onError(IMediaPlayer mp, int what, int extra);
+    }
+
+    interface OnInfoListener {
+        boolean onInfo(IMediaPlayer mp, int what, int extra);
+    }
+
+    interface OnTimedTextListener {
+        void onTimedText(IMediaPlayer mp, IjkTimedText text);
+    }
+
+    /*--------------------
+     * Optional
+     */
+    void setAudioStreamType(int streamtype);
+
+    @Deprecated
+    void setKeepInBackground(boolean keepInBackground);
+
+    int getVideoSarNum();
+
+    int getVideoSarDen();
+
+    @Deprecated
+    void setWakeMode(Context context, int mode);
+
+    void setLooping(boolean looping);
+
+    boolean isLooping();
+
+    /*--------------------
+     * AndroidMediaPlayer: JELLY_BEAN
+     */
+    ITrackInfo[] getTrackInfo();
+
+    /*--------------------
+     * AndroidMediaPlayer: ICE_CREAM_SANDWICH:
+     */
+    void setSurface(Surface surface);
+
+    /*--------------------
+     * AndroidMediaPlayer: M:
+     */
+    void setDataSource(IMediaDataSource mediaDataSource);
+}

+ 28 - 0
android/src/main/java/tv/danmaku/ijk/media/player/ISurfaceTextureHolder.java

@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2015 Bilibili
+ * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player;
+
+import android.graphics.SurfaceTexture;
+
+public interface ISurfaceTextureHolder {
+    void setSurfaceTexture(SurfaceTexture surfaceTexture);
+
+    SurfaceTexture getSurfaceTexture();
+
+    void setSurfaceTextureHost(ISurfaceTextureHost surfaceTextureHost);
+}

+ 24 - 0
android/src/main/java/tv/danmaku/ijk/media/player/ISurfaceTextureHost.java

@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2015 Bilibili
+ * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player;
+
+import android.graphics.SurfaceTexture;
+
+public interface ISurfaceTextureHost {
+    void releaseSurfaceTexture(SurfaceTexture surfaceTexture);
+}

+ 23 - 0
android/src/main/java/tv/danmaku/ijk/media/player/IjkLibLoader.java

@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2013-2014 Bilibili
+ * Copyright (C) 2013-2014 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player;
+
+public interface IjkLibLoader {
+    void loadLibrary(String libName) throws UnsatisfiedLinkError,
+            SecurityException;
+}

+ 293 - 0
android/src/main/java/tv/danmaku/ijk/media/player/IjkMediaCodecInfo.java

@@ -0,0 +1,293 @@
+package tv.danmaku.ijk.media.player;
+
+import android.annotation.TargetApi;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.CodecProfileLevel;
+import android.os.Build;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.Locale;
+import java.util.Map;
+import java.util.TreeMap;
+
+public class IjkMediaCodecInfo {
+    private final static String TAG = "IjkMediaCodecInfo";
+
+    public static final int RANK_MAX = 1000;
+    public static final int RANK_TESTED = 800;
+    public static final int RANK_ACCEPTABLE = 700;
+    public static final int RANK_LAST_CHANCE = 600;
+    public static final int RANK_SECURE = 300;
+    public static final int RANK_SOFTWARE = 200;
+    public static final int RANK_NON_STANDARD = 100;
+    public static final int RANK_NO_SENSE = 0;
+
+    public MediaCodecInfo mCodecInfo;
+    public int mRank = 0;
+    public String mMimeType;
+
+    private static Map<String, Integer> sKnownCodecList;
+
+    private static synchronized Map<String, Integer> getKnownCodecList() {
+        if (sKnownCodecList != null)
+            return sKnownCodecList;
+
+        sKnownCodecList = new TreeMap<String, Integer>(
+                String.CASE_INSENSITIVE_ORDER);
+
+        // ----- Nvidia -----
+        // Tegra3
+        // Nexus 7 (2012)
+        // Tegra K1
+        // Nexus 9
+        sKnownCodecList.put("OMX.Nvidia.h264.decode", RANK_TESTED);
+        sKnownCodecList.put("OMX.Nvidia.h264.decode.secure", RANK_SECURE);
+
+        // ----- Intel -----
+        // Atom Z3735
+        // Teclast X98 Air
+        sKnownCodecList.put("OMX.Intel.hw_vd.h264", RANK_TESTED + 1);
+        // Atom Z2560
+        // Dell Venue 7 3730
+        sKnownCodecList.put("OMX.Intel.VideoDecoder.AVC", RANK_TESTED);
+
+        // ----- Qualcomm -----
+        // MSM8260
+        // Xiaomi MI 1S
+        sKnownCodecList.put("OMX.qcom.video.decoder.avc", RANK_TESTED);
+        sKnownCodecList.put("OMX.ittiam.video.decoder.avc", RANK_NO_SENSE);
+
+        // ----- Samsung -----
+        // Exynos 3110
+        // Nexus S
+        sKnownCodecList.put("OMX.SEC.avc.dec", RANK_TESTED);
+        sKnownCodecList.put("OMX.SEC.AVC.Decoder", RANK_TESTED - 1);
+        // OMX.SEC.avcdec doesn't reorder output pictures on GT-9100
+        sKnownCodecList.put("OMX.SEC.avcdec", RANK_TESTED - 2);
+        sKnownCodecList.put("OMX.SEC.avc.sw.dec", RANK_SOFTWARE);
+        // Exynos 5 ?
+        sKnownCodecList.put("OMX.Exynos.avc.dec", RANK_TESTED);
+        sKnownCodecList.put("OMX.Exynos.AVC.Decoder", RANK_TESTED - 1);
+
+        // ------ Huawei hisilicon ------
+        // Kirin 910, Mali 450 MP
+        // Huawei HONOR 3C (H30-L01)
+        sKnownCodecList.put("OMX.k3.video.decoder.avc", RANK_TESTED);
+        // Kirin 920, Mali T624
+        // Huawei HONOR 6
+        sKnownCodecList.put("OMX.IMG.MSVDX.Decoder.AVC", RANK_TESTED);
+
+        // ----- TI -----
+        // TI OMAP4460
+        // Galaxy Nexus
+        sKnownCodecList.put("OMX.TI.DUCATI1.VIDEO.DECODER", RANK_TESTED);
+
+        // ------ RockChip ------
+        // Youku TVBox
+        sKnownCodecList.put("OMX.rk.video_decoder.avc", RANK_TESTED);
+
+        // ------ AMLogic -----
+        // MiBox1, 1s, 2
+        sKnownCodecList.put("OMX.amlogic.avc.decoder.awesome", RANK_TESTED);
+
+        // ------ Marvell ------
+        // Lenovo A788t
+        sKnownCodecList.put("OMX.MARVELL.VIDEO.HW.CODA7542DECODER", RANK_TESTED);
+        sKnownCodecList.put("OMX.MARVELL.VIDEO.H264DECODER", RANK_SOFTWARE);
+
+        // ----- TODO: need test -----
+        sKnownCodecList.remove("OMX.Action.Video.Decoder");
+        sKnownCodecList.remove("OMX.allwinner.video.decoder.avc");
+        sKnownCodecList.remove("OMX.BRCM.vc4.decoder.avc");
+        sKnownCodecList.remove("OMX.brcm.video.h264.hw.decoder");
+        sKnownCodecList.remove("OMX.brcm.video.h264.decoder");
+        sKnownCodecList.remove("OMX.cosmo.video.decoder.avc");
+        sKnownCodecList.remove("OMX.duos.h264.decoder");
+        sKnownCodecList.remove("OMX.hantro.81x0.video.decoder");
+        sKnownCodecList.remove("OMX.hantro.G1.video.decoder");
+        sKnownCodecList.remove("OMX.hisi.video.decoder");
+        sKnownCodecList.remove("OMX.LG.decoder.video.avc");
+        sKnownCodecList.remove("OMX.MS.AVC.Decoder");
+        sKnownCodecList.remove("OMX.RENESAS.VIDEO.DECODER.H264");
+        sKnownCodecList.remove("OMX.RTK.video.decoder");
+        sKnownCodecList.remove("OMX.sprd.h264.decoder");
+        sKnownCodecList.remove("OMX.ST.VFM.H264Dec");
+        sKnownCodecList.remove("OMX.vpu.video_decoder.avc");
+        sKnownCodecList.remove("OMX.WMT.decoder.avc");
+
+        // Really ?
+        sKnownCodecList.remove("OMX.bluestacks.hw.decoder");
+
+        // ---------------
+        // Useless codec
+        // ----- google -----
+        sKnownCodecList.put("OMX.google.h264.decoder", RANK_SOFTWARE);
+        sKnownCodecList.put("OMX.google.h264.lc.decoder", RANK_SOFTWARE);
+        // ----- huawei k920 -----
+        sKnownCodecList.put("OMX.k3.ffmpeg.decoder", RANK_SOFTWARE);
+        sKnownCodecList.put("OMX.ffmpeg.video.decoder", RANK_SOFTWARE);
+        // ----- unknown -----
+        sKnownCodecList.put("OMX.sprd.soft.h264.decoder", RANK_SOFTWARE);
+
+        return sKnownCodecList;
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    public static IjkMediaCodecInfo setupCandidate(MediaCodecInfo codecInfo,
+            String mimeType) {
+        if (codecInfo == null
+                || Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
+            return null;
+
+        String name = codecInfo.getName();
+        if (TextUtils.isEmpty(name))
+            return null;
+
+        name = name.toLowerCase(Locale.US);
+        int rank = RANK_NO_SENSE;
+        if (!name.startsWith("omx.")) {
+            rank = RANK_NON_STANDARD;
+        } else if (name.startsWith("omx.pv")) {
+            rank = RANK_SOFTWARE;
+        } else if (name.startsWith("omx.google.")) {
+            rank = RANK_SOFTWARE;
+        } else if (name.startsWith("omx.ffmpeg.")) {
+            rank = RANK_SOFTWARE;
+        } else if (name.startsWith("omx.k3.ffmpeg.")) {
+            rank = RANK_SOFTWARE;
+        } else if (name.startsWith("omx.avcodec.")) {
+            rank = RANK_SOFTWARE;
+        } else if (name.startsWith("omx.ittiam.")) {
+            // unknown codec in qualcomm SoC
+            rank = RANK_NO_SENSE;
+        } else if (name.startsWith("omx.mtk.")) {
+            // 1. MTK only works on 4.3 and above
+            // 2. MTK works on MIUI 6 (4.2.1)
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2)
+                rank = RANK_NO_SENSE;
+            else
+                rank = RANK_TESTED;
+        } else {
+            Integer knownRank = getKnownCodecList().get(name);
+            if (knownRank != null) {
+                rank = knownRank;
+            } else {
+                try {
+                    CodecCapabilities cap = codecInfo
+                            .getCapabilitiesForType(mimeType);
+                    if (cap != null)
+                        rank = RANK_ACCEPTABLE;
+                    else
+                        rank = RANK_LAST_CHANCE;
+                } catch (Throwable e) {
+                    rank = RANK_LAST_CHANCE;
+                }
+            }
+        }
+
+        IjkMediaCodecInfo candidate = new IjkMediaCodecInfo();
+        candidate.mCodecInfo = codecInfo;
+        candidate.mRank = rank;
+        candidate.mMimeType = mimeType;
+        return candidate;
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    public void dumpProfileLevels(String mimeType) {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
+            return;
+
+        try {
+            CodecCapabilities caps = mCodecInfo
+                    .getCapabilitiesForType(mimeType);
+            int maxProfile = 0;
+            int maxLevel = 0;
+            if (caps != null) {
+                if (caps.profileLevels != null) {
+                    for (CodecProfileLevel profileLevel : caps.profileLevels) {
+                        if (profileLevel == null)
+                            continue;
+
+                        maxProfile = Math.max(maxProfile, profileLevel.profile);
+                        maxLevel = Math.max(maxLevel, profileLevel.level);
+                    }
+                }
+            }
+
+            Log.i(TAG,
+                    String.format(Locale.US, "%s",
+                            getProfileLevelName(maxProfile, maxLevel)));
+        } catch (Throwable e) {
+            Log.i(TAG, "profile-level: exception");
+        }
+    }
+
+    public static String getProfileLevelName(int profile, int level) {
+        return String.format(Locale.US, " %s Profile Level %s (%d,%d)",
+                getProfileName(profile), getLevelName(level), profile, level);
+    }
+
+    public static String getProfileName(int profile) {
+        switch (profile) {
+        case CodecProfileLevel.AVCProfileBaseline:
+            return "Baseline";
+        case CodecProfileLevel.AVCProfileMain:
+            return "Main";
+        case CodecProfileLevel.AVCProfileExtended:
+            return "Extends";
+        case CodecProfileLevel.AVCProfileHigh:
+            return "High";
+        case CodecProfileLevel.AVCProfileHigh10:
+            return "High10";
+        case CodecProfileLevel.AVCProfileHigh422:
+            return "High422";
+        case CodecProfileLevel.AVCProfileHigh444:
+            return "High444";
+        default:
+            return "Unknown";
+        }
+    }
+
+    public static String getLevelName(int level) {
+        switch (level) {
+        case CodecProfileLevel.AVCLevel1:
+            return "1";
+        case CodecProfileLevel.AVCLevel1b:
+            return "1b";
+        case CodecProfileLevel.AVCLevel11:
+            return "11";
+        case CodecProfileLevel.AVCLevel12:
+            return "12";
+        case CodecProfileLevel.AVCLevel13:
+            return "13";
+        case CodecProfileLevel.AVCLevel2:
+            return "2";
+        case CodecProfileLevel.AVCLevel21:
+            return "21";
+        case CodecProfileLevel.AVCLevel22:
+            return "22";
+        case CodecProfileLevel.AVCLevel3:
+            return "3";
+        case CodecProfileLevel.AVCLevel31:
+            return "31";
+        case CodecProfileLevel.AVCLevel32:
+            return "32";
+        case CodecProfileLevel.AVCLevel4:
+            return "4";
+        case CodecProfileLevel.AVCLevel41:
+            return "41";
+        case CodecProfileLevel.AVCLevel42:
+            return "42";
+        case CodecProfileLevel.AVCLevel5:
+            return "5";
+        case CodecProfileLevel.AVCLevel51:
+            return "51";
+        case 65536: // CodecProfileLevel.AVCLevel52:
+            return "52";
+        default:
+            return "0";
+        }
+    }
+}

+ 401 - 0
android/src/main/java/tv/danmaku/ijk/media/player/IjkMediaMeta.java

@@ -0,0 +1,401 @@
+package tv.danmaku.ijk.media.player;
+
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import java.util.ArrayList;
+import java.util.Locale;
+
+@SuppressWarnings("SameParameterValue")
+public class IjkMediaMeta {
+    // media meta
+    public static final String IJKM_KEY_FORMAT = "format";
+    public static final String IJKM_KEY_DURATION_US = "duration_us";
+    public static final String IJKM_KEY_START_US = "start_us";
+    public static final String IJKM_KEY_BITRATE = "bitrate";
+    public static final String IJKM_KEY_VIDEO_STREAM = "video";
+    public static final String IJKM_KEY_AUDIO_STREAM = "audio";
+    public static final String IJKM_KEY_TIMEDTEXT_STREAM = "timedtext";
+
+    // stream meta
+    public static final String IJKM_KEY_TYPE = "type";
+    public static final String IJKM_VAL_TYPE__VIDEO = "video";
+    public static final String IJKM_VAL_TYPE__AUDIO = "audio";
+    public static final String IJKM_VAL_TYPE__TIMEDTEXT = "timedtext";
+    public static final String IJKM_VAL_TYPE__UNKNOWN = "unknown";
+    public static final String IJKM_KEY_LANGUAGE = "language";
+
+    public static final String IJKM_KEY_CODEC_NAME = "codec_name";
+    public static final String IJKM_KEY_CODEC_PROFILE = "codec_profile";
+    public static final String IJKM_KEY_CODEC_LEVEL = "codec_level";
+    public static final String IJKM_KEY_CODEC_LONG_NAME = "codec_long_name";
+    public static final String IJKM_KEY_CODEC_PIXEL_FORMAT = "codec_pixel_format";
+    public static final String IJKM_KEY_CODEC_PROFILE_ID = "codec_profile_id";
+
+    // stream: video
+    public static final String IJKM_KEY_WIDTH = "width";
+    public static final String IJKM_KEY_HEIGHT = "height";
+    public static final String IJKM_KEY_FPS_NUM = "fps_num";
+    public static final String IJKM_KEY_FPS_DEN = "fps_den";
+    public static final String IJKM_KEY_TBR_NUM = "tbr_num";
+    public static final String IJKM_KEY_TBR_DEN = "tbr_den";
+    public static final String IJKM_KEY_SAR_NUM = "sar_num";
+    public static final String IJKM_KEY_SAR_DEN = "sar_den";
+    // stream: audio
+    public static final String IJKM_KEY_SAMPLE_RATE = "sample_rate";
+    public static final String IJKM_KEY_CHANNEL_LAYOUT = "channel_layout";
+
+    public static final String IJKM_KEY_STREAMS = "streams";
+
+    public static final long AV_CH_FRONT_LEFT = 0x00000001;
+    public static final long AV_CH_FRONT_RIGHT = 0x00000002;
+    public static final long AV_CH_FRONT_CENTER = 0x00000004;
+    public static final long AV_CH_LOW_FREQUENCY = 0x00000008;
+    public static final long AV_CH_BACK_LEFT = 0x00000010;
+    public static final long AV_CH_BACK_RIGHT = 0x00000020;
+    public static final long AV_CH_FRONT_LEFT_OF_CENTER = 0x00000040;
+    public static final long AV_CH_FRONT_RIGHT_OF_CENTER = 0x00000080;
+    public static final long AV_CH_BACK_CENTER = 0x00000100;
+    public static final long AV_CH_SIDE_LEFT = 0x00000200;
+    public static final long AV_CH_SIDE_RIGHT = 0x00000400;
+    public static final long AV_CH_TOP_CENTER = 0x00000800;
+    public static final long AV_CH_TOP_FRONT_LEFT = 0x00001000;
+    public static final long AV_CH_TOP_FRONT_CENTER = 0x00002000;
+    public static final long AV_CH_TOP_FRONT_RIGHT = 0x00004000;
+    public static final long AV_CH_TOP_BACK_LEFT = 0x00008000;
+    public static final long AV_CH_TOP_BACK_CENTER = 0x00010000;
+    public static final long AV_CH_TOP_BACK_RIGHT = 0x00020000;
+    public static final long AV_CH_STEREO_LEFT = 0x20000000;
+    public static final long AV_CH_STEREO_RIGHT = 0x40000000;
+    public static final long AV_CH_WIDE_LEFT = 0x0000000080000000L;
+    public static final long AV_CH_WIDE_RIGHT = 0x0000000100000000L;
+    public static final long AV_CH_SURROUND_DIRECT_LEFT = 0x0000000200000000L;
+    public static final long AV_CH_SURROUND_DIRECT_RIGHT = 0x0000000400000000L;
+    public static final long AV_CH_LOW_FREQUENCY_2 = 0x0000000800000000L;
+
+    public static final long AV_CH_LAYOUT_MONO = (AV_CH_FRONT_CENTER);
+    public static final long AV_CH_LAYOUT_STEREO = (AV_CH_FRONT_LEFT | AV_CH_FRONT_RIGHT);
+    public static final long AV_CH_LAYOUT_2POINT1 = (AV_CH_LAYOUT_STEREO | AV_CH_LOW_FREQUENCY);
+    public static final long AV_CH_LAYOUT_2_1 = (AV_CH_LAYOUT_STEREO | AV_CH_BACK_CENTER);
+    public static final long AV_CH_LAYOUT_SURROUND = (AV_CH_LAYOUT_STEREO | AV_CH_FRONT_CENTER);
+    public static final long AV_CH_LAYOUT_3POINT1 = (AV_CH_LAYOUT_SURROUND | AV_CH_LOW_FREQUENCY);
+    public static final long AV_CH_LAYOUT_4POINT0 = (AV_CH_LAYOUT_SURROUND | AV_CH_BACK_CENTER);
+    public static final long AV_CH_LAYOUT_4POINT1 = (AV_CH_LAYOUT_4POINT0 | AV_CH_LOW_FREQUENCY);
+    public static final long AV_CH_LAYOUT_2_2 = (AV_CH_LAYOUT_STEREO
+            | AV_CH_SIDE_LEFT | AV_CH_SIDE_RIGHT);
+    public static final long AV_CH_LAYOUT_QUAD = (AV_CH_LAYOUT_STEREO
+            | AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT);
+    public static final long AV_CH_LAYOUT_5POINT0 = (AV_CH_LAYOUT_SURROUND
+            | AV_CH_SIDE_LEFT | AV_CH_SIDE_RIGHT);
+    public static final long AV_CH_LAYOUT_5POINT1 = (AV_CH_LAYOUT_5POINT0 | AV_CH_LOW_FREQUENCY);
+    public static final long AV_CH_LAYOUT_5POINT0_BACK = (AV_CH_LAYOUT_SURROUND
+            | AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT);
+    public static final long AV_CH_LAYOUT_5POINT1_BACK = (AV_CH_LAYOUT_5POINT0_BACK | AV_CH_LOW_FREQUENCY);
+    public static final long AV_CH_LAYOUT_6POINT0 = (AV_CH_LAYOUT_5POINT0 | AV_CH_BACK_CENTER);
+    public static final long AV_CH_LAYOUT_6POINT0_FRONT = (AV_CH_LAYOUT_2_2
+            | AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER);
+    public static final long AV_CH_LAYOUT_HEXAGONAL = (AV_CH_LAYOUT_5POINT0_BACK | AV_CH_BACK_CENTER);
+    public static final long AV_CH_LAYOUT_6POINT1 = (AV_CH_LAYOUT_5POINT1 | AV_CH_BACK_CENTER);
+    public static final long AV_CH_LAYOUT_6POINT1_BACK = (AV_CH_LAYOUT_5POINT1_BACK | AV_CH_BACK_CENTER);
+    public static final long AV_CH_LAYOUT_6POINT1_FRONT = (AV_CH_LAYOUT_6POINT0_FRONT | AV_CH_LOW_FREQUENCY);
+    public static final long AV_CH_LAYOUT_7POINT0 = (AV_CH_LAYOUT_5POINT0
+            | AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT);
+    public static final long AV_CH_LAYOUT_7POINT0_FRONT = (AV_CH_LAYOUT_5POINT0
+            | AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER);
+    public static final long AV_CH_LAYOUT_7POINT1 = (AV_CH_LAYOUT_5POINT1
+            | AV_CH_BACK_LEFT | AV_CH_BACK_RIGHT);
+    public static final long AV_CH_LAYOUT_7POINT1_WIDE = (AV_CH_LAYOUT_5POINT1
+            | AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER);
+    public static final long AV_CH_LAYOUT_7POINT1_WIDE_BACK = (AV_CH_LAYOUT_5POINT1_BACK
+            | AV_CH_FRONT_LEFT_OF_CENTER | AV_CH_FRONT_RIGHT_OF_CENTER);
+    public static final long AV_CH_LAYOUT_OCTAGONAL = (AV_CH_LAYOUT_5POINT0
+            | AV_CH_BACK_LEFT | AV_CH_BACK_CENTER | AV_CH_BACK_RIGHT);
+    public static final long AV_CH_LAYOUT_STEREO_DOWNMIX = (AV_CH_STEREO_LEFT | AV_CH_STEREO_RIGHT);
+
+    public static final int FF_PROFILE_H264_CONSTRAINED = (1<<9);  // 8+1; constraint_set1_flag
+    public static final int FF_PROFILE_H264_INTRA = (1<<11);       // 8+3; constraint_set3_flag
+
+    public static final int FF_PROFILE_H264_BASELINE = 66;
+    public static final int FF_PROFILE_H264_CONSTRAINED_BASELINE = (66|FF_PROFILE_H264_CONSTRAINED);
+    public static final int FF_PROFILE_H264_MAIN = 77;
+    public static final int FF_PROFILE_H264_EXTENDED = 88;
+    public static final int FF_PROFILE_H264_HIGH = 100;
+    public static final int FF_PROFILE_H264_HIGH_10 = 110;
+    public static final int FF_PROFILE_H264_HIGH_10_INTRA = (110|FF_PROFILE_H264_INTRA);
+    public static final int FF_PROFILE_H264_HIGH_422 = 122;
+    public static final int FF_PROFILE_H264_HIGH_422_INTRA = (122|FF_PROFILE_H264_INTRA);
+    public static final int FF_PROFILE_H264_HIGH_444 = 144;
+    public static final int FF_PROFILE_H264_HIGH_444_PREDICTIVE = 244;
+    public static final int FF_PROFILE_H264_HIGH_444_INTRA = (244|FF_PROFILE_H264_INTRA);
+    public static final int FF_PROFILE_H264_CAVLC_444 = 44;
+
+    public Bundle mMediaMeta;
+
+    public String mFormat;
+    public long mDurationUS;
+    public long mStartUS;
+    public long mBitrate;
+
+    public final ArrayList<IjkStreamMeta> mStreams = new ArrayList<IjkStreamMeta>();
+    public IjkStreamMeta mVideoStream;
+    public IjkStreamMeta mAudioStream;
+
+    public String getString(String key) {
+        return mMediaMeta.getString(key);
+    }
+
+    public int getInt(String key) {
+        return getInt(key, 0);
+    }
+
+    public int getInt(String key, int defaultValue) {
+        String value = getString(key);
+        if (TextUtils.isEmpty(value))
+            return defaultValue;
+
+        try {
+            return Integer.parseInt(value);
+        } catch (NumberFormatException e) {
+            return defaultValue;
+        }
+    }
+
+    public long getLong(String key) {
+        return getLong(key, 0);
+    }
+
+    public long getLong(String key, long defaultValue) {
+        String value = getString(key);
+        if (TextUtils.isEmpty(value))
+            return defaultValue;
+
+        try {
+            return Long.parseLong(value);
+        } catch (NumberFormatException e) {
+            return defaultValue;
+        }
+    }
+
+    public ArrayList<Bundle> getParcelableArrayList(String key) {
+        return mMediaMeta.getParcelableArrayList(key);
+    }
+
+    public String getDurationInline() {
+        long duration = mDurationUS + 5000;
+        long secs = duration / 1000000;
+        long mins = secs / 60;
+        secs %= 60;
+        long hours = mins / 60;
+        mins %= 60;
+        return String.format(Locale.US, "%02d:%02d:%02d", hours, mins, secs);
+    }
+
+    public static IjkMediaMeta parse(Bundle mediaMeta) {
+        if (mediaMeta == null)
+            return null;
+
+        IjkMediaMeta meta = new IjkMediaMeta();
+        meta.mMediaMeta = mediaMeta;
+
+        meta.mFormat = meta.getString(IJKM_KEY_FORMAT);
+        meta.mDurationUS = meta.getLong(IJKM_KEY_DURATION_US);
+        meta.mStartUS = meta.getLong(IJKM_KEY_START_US);
+        meta.mBitrate = meta.getLong(IJKM_KEY_BITRATE);
+
+        int videoStreamIndex = meta.getInt(IJKM_KEY_VIDEO_STREAM, -1);
+        int audioStreamIndex = meta.getInt(IJKM_KEY_AUDIO_STREAM, -1);
+        int subtitleStreamIndex = meta.getInt(IJKM_KEY_TIMEDTEXT_STREAM, -1);
+
+        ArrayList<Bundle> streams = meta
+                .getParcelableArrayList(IJKM_KEY_STREAMS);
+        if (streams == null)
+            return meta;
+
+        int index = -1;
+        for (Bundle streamBundle : streams) {
+            index++;
+
+            if (streamBundle == null) {
+                continue;
+            }
+
+            IjkStreamMeta streamMeta = new IjkStreamMeta(index);
+            streamMeta.mMeta = streamBundle;
+            streamMeta.mType = streamMeta.getString(IJKM_KEY_TYPE);
+            streamMeta.mLanguage = streamMeta.getString(IJKM_KEY_LANGUAGE);
+            if (TextUtils.isEmpty(streamMeta.mType))
+                continue;
+
+            streamMeta.mCodecName = streamMeta.getString(IJKM_KEY_CODEC_NAME);
+            streamMeta.mCodecProfile = streamMeta
+                    .getString(IJKM_KEY_CODEC_PROFILE);
+            streamMeta.mCodecLongName = streamMeta
+                    .getString(IJKM_KEY_CODEC_LONG_NAME);
+            streamMeta.mBitrate = streamMeta.getInt(IJKM_KEY_BITRATE);
+
+            if (streamMeta.mType.equalsIgnoreCase(IJKM_VAL_TYPE__VIDEO)) {
+                streamMeta.mWidth = streamMeta.getInt(IJKM_KEY_WIDTH);
+                streamMeta.mHeight = streamMeta.getInt(IJKM_KEY_HEIGHT);
+                streamMeta.mFpsNum = streamMeta.getInt(IJKM_KEY_FPS_NUM);
+                streamMeta.mFpsDen = streamMeta.getInt(IJKM_KEY_FPS_DEN);
+                streamMeta.mTbrNum = streamMeta.getInt(IJKM_KEY_TBR_NUM);
+                streamMeta.mTbrDen = streamMeta.getInt(IJKM_KEY_TBR_DEN);
+                streamMeta.mSarNum = streamMeta.getInt(IJKM_KEY_SAR_NUM);
+                streamMeta.mSarDen = streamMeta.getInt(IJKM_KEY_SAR_DEN);
+
+                if (videoStreamIndex == index) {
+                    meta.mVideoStream = streamMeta;
+                }
+            } else if (streamMeta.mType.equalsIgnoreCase(IJKM_VAL_TYPE__AUDIO)) {
+                streamMeta.mSampleRate = streamMeta
+                        .getInt(IJKM_KEY_SAMPLE_RATE);
+                streamMeta.mChannelLayout = streamMeta
+                        .getLong(IJKM_KEY_CHANNEL_LAYOUT);
+
+                if (audioStreamIndex == index) {
+                    meta.mAudioStream = streamMeta;
+                }
+            }
+            meta.mStreams.add(streamMeta);
+        }
+
+        return meta;
+    }
+
+    public static class IjkStreamMeta {
+        public Bundle mMeta;
+
+        public final int mIndex;
+        public String mType;
+        public String mLanguage;
+
+        // common
+        public String mCodecName;
+        public String mCodecProfile;
+        public String mCodecLongName;
+        public long mBitrate;
+
+        // video
+        public int mWidth;
+        public int mHeight;
+        public int mFpsNum;
+        public int mFpsDen;
+        public int mTbrNum;
+        public int mTbrDen;
+        public int mSarNum;
+        public int mSarDen;
+
+        // audio
+        public int mSampleRate;
+        public long mChannelLayout;
+
+        public IjkStreamMeta(int index) {
+            mIndex = index;
+        }
+
+        public String getString(String key) {
+            return mMeta.getString(key);
+        }
+
+        public int getInt(String key) {
+            return getInt(key, 0);
+        }
+
+        public int getInt(String key, int defaultValue) {
+            String value = getString(key);
+            if (TextUtils.isEmpty(value))
+                return defaultValue;
+
+            try {
+                return Integer.parseInt(value);
+            } catch (NumberFormatException e) {
+                return defaultValue;
+            }
+        }
+
+        public long getLong(String key) {
+            return getLong(key, 0);
+        }
+
+        public long getLong(String key, long defaultValue) {
+            String value = getString(key);
+            if (TextUtils.isEmpty(value))
+                return defaultValue;
+
+            try {
+                return Long.parseLong(value);
+            } catch (NumberFormatException e) {
+                return defaultValue;
+            }
+        }
+
+        public String getCodecLongNameInline() {
+            if (!TextUtils.isEmpty(mCodecLongName)) {
+                return mCodecLongName;
+            } else if (!TextUtils.isEmpty(mCodecName)) {
+                return mCodecName;
+            } else {
+                return "N/A";
+            }
+        }
+
+        public String getCodecShortNameInline() {
+            if (!TextUtils.isEmpty(mCodecName)) {
+                return mCodecName;
+            } else {
+                return "N/A";
+            }
+        }
+
+        public String getResolutionInline() {
+            if (mWidth <= 0 || mHeight <= 0) {
+                return "N/A";
+            } else if (mSarNum <= 0 || mSarDen <= 0) {
+                return String.format(Locale.US, "%d x %d", mWidth, mHeight);
+            } else {
+                return String.format(Locale.US, "%d x %d [SAR %d:%d]", mWidth,
+                        mHeight, mSarNum, mSarDen);
+            }
+        }
+
+        public String getFpsInline() {
+            if (mFpsNum <= 0 || mFpsDen <= 0) {
+                return "N/A";
+            } else {
+                return String.valueOf(((float) (mFpsNum)) / mFpsDen);
+            }
+        }
+
+        public String getBitrateInline() {
+            if (mBitrate <= 0) {
+                return "N/A";
+            } else if (mBitrate < 1000) {
+                return String.format(Locale.US, "%d bit/s", mBitrate);
+            } else {
+                return String.format(Locale.US, "%d kb/s", mBitrate / 1000);
+            }
+        }
+
+        public String getSampleRateInline() {
+            if (mSampleRate <= 0) {
+                return "N/A";
+            } else {
+                return String.format(Locale.US, "%d Hz", mSampleRate);
+            }
+        }
+
+        public String getChannelLayoutInline() {
+            if (mChannelLayout <= 0) {
+                return "N/A";
+            } else {
+                if (mChannelLayout == AV_CH_LAYOUT_MONO) {
+                    return "mono";
+                } else if (mChannelLayout == AV_CH_LAYOUT_STEREO) {
+                    return "stereo";
+                } else {
+                    return String.format(Locale.US, "%x", mChannelLayout);
+                }
+            }
+        }
+    }
+}

+ 1291 - 0
android/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java

@@ -0,0 +1,1291 @@
+/*
+ * Copyright (C) 2006 Bilibili
+ * Copyright (C) 2006 The Android Open Source Project
+ * Copyright (C) 2013 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player;
+
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.graphics.Bitmap;
+import android.graphics.Rect;
+import android.graphics.SurfaceTexture;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.security.InvalidParameterException;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.Map;
+
+import tv.danmaku.ijk.media.player.annotations.AccessedByNative;
+import tv.danmaku.ijk.media.player.annotations.CalledByNative;
+import tv.danmaku.ijk.media.player.misc.IAndroidIO;
+import tv.danmaku.ijk.media.player.misc.IMediaDataSource;
+import tv.danmaku.ijk.media.player.misc.ITrackInfo;
+import tv.danmaku.ijk.media.player.misc.IjkTrackInfo;
+import tv.danmaku.ijk.media.player.pragma.DebugLog;
+
+/**
+ * @author bbcallen
+ *
+ *         Java wrapper of ffplay.
+ */
+public final class IjkMediaPlayer extends AbstractMediaPlayer {
+    private final static String TAG = IjkMediaPlayer.class.getName();
+
+    private static final int MEDIA_NOP = 0; // interface test message
+    private static final int MEDIA_PREPARED = 1;
+    private static final int MEDIA_PLAYBACK_COMPLETE = 2;
+    private static final int MEDIA_BUFFERING_UPDATE = 3;
+    private static final int MEDIA_SEEK_COMPLETE = 4;
+    private static final int MEDIA_SET_VIDEO_SIZE = 5;
+    private static final int MEDIA_TIMED_TEXT = 99;
+    private static final int MEDIA_ERROR = 100;
+    private static final int MEDIA_INFO = 200;
+
+    protected static final int MEDIA_SET_VIDEO_SAR = 10001;
+
+    //----------------------------------------
+    // options
+    public static final int IJK_LOG_UNKNOWN = 0;
+    public static final int IJK_LOG_DEFAULT = 1;
+
+    public static final int IJK_LOG_VERBOSE = 2;
+    public static final int IJK_LOG_DEBUG = 3;
+    public static final int IJK_LOG_INFO = 4;
+    public static final int IJK_LOG_WARN = 5;
+    public static final int IJK_LOG_ERROR = 6;
+    public static final int IJK_LOG_FATAL = 7;
+    public static final int IJK_LOG_SILENT = 8;
+
+    public static final int OPT_CATEGORY_FORMAT     = 1;
+    public static final int OPT_CATEGORY_CODEC      = 2;
+    public static final int OPT_CATEGORY_SWS        = 3;
+    public static final int OPT_CATEGORY_PLAYER     = 4;
+
+    public static final int SDL_FCC_YV12 = 0x32315659; // YV12
+    public static final int SDL_FCC_RV16 = 0x36315652; // RGB565
+    public static final int SDL_FCC_RV32 = 0x32335652; // RGBX8888
+    //----------------------------------------
+
+    //----------------------------------------
+    // properties
+    public static final int PROP_FLOAT_VIDEO_DECODE_FRAMES_PER_SECOND       = 10001;
+    public static final int PROP_FLOAT_VIDEO_OUTPUT_FRAMES_PER_SECOND       = 10002;
+    public static final int FFP_PROP_FLOAT_PLAYBACK_RATE                    = 10003;
+    public static final int FFP_PROP_FLOAT_DROP_FRAME_RATE                  = 10007;
+
+    public static final int FFP_PROP_INT64_SELECTED_VIDEO_STREAM            = 20001;
+    public static final int FFP_PROP_INT64_SELECTED_AUDIO_STREAM            = 20002;
+    public static final int FFP_PROP_INT64_SELECTED_TIMEDTEXT_STREAM        = 20011;
+
+    public static final int FFP_PROP_INT64_VIDEO_DECODER                    = 20003;
+    public static final int FFP_PROP_INT64_AUDIO_DECODER                    = 20004;
+    public static final int     FFP_PROPV_DECODER_UNKNOWN                   = 0;
+    public static final int     FFP_PROPV_DECODER_AVCODEC                   = 1;
+    public static final int     FFP_PROPV_DECODER_MEDIACODEC                = 2;
+    public static final int     FFP_PROPV_DECODER_VIDEOTOOLBOX              = 3;
+    public static final int FFP_PROP_INT64_VIDEO_CACHED_DURATION            = 20005;
+    public static final int FFP_PROP_INT64_AUDIO_CACHED_DURATION            = 20006;
+    public static final int FFP_PROP_INT64_VIDEO_CACHED_BYTES               = 20007;
+    public static final int FFP_PROP_INT64_AUDIO_CACHED_BYTES               = 20008;
+    public static final int FFP_PROP_INT64_VIDEO_CACHED_PACKETS             = 20009;
+    public static final int FFP_PROP_INT64_AUDIO_CACHED_PACKETS             = 20010;
+    public static final int FFP_PROP_INT64_ASYNC_STATISTIC_BUF_BACKWARDS    = 20201;
+    public static final int FFP_PROP_INT64_ASYNC_STATISTIC_BUF_FORWARDS     = 20202;
+    public static final int FFP_PROP_INT64_ASYNC_STATISTIC_BUF_CAPACITY     = 20203;
+    public static final int FFP_PROP_INT64_TRAFFIC_STATISTIC_BYTE_COUNT     = 20204;
+    public static final int FFP_PROP_INT64_CACHE_STATISTIC_PHYSICAL_POS     = 20205;
+    public static final int FFP_PROP_INT64_CACHE_STATISTIC_FILE_FORWARDS    = 20206;
+    public static final int FFP_PROP_INT64_CACHE_STATISTIC_FILE_POS         = 20207;
+    public static final int FFP_PROP_INT64_CACHE_STATISTIC_COUNT_BYTES      = 20208;
+    public static final int FFP_PROP_INT64_LOGICAL_FILE_SIZE                = 20209;
+    public static final int FFP_PROP_INT64_SHARE_CACHE_DATA                 = 20210;
+    public static final int FFP_PROP_INT64_BIT_RATE                         = 20100;
+    public static final int FFP_PROP_INT64_TCP_SPEED                        = 20200;
+    public static final int FFP_PROP_INT64_LATEST_SEEK_LOAD_DURATION        = 20300;
+    public static final int FFP_PROP_INT64_IMMEDIATE_RECONNECT              = 20211;
+    //----------------------------------------
+
+    @AccessedByNative
+    private long mNativeMediaPlayer;
+    @AccessedByNative
+    private long mNativeMediaDataSource;
+
+    @AccessedByNative
+    private long mNativeAndroidIO;
+
+    @AccessedByNative
+    private int mNativeSurfaceTexture;
+
+    @AccessedByNative
+    private int mListenerContext;
+
+    private SurfaceHolder mSurfaceHolder;
+    private EventHandler mEventHandler;
+    private PowerManager.WakeLock mWakeLock = null;
+    private boolean mScreenOnWhilePlaying;
+    private boolean mStayAwake;
+
+    private int mVideoWidth;
+    private int mVideoHeight;
+    private int mVideoSarNum;
+    private int mVideoSarDen;
+
+    private String mDataSource;
+
+    /**
+     * Default library loader
+     * Load them by yourself, if your libraries are not installed at default place.
+     */
+    private static final IjkLibLoader sLocalLibLoader = new IjkLibLoader() {
+        @Override
+        public void loadLibrary(String libName) throws UnsatisfiedLinkError, SecurityException {
+            System.loadLibrary(libName);
+        }
+    };
+
+    private static volatile boolean mIsLibLoaded = false;
+    public static void loadLibrariesOnce(IjkLibLoader libLoader) {
+        synchronized (IjkMediaPlayer.class) {
+            if (!mIsLibLoaded) {
+                if (libLoader == null)
+                    libLoader = sLocalLibLoader;
+
+                libLoader.loadLibrary("ijkffmpeg");
+                libLoader.loadLibrary("ijksdl");
+                libLoader.loadLibrary("ijkplayer");
+                mIsLibLoaded = true;
+            }
+        }
+    }
+
+    private static volatile boolean mIsNativeInitialized = false;
+    private static void initNativeOnce() {
+        synchronized (IjkMediaPlayer.class) {
+            if (!mIsNativeInitialized) {
+                native_init();
+                mIsNativeInitialized = true;
+            }
+        }
+    }
+
+    /**
+     * Default constructor. Consider using one of the create() methods for
+     * synchronously instantiating a IjkMediaPlayer from a Uri or resource.
+     * <p>
+     * When done with the IjkMediaPlayer, you should call {@link #release()}, to
+     * free the resources. If not released, too many IjkMediaPlayer instances
+     * may result in an exception.
+     * </p>
+     */
+    public IjkMediaPlayer() {
+        this(sLocalLibLoader);
+    }
+
+    /**
+     * do not loadLibaray
+     * @param libLoader
+     *              custom library loader, can be null.
+     */
+    public IjkMediaPlayer(IjkLibLoader libLoader) {
+        initPlayer(libLoader);
+    }
+
+    private void initPlayer(IjkLibLoader libLoader) {
+        loadLibrariesOnce(libLoader);
+        initNativeOnce();
+
+        Looper looper;
+        if ((looper = Looper.myLooper()) != null) {
+            mEventHandler = new EventHandler(this, looper);
+        } else if ((looper = Looper.getMainLooper()) != null) {
+            mEventHandler = new EventHandler(this, looper);
+        } else {
+            mEventHandler = null;
+        }
+
+        /*
+         * Native setup requires a weak reference to our object. It's easier to
+         * create it here than in C++.
+         */
+        native_setup(new WeakReference<IjkMediaPlayer>(this));
+    }
+
+    private native void _setFrameAtTime(String imgCachePath, long startTime, long endTime, int num, int imgDefinition)
+            throws IllegalArgumentException, IllegalStateException;
+
+    /*
+     * Update the IjkMediaPlayer SurfaceTexture. Call after setting a new
+     * display surface.
+     */
+    private native void _setVideoSurface(Surface surface);
+
+    /**
+     * Sets the {@link SurfaceHolder} to use for displaying the video portion of
+     * the media.
+     *
+     * Either a surface holder or surface must be set if a display or video sink
+     * is needed. Not calling this method or {@link #setSurface(Surface)} when
+     * playing back a video will result in only the audio track being played. A
+     * null surface holder or surface will result in only the audio track being
+     * played.
+     *
+     * @param sh
+     *            the SurfaceHolder to use for video display
+     */
+    @Override
+    public void setDisplay(SurfaceHolder sh) {
+        mSurfaceHolder = sh;
+        Surface surface;
+        if (sh != null) {
+            surface = sh.getSurface();
+        } else {
+            surface = null;
+        }
+        _setVideoSurface(surface);
+        updateSurfaceScreenOn();
+    }
+
+    /**
+     * Sets the {@link Surface} to be used as the sink for the video portion of
+     * the media. This is similar to {@link #setDisplay(SurfaceHolder)}, but
+     * does not support {@link #setScreenOnWhilePlaying(boolean)}. Setting a
+     * Surface will un-set any Surface or SurfaceHolder that was previously set.
+     * A null surface will result in only the audio track being played.
+     *
+     * If the Surface sends frames to a {@link SurfaceTexture}, the timestamps
+     * returned from {@link SurfaceTexture#getTimestamp()} will have an
+     * unspecified zero point. These timestamps cannot be directly compared
+     * between different media sources, different instances of the same media
+     * source, or multiple runs of the same program. The timestamp is normally
+     * monotonically increasing and is unaffected by time-of-day adjustments,
+     * but it is reset when the position is set.
+     *
+     * @param surface
+     *            The {@link Surface} to be used for the video portion of the
+     *            media.
+     */
+    @Override
+    public void setSurface(Surface surface) {
+        if (mScreenOnWhilePlaying && surface != null) {
+            DebugLog.w(TAG,
+                    "setScreenOnWhilePlaying(true) is ineffective for Surface");
+        }
+        mSurfaceHolder = null;
+        _setVideoSurface(surface);
+        updateSurfaceScreenOn();
+    }
+
+    /**
+     * Sets the data source as a content Uri.
+     *
+     * @param context the Context to use when resolving the Uri
+     * @param uri the Content URI of the data you want to play
+     * @throws IllegalStateException if it is called in an invalid state
+     */
+    @Override
+    public void setDataSource(Context context, Uri uri)
+            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+        setDataSource(context, uri, null);
+    }
+
+    /**
+     * Sets the data source as a content Uri.
+     *
+     * @param context the Context to use when resolving the Uri
+     * @param uri the Content URI of the data you want to play
+     * @param headers the headers to be sent together with the request for the data
+     *                Note that the cross domain redirection is allowed by default, but that can be
+     *                changed with key/value pairs through the headers parameter with
+     *                "android-allow-cross-domain-redirect" as the key and "0" or "1" as the value
+     *                to disallow or allow cross domain redirection.
+     * @throws IllegalStateException if it is called in an invalid state
+     */
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+    @Override
+    public void setDataSource(Context context, Uri uri, Map<String, String> headers)
+            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+        final String scheme = uri.getScheme();
+        if (ContentResolver.SCHEME_FILE.equals(scheme)) {
+            setDataSource(uri.getPath());
+            return;
+        } else if (ContentResolver.SCHEME_CONTENT.equals(scheme)
+                && Settings.AUTHORITY.equals(uri.getAuthority())) {
+            // Redirect ringtones to go directly to underlying provider
+            uri = RingtoneManager.getActualDefaultRingtoneUri(context,
+                    RingtoneManager.getDefaultType(uri));
+            if (uri == null) {
+                throw new FileNotFoundException("Failed to resolve default ringtone");
+            }
+        }
+
+        AssetFileDescriptor fd = null;
+        try {
+            ContentResolver resolver = context.getContentResolver();
+            fd = resolver.openAssetFileDescriptor(uri, "r");
+            if (fd == null) {
+                return;
+            }
+            // Note: using getDeclaredLength so that our behavior is the same
+            // as previous versions when the content provider is returning
+            // a full file.
+            if (fd.getDeclaredLength() < 0) {
+                setDataSource(fd.getFileDescriptor());
+            } else {
+                setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getDeclaredLength());
+            }
+            return;
+        } catch (SecurityException ignored) {
+        } catch (IOException ignored) {
+        } finally {
+            if (fd != null) {
+                fd.close();
+            }
+        }
+
+        Log.d(TAG, "Couldn't open file on client side, trying server side");
+
+        setDataSource(uri.toString(), headers);
+    }
+
+    /**
+     * Sets the data source (file-path or http/rtsp URL) to use.
+     *
+     * @param path
+     *            the path of the file, or the http/rtsp URL of the stream you
+     *            want to play
+     * @throws IllegalStateException
+     *             if it is called in an invalid state
+     *
+     *             <p>
+     *             When <code>path</code> refers to a local file, the file may
+     *             actually be opened by a process other than the calling
+     *             application. This implies that the pathname should be an
+     *             absolute path (as any other process runs with unspecified
+     *             current working directory), and that the pathname should
+     *             reference a world-readable file.
+     */
+    @Override
+    public void setDataSource(String path)
+            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+        mDataSource = path;
+        _setDataSource(path, null, null);
+    }
+
+    /**
+     * Sets the data source (file-path or http/rtsp URL) to use.
+     *
+     * @param path the path of the file, or the http/rtsp URL of the stream you want to play
+     * @param headers the headers associated with the http request for the stream you want to play
+     * @throws IllegalStateException if it is called in an invalid state
+     */
+    public void setDataSource(String path, Map<String, String> headers)
+            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException
+    {
+        if (headers != null && !headers.isEmpty()) {
+            StringBuilder sb = new StringBuilder();
+            for(Map.Entry<String, String> entry: headers.entrySet()) {
+                sb.append(entry.getKey());
+                sb.append(":");
+                String value = entry.getValue();
+                if (!TextUtils.isEmpty(value))
+                    sb.append(entry.getValue());
+                sb.append("\r\n");
+                setOption(OPT_CATEGORY_FORMAT, "headers", sb.toString());
+                setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "protocol_whitelist", "async,cache,crypto,file,http,https,ijkhttphook,ijkinject,ijklivehook,ijklongurl,ijksegment,ijktcphook,pipe,rtp,tcp,tls,udp,ijkurlhook,data");
+            }
+        }
+        setDataSource(path);
+    }
+
+    /**
+     * Sets the data source (FileDescriptor) to use. It is the caller's responsibility
+     * to close the file descriptor. It is safe to do so as soon as this call returns.
+     *
+     * @param fd the FileDescriptor for the file you want to play
+     * @throws IllegalStateException if it is called in an invalid state
+     */
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
+    @Override
+    public void setDataSource(FileDescriptor fd)
+            throws IOException, IllegalArgumentException, IllegalStateException {
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR1) {
+            int native_fd = -1;
+            try {
+                Field f = fd.getClass().getDeclaredField("descriptor"); //NoSuchFieldException
+                f.setAccessible(true);
+                native_fd = f.getInt(fd); //IllegalAccessException
+            } catch (NoSuchFieldException e) {
+                throw new RuntimeException(e);
+            } catch (IllegalAccessException e) {
+                throw new RuntimeException(e);
+            }
+            _setDataSourceFd(native_fd);
+        } else {
+            ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(fd);
+            try {
+                _setDataSourceFd(pfd.getFd());
+            } finally {
+                pfd.close();
+            }
+        }
+    }
+
+    /**
+     * Sets the data source (FileDescriptor) to use.  The FileDescriptor must be
+     * seekable (N.B. a LocalSocket is not seekable). It is the caller's responsibility
+     * to close the file descriptor. It is safe to do so as soon as this call returns.
+     *
+     * @param fd the FileDescriptor for the file you want to play
+     * @param offset the offset into the file where the data to be played starts, in bytes
+     * @param length the length in bytes of the data to be played
+     * @throws IllegalStateException if it is called in an invalid state
+     */
+    private void setDataSource(FileDescriptor fd, long offset, long length)
+            throws IOException, IllegalArgumentException, IllegalStateException {
+        // FIXME: handle offset, length
+        setDataSource(fd);
+    }
+
+    public void setDataSource(IMediaDataSource mediaDataSource)
+            throws IllegalArgumentException, SecurityException, IllegalStateException {
+        _setDataSource(mediaDataSource);
+    }
+
+    public void setAndroidIOCallback(IAndroidIO androidIO)
+            throws IllegalArgumentException, SecurityException, IllegalStateException {
+        _setAndroidIOCallback(androidIO);
+    }
+
+    private native void _setDataSource(String path, String[] keys, String[] values)
+            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
+
+    private native void _setDataSourceFd(int fd)
+            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException;
+
+    private native void _setDataSource(IMediaDataSource mediaDataSource)
+            throws IllegalArgumentException, SecurityException, IllegalStateException;
+
+    private native void _setAndroidIOCallback(IAndroidIO androidIO)
+            throws IllegalArgumentException, SecurityException, IllegalStateException;
+
+    public native Bitmap getFrameBitmap();
+    // public native int getFrameBitmap();
+
+    @Override
+    public String getDataSource() {
+        return mDataSource;
+    }
+
+    @Override
+    public void prepareAsync() throws IllegalStateException {
+        _prepareAsync();
+    }
+
+    public native void _prepareAsync() throws IllegalStateException;
+
+    @Override
+    public void start() throws IllegalStateException {
+        stayAwake(true);
+        _start();
+    }
+
+    private native void _start() throws IllegalStateException;
+
+    @Override
+    public void stop() throws IllegalStateException {
+        stayAwake(false);
+        _stop();
+    }
+
+    private native void _stop() throws IllegalStateException;
+
+    @Override
+    public void pause() throws IllegalStateException {
+        stayAwake(false);
+        _pause();
+    }
+
+    private native void _pause() throws IllegalStateException;
+
+    @SuppressLint("Wakelock")
+    @Override
+    public void setWakeMode(Context context, int mode) {
+        boolean washeld = false;
+        if (mWakeLock != null) {
+            if (mWakeLock.isHeld()) {
+                washeld = true;
+                mWakeLock.release();
+            }
+            mWakeLock = null;
+        }
+
+        PowerManager pm = (PowerManager) context
+                .getSystemService(Context.POWER_SERVICE);
+        mWakeLock = pm.newWakeLock(mode | PowerManager.ON_AFTER_RELEASE,
+                IjkMediaPlayer.class.getName());
+        mWakeLock.setReferenceCounted(false);
+        if (washeld) {
+            mWakeLock.acquire();
+        }
+    }
+
+    @Override
+    public void setScreenOnWhilePlaying(boolean screenOn) {
+        if (mScreenOnWhilePlaying != screenOn) {
+            if (screenOn && mSurfaceHolder == null) {
+                DebugLog.w(TAG,
+                        "setScreenOnWhilePlaying(true) is ineffective without a SurfaceHolder");
+            }
+            mScreenOnWhilePlaying = screenOn;
+            updateSurfaceScreenOn();
+        }
+    }
+
+    @SuppressLint("Wakelock")
+    private void stayAwake(boolean awake) {
+        if (mWakeLock != null) {
+            if (awake && !mWakeLock.isHeld()) {
+                mWakeLock.acquire();
+            } else if (!awake && mWakeLock.isHeld()) {
+                mWakeLock.release();
+            }
+        }
+        mStayAwake = awake;
+        updateSurfaceScreenOn();
+    }
+
+    private void updateSurfaceScreenOn() {
+        if (mSurfaceHolder != null) {
+            mSurfaceHolder.setKeepScreenOn(mScreenOnWhilePlaying && mStayAwake);
+        }
+    }
+
+    @Override
+    public IjkTrackInfo[] getTrackInfo() {
+        Bundle bundle = getMediaMeta();
+        if (bundle == null)
+            return null;
+
+        IjkMediaMeta mediaMeta = IjkMediaMeta.parse(bundle);
+        if (mediaMeta == null || mediaMeta.mStreams == null)
+            return null;
+
+        ArrayList<IjkTrackInfo> trackInfos = new ArrayList<IjkTrackInfo>();
+        for (IjkMediaMeta.IjkStreamMeta streamMeta: mediaMeta.mStreams) {
+            IjkTrackInfo trackInfo = new IjkTrackInfo(streamMeta);
+            if (streamMeta.mType.equalsIgnoreCase(IjkMediaMeta.IJKM_VAL_TYPE__VIDEO)) {
+                trackInfo.setTrackType(ITrackInfo.MEDIA_TRACK_TYPE_VIDEO);
+            } else if (streamMeta.mType.equalsIgnoreCase(IjkMediaMeta.IJKM_VAL_TYPE__AUDIO)) {
+                trackInfo.setTrackType(ITrackInfo.MEDIA_TRACK_TYPE_AUDIO);
+            } else if (streamMeta.mType.equalsIgnoreCase(IjkMediaMeta.IJKM_VAL_TYPE__TIMEDTEXT)) {
+                trackInfo.setTrackType(ITrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT);
+            }
+            trackInfos.add(trackInfo);
+        }
+
+        return trackInfos.toArray(new IjkTrackInfo[trackInfos.size()]);
+    }
+
+    // TODO: @Override
+    public int getSelectedTrack(int trackType) {
+        switch (trackType) {
+            case ITrackInfo.MEDIA_TRACK_TYPE_VIDEO:
+                return (int)_getPropertyLong(FFP_PROP_INT64_SELECTED_VIDEO_STREAM, -1);
+            case ITrackInfo.MEDIA_TRACK_TYPE_AUDIO:
+                return (int)_getPropertyLong(FFP_PROP_INT64_SELECTED_AUDIO_STREAM, -1);
+            case ITrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT:
+                return (int)_getPropertyLong(FFP_PROP_INT64_SELECTED_TIMEDTEXT_STREAM, -1);
+            default:
+                return -1;
+        }
+    }
+
+    // experimental, should set DEFAULT_MIN_FRAMES and MAX_MIN_FRAMES to 25
+    // TODO: @Override
+    public void selectTrack(int track) {
+        _setStreamSelected(track, true);
+    }
+
+    // experimental, should set DEFAULT_MIN_FRAMES and MAX_MIN_FRAMES to 25
+    // TODO: @Override
+    public void deselectTrack(int track) {
+        _setStreamSelected(track, false);
+    }
+
+    private native void _setStreamSelected(int stream, boolean select);
+
+    @Override
+    public int getVideoWidth() {
+        return mVideoWidth;
+    }
+
+    @Override
+    public int getVideoHeight() {
+        return mVideoHeight;
+    }
+
+    @Override
+    public int getVideoSarNum() {
+        return mVideoSarNum;
+    }
+
+    @Override
+    public int getVideoSarDen() {
+        return mVideoSarDen;
+    }
+
+    @Override
+    public native boolean isPlaying();
+
+    @Override
+    public native void seekTo(long msec) throws IllegalStateException;
+
+    @Override
+    public native long getCurrentPosition();
+
+    @Override
+    public native long getDuration();
+
+    /**
+     * Releases resources associated with this IjkMediaPlayer object. It is
+     * considered good practice to call this method when you're done using the
+     * IjkMediaPlayer. In particular, whenever an Activity of an application is
+     * paused (its onPause() method is called), or stopped (its onStop() method
+     * is called), this method should be invoked to release the IjkMediaPlayer
+     * object, unless the application has a special need to keep the object
+     * around. In addition to unnecessary resources (such as memory and
+     * instances of codecs) being held, failure to call this method immediately
+     * if a IjkMediaPlayer object is no longer needed may also lead to
+     * continuous battery consumption for mobile devices, and playback failure
+     * for other applications if no multiple instances of the same codec are
+     * supported on a device. Even if multiple instances of the same codec are
+     * supported, some performance degradation may be expected when unnecessary
+     * multiple instances are used at the same time.
+     */
+    @Override
+    public void release() {
+        stayAwake(false);
+        updateSurfaceScreenOn();
+        resetListeners();
+        _release();
+    }
+
+    private native void _release();
+
+    @Override
+    public void reset() {
+        stayAwake(false);
+        _reset();
+        // make sure none of the listeners get called anymore
+        mEventHandler.removeCallbacksAndMessages(null);
+
+        mVideoWidth = 0;
+        mVideoHeight = 0;
+    }
+
+    private native void _reset();
+
+    /**
+     * Sets the player to be looping or non-looping.
+     *
+     * @param looping whether to loop or not
+     */
+    @Override
+    public void setLooping(boolean looping) {
+        int loopCount = looping ? 0 : 1;
+        setOption(OPT_CATEGORY_PLAYER, "loop", loopCount);
+        _setLoopCount(loopCount);
+    }
+
+    private native void _setLoopCount(int loopCount);
+
+    /**
+     * Checks whether the MediaPlayer is looping or non-looping.
+     *
+     * @return true if the MediaPlayer is currently looping, false otherwise
+     */
+    @Override
+    public boolean isLooping() {
+        int loopCount = _getLoopCount();
+        return loopCount != 1;
+    }
+
+    private native int _getLoopCount();
+
+    public void setSpeed(float speed) {
+        _setPropertyFloat(FFP_PROP_FLOAT_PLAYBACK_RATE, speed);
+    }
+
+    public float getSpeed(float speed) {
+        return _getPropertyFloat(FFP_PROP_FLOAT_PLAYBACK_RATE, .0f);
+    }
+
+    public int getVideoDecoder() {
+        return (int)_getPropertyLong(FFP_PROP_INT64_VIDEO_DECODER, FFP_PROPV_DECODER_UNKNOWN);
+    }
+
+    public float getVideoOutputFramesPerSecond() {
+        return _getPropertyFloat(PROP_FLOAT_VIDEO_OUTPUT_FRAMES_PER_SECOND, 0.0f);
+    }
+
+    public float getVideoDecodeFramesPerSecond() {
+        return _getPropertyFloat(PROP_FLOAT_VIDEO_DECODE_FRAMES_PER_SECOND, 0.0f);
+    }
+
+    public long getVideoCachedDuration() {
+        return _getPropertyLong(FFP_PROP_INT64_VIDEO_CACHED_DURATION, 0);
+    }
+
+    public long getAudioCachedDuration() {
+        return _getPropertyLong(FFP_PROP_INT64_AUDIO_CACHED_DURATION, 0);
+    }
+
+    public long getVideoCachedBytes() {
+        return _getPropertyLong(FFP_PROP_INT64_VIDEO_CACHED_BYTES, 0);
+    }
+
+    public long getAudioCachedBytes() {
+        return _getPropertyLong(FFP_PROP_INT64_AUDIO_CACHED_BYTES, 0);
+    }
+
+    public long getVideoCachedPackets() {
+        return _getPropertyLong(FFP_PROP_INT64_VIDEO_CACHED_PACKETS, 0);
+    }
+
+    public long getAudioCachedPackets() {
+        return _getPropertyLong(FFP_PROP_INT64_AUDIO_CACHED_PACKETS, 0);
+    }
+
+    public long getAsyncStatisticBufBackwards() {
+        return _getPropertyLong(FFP_PROP_INT64_ASYNC_STATISTIC_BUF_BACKWARDS, 0);
+    }
+
+    public long getAsyncStatisticBufForwards() {
+        return _getPropertyLong(FFP_PROP_INT64_ASYNC_STATISTIC_BUF_FORWARDS, 0);
+    }
+
+    public long getAsyncStatisticBufCapacity() {
+        return _getPropertyLong(FFP_PROP_INT64_ASYNC_STATISTIC_BUF_CAPACITY, 0);
+    }
+
+    public long getTrafficStatisticByteCount() {
+        return _getPropertyLong(FFP_PROP_INT64_TRAFFIC_STATISTIC_BYTE_COUNT, 0);
+    }
+
+    public long getCacheStatisticPhysicalPos() {
+        return _getPropertyLong(FFP_PROP_INT64_CACHE_STATISTIC_PHYSICAL_POS, 0);
+    }
+
+    public long getCacheStatisticFileForwards() {
+        return _getPropertyLong(FFP_PROP_INT64_CACHE_STATISTIC_FILE_FORWARDS, 0);
+    }
+
+    public long getCacheStatisticFilePos() {
+        return _getPropertyLong(FFP_PROP_INT64_CACHE_STATISTIC_FILE_POS, 0);
+    }
+
+    public long getCacheStatisticCountBytes() {
+        return _getPropertyLong(FFP_PROP_INT64_CACHE_STATISTIC_COUNT_BYTES, 0);
+    }
+
+    public long getFileSize() {
+        return _getPropertyLong(FFP_PROP_INT64_LOGICAL_FILE_SIZE, 0);
+    }
+
+    public long getBitRate() {
+        return _getPropertyLong(FFP_PROP_INT64_BIT_RATE, 0);
+    }
+
+    public long getTcpSpeed() {
+        return _getPropertyLong(FFP_PROP_INT64_TCP_SPEED, 0);
+    }
+
+    public long getSeekLoadDuration() {
+        return _getPropertyLong(FFP_PROP_INT64_LATEST_SEEK_LOAD_DURATION, 0);
+    }
+
+    private native float _getPropertyFloat(int property, float defaultValue);
+    private native void  _setPropertyFloat(int property, float value);
+    private native long  _getPropertyLong(int property, long defaultValue);
+    private native void  _setPropertyLong(int property, long value);
+
+    public float getDropFrameRate() {
+        return _getPropertyFloat(FFP_PROP_FLOAT_DROP_FRAME_RATE, .0f);
+    }
+
+    @Override
+    public native void setVolume(float leftVolume, float rightVolume);
+
+    @Override
+    public native int getAudioSessionId();
+
+    @Override
+    public MediaInfo getMediaInfo() {
+        MediaInfo mediaInfo = new MediaInfo();
+        mediaInfo.mMediaPlayerName = "ijkplayer";
+
+        String videoCodecInfo = _getVideoCodecInfo();
+        if (!TextUtils.isEmpty(videoCodecInfo)) {
+            String nodes[] = videoCodecInfo.split(",");
+            if (nodes.length >= 2) {
+                mediaInfo.mVideoDecoder = nodes[0];
+                mediaInfo.mVideoDecoderImpl = nodes[1];
+            } else if (nodes.length >= 1) {
+                mediaInfo.mVideoDecoder = nodes[0];
+                mediaInfo.mVideoDecoderImpl = "";
+            }
+        }
+
+        String audioCodecInfo = _getAudioCodecInfo();
+        if (!TextUtils.isEmpty(audioCodecInfo)) {
+            String nodes[] = audioCodecInfo.split(",");
+            if (nodes.length >= 2) {
+                mediaInfo.mAudioDecoder = nodes[0];
+                mediaInfo.mAudioDecoderImpl = nodes[1];
+            } else if (nodes.length >= 1) {
+                mediaInfo.mAudioDecoder = nodes[0];
+                mediaInfo.mAudioDecoderImpl = "";
+            }
+        }
+
+        try {
+            mediaInfo.mMeta = IjkMediaMeta.parse(_getMediaMeta());
+        } catch (Throwable e) {
+            e.printStackTrace();
+        }
+        return mediaInfo;
+    }
+
+    @Override
+    public void setLogEnabled(boolean enable) {
+        // do nothing
+    }
+
+    @Override
+    public boolean isPlayable() {
+        return true;
+    }
+
+    private native String _getVideoCodecInfo();
+    private native String _getAudioCodecInfo();
+
+    public void setOption(int category, String name, String value)
+    {
+        _setOption(category, name, value);
+    }
+
+    public void setOption(int category, String name, long value)
+    {
+        _setOption(category, name, value);
+    }
+
+    private native void _setOption(int category, String name, String value);
+    private native void _setOption(int category, String name, long value);
+
+    public Bundle getMediaMeta() {
+        return _getMediaMeta();
+    }
+    private native Bundle _getMediaMeta();
+
+    public static String getColorFormatName(int mediaCodecColorFormat) {
+        return _getColorFormatName(mediaCodecColorFormat);
+    }
+
+    private static native String _getColorFormatName(int mediaCodecColorFormat);
+
+    @Override
+    public void setAudioStreamType(int streamtype) {
+        // do nothing
+    }
+
+    @Override
+    public void setKeepInBackground(boolean keepInBackground) {
+        // do nothing
+    }
+
+    private static native void native_init();
+
+    private native void native_setup(Object IjkMediaPlayer_this);
+
+    private native void native_finalize();
+
+    private native void native_message_loop(Object IjkMediaPlayer_this);
+
+    protected void finalize() throws Throwable {
+        super.finalize();
+        native_finalize();
+    }
+
+    public void httphookReconnect() {
+        _setPropertyLong(FFP_PROP_INT64_IMMEDIATE_RECONNECT, 1);
+    }
+
+    public void setCacheShare(int share) {
+        _setPropertyLong(FFP_PROP_INT64_SHARE_CACHE_DATA, (long)share);
+    }
+
+    private static class EventHandler extends Handler {
+        private final WeakReference<IjkMediaPlayer> mWeakPlayer;
+
+        public EventHandler(IjkMediaPlayer mp, Looper looper) {
+            super(looper);
+            mWeakPlayer = new WeakReference<IjkMediaPlayer>(mp);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            IjkMediaPlayer player = mWeakPlayer.get();
+            if (player == null || player.mNativeMediaPlayer == 0) {
+                DebugLog.w(TAG,
+                        "IjkMediaPlayer went away with unhandled events");
+                return;
+            }
+
+            switch (msg.what) {
+            case MEDIA_PREPARED:
+                player.notifyOnPrepared();
+                return;
+
+            case MEDIA_PLAYBACK_COMPLETE:
+                player.stayAwake(false);
+                player.notifyOnCompletion();
+                return;
+
+            case MEDIA_BUFFERING_UPDATE:
+                long bufferPosition = msg.arg1;
+                if (bufferPosition < 0) {
+                    bufferPosition = 0;
+                }
+
+                long percent = 0;
+                long duration = player.getDuration();
+                if (duration > 0) {
+                    percent = bufferPosition * 100 / duration;
+                }
+                if (percent >= 100) {
+                    percent = 100;
+                }
+
+                // DebugLog.efmt(TAG, "Buffer (%d%%) %d/%d",  percent, bufferPosition, duration);
+                player.notifyOnBufferingUpdate((int)percent);
+                return;
+
+            case MEDIA_SEEK_COMPLETE:
+                player.notifyOnSeekComplete();
+                return;
+
+            case MEDIA_SET_VIDEO_SIZE:
+                player.mVideoWidth = msg.arg1;
+                player.mVideoHeight = msg.arg2;
+                player.notifyOnVideoSizeChanged(player.mVideoWidth, player.mVideoHeight,
+                        player.mVideoSarNum, player.mVideoSarDen);
+                return;
+
+            case MEDIA_ERROR:
+                DebugLog.e(TAG, "Error (" + msg.arg1 + "," + msg.arg2 + ")");
+                if (!player.notifyOnError(msg.arg1, msg.arg2)) {
+                    player.notifyOnCompletion();
+                }
+                player.stayAwake(false);
+                return;
+
+            case MEDIA_INFO:
+                switch (msg.arg1) {
+                    case MEDIA_INFO_VIDEO_RENDERING_START:
+                        DebugLog.i(TAG, "Info: MEDIA_INFO_VIDEO_RENDERING_START\n");
+                        break;
+                }
+                player.notifyOnInfo(msg.arg1, msg.arg2);
+                // No real default action so far.
+                return;
+            case MEDIA_TIMED_TEXT:
+                if (msg.obj == null) {
+                    player.notifyOnTimedText(null);
+                } else {
+                    IjkTimedText text = new IjkTimedText(new Rect(0, 0, 1, 1), (String)msg.obj);
+                    player.notifyOnTimedText(text);
+                }
+                return;
+            case MEDIA_NOP: // interface test message - ignore
+                break;
+
+            case MEDIA_SET_VIDEO_SAR:
+                player.mVideoSarNum = msg.arg1;
+                player.mVideoSarDen = msg.arg2;
+                player.notifyOnVideoSizeChanged(player.mVideoWidth, player.mVideoHeight,
+                        player.mVideoSarNum, player.mVideoSarDen);
+                break;
+
+            default:
+                DebugLog.e(TAG, "Unknown message type " + msg.what);
+            }
+        }
+    }
+
+    /*
+     * Called from native code when an interesting event happens. This method
+     * just uses the EventHandler system to post the event back to the main app
+     * thread. We use a weak reference to the original IjkMediaPlayer object so
+     * that the native code is safe from the object disappearing from underneath
+     * it. (This is the cookie passed to native_setup().)
+     */
+    @CalledByNative
+    private static void postEventFromNative(Object weakThiz, int what,
+            int arg1, int arg2, Object obj) {
+        if (weakThiz == null)
+            return;
+
+        @SuppressWarnings("rawtypes")
+        IjkMediaPlayer mp = (IjkMediaPlayer) ((WeakReference) weakThiz).get();
+        if (mp == null) {
+            return;
+        }
+
+        if (what == MEDIA_INFO && arg1 == MEDIA_INFO_STARTED_AS_NEXT) {
+            // this acquires the wakelock if needed, and sets the client side
+            // state
+            mp.start();
+        }
+        if (mp.mEventHandler != null) {
+            Message m = mp.mEventHandler.obtainMessage(what, arg1, arg2, obj);
+            mp.mEventHandler.sendMessage(m);
+        }
+    }
+
+    /*
+     * ControlMessage
+     */
+
+    private OnControlMessageListener mOnControlMessageListener;
+    public void setOnControlMessageListener(OnControlMessageListener listener) {
+        mOnControlMessageListener = listener;
+    }
+
+    public interface OnControlMessageListener {
+        String onControlResolveSegmentUrl(int segment);
+    }
+
+    /*
+     * NativeInvoke
+     */
+
+    private OnNativeInvokeListener mOnNativeInvokeListener;
+    public void setOnNativeInvokeListener(OnNativeInvokeListener listener) {
+        mOnNativeInvokeListener = listener;
+    }
+
+    public interface OnNativeInvokeListener {
+
+        int CTRL_WILL_TCP_OPEN = 0x20001;               // NO ARGS
+        int CTRL_DID_TCP_OPEN = 0x20002;                // ARG_ERROR, ARG_FAMILIY, ARG_IP, ARG_PORT, ARG_FD
+
+        int CTRL_WILL_HTTP_OPEN = 0x20003;              // ARG_URL, ARG_SEGMENT_INDEX, ARG_RETRY_COUNTER
+        int CTRL_WILL_LIVE_OPEN = 0x20005;              // ARG_URL, ARG_RETRY_COUNTER
+        int CTRL_WILL_CONCAT_RESOLVE_SEGMENT = 0x20007; // ARG_URL, ARG_SEGMENT_INDEX, ARG_RETRY_COUNTER
+
+        int EVENT_WILL_HTTP_OPEN = 0x1;                 // ARG_URL
+        int EVENT_DID_HTTP_OPEN = 0x2;                  // ARG_URL, ARG_ERROR, ARG_HTTP_CODE
+        int EVENT_WILL_HTTP_SEEK = 0x3;                 // ARG_URL, ARG_OFFSET
+        int EVENT_DID_HTTP_SEEK = 0x4;                  // ARG_URL, ARG_OFFSET, ARG_ERROR, ARG_HTTP_CODE, ARG_FILE_SIZE
+
+        String ARG_URL = "url";
+        String ARG_SEGMENT_INDEX = "segment_index";
+        String ARG_RETRY_COUNTER = "retry_counter";
+
+        String ARG_ERROR = "error";
+        String ARG_FAMILIY = "family";
+        String ARG_IP = "ip";
+        String ARG_PORT = "port";
+        String ARG_FD = "fd";
+
+        String ARG_OFFSET = "offset";
+        String ARG_HTTP_CODE = "http_code";
+        String ARG_FILE_SIZE = "file_size";
+
+        /*
+         * @return true if invoke is handled
+         * @throws Exception on any error
+         */
+        boolean onNativeInvoke(int what, Bundle args);
+    }
+
+    @CalledByNative
+    private static boolean onNativeInvoke(Object weakThiz, int what, Bundle args) {
+        DebugLog.ifmt(TAG, "onNativeInvoke %d", what);
+        if (weakThiz == null || !(weakThiz instanceof WeakReference<?>))
+            throw new IllegalStateException("<null weakThiz>.onNativeInvoke()");
+
+        @SuppressWarnings("unchecked")
+        WeakReference<IjkMediaPlayer> weakPlayer = (WeakReference<IjkMediaPlayer>) weakThiz;
+        IjkMediaPlayer player = weakPlayer.get();
+        if (player == null)
+            throw new IllegalStateException("<null weakPlayer>.onNativeInvoke()");
+
+        OnNativeInvokeListener listener = player.mOnNativeInvokeListener;
+        if (listener != null && listener.onNativeInvoke(what, args))
+            return true;
+
+        switch (what) {
+            case OnNativeInvokeListener.CTRL_WILL_CONCAT_RESOLVE_SEGMENT: {
+                OnControlMessageListener onControlMessageListener = player.mOnControlMessageListener;
+                if (onControlMessageListener == null)
+                    return false;
+
+                int segmentIndex = args.getInt(OnNativeInvokeListener.ARG_SEGMENT_INDEX, -1);
+                if (segmentIndex < 0)
+                    throw new InvalidParameterException("onNativeInvoke(invalid segment index)");
+
+                String newUrl = onControlMessageListener.onControlResolveSegmentUrl(segmentIndex);
+                if (newUrl == null)
+                    throw new RuntimeException(new IOException("onNativeInvoke() = <NULL newUrl>"));
+
+                args.putString(OnNativeInvokeListener.ARG_URL, newUrl);
+                return true;
+            }
+            default:
+                return false;
+        }
+    }
+
+    /*
+     * MediaCodec select
+     */
+
+    public interface OnMediaCodecSelectListener {
+        String onMediaCodecSelect(IMediaPlayer mp, String mimeType, int profile, int level);
+    }
+    private OnMediaCodecSelectListener mOnMediaCodecSelectListener;
+    public void setOnMediaCodecSelectListener(OnMediaCodecSelectListener listener) {
+        mOnMediaCodecSelectListener = listener;
+    }
+
+    public void resetListeners() {
+        super.resetListeners();
+        mOnMediaCodecSelectListener = null;
+    }
+
+    @CalledByNative
+    private static String onSelectCodec(Object weakThiz, String mimeType, int profile, int level) {
+        if (weakThiz == null || !(weakThiz instanceof WeakReference<?>))
+            return null;
+
+        @SuppressWarnings("unchecked")
+        WeakReference<IjkMediaPlayer> weakPlayer = (WeakReference<IjkMediaPlayer>) weakThiz;
+        IjkMediaPlayer player = weakPlayer.get();
+        if (player == null)
+            return null;
+
+        OnMediaCodecSelectListener listener = player.mOnMediaCodecSelectListener;
+        if (listener == null)
+            listener = DefaultMediaCodecSelector.sInstance;
+
+        return listener.onMediaCodecSelect(player, mimeType, profile, level);
+    }
+
+    public static class DefaultMediaCodecSelector implements OnMediaCodecSelectListener {
+        public static final DefaultMediaCodecSelector sInstance = new DefaultMediaCodecSelector();
+
+        @SuppressWarnings("deprecation")
+        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+        public String onMediaCodecSelect(IMediaPlayer mp, String mimeType, int profile, int level) {
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN)
+                return null;
+
+            if (TextUtils.isEmpty(mimeType))
+                return null;
+
+            Log.i(TAG, String.format(Locale.US, "onSelectCodec: mime=%s, profile=%d, level=%d", mimeType, profile, level));
+            ArrayList<IjkMediaCodecInfo> candidateCodecList = new ArrayList<IjkMediaCodecInfo>();
+            int numCodecs = MediaCodecList.getCodecCount();
+            for (int i = 0; i < numCodecs; i++) {
+                MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
+                Log.d(TAG, String.format(Locale.US, "  found codec: %s", codecInfo.getName()));
+                if (codecInfo.isEncoder())
+                    continue;
+
+                String[] types = codecInfo.getSupportedTypes();
+                if (types == null)
+                    continue;
+
+                for(String type: types) {
+                    if (TextUtils.isEmpty(type))
+                        continue;
+
+                    Log.d(TAG, String.format(Locale.US, "    mime: %s", type));
+                    if (!type.equalsIgnoreCase(mimeType))
+                        continue;
+
+                    IjkMediaCodecInfo candidate = IjkMediaCodecInfo.setupCandidate(codecInfo, mimeType);
+                    if (candidate == null)
+                        continue;
+
+                    candidateCodecList.add(candidate);
+                    Log.i(TAG, String.format(Locale.US, "candidate codec: %s rank=%d", codecInfo.getName(), candidate.mRank));
+                    candidate.dumpProfileLevels(mimeType);
+                }
+            }
+
+            if (candidateCodecList.isEmpty()) {
+                return null;
+            }
+
+            IjkMediaCodecInfo bestCodec = candidateCodecList.get(0);
+
+            for (IjkMediaCodecInfo codec : candidateCodecList) {
+                if (codec.mRank > bestCodec.mRank) {
+                    bestCodec = codec;
+                }
+            }
+
+            if (bestCodec.mRank < IjkMediaCodecInfo.RANK_LAST_CHANCE) {
+                Log.w(TAG, String.format(Locale.US, "unaccetable codec: %s", bestCodec.mCodecInfo.getName()));
+                return null;
+            }
+
+            Log.i(TAG, String.format(Locale.US, "selected codec: %s rank=%d", bestCodec.mCodecInfo.getName(), bestCodec.mRank));
+            return bestCodec.mCodecInfo.getName();
+        }
+    }
+
+    public static native void native_profileBegin(String libName);
+    public static native void native_profileEnd();
+    public static native void native_setLogLevel(int level);
+}

+ 39 - 0
android/src/main/java/tv/danmaku/ijk/media/player/IjkTimedText.java

@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 Zheng Yuan <zhengyuan10503@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player;
+
+import android.graphics.Rect;
+import java.lang.String;
+
+public final class IjkTimedText {
+
+    private Rect mTextBounds = null;
+    private String mTextChars = null;
+
+    public IjkTimedText(Rect bounds, String text) {
+        mTextBounds = bounds;
+        mTextChars = text;
+    }
+
+    public Rect getBounds() {
+        return mTextBounds;
+    }
+
+    public String getText() {
+        return mTextChars;
+    }
+}

+ 30 - 0
android/src/main/java/tv/danmaku/ijk/media/player/MediaInfo.java

@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2013-2014 Bilibili
+ * Copyright (C) 2013-2014 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player;
+
+public class MediaInfo {
+    public String mMediaPlayerName;
+
+    public String mVideoDecoder;
+    public String mVideoDecoderImpl;
+
+    public String mAudioDecoder;
+    public String mAudioDecoderImpl;
+
+    public IjkMediaMeta mMeta;
+}

+ 339 - 0
android/src/main/java/tv/danmaku/ijk/media/player/MediaPlayerProxy.java

@@ -0,0 +1,339 @@
+/*
+ * Copyright (C) 2015 Bilibili
+ * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Build;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.util.Map;
+
+import tv.danmaku.ijk.media.player.misc.IMediaDataSource;
+import tv.danmaku.ijk.media.player.misc.ITrackInfo;
+
+public class MediaPlayerProxy implements IMediaPlayer {
+    protected final IMediaPlayer mBackEndMediaPlayer;
+
+    public MediaPlayerProxy(IMediaPlayer backEndMediaPlayer) {
+        mBackEndMediaPlayer = backEndMediaPlayer;
+    }
+
+    public IMediaPlayer getInternalMediaPlayer() {
+        return mBackEndMediaPlayer;
+    }
+
+    @Override
+    public void setDisplay(SurfaceHolder sh) {
+        mBackEndMediaPlayer.setDisplay(sh);
+    }
+
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+    @Override
+    public void setSurface(Surface surface) {
+        mBackEndMediaPlayer.setSurface(surface);
+    }
+
+    @Override
+    public void setDataSource(Context context, Uri uri)
+            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+        mBackEndMediaPlayer.setDataSource(context, uri);
+    }
+
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+    @Override
+    public void setDataSource(Context context, Uri uri, Map<String, String> headers)
+            throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+        mBackEndMediaPlayer.setDataSource(context, uri, headers);
+    }
+
+    @Override
+    public void setDataSource(FileDescriptor fd)
+            throws IOException, IllegalArgumentException, IllegalStateException {
+        mBackEndMediaPlayer.setDataSource(fd);
+    }
+
+    @Override
+    public void setDataSource(String path) throws IOException, IllegalArgumentException, SecurityException, IllegalStateException {
+        mBackEndMediaPlayer.setDataSource(path);
+    }
+
+    @Override
+    public void setDataSource(IMediaDataSource mediaDataSource)  {
+        mBackEndMediaPlayer.setDataSource(mediaDataSource);
+    }
+
+    @Override
+    public String getDataSource() {
+        return mBackEndMediaPlayer.getDataSource();
+    }
+
+    @Override
+    public void prepareAsync() throws IllegalStateException {
+        mBackEndMediaPlayer.prepareAsync();
+    }
+
+    @Override
+    public void start() throws IllegalStateException {
+        mBackEndMediaPlayer.start();
+    }
+
+    @Override
+    public void stop() throws IllegalStateException {
+        mBackEndMediaPlayer.stop();
+    }
+
+    @Override
+    public void pause() throws IllegalStateException {
+        mBackEndMediaPlayer.pause();
+    }
+
+    @Override
+    public void setScreenOnWhilePlaying(boolean screenOn) {
+        mBackEndMediaPlayer.setScreenOnWhilePlaying(screenOn);
+    }
+
+    @Override
+    public int getVideoWidth() {
+        return mBackEndMediaPlayer.getVideoWidth();
+    }
+
+    @Override
+    public int getVideoHeight() {
+        return mBackEndMediaPlayer.getVideoHeight();
+    }
+
+    @Override
+    public boolean isPlaying() {
+        return mBackEndMediaPlayer.isPlaying();
+    }
+
+    @Override
+    public void seekTo(long msec) throws IllegalStateException {
+        mBackEndMediaPlayer.seekTo(msec);
+    }
+
+    @Override
+    public long getCurrentPosition() {
+        return mBackEndMediaPlayer.getCurrentPosition();
+    }
+
+    @Override
+    public long getDuration() {
+        return mBackEndMediaPlayer.getDuration();
+    }
+
+    @Override
+    public void release() {
+        mBackEndMediaPlayer.release();
+    }
+
+    @Override
+    public void reset() {
+        mBackEndMediaPlayer.reset();
+    }
+
+    @Override
+    public void setVolume(float leftVolume, float rightVolume) {
+        mBackEndMediaPlayer.setVolume(leftVolume, rightVolume);
+    }
+
+    @Override
+    public int getAudioSessionId() {
+        return mBackEndMediaPlayer.getAudioSessionId();
+    }
+
+    @Override
+    public MediaInfo getMediaInfo() {
+        return mBackEndMediaPlayer.getMediaInfo();
+    }
+
+    @Override
+    public void setLogEnabled(boolean enable) {
+
+    }
+
+    @Override
+    public boolean isPlayable() {
+        return false;
+    }
+
+    @Override
+    public void setOnPreparedListener(OnPreparedListener listener) {
+        if (listener != null) {
+            final OnPreparedListener finalListener = listener;
+            mBackEndMediaPlayer.setOnPreparedListener(new OnPreparedListener() {
+                @Override
+                public void onPrepared(IMediaPlayer mp) {
+                    finalListener.onPrepared(MediaPlayerProxy.this);
+                }
+            });
+        } else {
+            mBackEndMediaPlayer.setOnPreparedListener(null);
+        }
+    }
+
+    @Override
+    public void setOnCompletionListener(OnCompletionListener listener) {
+        if (listener != null) {
+            final OnCompletionListener finalListener = listener;
+            mBackEndMediaPlayer.setOnCompletionListener(new OnCompletionListener() {
+                @Override
+                public void onCompletion(IMediaPlayer mp) {
+                    finalListener.onCompletion(MediaPlayerProxy.this);
+                }
+            });
+        } else {
+            mBackEndMediaPlayer.setOnCompletionListener(null);
+        }
+    }
+
+    @Override
+    public void setOnBufferingUpdateListener(OnBufferingUpdateListener listener) {
+        if (listener != null) {
+            final OnBufferingUpdateListener finalListener = listener;
+            mBackEndMediaPlayer.setOnBufferingUpdateListener(new OnBufferingUpdateListener() {
+                @Override
+                public void onBufferingUpdate(IMediaPlayer mp, int percent) {
+                    finalListener.onBufferingUpdate(MediaPlayerProxy.this, percent);
+                }
+            });
+        } else {
+            mBackEndMediaPlayer.setOnBufferingUpdateListener(null);
+        }
+    }
+
+    @Override
+    public void setOnSeekCompleteListener(OnSeekCompleteListener listener) {
+        if (listener != null) {
+            final OnSeekCompleteListener finalListener = listener;
+            mBackEndMediaPlayer.setOnSeekCompleteListener(new OnSeekCompleteListener() {
+                @Override
+                public void onSeekComplete(IMediaPlayer mp) {
+                    finalListener.onSeekComplete(MediaPlayerProxy.this);
+                }
+            });
+        } else {
+            mBackEndMediaPlayer.setOnSeekCompleteListener(null);
+        }
+    }
+
+    @Override
+    public void setOnVideoSizeChangedListener(OnVideoSizeChangedListener listener) {
+        if (listener != null) {
+            final OnVideoSizeChangedListener finalListener = listener;
+            mBackEndMediaPlayer.setOnVideoSizeChangedListener(new OnVideoSizeChangedListener() {
+                @Override
+                public void onVideoSizeChanged(IMediaPlayer mp, int width, int height, int sar_num, int sar_den) {
+                    finalListener.onVideoSizeChanged(MediaPlayerProxy.this, width, height, sar_num, sar_den);
+                }
+            });
+        } else {
+            mBackEndMediaPlayer.setOnVideoSizeChangedListener(null);
+        }
+    }
+
+    @Override
+    public void setOnErrorListener(OnErrorListener listener) {
+        if (listener != null) {
+            final OnErrorListener finalListener = listener;
+            mBackEndMediaPlayer.setOnErrorListener(new OnErrorListener() {
+                @Override
+                public boolean onError(IMediaPlayer mp, int what, int extra) {
+                    return finalListener.onError(MediaPlayerProxy.this, what, extra);
+                }
+            });
+        } else {
+            mBackEndMediaPlayer.setOnErrorListener(null);
+        }
+    }
+
+    @Override
+    public void setOnInfoListener(OnInfoListener listener) {
+        if (listener != null) {
+            final OnInfoListener finalListener = listener;
+            mBackEndMediaPlayer.setOnInfoListener(new OnInfoListener() {
+                @Override
+                public boolean onInfo(IMediaPlayer mp, int what, int extra) {
+                    return finalListener.onInfo(MediaPlayerProxy.this, what, extra);
+                }
+            });
+        } else {
+            mBackEndMediaPlayer.setOnInfoListener(null);
+        }
+    }
+
+    @Override
+    public void setOnTimedTextListener(OnTimedTextListener listener) {
+        if (listener != null) {
+            final OnTimedTextListener finalListener = listener;
+            mBackEndMediaPlayer.setOnTimedTextListener(new OnTimedTextListener() {
+                @Override
+                public void onTimedText(IMediaPlayer mp, IjkTimedText text) {
+                    finalListener.onTimedText(MediaPlayerProxy.this, text);
+                }
+            });
+        } else {
+            mBackEndMediaPlayer.setOnTimedTextListener(null);
+        }
+    }
+
+    @Override
+    public void setAudioStreamType(int streamtype) {
+        mBackEndMediaPlayer.setAudioStreamType(streamtype);
+    }
+
+    @Override
+    public void setKeepInBackground(boolean keepInBackground) {
+        mBackEndMediaPlayer.setKeepInBackground(keepInBackground);
+    }
+
+    @Override
+    public int getVideoSarNum() {
+        return mBackEndMediaPlayer.getVideoSarNum();
+    }
+
+    @Override
+    public int getVideoSarDen() {
+        return mBackEndMediaPlayer.getVideoSarDen();
+    }
+
+    @Override
+    public void setWakeMode(Context context, int mode) {
+        mBackEndMediaPlayer.setWakeMode(context, mode);
+    }
+
+    @Override
+    public ITrackInfo[] getTrackInfo() {
+        return mBackEndMediaPlayer.getTrackInfo();
+    }
+
+    @Override
+    public void setLooping(boolean looping) {
+        mBackEndMediaPlayer.setLooping(looping);
+    }
+
+    @Override
+    public boolean isLooping() {
+        return mBackEndMediaPlayer.isLooping();
+    }
+}

+ 100 - 0
android/src/main/java/tv/danmaku/ijk/media/player/TextureMediaPlayer.java

@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2015 Bilibili
+ * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player;
+
+import android.annotation.TargetApi;
+import android.graphics.SurfaceTexture;
+import android.os.Build;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+public class TextureMediaPlayer extends MediaPlayerProxy implements IMediaPlayer, ISurfaceTextureHolder {
+    private SurfaceTexture mSurfaceTexture;
+    private ISurfaceTextureHost mSurfaceTextureHost;
+
+    public TextureMediaPlayer(IMediaPlayer backEndMediaPlayer) {
+        super(backEndMediaPlayer);
+    }
+
+    public void releaseSurfaceTexture() {
+        if (mSurfaceTexture != null) {
+            if (mSurfaceTextureHost != null) {
+                mSurfaceTextureHost.releaseSurfaceTexture(mSurfaceTexture);
+            } else {
+                mSurfaceTexture.release();
+            }
+            mSurfaceTexture = null;
+        }
+    }
+
+    //--------------------
+    // IMediaPlayer
+    //--------------------
+    @Override
+    public void reset() {
+        super.reset();
+        releaseSurfaceTexture();
+    }
+
+    @Override
+    public void release() {
+        super.release();
+        releaseSurfaceTexture();
+    }
+
+    @Override
+    public void setDisplay(SurfaceHolder sh) {
+        if (mSurfaceTexture == null)
+            super.setDisplay(sh);
+    }
+
+    @Override
+    public void setSurface(Surface surface) {
+        if (mSurfaceTexture == null)
+            super.setSurface(surface);
+    }
+
+    //--------------------
+    // ISurfaceTextureHolder
+    //--------------------
+
+    @Override
+    public void setSurfaceTexture(SurfaceTexture surfaceTexture) {
+        if (mSurfaceTexture == surfaceTexture)
+            return;
+
+        releaseSurfaceTexture();
+        mSurfaceTexture = surfaceTexture;
+        if (surfaceTexture == null) {
+            super.setSurface(null);
+        } else {
+            super.setSurface(new Surface(surfaceTexture));
+        }
+    }
+
+    @Override
+    public SurfaceTexture getSurfaceTexture() {
+        return mSurfaceTexture;
+    }
+
+    @Override
+    public void setSurfaceTextureHost(ISurfaceTextureHost surfaceTextureHost) {
+        mSurfaceTextureHost = surfaceTextureHost;
+    }
+}

+ 32 - 0
android/src/main/java/tv/danmaku/ijk/media/player/annotations/AccessedByNative.java

@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2013-2014 Bilibili
+ * Copyright (C) 2013-2014 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * is used by the JNI generator to create the necessary JNI
+ * bindings and expose this method to native code.
+ */
+@Target(ElementType.FIELD)
+@Retention(RetentionPolicy.CLASS)
+public @interface AccessedByNative {
+}

+ 36 - 0
android/src/main/java/tv/danmaku/ijk/media/player/annotations/CalledByNative.java

@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2013-2014 Bilibili
+ * Copyright (C) 2013-2014 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * is used by the JNI generator to create the necessary JNI
+ * bindings and expose this method to native code.
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.CLASS)
+public @interface CalledByNative {
+    /*
+     * If present, tells which inner class the method belongs to.
+     */
+    String value() default "";
+}

+ 22 - 0
android/src/main/java/tv/danmaku/ijk/media/player/exceptions/IjkMediaException.java

@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2013-2014 Bilibili
+ * Copyright (C) 2013-2014 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player.exceptions;
+
+public class IjkMediaException extends Exception {
+    private static final long serialVersionUID = 7234796519009099506L;
+}

+ 5 - 0
android/src/main/java/tv/danmaku/ijk/media/player/ffmpeg/FFmpegApi.java

@@ -0,0 +1,5 @@
+package tv.danmaku.ijk.media.player.ffmpeg;
+
+public class FFmpegApi {
+    public static native String av_base64_encode(byte in[]);
+}

+ 63 - 0
android/src/main/java/tv/danmaku/ijk/media/player/misc/AndroidMediaFormat.java

@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 Bilibili
+ * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player.misc;
+
+import android.annotation.TargetApi;
+import android.media.MediaFormat;
+import android.os.Build;
+
+public class AndroidMediaFormat implements IMediaFormat {
+    private final MediaFormat mMediaFormat;
+
+    public AndroidMediaFormat(MediaFormat mediaFormat) {
+        mMediaFormat = mediaFormat;
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    @Override
+    public int getInteger(String name) {
+        if (mMediaFormat == null)
+            return 0;
+
+        return mMediaFormat.getInteger(name);
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    @Override
+    public String getString(String name) {
+        if (mMediaFormat == null)
+            return null;
+
+        return mMediaFormat.getString(name);
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    @Override
+    public String toString() {
+        StringBuilder out = new StringBuilder(128);
+        out.append(getClass().getName());
+        out.append('{');
+        if (mMediaFormat != null) {
+            out.append(mMediaFormat.toString());
+        } else {
+            out.append("null");
+        }
+        out.append('}');
+        return out.toString();
+    }
+}

+ 109 - 0
android/src/main/java/tv/danmaku/ijk/media/player/misc/AndroidTrackInfo.java

@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2015 Bilibili
+ * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player.misc;
+
+import android.annotation.TargetApi;
+import android.media.MediaFormat;
+import android.media.MediaPlayer;
+import android.os.Build;
+
+public class AndroidTrackInfo implements ITrackInfo {
+    private final MediaPlayer.TrackInfo mTrackInfo;
+
+    public static AndroidTrackInfo[] fromMediaPlayer(MediaPlayer mp) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
+            return fromTrackInfo(mp.getTrackInfo());
+
+        return null;
+    }
+
+    private static AndroidTrackInfo[] fromTrackInfo(MediaPlayer.TrackInfo[] trackInfos) {
+        if (trackInfos == null)
+            return null;
+
+        AndroidTrackInfo androidTrackInfo[] = new AndroidTrackInfo[trackInfos.length];
+        for (int i = 0; i < trackInfos.length; ++i) {
+            androidTrackInfo[i] = new AndroidTrackInfo(trackInfos[i]);
+        }
+
+        return androidTrackInfo;
+    }
+
+    private AndroidTrackInfo(MediaPlayer.TrackInfo trackInfo) {
+        mTrackInfo = trackInfo;
+    }
+
+    @TargetApi(Build.VERSION_CODES.KITKAT)
+    @Override
+    public IMediaFormat getFormat() {
+        if (mTrackInfo == null)
+            return null;
+
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT)
+            return null;
+
+        MediaFormat mediaFormat = mTrackInfo.getFormat();
+        if (mediaFormat == null)
+            return null;
+
+        return new AndroidMediaFormat(mediaFormat);
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    @Override
+    public String getLanguage() {
+        if (mTrackInfo == null)
+            return "und";
+
+        return mTrackInfo.getLanguage();
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    @Override
+    public int getTrackType() {
+        if (mTrackInfo == null)
+            return MEDIA_TRACK_TYPE_UNKNOWN;
+
+        return mTrackInfo.getTrackType();
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    @Override
+    public String toString() {
+        StringBuilder out = new StringBuilder(128);
+        out.append(getClass().getSimpleName());
+        out.append('{');
+        if (mTrackInfo != null) {
+            out.append(mTrackInfo.toString());
+        } else {
+            out.append("null");
+        }
+        out.append('}');
+        return out.toString();
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    @Override
+    public String getInfoInline() {
+        if (mTrackInfo != null) {
+            return mTrackInfo.toString();
+        } else {
+            return "null";
+        }
+    }
+}

+ 28 - 0
android/src/main/java/tv/danmaku/ijk/media/player/misc/IAndroidIO.java

@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 Bilibili
+ * Copyright (C) 2016 Raymond Zheng <raymondzheng1412@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player.misc;
+
+import java.io.IOException;
+
+@SuppressWarnings("RedundantThrows")
+public interface IAndroidIO {
+    int  open(String url) throws IOException;
+    int  read(byte[] buffer, int size) throws IOException;
+    long seek(long offset, int whence) throws IOException;
+    int  close() throws IOException;
+}

+ 29 - 0
android/src/main/java/tv/danmaku/ijk/media/player/misc/IMediaDataSource.java

@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2015 Bilibili
+ * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player.misc;
+
+import java.io.IOException;
+
+@SuppressWarnings("RedundantThrows")
+public interface IMediaDataSource {
+    int	 readAt(long position, byte[] buffer, int offset, int size) throws IOException;
+
+    long getSize() throws IOException;
+
+    void close() throws IOException;
+}

+ 31 - 0
android/src/main/java/tv/danmaku/ijk/media/player/misc/IMediaFormat.java

@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 Bilibili
+ * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player.misc;
+
+public interface IMediaFormat {
+    // Common keys
+    String KEY_MIME = "mime";
+
+    // Video Keys
+    String KEY_WIDTH = "width";
+    String KEY_HEIGHT = "height";
+
+    String getString(String name);
+
+    int getInteger(String name);
+}

+ 35 - 0
android/src/main/java/tv/danmaku/ijk/media/player/misc/ITrackInfo.java

@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2015 Bilibili
+ * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player.misc;
+
+public interface ITrackInfo {
+    int MEDIA_TRACK_TYPE_AUDIO = 2;
+    int MEDIA_TRACK_TYPE_METADATA = 5;
+    int MEDIA_TRACK_TYPE_SUBTITLE = 4;
+    int MEDIA_TRACK_TYPE_TIMEDTEXT = 3;
+    int MEDIA_TRACK_TYPE_UNKNOWN = 0;
+    int MEDIA_TRACK_TYPE_VIDEO = 1;
+
+    IMediaFormat getFormat();
+
+    String getLanguage();
+
+    int getTrackType();
+
+    String getInfoInline();
+}

+ 259 - 0
android/src/main/java/tv/danmaku/ijk/media/player/misc/IjkMediaFormat.java

@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2015 Bilibili
+ * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player.misc;
+
+import android.annotation.TargetApi;
+import android.os.Build;
+import android.text.TextUtils;
+
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import tv.danmaku.ijk.media.player.IjkMediaMeta;
+
+public class IjkMediaFormat implements IMediaFormat {
+    // Common
+    public static final String KEY_IJK_CODEC_LONG_NAME_UI = "ijk-codec-long-name-ui";
+    public static final String KEY_IJK_CODEC_NAME_UI = "ijk-codec-name-ui";
+    public static final String KEY_IJK_BIT_RATE_UI = "ijk-bit-rate-ui";
+
+    // Video
+    public static final String KEY_IJK_CODEC_PROFILE_LEVEL_UI = "ijk-profile-level-ui";
+    public static final String KEY_IJK_CODEC_PIXEL_FORMAT_UI = "ijk-pixel-format-ui";
+    public static final String KEY_IJK_RESOLUTION_UI = "ijk-resolution-ui";
+    public static final String KEY_IJK_FRAME_RATE_UI = "ijk-frame-rate-ui";
+
+    // Audio
+    public static final String KEY_IJK_SAMPLE_RATE_UI = "ijk-sample-rate-ui";
+    public static final String KEY_IJK_CHANNEL_UI = "ijk-channel-ui";
+
+    // Codec
+    public static final String CODEC_NAME_H264 = "h264";
+
+    public final IjkMediaMeta.IjkStreamMeta mMediaFormat;
+
+    public IjkMediaFormat(IjkMediaMeta.IjkStreamMeta streamMeta) {
+        mMediaFormat = streamMeta;
+    }
+
+    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+    @Override
+    public int getInteger(String name) {
+        if (mMediaFormat == null)
+            return 0;
+
+        return mMediaFormat.getInt(name);
+    }
+
+    @Override
+    public String getString(String name) {
+        if (mMediaFormat == null)
+            return null;
+
+        if (sFormatterMap.containsKey(name)) {
+            Formatter formatter = sFormatterMap.get(name);
+            return formatter.format(this);
+        }
+
+        return mMediaFormat.getString(name);
+    }
+
+    //-------------------------
+    // Formatter
+    //-------------------------
+
+    private static abstract class Formatter {
+        public String format(IjkMediaFormat mediaFormat) {
+            String value = doFormat(mediaFormat);
+            if (TextUtils.isEmpty(value))
+                return getDefaultString();
+            return value;
+        }
+
+        protected abstract String doFormat(IjkMediaFormat mediaFormat);
+
+        @SuppressWarnings("SameReturnValue")
+        protected String getDefaultString() {
+            return "N/A";
+        }
+    }
+
+    private static final Map<String, Formatter> sFormatterMap = new HashMap<String, Formatter>();
+
+    {
+        sFormatterMap.put(KEY_IJK_CODEC_LONG_NAME_UI, new Formatter() {
+            @Override
+            public String doFormat(IjkMediaFormat mediaFormat) {
+                return mMediaFormat.getString(IjkMediaMeta.IJKM_KEY_CODEC_LONG_NAME);
+            }
+        });
+        sFormatterMap.put(KEY_IJK_CODEC_NAME_UI, new Formatter() {
+            @Override
+            public String doFormat(IjkMediaFormat mediaFormat) {
+                return mMediaFormat.getString(IjkMediaMeta.IJKM_KEY_CODEC_NAME);
+            }
+        });
+        sFormatterMap.put(KEY_IJK_BIT_RATE_UI, new Formatter() {
+            @Override
+            protected String doFormat(IjkMediaFormat mediaFormat) {
+                int bitRate = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_BITRATE);
+                if (bitRate <= 0) {
+                    return null;
+                } else if (bitRate < 1000) {
+                    return String.format(Locale.US, "%d bit/s", bitRate);
+                } else {
+                    return String.format(Locale.US, "%d kb/s", bitRate / 1000);
+                }
+            }
+        });
+        sFormatterMap.put(KEY_IJK_CODEC_PROFILE_LEVEL_UI, new Formatter() {
+            @Override
+            protected String doFormat(IjkMediaFormat mediaFormat) {
+                int profileIndex = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_CODEC_PROFILE_ID);
+                String profile;
+                switch (profileIndex) {
+                    case IjkMediaMeta.FF_PROFILE_H264_BASELINE:
+                        profile = "Baseline";
+                        break;
+                    case IjkMediaMeta.FF_PROFILE_H264_CONSTRAINED_BASELINE:
+                        profile = "Constrained Baseline";
+                        break;
+                    case IjkMediaMeta.FF_PROFILE_H264_MAIN:
+                        profile = "Main";
+                        break;
+                    case IjkMediaMeta.FF_PROFILE_H264_EXTENDED:
+                        profile = "Extended";
+                        break;
+                    case IjkMediaMeta.FF_PROFILE_H264_HIGH:
+                        profile = "High";
+                        break;
+                    case IjkMediaMeta.FF_PROFILE_H264_HIGH_10:
+                        profile = "High 10";
+                        break;
+                    case IjkMediaMeta.FF_PROFILE_H264_HIGH_10_INTRA:
+                        profile = "High 10 Intra";
+                        break;
+                    case IjkMediaMeta.FF_PROFILE_H264_HIGH_422:
+                        profile = "High 4:2:2";
+                        break;
+                    case IjkMediaMeta.FF_PROFILE_H264_HIGH_422_INTRA:
+                        profile = "High 4:2:2 Intra";
+                        break;
+                    case IjkMediaMeta.FF_PROFILE_H264_HIGH_444:
+                        profile = "High 4:4:4";
+                        break;
+                    case IjkMediaMeta.FF_PROFILE_H264_HIGH_444_PREDICTIVE:
+                        profile = "High 4:4:4 Predictive";
+                        break;
+                    case IjkMediaMeta.FF_PROFILE_H264_HIGH_444_INTRA:
+                        profile = "High 4:4:4 Intra";
+                        break;
+                    case IjkMediaMeta.FF_PROFILE_H264_CAVLC_444:
+                        profile = "CAVLC 4:4:4";
+                        break;
+                    default:
+                        return null;
+                }
+
+                StringBuilder sb = new StringBuilder();
+                sb.append(profile);
+
+                String codecName = mediaFormat.getString(IjkMediaMeta.IJKM_KEY_CODEC_NAME);
+                if (!TextUtils.isEmpty(codecName) && codecName.equalsIgnoreCase(CODEC_NAME_H264)) {
+                    int level = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_CODEC_LEVEL);
+                    if (level < 10)
+                        return sb.toString();
+
+                    sb.append(" Profile Level ");
+                    sb.append((level / 10) % 10);
+                    if ((level % 10) != 0) {
+                        sb.append(".");
+                        sb.append(level % 10);
+                    }
+                }
+
+                return sb.toString();
+            }
+        });
+        sFormatterMap.put(KEY_IJK_CODEC_PIXEL_FORMAT_UI, new Formatter() {
+            @Override
+            protected String doFormat(IjkMediaFormat mediaFormat) {
+                return mediaFormat.getString(IjkMediaMeta.IJKM_KEY_CODEC_PIXEL_FORMAT);
+            }
+        });
+        sFormatterMap.put(KEY_IJK_RESOLUTION_UI, new Formatter() {
+            @Override
+            protected String doFormat(IjkMediaFormat mediaFormat) {
+                int width = mediaFormat.getInteger(KEY_WIDTH);
+                int height = mediaFormat.getInteger(KEY_HEIGHT);
+                int sarNum = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_SAR_NUM);
+                int sarDen = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_SAR_DEN);
+
+                if (width <= 0 || height <= 0) {
+                    return null;
+                } else if (sarNum <= 0 || sarDen <= 0) {
+                    return String.format(Locale.US, "%d x %d", width, height);
+                } else {
+                    return String.format(Locale.US, "%d x %d [SAR %d:%d]", width,
+                            height, sarNum, sarDen);
+                }
+            }
+        });
+        sFormatterMap.put(KEY_IJK_FRAME_RATE_UI, new Formatter() {
+            @Override
+            protected String doFormat(IjkMediaFormat mediaFormat) {
+                int fpsNum = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_FPS_NUM);
+                int fpsDen = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_FPS_DEN);
+                if (fpsNum <= 0 || fpsDen <= 0) {
+                    return null;
+                } else {
+                    return String.valueOf(((float) (fpsNum)) / fpsDen);
+                }
+            }
+        });
+        sFormatterMap.put(KEY_IJK_SAMPLE_RATE_UI, new Formatter() {
+            @Override
+            protected String doFormat(IjkMediaFormat mediaFormat) {
+                int sampleRate = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_SAMPLE_RATE);
+                if (sampleRate <= 0) {
+                    return null;
+                } else {
+                    return String.format(Locale.US, "%d Hz", sampleRate);
+                }
+            }
+        });
+        sFormatterMap.put(KEY_IJK_CHANNEL_UI, new Formatter() {
+            @Override
+            protected String doFormat(IjkMediaFormat mediaFormat) {
+                int channelLayout = mediaFormat.getInteger(IjkMediaMeta.IJKM_KEY_CHANNEL_LAYOUT);
+                if (channelLayout <= 0) {
+                    return null;
+                } else {
+                    if (channelLayout == IjkMediaMeta.AV_CH_LAYOUT_MONO) {
+                        return "mono";
+                    } else if (channelLayout == IjkMediaMeta.AV_CH_LAYOUT_STEREO) {
+                        return "stereo";
+                    } else {
+                        return String.format(Locale.US, "%x", channelLayout);
+                    }
+                }
+            }
+        });
+    }
+}

+ 99 - 0
android/src/main/java/tv/danmaku/ijk/media/player/misc/IjkTrackInfo.java

@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2015 Bilibili
+ * Copyright (C) 2015 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player.misc;
+
+import android.text.TextUtils;
+
+import tv.danmaku.ijk.media.player.IjkMediaMeta;
+
+public class IjkTrackInfo implements ITrackInfo {
+    private int mTrackType = MEDIA_TRACK_TYPE_UNKNOWN;
+    private IjkMediaMeta.IjkStreamMeta mStreamMeta;
+
+    public IjkTrackInfo(IjkMediaMeta.IjkStreamMeta streamMeta) {
+        mStreamMeta = streamMeta;
+    }
+
+    public void setMediaMeta(IjkMediaMeta.IjkStreamMeta streamMeta) {
+        mStreamMeta = streamMeta;
+    }
+
+    @Override
+    public IMediaFormat getFormat() {
+        return new IjkMediaFormat(mStreamMeta);
+    }
+
+    @Override
+    public String getLanguage() {
+        if (mStreamMeta == null || TextUtils.isEmpty(mStreamMeta.mLanguage))
+            return "und";
+
+        return mStreamMeta.mLanguage;
+    }
+
+    @Override
+    public int getTrackType() {
+        return mTrackType;
+    }
+
+    public void setTrackType(int trackType) {
+        mTrackType = trackType;
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + '{' + getInfoInline() + "}";
+    }
+
+    @Override
+    public String getInfoInline() {
+        StringBuilder out = new StringBuilder(128);
+        switch (mTrackType) {
+            case MEDIA_TRACK_TYPE_VIDEO:
+                out.append("VIDEO");
+                out.append(", ");
+                out.append(mStreamMeta.getCodecShortNameInline());
+                out.append(", ");
+                out.append(mStreamMeta.getBitrateInline());
+                out.append(", ");
+                out.append(mStreamMeta.getResolutionInline());
+                break;
+            case MEDIA_TRACK_TYPE_AUDIO:
+                out.append("AUDIO");
+                out.append(", ");
+                out.append(mStreamMeta.getCodecShortNameInline());
+                out.append(", ");
+                out.append(mStreamMeta.getBitrateInline());
+                out.append(", ");
+                out.append(mStreamMeta.getSampleRateInline());
+                break;
+            case MEDIA_TRACK_TYPE_TIMEDTEXT:
+                out.append("TIMEDTEXT");
+                out.append(", ");
+                out.append(mStreamMeta.mLanguage);
+                break;
+            case MEDIA_TRACK_TYPE_SUBTITLE:
+                out.append("SUBTITLE");
+                break;
+            default:
+                out.append("UNKNOWN");
+                break;
+        }
+        return out.toString();
+    }
+}

+ 143 - 0
android/src/main/java/tv/danmaku/ijk/media/player/pragma/DebugLog.java

@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2013 Bilibili
+ * Copyright (C) 2013 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package tv.danmaku.ijk.media.player.pragma;
+
+import java.util.Locale;
+
+
+import android.util.Log;
+
+@SuppressWarnings({"SameParameterValue", "WeakerAccess"})
+public class DebugLog {
+    public static final boolean ENABLE_ERROR = Pragma.ENABLE_VERBOSE;
+    public static final boolean ENABLE_INFO = Pragma.ENABLE_VERBOSE;
+    public static final boolean ENABLE_WARN = Pragma.ENABLE_VERBOSE;
+    public static final boolean ENABLE_DEBUG = Pragma.ENABLE_VERBOSE;
+    public static final boolean ENABLE_VERBOSE = Pragma.ENABLE_VERBOSE;
+
+    public static void e(String tag, String msg) {
+        if (ENABLE_ERROR) {
+            Log.e(tag, msg);
+        }
+    }
+
+    public static void e(String tag, String msg, Throwable tr) {
+        if (ENABLE_ERROR) {
+            Log.e(tag, msg, tr);
+        }
+    }
+
+    public static void efmt(String tag, String fmt, Object... args) {
+        if (ENABLE_ERROR) {
+            String msg = String.format(Locale.US, fmt, args);
+            Log.e(tag, msg);
+        }
+    }
+
+    public static void i(String tag, String msg) {
+        if (ENABLE_INFO) {
+            Log.i(tag, msg);
+        }
+    }
+
+    public static void i(String tag, String msg, Throwable tr) {
+        if (ENABLE_INFO) {
+            Log.i(tag, msg, tr);
+        }
+    }
+
+    public static void ifmt(String tag, String fmt, Object... args) {
+        if (ENABLE_INFO) {
+            String msg = String.format(Locale.US, fmt, args);
+            Log.i(tag, msg);
+        }
+    }
+
+    public static void w(String tag, String msg) {
+        if (ENABLE_WARN) {
+            Log.w(tag, msg);
+        }
+    }
+
+    public static void w(String tag, String msg, Throwable tr) {
+        if (ENABLE_WARN) {
+            Log.w(tag, msg, tr);
+        }
+    }
+
+    public static void wfmt(String tag, String fmt, Object... args) {
+        if (ENABLE_WARN) {
+            String msg = String.format(Locale.US, fmt, args);
+            Log.w(tag, msg);
+        }
+    }
+
+    public static void d(String tag, String msg) {
+        if (ENABLE_DEBUG) {
+            Log.d(tag, msg);
+        }
+    }
+
+    public static void d(String tag, String msg, Throwable tr) {
+        if (ENABLE_DEBUG) {
+            Log.d(tag, msg, tr);
+        }
+    }
+
+    public static void dfmt(String tag, String fmt, Object... args) {
+        if (ENABLE_DEBUG) {
+            String msg = String.format(Locale.US, fmt, args);
+            Log.d(tag, msg);
+        }
+    }
+
+    public static void v(String tag, String msg) {
+        if (ENABLE_VERBOSE) {
+            Log.v(tag, msg);
+        }
+    }
+
+    public static void v(String tag, String msg, Throwable tr) {
+        if (ENABLE_VERBOSE) {
+            Log.v(tag, msg, tr);
+        }
+    }
+
+    public static void vfmt(String tag, String fmt, Object... args) {
+        if (ENABLE_VERBOSE) {
+            String msg = String.format(Locale.US, fmt, args);
+            Log.v(tag, msg);
+        }
+    }
+
+    public static void printStackTrace(Throwable e) {
+        if (ENABLE_WARN) {
+            e.printStackTrace();
+        }
+    }
+
+    public static void printCause(Throwable e) {
+        if (ENABLE_WARN) {
+            Throwable cause = e.getCause();
+            if (cause != null)
+                e = cause;
+
+            printStackTrace(e);
+        }
+    }
+}

+ 24 - 0
android/src/main/java/tv/danmaku/ijk/media/player/pragma/Pragma.java

@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2013 Bilibili
+ * Copyright (C) 2013 Zhang Rui <bbcallen@gmail.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package tv.danmaku.ijk.media.player.pragma;
+
+/*-
+ * configurated by app project
+ */
+public class Pragma {
+    public static final boolean ENABLE_VERBOSE = true;
+}

TEMPAT SAMPAH
android/src/main/libs/arm64-v8a/libijkffmpeg.so


TEMPAT SAMPAH
android/src/main/libs/arm64-v8a/libijkplayer.so


TEMPAT SAMPAH
android/src/main/libs/arm64-v8a/libijksdl.so


TEMPAT SAMPAH
android/src/main/libs/armeabi-v7a/libijkffmpeg.so


TEMPAT SAMPAH
android/src/main/libs/armeabi-v7a/libijkplayer.so


TEMPAT SAMPAH
android/src/main/libs/armeabi-v7a/libijksdl.so


TEMPAT SAMPAH
android/src/main/libs/x86/libijkffmpeg.so


TEMPAT SAMPAH
android/src/main/libs/x86/libijkplayer.so


TEMPAT SAMPAH
android/src/main/libs/x86/libijksdl.so


TEMPAT SAMPAH
android/src/main/libs/x86_64/libijkffmpeg.so


TEMPAT SAMPAH
android/src/main/libs/x86_64/libijkplayer.so


TEMPAT SAMPAH
android/src/main/libs/x86_64/libijksdl.so