a
This commit is contained in:
@@ -0,0 +1 @@
|
||||
/build
|
||||
@@ -0,0 +1,83 @@
|
||||
def isBuildModule = rootProject.ext.module.isBuildModule
|
||||
if (Boolean.valueOf(isBuildModule)) {
|
||||
apply plugin: 'com.android.application'
|
||||
} else {
|
||||
apply plugin: 'com.android.library'
|
||||
}
|
||||
apply plugin: 'org.jetbrains.kotlin.android'
|
||||
apply plugin: 'kotlin-kapt'
|
||||
|
||||
|
||||
android {
|
||||
compileSdk rootProject.ext.android.compileSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdk rootProject.ext.android.minSdkVersion
|
||||
targetSdk rootProject.ext.android.targetSdkVersion
|
||||
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
}
|
||||
|
||||
javaCompileOptions {
|
||||
annotationProcessorOptions {
|
||||
arguments += [
|
||||
"room.schemaLocation":"$projectDir/schemas".toString(),
|
||||
"room.incremental":"true",
|
||||
"room.expandProjection":"true",
|
||||
AROUTER_MODULE_NAME: project.getName()
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_1_8.toString()
|
||||
}
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
if (Boolean.valueOf(isBuildModule)) {
|
||||
manifest.srcFile 'src/main/module/AndroidManifest.xml'
|
||||
} else {
|
||||
manifest.srcFile 'src/main/AndroidManifest.xml'
|
||||
java {
|
||||
//排除java/debug文件夹下的所有文件
|
||||
exclude '*module'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kapt {
|
||||
arguments {
|
||||
arg("AROUTER_MODULE_NAME", project.getName())
|
||||
}
|
||||
generateStubs = true
|
||||
useBuildCache = true
|
||||
javacOptions {
|
||||
option("-Xmaxerrs", 500)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
implementation project(":common:common-base")
|
||||
implementation 'tv.danmaku.ijk.media:ijkplayer-java:0.8.8'
|
||||
kapt rootProject.ext.compiler["arouterCompiler"]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
package com.bytebitx.media
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("com.bytebitx.media.test", appContext.packageName)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.bytebitx.media">
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:screenOrientation="landscape"
|
||||
android:name=".ui.activity.VideoActivity"/>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
package com.bytebitx.media.ui.activity
|
||||
|
||||
import android.os.Bundle
|
||||
import com.alibaba.android.arouter.facade.annotation.Route
|
||||
import com.alibaba.android.arouter.launcher.ARouter
|
||||
import com.bbgo.common_base.base.BaseActivity
|
||||
import com.bbgo.common_base.constants.RouterPath
|
||||
import com.bytebitx.media.databinding.ActivityVideoBinding
|
||||
import com.bytebitx.media.widget.AndroidMediaController
|
||||
|
||||
@Route(path = RouterPath.Media.PAGE_VIDEO)
|
||||
class VideoActivity : BaseActivity<ActivityVideoBinding>() {
|
||||
|
||||
private var mMediaController: AndroidMediaController? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(binding.root)
|
||||
initView()
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
ARouter.getInstance().inject(this)
|
||||
mMediaController = AndroidMediaController(this, false)
|
||||
binding.videoView.setMediaController(mMediaController)
|
||||
// binding.videoView.setVideoPath("http://vfx.mtime.cn/Video/2019/03/09/mp4/190309153658147087.mp4")
|
||||
// binding.videoView.setVideoPath("https://media.w3.org/2010/05/sintel/trailer.mp4")
|
||||
binding.videoView.setVideoPath("rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4")
|
||||
binding.videoView.start()
|
||||
}
|
||||
|
||||
override fun inflateViewBinding() = ActivityVideoBinding.inflate(layoutInflater)
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* 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.bytebitx.media.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.MediaController;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class AndroidMediaController extends MediaController implements IMediaController {
|
||||
|
||||
public AndroidMediaController(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initView(context);
|
||||
}
|
||||
|
||||
public AndroidMediaController(Context context, boolean useFastForward) {
|
||||
super(context, useFastForward);
|
||||
initView(context);
|
||||
}
|
||||
|
||||
public AndroidMediaController(Context context) {
|
||||
super(context);
|
||||
initView(context);
|
||||
}
|
||||
|
||||
private void initView(Context context) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hide() {
|
||||
super.hide();
|
||||
for (View view : mShowOnceArray)
|
||||
view.setVisibility(View.GONE);
|
||||
mShowOnceArray.clear();
|
||||
}
|
||||
|
||||
//----------
|
||||
// Extends
|
||||
//----------
|
||||
private ArrayList<View> mShowOnceArray = new ArrayList<View>();
|
||||
|
||||
public void showOnce(@NonNull View view) {
|
||||
mShowOnceArray.add(view);
|
||||
view.setVisibility(View.VISIBLE);
|
||||
show();
|
||||
}
|
||||
}
|
||||
+42
@@ -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.bytebitx.media.widget;
|
||||
|
||||
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);
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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.bytebitx.media.widget;
|
||||
|
||||
import android.graphics.SurfaceTexture;
|
||||
import android.view.Surface;
|
||||
import android.view.SurfaceHolder;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.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);
|
||||
}
|
||||
}
|
||||
+785
@@ -0,0 +1,785 @@
|
||||
/*
|
||||
* 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.bytebitx.media.widget;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.MediaController;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import tv.danmaku.ijk.media.player.IMediaPlayer;
|
||||
import tv.danmaku.ijk.media.player.IjkMediaPlayer;
|
||||
import tv.danmaku.ijk.media.player.IjkTimedText;
|
||||
|
||||
public class IjkVideoView extends FrameLayout implements MediaController.MediaPlayerControl {
|
||||
private String TAG = "IjkVideoView";
|
||||
// settable by the client
|
||||
private Uri mUri;
|
||||
|
||||
// all possible internal states
|
||||
private static final int STATE_ERROR = -1;
|
||||
private static final int STATE_IDLE = 0;
|
||||
private static final int STATE_PREPARING = 1;
|
||||
private static final int STATE_PREPARED = 2;
|
||||
private static final int STATE_PLAYING = 3;
|
||||
private static final int STATE_PAUSED = 4;
|
||||
private static final int STATE_PLAYBACK_COMPLETED = 5;
|
||||
|
||||
// mCurrentState is a VideoView object's current state.
|
||||
// mTargetState is the state that a method caller intends to reach.
|
||||
// For instance, regardless the VideoView object's current state,
|
||||
// calling pause() intends to bring the object to a target state
|
||||
// of STATE_PAUSED.
|
||||
private int mCurrentState = STATE_IDLE;
|
||||
private int mTargetState = STATE_IDLE;
|
||||
|
||||
// All the stuff we need for playing and showing a video
|
||||
private IRenderView.ISurfaceHolder mSurfaceHolder = null;
|
||||
private IMediaPlayer mMediaPlayer = null;
|
||||
// private int mAudioSession;
|
||||
private int mVideoWidth;
|
||||
private int mVideoHeight;
|
||||
private int mSurfaceWidth;
|
||||
private int mSurfaceHeight;
|
||||
private IMediaController mMediaController;
|
||||
private IMediaPlayer.OnCompletionListener mOnCompletionListener;
|
||||
private IMediaPlayer.OnPreparedListener mOnPreparedListener;
|
||||
private int mCurrentBufferPercentage;
|
||||
private IMediaPlayer.OnErrorListener mOnErrorListener;
|
||||
private IMediaPlayer.OnInfoListener mOnInfoListener;
|
||||
private int mSeekWhenPrepared; // recording the seek position while preparing
|
||||
private boolean mCanPause = true;
|
||||
private boolean mCanSeekBack = true;
|
||||
private boolean mCanSeekForward = true;
|
||||
|
||||
private Context mAppContext;
|
||||
private IRenderView mRenderView;
|
||||
private int mVideoSarNum;
|
||||
private int mVideoSarDen;
|
||||
|
||||
private TextView subtitleDisplay;
|
||||
|
||||
public IjkVideoView(Context context) {
|
||||
super(context);
|
||||
initVideoView(context);
|
||||
}
|
||||
|
||||
public IjkVideoView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
initVideoView(context);
|
||||
}
|
||||
|
||||
public IjkVideoView(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
initVideoView(context);
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
public IjkVideoView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
initVideoView(context);
|
||||
}
|
||||
|
||||
private void initVideoView(Context context) {
|
||||
mAppContext = context.getApplicationContext();
|
||||
initRenders();
|
||||
|
||||
mVideoWidth = 0;
|
||||
mVideoHeight = 0;
|
||||
setFocusable(true);
|
||||
setFocusableInTouchMode(true);
|
||||
requestFocus();
|
||||
mCurrentState = STATE_IDLE;
|
||||
mTargetState = STATE_IDLE;
|
||||
|
||||
subtitleDisplay = new TextView(context);
|
||||
subtitleDisplay.setTextSize(24);
|
||||
subtitleDisplay.setGravity(Gravity.CENTER);
|
||||
LayoutParams layoutParams_txt = new LayoutParams(
|
||||
LayoutParams.MATCH_PARENT,
|
||||
LayoutParams.WRAP_CONTENT,
|
||||
Gravity.BOTTOM);
|
||||
addView(subtitleDisplay, layoutParams_txt);
|
||||
}
|
||||
|
||||
public void setRenderView(IRenderView renderView) {
|
||||
if (mRenderView != null) {
|
||||
if (mMediaPlayer != null)
|
||||
mMediaPlayer.setDisplay(null);
|
||||
|
||||
View renderUIView = mRenderView.getView();
|
||||
mRenderView.removeRenderCallback(mSHCallback);
|
||||
mRenderView = null;
|
||||
removeView(renderUIView);
|
||||
}
|
||||
|
||||
if (renderView == null)
|
||||
return;
|
||||
|
||||
mRenderView = renderView;
|
||||
if (mVideoWidth > 0 && mVideoHeight > 0)
|
||||
renderView.setVideoSize(mVideoWidth, mVideoHeight);
|
||||
if (mVideoSarNum > 0 && mVideoSarDen > 0)
|
||||
renderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen);
|
||||
|
||||
View renderUIView = mRenderView.getView();
|
||||
LayoutParams lp = new LayoutParams(
|
||||
LayoutParams.WRAP_CONTENT,
|
||||
LayoutParams.WRAP_CONTENT,
|
||||
Gravity.CENTER);
|
||||
renderUIView.setLayoutParams(lp);
|
||||
addView(renderUIView);
|
||||
|
||||
mRenderView.addRenderCallback(mSHCallback);
|
||||
// mRenderView.setVideoRotation(mVideoRotationDegree);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets video path.
|
||||
*
|
||||
* @param path the path of the video.
|
||||
*/
|
||||
public void setVideoPath(String path) {
|
||||
setVideoURI(Uri.parse(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets video URI.
|
||||
*
|
||||
* @param uri the URI of the video.
|
||||
*/
|
||||
public void setVideoURI(Uri uri) {
|
||||
setVideoURI(uri, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets video URI using specific headers.
|
||||
*
|
||||
* @param uri the URI of the video.
|
||||
* @param headers the headers for the URI request.
|
||||
* 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.
|
||||
*/
|
||||
private void setVideoURI(Uri uri, Map<String, String> headers) {
|
||||
mUri = uri;
|
||||
mSeekWhenPrepared = 0;
|
||||
openVideo();
|
||||
requestLayout();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
// REMOVED: addSubtitleSource
|
||||
// REMOVED: mPendingSubtitleTracks
|
||||
|
||||
public void stopPlayback() {
|
||||
if (mMediaPlayer != null) {
|
||||
mMediaPlayer.stop();
|
||||
mMediaPlayer.release();
|
||||
mMediaPlayer = null;
|
||||
mCurrentState = STATE_IDLE;
|
||||
mTargetState = STATE_IDLE;
|
||||
AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
am.abandonAudioFocus(null);
|
||||
}
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
private void openVideo() {
|
||||
if (mUri == null || mSurfaceHolder == null) {
|
||||
// not ready for playback just yet, will try again later
|
||||
return;
|
||||
}
|
||||
// we shouldn't clear the target state, because somebody might have
|
||||
// called start() previously
|
||||
release(false);
|
||||
|
||||
AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
am.requestAudioFocus(null, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN);
|
||||
|
||||
try {
|
||||
mMediaPlayer = createPlayer();
|
||||
mMediaPlayer.setOnPreparedListener(mPreparedListener);
|
||||
mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener);
|
||||
mMediaPlayer.setOnCompletionListener(mCompletionListener);
|
||||
mMediaPlayer.setOnErrorListener(mErrorListener);
|
||||
mMediaPlayer.setOnInfoListener(mInfoListener);
|
||||
mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener);
|
||||
mMediaPlayer.setOnSeekCompleteListener(mSeekCompleteListener);
|
||||
mMediaPlayer.setOnTimedTextListener(mOnTimedTextListener);
|
||||
mCurrentBufferPercentage = 0;
|
||||
mMediaPlayer.setDataSource(mUri.toString());
|
||||
bindSurfaceHolder(mMediaPlayer, mSurfaceHolder);
|
||||
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
|
||||
mMediaPlayer.setScreenOnWhilePlaying(true);
|
||||
mMediaPlayer.prepareAsync();
|
||||
// REMOVED: mPendingSubtitleTracks
|
||||
|
||||
// we don't set the target state here either, but preserve the
|
||||
// target state that was there before.
|
||||
mCurrentState = STATE_PREPARING;
|
||||
attachMediaController();
|
||||
} catch (IOException | IllegalArgumentException ex) {
|
||||
Log.w(TAG, "Unable to open content: " + mUri, ex);
|
||||
mCurrentState = STATE_ERROR;
|
||||
mTargetState = STATE_ERROR;
|
||||
mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void setMediaController(IMediaController controller) {
|
||||
if (mMediaController != null) {
|
||||
mMediaController.hide();
|
||||
}
|
||||
mMediaController = controller;
|
||||
attachMediaController();
|
||||
}
|
||||
|
||||
private void attachMediaController() {
|
||||
if (mMediaPlayer != null && mMediaController != null) {
|
||||
mMediaController.setMediaPlayer(this);
|
||||
View anchorView = this.getParent() instanceof View ?
|
||||
(View) this.getParent() : this;
|
||||
mMediaController.setAnchorView(anchorView);
|
||||
mMediaController.setEnabled(isInPlaybackState());
|
||||
}
|
||||
}
|
||||
|
||||
IMediaPlayer.OnVideoSizeChangedListener mSizeChangedListener =
|
||||
new IMediaPlayer.OnVideoSizeChangedListener() {
|
||||
public void onVideoSizeChanged(IMediaPlayer mp, int width, int height, int sarNum, int sarDen) {
|
||||
mVideoWidth = mp.getVideoWidth();
|
||||
mVideoHeight = mp.getVideoHeight();
|
||||
mVideoSarNum = mp.getVideoSarNum();
|
||||
mVideoSarDen = mp.getVideoSarDen();
|
||||
if (mVideoWidth != 0 && mVideoHeight != 0) {
|
||||
if (mRenderView != null) {
|
||||
mRenderView.setVideoSize(mVideoWidth, mVideoHeight);
|
||||
mRenderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen);
|
||||
}
|
||||
// REMOVED: getHolder().setFixedSize(mVideoWidth, mVideoHeight);
|
||||
requestLayout();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IMediaPlayer.OnPreparedListener mPreparedListener = new IMediaPlayer.OnPreparedListener() {
|
||||
public void onPrepared(IMediaPlayer mp) {
|
||||
mCurrentState = STATE_PREPARED;
|
||||
|
||||
// Get the capabilities of the player for this stream
|
||||
// REMOVED: Metadata
|
||||
|
||||
if (mOnPreparedListener != null) {
|
||||
mOnPreparedListener.onPrepared(mMediaPlayer);
|
||||
}
|
||||
if (mMediaController != null) {
|
||||
mMediaController.setEnabled(true);
|
||||
}
|
||||
mVideoWidth = mp.getVideoWidth();
|
||||
mVideoHeight = mp.getVideoHeight();
|
||||
|
||||
int seekToPosition = mSeekWhenPrepared; // mSeekWhenPrepared may be changed after seekTo() call
|
||||
if (seekToPosition != 0) {
|
||||
seekTo(seekToPosition);
|
||||
}
|
||||
if (mVideoWidth != 0 && mVideoHeight != 0) {
|
||||
//Log.i("@@@@", "video size: " + mVideoWidth +"/"+ mVideoHeight);
|
||||
// REMOVED: getHolder().setFixedSize(mVideoWidth, mVideoHeight);
|
||||
if (mRenderView != null) {
|
||||
mRenderView.setVideoSize(mVideoWidth, mVideoHeight);
|
||||
mRenderView.setVideoSampleAspectRatio(mVideoSarNum, mVideoSarDen);
|
||||
if (!mRenderView.shouldWaitForResize() || mSurfaceWidth == mVideoWidth && mSurfaceHeight == mVideoHeight) {
|
||||
// We didn't actually change the size (it was already at the size
|
||||
// we need), so we won't get a "surface changed" callback, so
|
||||
// start the video here instead of in the callback.
|
||||
if (mTargetState == STATE_PLAYING) {
|
||||
start();
|
||||
if (mMediaController != null) {
|
||||
mMediaController.show();
|
||||
}
|
||||
} else if (!isPlaying() &&
|
||||
(seekToPosition != 0 || getCurrentPosition() > 0)) {
|
||||
if (mMediaController != null) {
|
||||
// Show the media controls when we're paused into a video and make 'em stick.
|
||||
mMediaController.show(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// We don't know the video size yet, but should start anyway.
|
||||
// The video size might be reported to us later.
|
||||
if (mTargetState == STATE_PLAYING) {
|
||||
start();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private IMediaPlayer.OnCompletionListener mCompletionListener =
|
||||
new IMediaPlayer.OnCompletionListener() {
|
||||
public void onCompletion(IMediaPlayer mp) {
|
||||
mCurrentState = STATE_PLAYBACK_COMPLETED;
|
||||
mTargetState = STATE_PLAYBACK_COMPLETED;
|
||||
if (mMediaController != null) {
|
||||
mMediaController.hide();
|
||||
}
|
||||
if (mOnCompletionListener != null) {
|
||||
mOnCompletionListener.onCompletion(mMediaPlayer);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private IMediaPlayer.OnInfoListener mInfoListener =
|
||||
new IMediaPlayer.OnInfoListener() {
|
||||
public boolean onInfo(IMediaPlayer mp, int arg1, int arg2) {
|
||||
if (mOnInfoListener != null) {
|
||||
mOnInfoListener.onInfo(mp, arg1, arg2);
|
||||
}
|
||||
switch (arg1) {
|
||||
case IMediaPlayer.MEDIA_INFO_VIDEO_TRACK_LAGGING:
|
||||
Log.d(TAG, "MEDIA_INFO_VIDEO_TRACK_LAGGING:");
|
||||
break;
|
||||
case IMediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START:
|
||||
Log.d(TAG, "MEDIA_INFO_VIDEO_RENDERING_START:");
|
||||
break;
|
||||
case IMediaPlayer.MEDIA_INFO_BUFFERING_START:
|
||||
Log.d(TAG, "MEDIA_INFO_BUFFERING_START:");
|
||||
break;
|
||||
case IMediaPlayer.MEDIA_INFO_BUFFERING_END:
|
||||
Log.d(TAG, "MEDIA_INFO_BUFFERING_END:");
|
||||
break;
|
||||
case IMediaPlayer.MEDIA_INFO_NETWORK_BANDWIDTH:
|
||||
Log.d(TAG, "MEDIA_INFO_NETWORK_BANDWIDTH: " + arg2);
|
||||
break;
|
||||
case IMediaPlayer.MEDIA_INFO_BAD_INTERLEAVING:
|
||||
Log.d(TAG, "MEDIA_INFO_BAD_INTERLEAVING:");
|
||||
break;
|
||||
case IMediaPlayer.MEDIA_INFO_NOT_SEEKABLE:
|
||||
Log.d(TAG, "MEDIA_INFO_NOT_SEEKABLE:");
|
||||
break;
|
||||
case IMediaPlayer.MEDIA_INFO_METADATA_UPDATE:
|
||||
Log.d(TAG, "MEDIA_INFO_METADATA_UPDATE:");
|
||||
break;
|
||||
case IMediaPlayer.MEDIA_INFO_UNSUPPORTED_SUBTITLE:
|
||||
Log.d(TAG, "MEDIA_INFO_UNSUPPORTED_SUBTITLE:");
|
||||
break;
|
||||
case IMediaPlayer.MEDIA_INFO_SUBTITLE_TIMED_OUT:
|
||||
Log.d(TAG, "MEDIA_INFO_SUBTITLE_TIMED_OUT:");
|
||||
break;
|
||||
case IMediaPlayer.MEDIA_INFO_VIDEO_ROTATION_CHANGED:
|
||||
Log.d(TAG, "MEDIA_INFO_VIDEO_ROTATION_CHANGED: " + arg2);
|
||||
if (mRenderView != null)
|
||||
mRenderView.setVideoRotation(arg2);
|
||||
break;
|
||||
case IMediaPlayer.MEDIA_INFO_AUDIO_RENDERING_START:
|
||||
Log.d(TAG, "MEDIA_INFO_AUDIO_RENDERING_START:");
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private IMediaPlayer.OnErrorListener mErrorListener =
|
||||
new IMediaPlayer.OnErrorListener() {
|
||||
public boolean onError(IMediaPlayer mp, int framework_err, int impl_err) {
|
||||
Log.d(TAG, "Error: " + framework_err + "," + impl_err);
|
||||
mCurrentState = STATE_ERROR;
|
||||
mTargetState = STATE_ERROR;
|
||||
if (mMediaController != null) {
|
||||
mMediaController.hide();
|
||||
}
|
||||
|
||||
/* If an error handler has been supplied, use it and finish. */
|
||||
if (mOnErrorListener != null) {
|
||||
if (mOnErrorListener.onError(mMediaPlayer, framework_err, impl_err)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private IMediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
|
||||
new IMediaPlayer.OnBufferingUpdateListener() {
|
||||
public void onBufferingUpdate(IMediaPlayer mp, int percent) {
|
||||
mCurrentBufferPercentage = percent;
|
||||
}
|
||||
};
|
||||
|
||||
private IMediaPlayer.OnSeekCompleteListener mSeekCompleteListener = new IMediaPlayer.OnSeekCompleteListener() {
|
||||
|
||||
@Override
|
||||
public void onSeekComplete(IMediaPlayer mp) {
|
||||
}
|
||||
};
|
||||
|
||||
private IMediaPlayer.OnTimedTextListener mOnTimedTextListener = new IMediaPlayer.OnTimedTextListener() {
|
||||
@Override
|
||||
public void onTimedText(IMediaPlayer mp, IjkTimedText text) {
|
||||
if (text != null) {
|
||||
subtitleDisplay.setText(text.getText());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when the media file
|
||||
* is loaded and ready to go.
|
||||
*
|
||||
* @param l The callback that will be run
|
||||
*/
|
||||
public void setOnPreparedListener(IMediaPlayer.OnPreparedListener l) {
|
||||
mOnPreparedListener = l;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when the end of a media file
|
||||
* has been reached during playback.
|
||||
*
|
||||
* @param l The callback that will be run
|
||||
*/
|
||||
public void setOnCompletionListener(IMediaPlayer.OnCompletionListener l) {
|
||||
mOnCompletionListener = l;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when an error occurs
|
||||
* during playback or setup. If no listener is specified,
|
||||
* or if the listener returned false, VideoView will inform
|
||||
* the user of any errors.
|
||||
*
|
||||
* @param l The callback that will be run
|
||||
*/
|
||||
public void setOnErrorListener(IMediaPlayer.OnErrorListener l) {
|
||||
mOnErrorListener = l;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to be invoked when an informational event
|
||||
* occurs during playback or setup.
|
||||
*
|
||||
* @param l The callback that will be run
|
||||
*/
|
||||
public void setOnInfoListener(IMediaPlayer.OnInfoListener l) {
|
||||
mOnInfoListener = l;
|
||||
}
|
||||
|
||||
// REMOVED: mSHCallback
|
||||
private void bindSurfaceHolder(IMediaPlayer mp, IRenderView.ISurfaceHolder holder) {
|
||||
if (mp == null)
|
||||
return;
|
||||
|
||||
if (holder == null) {
|
||||
mp.setDisplay(null);
|
||||
return;
|
||||
}
|
||||
|
||||
holder.bindToMediaPlayer(mp);
|
||||
}
|
||||
|
||||
IRenderView.IRenderCallback mSHCallback = new IRenderView.IRenderCallback() {
|
||||
@Override
|
||||
public void onSurfaceChanged(@NonNull IRenderView.ISurfaceHolder holder, int format, int w, int h) {
|
||||
if (holder.getRenderView() != mRenderView) {
|
||||
Log.e(TAG, "onSurfaceChanged: unmatched render callback\n");
|
||||
return;
|
||||
}
|
||||
|
||||
mSurfaceWidth = w;
|
||||
mSurfaceHeight = h;
|
||||
boolean isValidState = (mTargetState == STATE_PLAYING);
|
||||
boolean hasValidSize = !mRenderView.shouldWaitForResize() || (mVideoWidth == w && mVideoHeight == h);
|
||||
if (mMediaPlayer != null && isValidState && hasValidSize) {
|
||||
if (mSeekWhenPrepared != 0) {
|
||||
seekTo(mSeekWhenPrepared);
|
||||
}
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceCreated(@NonNull IRenderView.ISurfaceHolder holder, int width, int height) {
|
||||
if (holder.getRenderView() != mRenderView) {
|
||||
Log.e(TAG, "onSurfaceCreated: unmatched render callback\n");
|
||||
return;
|
||||
}
|
||||
|
||||
mSurfaceHolder = holder;
|
||||
if (mMediaPlayer != null)
|
||||
bindSurfaceHolder(mMediaPlayer, holder);
|
||||
else
|
||||
openVideo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSurfaceDestroyed(@NonNull IRenderView.ISurfaceHolder holder) {
|
||||
if (holder.getRenderView() != mRenderView) {
|
||||
Log.e(TAG, "onSurfaceDestroyed: unmatched render callback\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// after we return from this we can't use the surface any more
|
||||
mSurfaceHolder = null;
|
||||
// REMOVED: if (mMediaController != null) mMediaController.hide();
|
||||
// REMOVED: release(true);
|
||||
releaseWithoutStop();
|
||||
}
|
||||
};
|
||||
|
||||
public void releaseWithoutStop() {
|
||||
if (mMediaPlayer != null)
|
||||
mMediaPlayer.setDisplay(null);
|
||||
}
|
||||
|
||||
/*
|
||||
* release the media player in any state
|
||||
*/
|
||||
public void release(boolean cleartargetstate) {
|
||||
if (mMediaPlayer != null) {
|
||||
mMediaPlayer.reset();
|
||||
mMediaPlayer.release();
|
||||
mMediaPlayer = null;
|
||||
// REMOVED: mPendingSubtitleTracks.clear();
|
||||
mCurrentState = STATE_IDLE;
|
||||
if (cleartargetstate) {
|
||||
mTargetState = STATE_IDLE;
|
||||
}
|
||||
AudioManager am = (AudioManager) mAppContext.getSystemService(Context.AUDIO_SERVICE);
|
||||
am.abandonAudioFocus(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent ev) {
|
||||
if (isInPlaybackState() && mMediaController != null) {
|
||||
toggleMediaControlsVisiblity();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTrackballEvent(MotionEvent ev) {
|
||||
if (isInPlaybackState() && mMediaController != null) {
|
||||
toggleMediaControlsVisiblity();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
boolean isKeyCodeSupported = keyCode != KeyEvent.KEYCODE_BACK &&
|
||||
keyCode != KeyEvent.KEYCODE_VOLUME_UP &&
|
||||
keyCode != KeyEvent.KEYCODE_VOLUME_DOWN &&
|
||||
keyCode != KeyEvent.KEYCODE_VOLUME_MUTE &&
|
||||
keyCode != KeyEvent.KEYCODE_MENU &&
|
||||
keyCode != KeyEvent.KEYCODE_CALL &&
|
||||
keyCode != KeyEvent.KEYCODE_ENDCALL;
|
||||
if (isInPlaybackState() && isKeyCodeSupported && mMediaController != null) {
|
||||
if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK ||
|
||||
keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE) {
|
||||
if (mMediaPlayer.isPlaying()) {
|
||||
pause();
|
||||
mMediaController.show();
|
||||
} else {
|
||||
start();
|
||||
mMediaController.hide();
|
||||
}
|
||||
return true;
|
||||
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) {
|
||||
if (!mMediaPlayer.isPlaying()) {
|
||||
start();
|
||||
mMediaController.hide();
|
||||
}
|
||||
return true;
|
||||
} else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP
|
||||
|| keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) {
|
||||
if (mMediaPlayer.isPlaying()) {
|
||||
pause();
|
||||
mMediaController.show();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
toggleMediaControlsVisiblity();
|
||||
}
|
||||
}
|
||||
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
private void toggleMediaControlsVisiblity() {
|
||||
if (mMediaController.isShowing()) {
|
||||
mMediaController.hide();
|
||||
} else {
|
||||
mMediaController.show();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
if (isInPlaybackState()) {
|
||||
mMediaPlayer.start();
|
||||
mCurrentState = STATE_PLAYING;
|
||||
}
|
||||
mTargetState = STATE_PLAYING;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void pause() {
|
||||
if (isInPlaybackState()) {
|
||||
if (mMediaPlayer.isPlaying()) {
|
||||
mMediaPlayer.pause();
|
||||
mCurrentState = STATE_PAUSED;
|
||||
}
|
||||
}
|
||||
mTargetState = STATE_PAUSED;
|
||||
}
|
||||
|
||||
public void suspend() {
|
||||
release(false);
|
||||
}
|
||||
|
||||
public void resume() {
|
||||
openVideo();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDuration() {
|
||||
if (isInPlaybackState()) {
|
||||
return (int) mMediaPlayer.getDuration();
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCurrentPosition() {
|
||||
if (isInPlaybackState()) {
|
||||
return (int) mMediaPlayer.getCurrentPosition();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seekTo(int msec) {
|
||||
if (isInPlaybackState()) {
|
||||
mMediaPlayer.seekTo(msec);
|
||||
mSeekWhenPrepared = 0;
|
||||
} else {
|
||||
mSeekWhenPrepared = msec;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPlaying() {
|
||||
return isInPlaybackState() && mMediaPlayer.isPlaying();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getBufferPercentage() {
|
||||
if (mMediaPlayer != null) {
|
||||
return mCurrentBufferPercentage;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private boolean isInPlaybackState() {
|
||||
return (mMediaPlayer != null &&
|
||||
mCurrentState != STATE_ERROR &&
|
||||
mCurrentState != STATE_IDLE &&
|
||||
mCurrentState != STATE_PREPARING);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPause() {
|
||||
return mCanPause;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSeekBackward() {
|
||||
return mCanSeekBack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canSeekForward() {
|
||||
return mCanSeekForward;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getAudioSessionId() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
private void initRenders() {
|
||||
SurfaceRenderView renderView = new SurfaceRenderView(getContext());
|
||||
setRenderView(renderView);
|
||||
}
|
||||
|
||||
//-------------------------
|
||||
// Extend: Player
|
||||
//-------------------------
|
||||
public void togglePlayer() {
|
||||
if (mMediaPlayer != null)
|
||||
mMediaPlayer.release();
|
||||
|
||||
if (mRenderView != null)
|
||||
mRenderView.getView().invalidate();
|
||||
openVideo();
|
||||
}
|
||||
|
||||
public IMediaPlayer createPlayer() {
|
||||
IMediaPlayer mediaPlayer;
|
||||
|
||||
IjkMediaPlayer ijkMediaPlayer = null;
|
||||
if (mUri != null) {
|
||||
ijkMediaPlayer = new IjkMediaPlayer();
|
||||
ijkMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG);
|
||||
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1);
|
||||
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 1);
|
||||
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-handle-resolution-change", 1);
|
||||
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 1);
|
||||
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32);
|
||||
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "framedrop", 1);
|
||||
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "start-on-prepared", 0);
|
||||
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_FORMAT, "http-detect-range-support", 0);
|
||||
ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_CODEC, "skip_loop_filter", 48);
|
||||
}
|
||||
mediaPlayer = ijkMediaPlayer;
|
||||
return mediaPlayer;
|
||||
}
|
||||
}
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
/*
|
||||
* 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.bytebitx.media.widget;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
+289
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* 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.bytebitx.media.widget;
|
||||
|
||||
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.SurfaceView;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
//--------------------
|
||||
// Layout & Measure
|
||||
//--------------------
|
||||
@Override
|
||||
public void setVideoSize(int videoWidth, int videoHeight) {
|
||||
if (videoWidth > 0 && videoHeight > 0) {
|
||||
mMeasureHelper.setVideoSize(videoWidth, videoHeight);
|
||||
getHolder().setFixedSize(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) {
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
+371
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
* 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.bytebitx.media.widget;
|
||||
|
||||
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 androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.bytebitx.media.widget.IjkVideoView
|
||||
android:id="@+id/video_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package com.bytebitx.media
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user