Bladeren bron

1.降低targetSdkVersion为28,播放器兼容性问题

FailedToRead 2 jaren geleden
bovenliggende
commit
98d555a57a

+ 2 - 2
app/build.gradle

@@ -7,12 +7,12 @@ def releaseTime() {
 }
 
 android {
-    compileSdkVersion 31
+    compileSdkVersion 30
 
     defaultConfig {
         applicationId "com.edufound.reader"
         minSdkVersion 21 //>23导致无法直接签名
-        targetSdkVersion 31
+        targetSdkVersion 28
         versionCode 32
         versionName "3.2"
         flavorDimensions "versionCode"

+ 103 - 0
app/src/main/java/com/edufound/reader/ijkplayer/application/Settings.java

@@ -0,0 +1,103 @@
+/*
+ * 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 com.edufound.reader.ijkplayer.application;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+import com.edufound.reader.R;
+
+public class Settings {
+    private Context mAppContext;
+    private SharedPreferences mSharedPreferences;
+
+    public static final int PV_PLAYER__AndroidMediaPlayer = 1;
+    public static final int PV_PLAYER__IjkMediaPlayer = 2;
+    public static final int PV_PLAYER__IjkExoMediaPlayer = 3;
+
+    public Settings(Context context) {
+        mAppContext = context.getApplicationContext();
+        mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mAppContext);
+    }
+
+    public boolean getEnableBackgroundPlay() {
+        String key = mAppContext.getString(R.string.pref_key_enable_background_play);
+        return mSharedPreferences.getBoolean(key, false);
+    }
+
+    public int getPlayer() {
+        String key = mAppContext.getString(R.string.pref_key_player);
+        String value = mSharedPreferences.getString(key, "");
+        try {
+            return Integer.valueOf(value).intValue();
+        } catch (NumberFormatException e) {
+            return 0;
+        }
+    }
+
+    public boolean getUsingMediaCodec() {
+        String key = mAppContext.getString(R.string.pref_key_using_media_codec);
+        return mSharedPreferences.getBoolean(key, false);
+    }
+
+    public boolean getUsingMediaCodecAutoRotate() {
+        String key = mAppContext.getString(R.string.pref_key_using_media_codec_auto_rotate);
+        return mSharedPreferences.getBoolean(key, false);
+    }
+
+    public boolean getMediaCodecHandleResolutionChange() {
+        String key = mAppContext.getString(R.string.pref_key_media_codec_handle_resolution_change);
+        return mSharedPreferences.getBoolean(key, false);
+    }
+
+    public boolean getUsingOpenSLES() {
+        String key = mAppContext.getString(R.string.pref_key_using_opensl_es);
+        return mSharedPreferences.getBoolean(key, false);
+    }
+
+    public String getPixelFormat() {
+        String key = mAppContext.getString(R.string.pref_key_pixel_format);
+        return mSharedPreferences.getString(key, "");
+    }
+
+    public boolean getEnableNoView() {
+        String key = mAppContext.getString(R.string.pref_key_enable_no_view);
+        return mSharedPreferences.getBoolean(key, false);
+    }
+
+    public boolean getEnableSurfaceView() {
+        String key = mAppContext.getString(R.string.pref_key_enable_surface_view);
+        return mSharedPreferences.getBoolean(key, false);
+    }
+
+    public boolean getEnableTextureView() {
+        String key = mAppContext.getString(R.string.pref_key_enable_texture_view);
+        return mSharedPreferences.getBoolean(key, false);
+    }
+
+    public boolean getEnableDetachedSurfaceTextureView() {
+        String key = mAppContext.getString(R.string.pref_key_enable_detached_surface_texture);
+        return mSharedPreferences.getBoolean(key, false);
+    }
+
+    public boolean getUsingMediaDataSource() {
+        String key = mAppContext.getString(R.string.pref_key_using_mediadatasource);
+        return mSharedPreferences.getBoolean(key, false);
+    }
+}

+ 57 - 0
app/src/main/java/com/edufound/reader/ijkplayer/media/FileMediaDataSource.java

@@ -0,0 +1,57 @@
+/*
+ * 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 com.edufound.reader.ijkplayer.media;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+
+import tv.danmaku.ijk.media.player.misc.IMediaDataSource;
+
+public class FileMediaDataSource implements IMediaDataSource {
+    private RandomAccessFile mFile;
+    private long mFileSize;
+
+    public FileMediaDataSource(File file) throws IOException {
+        mFile = new RandomAccessFile(file, "r");
+        mFileSize = mFile.length();
+    }
+
+    @Override
+    public int readAt(long position, byte[] buffer, int offset, int size) throws IOException {
+        if (mFile.getFilePointer() != position)
+            mFile.seek(position);
+
+        if (size == 0)
+            return 0;
+
+        return mFile.read(buffer, 0, size);
+    }
+
+    @Override
+    public long getSize() throws IOException {
+        return mFileSize;
+    }
+
+    @Override
+    public void close() throws IOException {
+        mFileSize = 0;
+        mFile.close();
+        mFile = null;
+    }
+}

+ 42 - 0
app/src/main/java/com/edufound/reader/ijkplayer/media/IMediaController.java

@@ -0,0 +1,42 @@
+/*
+ * 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 com.edufound.reader.ijkplayer.media;
+
+import android.view.View;
+import android.widget.MediaController;
+
+public interface IMediaController {
+    void hide();
+
+    boolean isShowing();
+
+    void setAnchorView(View view);
+
+    void setEnabled(boolean enabled);
+
+    void setMediaPlayer(MediaController.MediaPlayerControl player);
+
+    void show(int timeout);
+
+    void show();
+
+    //----------
+    // Extends
+    //----------
+    void showOnce(View view);
+}

+ 87 - 0
app/src/main/java/com/edufound/reader/ijkplayer/media/IRenderView.java

@@ -0,0 +1,87 @@
+/*
+ * 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 com.edufound.reader.ijkplayer.media;
+
+import android.graphics.SurfaceTexture;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.View;
+
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.annotations.Nullable;
+import tv.danmaku.ijk.media.player.IMediaPlayer;
+
+public interface IRenderView {
+    int AR_ASPECT_FIT_PARENT = 0; // without clip
+    int AR_ASPECT_FILL_PARENT = 1; // may clip
+    int AR_ASPECT_WRAP_CONTENT = 2;
+    int AR_MATCH_PARENT = 3;
+    int AR_16_9_FIT_PARENT = 4;
+    int AR_4_3_FIT_PARENT = 5;
+
+    View getView();
+
+    boolean shouldWaitForResize();
+
+    void setVideoSize(int videoWidth, int videoHeight);
+
+    void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen);
+
+    void setVideoRotation(int degree);
+
+    void setAspectRatio(int aspectRatio);
+
+    void addRenderCallback(@NonNull IRenderCallback callback);
+
+    void removeRenderCallback(@NonNull IRenderCallback callback);
+
+    interface ISurfaceHolder {
+        void bindToMediaPlayer(IMediaPlayer mp);
+
+        @NonNull
+        IRenderView getRenderView();
+
+        @Nullable
+        SurfaceHolder getSurfaceHolder();
+
+        @Nullable
+        Surface openSurface();
+
+        @Nullable
+        SurfaceTexture getSurfaceTexture();
+    }
+
+    interface IRenderCallback {
+        /**
+         * @param holder
+         * @param width  could be 0
+         * @param height could be 0
+         */
+        void onSurfaceCreated(@NonNull ISurfaceHolder holder, int width, int height);
+
+        /**
+         * @param holder
+         * @param format could be 0
+         * @param width
+         * @param height
+         */
+        void onSurfaceChanged(@NonNull ISurfaceHolder holder, int format, int width, int height);
+
+        void onSurfaceDestroyed(@NonNull ISurfaceHolder holder);
+    }
+}

File diff suppressed because it is too large
+ 1188 - 0
app/src/main/java/com/edufound/reader/ijkplayer/media/IjkVideoView.java


+ 215 - 0
app/src/main/java/com/edufound/reader/ijkplayer/media/MeasureHelper.java

@@ -0,0 +1,215 @@
+/*
+ * 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 com.edufound.reader.ijkplayer.media;
+
+import android.view.View;
+
+import java.lang.ref.WeakReference;
+
+public final class MeasureHelper {
+    private WeakReference<View> mWeakView;
+
+    private int mVideoWidth;
+    private int mVideoHeight;
+    private int mVideoSarNum;
+    private int mVideoSarDen;
+
+    private int mVideoRotationDegree;
+
+    private int mMeasuredWidth;
+    private int mMeasuredHeight;
+
+    private int mCurrentAspectRatio = IRenderView.AR_ASPECT_FIT_PARENT;
+
+    public MeasureHelper(View view) {
+        mWeakView = new WeakReference<View>(view);
+    }
+
+    public View getView() {
+        if (mWeakView == null)
+            return null;
+        return mWeakView.get();
+    }
+
+    public void setVideoSize(int videoWidth, int videoHeight) {
+        mVideoWidth = videoWidth;
+        mVideoHeight = videoHeight;
+    }
+
+    public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) {
+        mVideoSarNum = videoSarNum;
+        mVideoSarDen = videoSarDen;
+    }
+
+    public void setVideoRotation(int videoRotationDegree) {
+        mVideoRotationDegree = videoRotationDegree;
+    }
+
+    /**
+     * Must be called by View.onMeasure(int, int)
+     *
+     * @param widthMeasureSpec
+     * @param heightMeasureSpec
+     */
+    public void doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        //Log.i("@@@@", "onMeasure(" + MeasureSpec.toString(widthMeasureSpec) + ", "
+        //        + MeasureSpec.toString(heightMeasureSpec) + ")");
+        if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) {
+            int tempSpec = widthMeasureSpec;
+            widthMeasureSpec  = heightMeasureSpec;
+            heightMeasureSpec = tempSpec;
+        }
+
+        int width = View.getDefaultSize(mVideoWidth, widthMeasureSpec);
+        int height = View.getDefaultSize(mVideoHeight, heightMeasureSpec);
+        if (mCurrentAspectRatio == IRenderView.AR_MATCH_PARENT) {
+            width = widthMeasureSpec;
+            height = heightMeasureSpec;
+        } else if (mVideoWidth > 0 && mVideoHeight > 0) {
+            int widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec);
+            int widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec);
+            int heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec);
+            int heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec);
+
+            if (widthSpecMode == View.MeasureSpec.AT_MOST && heightSpecMode == View.MeasureSpec.AT_MOST) {
+                float specAspectRatio = (float) widthSpecSize / (float) heightSpecSize;
+                float displayAspectRatio;
+                switch (mCurrentAspectRatio) {
+                    case IRenderView.AR_16_9_FIT_PARENT:
+                        displayAspectRatio = 16.0f / 9.0f;
+                        if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270)
+                            displayAspectRatio = 1.0f / displayAspectRatio;
+                        break;
+                    case IRenderView.AR_4_3_FIT_PARENT:
+                        displayAspectRatio = 4.0f / 3.0f;
+                        if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270)
+                            displayAspectRatio = 1.0f / displayAspectRatio;
+                        break;
+                    case IRenderView.AR_ASPECT_FIT_PARENT:
+                    case IRenderView.AR_ASPECT_FILL_PARENT:
+                    case IRenderView.AR_ASPECT_WRAP_CONTENT:
+                    default:
+                        displayAspectRatio = (float) mVideoWidth / (float) mVideoHeight;
+                        if (mVideoSarNum > 0 && mVideoSarDen > 0)
+                            displayAspectRatio = displayAspectRatio * mVideoSarNum / mVideoSarDen;
+                        break;
+                }
+                boolean shouldBeWider = displayAspectRatio > specAspectRatio;
+
+                switch (mCurrentAspectRatio) {
+                    case IRenderView.AR_ASPECT_FIT_PARENT:
+                    case IRenderView.AR_16_9_FIT_PARENT:
+                    case IRenderView.AR_4_3_FIT_PARENT:
+                        if (shouldBeWider) {
+                            // too wide, fix width
+                            width = widthSpecSize;
+                            height = (int) (width / displayAspectRatio);
+                        } else {
+                            // too high, fix height
+                            height = heightSpecSize;
+                            width = (int) (height * displayAspectRatio);
+                        }
+                        break;
+                    case IRenderView.AR_ASPECT_FILL_PARENT:
+                        if (shouldBeWider) {
+                            // not high enough, fix height
+                            height = heightSpecSize;
+                            width = (int) (height * displayAspectRatio);
+                        } else {
+                            // not wide enough, fix width
+                            width = widthSpecSize;
+                            height = (int) (width / displayAspectRatio);
+                        }
+                        break;
+                    case IRenderView.AR_ASPECT_WRAP_CONTENT:
+                    default:
+                        if (shouldBeWider) {
+                            // too wide, fix width
+                            width = Math.min(mVideoWidth, widthSpecSize);
+                            height = (int) (width / displayAspectRatio);
+                        } else {
+                            // too high, fix height
+                            height = Math.min(mVideoHeight, heightSpecSize);
+                            width = (int) (height * displayAspectRatio);
+                        }
+                        break;
+                }
+            } else if (widthSpecMode == View.MeasureSpec.EXACTLY && heightSpecMode == View.MeasureSpec.EXACTLY) {
+                // the size is fixed
+                width = widthSpecSize;
+                height = heightSpecSize;
+
+                // for compatibility, we adjust size based on aspect ratio
+                if (mVideoWidth * height < width * mVideoHeight) {
+                    //Log.i("@@@", "image too wide, correcting");
+                    width = height * mVideoWidth / mVideoHeight;
+                } else if (mVideoWidth * height > width * mVideoHeight) {
+                    //Log.i("@@@", "image too tall, correcting");
+                    height = width * mVideoHeight / mVideoWidth;
+                }
+            } else if (widthSpecMode == View.MeasureSpec.EXACTLY) {
+                // only the width is fixed, adjust the height to match aspect ratio if possible
+                width = widthSpecSize;
+                height = width * mVideoHeight / mVideoWidth;
+                if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {
+                    // couldn't match aspect ratio within the constraints
+                    height = heightSpecSize;
+                }
+            } else if (heightSpecMode == View.MeasureSpec.EXACTLY) {
+                // only the height is fixed, adjust the width to match aspect ratio if possible
+                height = heightSpecSize;
+                width = height * mVideoWidth / mVideoHeight;
+                if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {
+                    // couldn't match aspect ratio within the constraints
+                    width = widthSpecSize;
+                }
+            } else {
+                // neither the width nor the height are fixed, try to use actual video size
+                width = mVideoWidth;
+                height = mVideoHeight;
+                if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {
+                    // too tall, decrease both width and height
+                    height = heightSpecSize;
+                    width = height * mVideoWidth / mVideoHeight;
+                }
+                if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {
+                    // too wide, decrease both width and height
+                    width = widthSpecSize;
+                    height = width * mVideoHeight / mVideoWidth;
+                }
+            }
+        } else {
+            // no size yet, just adopt the given spec sizes
+        }
+
+        mMeasuredWidth = width;
+        mMeasuredHeight = height;
+    }
+
+    public int getMeasuredWidth() {
+        return mMeasuredWidth;
+    }
+
+    public int getMeasuredHeight() {
+        return mMeasuredHeight;
+    }
+
+    public void setAspectRatio(int aspectRatio) {
+        mCurrentAspectRatio = aspectRatio;
+    }
+}

+ 309 - 0
app/src/main/java/com/edufound/reader/ijkplayer/media/SurfaceRenderView.java

@@ -0,0 +1,309 @@
+/*
+ * 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 com.edufound.reader.ijkplayer.media;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import java.lang.ref.WeakReference;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.annotations.Nullable;
+import tv.danmaku.ijk.media.player.IMediaPlayer;
+import tv.danmaku.ijk.media.player.ISurfaceTextureHolder;
+
+public class SurfaceRenderView extends SurfaceView implements IRenderView {
+    private MeasureHelper mMeasureHelper;
+
+    public SurfaceRenderView(Context context) {
+        super(context);
+        initView(context);
+    }
+
+    public SurfaceRenderView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initView(context);
+    }
+
+    public SurfaceRenderView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initView(context);
+    }
+
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public SurfaceRenderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        initView(context);
+    }
+
+    private void initView(Context context) {
+        mMeasureHelper = new MeasureHelper(this);
+        mSurfaceCallback = new SurfaceCallback(this);
+        getHolder().addCallback(mSurfaceCallback);
+        //noinspection deprecation
+        getHolder().setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
+    }
+
+    @Override
+    public View getView() {
+        return this;
+    }
+
+    @Override
+    public boolean shouldWaitForResize() {
+        return true;
+    }
+
+    int setVideoSizevideoWidth;
+    int setVideoSizevideoHeight;
+
+    //--------------------
+    // Layout & Measure
+    //--------------------
+    @Override
+    public void setVideoSize(int videoWidth, int videoHeight) {
+        if (videoWidth > 0 && videoHeight > 0) {
+            mMeasureHelper.setVideoSize(videoWidth, videoHeight);
+            setVideoSizevideoWidth = videoWidth;
+            setVideoSizevideoHeight = videoHeight;
+            handler.removeMessages(1);
+            handler.sendEmptyMessageDelayed(1, 300);
+        }
+    }
+
+    Handler handler = new Handler(new Handler.Callback() {
+        @Override
+        public boolean handleMessage(Message msg) {
+            switch (msg.what) {
+                case 1:
+                    getHolder().setFixedSize(setVideoSizevideoWidth, setVideoSizevideoHeight);
+                    requestLayout();
+                    break;
+            }
+            return false;
+        }
+    });
+
+    @Override
+    public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) {
+        if (videoSarNum > 0 && videoSarDen > 0) {
+            mMeasureHelper.setVideoSampleAspectRatio(videoSarNum, videoSarDen);
+//            requestLayout();
+            handler.sendEmptyMessage(1);
+        }
+    }
+
+    @Override
+    public void setVideoRotation(int degree) {
+        Log.e("", "SurfaceView doesn't support rotation (" + degree + ")!\n");
+    }
+
+    @Override
+    public void setAspectRatio(int aspectRatio) {
+        mMeasureHelper.setAspectRatio(aspectRatio);
+        requestLayout();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec);
+        setMeasuredDimension(mMeasureHelper.getMeasuredWidth(), mMeasureHelper.getMeasuredHeight());
+    }
+
+    //--------------------
+    // SurfaceViewHolder
+    //--------------------
+
+    private static final class InternalSurfaceHolder implements ISurfaceHolder {
+        private SurfaceRenderView mSurfaceView;
+        private SurfaceHolder mSurfaceHolder;
+
+        public InternalSurfaceHolder(@NonNull SurfaceRenderView surfaceView,
+                                     @Nullable SurfaceHolder surfaceHolder) {
+            mSurfaceView = surfaceView;
+            mSurfaceHolder = surfaceHolder;
+        }
+
+        public void bindToMediaPlayer(IMediaPlayer mp) {
+            if (mp != null) {
+                if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) &&
+                        (mp instanceof ISurfaceTextureHolder)) {
+                    ISurfaceTextureHolder textureHolder = (ISurfaceTextureHolder) mp;
+                    textureHolder.setSurfaceTexture(null);
+                }
+                mp.setDisplay(mSurfaceHolder);
+            }
+        }
+
+        @NonNull
+        @Override
+        public IRenderView getRenderView() {
+            return mSurfaceView;
+        }
+
+        @Nullable
+        @Override
+        public SurfaceHolder getSurfaceHolder() {
+            return mSurfaceHolder;
+        }
+
+        @Nullable
+        @Override
+        public SurfaceTexture getSurfaceTexture() {
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public Surface openSurface() {
+            if (mSurfaceHolder == null)
+                return null;
+            return mSurfaceHolder.getSurface();
+        }
+    }
+
+    //-------------------------
+    // SurfaceHolder.Callback
+    //-------------------------
+
+    @Override
+    public void addRenderCallback(IRenderCallback callback) {
+        mSurfaceCallback.addRenderCallback(callback);
+    }
+
+    @Override
+    public void removeRenderCallback(IRenderCallback callback) {
+        mSurfaceCallback.removeRenderCallback(callback);
+    }
+
+    private SurfaceCallback mSurfaceCallback;
+
+    private static final class SurfaceCallback implements SurfaceHolder.Callback {
+        private SurfaceHolder mSurfaceHolder;
+        private boolean mIsFormatChanged;
+        private int mFormat;
+        private int mWidth;
+        private int mHeight;
+
+        private WeakReference<SurfaceRenderView> mWeakSurfaceView;
+        private Map<IRenderCallback, Object> mRenderCallbackMap = new ConcurrentHashMap<IRenderCallback, Object>();
+
+        public SurfaceCallback(@NonNull SurfaceRenderView surfaceView) {
+            mWeakSurfaceView = new WeakReference<SurfaceRenderView>(surfaceView);
+        }
+
+        public void addRenderCallback(@NonNull IRenderCallback callback) {
+            mRenderCallbackMap.put(callback, callback);
+
+            ISurfaceHolder surfaceHolder = null;
+            if (mSurfaceHolder != null) {
+                if (surfaceHolder == null)
+                    surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder);
+                callback.onSurfaceCreated(surfaceHolder, mWidth, mHeight);
+            }
+
+            if (mIsFormatChanged) {
+                if (surfaceHolder == null)
+                    surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder);
+                callback.onSurfaceChanged(surfaceHolder, mFormat, mWidth, mHeight);
+            }
+        }
+
+        public void removeRenderCallback(@NonNull IRenderCallback callback) {
+            mRenderCallbackMap.remove(callback);
+        }
+
+        @Override
+        public void surfaceCreated(SurfaceHolder holder) {
+            mSurfaceHolder = holder;
+            mIsFormatChanged = false;
+            mFormat = 0;
+            mWidth = 0;
+            mHeight = 0;
+
+            ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder);
+            for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
+                renderCallback.onSurfaceCreated(surfaceHolder, 0, 0);
+            }
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder holder) {
+            mSurfaceHolder = null;
+            mIsFormatChanged = false;
+            mFormat = 0;
+            mWidth = 0;
+            mHeight = 0;
+
+            ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder);
+            for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
+                renderCallback.onSurfaceDestroyed(surfaceHolder);
+            }
+        }
+
+        @Override
+        public void surfaceChanged(SurfaceHolder holder, int format,
+                                   int width, int height) {
+            mSurfaceHolder = holder;
+            mIsFormatChanged = true;
+            mFormat = format;
+            mWidth = width;
+            mHeight = height;
+
+            // mMeasureHelper.setVideoSize(width, height);
+
+            ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakSurfaceView.get(), mSurfaceHolder);
+            for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
+                renderCallback.onSurfaceChanged(surfaceHolder, format, width, height);
+            }
+        }
+    }
+
+    //--------------------
+    // Accessibility
+    //--------------------
+
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(event);
+        event.setClassName(SurfaceRenderView.class.getName());
+    }
+
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+            info.setClassName(SurfaceRenderView.class.getName());
+        }
+    }
+}

+ 370 - 0
app/src/main/java/com/edufound/reader/ijkplayer/media/TextureRenderView.java

@@ -0,0 +1,370 @@
+/*
+ * 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 com.edufound.reader.ijkplayer.media;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.graphics.SurfaceTexture;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Surface;
+import android.view.SurfaceHolder;
+import android.view.TextureView;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import java.lang.ref.WeakReference;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import io.reactivex.rxjava3.annotations.NonNull;
+import io.reactivex.rxjava3.annotations.Nullable;
+import tv.danmaku.ijk.media.player.IMediaPlayer;
+import tv.danmaku.ijk.media.player.ISurfaceTextureHolder;
+import tv.danmaku.ijk.media.player.ISurfaceTextureHost;
+
+@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+public class TextureRenderView extends TextureView implements IRenderView {
+    private static final String TAG = "TextureRenderView";
+    private MeasureHelper mMeasureHelper;
+
+    public TextureRenderView(Context context) {
+        super(context);
+        initView(context);
+    }
+
+    public TextureRenderView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        initView(context);
+    }
+
+    public TextureRenderView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+        initView(context);
+    }
+
+    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    public TextureRenderView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+        super(context, attrs, defStyleAttr, defStyleRes);
+        initView(context);
+    }
+
+    private void initView(Context context) {
+        mMeasureHelper = new MeasureHelper(this);
+        mSurfaceCallback = new SurfaceCallback(this);
+        setSurfaceTextureListener(mSurfaceCallback);
+    }
+
+    @Override
+    public View getView() {
+        return this;
+    }
+
+    @Override
+    public boolean shouldWaitForResize() {
+        return false;
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        mSurfaceCallback.willDetachFromWindow();
+        super.onDetachedFromWindow();
+        mSurfaceCallback.didDetachFromWindow();
+    }
+
+    //--------------------
+    // Layout & Measure
+    //--------------------
+    @Override
+    public void setVideoSize(int videoWidth, int videoHeight) {
+        if (videoWidth > 0 && videoHeight > 0) {
+            mMeasureHelper.setVideoSize(videoWidth, videoHeight);
+            requestLayout();
+        }
+    }
+
+    @Override
+    public void setVideoSampleAspectRatio(int videoSarNum, int videoSarDen) {
+        if (videoSarNum > 0 && videoSarDen > 0) {
+            mMeasureHelper.setVideoSampleAspectRatio(videoSarNum, videoSarDen);
+            requestLayout();
+        }
+    }
+
+    @Override
+    public void setVideoRotation(int degree) {
+        mMeasureHelper.setVideoRotation(degree);
+        setRotation(degree);
+    }
+
+    @Override
+    public void setAspectRatio(int aspectRatio) {
+        mMeasureHelper.setAspectRatio(aspectRatio);
+        requestLayout();
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec);
+        setMeasuredDimension(mMeasureHelper.getMeasuredWidth(), mMeasureHelper.getMeasuredHeight());
+    }
+
+    //--------------------
+    // TextureViewHolder
+    //--------------------
+
+    public ISurfaceHolder getSurfaceHolder() {
+        return new InternalSurfaceHolder(this, mSurfaceCallback.mSurfaceTexture, mSurfaceCallback);
+    }
+
+    private static final class InternalSurfaceHolder implements ISurfaceHolder {
+        private TextureRenderView mTextureView;
+        private SurfaceTexture mSurfaceTexture;
+        private ISurfaceTextureHost mSurfaceTextureHost;
+
+        public InternalSurfaceHolder(@NonNull TextureRenderView textureView,
+                                     @Nullable SurfaceTexture surfaceTexture,
+                                     @NonNull ISurfaceTextureHost surfaceTextureHost) {
+            mTextureView = textureView;
+            mSurfaceTexture = surfaceTexture;
+            mSurfaceTextureHost = surfaceTextureHost;
+        }
+
+        @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+        public void bindToMediaPlayer(IMediaPlayer mp) {
+            if (mp == null)
+                return;
+
+            if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) &&
+                    (mp instanceof ISurfaceTextureHolder)) {
+                ISurfaceTextureHolder textureHolder = (ISurfaceTextureHolder) mp;
+                mTextureView.mSurfaceCallback.setOwnSurfaceTexture(false);
+
+                SurfaceTexture surfaceTexture = textureHolder.getSurfaceTexture();
+                if (surfaceTexture != null) {
+                    mTextureView.setSurfaceTexture(surfaceTexture);
+                } else {
+                    textureHolder.setSurfaceTexture(mSurfaceTexture);
+                    textureHolder.setSurfaceTextureHost(mTextureView.mSurfaceCallback);
+                }
+            } else {
+                mp.setSurface(openSurface());
+            }
+        }
+
+        @NonNull
+        @Override
+        public IRenderView getRenderView() {
+            return mTextureView;
+        }
+
+        @Nullable
+        @Override
+        public SurfaceHolder getSurfaceHolder() {
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public SurfaceTexture getSurfaceTexture() {
+            return mSurfaceTexture;
+        }
+
+        @Nullable
+        @Override
+        public Surface openSurface() {
+            if (mSurfaceTexture == null)
+                return null;
+            return new Surface(mSurfaceTexture);
+        }
+    }
+
+    //-------------------------
+    // SurfaceHolder.Callback
+    //-------------------------
+
+    @Override
+    public void addRenderCallback(IRenderCallback callback) {
+        mSurfaceCallback.addRenderCallback(callback);
+    }
+
+    @Override
+    public void removeRenderCallback(IRenderCallback callback) {
+        mSurfaceCallback.removeRenderCallback(callback);
+    }
+
+    private SurfaceCallback mSurfaceCallback;
+
+    private static final class SurfaceCallback implements SurfaceTextureListener, ISurfaceTextureHost {
+        private SurfaceTexture mSurfaceTexture;
+        private boolean mIsFormatChanged;
+        private int mWidth;
+        private int mHeight;
+
+        private boolean mOwnSurfaceTexture = true;
+        private boolean mWillDetachFromWindow = false;
+        private boolean mDidDetachFromWindow = false;
+
+        private WeakReference<TextureRenderView> mWeakRenderView;
+        private Map<IRenderCallback, Object> mRenderCallbackMap = new ConcurrentHashMap<IRenderCallback, Object>();
+
+        public SurfaceCallback(@NonNull TextureRenderView renderView) {
+            mWeakRenderView = new WeakReference<TextureRenderView>(renderView);
+        }
+
+        public void setOwnSurfaceTexture(boolean ownSurfaceTexture) {
+            mOwnSurfaceTexture = ownSurfaceTexture;
+        }
+
+        public void addRenderCallback(@NonNull IRenderCallback callback) {
+            mRenderCallbackMap.put(callback, callback);
+
+            ISurfaceHolder surfaceHolder = null;
+            if (mSurfaceTexture != null) {
+                if (surfaceHolder == null)
+                    surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), mSurfaceTexture, this);
+                callback.onSurfaceCreated(surfaceHolder, mWidth, mHeight);
+            }
+
+            if (mIsFormatChanged) {
+                if (surfaceHolder == null)
+                    surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), mSurfaceTexture, this);
+                callback.onSurfaceChanged(surfaceHolder, 0, mWidth, mHeight);
+            }
+        }
+
+        public void removeRenderCallback(@NonNull IRenderCallback callback) {
+            mRenderCallbackMap.remove(callback);
+        }
+
+        @Override
+        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+            mSurfaceTexture = surface;
+            mIsFormatChanged = false;
+            mWidth = 0;
+            mHeight = 0;
+
+            ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), surface, this);
+            for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
+                renderCallback.onSurfaceCreated(surfaceHolder, 0, 0);
+            }
+        }
+
+        @Override
+        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+            mSurfaceTexture = surface;
+            mIsFormatChanged = true;
+            mWidth = width;
+            mHeight = height;
+
+            ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), surface, this);
+            for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
+                renderCallback.onSurfaceChanged(surfaceHolder, 0, width, height);
+            }
+        }
+
+        @Override
+        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+            mSurfaceTexture = surface;
+            mIsFormatChanged = false;
+            mWidth = 0;
+            mHeight = 0;
+
+            ISurfaceHolder surfaceHolder = new InternalSurfaceHolder(mWeakRenderView.get(), surface, this);
+            for (IRenderCallback renderCallback : mRenderCallbackMap.keySet()) {
+                renderCallback.onSurfaceDestroyed(surfaceHolder);
+            }
+
+            Log.d(TAG, "onSurfaceTextureDestroyed: destroy: " + mOwnSurfaceTexture);
+            return mOwnSurfaceTexture;
+        }
+
+        @Override
+        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+        }
+
+        //-------------------------
+        // ISurfaceTextureHost
+        //-------------------------
+
+        @Override
+        public void releaseSurfaceTexture(SurfaceTexture surfaceTexture) {
+            if (surfaceTexture == null) {
+                Log.d(TAG, "releaseSurfaceTexture: null");
+            } else if (mDidDetachFromWindow) {
+                if (surfaceTexture != mSurfaceTexture) {
+                    Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): release different SurfaceTexture");
+                    surfaceTexture.release();
+                } else if (!mOwnSurfaceTexture) {
+                    Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): release detached SurfaceTexture");
+                    surfaceTexture.release();
+                } else {
+                    Log.d(TAG, "releaseSurfaceTexture: didDetachFromWindow(): already released by TextureView");
+                }
+            } else if (mWillDetachFromWindow) {
+                if (surfaceTexture != mSurfaceTexture) {
+                    Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): release different SurfaceTexture");
+                    surfaceTexture.release();
+                } else if (!mOwnSurfaceTexture) {
+                    Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): re-attach SurfaceTexture to TextureView");
+                    setOwnSurfaceTexture(true);
+                } else {
+                    Log.d(TAG, "releaseSurfaceTexture: willDetachFromWindow(): will released by TextureView");
+                }
+            } else {
+                if (surfaceTexture != mSurfaceTexture) {
+                    Log.d(TAG, "releaseSurfaceTexture: alive: release different SurfaceTexture");
+                    surfaceTexture.release();
+                } else if (!mOwnSurfaceTexture) {
+                    Log.d(TAG, "releaseSurfaceTexture: alive: re-attach SurfaceTexture to TextureView");
+                    setOwnSurfaceTexture(true);
+                } else {
+                    Log.d(TAG, "releaseSurfaceTexture: alive: will released by TextureView");
+                }
+            }
+        }
+
+        public void willDetachFromWindow() {
+            Log.d(TAG, "willDetachFromWindow()");
+            mWillDetachFromWindow = true;
+        }
+
+        public void didDetachFromWindow() {
+            Log.d(TAG, "didDetachFromWindow()");
+            mDidDetachFromWindow = true;
+        }
+    }
+
+    //--------------------
+    // Accessibility
+    //--------------------
+
+    @Override
+    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+        super.onInitializeAccessibilityEvent(event);
+        event.setClassName(TextureRenderView.class.getName());
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(info);
+        info.setClassName(TextureRenderView.class.getName());
+    }
+}

+ 63 - 0
app/src/main/java/com/edufound/reader/ijkplayer/services/MediaPlayerService.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 com.edufound.reader.ijkplayer.services;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+
+import io.reactivex.rxjava3.annotations.Nullable;
+import tv.danmaku.ijk.media.player.IMediaPlayer;
+
+public class MediaPlayerService extends Service {
+    private static IMediaPlayer sMediaPlayer;
+
+    public static Intent newIntent(Context context) {
+        Intent intent = new Intent(context, MediaPlayerService.class);
+        return intent;
+    }
+
+    public static void intentToStart(Context context) {
+        context.startService(newIntent(context));
+    }
+
+    public static void intentToStop(Context context) {
+        context.stopService(newIntent(context));
+    }
+
+    @Nullable
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    public static void setMediaPlayer(IMediaPlayer mp) {
+        if (sMediaPlayer != null && sMediaPlayer != mp) {
+            if (sMediaPlayer.isPlaying())
+                sMediaPlayer.stop();
+            sMediaPlayer.release();
+            sMediaPlayer = null;
+        }
+        sMediaPlayer = mp;
+    }
+
+    public static IMediaPlayer getMediaPlayer() {
+        return sMediaPlayer;
+    }
+}