Browse Source

Merge pull request #15 from CaiJingLong/dev

Add screenshot method
Caijinglong 6 năm trước cách đây
mục cha
commit
40d5eb533e
62 tập tin đã thay đổi với 4541 bổ sung26 xóa
  1. 4 0
      CHANGELOG.md
  2. 12 0
      README-EN.md
  3. 12 0
      README.md
  4. 1 0
      TODOLIST.md
  5. 0 3
      android/build.gradle
  6. 18 1
      android/src/main/java/top/kikt/ijkplayer/Ijk.kt
  7. 121 0
      android/src/main/java/tv/danmaku/ijk/media/player/AbstractMediaPlayer.java
  8. 437 0
      android/src/main/java/tv/danmaku/ijk/media/player/AndroidMediaPlayer.java
  9. 215 0
      android/src/main/java/tv/danmaku/ijk/media/player/IMediaPlayer.java
  10. 28 0
      android/src/main/java/tv/danmaku/ijk/media/player/ISurfaceTextureHolder.java
  11. 24 0
      android/src/main/java/tv/danmaku/ijk/media/player/ISurfaceTextureHost.java
  12. 23 0
      android/src/main/java/tv/danmaku/ijk/media/player/IjkLibLoader.java
  13. 293 0
      android/src/main/java/tv/danmaku/ijk/media/player/IjkMediaCodecInfo.java
  14. 401 0
      android/src/main/java/tv/danmaku/ijk/media/player/IjkMediaMeta.java
  15. 1293 0
      android/src/main/java/tv/danmaku/ijk/media/player/IjkMediaPlayer.java
  16. 39 0
      android/src/main/java/tv/danmaku/ijk/media/player/IjkTimedText.java
  17. 30 0
      android/src/main/java/tv/danmaku/ijk/media/player/MediaInfo.java
  18. 339 0
      android/src/main/java/tv/danmaku/ijk/media/player/MediaPlayerProxy.java
  19. 100 0
      android/src/main/java/tv/danmaku/ijk/media/player/TextureMediaPlayer.java
  20. 32 0
      android/src/main/java/tv/danmaku/ijk/media/player/annotations/AccessedByNative.java
  21. 36 0
      android/src/main/java/tv/danmaku/ijk/media/player/annotations/CalledByNative.java
  22. 22 0
      android/src/main/java/tv/danmaku/ijk/media/player/exceptions/IjkMediaException.java
  23. 5 0
      android/src/main/java/tv/danmaku/ijk/media/player/ffmpeg/FFmpegApi.java
  24. 63 0
      android/src/main/java/tv/danmaku/ijk/media/player/misc/AndroidMediaFormat.java
  25. 109 0
      android/src/main/java/tv/danmaku/ijk/media/player/misc/AndroidTrackInfo.java
  26. 28 0
      android/src/main/java/tv/danmaku/ijk/media/player/misc/IAndroidIO.java
  27. 29 0
      android/src/main/java/tv/danmaku/ijk/media/player/misc/IMediaDataSource.java
  28. 31 0
      android/src/main/java/tv/danmaku/ijk/media/player/misc/IMediaFormat.java
  29. 35 0
      android/src/main/java/tv/danmaku/ijk/media/player/misc/ITrackInfo.java
  30. 259 0
      android/src/main/java/tv/danmaku/ijk/media/player/misc/IjkMediaFormat.java
  31. 99 0
      android/src/main/java/tv/danmaku/ijk/media/player/misc/IjkTrackInfo.java
  32. 143 0
      android/src/main/java/tv/danmaku/ijk/media/player/pragma/DebugLog.java
  33. 24 0
      android/src/main/java/tv/danmaku/ijk/media/player/pragma/Pragma.java
  34. BIN
      android/src/main/libs/arm64-v8a/libijkffmpeg.so
  35. BIN
      android/src/main/libs/arm64-v8a/libijkplayer.so
  36. BIN
      android/src/main/libs/arm64-v8a/libijksdl.so
  37. BIN
      android/src/main/libs/armeabi-v7a/libijkffmpeg.so
  38. BIN
      android/src/main/libs/armeabi-v7a/libijkplayer.so
  39. BIN
      android/src/main/libs/armeabi-v7a/libijksdl.so
  40. BIN
      android/src/main/libs/x86/libijkffmpeg.so
  41. BIN
      android/src/main/libs/x86/libijkplayer.so
  42. BIN
      android/src/main/libs/x86/libijksdl.so
  43. BIN
      android/src/main/libs/x86_64/libijkffmpeg.so
  44. BIN
      android/src/main/libs/x86_64/libijkplayer.so
  45. BIN
      android/src/main/libs/x86_64/libijksdl.so
  46. 4 4
      example/ios/Podfile.lock
  47. 3 0
      example/lib/i18n/cn.dart
  48. 3 0
      example/lib/i18n/en.dart
  49. 3 1
      example/lib/i18n/i18n.dart
  50. 2 3
      example/lib/page/controller_stream_use.dart
  51. 2 0
      example/lib/page/index.dart
  52. 19 0
      example/lib/page/paging_page.dart
  53. 77 0
      example/lib/page/screen_shot_page.dart
  54. 1 1
      example/pubspec.lock
  55. 32 1
      ios/Classes/CoolFlutterIJK.m
  56. 1 1
      ios/Classes/IjkplayerPlugin.m
  57. 1 1
      lib/flutter_ijkplayer.dart
  58. 18 1
      lib/src/controller.dart
  59. 53 4
      lib/src/ijkplayer.dart
  60. 15 0
      lib/src/ijkplayer_controller_mixin.dart
  61. 1 4
      lib/src/widget/ijkplayer_builder.dart
  62. 1 1
      pubspec.yaml

