diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4f5e23f --- /dev/null +++ b/.gitignore @@ -0,0 +1,35 @@ +#built application files +*.apk +*.ap_ +*.iml + +# files for the dex VM +*.dex + +# Java class files +*.class + +# generated files +bin/ +gen/ + +# Local configuration file (sdk path, etc) +local.properties + +# Windows thumbnail db +Thumbs.db + +# OSX files +.DS_Store + +# Eclipse project files +.classpath +.project + +# Android Studio +.idea/ +#.idea/workspace.xml - remove # and delete .idea if it better suit your needs. +.gradle +build/ +Medusa/src/main/res/values/com_crashlytics_export_strings.xml +ThirdLibs/ImageGalleryLibrary/ImageGalleryLibrary.iml diff --git a/IjkplayerLib/build.gradle b/IjkplayerLib/build.gradle new file mode 100644 index 0000000..8c93ba6 --- /dev/null +++ b/IjkplayerLib/build.gradle @@ -0,0 +1,27 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + defaultConfig { + minSdkVersion 15 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + compile 'tv.danmaku.ijk.media:ijkplayer-java:0.4.5.1' + compile 'tv.danmaku.ijk.media:ijkplayer-exo:0.4.5.1' + compile 'com.android.support:appcompat-v7:23.2.1' + compile 'com.android.support:preference-v7:23.2.1' +} diff --git a/player-java/proguard-rules.pro b/IjkplayerLib/proguard-rules.pro old mode 100755 new mode 100644 similarity index 88% rename from player-java/proguard-rules.pro rename to IjkplayerLib/proguard-rules.pro index 034485d..a61e66b --- a/player-java/proguard-rules.pro +++ b/IjkplayerLib/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified -# in /opt/android/ADK/tools/proguard/proguard-android.txt +# in /Users/zhangyang/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # diff --git a/player-java/src/androidTest/java/tv/danmaku/ijk/media/player/ApplicationTest.java b/IjkplayerLib/src/androidTest/java/com/example/zhangyang/ijkplayerlib/ApplicationTest.java old mode 100755 new mode 100644 similarity index 87% rename from player-java/src/androidTest/java/tv/danmaku/ijk/media/player/ApplicationTest.java rename to IjkplayerLib/src/androidTest/java/com/example/zhangyang/ijkplayerlib/ApplicationTest.java index 60d1d73..d742406 --- a/player-java/src/androidTest/java/tv/danmaku/ijk/media/player/ApplicationTest.java +++ b/IjkplayerLib/src/androidTest/java/com/example/zhangyang/ijkplayerlib/ApplicationTest.java @@ -1,4 +1,4 @@ -package tv.danmaku.ijk.media.player; +package com.example.zhangyang.ijkplayerlib; import android.app.Application; import android.test.ApplicationTestCase; diff --git a/IjkplayerLib/src/main/AndroidManifest.xml b/IjkplayerLib/src/main/AndroidManifest.xml new file mode 100644 index 0000000..cc58024 --- /dev/null +++ b/IjkplayerLib/src/main/AndroidManifest.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/services/MediaPlayerService.java b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/services/MediaPlayerService.java new file mode 100644 index 0000000..8dad259 --- /dev/null +++ b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/services/MediaPlayerService.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2015 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.services; + +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.os.IBinder; +import android.support.annotation.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; + } +} diff --git a/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/Settings.java b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/Settings.java new file mode 100644 index 0000000..8e1dbff --- /dev/null +++ b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/Settings.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2015 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.widget; + +import android.content.Context; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; + +import tv.danmaku.ijk.media.playerLib.R; + + +public class Settings { + private Context mAppContext; + private SharedPreferences mSharedPreferences; + + public static final int PV_PLAYER__Auto = 0; + 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 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 String getLastDirectory() { + String key = mAppContext.getString(R.string.pref_key_last_directory); + return mSharedPreferences.getString(key, "/"); + } + + public void setLastDirectory(String path) { + String key = mAppContext.getString(R.string.pref_key_last_directory); + mSharedPreferences.edit().putString(key, path).commit(); + } +} diff --git a/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/AndroidMediaController.java b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/AndroidMediaController.java new file mode 100644 index 0000000..f4d99c8 --- /dev/null +++ b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/AndroidMediaController.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2015 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.widget.media; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v7.app.ActionBar; +import android.util.AttributeSet; +import android.view.View; + +import java.util.ArrayList; + +public class AndroidMediaController extends PlayerMediaController implements IMediaController { + private ActionBar mActionBar; + + public AndroidMediaController(Context context, AttributeSet attrs) { + super(context, attrs); + initView(context); + } + + public AndroidMediaController(Context context, boolean useFastForward) { + super(context); + initView(context); + } + + public AndroidMediaController(Context context) { + super(context); + initView(context); + } + + protected void initView(Context context) { + } + + public void setSupportActionBar(@Nullable ActionBar actionBar) { + mActionBar = actionBar; + if (isShowing()) { + actionBar.show(); + } else { + actionBar.hide(); + } + } + + @Override + public void show() { + super.show(); + if (mActionBar != null) + mActionBar.show(); + } + + @Override + public void hide() { + super.hide(); + if (mActionBar != null) + mActionBar.hide(); + for (View view : mShowOnceArray) + view.setVisibility(View.GONE); + mShowOnceArray.clear(); + } + + //---------- + // Extends + //---------- + private ArrayList mShowOnceArray = new ArrayList(); + + public void showOnce(@NonNull View view) { + mShowOnceArray.add(view); + view.setVisibility(View.VISIBLE); + show(); + } + + @Override + public void setAnchorView(View view) { + super.setAnchorView(view); + } + + public void setLiveRoomCount(String count) { + + } +} diff --git a/player-java/src/main/java/tv/danmaku/ijk/media/player/MediaInfo.java b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/IMediaController.java old mode 100755 new mode 100644 similarity index 52% rename from player-java/src/main/java/tv/danmaku/ijk/media/player/MediaInfo.java rename to IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/IMediaController.java index 6cd5004..5e84c97 --- a/player-java/src/main/java/tv/danmaku/ijk/media/player/MediaInfo.java +++ b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/IMediaController.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2014 Zhang Rui + * Copyright (C) 2015 Zhang Rui * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,16 +14,28 @@ * limitations under the License. */ -package tv.danmaku.ijk.media.player; +package tv.danmaku.ijk.media.widget.media; -public class MediaInfo { - public String mMediaPlayerName; +import android.view.View; +import android.widget.MediaController; - public String mVideoDecoder; - public String mVideoDecoderImpl; +public interface IMediaController { + void hide(); - public String mAudioDecoder; - public String mAudioDecoderImpl; + boolean isShowing(); - public IjkMediaMeta mMeta; + void setAnchorView(View view); + + void setEnabled(boolean enabled); + + void setMediaPlayer(MediaController.MediaPlayerControl player); + + void show(int timeout); + + void show(); + + //---------- + // Extends + //---------- + void showOnce(View view); } diff --git a/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/IRenderView.java b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/IRenderView.java new file mode 100644 index 0000000..762e0fa --- /dev/null +++ b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/IRenderView.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.widget.media; + +import android.graphics.SurfaceTexture; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.View; + +import tv.danmaku.ijk.media.player.IMediaPlayer; + +public interface IRenderView { + static final int AR_ASPECT_FIT_PARENT = 0; // without clip + static final int AR_ASPECT_FILL_PARENT = 1; // may clip + static final int AR_ASPECT_WRAP_CONTENT = 2; + static final int AR_MATCH_PARENT = 3; + static final int AR_16_9_FIT_PARENT = 4; + static final 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(); + } + + public 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); + } +} diff --git a/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/IjkVideoView.java b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/IjkVideoView.java new file mode 100644 index 0000000..663301b --- /dev/null +++ b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/IjkVideoView.java @@ -0,0 +1,1208 @@ +/* + * Copyright (C) 2015 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.widget.media; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.res.Resources; +import android.media.AudioManager; +import android.media.MediaPlayer; +import android.net.Uri; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.v7.app.AlertDialog; +import android.text.TextUtils; +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.TableLayout; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import tv.danmaku.ijk.media.exo.IjkExoMediaPlayer; +import tv.danmaku.ijk.media.player.AndroidMediaPlayer; +import tv.danmaku.ijk.media.player.IMediaPlayer; +import tv.danmaku.ijk.media.player.IjkMediaPlayer; +import tv.danmaku.ijk.media.player.TextureMediaPlayer; +import tv.danmaku.ijk.media.player.misc.IMediaFormat; +import tv.danmaku.ijk.media.player.misc.ITrackInfo; +import tv.danmaku.ijk.media.player.misc.IjkMediaFormat; +import tv.danmaku.ijk.media.playerLib.R; +import tv.danmaku.ijk.media.services.MediaPlayerService; +import tv.danmaku.ijk.media.widget.Settings; + + +public class IjkVideoView extends FrameLayout implements MediaController.MediaPlayerControl { + private String TAG = "IjkVideoView"; + // settable by the client + private Uri mUri; + private Map mHeaders; + + // 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 int mVideoRotationDegree; + 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; + + /** Subtitle rendering widget overlaid on top of the video. */ + // private RenderingWidget mSubtitleWidget; + + /** + * Listener for changes to subtitle data, used to redraw when needed. + */ + // private RenderingWidget.OnChangedListener mSubtitlesChangedListener; + + private Context mAppContext; + private Settings mSettings; + private IRenderView mRenderView; + private int mVideoSarNum; + private int mVideoSarDen; + + private InfoHudViewHolder mHudViewHolder; + + 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); + } + + // REMOVED: onMeasure + // REMOVED: onInitializeAccessibilityEvent + // REMOVED: onInitializeAccessibilityNodeInfo + // REMOVED: resolveAdjustedSize + + private void initVideoView(Context context) { + mAppContext = context.getApplicationContext(); + mSettings = new Settings(mAppContext); + + initBackground(); + initRenders(); + + mVideoWidth = 0; + mVideoHeight = 0; + // REMOVED: getHolder().addCallback(mSHCallback); + // REMOVED: getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + setFocusable(true); + setFocusableInTouchMode(true); + requestFocus(); + // REMOVED: mPendingSubtitleTracks = new Vector>(); + mCurrentState = STATE_IDLE; + mTargetState = STATE_IDLE; + } + + 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; + renderView.setAspectRatio(mCurrentAspectRatio); + 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); + } + + public void setRender(int render) { + switch (render) { + case RENDER_NONE: + setRenderView(null); + break; + case RENDER_TEXTURE_VIEW: { + TextureRenderView renderView = new TextureRenderView(getContext()); + if (mMediaPlayer != null) { + renderView.getSurfaceHolder().bindToMediaPlayer(mMediaPlayer); + renderView.setVideoSize(mMediaPlayer.getVideoWidth(), mMediaPlayer.getVideoHeight()); + renderView.setVideoSampleAspectRatio(mMediaPlayer.getVideoSarNum(), mMediaPlayer.getVideoSarDen()); + renderView.setAspectRatio(mCurrentAspectRatio); + } + setRenderView(renderView); + break; + } + case RENDER_SURFACE_VIEW: { + SurfaceRenderView renderView = new SurfaceRenderView(getContext()); + setRenderView(renderView); + break; + } + default: + Log.e(TAG, String.format(Locale.getDefault(), "invalid render %d\n", render)); + break; + } + } + + public void setHudView(TableLayout tableLayout) { + mHudViewHolder = new InfoHudViewHolder(getContext(), tableLayout); + } + + /** + * 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 headers) { + mUri = uri; + mHeaders = headers; + mSeekWhenPrepared = 0; + openVideo(); + requestLayout(); + invalidate(); + } + + // REMOVED: addSubtitleSource + // REMOVED: mPendingSubtitleTracks + + public void stopPlayback() { + if (mMediaPlayer != null) { + mMediaPlayer.stop(); + mMediaPlayer.release(); + mMediaPlayer = null; + if (mHudViewHolder != null) + mHudViewHolder.setMediaPlayer(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(mSettings.getPlayer()); + + // TODO: create SubtitleController in MediaPlayer, but we need + // a context for the subtitle renderers + final Context context = getContext(); + // REMOVED: SubtitleController + + // REMOVED: mAudioSession + mMediaPlayer.setOnPreparedListener(mPreparedListener); + mMediaPlayer.setOnVideoSizeChangedListener(mSizeChangedListener); + mMediaPlayer.setOnCompletionListener(mCompletionListener); + mMediaPlayer.setOnErrorListener(mErrorListener); + mMediaPlayer.setOnInfoListener(mInfoListener); + mMediaPlayer.setOnBufferingUpdateListener(mBufferingUpdateListener); + mCurrentBufferPercentage = 0; + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + mMediaPlayer.setDataSource(mAppContext, mUri, mHeaders); + } else { + mMediaPlayer.setDataSource(mUri.toString()); + } + bindSurfaceHolder(mMediaPlayer, mSurfaceHolder); + mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); + mMediaPlayer.setScreenOnWhilePlaying(true); + mMediaPlayer.prepareAsync(); + if (mHudViewHolder != null) + mHudViewHolder.setMediaPlayer(mMediaPlayer); + + // 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 ex) { + Log.w(TAG, "Unable to open content: " + mUri, ex); + mCurrentState = STATE_ERROR; + mTargetState = STATE_ERROR; + mErrorListener.onError(mMediaPlayer, MediaPlayer.MEDIA_ERROR_UNKNOWN, 0); + return; + } catch (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); + return; + } finally { + // REMOVED: mPendingSubtitleTracks.clear(); + } + } + + public void setMediaController(IMediaController controller) { + if (mMediaController != null) { + mMediaController.hide(); + } + mMediaController = controller; + attachMediaController(); + } + + private void attachMediaController() { + if (mMediaPlayer != null && mMediaController != null) { + View anchorView = this.getParent() instanceof View ? + (View) this.getParent() : this; + mMediaController.setAnchorView(anchorView); + mMediaController.setMediaPlayer(this); + 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 (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(); + } + } + if (mOnPreparedListener != null) { + mOnPreparedListener.onPrepared(mMediaPlayer); + } + } + }; + + 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: + mVideoRotationDegree = arg2; + 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; + } + } + + /* Otherwise, pop up an error dialog so the user knows that + * something bad has happened. Only try and pop up the dialog + * if we're attached to a window. When we're going away and no + * longer have a window, don't bother showing the user an error. + */ + if (getWindowToken() != null) { + Resources r = mAppContext.getResources(); + int messageId; + + if (framework_err == MediaPlayer.MEDIA_ERROR_NOT_VALID_FOR_PROGRESSIVE_PLAYBACK) { + messageId = R.string.VideoView_error_text_invalid_progressive_playback; + } else { + messageId = R.string.VideoView_error_text_unknown; + } + + /* new AlertDialog.Builder(getContext()) + .setMessage(messageId) + .setPositiveButton(R.string.VideoView_error_button, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + *//* If we get here, there is no onError listener, so + * at least inform them that the video is over. + *//* + if (mOnCompletionListener != null) { + mOnCompletionListener.onCompletion(mMediaPlayer); + } + } + }) + .setCancelable(false) + .show();*/ + } + return true; + } + }; + + private IMediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener = + new IMediaPlayer.OnBufferingUpdateListener() { + public void onBufferingUpdate(IMediaPlayer mp, int percent) { + mCurrentBufferPercentage = percent; + } + }; + + /** + * 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; + } + + // REMOVED: getAudioSessionId(); + // REMOVED: onAttachedToWindow(); + // REMOVED: onDetachedFromWindow(); + // REMOVED: onLayout(); + // REMOVED: draw(); + // REMOVED: measureAndLayoutSubtitleWidget(); + // REMOVED: setSubtitleWidget(); + // REMOVED: getSubtitleLooper(); + + //------------------------- + // Extend: Aspect Ratio + //------------------------- + + private static final int[] s_allAspectRatio = { + IRenderView.AR_ASPECT_FIT_PARENT, + IRenderView.AR_ASPECT_FILL_PARENT, + IRenderView.AR_ASPECT_WRAP_CONTENT, + // IRenderView.AR_MATCH_PARENT, + IRenderView.AR_16_9_FIT_PARENT, + IRenderView.AR_4_3_FIT_PARENT}; + private int mCurrentAspectRatioIndex = 0; + private int mCurrentAspectRatio = s_allAspectRatio[1]; + + public int toggleAspectRatio() { + mCurrentAspectRatioIndex++; + mCurrentAspectRatioIndex %= s_allAspectRatio.length; + + mCurrentAspectRatio = s_allAspectRatio[mCurrentAspectRatioIndex]; + if (mRenderView != null) + mRenderView.setAspectRatio(mCurrentAspectRatio); + return mCurrentAspectRatio; + } + + public void changeAspectRaito() { + mRenderView.setAspectRatio(s_allAspectRatio[1]); + } + + //------------------------- + // Extend: Render + //------------------------- + public static final int RENDER_NONE = 0; + public static final int RENDER_SURFACE_VIEW = 1; + public static final int RENDER_TEXTURE_VIEW = 2; + + private List mAllRenders = new ArrayList(); + private int mCurrentRenderIndex = 0; + private int mCurrentRender = RENDER_NONE; + + private void initRenders() { + mAllRenders.clear(); + + if (mSettings.getEnableSurfaceView()) + mAllRenders.add(RENDER_SURFACE_VIEW); + if (mSettings.getEnableTextureView() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) + mAllRenders.add(RENDER_TEXTURE_VIEW); + if (mSettings.getEnableNoView()) + mAllRenders.add(RENDER_NONE); + + if (mAllRenders.isEmpty()) + mAllRenders.add(RENDER_SURFACE_VIEW); + mCurrentRender = mAllRenders.get(mCurrentRenderIndex); + setRender(mCurrentRender); + } + + public int toggleRender() { + mCurrentRenderIndex++; + mCurrentRenderIndex %= mAllRenders.size(); + + mCurrentRender = mAllRenders.get(mCurrentRenderIndex); + setRender(mCurrentRender); + return mCurrentRender; + } + + @NonNull + public static String getRenderText(Context context, int render) { + String text; + switch (render) { + case RENDER_NONE: + text = context.getString(R.string.VideoView_render_none); + break; + case RENDER_SURFACE_VIEW: + text = context.getString(R.string.VideoView_render_surface_view); + break; + case RENDER_TEXTURE_VIEW: + text = context.getString(R.string.VideoView_render_texture_view); + break; + default: + text = context.getString(R.string.N_A); + break; + } + return text; + } + + //------------------------- + // Extend: Player + //------------------------- + public int togglePlayer() { + if (mMediaPlayer != null) + mMediaPlayer.release(); + + if (mRenderView != null) + mRenderView.getView().invalidate(); + openVideo(); + return mSettings.getPlayer(); + } + + @NonNull + public static String getPlayerText(Context context, int player) { + String text; + switch (player) { + case Settings.PV_PLAYER__AndroidMediaPlayer: + text = context.getString(R.string.VideoView_player_AndroidMediaPlayer); + break; + case Settings.PV_PLAYER__IjkMediaPlayer: + text = context.getString(R.string.VideoView_player_IjkMediaPlayer); + break; + case Settings.PV_PLAYER__IjkExoMediaPlayer: + text = context.getString(R.string.VideoView_player_IjkExoMediaPlayer); + break; + default: + text = context.getString(R.string.N_A); + break; + } + return text; + } + + public IMediaPlayer createPlayer(int playerType) { + IMediaPlayer mediaPlayer = null; + + switch (playerType) { + case Settings.PV_PLAYER__IjkExoMediaPlayer: { + IjkExoMediaPlayer IjkExoMediaPlayer = new IjkExoMediaPlayer(mAppContext); + mediaPlayer = IjkExoMediaPlayer; + } + break; + case Settings.PV_PLAYER__AndroidMediaPlayer: { + AndroidMediaPlayer androidMediaPlayer = new AndroidMediaPlayer(); + mediaPlayer = androidMediaPlayer; + } + break; + case Settings.PV_PLAYER__IjkMediaPlayer: + default: { + IjkMediaPlayer ijkMediaPlayer = null; + if (mUri != null) { + ijkMediaPlayer = new IjkMediaPlayer(); + ijkMediaPlayer.native_setLogLevel(IjkMediaPlayer.IJK_LOG_DEBUG); + + if (mSettings.getUsingMediaCodec()) { + ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 1); + if (mSettings.getUsingMediaCodecAutoRotate()) { + ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 1); + } else { + ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec-auto-rotate", 0); + } + } else { + ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "mediacodec", 0); + } + + if (mSettings.getUsingOpenSLES()) { + ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 1); + } else { + ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "opensles", 0); + } + + String pixelFormat = mSettings.getPixelFormat(); + if (TextUtils.isEmpty(pixelFormat)) { + ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", IjkMediaPlayer.SDL_FCC_RV32); + } else { + ijkMediaPlayer.setOption(IjkMediaPlayer.OPT_CATEGORY_PLAYER, "overlay-format", pixelFormat); + } + 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; + } + break; + } + + if (mSettings.getEnableDetachedSurfaceTextureView()) { + mediaPlayer = new TextureMediaPlayer(mediaPlayer); + } + + return mediaPlayer; + } + + //------------------------- + // Extend: Background + //------------------------- + + private boolean mEnableBackgroundPlay = false; + + private void initBackground() { + mEnableBackgroundPlay = mSettings.getEnableBackgroundPlay(); + if (mEnableBackgroundPlay) { + MediaPlayerService.intentToStart(getContext()); + mMediaPlayer = MediaPlayerService.getMediaPlayer(); + if (mHudViewHolder != null) + mHudViewHolder.setMediaPlayer(mMediaPlayer); + } + } + + public boolean isBackgroundPlayEnabled() { + return mEnableBackgroundPlay; + } + + public void enterBackground() { + MediaPlayerService.setMediaPlayer(mMediaPlayer); + } + + public void stopBackgroundPlay() { + MediaPlayerService.setMediaPlayer(null); + } + + //------------------------- + // Extend: Background + //------------------------- + public void showMediaInfo() { + if (mMediaPlayer == null) + return; + + int selectedVideoTrack = MediaPlayerCompat.getSelectedTrack(mMediaPlayer, ITrackInfo.MEDIA_TRACK_TYPE_VIDEO); + int selectedAudioTrack = MediaPlayerCompat.getSelectedTrack(mMediaPlayer, ITrackInfo.MEDIA_TRACK_TYPE_AUDIO); + + TableLayoutBinder builder = new TableLayoutBinder(getContext()); + builder.appendSection(R.string.mi_player); + builder.appendRow2(R.string.mi_player, MediaPlayerCompat.getName(mMediaPlayer)); + builder.appendSection(R.string.mi_media); + builder.appendRow2(R.string.mi_resolution, buildResolution(mVideoWidth, mVideoHeight, mVideoSarNum, mVideoSarDen)); + builder.appendRow2(R.string.mi_length, buildTimeMilli(mMediaPlayer.getDuration())); + + ITrackInfo trackInfos[] = mMediaPlayer.getTrackInfo(); + if (trackInfos != null) { + int index = -1; + for (ITrackInfo trackInfo : trackInfos) { + index++; + + int trackType = trackInfo.getTrackType(); + if (index == selectedVideoTrack) { + builder.appendSection(getContext().getString(R.string.mi_stream_fmt1, index) + " " + getContext().getString(R.string.mi__selected_video_track)); + } else if (index == selectedAudioTrack) { + builder.appendSection(getContext().getString(R.string.mi_stream_fmt1, index) + " " + getContext().getString(R.string.mi__selected_audio_track)); + } else { + builder.appendSection(getContext().getString(R.string.mi_stream_fmt1, index)); + } + builder.appendRow2(R.string.mi_type, buildTrackType(trackType)); + builder.appendRow2(R.string.mi_language, buildLanguage(trackInfo.getLanguage())); + + IMediaFormat mediaFormat = trackInfo.getFormat(); + if (mediaFormat == null) { + } else if (mediaFormat instanceof IjkMediaFormat) { + switch (trackType) { + case ITrackInfo.MEDIA_TRACK_TYPE_VIDEO: + builder.appendRow2(R.string.mi_codec, mediaFormat.getString(IjkMediaFormat.KEY_IJK_CODEC_LONG_NAME_UI)); + builder.appendRow2(R.string.mi_profile_level, mediaFormat.getString(IjkMediaFormat.KEY_IJK_CODEC_PROFILE_LEVEL_UI)); + builder.appendRow2(R.string.mi_pixel_format, mediaFormat.getString(IjkMediaFormat.KEY_IJK_CODEC_PIXEL_FORMAT_UI)); + builder.appendRow2(R.string.mi_resolution, mediaFormat.getString(IjkMediaFormat.KEY_IJK_RESOLUTION_UI)); + builder.appendRow2(R.string.mi_frame_rate, mediaFormat.getString(IjkMediaFormat.KEY_IJK_FRAME_RATE_UI)); + builder.appendRow2(R.string.mi_bit_rate, mediaFormat.getString(IjkMediaFormat.KEY_IJK_BIT_RATE_UI)); + break; + case ITrackInfo.MEDIA_TRACK_TYPE_AUDIO: + builder.appendRow2(R.string.mi_codec, mediaFormat.getString(IjkMediaFormat.KEY_IJK_CODEC_LONG_NAME_UI)); + builder.appendRow2(R.string.mi_profile_level, mediaFormat.getString(IjkMediaFormat.KEY_IJK_CODEC_PROFILE_LEVEL_UI)); + builder.appendRow2(R.string.mi_sample_rate, mediaFormat.getString(IjkMediaFormat.KEY_IJK_SAMPLE_RATE_UI)); + builder.appendRow2(R.string.mi_channels, mediaFormat.getString(IjkMediaFormat.KEY_IJK_CHANNEL_UI)); + builder.appendRow2(R.string.mi_bit_rate, mediaFormat.getString(IjkMediaFormat.KEY_IJK_BIT_RATE_UI)); + break; + default: + break; + } + } + } + } + + AlertDialog.Builder adBuilder = builder.buildAlertDialogBuilder(); + adBuilder.setTitle(R.string.media_information); + adBuilder.setNegativeButton(R.string.close, null); + adBuilder.show(); + } + + private String buildResolution(int width, int height, int sarNum, int sarDen) { + StringBuilder sb = new StringBuilder(); + sb.append(width); + sb.append(" x "); + sb.append(height); + + if (sarNum > 1 || sarDen > 1) { + sb.append("["); + sb.append(sarNum); + sb.append(":"); + sb.append(sarDen); + sb.append("]"); + } + + return sb.toString(); + } + + private String buildTimeMilli(long duration) { + long total_seconds = duration / 1000; + long hours = total_seconds / 3600; + long minutes = (total_seconds % 3600) / 60; + long seconds = total_seconds % 60; + if (duration <= 0) { + return "--:--"; + } + if (hours >= 100) { + return String.format(Locale.US, "%d:%02d:%02d", hours, minutes, seconds); + } else if (hours > 0) { + return String.format(Locale.US, "%02d:%02d:%02d", hours, minutes, seconds); + } else { + return String.format(Locale.US, "%02d:%02d", minutes, seconds); + } + } + + private String buildTrackType(int type) { + Context context = getContext(); + switch (type) { + case ITrackInfo.MEDIA_TRACK_TYPE_VIDEO: + return context.getString(R.string.TrackType_video); + case ITrackInfo.MEDIA_TRACK_TYPE_AUDIO: + return context.getString(R.string.TrackType_audio); + case ITrackInfo.MEDIA_TRACK_TYPE_SUBTITLE: + return context.getString(R.string.TrackType_subtitle); + case ITrackInfo.MEDIA_TRACK_TYPE_TIMEDTEXT: + return context.getString(R.string.TrackType_timedtext); + case ITrackInfo.MEDIA_TRACK_TYPE_METADATA: + return context.getString(R.string.TrackType_metadata); + case ITrackInfo.MEDIA_TRACK_TYPE_UNKNOWN: + default: + return context.getString(R.string.TrackType_unknown); + } + } + + private String buildLanguage(String language) { + if (TextUtils.isEmpty(language)) + return "und"; + return language; + } + + public IMediaController getMediaController() { + return mMediaController; + } + + public ITrackInfo[] getTrackInfo() { + if (mMediaPlayer == null) + return null; + + return mMediaPlayer.getTrackInfo(); + } + + public void selectTrack(int stream) { + MediaPlayerCompat.selectTrack(mMediaPlayer, stream); + } + + public void deselectTrack(int stream) { + MediaPlayerCompat.deselectTrack(mMediaPlayer, stream); + } + + public int getSelectedTrack(int trackType) { + return MediaPlayerCompat.getSelectedTrack(mMediaPlayer, trackType); + } +} diff --git a/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/InfoHudViewHolder.java b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/InfoHudViewHolder.java new file mode 100644 index 0000000..2ea6bc4 --- /dev/null +++ b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/InfoHudViewHolder.java @@ -0,0 +1,125 @@ +package tv.danmaku.ijk.media.widget.media; + +import android.content.Context; +import android.os.Handler; +import android.os.Message; +import android.view.View; +import android.widget.TableLayout; + +import java.util.HashMap; +import java.util.Locale; + +import tv.danmaku.ijk.media.player.IMediaPlayer; +import tv.danmaku.ijk.media.player.IjkMediaPlayer; +import tv.danmaku.ijk.media.player.MediaPlayerProxy; +import tv.danmaku.ijk.media.playerLib.R; + +public class InfoHudViewHolder { + private TableLayoutBinder mTableLayoutBinder; + private HashMap mRowMap = new HashMap(); + private IMediaPlayer mMediaPlayer; + + public InfoHudViewHolder(Context context, TableLayout tableLayout) { + mTableLayoutBinder = new TableLayoutBinder(context, tableLayout); + } + + private void appendSection(int nameId) { + mTableLayoutBinder.appendSection(nameId); + } + + private void appendRow(int nameId) { + View rowView = mTableLayoutBinder.appendRow2(nameId, null); + mRowMap.put(nameId, rowView); + } + + private void setRowValue(int id, String value) { + View rowView = mRowMap.get(id); + if (rowView == null) { + rowView = mTableLayoutBinder.appendRow2(id, value); + mRowMap.put(id, rowView); + } else { + mTableLayoutBinder.setValueText(rowView, value); + } + } + + public void setMediaPlayer(IMediaPlayer mp) { + mMediaPlayer = mp; + if (mMediaPlayer != null) { + mHandler.sendEmptyMessageDelayed(MSG_UPDATE_HUD, 500); + } else { + mHandler.removeMessages(MSG_UPDATE_HUD); + } + } + + private static String formatedDurationMilli(long duration) { + if (duration >= 1000) { + return String.format(Locale.US, "%.2f sec", ((float) duration) / 1000); + } else { + return String.format(Locale.US, "%d msec", duration); + } + } + + private static String formatedSize(long bytes) { + if (bytes >= 100 * 1000) { + return String.format(Locale.US, "%.2f MB", ((float) bytes) / 1000 / 1000); + } else if (bytes >= 100) { + return String.format(Locale.US, "%.1f KB", ((float) bytes) / 1000); + } else { + return String.format(Locale.US, "%d B", bytes); + } + } + + private static final int MSG_UPDATE_HUD = 1; + private Handler mHandler = new Handler(new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + switch (msg.what) { + case MSG_UPDATE_HUD: { + InfoHudViewHolder holder = InfoHudViewHolder.this; + IjkMediaPlayer mp = null; + if (mMediaPlayer == null) + break; + if (mMediaPlayer instanceof IjkMediaPlayer) { + mp = (IjkMediaPlayer) mMediaPlayer; + } else if (mMediaPlayer instanceof MediaPlayerProxy) { + MediaPlayerProxy proxy = (MediaPlayerProxy) mMediaPlayer; + IMediaPlayer internal = proxy.getInternalMediaPlayer(); + if (internal != null && internal instanceof IjkMediaPlayer) + mp = (IjkMediaPlayer) internal; + } + if (mp == null) + break; + + int vdec = mp.getVideoDecoder(); + switch (vdec) { + case IjkMediaPlayer.FFP_PROPV_DECODER_AVCODEC: + setRowValue(R.string.vdec, "avcodec"); + break; + case IjkMediaPlayer.FFP_PROPV_DECODER_MEDIACODEC: + setRowValue(R.string.vdec, "MediaCodec"); + break; + default: + setRowValue(R.string.vdec, ""); + break; + } + + float fpsOutput = mp.getVideoOutputFramesPerSecond(); + float fpsDecode = mp.getVideoDecodeFramesPerSecond(); + setRowValue(R.string.fps, String.format(Locale.US, "%.2f / %.2f", fpsDecode, fpsOutput)); + + long videoCachedDuration = mp.getVideoCachedDuration(); + long audioCachedDuration = mp.getAudioCachedDuration(); + long videoCachedBytes = mp.getVideoCachedBytes(); + long audioCachedBytes = mp.getAudioCachedBytes(); + + setRowValue(R.string.v_cache, String.format(Locale.US, "%s, %s", formatedDurationMilli(videoCachedDuration), formatedSize(videoCachedBytes))); + setRowValue(R.string.a_cache, String.format(Locale.US, "%s, %s", formatedDurationMilli(audioCachedDuration), formatedSize(audioCachedBytes))); + + mHandler.removeMessages(MSG_UPDATE_HUD); + mHandler.sendEmptyMessageDelayed(MSG_UPDATE_HUD, 500); + } + } + return true; + } + }); +} diff --git a/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/MeasureHelper.java b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/MeasureHelper.java new file mode 100644 index 0000000..f64ad36 --- /dev/null +++ b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/MeasureHelper.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2015 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.widget.media; + +import android.content.Context; +import android.support.annotation.NonNull; +import android.view.View; + +import java.lang.ref.WeakReference; + +import tv.danmaku.ijk.media.playerLib.R; + + +public final class MeasureHelper { + private WeakReference 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); + } + + 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; + } + + @NonNull + public static String getAspectRatioText(Context context, int aspectRatio) { + String text; + switch (aspectRatio) { + case IRenderView.AR_ASPECT_FIT_PARENT: + text = context.getString(R.string.VideoView_ar_aspect_fit_parent); + break; + case IRenderView.AR_ASPECT_FILL_PARENT: + text = context.getString(R.string.VideoView_ar_aspect_fill_parent); + break; + case IRenderView.AR_ASPECT_WRAP_CONTENT: + text = context.getString(R.string.VideoView_ar_aspect_wrap_content); + break; + case IRenderView.AR_MATCH_PARENT: + text = context.getString(R.string.VideoView_ar_match_parent); + break; + case IRenderView.AR_16_9_FIT_PARENT: + text = context.getString(R.string.VideoView_ar_16_9_fit_parent); + break; + case IRenderView.AR_4_3_FIT_PARENT: + text = context.getString(R.string.VideoView_ar_4_3_fit_parent); + break; + default: + text = context.getString(R.string.N_A); + break; + } + return text; + } +} diff --git a/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/MediaPlayerCompat.java b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/MediaPlayerCompat.java new file mode 100644 index 0000000..f7231a4 --- /dev/null +++ b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/MediaPlayerCompat.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2015 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.widget.media; + +import tv.danmaku.ijk.media.player.IMediaPlayer; +import tv.danmaku.ijk.media.player.IjkMediaPlayer; +import tv.danmaku.ijk.media.player.MediaPlayerProxy; +import tv.danmaku.ijk.media.player.TextureMediaPlayer; + +public class MediaPlayerCompat { + public static String getName(IMediaPlayer mp) { + if (mp == null) { + return "null"; + } else if (mp instanceof TextureMediaPlayer) { + StringBuilder sb = new StringBuilder("TextureMediaPlayer <"); + IMediaPlayer internalMediaPlayer = ((TextureMediaPlayer) mp).getInternalMediaPlayer(); + if (internalMediaPlayer == null) { + sb.append("null>"); + } else { + sb.append(internalMediaPlayer.getClass().getSimpleName()); + sb.append(">"); + } + return sb.toString(); + } else { + return mp.getClass().getSimpleName(); + } + } + + public static IjkMediaPlayer getIjkMediaPlayer(IMediaPlayer mp) { + IjkMediaPlayer ijkMediaPlayer = null; + if (mp == null) { + return null; + } if (mp instanceof IjkMediaPlayer) { + ijkMediaPlayer = (IjkMediaPlayer) mp; + } else if (mp instanceof MediaPlayerProxy && ((MediaPlayerProxy) mp).getInternalMediaPlayer() instanceof IjkMediaPlayer) { + ijkMediaPlayer = (IjkMediaPlayer) ((MediaPlayerProxy) mp).getInternalMediaPlayer(); + } + return ijkMediaPlayer; + } + + public static void selectTrack(IMediaPlayer mp, int stream) { + IjkMediaPlayer ijkMediaPlayer = getIjkMediaPlayer(mp); + if (ijkMediaPlayer == null) + return; + ijkMediaPlayer.selectTrack(stream); + } + + public static void deselectTrack(IMediaPlayer mp, int stream) { + IjkMediaPlayer ijkMediaPlayer = getIjkMediaPlayer(mp); + if (ijkMediaPlayer == null) + return; + ijkMediaPlayer.deselectTrack(stream); + } + + public static int getSelectedTrack(IMediaPlayer mp, int trackType) { + IjkMediaPlayer ijkMediaPlayer = getIjkMediaPlayer(mp); + if (ijkMediaPlayer == null) + return -1; + return ijkMediaPlayer.getSelectedTrack(trackType); + } +} diff --git a/sample/src/main/java/tv/danmaku/ijk/media/widget/OutlineTextView.java b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/OutlineTextView.java similarity index 98% rename from sample/src/main/java/tv/danmaku/ijk/media/widget/OutlineTextView.java rename to IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/OutlineTextView.java index 98379bb..6f16a7d 100755 --- a/sample/src/main/java/tv/danmaku/ijk/media/widget/OutlineTextView.java +++ b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/OutlineTextView.java @@ -15,7 +15,7 @@ * limitations under the License. */ -package tv.danmaku.ijk.media.widget; +package tv.danmaku.ijk.media.widget.media; import android.annotation.SuppressLint; import android.content.Context; @@ -30,7 +30,7 @@ /** * Display text with border, use the same XML attrs as - * {@link android.widget.TextView}, except that {@link OutlineTextView} will + * {@link TextView}, except that {@link OutlineTextView} will * transform the shadow to border */ @SuppressLint("DrawAllocation") diff --git a/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/PlayerMediaController.java b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/PlayerMediaController.java new file mode 100644 index 0000000..2299db7 --- /dev/null +++ b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/PlayerMediaController.java @@ -0,0 +1,577 @@ +package tv.danmaku.ijk.media.widget.media; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.media.AudioManager; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.util.Log; +import android.view.Gravity; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.MediaController; +import android.widget.ProgressBar; +import android.widget.SeekBar; +import android.widget.TextView; + +import java.util.Formatter; +import java.util.Locale; + +import tv.danmaku.ijk.media.playerLib.R; + +/** + * Created by zhangyang on 16/4/5. + */ +public class PlayerMediaController extends FrameLayout { + + private MediaController.MediaPlayerControl mPlayer; + private final Context mContext; + private View mAnchor; + private View mRoot; + private WindowManager mWindowManager; + private Window mWindow; + private View mDecor; + private WindowManager.LayoutParams mDecorLayoutParams; + protected ProgressBar mProgress; + protected TextView mEndTime, mCurrentTime; + private boolean mShowing; + private boolean mDragging; + private int sDefaultTimeout = 5000; + private static final int FADE_OUT = 1; + private static final int SHOW_PROGRESS = 2; + private final boolean mUseFastForward; + private boolean mFromXml; + StringBuilder mFormatBuilder; + Formatter mFormatter; + protected ImageView mPauseButton; + protected static int IC_MEDIA_PAUSE_ID = Resources.getSystem().getIdentifier("ic_media_pause", "drawable", "android"); + protected static int IC_MEDIA_PLAY_ID = Resources.getSystem().getIdentifier("ic_media_play", "drawable", "android"); + + + public PlayerMediaController(Context context, AttributeSet attrs) { + super(context, attrs); + mRoot = this; + mContext = context; + mUseFastForward = true; + mFromXml = true; + } + + @Override + public void onFinishInflate() { + super.onFinishInflate(); + if (mRoot != null) + initControllerView(mRoot); + } + + public PlayerMediaController(Context context, boolean useFastForward) { + super(context); + mContext = context; + mUseFastForward = useFastForward; + initFloatingWindowLayout(); + initFloatingWindow(); + } + + public PlayerMediaController(Context context) { + this(context, true); + } + + private void initFloatingWindow() { + mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); + Dialog dialog = new Dialog(mContext); + mWindow = dialog.getWindow(); + mWindow.setWindowManager(mWindowManager, null, null); + mWindow.requestFeature(Window.FEATURE_NO_TITLE); + mDecor = mWindow.getDecorView(); + mDecor.setOnTouchListener(mTouchListener); + mWindow.setContentView(this); + mWindow.setBackgroundDrawableResource(android.R.color.transparent); + + // While the media controller is up, the volume control keys should + // affect the media stream type + mWindow.setVolumeControlStream(AudioManager.STREAM_MUSIC); + + setFocusable(true); + setFocusableInTouchMode(true); + setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); + requestFocus(); + } + + // Allocate and initialize the static parts of mDecorLayoutParams. Must + // also call updateFloatingWindowLayout() to fill in the dynamic parts + // (y and width) before mDecorLayoutParams can be used. + private void initFloatingWindowLayout() { + mDecorLayoutParams = new WindowManager.LayoutParams(); + WindowManager.LayoutParams p = mDecorLayoutParams; + p.gravity = Gravity.TOP | Gravity.LEFT; + p.height = LayoutParams.WRAP_CONTENT; + p.x = 0; + p.format = PixelFormat.TRANSLUCENT; + p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; + p.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH; + p.token = null; + p.windowAnimations = 0; // android.R.style.DropDownAnimationDown; + } + + // Update the dynamic parts of mDecorLayoutParams + // Must be called with mAnchor != NULL. + private void updateFloatingWindowLayout() { + int[] anchorPos = new int[2]; + mAnchor.getLocationOnScreen(anchorPos); + + // we need to know the size of the controller so we can properly position it + // within its space + mDecor.measure(MeasureSpec.makeMeasureSpec(mAnchor.getWidth(), MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(mAnchor.getHeight(), MeasureSpec.AT_MOST)); + + WindowManager.LayoutParams p = mDecorLayoutParams; + p.width = mAnchor.getWidth(); + p.x = anchorPos[0] + (mAnchor.getWidth() - p.width) / 2; + p.y = anchorPos[1] + mAnchor.getHeight() - mDecor.getMeasuredHeight(); + } + + // This is called whenever mAnchor's layout bound changes + private final OnLayoutChangeListener mLayoutChangeListener = + new OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, + int bottom, int oldLeft, int oldTop, int oldRight, + int oldBottom) { + updateFloatingWindowLayout(); + if (mShowing) { + mWindowManager.updateViewLayout(mDecor, mDecorLayoutParams); + } + } + }; + + private final OnTouchListener mTouchListener = new OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + if (event.getAction() == MotionEvent.ACTION_DOWN) { + if (mShowing) { + hide(); + } + } + return false; + } + }; + + public void setMediaPlayer(MediaController.MediaPlayerControl player) { + mPlayer = player; + updatePausePlay(); + } + + /** + * Set the view that acts as the anchor for the control view. + * This can for example be a VideoView, or your Activity's main view. + * When VideoView calls this method, it will use the VideoView's parent + * as the anchor. + * + * @param view The view to which to anchor the controller when it is visible. + */ + public void setAnchorView(View view) { + if (mAnchor != null) { + mAnchor.removeOnLayoutChangeListener(mLayoutChangeListener); + } + mAnchor = view; + if (mAnchor != null) { + mAnchor.addOnLayoutChangeListener(mLayoutChangeListener); + } + + LayoutParams frameParams = new LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ); + + removeAllViews(); + View v = makeControllerView(); + addView(v, frameParams); + } + + /** + * Create the view that holds the widgets that control playback. + * Derived classes can override this to create their own. + * + * @return The controller view. + * @hide This doesn't work as advertised + */ + protected View makeControllerView() { + mRoot = LayoutInflater.from(mContext).inflate(getControllerLayoutId(), null); + initControllerView(mRoot); + return mRoot; + } + + protected int getControllerLayoutId() { + return R.layout.ijk_media_control; + } + + protected void initControllerView(View v) { + mPauseButton = (ImageView) v.findViewById(R.id.mediacontroller_play_pause); + if (mPauseButton != null) { + mPauseButton.requestFocus(); + mPauseButton.setOnClickListener(mPauseListener); + } + + + mProgress = (ProgressBar) v.findViewById(R.id.mediacontroller_seekbar); + if (mProgress != null) { + if (mProgress instanceof SeekBar) { + SeekBar seeker = (SeekBar) mProgress; + seeker.setOnSeekBarChangeListener(mSeekListener); + } + mProgress.setMax(1000); + } + + mEndTime = (TextView) v.findViewById(R.id.mediacontroller_time_total); + mCurrentTime = (TextView) v.findViewById(R.id.mediacontroller_time_current); + mFormatBuilder = new StringBuilder(); + mFormatter = new Formatter(mFormatBuilder, Locale.getDefault()); + + } + + /** + * Show the controller on screen. It will go away + * automatically after 3 seconds of inactivity. + */ + public void show() { + show(sDefaultTimeout); + } + + /** + * Disable pause or seek buttons if the stream cannot be paused or seeked. + * This requires the control interface to be a MediaPlayerControlExt + */ + private void disableUnsupportedButtons() { + try { + if (mPauseButton != null && !mPlayer.canPause()) { + mPauseButton.setEnabled(false); + } + // TODO What we really should do is add a canSeek to the MediaPlayerControl interface; + // this scheme can break the case when applications want to allow seek through the + // progress bar but disable forward/backward buttons. + // + // However, currently the flags SEEK_BACKWARD_AVAILABLE, SEEK_FORWARD_AVAILABLE, + // and SEEK_AVAILABLE are all (un)set together; as such the aforementioned issue + // shouldn't arise in existing applications. + if (mProgress != null && !mPlayer.canSeekBackward() && !mPlayer.canSeekForward()) { + mProgress.setEnabled(false); + } + } catch (IncompatibleClassChangeError ex) { + // We were given an old version of the interface, that doesn't have + // the canPause/canSeekXYZ methods. This is OK, it just means we + // assume the media can be paused and seeked, and so we don't disable + // the buttons. + } + } + + /** + * Show the controller on screen. It will go away + * automatically after 'timeout' milliseconds of inactivity. + * + * @param timeout The timeout in milliseconds. Use 0 to show + * the controller until hide() is called. + */ + public void show(int timeout) { + if (!mShowing && mAnchor != null) { + setProgress(); + if (mPauseButton != null) { + mPauseButton.requestFocus(); + } + disableUnsupportedButtons(); + updateFloatingWindowLayout(); + mWindowManager.addView(mDecor, mDecorLayoutParams); + mShowing = true; + } + updatePausePlay(); + + // cause the progress bar to be updated even if mShowing + // was already true. This happens, for example, if we're + // paused with the progress bar showing the user hits play. + mHandler.sendEmptyMessage(SHOW_PROGRESS); + + if (timeout != 0) { + mHandler.removeMessages(FADE_OUT); + Message msg = mHandler.obtainMessage(FADE_OUT); + mHandler.sendMessageDelayed(msg, timeout); + } + } + + public boolean isShowing() { + return mShowing; + } + + /** + * Remove the controller from the screen. + */ + public void hide() { + if (mAnchor == null) + return; + + if (mShowing) { + try { + mHandler.removeMessages(SHOW_PROGRESS); + mWindowManager.removeView(mDecor); + } catch (IllegalArgumentException ex) { + Log.w("MediaController", "already removed"); + } + mShowing = false; + } + } + + private boolean needHide = true; + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + int pos; + switch (msg.what) { + case FADE_OUT: + if (needHide) + hide(); + break; + case SHOW_PROGRESS: + pos = setProgress(); + if (!mDragging && mShowing && mPlayer.isPlaying()) { + msg = obtainMessage(SHOW_PROGRESS); + sendMessageDelayed(msg, 1000 - (pos % 1000)); + } + break; + } + } + }; + + public void setNeedHide(boolean needHide) { + this.needHide = needHide; + } + + private String stringForTime(int timeMs) { + int totalSeconds = timeMs / 1000; + + int seconds = totalSeconds % 60; + int minutes = (totalSeconds / 60) % 60; + int hours = totalSeconds / 3600; + + mFormatBuilder.setLength(0); + if (hours > 0) { + return mFormatter.format("%d:%02d:%02d", hours, minutes, seconds).toString(); + } else { + return mFormatter.format("%02d:%02d", minutes, seconds).toString(); + } + } + + private int setProgress() { + if (mPlayer == null || mDragging) { + return 0; + } + int position = mPlayer.getCurrentPosition(); + int duration = mPlayer.getDuration(); + if (mProgress != null) { + if (duration > 0) { + // use long to avoid overflow + long pos = 1000L * position / duration; + mProgress.setProgress((int) pos); + } + int percent = mPlayer.getBufferPercentage(); + mProgress.setSecondaryProgress(percent * 10); + } + + if (mEndTime != null) + mEndTime.setText(stringForTime(duration)); + if (mCurrentTime != null) + mCurrentTime.setText(stringForTime(position)); + updatePausePlay(); + return position; + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + show(0); // show until hide is called + break; + case MotionEvent.ACTION_UP: + show(sDefaultTimeout); // start timeout + break; + case MotionEvent.ACTION_CANCEL: + hide(); + break; + default: + break; + } + return true; + } + + @Override + public boolean onTrackballEvent(MotionEvent ev) { + show(sDefaultTimeout); + return false; + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + int keyCode = event.getKeyCode(); + final boolean uniqueDown = event.getRepeatCount() == 0 + && event.getAction() == KeyEvent.ACTION_DOWN; + if (keyCode == KeyEvent.KEYCODE_HEADSETHOOK + || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE + || keyCode == KeyEvent.KEYCODE_SPACE) { + if (uniqueDown) { + doPauseResume(); + show(sDefaultTimeout); + if (mPauseButton != null) { + mPauseButton.requestFocus(); + } + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_PLAY) { + if (uniqueDown && !mPlayer.isPlaying()) { + mPlayer.start(); + updatePausePlay(); + show(sDefaultTimeout); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_STOP + || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE) { + if (uniqueDown && mPlayer.isPlaying()) { + mPlayer.pause(); + updatePausePlay(); + show(sDefaultTimeout); + } + return true; + } else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN + || keyCode == KeyEvent.KEYCODE_VOLUME_UP + || keyCode == KeyEvent.KEYCODE_VOLUME_MUTE + || keyCode == KeyEvent.KEYCODE_CAMERA) { + // don't show the controls for volume adjustment + return super.dispatchKeyEvent(event); + } else if (keyCode == KeyEvent.KEYCODE_BACK || keyCode == KeyEvent.KEYCODE_MENU) { + if (!needHide) { + ((Activity) getContext()).finish(); + return true; + } + if (uniqueDown) { + hide(); + } + return true; + } + + show(sDefaultTimeout); + return super.dispatchKeyEvent(event); + } + + private final OnClickListener mPauseListener = new OnClickListener() { + @Override + public void onClick(View v) { + doPauseResume(); + show(sDefaultTimeout); + } + }; + + private void updatePausePlay() { + if (mRoot == null || mPauseButton == null) + return; + if (mPlayer.isPlaying()) { + mPauseButton.setImageResource(IC_MEDIA_PAUSE_ID); + } else { + mPauseButton.setImageResource(IC_MEDIA_PLAY_ID); + } + } + + private void doPauseResume() { + if (mPlayer.isPlaying()) { + mPlayer.pause(); + } else { + mPlayer.start(); + } + updatePausePlay(); + } + + // There are two scenarios that can trigger the seekbar listener to trigger: + // + // The first is the user using the touchpad to adjust the posititon of the + // seekbar's thumb. In this case onStartTrackingTouch is called followed by + // a number of onProgressChanged notifications, concluded by onStopTrackingTouch. + // We're setting the field "mDragging" to true for the duration of the dragging + // session to avoid jumps in the position in case of ongoing playback. + // + // The second scenario involves the user operating the scroll ball, in this + // case there WON'T BE onStartTrackingTouch/onStopTrackingTouch notifications, + // we will simply apply the updated position without suspending regular updates. + private final SeekBar.OnSeekBarChangeListener mSeekListener = new SeekBar.OnSeekBarChangeListener() { + @Override + public void onStartTrackingTouch(SeekBar bar) { + show(3600000); + + mDragging = true; + + // By removing these pending progress messages we make sure + // that a) we won't update the progress while the user adjusts + // the seekbar and b) once the user is done dragging the thumb + // we will post one of these messages to the queue again and + // this ensures that there will be exactly one message queued up. + mHandler.removeMessages(SHOW_PROGRESS); + } + + @Override + public void onProgressChanged(SeekBar bar, int progress, boolean fromuser) { + if (!fromuser) { + // We're not interested in programmatically generated changes to + // the progress bar's position. + return; + } + long duration = mPlayer.getDuration(); + long newposition = (duration * progress) / 1000L; + if (mCurrentTime != null) + mCurrentTime.setText(stringForTime((int) newposition)); + } + + @Override + public void onStopTrackingTouch(SeekBar bar) { + long duration = mPlayer.getDuration(); + long newposition = (duration * bar.getProgress()) / 1000L; + mPlayer.seekTo((int) newposition); + mDragging = false; + setProgress(); + updatePausePlay(); + show(sDefaultTimeout); + + // Ensure that progress is properly updated in the future, + // the call to show() does not guarantee this because it is a + // no-op if we are already showing. + mHandler.sendEmptyMessage(SHOW_PROGRESS); + } + }; + + @Override + public void setEnabled(boolean enabled) { + if (mPauseButton != null) { + mPauseButton.setEnabled(enabled); + } + if (mProgress != null) { + mProgress.setEnabled(enabled); + } + disableUnsupportedButtons(); + super.setEnabled(enabled); + } + + @Override + public CharSequence getAccessibilityClassName() { + return MediaController.class.getName(); + } + + + public void setDefaultTimeout(int sDefaultTimeout) { + this.sDefaultTimeout = sDefaultTimeout; + } +} \ No newline at end of file diff --git a/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/SurfaceRenderView.java b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/SurfaceRenderView.java new file mode 100644 index 0000000..8a2c3bc --- /dev/null +++ b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/SurfaceRenderView.java @@ -0,0 +1,287 @@ +/* + * Copyright (C) 2015 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.widget.media; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +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 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 IRenderView.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 mWeakSurfaceView; + private Map mRenderCallbackMap = new ConcurrentHashMap(); + + public SurfaceCallback(@NonNull SurfaceRenderView surfaceView) { + mWeakSurfaceView = new WeakReference(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()); + } + } +} diff --git a/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/TableLayoutBinder.java b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/TableLayoutBinder.java new file mode 100644 index 0000000..16b7c18 --- /dev/null +++ b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/TableLayoutBinder.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2015 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.widget.media; + +import android.content.Context; +import android.support.v7.app.AlertDialog; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TableLayout; +import android.widget.TextView; + +import tv.danmaku.ijk.media.playerLib.R; + + +public class TableLayoutBinder { + private Context mContext; + public ViewGroup mTableView; + public TableLayout mTableLayout; + + public TableLayoutBinder(Context context) { + this(context, R.layout.table_media_info); + } + + public TableLayoutBinder(Context context, int layoutResourceId) { + mContext = context; + mTableView = (ViewGroup) LayoutInflater.from(mContext).inflate(layoutResourceId, null); + mTableLayout = (TableLayout) mTableView.findViewById(R.id.table); + } + + public TableLayoutBinder(Context context, TableLayout tableLayout) { + mContext = context; + mTableView = tableLayout; + mTableLayout = tableLayout; + } + + public View appendRow1(String name, String value) { + return appendRow(R.layout.table_media_info_row1, name, value); + } + + public View appendRow1(int nameId, String value) { + return appendRow1(mContext.getString(nameId), value); + } + + public View appendRow2(String name, String value) { + return appendRow(R.layout.table_media_info_row2, name, value); + } + + public View appendRow2(int nameId, String value) { + return appendRow2(mContext.getString(nameId), value); + } + + public View appendSection(String name) { + return appendRow(R.layout.table_media_info_section, name, null); + } + + public View appendSection(int nameId) { + return appendSection(mContext.getString(nameId)); + } + + public View appendRow(int layoutId, String name, String value) { + ViewGroup rowView = (ViewGroup) LayoutInflater.from(mContext).inflate(layoutId, mTableLayout, false); + setNameValueText(rowView, name, value); + + mTableLayout.addView(rowView); + return rowView; + } + + public ViewHolder obtainViewHolder(View rowView) { + ViewHolder viewHolder = (ViewHolder) rowView.getTag(); + if (viewHolder == null) { + viewHolder = new ViewHolder(); + viewHolder.mNameTextView = (TextView) rowView.findViewById(R.id.name); + viewHolder.mValueTextView = (TextView) rowView.findViewById(R.id.value); + rowView.setTag(viewHolder); + } + return viewHolder; + } + + public void setNameValueText(View rowView, String name, String value) { + ViewHolder viewHolder = obtainViewHolder(rowView); + viewHolder.setName(name); + viewHolder.setValue(value); + } + + public void setValueText(View rowView, String value) { + ViewHolder viewHolder = obtainViewHolder(rowView); + viewHolder.setValue(value); + } + + public ViewGroup buildLayout() { + return mTableView; + } + + public AlertDialog.Builder buildAlertDialogBuilder() { + AlertDialog.Builder dlgBuilder = new AlertDialog.Builder(mContext); + dlgBuilder.setView(buildLayout()); + return dlgBuilder; + } + + private static class ViewHolder { + public TextView mNameTextView; + public TextView mValueTextView; + + public void setName(String name) { + if (mNameTextView != null) { + mNameTextView.setText(name); + } + } + + public void setValue(String value) { + if (mValueTextView != null) { + mValueTextView.setText(value); + } + } + } +} diff --git a/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/TextureRenderView.java b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/TextureRenderView.java new file mode 100644 index 0000000..66c37cd --- /dev/null +++ b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/media/TextureRenderView.java @@ -0,0 +1,370 @@ +/* + * Copyright (C) 2015 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.widget.media; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.SurfaceTexture; +import android.os.Build; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +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 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 IRenderView.ISurfaceHolder getSurfaceHolder() { + return new InternalSurfaceHolder(this, mSurfaceCallback.mSurfaceTexture, mSurfaceCallback); + } + + private static final class InternalSurfaceHolder implements IRenderView.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 mWeakRenderView; + private Map mRenderCallbackMap = new ConcurrentHashMap(); + + public SurfaceCallback(@NonNull TextureRenderView renderView) { + mWeakRenderView = new WeakReference(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"); + return; + } 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()); + } +} diff --git a/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/preference/IjkListPreference.java b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/preference/IjkListPreference.java new file mode 100644 index 0000000..d5c1cd5 --- /dev/null +++ b/IjkplayerLib/src/main/java/tv/danmaku/ijk/media/widget/preference/IjkListPreference.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2015 Zhang Rui + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package tv.danmaku.ijk.media.widget.preference; + +import android.content.Context; +import android.content.res.TypedArray; +import android.support.v7.preference.ListPreference; +import android.text.TextUtils; +import android.util.AttributeSet; + +import tv.danmaku.ijk.media.playerLib.R; + + +public class IjkListPreference extends ListPreference { + private CharSequence[] mEntrySummaries; + + public IjkListPreference(Context context) { + super(context); + initPreference(context, null); + } + + public IjkListPreference(Context context, AttributeSet attrs) { + super(context, attrs); + initPreference(context, attrs); + } + + public IjkListPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initPreference(context, attrs); + } + + public IjkListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + initPreference(context, attrs); + } + + private final void initPreference(Context context, AttributeSet attrs) { + TypedArray a = context.obtainStyledAttributes(attrs, + R.styleable.IjkListPreference, 0, 0); + if (a == null) + return; + + mEntrySummaries = a + .getTextArray(R.styleable.IjkListPreference_entrySummaries); + + a.recycle(); + } + + @Override + protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { + super.onSetInitialValue(restoreValue, defaultValue); + syncSummary(); + } + + @Override + public void setValue(String value) { + super.setValue(value); + syncSummary(); + } + + @Override + public void setValueIndex(int index) { + super.setValueIndex(index); + syncSummary(); + } + + public int getEntryIndex() { + CharSequence[] entryValues = getEntryValues(); + CharSequence value = getValue(); + + if (entryValues == null || value == null) { + return -1; + } + + for (int i = 0; i < entryValues.length; ++i) { + if (TextUtils.equals(value, entryValues[i])) { + return i; + } + } + + return -1; + } + + // ----- summary -------------------- + public void setEntrySummaries(Context context, int resId) { + setEntrySummaries(context.getResources().getTextArray(resId)); + } + + public void setEntrySummaries(CharSequence[] entrySummaries) { + mEntrySummaries = entrySummaries; + notifyChanged(); + } + + public CharSequence[] getEntrySummaries() { + return mEntrySummaries; + } + + private void syncSummary() { + int index = getEntryIndex(); + if (index < 0) + return; + + if (mEntrySummaries != null && index < mEntrySummaries.length) { + setSummary(mEntrySummaries[index]); + } else { + setSummary(getEntries()[index]); + } + } +} diff --git a/sample/src/main/jniLibs/armeabi-v7a/libijkffmpeg.so b/IjkplayerLib/src/main/jniLibs/armeabi-v7a/libijkffmpeg.so similarity index 58% rename from sample/src/main/jniLibs/armeabi-v7a/libijkffmpeg.so rename to IjkplayerLib/src/main/jniLibs/armeabi-v7a/libijkffmpeg.so index 951f5ed..5efbf37 100755 Binary files a/sample/src/main/jniLibs/armeabi-v7a/libijkffmpeg.so and b/IjkplayerLib/src/main/jniLibs/armeabi-v7a/libijkffmpeg.so differ diff --git a/IjkplayerLib/src/main/jniLibs/armeabi-v7a/libijkplayer.so b/IjkplayerLib/src/main/jniLibs/armeabi-v7a/libijkplayer.so new file mode 100755 index 0000000..b7723eb Binary files /dev/null and b/IjkplayerLib/src/main/jniLibs/armeabi-v7a/libijkplayer.so differ diff --git a/IjkplayerLib/src/main/jniLibs/armeabi-v7a/libijksdl.so b/IjkplayerLib/src/main/jniLibs/armeabi-v7a/libijksdl.so new file mode 100755 index 0000000..98adbbc Binary files /dev/null and b/IjkplayerLib/src/main/jniLibs/armeabi-v7a/libijksdl.so differ diff --git a/sample/src/main/jniLibs/armeabi/libijkffmpeg.so b/IjkplayerLib/src/main/jniLibs/armeabi/libijkffmpeg.so similarity index 64% rename from sample/src/main/jniLibs/armeabi/libijkffmpeg.so rename to IjkplayerLib/src/main/jniLibs/armeabi/libijkffmpeg.so index d37b652..e780eed 100755 Binary files a/sample/src/main/jniLibs/armeabi/libijkffmpeg.so and b/IjkplayerLib/src/main/jniLibs/armeabi/libijkffmpeg.so differ diff --git a/IjkplayerLib/src/main/jniLibs/armeabi/libijkplayer.so b/IjkplayerLib/src/main/jniLibs/armeabi/libijkplayer.so new file mode 100755 index 0000000..c64a1d3 Binary files /dev/null and b/IjkplayerLib/src/main/jniLibs/armeabi/libijkplayer.so differ diff --git a/IjkplayerLib/src/main/jniLibs/armeabi/libijksdl.so b/IjkplayerLib/src/main/jniLibs/armeabi/libijksdl.so new file mode 100755 index 0000000..66b3d53 Binary files /dev/null and b/IjkplayerLib/src/main/jniLibs/armeabi/libijksdl.so differ diff --git a/sample/src/main/jniLibs/x86/libijkffmpeg.so b/IjkplayerLib/src/main/jniLibs/x86/libijkffmpeg.so similarity index 58% rename from sample/src/main/jniLibs/x86/libijkffmpeg.so rename to IjkplayerLib/src/main/jniLibs/x86/libijkffmpeg.so index bf87655..51099ca 100755 Binary files a/sample/src/main/jniLibs/x86/libijkffmpeg.so and b/IjkplayerLib/src/main/jniLibs/x86/libijkffmpeg.so differ diff --git a/IjkplayerLib/src/main/jniLibs/x86/libijkplayer.so b/IjkplayerLib/src/main/jniLibs/x86/libijkplayer.so new file mode 100755 index 0000000..4865417 Binary files /dev/null and b/IjkplayerLib/src/main/jniLibs/x86/libijkplayer.so differ diff --git a/IjkplayerLib/src/main/jniLibs/x86/libijksdl.so b/IjkplayerLib/src/main/jniLibs/x86/libijksdl.so new file mode 100755 index 0000000..3d66311 Binary files /dev/null and b/IjkplayerLib/src/main/jniLibs/x86/libijksdl.so differ diff --git a/IjkplayerLib/src/main/jniLibs/x86_64/libijkffmpeg.so b/IjkplayerLib/src/main/jniLibs/x86_64/libijkffmpeg.so new file mode 100755 index 0000000..e5864b4 Binary files /dev/null and b/IjkplayerLib/src/main/jniLibs/x86_64/libijkffmpeg.so differ diff --git a/IjkplayerLib/src/main/jniLibs/x86_64/libijkplayer.so b/IjkplayerLib/src/main/jniLibs/x86_64/libijkplayer.so new file mode 100755 index 0000000..0054603 Binary files /dev/null and b/IjkplayerLib/src/main/jniLibs/x86_64/libijkplayer.so differ diff --git a/IjkplayerLib/src/main/jniLibs/x86_64/libijksdl.so b/IjkplayerLib/src/main/jniLibs/x86_64/libijksdl.so new file mode 100755 index 0000000..826af92 Binary files /dev/null and b/IjkplayerLib/src/main/jniLibs/x86_64/libijksdl.so differ diff --git a/IjkplayerLib/src/main/res/drawable-hdpi/ic_action_dark_aspect_ratio.png b/IjkplayerLib/src/main/res/drawable-hdpi/ic_action_dark_aspect_ratio.png new file mode 100644 index 0000000..24dc109 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-hdpi/ic_action_dark_aspect_ratio.png differ diff --git a/IjkplayerLib/src/main/res/drawable-hdpi/ic_action_dark_filter.png b/IjkplayerLib/src/main/res/drawable-hdpi/ic_action_dark_filter.png new file mode 100644 index 0000000..b718839 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-hdpi/ic_action_dark_filter.png differ diff --git a/IjkplayerLib/src/main/res/drawable-hdpi/ic_action_dark_settings.png b/IjkplayerLib/src/main/res/drawable-hdpi/ic_action_dark_settings.png new file mode 100644 index 0000000..21c2504 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-hdpi/ic_action_dark_settings.png differ diff --git a/IjkplayerLib/src/main/res/drawable-hdpi/ic_theme_description.png b/IjkplayerLib/src/main/res/drawable-hdpi/ic_theme_description.png new file mode 100644 index 0000000..ed7120e Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-hdpi/ic_theme_description.png differ diff --git a/IjkplayerLib/src/main/res/drawable-hdpi/ic_theme_folder.png b/IjkplayerLib/src/main/res/drawable-hdpi/ic_theme_folder.png new file mode 100644 index 0000000..ac105ad Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-hdpi/ic_theme_folder.png differ diff --git a/IjkplayerLib/src/main/res/drawable-hdpi/ic_theme_play_arrow.png b/IjkplayerLib/src/main/res/drawable-hdpi/ic_theme_play_arrow.png new file mode 100644 index 0000000..65128aa Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-hdpi/ic_theme_play_arrow.png differ diff --git a/IjkplayerLib/src/main/res/drawable-mdpi/ic_action_dark_aspect_ratio.png b/IjkplayerLib/src/main/res/drawable-mdpi/ic_action_dark_aspect_ratio.png new file mode 100644 index 0000000..aee6445 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-mdpi/ic_action_dark_aspect_ratio.png differ diff --git a/IjkplayerLib/src/main/res/drawable-mdpi/ic_action_dark_filter.png b/IjkplayerLib/src/main/res/drawable-mdpi/ic_action_dark_filter.png new file mode 100644 index 0000000..2e84926 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-mdpi/ic_action_dark_filter.png differ diff --git a/IjkplayerLib/src/main/res/drawable-mdpi/ic_action_dark_settings.png b/IjkplayerLib/src/main/res/drawable-mdpi/ic_action_dark_settings.png new file mode 100644 index 0000000..5462003 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-mdpi/ic_action_dark_settings.png differ diff --git a/IjkplayerLib/src/main/res/drawable-mdpi/ic_theme_description.png b/IjkplayerLib/src/main/res/drawable-mdpi/ic_theme_description.png new file mode 100644 index 0000000..13d665c Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-mdpi/ic_theme_description.png differ diff --git a/IjkplayerLib/src/main/res/drawable-mdpi/ic_theme_folder.png b/IjkplayerLib/src/main/res/drawable-mdpi/ic_theme_folder.png new file mode 100644 index 0000000..889d2eb Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-mdpi/ic_theme_folder.png differ diff --git a/IjkplayerLib/src/main/res/drawable-mdpi/ic_theme_play_arrow.png b/IjkplayerLib/src/main/res/drawable-mdpi/ic_theme_play_arrow.png new file mode 100644 index 0000000..1f86eee Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-mdpi/ic_theme_play_arrow.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xhdpi/ic_action_dark_aspect_ratio.png b/IjkplayerLib/src/main/res/drawable-xhdpi/ic_action_dark_aspect_ratio.png new file mode 100644 index 0000000..e2c7044 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xhdpi/ic_action_dark_aspect_ratio.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xhdpi/ic_action_dark_filter.png b/IjkplayerLib/src/main/res/drawable-xhdpi/ic_action_dark_filter.png new file mode 100644 index 0000000..64f91d6 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xhdpi/ic_action_dark_filter.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xhdpi/ic_action_dark_settings.png b/IjkplayerLib/src/main/res/drawable-xhdpi/ic_action_dark_settings.png new file mode 100644 index 0000000..085e1d2 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xhdpi/ic_action_dark_settings.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xhdpi/ic_theme_description.png b/IjkplayerLib/src/main/res/drawable-xhdpi/ic_theme_description.png new file mode 100644 index 0000000..b378faf Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xhdpi/ic_theme_description.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xhdpi/ic_theme_folder.png b/IjkplayerLib/src/main/res/drawable-xhdpi/ic_theme_folder.png new file mode 100644 index 0000000..57ab17e Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xhdpi/ic_theme_folder.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xhdpi/ic_theme_play_arrow.png b/IjkplayerLib/src/main/res/drawable-xhdpi/ic_theme_play_arrow.png new file mode 100644 index 0000000..2414af6 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xhdpi/ic_theme_play_arrow.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_action_dark_aspect_ratio.png b/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_action_dark_aspect_ratio.png new file mode 100644 index 0000000..1c3dce7 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_action_dark_aspect_ratio.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_action_dark_filter.png b/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_action_dark_filter.png new file mode 100644 index 0000000..e0960c3 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_action_dark_filter.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_action_dark_settings.png b/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_action_dark_settings.png new file mode 100644 index 0000000..4f04891 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_action_dark_settings.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_theme_description.png b/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_theme_description.png new file mode 100644 index 0000000..fdb98ad Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_theme_description.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_theme_folder.png b/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_theme_folder.png new file mode 100644 index 0000000..4e4e3f3 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_theme_folder.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_theme_play_arrow.png b/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_theme_play_arrow.png new file mode 100644 index 0000000..ead95af Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xxhdpi/ic_theme_play_arrow.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xxhdpi/live_room_audio.png b/IjkplayerLib/src/main/res/drawable-xxhdpi/live_room_audio.png new file mode 100644 index 0000000..152d7f8 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xxhdpi/live_room_audio.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xxhdpi/live_room_seek_bar_point.png b/IjkplayerLib/src/main/res/drawable-xxhdpi/live_room_seek_bar_point.png new file mode 100644 index 0000000..ba52fce Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xxhdpi/live_room_seek_bar_point.png differ diff --git a/sample/src/main/res/drawable-xhdpi/scrubber_control_disabled_holo.png b/IjkplayerLib/src/main/res/drawable-xxhdpi/scrubber_control_disabled_holo.png similarity index 100% rename from sample/src/main/res/drawable-xhdpi/scrubber_control_disabled_holo.png rename to IjkplayerLib/src/main/res/drawable-xxhdpi/scrubber_control_disabled_holo.png diff --git a/sample/src/main/res/drawable-xhdpi/scrubber_control_focused_holo.png b/IjkplayerLib/src/main/res/drawable-xxhdpi/scrubber_control_focused_holo.png similarity index 100% rename from sample/src/main/res/drawable-xhdpi/scrubber_control_focused_holo.png rename to IjkplayerLib/src/main/res/drawable-xxhdpi/scrubber_control_focused_holo.png diff --git a/sample/src/main/res/drawable-xhdpi/scrubber_control_normal_holo.png b/IjkplayerLib/src/main/res/drawable-xxhdpi/scrubber_control_normal_holo.png similarity index 100% rename from sample/src/main/res/drawable-xhdpi/scrubber_control_normal_holo.png rename to IjkplayerLib/src/main/res/drawable-xxhdpi/scrubber_control_normal_holo.png diff --git a/sample/src/main/res/drawable-xhdpi/scrubber_control_pressed_holo.png b/IjkplayerLib/src/main/res/drawable-xxhdpi/scrubber_control_pressed_holo.png similarity index 100% rename from sample/src/main/res/drawable-xhdpi/scrubber_control_pressed_holo.png rename to IjkplayerLib/src/main/res/drawable-xxhdpi/scrubber_control_pressed_holo.png diff --git a/sample/src/main/res/drawable-xhdpi/scrubber_primary_holo.9.png b/IjkplayerLib/src/main/res/drawable-xxhdpi/scrubber_primary_holo.9.png similarity index 100% rename from sample/src/main/res/drawable-xhdpi/scrubber_primary_holo.9.png rename to IjkplayerLib/src/main/res/drawable-xxhdpi/scrubber_primary_holo.9.png diff --git a/sample/src/main/res/drawable-xhdpi/scrubber_secondary_holo.9.png b/IjkplayerLib/src/main/res/drawable-xxhdpi/scrubber_secondary_holo.9.png similarity index 100% rename from sample/src/main/res/drawable-xhdpi/scrubber_secondary_holo.9.png rename to IjkplayerLib/src/main/res/drawable-xxhdpi/scrubber_secondary_holo.9.png diff --git a/sample/src/main/res/drawable-xhdpi/scrubber_track_holo_dark.9.png b/IjkplayerLib/src/main/res/drawable-xxhdpi/scrubber_track_holo_dark.9.png similarity index 100% rename from sample/src/main/res/drawable-xhdpi/scrubber_track_holo_dark.9.png rename to IjkplayerLib/src/main/res/drawable-xxhdpi/scrubber_track_holo_dark.9.png diff --git a/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_action_dark_aspect_ratio.png b/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_action_dark_aspect_ratio.png new file mode 100644 index 0000000..4baf9ae Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_action_dark_aspect_ratio.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_action_dark_filter.png b/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_action_dark_filter.png new file mode 100644 index 0000000..e2a3b5b Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_action_dark_filter.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_action_dark_settings.png b/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_action_dark_settings.png new file mode 100644 index 0000000..335c367 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_action_dark_settings.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_theme_description.png b/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_theme_description.png new file mode 100644 index 0000000..1f3e53a Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_theme_description.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_theme_folder.png b/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_theme_folder.png new file mode 100644 index 0000000..f5f4592 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_theme_folder.png differ diff --git a/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_theme_play_arrow.png b/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_theme_play_arrow.png new file mode 100644 index 0000000..1055e84 Binary files /dev/null and b/IjkplayerLib/src/main/res/drawable-xxxhdpi/ic_theme_play_arrow.png differ diff --git a/sample/src/main/res/drawable/scrubber_control_selector_holo.xml b/IjkplayerLib/src/main/res/drawable/scrubber_control_selector_holo.xml similarity index 98% rename from sample/src/main/res/drawable/scrubber_control_selector_holo.xml rename to IjkplayerLib/src/main/res/drawable/scrubber_control_selector_holo.xml index f64429d..716e608 100755 --- a/sample/src/main/res/drawable/scrubber_control_selector_holo.xml +++ b/IjkplayerLib/src/main/res/drawable/scrubber_control_selector_holo.xml @@ -1,19 +1,19 @@ - - - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/drawable/scrubber_progress_horizontal_holo_dark.xml b/IjkplayerLib/src/main/res/drawable/scrubber_progress_horizontal_holo_dark.xml similarity index 97% rename from sample/src/main/res/drawable/scrubber_progress_horizontal_holo_dark.xml rename to IjkplayerLib/src/main/res/drawable/scrubber_progress_horizontal_holo_dark.xml index 5134258..0a8be8e 100755 --- a/sample/src/main/res/drawable/scrubber_progress_horizontal_holo_dark.xml +++ b/IjkplayerLib/src/main/res/drawable/scrubber_progress_horizontal_holo_dark.xml @@ -1,28 +1,28 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + \ No newline at end of file diff --git a/IjkplayerLib/src/main/res/layout/ijk_media_control.xml b/IjkplayerLib/src/main/res/layout/ijk_media_control.xml new file mode 100644 index 0000000..3db6983 --- /dev/null +++ b/IjkplayerLib/src/main/res/layout/ijk_media_control.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + +