diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 99ef2bf2..00000000 Binary files a/.DS_Store and /dev/null differ diff --git a/.gitignore b/.gitignore index 81d4b505..ed7ba8ae 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,5 @@ captures/ # Keystore files *.jks +.DS_Store +*/.DS_Store diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 13ed4651..f15f0386 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -76,6 +76,8 @@ android:name=".ui.TestPlayActivity" android:exported="true"/> + \ No newline at end of file diff --git a/app/src/main/java/com/kk/taurus/avplayer/HomeActivity.java b/app/src/main/java/com/kk/taurus/avplayer/HomeActivity.java index 139cd0f3..acbda3f6 100644 --- a/app/src/main/java/com/kk/taurus/avplayer/HomeActivity.java +++ b/app/src/main/java/com/kk/taurus/avplayer/HomeActivity.java @@ -13,6 +13,7 @@ import com.kk.taurus.avplayer.ui.InputUrlPlayActivity; import com.kk.taurus.avplayer.ui.MultiPlayActivity; +import com.kk.taurus.avplayer.ui.TvBaseVideoViewActivity; import com.kk.taurus.avplayer.ui.ViewPagerPlayActivity; import com.kk.taurus.avplayer.ui.listplay.MultiListActivity; import com.kk.taurus.avplayer.ui.ShareAnimationActivityA; @@ -96,6 +97,10 @@ public void multiVideoPlay(View view){ intentTo(MultiPlayActivity.class); } + public void tvPlayUserBaseVideoView(View view){ + intentTo(TvBaseVideoViewActivity.class); + } + public void viewPagerPlay(View view){ intentTo(ViewPagerPlayActivity.class); } diff --git a/app/src/main/java/com/kk/taurus/avplayer/cover/BaseKeyEventCover.java b/app/src/main/java/com/kk/taurus/avplayer/cover/BaseKeyEventCover.java new file mode 100644 index 00000000..ce33c027 --- /dev/null +++ b/app/src/main/java/com/kk/taurus/avplayer/cover/BaseKeyEventCover.java @@ -0,0 +1,100 @@ +package com.kk.taurus.avplayer.cover; + +import android.content.Context; +import android.os.Bundle; +import android.view.KeyEvent; + +import com.kk.taurus.playerbase.event.OnPlayerEventListener; +import com.kk.taurus.playerbase.player.IPlayer; +import com.kk.taurus.playerbase.receiver.BaseCover; +import com.kk.taurus.playerbase.touch.OnKeyEventListener; + +public abstract class BaseKeyEventCover extends BaseCover implements OnKeyEventListener { + public BaseKeyEventCover(Context context) { + super(context); + } + + @Override + public void onPlayerEvent(int eventCode, Bundle bundle) { + switch (eventCode) { + case OnPlayerEventListener.PLAYER_EVENT_ON_PREPARED: + + break; + } + } + + @Override + public void onKeyDownInCover(int keyCode, KeyEvent event) { + dispatchKeyEventInner(true, event); + } + + @Override + public void onKeyUpInCover(int keyCode, KeyEvent event) { + dispatchKeyEventInner(false, event); + } + + private void dispatchKeyEventInner(boolean down, KeyEvent event){ + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + onBackKeyEvent(down, event); + } + if (!isActiveState()) { + return; + } + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_MENU: + onMenuKeyEvent(down, event); + break; + case KeyEvent.KEYCODE_DPAD_CENTER: + case KeyEvent.KEYCODE_ENTER: + onCenterKeyEvent(down, event); + break; + case KeyEvent.KEYCODE_DPAD_UP: + onUpKeyEvent(down, event); + break; + case KeyEvent.KEYCODE_DPAD_DOWN: + onDownKeyEvent(down, event); + break; + case KeyEvent.KEYCODE_DPAD_LEFT: + onLeftKeyEvent(down, event); + break; + case KeyEvent.KEYCODE_DPAD_RIGHT: + onRightKeyEvent(down, event); + break; + default: + break; + } + } + + protected void onCenterKeyEvent(boolean down, KeyEvent event){ + + } + + protected void onUpKeyEvent(boolean down, KeyEvent event){ + + } + + protected void onDownKeyEvent(boolean down, KeyEvent event){ + + } + + protected void onLeftKeyEvent(boolean down, KeyEvent event){ + + } + + protected void onRightKeyEvent(boolean down, KeyEvent event){ + + } + + protected void onBackKeyEvent(boolean down, KeyEvent event){ + + } + + protected void onMenuKeyEvent(boolean down, KeyEvent event){ + + } + + protected boolean isActiveState(){ + int state = getPlayerStateGetter().getState(); + return state >= IPlayer.STATE_PREPARED && state <= IPlayer.STATE_PAUSED; + } +} diff --git a/app/src/main/java/com/kk/taurus/avplayer/cover/TvControllerCover.java b/app/src/main/java/com/kk/taurus/avplayer/cover/TvControllerCover.java new file mode 100644 index 00000000..b86cab4d --- /dev/null +++ b/app/src/main/java/com/kk/taurus/avplayer/cover/TvControllerCover.java @@ -0,0 +1,258 @@ +package com.kk.taurus.avplayer.cover; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.os.Handler; +import android.text.TextUtils; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.widget.ImageView; +import android.widget.SeekBar; +import android.widget.TextView; + +import com.kk.taurus.avplayer.R; +import com.kk.taurus.avplayer.play.DataInter; +import com.kk.taurus.playerbase.entity.DataSource; +import com.kk.taurus.playerbase.event.BundlePool; +import com.kk.taurus.playerbase.event.EventKey; +import com.kk.taurus.playerbase.event.OnPlayerEventListener; +import com.kk.taurus.playerbase.player.IPlayer; +import com.kk.taurus.playerbase.player.OnTimerUpdateListener; +import com.kk.taurus.playerbase.utils.TimeUtil; + +public class TvControllerCover extends BaseKeyEventCover implements OnTimerUpdateListener { + private static final String TAG = "TvControllerCover"; + private TextView mTopTitle, mCurrTime, mTotalTime; + private ImageView mStateIcon; + private SeekBar mSeekBar, mBottomSeekBar; + private View mBottomContainer; + + private int mBufferPercentage; + + private int mSeekProgress = -1; + private String mTimeFormat; + + private boolean mTimerUpdateProgressEnable = false; + + private Handler mHandler = new Handler(); + public TvControllerCover(Context context) { + super(context); + } + + @Override + protected View onCreateCoverView(Context context) { + View view = View.inflate(context, R.layout.layout_controller_cover, null); + mTopTitle = view.findViewById(R.id.cover_player_controller_text_view_video_title); + mStateIcon = view.findViewById(R.id.cover_player_controller_image_view_play_state); + mCurrTime = view.findViewById(R.id.cover_player_controller_text_view_curr_time); + mTotalTime = view.findViewById(R.id.cover_player_controller_text_view_total_time); + mSeekBar = view.findViewById(R.id.cover_player_controller_seek_bar); + mBottomSeekBar = view.findViewById(R.id.cover_bottom_seek_bar); + mBottomContainer = view.findViewById(R.id.cover_player_controller_bottom_container); + view.findViewById(R.id.cover_player_controller_image_view_switch_screen).setVisibility(View.GONE); + return view; + } + + @Override + public void onPlayerEvent(int eventCode, Bundle bundle) { + switch (eventCode){ + case OnPlayerEventListener.PLAYER_EVENT_ON_DATA_SOURCE_SET: + mBufferPercentage = 0; + mTimeFormat = null; + updateUI(0, 0); + setBottomSeekBarState(true); + DataSource data = (DataSource) bundle.getSerializable(EventKey.SERIALIZABLE_DATA); + getGroupValue().putObject(DataInter.Key.KEY_DATA_SOURCE, data); + setTitle(data); + break; + case OnPlayerEventListener.PLAYER_EVENT_ON_STATUS_CHANGE: + int status = bundle.getInt(EventKey.INT_DATA); + if(status== IPlayer.STATE_PAUSED){ + mStateIcon.setSelected(true); + }else if(status==IPlayer.STATE_STARTED){ + mStateIcon.setSelected(false); + } + break; + case OnPlayerEventListener.PLAYER_EVENT_ON_PREPARED://某些机型没有render start这个消息 + case OnPlayerEventListener.PLAYER_EVENT_ON_VIDEO_RENDER_START: + case OnPlayerEventListener.PLAYER_EVENT_ON_SEEK_COMPLETE: + mTimerUpdateProgressEnable = true; + break; + } + } + + @Override + public void onErrorEvent(int eventCode, Bundle bundle) { + + } + + @Override + public void onReceiverEvent(int eventCode, Bundle bundle) { + + } + + private void updateUI(int curr, int duration) { + setSeekProgress(curr, duration); + setBottomSeekProgress(curr, duration); + setCurrTime(curr); + setTotalTime(duration); + } + + private void setCurrTime(int curr){ + mCurrTime.setText(TimeUtil.getTime(mTimeFormat, curr)); + } + + private void setTotalTime(int duration){ + mTotalTime.setText(TimeUtil.getTime(mTimeFormat, duration)); + } + + private void setBottomSeekBarState(boolean state){ + mBottomSeekBar.setVisibility(state?View.VISIBLE:View.GONE); + } + + private void setTitle(DataSource dataSource){ + if(dataSource!=null){ + String title = dataSource.getTitle(); + if(!TextUtils.isEmpty(title)){ + setTitle(title); + return; + } + String data = dataSource.getData(); + if(!TextUtils.isEmpty(data)){ + setTitle(data); + } + } + } + + private void setTitle(String text){ + mTopTitle.setText(text); + } + + private void setSeekProgress(int curr, int duration){ + mSeekBar.setMax(duration); + mSeekBar.setProgress(curr); + float secondProgress = mBufferPercentage * 1.0f/100 * duration; + setSecondProgress((int) secondProgress); + } + + private void setSecondProgress(int secondProgress){ + mSeekBar.setSecondaryProgress(secondProgress); + } + + private void setBottomSeekProgress(int curr, int duration){ + mBottomSeekBar.setMax(duration); + mBottomSeekBar.setProgress(curr); + float secondProgress = mBufferPercentage * 1.0f/100 * duration; + mBottomSeekBar.setSecondaryProgress((int) secondProgress); + } + + private void sendSeekEvent(int progress){ + mTimerUpdateProgressEnable = false; + setCurrTime(progress); + int duration = getPlayerStateGetter().getDuration(); + setSeekProgress(progress, duration); + setBottomContainerVisibility(true); + mSeekProgress = progress; + mHandler.removeCallbacks(mSeekEventRunnable); + mHandler.postDelayed(mSeekEventRunnable, 500); + } + + private void setBottomContainerVisibility(boolean visibility){ + int visible = visibility ? View.VISIBLE : View.GONE; + if (mBottomContainer.getVisibility() != visible) { + mBottomContainer.setVisibility(visible); + } + } + private Runnable mSeekEventRunnable = new Runnable() { + @Override + public void run() { + if(mSeekProgress < 0) + return; + Bundle bundle = BundlePool.obtain(); + bundle.putInt(EventKey.INT_DATA, mSeekProgress); + Log.d(TAG, "seek: " + mSeekProgress); + requestSeek(bundle); + mSeekProgress = -1; + boolean selected = mStateIcon.isSelected(); + if (!selected) { + setBottomContainerVisibility(false); + } + } + }; + + @Override + public void onTimerUpdate(int curr, int duration, int bufferPercentage) { + if(!mTimerUpdateProgressEnable) + return; + if(mTimeFormat==null || duration != mSeekBar.getMax()){ + mTimeFormat = TimeUtil.getFormat(duration); + } + mBufferPercentage = bufferPercentage; + updateUI(curr, duration); + } + + /**----------- key control ------------------- */ + + @Override + protected void onBackKeyEvent(boolean down, KeyEvent event) { + if (!down) { + ((Activity) getContext()).finish(); + } + } + + @Override + protected void onCenterKeyEvent(boolean down, KeyEvent event) { + if (!down) { + boolean selected = mStateIcon.isSelected(); + if(selected){ + requestResume(null); + setBottomContainerVisibility(false); + }else{ + requestPause(null); + setBottomContainerVisibility(true); + } + mStateIcon.setSelected(!selected); + } + } + + @Override + protected void onLeftKeyEvent(boolean down, KeyEvent event) { + if (down) { + calculateSeekPosition(false); + } + } + + @Override + protected void onRightKeyEvent(boolean down, KeyEvent event) { + if (down) { + calculateSeekPosition(true); + } + } + + private void calculateSeekPosition(boolean right){ + //-1代表不在快进状态中 + int seekingProgress; + if (mSeekProgress == -1) { + seekingProgress = mSeekProgress = getPlayerStateGetter().getCurrentPosition(); + } else { + seekingProgress = mSeekProgress; + } + Log.d(TAG, "calculateSeekPosition: " + seekingProgress); + if (right) { + seekingProgress += 500; + } else { + seekingProgress -= 500; + } + int duration = getPlayerStateGetter().getDuration(); + //做一个前后判断,tv有些设备,seek到0或者duration的位置,会播放出错 + if (seekingProgress > duration) { + seekingProgress = duration - 1000; + } + if (seekingProgress <= 0) { + seekingProgress = 500; + } + sendSeekEvent(seekingProgress); + } +} diff --git a/app/src/main/java/com/kk/taurus/avplayer/ui/TvBaseVideoViewActivity.java b/app/src/main/java/com/kk/taurus/avplayer/ui/TvBaseVideoViewActivity.java new file mode 100644 index 00000000..f8a4b47a --- /dev/null +++ b/app/src/main/java/com/kk/taurus/avplayer/ui/TvBaseVideoViewActivity.java @@ -0,0 +1,68 @@ +package com.kk.taurus.avplayer.ui; + +import android.os.Bundle; +import android.view.KeyEvent; +import android.widget.Toast; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import com.kk.taurus.avplayer.cover.LoadingCover; +import com.kk.taurus.avplayer.cover.TvControllerCover; +import com.kk.taurus.avplayer.utils.DataUtils; +import com.kk.taurus.playerbase.assist.OnVideoViewEventHandler; +import com.kk.taurus.playerbase.entity.DataSource; +import com.kk.taurus.playerbase.receiver.ReceiverGroup; +import com.kk.taurus.playerbase.widget.BaseVideoView; + +import static com.kk.taurus.avplayer.play.DataInter.ReceiverKey.KEY_LOADING_COVER; + +public class TvBaseVideoViewActivity extends AppCompatActivity { + + private BaseVideoView mVideoView; + private ReceiverGroup mReceiverGroup; + + private boolean hasStart; + private boolean videoViewKeyEnable = true; + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mVideoView = new BaseVideoView(this); + mVideoView.setFocusable(true); + mVideoView.requestFocus(); + mReceiverGroup = new ReceiverGroup(); + mReceiverGroup.addReceiver(KEY_LOADING_COVER, new LoadingCover(this)); + mReceiverGroup.addReceiver("key_event_cover", new TvControllerCover(this)); + mVideoView.setReceiverGroup(mReceiverGroup); + mVideoView.setEventHandler(new OnVideoViewEventHandler()); + setContentView(mVideoView); + initPlay(); + } + + + private void initPlay(){ + if(!hasStart){ + DataSource dataSource = new DataSource(DataUtils.VIDEO_URL_09); + dataSource.setTitle("音乐和艺术如何改变世界"); + mVideoView.setDataSource(dataSource); + mVideoView.start(); + hasStart = true; + } + } + + @Override + public boolean dispatchKeyEvent(KeyEvent event) { + if (event.getKeyCode() == KeyEvent.KEYCODE_MENU && event.getAction() == KeyEvent.ACTION_DOWN) { + videoViewKeyEnable = !videoViewKeyEnable; + mVideoView.getSuperContainer().setKeyEventEnable(videoViewKeyEnable); + Toast.makeText(this, videoViewKeyEnable ? "播放器可以分发" : "播放器移除分发", Toast.LENGTH_SHORT).show(); + } + return super.dispatchKeyEvent(event); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mVideoView.stopPlayback(); + } +} diff --git a/app/src/main/res/layout/activity_home.xml b/app/src/main/res/layout/activity_home.xml index 7bb5673f..710580e1 100644 --- a/app/src/main/res/layout/activity_home.xml +++ b/app/src/main/res/layout/activity_home.xml @@ -80,6 +80,18 @@ android:textSize="16sp" android:textColor="@color/buttonTextColor"/> +