+ 4 - 0
CHANGELOG.md

@@ -1,3 +1,7 @@
+## 0.1.9
+
+add screen shot method.
+
 ## 0.1.8
 ## 0.1.8
 
 
 Screen rotation and full screen control code.
 Screen rotation and full screen control code.

+ 12 - 0
README-EN.md

@@ -196,6 +196,18 @@ await controller.seekTo(0); // double value , such as : 1.1 = 1s100ms, 60 = 1min
   VideoInfo info = await controller.getVideoInfo();
   VideoInfo info = await controller.getVideoInfo();
 ```
 ```
 
 
+#### screen shot
+
+Intercept the current video frame
+This video frame comes from the video frame currently decoded by ffmpeg and does not contain the contents of the controller, etc.
+The format in dart is Uint8List.
+
+```dart
+var uint8List = await controller.screenShot();
+var provider = MemoryImage(uint8List);
+Widget image = Image(image:provider);
+```
+
 #### Observer for resource
 #### Observer for resource
 
 
 Broadcasting changes in information outward in the form of streams, in principle the attributes ending with streams are monitorable.
 Broadcasting changes in information outward in the form of streams, in principle the attributes ending with streams are monitorable.

+ 12 - 0
README.md

@@ -220,6 +220,18 @@ await controller.setSystemVolume(100); // 范围0~100
   VideoInfo info = await controller.getVideoInfo();
   VideoInfo info = await controller.getVideoInfo();
 ```
 ```
 
 
+#### 截取视频帧
+
+视频帧的截图
+
+以`Uint8List`的格式导出,可以使用`Image`控件查看
+
+```dart
+var uint8List = await controller.screenShot();
+var provider = MemoryImage(uint8List);
+Widget image = Image(image:provider);
+```
+
 #### 资源监听
 #### 资源监听
 
 
 使用 stream 的形式向外广播一些信息的变化,原则上以 stream 结尾的属性都是可监听的
 使用 stream 的形式向外广播一些信息的变化,原则上以 stream 结尾的属性都是可监听的

+ 1 - 0
TODOLIST.md

@@ -36,6 +36,7 @@
     - [ ] iPad 无效,暂不知原因
     - [ ] iPad 无效,暂不知原因
   - [x] 屏幕旋转: 这个指强制当前屏幕旋转至哪个方向
   - [x] 屏幕旋转: 这个指强制当前屏幕旋转至哪个方向
     - [ ] iPad 无效,暂不知原因
     - [ ] iPad 无效,暂不知原因
+  - [x] 截图
 - [x] 默认控制器 UI
 - [x] 默认控制器 UI
   - [x] 进度条
   - [x] 进度条
   - [x] 播放/暂停按钮
   - [x] 播放/暂停按钮

+ 0 - 3
android/build.gradle

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

+ 18 - 1
android/src/main/java/top/kikt/ijkplayer/Ijk.kt

@@ -3,6 +3,7 @@ package top.kikt.ijkplayer
 /// create 2019/3/7 by cai
 /// create 2019/3/7 by cai
 
 
 
 
+import android.graphics.Bitmap
 import android.util.Base64
 import android.util.Base64
 import io.flutter.plugin.common.MethodCall
 import io.flutter.plugin.common.MethodCall
 import io.flutter.plugin.common.MethodChannel
 import io.flutter.plugin.common.MethodChannel
@@ -10,6 +11,7 @@ import io.flutter.plugin.common.PluginRegistry
 import top.kikt.ijkplayer.entity.Info
 import top.kikt.ijkplayer.entity.Info
 import tv.danmaku.ijk.media.player.IjkMediaPlayer
 import tv.danmaku.ijk.media.player.IjkMediaPlayer
 import tv.danmaku.ijk.media.player.TextureMediaPlayer
 import tv.danmaku.ijk.media.player.TextureMediaPlayer
+import java.io.ByteArrayOutputStream
 import java.io.File
 import java.io.File
 
 
 class Ijk(private val registry: PluginRegistry.Registrar) : MethodChannel.MethodCallHandler {
 class Ijk(private val registry: PluginRegistry.Registrar) : MethodChannel.MethodCallHandler {
@@ -111,15 +113,30 @@ class Ijk(private val registry: PluginRegistry.Registrar) : MethodChannel.Method
                 result?.success(true)
                 result?.success(true)
             }
             }
             "getVolume" -> {
             "getVolume" -> {
-
 //                result?.success(this.mediaPlayer.setVolume())
 //                result?.success(this.mediaPlayer.setVolume())
             }
             }
+            "screenShot" -> {
+                val bytes = screenShot()
+                result?.success(bytes)
+            }
             else -> {
             else -> {
                 result?.notImplemented()
                 result?.notImplemented()
             }
             }
         }
         }
     }
     }
 
 
+    private fun screenShot(): ByteArray? {
+        val frameBitmap = mediaPlayer.frameBitmap
+        return if (frameBitmap != null) {
+            val outputStream = ByteArrayOutputStream()
+            frameBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
+            frameBitmap.recycle()
+            outputStream.toByteArray()
+        } else {
+            null
+        }
+    }
+
     fun getInfo(): Info {
     fun getInfo(): Info {
         val duration = mediaPlayer.duration
         val duration = mediaPlayer.duration
         val currentPosition = mediaPlayer.currentPosition
         val currentPosition = mediaPlayer.currentPosition

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

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

@@ -0,0 +1,1293 @@
+/*
+ * 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 org.jetbrains.annotations.Nullable;
+
+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;
+
+    @Nullable
+    public native Bitmap 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;
+}

BIN
android/src/main/libs/arm64-v8a/libijkffmpeg.so


BIN
android/src/main/libs/arm64-v8a/libijkplayer.so


BIN
android/src/main/libs/arm64-v8a/libijksdl.so


BIN
android/src/main/libs/armeabi-v7a/libijkffmpeg.so


BIN
android/src/main/libs/armeabi-v7a/libijkplayer.so


BIN
android/src/main/libs/armeabi-v7a/libijksdl.so


BIN
android/src/main/libs/x86/libijkffmpeg.so


BIN
android/src/main/libs/x86/libijkplayer.so


BIN
android/src/main/libs/x86/libijksdl.so


BIN
android/src/main/libs/x86_64/libijkffmpeg.so


BIN
android/src/main/libs/x86_64/libijkplayer.so


BIN
android/src/main/libs/x86_64/libijksdl.so


+ 4 - 4
example/ios/Podfile.lock

@@ -9,8 +9,8 @@ PODS:
 
 
 DEPENDENCIES:
 DEPENDENCIES:
   - Flutter (from `.symlinks/flutter/ios`)
   - Flutter (from `.symlinks/flutter/ios`)
-  - flutter_ijkplayer (from `/Users/cai/Documents/GitHub/flutter_ijkplayer/ios/flutter_ijkplayer.podspec`)
-  - photo_manager (from `/Users/cai/.pub-cache/hosted/pub.flutter-io.cn/photo_manager-0.3.3/ios/photo_manager.podspec`)
+  - flutter_ijkplayer (from `/Users/caijinglong/Documents/GitHub/flutter_ijkplayer/ios/flutter_ijkplayer.podspec`)
+  - photo_manager (from `/Users/caijinglong/.pub-cache/hosted/pub.flutter-io.cn/photo_manager-0.3.3/ios/photo_manager.podspec`)
 
 
 SPEC REPOS:
 SPEC REPOS:
   https://github.com/cocoapods/specs.git:
   https://github.com/cocoapods/specs.git:
@@ -20,9 +20,9 @@ EXTERNAL SOURCES:
   Flutter:
   Flutter:
     :path: ".symlinks/flutter/ios"
     :path: ".symlinks/flutter/ios"
   flutter_ijkplayer:
   flutter_ijkplayer:
-    :path: "/Users/cai/Documents/GitHub/flutter_ijkplayer/ios/flutter_ijkplayer.podspec"
+    :path: "/Users/caijinglong/Documents/GitHub/flutter_ijkplayer/ios/flutter_ijkplayer.podspec"
   photo_manager:
   photo_manager:
-    :path: "/Users/cai/.pub-cache/hosted/pub.flutter-io.cn/photo_manager-0.3.3/ios/photo_manager.podspec"
+    :path: "/Users/caijinglong/.pub-cache/hosted/pub.flutter-io.cn/photo_manager-0.3.3/ios/photo_manager.podspec"
 
 
 SPEC CHECKSUMS:
 SPEC CHECKSUMS:
   Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a
   Flutter: 58dd7d1b27887414a370fcccb9e645c08ffd7a6a

+ 3 - 0
example/lib/i18n/cn.dart

@@ -56,4 +56,7 @@ class _I18nZh extends I18n {
 
 
   @override
   @override
   String get playFinishToast => "播放完毕";
   String get playFinishToast => "播放完毕";
+
+  @override
+  String get screenshotTitle => "截取视频画面(开发中)";
 }
 }

+ 3 - 0
example/lib/i18n/en.dart

@@ -55,4 +55,7 @@ class _I18nEn extends I18n {
 
 
   @override
   @override
   String get playFinishToast => "Play video finish";
   String get playFinishToast => "Play video finish";
+
+  @override
+  String get screenshotTitle => "screenshot video frame(experiment)";
 }
 }

+ 3 - 1
example/lib/i18n/i18n.dart

@@ -6,7 +6,7 @@ abstract class I18n {
   I18n._();
   I18n._();
 
 
   factory I18n(Locale locale) {
   factory I18n(Locale locale) {
-    if (locale.languageCode == "zh") {
+    if (locale?.languageCode == "zh") {
       return _I18nZh();
       return _I18nZh();
     } else {
     } else {
       return _I18nEn();
       return _I18nEn();
@@ -40,6 +40,8 @@ abstract class I18n {
   String get useStreamUsage;
   String get useStreamUsage;
 
 
   String get playFinishToast;
   String get playFinishToast;
+
+  String get screenshotTitle;
 }
 }
 
 
 I18n get currentI18n => I18n(window.locale);
 I18n get currentI18n => I18n(window.locale);

+ 2 - 3
example/lib/page/controller_stream_use.dart

@@ -60,9 +60,8 @@ class _ControllerStreamUsagePageState extends State<ControllerStreamUsagePage> {
                 ),
                 ),
                 Expanded(
                 Expanded(
                   child: Container(
                   child: Container(
-                    decoration: BoxDecoration(
-                      border: Border(left: _borderSlider)
-                    ),
+                    decoration:
+                        BoxDecoration(border: Border(left: _borderSlider)),
                     child: buildVideoInfo(),
                     child: buildVideoInfo(),
                   ),
                   ),
                 ),
                 ),

+ 2 - 0
example/lib/page/index.dart

@@ -7,6 +7,7 @@ import 'package:ijkplayer_example/page/full_screen.dart';
 import 'package:ijkplayer_example/page/gallery_page.dart';
 import 'package:ijkplayer_example/page/gallery_page.dart';
 import 'package:ijkplayer_example/page/network.dart';
 import 'package:ijkplayer_example/page/network.dart';
 import 'package:ijkplayer_example/page/paging_page.dart';
 import 'package:ijkplayer_example/page/paging_page.dart';
+import 'package:ijkplayer_example/page/screen_shot_page.dart';
 import 'package:ijkplayer_example/page/video_list.dart';
 import 'package:ijkplayer_example/page/video_list.dart';
 
 
 class IndexPage extends StatefulWidget {
 class IndexPage extends StatefulWidget {
@@ -32,6 +33,7 @@ class _IndexPageState extends State<IndexPage> {
           buildButton(currentI18n.withDialogButton, DialogVideoPage()),
           buildButton(currentI18n.withDialogButton, DialogVideoPage()),
           buildButton(currentI18n.pageViewButton, PagingPickPage()),
           buildButton(currentI18n.pageViewButton, PagingPickPage()),
           buildButton(currentI18n.useStreamUsage, ControllerStreamUsagePage()),
           buildButton(currentI18n.useStreamUsage, ControllerStreamUsagePage()),
+          buildButton(currentI18n.screenshotTitle, ScreenShotPage()),
         ],
         ],
       ),
       ),
     );
     );

+ 19 - 0
example/lib/page/paging_page.dart

@@ -131,6 +131,25 @@ class _PagingPageState extends State<PagingPage> {
     );
     );
   }
   }
 
 
+  // Hold up to three controllers.
+  // IjkMediaController initControllers(int current) {
+  //   var src = widget.dataSourceList[current];
+  //   var ctl = getControllerWithSrc(src);
+
+  //   var next = current + 1;
+  //   if (next < widget.dataSourceList.length) {
+  //     var datasource = widget.dataSourceList[next];
+  //     var nextCtl = getControllerWithSrc(datasource);
+  //   }
+
+  //   return ctl;
+  // }
+
+  // void disposeOther(int current) {
+  //   var last = current - 1;
+  //   var next = current + 1;
+  // }
+
   Widget _buildItem(BuildContext context, int index) {
   Widget _buildItem(BuildContext context, int index) {
     var src = widget.dataSourceList[index];
     var src = widget.dataSourceList[index];
     var ctl = getControllerWithSrc(src);
     var ctl = getControllerWithSrc(src);

+ 77 - 0
example/lib/page/screen_shot_page.dart

@@ -0,0 +1,77 @@
+import 'dart:typed_data';
+import 'dart:async';
+import 'package:flutter/material.dart';
+
+import 'package:flutter_ijkplayer/flutter_ijkplayer.dart';
+import 'package:ijkplayer_example/i18n/i18n.dart';
+import 'dart:ui' as ui;
+
+class ScreenShotPage extends StatefulWidget {
+  @override
+  _ScreenShotPageState createState() => _ScreenShotPageState();
+}
+
+class _ScreenShotPageState extends State<ScreenShotPage> {
+  IjkMediaController mediaController = IjkMediaController();
+
+  ImageProvider provider;
+
+  @override
+  void initState() {
+    super.initState();
+    mediaController.setDataSource(
+        DataSource.network(
+            "http://img.ksbbs.com/asset/Mon_1703/05cacb4e02f9d9e.mp4"),
+        autoPlay: true);
+  }
+
+  @override
+  void dispose() {
+    mediaController.dispose();
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(
+        title: Text(currentI18n.screenshotTitle),
+      ),
+      body: ListView(
+        children: <Widget>[
+          AspectRatio(
+            aspectRatio: 1280 / 720,
+            child: IjkPlayer(
+              mediaController: mediaController,
+            ),
+          ),
+          FlatButton(
+            child: Text(currentI18n.screenshotTitle),
+            onPressed: () async {
+              var uint8List = await mediaController.screenShot();
+              if (uint8List == null) {
+                return;
+              }
+              provider = MemoryImage(uint8List);
+              setState(() {});
+            },
+          ),
+          provider == null
+              ? Container()
+              : Image(
+                  image: provider,
+                ),
+        ],
+      ),
+    );
+  }
+}
+
+Future<Size> getImageForUint8List(Uint8List imageSrc) {
+  Completer<Size> completer = Completer();
+  ui.decodeImageFromList(imageSrc, (img) {
+    completer.complete(Size(img.width.toDouble(), img.height.toDouble()));
+    img.dispose();
+  });
+  return completer.future;
+}

+ 1 - 1
example/pubspec.lock

@@ -82,7 +82,7 @@ packages:
       path: ".."
       path: ".."
       relative: true
       relative: true
     source: path
     source: path
-    version: "0.1.8"
+    version: "0.1.9"
   flutter_localizations:
   flutter_localizations:
     dependency: "direct main"
     dependency: "direct main"
     description: flutter
     description: flutter

+ 32 - 1
ios/Classes/CoolFlutterIJK.m

@@ -32,8 +32,9 @@
         textureId = [textures registerTexture:self];
         textureId = [textures registerTexture:self];
         NSString *channelName = [NSString stringWithFormat:@"top.kikt/ijkplayer/%lli", textureId];
         NSString *channelName = [NSString stringWithFormat:@"top.kikt/ijkplayer/%lli", textureId];
         channel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:[registrar messenger]];
         channel = [FlutterMethodChannel methodChannelWithName:channelName binaryMessenger:[registrar messenger]];
+        __weak typeof(&*self) weakSelf = self;
         [channel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) {
         [channel setMethodCallHandler:^(FlutterMethodCall *call, FlutterResult result) {
-            [self handleMethodCall:call result:result];
+            [weakSelf handleMethodCall:call result:result];
         }];
         }];
     }
     }
 
 
@@ -105,6 +106,12 @@
         float v = [params[@"volume"] floatValue] / 100;
         float v = [params[@"volume"] floatValue] / 100;
         controller.playbackVolume = v;
         controller.playbackVolume = v;
         result(@(YES));
         result(@(YES));
+    } else if ([@"screenShot" isEqualToString:call.method]) {
+        __weak typeof(&*self) weakSelf = self;
+        dispatch_async(dispatch_get_main_queue(), ^{
+            NSData *data = [weakSelf screenShot];
+            result(data);
+        });
     } else {
     } else {
         result(FlutterMethodNotImplemented);
         result(FlutterMethodNotImplemented);
     }
     }
@@ -296,4 +303,28 @@
     return mDegree;
     return mDegree;
 }
 }
 
 
+- (NSData*) screenShot{
+    CVPixelBufferRef ref = [self copyPixelBuffer];
+    if(!ref){
+        return nil;
+    }
+    
+    UIImage *img = [self convertPixeclBufferToUIImage:ref];
+    return UIImageJPEGRepresentation(img, 1.0);
+}
+
+-(UIImage*)convertPixeclBufferToUIImage:(CVPixelBufferRef)pixelBuffer{
+    CIImage *ciImage = [CIImage imageWithCVPixelBuffer:pixelBuffer];
+    
+    CIContext *temporaryContext = [CIContext contextWithOptions:nil];
+    CGImageRef videoImage = [temporaryContext
+                             createCGImage:ciImage
+                             fromRect:CGRectMake(0, 0, CVPixelBufferGetWidth(pixelBuffer), CVPixelBufferGetHeight(pixelBuffer))];
+    
+    UIImage *uiImage = [UIImage imageWithCGImage:videoImage];
+    CGImageRelease(videoImage);
+    
+    return uiImage;
+}
+
 @end
 @end

+ 1 - 1
ios/Classes/IjkplayerPlugin.m

@@ -53,7 +53,7 @@ static IjkplayerPlugin *__sharedInstance;
 - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
 - (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
     
     
     dispatch_queue_t mainQueue = dispatch_get_main_queue();
     dispatch_queue_t mainQueue = dispatch_get_main_queue();
-    
+//    __weak typeof(&*self) weakSelf = self;
     dispatch_async(mainQueue, ^{
     dispatch_async(mainQueue, ^{
         if ([@"create" isEqualToString:call.method]) {
         if ([@"create" isEqualToString:call.method]) {
             @try {
             @try {

+ 1 - 1
lib/flutter_ijkplayer.dart

@@ -2,5 +2,5 @@ export 'src/error.dart';
 export 'src/ijkplayer.dart';
 export 'src/ijkplayer.dart';
 export 'package:flutter_ijkplayer/src/entity/video_info.dart';
 export 'package:flutter_ijkplayer/src/entity/video_info.dart';
 export 'src/widget/controller_widget_builder.dart'
 export 'src/widget/controller_widget_builder.dart'
-    show DefaultIJKControllerWidget,VolumeType;
+    show DefaultIJKControllerWidget, VolumeType;
 export 'package:flutter_ijkplayer/src/helper/config.dart';
 export 'package:flutter_ijkplayer/src/helper/config.dart';

+ 18 - 1
lib/src/controller.dart

@@ -1,7 +1,7 @@
 part of './ijkplayer.dart';
 part of './ijkplayer.dart';
 
 
 /// Media Controller
 /// Media Controller
-class IjkMediaController {
+class IjkMediaController with IjkMediaControllerMixin {
   /// MediaController
   /// MediaController
   IjkMediaController({
   IjkMediaController({
     this.autoRotate = true,
     this.autoRotate = true,
@@ -328,6 +328,7 @@ class IjkMediaController {
     await IjkManager.setSystemVolume(volume);
     await IjkManager.setSystemVolume(volume);
   }
   }
 
 
+  /// Pause all other players.
   Future<void> pauseOtherController() async {
   Future<void> pauseOtherController() async {
     await IjkMediaPlayerManager().pauseOther(this);
     await IjkMediaPlayerManager().pauseOther(this);
   }
   }
@@ -346,6 +347,13 @@ class IjkMediaController {
     refreshVideoInfo();
     refreshVideoInfo();
     _playFinishController?.add(this);
     _playFinishController?.add(this);
   }
   }
+
+  /// Intercept the video frame image and get the `Uint8List` format.
+  ///
+  /// Player UI is not included. If you need the effect of the player, use the screenshot of the system.
+  Future<Uint8List> screenShot() {
+    return _plugin.screenShot();
+  }
 }
 }
 
 
 /// about channel
 /// about channel
@@ -425,11 +433,20 @@ class _IjkPlugin {
     });
     });
   }
   }
 
 
+  ///
   Future<void> setVolume(int volume) async {
   Future<void> setVolume(int volume) async {
     await channel.invokeMethod("setVolume", <String, dynamic>{
     await channel.invokeMethod("setVolume", <String, dynamic>{
       "volume": volume,
       "volume": volume,
     });
     });
   }
   }
+
+  Future<Uint8List> screenShot() async {
+    var result = await channel.invokeMethod("screenShot");
+    if (result == null) {
+      return null;
+    }
+    return result;
+  }
 }
 }
 
 
 /// Entity classe for data sources.
 /// Entity classe for data sources.

+ 53 - 4
lib/src/ijkplayer.dart

@@ -1,8 +1,10 @@
 import 'dart:async';
 import 'dart:async';
 import 'dart:io';
 import 'dart:io';
+import 'dart:typed_data';
 
 
 import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter/services.dart';
+import 'package:flutter_ijkplayer/src/ijkplayer_controller_mixin.dart';
 
 
 import 'error.dart';
 import 'error.dart';
 import 'package:flutter_ijkplayer/src/helper/logutil.dart';
 import 'package:flutter_ijkplayer/src/helper/logutil.dart';
@@ -41,7 +43,7 @@ class IjkPlayer extends StatefulWidget {
 class IjkPlayerState extends State<IjkPlayer> {
 class IjkPlayerState extends State<IjkPlayer> {
   /// see [IjkMediaController]
   /// see [IjkMediaController]
   IjkMediaController controller;
   IjkMediaController controller;
-
+  GlobalKey _wrapperKey = GlobalKey();
   @override
   @override
   void initState() {
   void initState() {
     super.initState();
     super.initState();
@@ -92,7 +94,12 @@ class IjkPlayerState extends State<IjkPlayer> {
 
 
   Widget _buildTexture(int id, VideoInfo info) {
   Widget _buildTexture(int id, VideoInfo info) {
     if (widget?.textureBuilder != null) {
     if (widget?.textureBuilder != null) {
-      return widget.textureBuilder.call(context, controller, info);
+      var texture = widget.textureBuilder.call(context, controller, info);
+      return _IjkPlayerWrapper(
+        child: texture,
+        globalKey: _wrapperKey,
+        controller: controller,
+      );
     }
     }
 
 
     if (id == null) {
     if (id == null) {
@@ -103,9 +110,51 @@ class IjkPlayerState extends State<IjkPlayer> {
 
 
     return Container(
     return Container(
       color: Colors.black,
       color: Colors.black,
-      child: Texture(
-        textureId: id,
+      child: _IjkPlayerWrapper(
+        globalKey: _wrapperKey,
+        controller: controller,
+        child: Texture(
+          textureId: id,
+        ),
       ),
       ),
     );
     );
   }
   }
 }
 }
+
+class _IjkPlayerWrapper extends StatefulWidget {
+  final Widget child;
+  final GlobalKey globalKey;
+  final IjkMediaController controller;
+
+  const _IjkPlayerWrapper({
+    @required this.globalKey,
+    @required this.child,
+    Key key,
+    @required this.controller,
+  }) : super(key: key);
+
+  @override
+  __IjkPlayerWrapperState createState() => __IjkPlayerWrapperState();
+}
+
+class __IjkPlayerWrapperState extends State<_IjkPlayerWrapper> {
+  @override
+  void initState() {
+    super.initState();
+    widget.controller?.attach(widget.globalKey);
+  }
+
+  @override
+  void dispose() {
+    widget.controller?.detach(widget.globalKey);
+    super.dispose();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return RepaintBoundary(
+      child: widget.child,
+      key: widget.globalKey,
+    );
+  }
+}

+ 15 - 0
lib/src/ijkplayer_controller_mixin.dart

@@ -0,0 +1,15 @@
+import 'package:flutter/material.dart';
+
+mixin IjkMediaControllerMixin {
+  List<GlobalKey> _keys = [];
+
+  attach(GlobalKey key) {
+    print("IjkMediaControllerMixin attach $key");
+    _keys.add(key);
+  }
+
+  detach(GlobalKey key) {
+    print("IjkMediaControllerMixin detach $key");
+    _keys.remove(key);
+  }
+}

+ 1 - 4
lib/src/widget/ijkplayer_builder.dart

@@ -56,10 +56,7 @@ class DefaultIJKPlayerWrapper extends StatelessWidget {
     );
     );
 
 
     if (!controller.autoRotate) {
     if (!controller.autoRotate) {
-      return AspectRatio(
-        aspectRatio: null,
-        child: w,
-      );
+      return w;
     }
     }
 
 
     int degree = info?.degree ?? 0;
     int degree = info?.degree ?? 0;

+ 1 - 1
pubspec.yaml

@@ -1,6 +1,6 @@
 name: flutter_ijkplayer
 name: flutter_ijkplayer
 description: Flutter version of bilibilibili ijkplayer, supports common playback protocols, easy to use.
 description: Flutter version of bilibilibili ijkplayer, supports common playback protocols, easy to use.
-version: 0.1.8
+version: 0.1.9
 author: caijinglong<cjl_spy@163.com>
 author: caijinglong<cjl_spy@163.com>
 homepage: https://github.com/CaiJingLong/flutter_ijkplayer
 homepage: https://github.com/CaiJingLong/flutter_ijkplayer