Commit eb47ac608b5e6dc7c812a7975fe42badde9be6a1
1 parent
beebfcb8
1 封装视频库
Showing
124 changed files
with
7600 additions
and
259 deletions
app/strike/src/main/java/com/cnlive/strike/demo/activity/CNplayerActivity.java
| 1 | 1 | package com.cnlive.strike.demo.activity; |
| 2 | 2 | |
| 3 | 3 | import android.content.Intent; |
| 4 | -import android.os.Handler; | |
| 5 | -import android.widget.Toast; | |
| 6 | 4 | |
| 7 | -import androidx.annotation.Nullable; | |
| 8 | 5 | import androidx.databinding.ViewDataBinding; |
| 9 | 6 | |
| 10 | 7 | import com.alibaba.android.arouter.facade.annotation.Route; |
| 11 | 8 | import com.cnlive.cloud.BuildConfig; |
| 12 | 9 | import com.cnlive.cloud.R; |
| 13 | 10 | import com.cnlive.cloud.databinding.ActivityCnvideoBinding; |
| 14 | -import com.cnlive.cloud.user.auth.UserService; | |
| 15 | 11 | import com.cnlive.core.libs.base.frame.activity.BaseActivity; |
| 16 | -import com.cnlive.core.libs.base.util.AlertUtil; | |
| 17 | 12 | import com.cnlive.core.libs.base.util.ChannelUtil; |
| 18 | 13 | import com.cnlive.core.libs.base.util.QMUITipDialogUtil; |
| 19 | 14 | import com.libs.video.util.Config; |
| 20 | -import com.libs.video.util.ProbeUtil; | |
| 21 | -import com.libs.video.video.VideoView; | |
| 22 | 15 | import com.libs.video.video.base.IMediaPlayer; |
| 23 | 16 | |
| 24 | -import org.greenrobot.eventbus.Subscribe; | |
| 25 | -import org.greenrobot.eventbus.ThreadMode; | |
| 26 | - | |
| 27 | 17 | @Route(path = CNplayerActivity.PATH) |
| 28 | 18 | public class CNplayerActivity extends BaseActivity implements IMediaPlayer.OnPreparedListener, |
| 29 | 19 | IMediaPlayer.OnCompletionListener, IMediaPlayer.OnErrorListener, IMediaPlayer.OnInfoListener, | ... | ... |
app/strike/src/main/java/com/cnlive/strike/demo/activity/ExoPlayerActivity.java
| ... | ... | @@ -3,32 +3,31 @@ package com.cnlive.strike.demo.activity; |
| 3 | 3 | import android.net.Uri; |
| 4 | 4 | import android.view.Surface; |
| 5 | 5 | import android.view.SurfaceControl; |
| 6 | -import android.view.SurfaceView; | |
| 7 | -import android.view.View; | |
| 6 | +import android.widget.Toast; | |
| 8 | 7 | |
| 9 | -import androidx.annotation.Nullable; | |
| 10 | -import androidx.core.content.ContextCompat; | |
| 11 | 8 | import androidx.databinding.ViewDataBinding; |
| 12 | 9 | |
| 10 | +import com.cnlive.cloud.BuildConfig; | |
| 13 | 11 | import com.cnlive.cloud.R; |
| 14 | 12 | import com.cnlive.cloud.databinding.ActivityExplayerBinding; |
| 15 | 13 | import com.cnlive.core.libs.base.frame.activity.BaseActivity; |
| 16 | -import com.google.android.exoplayer2.C; | |
| 17 | -import com.google.android.exoplayer2.ExoPlaybackException; | |
| 18 | -import com.google.android.exoplayer2.PlaybackParameters; | |
| 19 | 14 | import com.google.android.exoplayer2.Player; |
| 20 | 15 | import com.google.android.exoplayer2.SimpleExoPlayer; |
| 21 | -import com.google.android.exoplayer2.Timeline; | |
| 22 | -import com.google.android.exoplayer2.source.TrackGroupArray; | |
| 23 | 16 | import com.google.android.exoplayer2.source.hls.HlsMediaSource; |
| 24 | -import com.google.android.exoplayer2.trackselection.TrackSelectionArray; | |
| 25 | -import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; | |
| 26 | 17 | import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; |
| 27 | 18 | import com.google.android.exoplayer2.util.Util; |
| 28 | 19 | import com.google.android.exoplayer2.video.VideoListener; |
| 29 | -import com.libs.video.video.exoplayer.CNLiveBaseVideoControlView; | |
| 30 | -import com.libs.video.video.exoplayer.MyTestControlView; | |
| 31 | -import com.libs.video.video.exoplayer.VideoPlayer; | |
| 20 | +import com.libs.video.video.exoplayer.play.CNVideoView; | |
| 21 | +import com.libs.video.video.test.VideoPlayer; | |
| 22 | +import com.libs.video.video.exoplayer.component.DefaultAdControlView; | |
| 23 | +import com.libs.video.video.exoplayer.controller.StandardVideoController; | |
| 24 | +import com.libs.video.video.exoplayer.play.VideoViewConfig; | |
| 25 | +import com.libs.video.video.exoplayer.play.VideoViewManager; | |
| 26 | +import com.libs.video.video.exoplayer.playerCore.exoplayer.ExoMediaPlayerFactory; | |
| 27 | +import com.libs.video.video.exoplayer.render.surface.SurfaceRenderViewFactory; | |
| 28 | + | |
| 29 | +import java.util.ArrayList; | |
| 30 | +import java.util.List; | |
| 32 | 31 | |
| 33 | 32 | public class ExoPlayerActivity extends BaseActivity implements VideoListener, Player.EventListener { |
| 34 | 33 | ActivityExplayerBinding binding; |
| ... | ... | @@ -37,6 +36,9 @@ public class ExoPlayerActivity extends BaseActivity implements VideoListener, Pl |
| 37 | 36 | SurfaceControl surfaceControl; |
| 38 | 37 | Surface videoSurface; |
| 39 | 38 | private VideoPlayer videoPlayer; |
| 39 | + private static final String URL_AD = "https://gslb.miaopai.com/stream/IR3oMYDhrON5huCmf7sHCfnU5YKEkgO2.mp4"; | |
| 40 | + | |
| 41 | + private StandardVideoController mController; | |
| 40 | 42 | |
| 41 | 43 | @Override |
| 42 | 44 | protected int getBindLayoutId() { |
| ... | ... | @@ -51,9 +53,6 @@ public class ExoPlayerActivity extends BaseActivity implements VideoListener, Pl |
| 51 | 53 | @Override |
| 52 | 54 | protected void initView() { |
| 53 | 55 | initVideoPlayer(); |
| 54 | - // Produces DataSource instances through which media data is loaded. | |
| 55 | -// DataSource dataSource =new DefaultDataSource(context,Util.getUserAgent(context, "yourApplicationName"),true); | |
| 56 | -// Create a data source factory. | |
| 57 | 56 | DefaultHttpDataSourceFactory dataSourceFactory = |
| 58 | 57 | new DefaultHttpDataSourceFactory(Util.getUserAgent(context, "app-name")); |
| 59 | 58 | |
| ... | ... | @@ -76,50 +75,99 @@ public class ExoPlayerActivity extends BaseActivity implements VideoListener, Pl |
| 76 | 75 | // player.addListener(this); |
| 77 | 76 | // // Prepare the player with the source. |
| 78 | 77 | player.prepare(hlsMediaSource); |
| 79 | -// //绑定视图 | |
| 80 | -// binding.playerView.setPlayer(player); | |
| 81 | -// player.setPlayWhenReady(true); | |
| 82 | - //全屏 | |
| 83 | -// player.setVideoScalingMode(C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING); | |
| 84 | - //按比例放大 | |
| 85 | -// player.setVideoScalingMode(C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING); | |
| 86 | -// player.setForegroundMode(true); | |
| 87 | -// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { | |
| 88 | -// surfaceControl = | |
| 89 | -// new SurfaceControl.Builder() | |
| 90 | -// .setName("测试") | |
| 91 | -// .setBufferSize(/* width= */ 0, /* height= */ 0) | |
| 92 | -// .build(); | |
| 93 | -// videoSurface = new Surface(surfaceControl); | |
| 94 | -// player.setVideoSurface(videoSurface); | |
| 95 | -// } | |
| 96 | -// CacheUtil cacheUtil=new CacheUtil(this); | |
| 97 | -// try { | |
| 98 | -// cacheUtil.getVideoUri("123456"); | |
| 99 | -// cacheUtil.add("123456","test"); | |
| 100 | -// cacheUtil.getVideoUri("123456"); | |
| 101 | -// } catch (IllegalBlockSizeException e) { | |
| 102 | -// e.printStackTrace(); | |
| 103 | -// } catch (NoSuchPaddingException e) { | |
| 104 | -// e.printStackTrace(); | |
| 105 | -// } catch (BadPaddingException e) { | |
| 106 | -// e.printStackTrace(); | |
| 107 | -// } catch (InvalidKeySpecException e) { | |
| 108 | -// e.printStackTrace(); | |
| 109 | -// } catch (InvalidKeyException e) { | |
| 110 | -// e.printStackTrace(); | |
| 111 | -// } | |
| 112 | -// | |
| 113 | -// binding.playerView.setOnClickListener(new View.OnClickListener() { | |
| 78 | + //播放器配置,注意:此为全局配置,按需开启 | |
| 79 | + VideoViewManager.setConfig(VideoViewConfig.newBuilder() | |
| 80 | + .setLogEnabled(BuildConfig.DEBUG)//调试的时候请打开日志,方便排错 | |
| 81 | +// .setPlayerFactory(IjkPlayerFactory.create()) | |
| 82 | +// .setPlayerFactory(ThunderMediaPlayerFactory.create()) | |
| 83 | + .setPlayerFactory(ExoMediaPlayerFactory.create()) | |
| 84 | + .setRenderViewFactory(SurfaceRenderViewFactory.create()) | |
| 85 | + .setEnableOrientation(true) | |
| 86 | +// .setEnableAudioFocus(false) | |
| 87 | +// .setScreenScaleType(VideoView.SCREEN_SCALE_MATCH_PARENT) | |
| 88 | +// .setAdaptCutout(false) | |
| 89 | +// .setPlayOnMobileNetwork(true) | |
| 90 | +// .setProgressManager(new ProgressManagerImpl()) | |
| 91 | + .build()); | |
| 92 | + mController = new StandardVideoController(this); | |
| 93 | + DefaultAdControlView adControlView = new DefaultAdControlView(this); | |
| 94 | + adControlView.setListener(new DefaultAdControlView.AdControlListener() { | |
| 95 | + @Override | |
| 96 | + public void onAdClick() { | |
| 97 | + Toast.makeText(me, "广告点击跳转", Toast.LENGTH_SHORT).show(); | |
| 98 | + } | |
| 99 | + | |
| 100 | + @Override | |
| 101 | + public void onSkipAd() { | |
| 102 | + playVideo(); | |
| 103 | + } | |
| 104 | + }); | |
| 105 | + mController.addControlComponent(adControlView); | |
| 106 | + binding.playerView.setUrl("http://ffyl.vods2.cnlive.com/223/vod/2018/1120/223_88c98ab1395f4762b703fbdd9d59d2e5/ff80808166c2a45201672f14a9933131_800.m3u8"); | |
| 107 | + binding.playerView.setVideoController(mController); | |
| 108 | + binding.playerView.start(); | |
| 109 | + //监听播放结束 | |
| 110 | +// binding.playerView.addOnStateChangeListener(new CNVideoView.SimpleOnStateChangeListener() { | |
| 114 | 111 | // @Override |
| 115 | -// public void onClick(View v) { | |
| 116 | -//// player.seekTo(player.getCurrentWindowIndex(),5000); | |
| 112 | +// public void onPlayStateChanged(int playState) { | |
| 113 | +// if (playState == CNVideoView.STATE_PLAYBACK_COMPLETED) { | |
| 114 | +// playVideo(); | |
| 115 | +// } | |
| 117 | 116 | // } |
| 118 | 117 | // }); |
| 119 | 118 | } |
| 120 | 119 | |
| 120 | + @Override | |
| 121 | + protected void onResume() { | |
| 122 | + super.onResume(); | |
| 123 | + if (binding.playerView != null) { | |
| 124 | + binding.playerView.onResume(); | |
| 125 | + } | |
| 126 | + } | |
| 127 | + | |
| 128 | + @Override | |
| 129 | + public void onBackPressed() { | |
| 130 | + if (!binding.playerView.isBackPressed()) { | |
| 131 | + super.onBackPressed(); | |
| 132 | + } | |
| 133 | + } | |
| 134 | + | |
| 135 | + @Override | |
| 136 | + protected void onPause() { | |
| 137 | + super.onPause(); | |
| 138 | + if (binding.playerView != null) { | |
| 139 | + binding.playerView.onPause(); | |
| 140 | + } | |
| 141 | + } | |
| 142 | + | |
| 143 | + @Override | |
| 144 | + protected void onDestroy() { | |
| 145 | + super.onDestroy(); | |
| 146 | + if (binding.playerView != null) { | |
| 147 | + binding.playerView.onDestroy(); | |
| 148 | + } | |
| 149 | + } | |
| 150 | + | |
| 151 | + /** | |
| 152 | + * 播放正片 | |
| 153 | + */ | |
| 154 | + private void playVideo() { | |
| 155 | + binding.playerView.release(); | |
| 156 | + mController.removeAllControlComponent(); | |
| 157 | + mController.addDefaultControlComponent("正片", false); | |
| 158 | + //重新设置数据 | |
| 159 | + List<String> list = new ArrayList<>(); | |
| 160 | + list.add("http://sbslive.cnrmobile.com/storage/storage4/76/23/10/14de52115893fb5371b7e064f910ab7b.3gp"); | |
| 161 | + list.add("https://qq.com-ok-qq.com/20191005/24249_f5e36323/index.m3u8"); | |
| 162 | + list.add("http://ffyl.vods2.cnlive.com/223/vod/2018/1120/223_88c98ab1395f4762b703fbdd9d59d2e5/ff80808166c2a45201672f14a9933131_800.m3u8"); | |
| 163 | + list.add("http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8"); | |
| 164 | + binding.playerView.setDataSource(list); | |
| 165 | + //开始播放 | |
| 166 | + binding.playerView.start(); | |
| 167 | + } | |
| 168 | + | |
| 121 | 169 | private void initVideoPlayer() { |
| 122 | - binding.playerView.setCustomControllView(new MyTestControlView(context)); | |
| 170 | +// binding.playerView.setCustomControllView(new MyTestControlView(context)); | |
| 123 | 171 | // Uri dashUri = Uri.parse("rtmp://tyzg.rtmps2.cnlive.com/live-tyzg-577/577_999"); |
| 124 | 172 | |
| 125 | 173 | // Uri mp4VideoUri = Uri.parse("http://ffyl.vods2.cnlive.com/223/vod/2018/1120/223_88c98ab1395f4762b703fbdd9d59d2e5/ff80808166c2a45201672f14a9933131_800.m3u8"); |
| ... | ... | @@ -137,73 +185,4 @@ public class ExoPlayerActivity extends BaseActivity implements VideoListener, Pl |
| 137 | 185 | // }); |
| 138 | 186 | } |
| 139 | 187 | |
| 140 | - @Override | |
| 141 | - public void onBackPressed() { | |
| 142 | - binding.playerView.onBackPress(); | |
| 143 | - } | |
| 144 | - | |
| 145 | - @Override | |
| 146 | - public void onTimelineChanged(Timeline timeline, int reason) { | |
| 147 | - | |
| 148 | - } | |
| 149 | - | |
| 150 | - @Override | |
| 151 | - public void onTimelineChanged(Timeline timeline, @Nullable Object manifest, int reason) { | |
| 152 | - | |
| 153 | - } | |
| 154 | - | |
| 155 | - @Override | |
| 156 | - public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { | |
| 157 | - | |
| 158 | - } | |
| 159 | - | |
| 160 | - @Override | |
| 161 | - public void onLoadingChanged(boolean isLoading) { | |
| 162 | - | |
| 163 | - } | |
| 164 | - | |
| 165 | - @Override | |
| 166 | - public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { | |
| 167 | - logError(playbackState + ""); | |
| 168 | - } | |
| 169 | - | |
| 170 | - @Override | |
| 171 | - public void onPlaybackSuppressionReasonChanged(int playbackSuppressionReason) { | |
| 172 | - | |
| 173 | - } | |
| 174 | - | |
| 175 | - @Override | |
| 176 | - public void onIsPlayingChanged(boolean isPlaying) { | |
| 177 | - | |
| 178 | - } | |
| 179 | - | |
| 180 | - @Override | |
| 181 | - public void onRepeatModeChanged(int repeatMode) { | |
| 182 | - | |
| 183 | - } | |
| 184 | - | |
| 185 | - @Override | |
| 186 | - public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { | |
| 187 | - | |
| 188 | - } | |
| 189 | - | |
| 190 | - @Override | |
| 191 | - public void onPlayerError(ExoPlaybackException error) { | |
| 192 | - | |
| 193 | - } | |
| 194 | - | |
| 195 | - @Override | |
| 196 | - public void onPositionDiscontinuity(int reason) { | |
| 197 | - | |
| 198 | - } | |
| 199 | - | |
| 200 | - @Override | |
| 201 | - public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { | |
| 202 | - | |
| 203 | - } | |
| 204 | - | |
| 205 | - @Override | |
| 206 | - public void onSeekProcessed() { | |
| 207 | - | |
| 208 | - } | |
| 209 | 188 | } | ... | ... |
app/strike/src/main/res/layout/activity_cnvideo.xml
| ... | ... | @@ -6,9 +6,9 @@ |
| 6 | 6 | android:layout_height="match_parent" |
| 7 | 7 | android:orientation="vertical"> |
| 8 | 8 | |
| 9 | - <com.libs.video.video.VideoView | |
| 9 | + <com.libs.video.video.test.VideoView | |
| 10 | 10 | android:id="@+id/videoview" |
| 11 | 11 | android:layout_width="match_parent" |
| 12 | - android:layout_height="300dp"></com.libs.video.video.VideoView> | |
| 12 | + android:layout_height="300dp"></com.libs.video.video.test.VideoView> | |
| 13 | 13 | </LinearLayout> |
| 14 | 14 | </layout> |
| 15 | 15 | \ No newline at end of file | ... | ... |
app/strike/src/main/res/layout/activity_explayer.xml
| ... | ... | @@ -7,14 +7,18 @@ |
| 7 | 7 | android:background="@color/black" |
| 8 | 8 | android:orientation="vertical"> |
| 9 | 9 | |
| 10 | - <com.libs.video.video.exoplayer.CNLiveVideoView | |
| 10 | + <!-- <com.libs.video.video.test.CNLiveVideoView--> | |
| 11 | + <!-- android:id="@+id/playerView"--> | |
| 12 | + <!-- android:layout_width="match_parent"--> | |
| 13 | + <!-- android:layout_height="@dimen/dp_200" />--> | |
| 14 | + | |
| 15 | + <com.libs.video.video.exoplayer.play.CNVideoView | |
| 11 | 16 | android:id="@+id/playerView" |
| 12 | 17 | android:layout_width="match_parent" |
| 13 | - android:layout_height="@dimen/dp_200" /> | |
| 14 | - | |
| 15 | -<!-- <com.google.android.exoplayer2.ui.PlayerView--> | |
| 16 | -<!-- android:layout_width="match_parent"--> | |
| 17 | -<!-- android:id="@+id/playerView"--> | |
| 18 | -<!-- android:layout_height="match_parent" />--> | |
| 18 | + android:layout_height="match_parent" /> | |
| 19 | + <!-- <com.google.android.exoplayer2.ui.PlayerView--> | |
| 20 | + <!-- android:layout_width="match_parent"--> | |
| 21 | + <!-- android:id="@+id/playerView"--> | |
| 22 | + <!-- android:layout_height="match_parent" />--> | |
| 19 | 23 | </LinearLayout> |
| 20 | 24 | </layout> |
| 21 | 25 | \ No newline at end of file | ... | ... |
cloud/user/src/main/java/com/cnlive/cloud/user/frame/presenter/LoginDialogPresenter.java
cloud/user/src/main/java/com/cnlive/cloud/user/frame/presenter/QuickLoginPresenter.java
| ... | ... | @@ -322,6 +322,9 @@ public class QuickLoginPresenter extends BasePresenter<QuickLoginView> { |
| 322 | 322 | // }); |
| 323 | 323 | // } |
| 324 | 324 | public void cancelLoad() { |
| 325 | + if (timerUtil != null) { | |
| 326 | + timerUtil.cancel(); | |
| 327 | + } | |
| 325 | 328 | if (event != null) { |
| 326 | 329 | if (!event.isDisposed()) { |
| 327 | 330 | event.dispose(); |
| ... | ... | @@ -336,4 +339,5 @@ public class QuickLoginPresenter extends BasePresenter<QuickLoginView> { |
| 336 | 339 | otherEvent = null; |
| 337 | 340 | } |
| 338 | 341 | } |
| 342 | + | |
| 339 | 343 | } | ... | ... |
cloud/user/src/main/java/com/cnlive/cloud/user/ui/activity/LoginDialogActivity.java
| ... | ... | @@ -223,7 +223,7 @@ public class LoginDialogActivity extends BaseActivity<LoginDialogView, LoginDial |
| 223 | 223 | public void onDestroy() { |
| 224 | 224 | super.onDestroy(); |
| 225 | 225 | if (getMvpView() != null) getMvpView().hideProgress(); |
| 226 | - if (getPresenter() != null) getPresenter().destroy(); | |
| 226 | + if (getPresenter() != null) getPresenter().onDestroy(); | |
| 227 | 227 | |
| 228 | 228 | WechatLoginObservable.getInstance().deleteObserver(this); |
| 229 | 229 | ThirdLoginObservable.getInstance().deleteObserver(this); | ... | ... |
cloud/user/src/main/java/com/cnlive/cloud/user/ui/fragment/QuickLoginFragment.java
| ... | ... | @@ -11,7 +11,6 @@ import androidx.databinding.ViewDataBinding; |
| 11 | 11 | |
| 12 | 12 | import com.cnlive.cloud.user.databinding.FragmentQuickLoginBinding; |
| 13 | 13 | import com.cnlive.cloud.user.frame.presenter.QuickLoginPresenter; |
| 14 | -import com.cnlive.cloud.user.ui.activity.LoginDialogActivity; | |
| 15 | 14 | import com.cnlive.core.libs.base.application.AppConfig; |
| 16 | 15 | import com.cnlive.core.libs.base.frame.fragment.BaseFragment; |
| 17 | 16 | import com.cnlive.core.libs.base.util.DoublePressed; |
| ... | ... | @@ -28,7 +27,6 @@ import com.cnlive.login.model.ThirdLoginInfo; |
| 28 | 27 | import com.cnlive.login.observer.ThirdLoginObservable; |
| 29 | 28 | import com.cnlive.login.observer.WechatLoginObservable; |
| 30 | 29 | import com.cnlive.login.utils.ThirdLoginUtil; |
| 31 | -import com.cnlive.share.BuildConfig; | |
| 32 | 30 | import com.cnlive.share.data.WeChatInfo; |
| 33 | 31 | import com.cnlive.share.util.Util; |
| 34 | 32 | |
| ... | ... | @@ -238,7 +236,7 @@ public class QuickLoginFragment extends BaseFragment<QuickLoginView, QuickLoginP |
| 238 | 236 | public void onDestroy() { |
| 239 | 237 | super.onDestroy(); |
| 240 | 238 | if (getMvpView() != null) getMvpView().hideProgress(); |
| 241 | - if (getPresenter() != null) getPresenter().destroy(); | |
| 239 | + if (getPresenter() != null) getPresenter().cancelLoad(); | |
| 242 | 240 | WechatLoginObservable.getInstance().deleteObserver(this); |
| 243 | 241 | ThirdLoginObservable.getInstance().deleteObserver(this); |
| 244 | 242 | } | ... | ... |
cloud/video/src/main/AndroidManifest.xml
cloud/video/src/main/java/com/libs/video/video/exoplayer/component/DefaultAdControlView.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.component; | |
| 2 | + | |
| 3 | +import android.app.Activity; | |
| 4 | +import android.content.Context; | |
| 5 | +import android.util.AttributeSet; | |
| 6 | +import android.view.LayoutInflater; | |
| 7 | +import android.view.View; | |
| 8 | +import android.view.animation.Animation; | |
| 9 | +import android.widget.FrameLayout; | |
| 10 | +import android.widget.ImageView; | |
| 11 | +import android.widget.TextView; | |
| 12 | + | |
| 13 | + | |
| 14 | +import com.cnlive.cloud.video.R; | |
| 15 | +import com.libs.video.video.exoplayer.controller.ControlWrapper; | |
| 16 | +import com.libs.video.video.exoplayer.controller.IControlComponent; | |
| 17 | +import com.libs.video.video.exoplayer.play.CNVideoView; | |
| 18 | +import com.libs.video.video.exoplayer.util.PlayerUtils; | |
| 19 | + | |
| 20 | +import androidx.annotation.NonNull; | |
| 21 | +import androidx.annotation.Nullable; | |
| 22 | + | |
| 23 | +/** | |
| 24 | + * 广告播放视图 | |
| 25 | + */ | |
| 26 | +public class DefaultAdControlView extends FrameLayout implements IControlComponent, View.OnClickListener { | |
| 27 | + | |
| 28 | + protected TextView mAdTime, mAdDetail; | |
| 29 | + protected ImageView mBack, mVolume, mFullScreen, mPlayButton; | |
| 30 | + protected AdControlListener mListener; | |
| 31 | + | |
| 32 | + private ControlWrapper mControlWrapper;//控制API | |
| 33 | + | |
| 34 | + public DefaultAdControlView(@NonNull Context context) { | |
| 35 | + super(context); | |
| 36 | + } | |
| 37 | + | |
| 38 | + public DefaultAdControlView(@NonNull Context context, @Nullable AttributeSet attrs) { | |
| 39 | + super(context, attrs); | |
| 40 | + } | |
| 41 | + | |
| 42 | + public DefaultAdControlView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | |
| 43 | + super(context, attrs, defStyleAttr); | |
| 44 | + } | |
| 45 | + | |
| 46 | + { | |
| 47 | + LayoutInflater.from(getContext()).inflate(R.layout.layout_ad_control_view, this, true); | |
| 48 | + mAdTime = findViewById(R.id.ad_time); | |
| 49 | + mAdDetail = findViewById(R.id.ad_detail); | |
| 50 | + mAdDetail.setText("了解详情>"); | |
| 51 | + mBack = findViewById(R.id.back); | |
| 52 | + mBack.setVisibility(GONE); | |
| 53 | + mVolume = findViewById(R.id.iv_volume); | |
| 54 | + mFullScreen = findViewById(R.id.fullscreen); | |
| 55 | + mPlayButton = findViewById(R.id.iv_play); | |
| 56 | + mPlayButton.setOnClickListener(this); | |
| 57 | + mAdTime.setOnClickListener(this); | |
| 58 | + mAdDetail.setOnClickListener(this); | |
| 59 | + mBack.setOnClickListener(this); | |
| 60 | + mVolume.setOnClickListener(this); | |
| 61 | + mFullScreen.setOnClickListener(this); | |
| 62 | + this.setOnClickListener(new OnClickListener() { | |
| 63 | + @Override | |
| 64 | + public void onClick(View v) { | |
| 65 | + if (mListener != null) mListener.onAdClick(); | |
| 66 | + } | |
| 67 | + }); | |
| 68 | + } | |
| 69 | + | |
| 70 | + @Override | |
| 71 | + public void attach(@NonNull ControlWrapper controlWrapper) { | |
| 72 | + mControlWrapper = controlWrapper; | |
| 73 | + } | |
| 74 | + | |
| 75 | + @Override | |
| 76 | + public View getView() { | |
| 77 | + return this; | |
| 78 | + } | |
| 79 | + | |
| 80 | + @Override | |
| 81 | + public void onVisibilityChanged(boolean isVisible, Animation anim) { | |
| 82 | + | |
| 83 | + } | |
| 84 | + | |
| 85 | + @Override | |
| 86 | + public void onPlayStateChanged(int playState) { | |
| 87 | + switch (playState) { | |
| 88 | + case CNVideoView.STATE_PLAYING: | |
| 89 | + mControlWrapper.startProgress(); | |
| 90 | + mPlayButton.setSelected(true); | |
| 91 | + break; | |
| 92 | + case CNVideoView.STATE_PAUSED: | |
| 93 | + mPlayButton.setSelected(false); | |
| 94 | + break; | |
| 95 | + } | |
| 96 | + } | |
| 97 | + | |
| 98 | + @Override | |
| 99 | + public void onPlayerStateChanged(int playerState) { | |
| 100 | + switch (playerState) { | |
| 101 | + case CNVideoView.PLAYER_NORMAL: | |
| 102 | + mBack.setVisibility(GONE); | |
| 103 | + mFullScreen.setSelected(false); | |
| 104 | + break; | |
| 105 | + case CNVideoView.PLAYER_FULL_SCREEN: | |
| 106 | + mBack.setVisibility(VISIBLE); | |
| 107 | + mFullScreen.setSelected(true); | |
| 108 | + break; | |
| 109 | + } | |
| 110 | + | |
| 111 | + //暂未实现全面屏适配逻辑,需要你自己补全 | |
| 112 | + } | |
| 113 | + | |
| 114 | + @Override | |
| 115 | + public void setProgress(int duration, int position) { | |
| 116 | + if (mAdTime != null) | |
| 117 | + mAdTime.setText(String.format("%s | 跳过", (duration - position) / 1000)); | |
| 118 | + } | |
| 119 | + | |
| 120 | + @Override | |
| 121 | + public void onLockStateChanged(boolean isLocked) { | |
| 122 | + | |
| 123 | + } | |
| 124 | + | |
| 125 | + @Override | |
| 126 | + public void onClick(View v) { | |
| 127 | + int id = v.getId(); | |
| 128 | + if (id == R.id.back | id == R.id.fullscreen) { | |
| 129 | + toggleFullScreen(); | |
| 130 | + } else if (id == R.id.iv_volume) { | |
| 131 | + doMute(); | |
| 132 | + } else if (id == R.id.ad_detail) { | |
| 133 | + if (mListener != null) mListener.onAdClick(); | |
| 134 | + } else if (id == R.id.ad_time) { | |
| 135 | + if (mListener != null) mListener.onSkipAd(); | |
| 136 | + } else if (id == R.id.iv_play) { | |
| 137 | + mControlWrapper.togglePlay(); | |
| 138 | + } | |
| 139 | + } | |
| 140 | + | |
| 141 | + private void doMute() { | |
| 142 | + mControlWrapper.setMute(!mControlWrapper.isMute()); | |
| 143 | + mVolume.setImageResource(mControlWrapper.isMute() ? R.drawable.cnvideo_ic_action_volume_up : R.drawable.cnvideo_ic_action_volume_off); | |
| 144 | + } | |
| 145 | + | |
| 146 | + /** | |
| 147 | + * 横竖屏切换 | |
| 148 | + */ | |
| 149 | + private void toggleFullScreen() { | |
| 150 | + Activity activity = PlayerUtils.scanForActivity(getContext()); | |
| 151 | + mControlWrapper.toggleFullScreen(activity); | |
| 152 | + } | |
| 153 | + | |
| 154 | + public void setListener(AdControlListener listener) { | |
| 155 | + this.mListener = listener; | |
| 156 | + } | |
| 157 | + | |
| 158 | + public interface AdControlListener { | |
| 159 | + | |
| 160 | + void onAdClick(); | |
| 161 | + | |
| 162 | + void onSkipAd(); | |
| 163 | + } | |
| 164 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/component/DefaultCompleteView.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.component; | |
| 2 | + | |
| 3 | +import android.app.Activity; | |
| 4 | +import android.content.Context; | |
| 5 | +import android.content.pm.ActivityInfo; | |
| 6 | +import android.util.AttributeSet; | |
| 7 | +import android.view.LayoutInflater; | |
| 8 | +import android.view.View; | |
| 9 | +import android.view.animation.Animation; | |
| 10 | +import android.widget.FrameLayout; | |
| 11 | +import android.widget.ImageView; | |
| 12 | + | |
| 13 | +import com.cnlive.cloud.video.R; | |
| 14 | +import com.libs.video.video.exoplayer.controller.ControlWrapper; | |
| 15 | +import com.libs.video.video.exoplayer.controller.IControlComponent; | |
| 16 | +import com.libs.video.video.exoplayer.play.CNVideoView; | |
| 17 | +import com.libs.video.video.exoplayer.util.PlayerUtils; | |
| 18 | + | |
| 19 | +import androidx.annotation.NonNull; | |
| 20 | +import androidx.annotation.Nullable; | |
| 21 | + | |
| 22 | +/** | |
| 23 | + * 自动播放完成界面 | |
| 24 | + */ | |
| 25 | +public class DefaultCompleteView extends FrameLayout implements IControlComponent { | |
| 26 | + | |
| 27 | + private ControlWrapper mControlWrapper; | |
| 28 | + | |
| 29 | + private ImageView mStopFullscreen; | |
| 30 | + | |
| 31 | + public DefaultCompleteView(@NonNull Context context) { | |
| 32 | + super(context); | |
| 33 | + } | |
| 34 | + | |
| 35 | + public DefaultCompleteView(@NonNull Context context, @Nullable AttributeSet attrs) { | |
| 36 | + super(context, attrs); | |
| 37 | + } | |
| 38 | + | |
| 39 | + public DefaultCompleteView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | |
| 40 | + super(context, attrs, defStyleAttr); | |
| 41 | + } | |
| 42 | + | |
| 43 | + { | |
| 44 | + setVisibility(GONE); | |
| 45 | + LayoutInflater.from(getContext()).inflate(R.layout.cnvideo_layout_complete_view, this, true); | |
| 46 | + findViewById(R.id.iv_replay).setOnClickListener(new OnClickListener() { | |
| 47 | + @Override | |
| 48 | + public void onClick(View v) { | |
| 49 | + mControlWrapper.replay(true); | |
| 50 | + } | |
| 51 | + }); | |
| 52 | + mStopFullscreen = findViewById(R.id.stop_fullscreen); | |
| 53 | + mStopFullscreen.setOnClickListener(new OnClickListener() { | |
| 54 | + @Override | |
| 55 | + public void onClick(View v) { | |
| 56 | + if (mControlWrapper.isFullScreen()) { | |
| 57 | + Activity activity = PlayerUtils.scanForActivity(getContext()); | |
| 58 | + if (activity != null && !activity.isFinishing()) { | |
| 59 | + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); | |
| 60 | + mControlWrapper.stopFullScreen(); | |
| 61 | + } | |
| 62 | + } | |
| 63 | + } | |
| 64 | + }); | |
| 65 | + setClickable(true); | |
| 66 | + } | |
| 67 | + | |
| 68 | + @Override | |
| 69 | + public void attach(@NonNull ControlWrapper controlWrapper) { | |
| 70 | + mControlWrapper = controlWrapper; | |
| 71 | + } | |
| 72 | + | |
| 73 | + @Override | |
| 74 | + public View getView() { | |
| 75 | + return this; | |
| 76 | + } | |
| 77 | + | |
| 78 | + @Override | |
| 79 | + public void onVisibilityChanged(boolean isVisible, Animation anim) { | |
| 80 | + | |
| 81 | + } | |
| 82 | + | |
| 83 | + @Override | |
| 84 | + public void onPlayStateChanged(int playState) { | |
| 85 | + if (playState == CNVideoView.STATE_PLAYBACK_COMPLETED) { | |
| 86 | + setVisibility(VISIBLE); | |
| 87 | + mStopFullscreen.setVisibility(mControlWrapper.isFullScreen() ? VISIBLE : GONE); | |
| 88 | + bringToFront(); | |
| 89 | + } else { | |
| 90 | + setVisibility(GONE); | |
| 91 | + } | |
| 92 | + } | |
| 93 | + | |
| 94 | + @Override | |
| 95 | + public void onPlayerStateChanged(int playerState) { | |
| 96 | + if (playerState == CNVideoView.PLAYER_FULL_SCREEN) { | |
| 97 | + mStopFullscreen.setVisibility(VISIBLE); | |
| 98 | + } else if (playerState == CNVideoView.PLAYER_NORMAL) { | |
| 99 | + mStopFullscreen.setVisibility(GONE); | |
| 100 | + } | |
| 101 | + | |
| 102 | + Activity activity = PlayerUtils.scanForActivity(getContext()); | |
| 103 | + if (activity != null && mControlWrapper.hasCutout()) { | |
| 104 | + int orientation = activity.getRequestedOrientation(); | |
| 105 | + int cutoutHeight = mControlWrapper.getCutoutHeight(); | |
| 106 | + if (orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { | |
| 107 | + LayoutParams sflp = (LayoutParams) mStopFullscreen.getLayoutParams(); | |
| 108 | + sflp.setMargins(0, 0, 0, 0); | |
| 109 | + } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { | |
| 110 | + LayoutParams sflp = (LayoutParams) mStopFullscreen.getLayoutParams(); | |
| 111 | + sflp.setMargins(cutoutHeight, 0, 0, 0); | |
| 112 | + } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) { | |
| 113 | + LayoutParams sflp = (LayoutParams) mStopFullscreen.getLayoutParams(); | |
| 114 | + sflp.setMargins(0, 0, 0, 0); | |
| 115 | + } | |
| 116 | + } | |
| 117 | + } | |
| 118 | + | |
| 119 | + @Override | |
| 120 | + public void setProgress(int duration, int position) { | |
| 121 | + | |
| 122 | + } | |
| 123 | + | |
| 124 | + @Override | |
| 125 | + public void onLockStateChanged(boolean isLock) { | |
| 126 | + | |
| 127 | + } | |
| 128 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/component/DefaultErrorView.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.component; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.util.AttributeSet; | |
| 5 | +import android.view.LayoutInflater; | |
| 6 | +import android.view.MotionEvent; | |
| 7 | +import android.view.View; | |
| 8 | +import android.view.ViewConfiguration; | |
| 9 | +import android.view.animation.Animation; | |
| 10 | +import android.widget.LinearLayout; | |
| 11 | + | |
| 12 | +import com.cnlive.cloud.video.R; | |
| 13 | +import com.libs.video.video.exoplayer.controller.ControlWrapper; | |
| 14 | +import com.libs.video.video.exoplayer.controller.IControlComponent; | |
| 15 | +import com.libs.video.video.exoplayer.play.CNVideoView; | |
| 16 | + | |
| 17 | +import androidx.annotation.NonNull; | |
| 18 | +import androidx.annotation.Nullable; | |
| 19 | + | |
| 20 | +/** | |
| 21 | + * 播放出错提示界面 | |
| 22 | + * Created by dueeeke on 2017/4/13. | |
| 23 | + */ | |
| 24 | +public class DefaultErrorView extends LinearLayout implements IControlComponent { | |
| 25 | + | |
| 26 | + private float mDownX; | |
| 27 | + private float mDownY; | |
| 28 | + | |
| 29 | + private ControlWrapper mControlWrapper; | |
| 30 | + | |
| 31 | + public DefaultErrorView(Context context) { | |
| 32 | + this(context, null); | |
| 33 | + } | |
| 34 | + | |
| 35 | + public DefaultErrorView(Context context, @Nullable AttributeSet attrs) { | |
| 36 | + super(context, attrs); | |
| 37 | + } | |
| 38 | + | |
| 39 | + public DefaultErrorView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | |
| 40 | + super(context, attrs, defStyleAttr); | |
| 41 | + } | |
| 42 | + | |
| 43 | + { | |
| 44 | + setVisibility(GONE); | |
| 45 | + LayoutInflater.from(getContext()).inflate(R.layout.cnvideo_layout_error_view, this, true); | |
| 46 | + findViewById(R.id.status_btn).setOnClickListener(new OnClickListener() { | |
| 47 | + @Override | |
| 48 | + public void onClick(View v) { | |
| 49 | + setVisibility(GONE); | |
| 50 | + mControlWrapper.replay(false); | |
| 51 | + } | |
| 52 | + }); | |
| 53 | + setClickable(true); | |
| 54 | + } | |
| 55 | + | |
| 56 | + @Override | |
| 57 | + public void attach(@NonNull ControlWrapper controlWrapper) { | |
| 58 | + mControlWrapper = controlWrapper; | |
| 59 | + } | |
| 60 | + | |
| 61 | + @Override | |
| 62 | + public View getView() { | |
| 63 | + return this; | |
| 64 | + } | |
| 65 | + | |
| 66 | + @Override | |
| 67 | + public void onVisibilityChanged(boolean isVisible, Animation anim) { | |
| 68 | + | |
| 69 | + } | |
| 70 | + | |
| 71 | + @Override | |
| 72 | + public void onPlayStateChanged(int playState) { | |
| 73 | + if (playState == CNVideoView.STATE_ERROR) { | |
| 74 | + bringToFront(); | |
| 75 | + setVisibility(VISIBLE); | |
| 76 | + } else if (playState == CNVideoView.STATE_IDLE) { | |
| 77 | + setVisibility(GONE); | |
| 78 | + } | |
| 79 | + } | |
| 80 | + | |
| 81 | + @Override | |
| 82 | + public void onPlayerStateChanged(int playerState) { | |
| 83 | + | |
| 84 | + } | |
| 85 | + | |
| 86 | + @Override | |
| 87 | + public void setProgress(int duration, int position) { | |
| 88 | + | |
| 89 | + } | |
| 90 | + | |
| 91 | + @Override | |
| 92 | + public void onLockStateChanged(boolean isLock) { | |
| 93 | + | |
| 94 | + } | |
| 95 | + | |
| 96 | + @Override | |
| 97 | + public boolean dispatchTouchEvent(MotionEvent ev) { | |
| 98 | + switch (ev.getAction()) { | |
| 99 | + case MotionEvent.ACTION_DOWN: | |
| 100 | + mDownX = ev.getX(); | |
| 101 | + mDownY = ev.getY(); | |
| 102 | + // True if the child does not want the parent to intercept touch events. | |
| 103 | + getParent().requestDisallowInterceptTouchEvent(true); | |
| 104 | + break; | |
| 105 | + case MotionEvent.ACTION_MOVE: | |
| 106 | + float absDeltaX = Math.abs(ev.getX() - mDownX); | |
| 107 | + float absDeltaY = Math.abs(ev.getY() - mDownY); | |
| 108 | + if (absDeltaX > ViewConfiguration.get(getContext()).getScaledTouchSlop() || | |
| 109 | + absDeltaY > ViewConfiguration.get(getContext()).getScaledTouchSlop()) { | |
| 110 | + getParent().requestDisallowInterceptTouchEvent(false); | |
| 111 | + } | |
| 112 | + case MotionEvent.ACTION_UP: | |
| 113 | + break; | |
| 114 | + } | |
| 115 | + return super.dispatchTouchEvent(ev); | |
| 116 | + } | |
| 117 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/component/DefaultPrepareView.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.component; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.util.AttributeSet; | |
| 5 | +import android.view.LayoutInflater; | |
| 6 | +import android.view.View; | |
| 7 | +import android.view.animation.Animation; | |
| 8 | +import android.widget.FrameLayout; | |
| 9 | +import android.widget.ImageView; | |
| 10 | +import android.widget.ProgressBar; | |
| 11 | + | |
| 12 | +import com.cnlive.cloud.video.R; | |
| 13 | +import com.libs.video.video.exoplayer.controller.ControlWrapper; | |
| 14 | +import com.libs.video.video.exoplayer.controller.IControlComponent; | |
| 15 | +import com.libs.video.video.exoplayer.play.CNVideoView; | |
| 16 | +import com.libs.video.video.exoplayer.play.VideoViewManager; | |
| 17 | + | |
| 18 | +import androidx.annotation.NonNull; | |
| 19 | +import androidx.annotation.Nullable; | |
| 20 | + | |
| 21 | +/** | |
| 22 | + * 准备播放界面 | |
| 23 | + */ | |
| 24 | +public class DefaultPrepareView extends FrameLayout implements IControlComponent { | |
| 25 | + | |
| 26 | + private ControlWrapper mControlWrapper; | |
| 27 | + | |
| 28 | + private ImageView mThumb; | |
| 29 | + private ImageView mStartPlay; | |
| 30 | + private ProgressBar mLoading; | |
| 31 | + private FrameLayout mNetWarning; | |
| 32 | + | |
| 33 | + public DefaultPrepareView(@NonNull Context context) { | |
| 34 | + super(context); | |
| 35 | + } | |
| 36 | + | |
| 37 | + public DefaultPrepareView(@NonNull Context context, @Nullable AttributeSet attrs) { | |
| 38 | + super(context, attrs); | |
| 39 | + } | |
| 40 | + | |
| 41 | + public DefaultPrepareView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | |
| 42 | + super(context, attrs, defStyleAttr); | |
| 43 | + } | |
| 44 | + | |
| 45 | + { | |
| 46 | + LayoutInflater.from(getContext()).inflate(R.layout.cnvideo_layout_prepare_view, this, true); | |
| 47 | + mThumb = findViewById(R.id.thumb); | |
| 48 | + mStartPlay = findViewById(R.id.start_play); | |
| 49 | + mLoading = findViewById(R.id.loading); | |
| 50 | + mNetWarning = findViewById(R.id.net_warning_layout); | |
| 51 | + findViewById(R.id.status_btn).setOnClickListener(new OnClickListener() { | |
| 52 | + @Override | |
| 53 | + public void onClick(View v) { | |
| 54 | + mNetWarning.setVisibility(GONE); | |
| 55 | + VideoViewManager.instance().setPlayOnMobileNetwork(true); | |
| 56 | + mControlWrapper.start(); | |
| 57 | + } | |
| 58 | + }); | |
| 59 | + } | |
| 60 | + | |
| 61 | + /** | |
| 62 | + * 设置点击此界面开始播放 | |
| 63 | + */ | |
| 64 | + public void setClickStart() { | |
| 65 | + setOnClickListener(new OnClickListener() { | |
| 66 | + @Override | |
| 67 | + public void onClick(View v) { | |
| 68 | + mControlWrapper.start(); | |
| 69 | + } | |
| 70 | + }); | |
| 71 | + } | |
| 72 | + | |
| 73 | + @Override | |
| 74 | + public void attach(@NonNull ControlWrapper controlWrapper) { | |
| 75 | + mControlWrapper = controlWrapper; | |
| 76 | + } | |
| 77 | + | |
| 78 | + @Override | |
| 79 | + public View getView() { | |
| 80 | + return this; | |
| 81 | + } | |
| 82 | + | |
| 83 | + @Override | |
| 84 | + public void onVisibilityChanged(boolean isVisible, Animation anim) { | |
| 85 | + | |
| 86 | + } | |
| 87 | + | |
| 88 | + @Override | |
| 89 | + public void onPlayStateChanged(int playState) { | |
| 90 | + switch (playState) { | |
| 91 | + case CNVideoView.STATE_PREPARING: | |
| 92 | + bringToFront(); | |
| 93 | + setVisibility(VISIBLE); | |
| 94 | + mStartPlay.setVisibility(View.GONE); | |
| 95 | + mNetWarning.setVisibility(GONE); | |
| 96 | + mLoading.setVisibility(View.VISIBLE); | |
| 97 | + break; | |
| 98 | + case CNVideoView.STATE_PLAYING: | |
| 99 | + case CNVideoView.STATE_PAUSED: | |
| 100 | + case CNVideoView.STATE_ERROR: | |
| 101 | + case CNVideoView.STATE_BUFFERING: | |
| 102 | + case CNVideoView.STATE_BUFFERED: | |
| 103 | + case CNVideoView.STATE_PLAYBACK_COMPLETED: | |
| 104 | + setVisibility(GONE); | |
| 105 | + break; | |
| 106 | + case CNVideoView.STATE_IDLE: | |
| 107 | + setVisibility(VISIBLE); | |
| 108 | + bringToFront(); | |
| 109 | + mLoading.setVisibility(View.GONE); | |
| 110 | + mNetWarning.setVisibility(GONE); | |
| 111 | + mStartPlay.setVisibility(View.VISIBLE); | |
| 112 | + mThumb.setVisibility(View.VISIBLE); | |
| 113 | + break; | |
| 114 | + case CNVideoView.STATE_START_ABORT: | |
| 115 | + setVisibility(VISIBLE); | |
| 116 | + mNetWarning.setVisibility(VISIBLE); | |
| 117 | + mNetWarning.bringToFront(); | |
| 118 | + break; | |
| 119 | + } | |
| 120 | + } | |
| 121 | + | |
| 122 | + @Override | |
| 123 | + public void onPlayerStateChanged(int playerState) { | |
| 124 | + | |
| 125 | + } | |
| 126 | + | |
| 127 | + @Override | |
| 128 | + public void setProgress(int duration, int position) { | |
| 129 | + | |
| 130 | + } | |
| 131 | + | |
| 132 | + @Override | |
| 133 | + public void onLockStateChanged(boolean isLocked) { | |
| 134 | + | |
| 135 | + } | |
| 136 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/component/DefaultTitleView.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.component; | |
| 2 | + | |
| 3 | +import android.app.Activity; | |
| 4 | +import android.content.BroadcastReceiver; | |
| 5 | +import android.content.Context; | |
| 6 | +import android.content.Intent; | |
| 7 | +import android.content.IntentFilter; | |
| 8 | +import android.content.pm.ActivityInfo; | |
| 9 | +import android.os.Bundle; | |
| 10 | +import android.util.AttributeSet; | |
| 11 | +import android.view.LayoutInflater; | |
| 12 | +import android.view.View; | |
| 13 | +import android.view.animation.Animation; | |
| 14 | +import android.widget.FrameLayout; | |
| 15 | +import android.widget.ImageView; | |
| 16 | +import android.widget.LinearLayout; | |
| 17 | +import android.widget.TextView; | |
| 18 | + | |
| 19 | + | |
| 20 | +import com.cnlive.cloud.video.R; | |
| 21 | +import com.libs.video.video.exoplayer.controller.ControlWrapper; | |
| 22 | +import com.libs.video.video.exoplayer.controller.IControlComponent; | |
| 23 | +import com.libs.video.video.exoplayer.play.CNVideoView; | |
| 24 | +import com.libs.video.video.exoplayer.util.PlayerUtils; | |
| 25 | + | |
| 26 | +import androidx.annotation.NonNull; | |
| 27 | +import androidx.annotation.Nullable; | |
| 28 | + | |
| 29 | +/** | |
| 30 | + * 播放器顶部标题栏 | |
| 31 | + */ | |
| 32 | +public class DefaultTitleView extends FrameLayout implements IControlComponent { | |
| 33 | + | |
| 34 | + private ControlWrapper mControlWrapper; | |
| 35 | + | |
| 36 | + private LinearLayout mTitleContainer; | |
| 37 | + private MarqueeTextView mTitle; | |
| 38 | + private TextView mSysTime;//系统当前时间 | |
| 39 | + | |
| 40 | + private BatteryReceiver mBatteryReceiver; | |
| 41 | + private boolean mIsRegister;//是否注册BatteryReceiver | |
| 42 | + | |
| 43 | + public DefaultTitleView(@NonNull Context context) { | |
| 44 | + super(context); | |
| 45 | + } | |
| 46 | + | |
| 47 | + public DefaultTitleView(@NonNull Context context, @Nullable AttributeSet attrs) { | |
| 48 | + super(context, attrs); | |
| 49 | + } | |
| 50 | + | |
| 51 | + public DefaultTitleView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | |
| 52 | + super(context, attrs, defStyleAttr); | |
| 53 | + } | |
| 54 | + | |
| 55 | + { | |
| 56 | + setVisibility(GONE); | |
| 57 | + LayoutInflater.from(getContext()).inflate(R.layout.cnvideo_layout_title_view, this, true); | |
| 58 | + mTitleContainer = findViewById(R.id.title_container); | |
| 59 | + ImageView back = findViewById(R.id.back); | |
| 60 | + back.setOnClickListener(new OnClickListener() { | |
| 61 | + @Override | |
| 62 | + public void onClick(View v) { | |
| 63 | + Activity activity = PlayerUtils.scanForActivity(getContext()); | |
| 64 | + if (activity != null && mControlWrapper.isFullScreen()) { | |
| 65 | + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); | |
| 66 | + mControlWrapper.stopFullScreen(); | |
| 67 | + } | |
| 68 | + } | |
| 69 | + }); | |
| 70 | + mTitle = findViewById(R.id.title); | |
| 71 | + mSysTime = findViewById(R.id.sys_time); | |
| 72 | + //电量 | |
| 73 | + ImageView batteryLevel = findViewById(R.id.iv_battery); | |
| 74 | + mBatteryReceiver = new BatteryReceiver(batteryLevel); | |
| 75 | + } | |
| 76 | + | |
| 77 | + public void setTitle(String title) { | |
| 78 | + mTitle.setText(title); | |
| 79 | + } | |
| 80 | + | |
| 81 | + @Override | |
| 82 | + protected void onDetachedFromWindow() { | |
| 83 | + super.onDetachedFromWindow(); | |
| 84 | + if (mIsRegister) { | |
| 85 | + getContext().unregisterReceiver(mBatteryReceiver); | |
| 86 | + mIsRegister = false; | |
| 87 | + } | |
| 88 | + } | |
| 89 | + | |
| 90 | + @Override | |
| 91 | + protected void onAttachedToWindow() { | |
| 92 | + super.onAttachedToWindow(); | |
| 93 | + if (!mIsRegister) { | |
| 94 | + getContext().registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); | |
| 95 | + mIsRegister = true; | |
| 96 | + } | |
| 97 | + } | |
| 98 | + | |
| 99 | + @Override | |
| 100 | + public void attach(@NonNull ControlWrapper controlWrapper) { | |
| 101 | + mControlWrapper = controlWrapper; | |
| 102 | + } | |
| 103 | + | |
| 104 | + @Override | |
| 105 | + public View getView() { | |
| 106 | + return this; | |
| 107 | + } | |
| 108 | + | |
| 109 | + @Override | |
| 110 | + public void onVisibilityChanged(boolean isVisible, Animation anim) { | |
| 111 | + //只在全屏时才有效 | |
| 112 | + if (!mControlWrapper.isFullScreen()) return; | |
| 113 | + if (isVisible) { | |
| 114 | + if (getVisibility() == GONE) { | |
| 115 | + mSysTime.setText(PlayerUtils.getCurrentSystemTime()); | |
| 116 | + setVisibility(VISIBLE); | |
| 117 | + if (anim != null) { | |
| 118 | + startAnimation(anim); | |
| 119 | + } | |
| 120 | + } | |
| 121 | + } else { | |
| 122 | + if (getVisibility() == VISIBLE) { | |
| 123 | + if (anim != null) { | |
| 124 | + setVisibility(GONE); | |
| 125 | + startAnimation(anim); | |
| 126 | + } | |
| 127 | + } | |
| 128 | + } | |
| 129 | + } | |
| 130 | + | |
| 131 | + @Override | |
| 132 | + public void onPlayStateChanged(int playState) { | |
| 133 | + switch (playState) { | |
| 134 | + case CNVideoView.STATE_IDLE: | |
| 135 | + case CNVideoView.STATE_START_ABORT: | |
| 136 | + case CNVideoView.STATE_PREPARING: | |
| 137 | + case CNVideoView.STATE_PREPARED: | |
| 138 | + case CNVideoView.STATE_ERROR: | |
| 139 | + case CNVideoView.STATE_PLAYBACK_COMPLETED: | |
| 140 | + setVisibility(GONE); | |
| 141 | + break; | |
| 142 | + } | |
| 143 | + } | |
| 144 | + | |
| 145 | + @Override | |
| 146 | + public void onPlayerStateChanged(int playerState) { | |
| 147 | + if (playerState == CNVideoView.PLAYER_FULL_SCREEN) { | |
| 148 | + if (mControlWrapper.isShowing() && !mControlWrapper.isLocked()) { | |
| 149 | + setVisibility(VISIBLE); | |
| 150 | + mSysTime.setText(PlayerUtils.getCurrentSystemTime()); | |
| 151 | + } | |
| 152 | + mTitle.setNeedFocus(true); | |
| 153 | + } else { | |
| 154 | + setVisibility(GONE); | |
| 155 | + mTitle.setNeedFocus(false); | |
| 156 | + } | |
| 157 | + | |
| 158 | + Activity activity = PlayerUtils.scanForActivity(getContext()); | |
| 159 | + if (activity != null && mControlWrapper.hasCutout()) { | |
| 160 | + int orientation = activity.getRequestedOrientation(); | |
| 161 | + int cutoutHeight = mControlWrapper.getCutoutHeight(); | |
| 162 | + if (orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { | |
| 163 | + mTitleContainer.setPadding(0, 0, 0, 0); | |
| 164 | + } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { | |
| 165 | + mTitleContainer.setPadding(cutoutHeight, 0, 0, 0); | |
| 166 | + } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) { | |
| 167 | + mTitleContainer.setPadding(0, 0, cutoutHeight, 0); | |
| 168 | + } | |
| 169 | + } | |
| 170 | + } | |
| 171 | + | |
| 172 | + @Override | |
| 173 | + public void setProgress(int duration, int position) { | |
| 174 | + | |
| 175 | + } | |
| 176 | + | |
| 177 | + @Override | |
| 178 | + public void onLockStateChanged(boolean isLocked) { | |
| 179 | + if (isLocked) { | |
| 180 | + setVisibility(GONE); | |
| 181 | + } else { | |
| 182 | + setVisibility(VISIBLE); | |
| 183 | + mSysTime.setText(PlayerUtils.getCurrentSystemTime()); | |
| 184 | + } | |
| 185 | + } | |
| 186 | + | |
| 187 | + private static class BatteryReceiver extends BroadcastReceiver { | |
| 188 | + private ImageView pow; | |
| 189 | + | |
| 190 | + public BatteryReceiver(ImageView pow) { | |
| 191 | + this.pow = pow; | |
| 192 | + } | |
| 193 | + | |
| 194 | + @Override | |
| 195 | + public void onReceive(Context context, Intent intent) { | |
| 196 | + Bundle extras = intent.getExtras(); | |
| 197 | + if (extras == null) return; | |
| 198 | + int current = extras.getInt("level");// 获得当前电量 | |
| 199 | + int total = extras.getInt("scale");// 获得总电量 | |
| 200 | + int percent = current * 100 / total; | |
| 201 | + pow.getDrawable().setLevel(percent); | |
| 202 | + } | |
| 203 | + } | |
| 204 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/component/DefaultVodControlView.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.component; | |
| 2 | + | |
| 3 | +import android.app.Activity; | |
| 4 | +import android.content.Context; | |
| 5 | +import android.content.pm.ActivityInfo; | |
| 6 | +import android.os.Build; | |
| 7 | +import android.util.AttributeSet; | |
| 8 | +import android.view.LayoutInflater; | |
| 9 | +import android.view.View; | |
| 10 | +import android.view.ViewGroup; | |
| 11 | +import android.view.animation.AlphaAnimation; | |
| 12 | +import android.view.animation.Animation; | |
| 13 | +import android.widget.FrameLayout; | |
| 14 | +import android.widget.ImageView; | |
| 15 | +import android.widget.LinearLayout; | |
| 16 | +import android.widget.ProgressBar; | |
| 17 | +import android.widget.SeekBar; | |
| 18 | +import android.widget.TextView; | |
| 19 | + | |
| 20 | +import com.cnlive.cloud.video.R; | |
| 21 | +import com.libs.video.video.exoplayer.controller.ControlWrapper; | |
| 22 | +import com.libs.video.video.exoplayer.controller.IControlComponent; | |
| 23 | +import com.libs.video.video.exoplayer.play.CNVideoView; | |
| 24 | +import com.libs.video.video.exoplayer.util.PlayerUtils; | |
| 25 | + | |
| 26 | +import androidx.annotation.NonNull; | |
| 27 | +import androidx.annotation.Nullable; | |
| 28 | + | |
| 29 | +import static com.libs.video.video.exoplayer.util.PlayerUtils.stringForTime; | |
| 30 | + | |
| 31 | + | |
| 32 | +/** | |
| 33 | + * 点播底部控制栏 | |
| 34 | + */ | |
| 35 | +public class DefaultVodControlView extends FrameLayout implements IControlComponent, View.OnClickListener, SeekBar.OnSeekBarChangeListener { | |
| 36 | + | |
| 37 | + protected ControlWrapper mControlWrapper; | |
| 38 | + | |
| 39 | + private TextView mTotalTime, mCurrTime; | |
| 40 | + private ImageView mFullScreen; | |
| 41 | + private LinearLayout mBottomContainer; | |
| 42 | + private SeekBar mVideoProgress; | |
| 43 | + private ProgressBar mBottomProgress; | |
| 44 | + private ImageView mPlayButton; | |
| 45 | + | |
| 46 | + private boolean mIsDragging; | |
| 47 | + | |
| 48 | + private boolean mIsShowBottomProgress = true; | |
| 49 | + | |
| 50 | + public DefaultVodControlView(@NonNull Context context) { | |
| 51 | + super(context); | |
| 52 | + } | |
| 53 | + | |
| 54 | + public DefaultVodControlView(@NonNull Context context, @Nullable AttributeSet attrs) { | |
| 55 | + super(context, attrs); | |
| 56 | + } | |
| 57 | + | |
| 58 | + public DefaultVodControlView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | |
| 59 | + super(context, attrs, defStyleAttr); | |
| 60 | + } | |
| 61 | + | |
| 62 | + | |
| 63 | + { | |
| 64 | + setVisibility(GONE); | |
| 65 | + LayoutInflater.from(getContext()).inflate(getLayoutId(), this, true); | |
| 66 | + mFullScreen = findViewById(R.id.fullscreen); | |
| 67 | + mFullScreen.setOnClickListener(this); | |
| 68 | + mBottomContainer = findViewById(R.id.bottom_container); | |
| 69 | + mVideoProgress = findViewById(R.id.seekBar); | |
| 70 | + mVideoProgress.setOnSeekBarChangeListener(this); | |
| 71 | + mTotalTime = findViewById(R.id.total_time); | |
| 72 | + mCurrTime = findViewById(R.id.curr_time); | |
| 73 | + mPlayButton = findViewById(R.id.iv_play); | |
| 74 | + mPlayButton.setOnClickListener(this); | |
| 75 | + mBottomProgress = findViewById(R.id.bottom_progress); | |
| 76 | + | |
| 77 | + //5.1以下系统SeekBar高度需要设置成WRAP_CONTENT | |
| 78 | + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1) { | |
| 79 | + mVideoProgress.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT; | |
| 80 | + } | |
| 81 | + } | |
| 82 | + | |
| 83 | + protected int getLayoutId() { | |
| 84 | + return R.layout.cnvideo_layout_vod_control_view; | |
| 85 | + } | |
| 86 | + | |
| 87 | + /** | |
| 88 | + * 是否显示底部进度条,默认显示 | |
| 89 | + */ | |
| 90 | + public void showBottomProgress(boolean isShow) { | |
| 91 | + mIsShowBottomProgress = isShow; | |
| 92 | + } | |
| 93 | + | |
| 94 | + @Override | |
| 95 | + public void attach(@NonNull ControlWrapper controlWrapper) { | |
| 96 | + mControlWrapper = controlWrapper; | |
| 97 | + } | |
| 98 | + | |
| 99 | + @Override | |
| 100 | + public View getView() { | |
| 101 | + return this; | |
| 102 | + } | |
| 103 | + | |
| 104 | + @Override | |
| 105 | + public void onVisibilityChanged(boolean isVisible, Animation anim) { | |
| 106 | + if (isVisible) { | |
| 107 | + mBottomContainer.setVisibility(VISIBLE); | |
| 108 | + if (anim != null) { | |
| 109 | + mBottomContainer.startAnimation(anim); | |
| 110 | + } | |
| 111 | + if (mIsShowBottomProgress) { | |
| 112 | + mBottomProgress.setVisibility(GONE); | |
| 113 | + } | |
| 114 | + } else { | |
| 115 | + mBottomContainer.setVisibility(GONE); | |
| 116 | + if (anim != null) { | |
| 117 | + mBottomContainer.startAnimation(anim); | |
| 118 | + } | |
| 119 | + if (mIsShowBottomProgress) { | |
| 120 | + mBottomProgress.setVisibility(VISIBLE); | |
| 121 | + AlphaAnimation animation = new AlphaAnimation(0f, 1f); | |
| 122 | + animation.setDuration(300); | |
| 123 | + mBottomProgress.startAnimation(animation); | |
| 124 | + } | |
| 125 | + } | |
| 126 | + } | |
| 127 | + | |
| 128 | + @Override | |
| 129 | + public void onPlayStateChanged(int playState) { | |
| 130 | + switch (playState) { | |
| 131 | + case CNVideoView.STATE_IDLE: | |
| 132 | + case CNVideoView.STATE_PLAYBACK_COMPLETED: | |
| 133 | + setVisibility(GONE); | |
| 134 | + mBottomProgress.setProgress(0); | |
| 135 | + mBottomProgress.setSecondaryProgress(0); | |
| 136 | + mVideoProgress.setProgress(0); | |
| 137 | + mVideoProgress.setSecondaryProgress(0); | |
| 138 | + break; | |
| 139 | + case CNVideoView.STATE_START_ABORT: | |
| 140 | + case CNVideoView.STATE_PREPARING: | |
| 141 | + case CNVideoView.STATE_PREPARED: | |
| 142 | + case CNVideoView.STATE_ERROR: | |
| 143 | + setVisibility(GONE); | |
| 144 | + break; | |
| 145 | + case CNVideoView.STATE_PLAYING: | |
| 146 | + mPlayButton.setSelected(true); | |
| 147 | + if (mIsShowBottomProgress) { | |
| 148 | + if (mControlWrapper.isShowing()) { | |
| 149 | + mBottomProgress.setVisibility(GONE); | |
| 150 | + mBottomContainer.setVisibility(VISIBLE); | |
| 151 | + } else { | |
| 152 | + mBottomContainer.setVisibility(GONE); | |
| 153 | + mBottomProgress.setVisibility(VISIBLE); | |
| 154 | + } | |
| 155 | + } else { | |
| 156 | + mBottomContainer.setVisibility(GONE); | |
| 157 | + } | |
| 158 | + setVisibility(VISIBLE); | |
| 159 | + //开始刷新进度 | |
| 160 | + mControlWrapper.startProgress(); | |
| 161 | + break; | |
| 162 | + case CNVideoView.STATE_PAUSED: | |
| 163 | + mPlayButton.setSelected(false); | |
| 164 | + break; | |
| 165 | + case CNVideoView.STATE_BUFFERING: | |
| 166 | + case CNVideoView.STATE_BUFFERED: | |
| 167 | + mPlayButton.setSelected(mControlWrapper.isPlaying()); | |
| 168 | + break; | |
| 169 | + } | |
| 170 | + } | |
| 171 | + | |
| 172 | + @Override | |
| 173 | + public void onPlayerStateChanged(int playerState) { | |
| 174 | + switch (playerState) { | |
| 175 | + case CNVideoView.PLAYER_NORMAL: | |
| 176 | + mFullScreen.setSelected(false); | |
| 177 | + break; | |
| 178 | + case CNVideoView.PLAYER_FULL_SCREEN: | |
| 179 | + mFullScreen.setSelected(true); | |
| 180 | + break; | |
| 181 | + } | |
| 182 | + | |
| 183 | + Activity activity = PlayerUtils.scanForActivity(getContext()); | |
| 184 | + if (activity != null && mControlWrapper.hasCutout()) { | |
| 185 | + int orientation = activity.getRequestedOrientation(); | |
| 186 | + int cutoutHeight = mControlWrapper.getCutoutHeight(); | |
| 187 | + if (orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { | |
| 188 | + mBottomContainer.setPadding(0, 0, 0, 0); | |
| 189 | + mBottomProgress.setPadding(0, 0, 0, 0); | |
| 190 | + } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { | |
| 191 | + mBottomContainer.setPadding(cutoutHeight, 0, 0, 0); | |
| 192 | + mBottomProgress.setPadding(cutoutHeight, 0, 0, 0); | |
| 193 | + } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) { | |
| 194 | + mBottomContainer.setPadding(0, 0, cutoutHeight, 0); | |
| 195 | + mBottomProgress.setPadding(0, 0, cutoutHeight, 0); | |
| 196 | + } | |
| 197 | + } | |
| 198 | + } | |
| 199 | + | |
| 200 | + @Override | |
| 201 | + public void setProgress(int duration, int position) { | |
| 202 | + if (mIsDragging) { | |
| 203 | + return; | |
| 204 | + } | |
| 205 | + | |
| 206 | + if (mVideoProgress != null) { | |
| 207 | + if (duration > 0) { | |
| 208 | + mVideoProgress.setEnabled(true); | |
| 209 | + int pos = (int) (position * 1.0 / duration * mVideoProgress.getMax()); | |
| 210 | + mVideoProgress.setProgress(pos); | |
| 211 | + mBottomProgress.setProgress(pos); | |
| 212 | + } else { | |
| 213 | + mVideoProgress.setEnabled(false); | |
| 214 | + } | |
| 215 | + int percent = mControlWrapper.getBufferedPercentage(); | |
| 216 | + if (percent >= 95) { //解决缓冲进度不能100%问题 | |
| 217 | + mVideoProgress.setSecondaryProgress(mVideoProgress.getMax()); | |
| 218 | + mBottomProgress.setSecondaryProgress(mBottomProgress.getMax()); | |
| 219 | + } else { | |
| 220 | + mVideoProgress.setSecondaryProgress(percent * 10); | |
| 221 | + mBottomProgress.setSecondaryProgress(percent * 10); | |
| 222 | + } | |
| 223 | + } | |
| 224 | + | |
| 225 | + if (mTotalTime != null) | |
| 226 | + mTotalTime.setText(stringForTime(duration)); | |
| 227 | + if (mCurrTime != null) | |
| 228 | + mCurrTime.setText(stringForTime(position)); | |
| 229 | + } | |
| 230 | + | |
| 231 | + @Override | |
| 232 | + public void onLockStateChanged(boolean isLocked) { | |
| 233 | + onVisibilityChanged(!isLocked, null); | |
| 234 | + } | |
| 235 | + | |
| 236 | + @Override | |
| 237 | + public void onClick(View v) { | |
| 238 | + int id = v.getId(); | |
| 239 | + if (id == R.id.fullscreen) { | |
| 240 | + toggleFullScreen(); | |
| 241 | + } else if (id == R.id.iv_play) { | |
| 242 | + mControlWrapper.togglePlay(); | |
| 243 | + } | |
| 244 | + } | |
| 245 | + | |
| 246 | + /** | |
| 247 | + * 横竖屏切换 | |
| 248 | + */ | |
| 249 | + private void toggleFullScreen() { | |
| 250 | + Activity activity = PlayerUtils.scanForActivity(getContext()); | |
| 251 | + mControlWrapper.toggleFullScreen(activity); | |
| 252 | + if (mControlWrapper.isFullScreen()){ | |
| 253 | + mBottomProgress.setVisibility(GONE); | |
| 254 | + } | |
| 255 | + } | |
| 256 | + | |
| 257 | + @Override | |
| 258 | + public void onStartTrackingTouch(SeekBar seekBar) { | |
| 259 | + mIsDragging = true; | |
| 260 | + mControlWrapper.stopProgress(); | |
| 261 | + mControlWrapper.stopFadeOut(); | |
| 262 | + } | |
| 263 | + | |
| 264 | + @Override | |
| 265 | + public void onStopTrackingTouch(SeekBar seekBar) { | |
| 266 | + long duration = mControlWrapper.getDuration(); | |
| 267 | + long newPosition = (duration * seekBar.getProgress()) / mVideoProgress.getMax(); | |
| 268 | + mControlWrapper.seekTo((int) newPosition); | |
| 269 | + mIsDragging = false; | |
| 270 | + mControlWrapper.startProgress(); | |
| 271 | + mControlWrapper.startFadeOut(); | |
| 272 | + } | |
| 273 | + | |
| 274 | + @Override | |
| 275 | + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { | |
| 276 | + if (!fromUser) { | |
| 277 | + return; | |
| 278 | + } | |
| 279 | + | |
| 280 | + long duration = mControlWrapper.getDuration(); | |
| 281 | + long newPosition = (duration * progress) / mVideoProgress.getMax(); | |
| 282 | + if (mCurrTime != null) | |
| 283 | + mCurrTime.setText(stringForTime((int) newPosition)); | |
| 284 | + } | |
| 285 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/component/GestureView.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.component; | |
| 2 | + | |
| 3 | +import android.animation.Animator; | |
| 4 | +import android.animation.AnimatorListenerAdapter; | |
| 5 | +import android.content.Context; | |
| 6 | +import android.util.AttributeSet; | |
| 7 | +import android.view.LayoutInflater; | |
| 8 | +import android.view.View; | |
| 9 | +import android.view.animation.Animation; | |
| 10 | +import android.widget.FrameLayout; | |
| 11 | +import android.widget.ImageView; | |
| 12 | +import android.widget.LinearLayout; | |
| 13 | +import android.widget.ProgressBar; | |
| 14 | +import android.widget.TextView; | |
| 15 | + | |
| 16 | + | |
| 17 | +import com.cnlive.cloud.video.R; | |
| 18 | +import com.libs.video.video.exoplayer.controller.ControlWrapper; | |
| 19 | +import com.libs.video.video.exoplayer.controller.IGestureComponent; | |
| 20 | +import com.libs.video.video.exoplayer.play.CNVideoView; | |
| 21 | + | |
| 22 | +import androidx.annotation.NonNull; | |
| 23 | +import androidx.annotation.Nullable; | |
| 24 | + | |
| 25 | +import static com.libs.video.video.exoplayer.util.PlayerUtils.stringForTime; | |
| 26 | + | |
| 27 | + | |
| 28 | +/** | |
| 29 | + * 手势控制 | |
| 30 | + */ | |
| 31 | +public class GestureView extends FrameLayout implements IGestureComponent { | |
| 32 | + | |
| 33 | + public GestureView(@NonNull Context context) { | |
| 34 | + super(context); | |
| 35 | + } | |
| 36 | + | |
| 37 | + public GestureView(@NonNull Context context, @Nullable AttributeSet attrs) { | |
| 38 | + super(context, attrs); | |
| 39 | + } | |
| 40 | + | |
| 41 | + public GestureView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | |
| 42 | + super(context, attrs, defStyleAttr); | |
| 43 | + } | |
| 44 | + | |
| 45 | + private ControlWrapper mControlWrapper; | |
| 46 | + | |
| 47 | + private ImageView mIcon; | |
| 48 | + private ProgressBar mProgressPercent; | |
| 49 | + private TextView mTextPercent; | |
| 50 | + | |
| 51 | + private LinearLayout mCenterContainer; | |
| 52 | + | |
| 53 | + | |
| 54 | + { | |
| 55 | + setVisibility(GONE); | |
| 56 | + LayoutInflater.from(getContext()).inflate(R.layout.cnvideo_layout_gesture_control_view, this, true); | |
| 57 | + mIcon = findViewById(R.id.iv_icon); | |
| 58 | + mProgressPercent = findViewById(R.id.pro_percent); | |
| 59 | + mTextPercent = findViewById(R.id.tv_percent); | |
| 60 | + mCenterContainer = findViewById(R.id.center_container); | |
| 61 | + } | |
| 62 | + | |
| 63 | + @Override | |
| 64 | + public void attach(@NonNull ControlWrapper controlWrapper) { | |
| 65 | + mControlWrapper = controlWrapper; | |
| 66 | + } | |
| 67 | + | |
| 68 | + @Override | |
| 69 | + public View getView() { | |
| 70 | + return this; | |
| 71 | + } | |
| 72 | + | |
| 73 | + @Override | |
| 74 | + public void onVisibilityChanged(boolean isVisible, Animation anim) { | |
| 75 | + | |
| 76 | + } | |
| 77 | + | |
| 78 | + @Override | |
| 79 | + public void onPlayerStateChanged(int playerState) { | |
| 80 | + | |
| 81 | + } | |
| 82 | + | |
| 83 | + @Override | |
| 84 | + public void onStartSlide() { | |
| 85 | + mControlWrapper.hide(); | |
| 86 | + mCenterContainer.setVisibility(VISIBLE); | |
| 87 | + mCenterContainer.setAlpha(1f); | |
| 88 | + } | |
| 89 | + | |
| 90 | + @Override | |
| 91 | + public void onStopSlide() { | |
| 92 | + mCenterContainer.animate() | |
| 93 | + .alpha(0f) | |
| 94 | + .setDuration(300) | |
| 95 | + .setListener(new AnimatorListenerAdapter() { | |
| 96 | + @Override | |
| 97 | + public void onAnimationEnd(Animator animation) { | |
| 98 | + super.onAnimationEnd(animation); | |
| 99 | + mCenterContainer.setVisibility(GONE); | |
| 100 | + } | |
| 101 | + }) | |
| 102 | + .start(); | |
| 103 | + } | |
| 104 | + | |
| 105 | + @Override | |
| 106 | + public void onPositionChange(int slidePosition, int currentPosition, int duration) { | |
| 107 | + mProgressPercent.setVisibility(GONE); | |
| 108 | + if (slidePosition > currentPosition) { | |
| 109 | + mIcon.setImageResource(R.drawable.cnvideo_ic_action_fast_forward); | |
| 110 | + } else { | |
| 111 | + mIcon.setImageResource(R.drawable.cnvideo_ic_action_fast_rewind); | |
| 112 | + } | |
| 113 | + mTextPercent.setText(String.format("%s/%s", stringForTime(slidePosition), stringForTime(duration))); | |
| 114 | + } | |
| 115 | + | |
| 116 | + @Override | |
| 117 | + public void onBrightnessChange(int percent) { | |
| 118 | + mProgressPercent.setVisibility(VISIBLE); | |
| 119 | + mIcon.setImageResource(R.drawable.cnvideo_ic_action_brightness); | |
| 120 | + mTextPercent.setText(percent + "%"); | |
| 121 | + mProgressPercent.setProgress(percent); | |
| 122 | + } | |
| 123 | + | |
| 124 | + @Override | |
| 125 | + public void onVolumeChange(int percent) { | |
| 126 | + | |
| 127 | + mProgressPercent.setVisibility(VISIBLE); | |
| 128 | + if (percent <= 0) { | |
| 129 | + mIcon.setImageResource(R.drawable.cnvideo_ic_action_volume_off); | |
| 130 | + } else { | |
| 131 | + mIcon.setImageResource(R.drawable.cnvideo_ic_action_volume_up); | |
| 132 | + } | |
| 133 | + mTextPercent.setText(percent + "%"); | |
| 134 | + mProgressPercent.setProgress(percent); | |
| 135 | + } | |
| 136 | + | |
| 137 | + @Override | |
| 138 | + public void onPlayStateChanged(int playState) { | |
| 139 | + if (playState == CNVideoView.STATE_IDLE | |
| 140 | + || playState == CNVideoView.STATE_START_ABORT | |
| 141 | + || playState == CNVideoView.STATE_PREPARING | |
| 142 | + || playState == CNVideoView.STATE_PREPARED | |
| 143 | + || playState == CNVideoView.STATE_ERROR | |
| 144 | + || playState == CNVideoView.STATE_PLAYBACK_COMPLETED) { | |
| 145 | + setVisibility(GONE); | |
| 146 | + } else { | |
| 147 | + setVisibility(VISIBLE); | |
| 148 | + } | |
| 149 | + } | |
| 150 | + | |
| 151 | + @Override | |
| 152 | + public void setProgress(int duration, int position) { | |
| 153 | + | |
| 154 | + } | |
| 155 | + | |
| 156 | + @Override | |
| 157 | + public void onLockStateChanged(boolean isLock) { | |
| 158 | + | |
| 159 | + } | |
| 160 | + | |
| 161 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/component/LiveControlView.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.component; | |
| 2 | + | |
| 3 | +import android.app.Activity; | |
| 4 | +import android.content.Context; | |
| 5 | +import android.content.pm.ActivityInfo; | |
| 6 | +import android.util.AttributeSet; | |
| 7 | +import android.view.LayoutInflater; | |
| 8 | +import android.view.View; | |
| 9 | +import android.view.animation.Animation; | |
| 10 | +import android.widget.FrameLayout; | |
| 11 | +import android.widget.ImageView; | |
| 12 | +import android.widget.LinearLayout; | |
| 13 | + | |
| 14 | +import com.cnlive.cloud.video.R; | |
| 15 | +import com.libs.video.video.exoplayer.controller.ControlWrapper; | |
| 16 | +import com.libs.video.video.exoplayer.controller.IControlComponent; | |
| 17 | +import com.libs.video.video.exoplayer.play.CNVideoView; | |
| 18 | +import com.libs.video.video.exoplayer.util.PlayerUtils; | |
| 19 | + | |
| 20 | +import androidx.annotation.NonNull; | |
| 21 | +import androidx.annotation.Nullable; | |
| 22 | + | |
| 23 | +/** | |
| 24 | + * 直播底部控制栏 | |
| 25 | + */ | |
| 26 | +public class LiveControlView extends FrameLayout implements IControlComponent, View.OnClickListener { | |
| 27 | + | |
| 28 | + private ControlWrapper mControlWrapper; | |
| 29 | + | |
| 30 | + private ImageView mFullScreen; | |
| 31 | + private LinearLayout mBottomContainer; | |
| 32 | + private ImageView mPlayButton; | |
| 33 | + | |
| 34 | + public LiveControlView(@NonNull Context context) { | |
| 35 | + super(context); | |
| 36 | + } | |
| 37 | + | |
| 38 | + public LiveControlView(@NonNull Context context, @Nullable AttributeSet attrs) { | |
| 39 | + super(context, attrs); | |
| 40 | + } | |
| 41 | + | |
| 42 | + public LiveControlView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | |
| 43 | + super(context, attrs, defStyleAttr); | |
| 44 | + } | |
| 45 | + | |
| 46 | + | |
| 47 | + { | |
| 48 | + setVisibility(GONE); | |
| 49 | + LayoutInflater.from(getContext()).inflate(R.layout.cnvideo_layout_live_control_view, this, true); | |
| 50 | + mFullScreen = findViewById(R.id.fullscreen); | |
| 51 | + mFullScreen.setOnClickListener(this); | |
| 52 | + mBottomContainer = findViewById(R.id.bottom_container); | |
| 53 | + mPlayButton = findViewById(R.id.iv_play); | |
| 54 | + mPlayButton.setOnClickListener(this); | |
| 55 | + ImageView refresh = findViewById(R.id.iv_refresh); | |
| 56 | + refresh.setOnClickListener(this); | |
| 57 | + } | |
| 58 | + | |
| 59 | + @Override | |
| 60 | + public void attach(@NonNull ControlWrapper controlWrapper) { | |
| 61 | + mControlWrapper = controlWrapper; | |
| 62 | + } | |
| 63 | + | |
| 64 | + @Override | |
| 65 | + public View getView() { | |
| 66 | + return this; | |
| 67 | + } | |
| 68 | + | |
| 69 | + @Override | |
| 70 | + public void onVisibilityChanged(boolean isVisible, Animation anim) { | |
| 71 | + if (isVisible) { | |
| 72 | + if (getVisibility() == GONE) { | |
| 73 | + setVisibility(VISIBLE); | |
| 74 | + if (anim != null) { | |
| 75 | + startAnimation(anim); | |
| 76 | + } | |
| 77 | + } | |
| 78 | + } else { | |
| 79 | + if (getVisibility() == VISIBLE) { | |
| 80 | + setVisibility(GONE); | |
| 81 | + if (anim != null) { | |
| 82 | + startAnimation(anim); | |
| 83 | + } | |
| 84 | + } | |
| 85 | + } | |
| 86 | + } | |
| 87 | + | |
| 88 | + @Override | |
| 89 | + public void onPlayStateChanged(int playState) { | |
| 90 | + switch (playState) { | |
| 91 | + case CNVideoView.STATE_IDLE: | |
| 92 | + case CNVideoView.STATE_START_ABORT: | |
| 93 | + case CNVideoView.STATE_PREPARING: | |
| 94 | + case CNVideoView.STATE_PREPARED: | |
| 95 | + case CNVideoView.STATE_ERROR: | |
| 96 | + case CNVideoView.STATE_PLAYBACK_COMPLETED: | |
| 97 | + setVisibility(GONE); | |
| 98 | + break; | |
| 99 | + case CNVideoView.STATE_PLAYING: | |
| 100 | + mPlayButton.setSelected(true); | |
| 101 | + break; | |
| 102 | + case CNVideoView.STATE_PAUSED: | |
| 103 | + mPlayButton.setSelected(false); | |
| 104 | + break; | |
| 105 | + case CNVideoView.STATE_BUFFERING: | |
| 106 | + case CNVideoView.STATE_BUFFERED: | |
| 107 | + mPlayButton.setSelected(mControlWrapper.isPlaying()); | |
| 108 | + break; | |
| 109 | + } | |
| 110 | + } | |
| 111 | + | |
| 112 | + @Override | |
| 113 | + public void onPlayerStateChanged(int playerState) { | |
| 114 | + switch (playerState) { | |
| 115 | + case CNVideoView.PLAYER_NORMAL: | |
| 116 | + mFullScreen.setSelected(false); | |
| 117 | + break; | |
| 118 | + case CNVideoView.PLAYER_FULL_SCREEN: | |
| 119 | + mFullScreen.setSelected(true); | |
| 120 | + break; | |
| 121 | + } | |
| 122 | + | |
| 123 | + Activity activity = PlayerUtils.scanForActivity(getContext()); | |
| 124 | + if (activity != null && mControlWrapper.hasCutout()) { | |
| 125 | + int orientation = activity.getRequestedOrientation(); | |
| 126 | + int cutoutHeight = mControlWrapper.getCutoutHeight(); | |
| 127 | + if (orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { | |
| 128 | + mBottomContainer.setPadding(0, 0, 0, 0); | |
| 129 | + } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { | |
| 130 | + mBottomContainer.setPadding(cutoutHeight, 0, 0, 0); | |
| 131 | + } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) { | |
| 132 | + mBottomContainer.setPadding(0, 0, cutoutHeight, 0); | |
| 133 | + } | |
| 134 | + } | |
| 135 | + } | |
| 136 | + | |
| 137 | + @Override | |
| 138 | + public void setProgress(int duration, int position) { | |
| 139 | + | |
| 140 | + } | |
| 141 | + | |
| 142 | + @Override | |
| 143 | + public void onLockStateChanged(boolean isLocked) { | |
| 144 | + onVisibilityChanged(!isLocked, null); | |
| 145 | + } | |
| 146 | + | |
| 147 | + @Override | |
| 148 | + public void onClick(View v) { | |
| 149 | + int id = v.getId(); | |
| 150 | + if (id == R.id.fullscreen) { | |
| 151 | + toggleFullScreen(); | |
| 152 | + } else if (id == R.id.iv_play) { | |
| 153 | + mControlWrapper.togglePlay(); | |
| 154 | + } else if (id == R.id.iv_refresh) { | |
| 155 | + mControlWrapper.replay(true); | |
| 156 | + } | |
| 157 | + } | |
| 158 | + | |
| 159 | + /** | |
| 160 | + * 横竖屏切换 | |
| 161 | + */ | |
| 162 | + private void toggleFullScreen() { | |
| 163 | + Activity activity = PlayerUtils.scanForActivity(getContext()); | |
| 164 | + mControlWrapper.toggleFullScreen(activity); | |
| 165 | + } | |
| 166 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/component/MarqueeTextView.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.component; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.util.AttributeSet; | |
| 5 | +import android.widget.TextView; | |
| 6 | + | |
| 7 | +/** | |
| 8 | + * 实现跑马灯效果的TextView | |
| 9 | + */ | |
| 10 | +public class MarqueeTextView extends TextView { | |
| 11 | + private boolean mNeedFocus; | |
| 12 | + public MarqueeTextView(Context context) { | |
| 13 | + super(context); | |
| 14 | + } | |
| 15 | + public MarqueeTextView(Context context, AttributeSet attrs) { | |
| 16 | + super(context, attrs); | |
| 17 | + } | |
| 18 | + public MarqueeTextView(Context context, AttributeSet attrs, int defStyleAttr) { | |
| 19 | + super(context, attrs, defStyleAttr); | |
| 20 | + } | |
| 21 | + //返回textview是否处在选中的状态 | |
| 22 | + //而只有选中的textview才能够实现跑马灯效果 | |
| 23 | + @Override | |
| 24 | + public boolean isFocused() { | |
| 25 | + if (mNeedFocus) { | |
| 26 | + return false; | |
| 27 | + } | |
| 28 | + return super.isFocused(); | |
| 29 | + } | |
| 30 | + | |
| 31 | + public void setNeedFocus(boolean needFocus) { | |
| 32 | + mNeedFocus = needFocus; | |
| 33 | + } | |
| 34 | +} | |
| 0 | 35 | \ No newline at end of file | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/controller/BaseVideoController.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.controller; | |
| 2 | + | |
| 3 | +import android.app.Activity; | |
| 4 | +import android.content.Context; | |
| 5 | +import android.content.pm.ActivityInfo; | |
| 6 | +import android.util.AttributeSet; | |
| 7 | +import android.view.LayoutInflater; | |
| 8 | +import android.view.OrientationEventListener; | |
| 9 | +import android.view.View; | |
| 10 | +import android.view.animation.AlphaAnimation; | |
| 11 | +import android.view.animation.Animation; | |
| 12 | +import android.widget.FrameLayout; | |
| 13 | + | |
| 14 | +import com.libs.video.video.exoplayer.play.CNVideoView; | |
| 15 | +import com.libs.video.video.exoplayer.play.VideoViewManager; | |
| 16 | +import com.libs.video.video.exoplayer.util.CutoutUtil; | |
| 17 | +import com.libs.video.video.exoplayer.util.L; | |
| 18 | +import com.libs.video.video.exoplayer.util.PlayerUtils; | |
| 19 | + | |
| 20 | +import java.util.Iterator; | |
| 21 | +import java.util.LinkedHashMap; | |
| 22 | +import java.util.Map; | |
| 23 | + | |
| 24 | +import androidx.annotation.AttrRes; | |
| 25 | +import androidx.annotation.CallSuper; | |
| 26 | +import androidx.annotation.NonNull; | |
| 27 | +import androidx.annotation.Nullable; | |
| 28 | + | |
| 29 | +/** | |
| 30 | + * 控制器基类 | |
| 31 | + */ | |
| 32 | +public abstract class BaseVideoController extends FrameLayout | |
| 33 | + implements IVideoController, | |
| 34 | + OrientationHelper.OnOrientationChangeListener { | |
| 35 | + | |
| 36 | + //播放器包装类,集合了MediaPlayerControl的api和IVideoController的api | |
| 37 | + protected ControlWrapper mControlWrapper; | |
| 38 | + | |
| 39 | + @Nullable | |
| 40 | + protected Activity mActivity; | |
| 41 | + | |
| 42 | + //控制器是否处于显示状态 | |
| 43 | + protected boolean mShowing; | |
| 44 | + | |
| 45 | + //是否处于锁定状态 | |
| 46 | + protected boolean mIsLocked; | |
| 47 | + | |
| 48 | + //播放视图隐藏超时 | |
| 49 | + protected int mDefaultTimeout = 4000; | |
| 50 | + | |
| 51 | + //是否开启根据屏幕方向进入/退出全屏 | |
| 52 | + private boolean mEnableOrientation; | |
| 53 | + //屏幕方向监听辅助类 | |
| 54 | + protected OrientationHelper mOrientationHelper; | |
| 55 | + | |
| 56 | + //用户设置是否适配刘海屏 | |
| 57 | + private boolean mAdaptCutout; | |
| 58 | + //是否有刘海 | |
| 59 | + private Boolean mHasCutout; | |
| 60 | + //刘海的高度 | |
| 61 | + private int mCutoutHeight; | |
| 62 | + | |
| 63 | + //是否开始刷新进度 | |
| 64 | + private boolean mIsStartProgress; | |
| 65 | + | |
| 66 | + //保存了所有的控制组件 | |
| 67 | + protected LinkedHashMap<IControlComponent, Boolean> mControlComponents = new LinkedHashMap<>(); | |
| 68 | + | |
| 69 | + private Animation mShowAnim; | |
| 70 | + private Animation mHideAnim; | |
| 71 | + | |
| 72 | + public BaseVideoController(@NonNull Context context) { | |
| 73 | + this(context, null); | |
| 74 | + } | |
| 75 | + | |
| 76 | + public BaseVideoController(@NonNull Context context, @Nullable AttributeSet attrs) { | |
| 77 | + this(context, attrs, 0); | |
| 78 | + | |
| 79 | + } | |
| 80 | + | |
| 81 | + public BaseVideoController(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { | |
| 82 | + super(context, attrs, defStyleAttr); | |
| 83 | + initView(); | |
| 84 | + } | |
| 85 | + | |
| 86 | + protected void initView() { | |
| 87 | + if (getLayoutId() != 0) { | |
| 88 | + LayoutInflater.from(getContext()).inflate(getLayoutId(), this, true); | |
| 89 | + } | |
| 90 | + mOrientationHelper = new OrientationHelper(getContext().getApplicationContext()); | |
| 91 | + mEnableOrientation = VideoViewManager.getConfig().mEnableOrientation; | |
| 92 | + mAdaptCutout = VideoViewManager.getConfig().mAdaptCutout; | |
| 93 | + | |
| 94 | + mShowAnim = new AlphaAnimation(0f, 1f); | |
| 95 | + mShowAnim.setDuration(300); | |
| 96 | + mHideAnim = new AlphaAnimation(1f, 0f); | |
| 97 | + mHideAnim.setDuration(300); | |
| 98 | + | |
| 99 | + mActivity = PlayerUtils.scanForActivity(getContext()); | |
| 100 | + } | |
| 101 | + | |
| 102 | + /** | |
| 103 | + * 设置控制器布局文件,子类必须实现 | |
| 104 | + */ | |
| 105 | + protected abstract int getLayoutId(); | |
| 106 | + | |
| 107 | + /** | |
| 108 | + * 重要:此方法用于将{@link CNVideoView} 和控制器绑定 | |
| 109 | + */ | |
| 110 | + @CallSuper | |
| 111 | + public void setMediaPlayer(MediaPlayerControl mediaPlayer) { | |
| 112 | + mControlWrapper = new ControlWrapper(mediaPlayer, this); | |
| 113 | + //绑定ControlComponent和Controller | |
| 114 | + for (Map.Entry<IControlComponent, Boolean> next : mControlComponents.entrySet()) { | |
| 115 | + IControlComponent component = next.getKey(); | |
| 116 | + component.attach(mControlWrapper); | |
| 117 | + } | |
| 118 | + //开始监听设备方向 | |
| 119 | + mOrientationHelper.setOnOrientationChangeListener(this); | |
| 120 | + } | |
| 121 | + | |
| 122 | + /** | |
| 123 | + * 添加控制组件,最后面添加的在最下面,合理组织添加顺序,可让ControlComponent位于不同的层级 | |
| 124 | + */ | |
| 125 | + public void addControlComponent(IControlComponent... component) { | |
| 126 | + for (IControlComponent item : component) { | |
| 127 | + addControlComponent(item, false); | |
| 128 | + } | |
| 129 | + } | |
| 130 | + | |
| 131 | + /** | |
| 132 | + * 添加控制组件,最后面添加的在最下面,合理组织添加顺序,可让ControlComponent位于不同的层级 | |
| 133 | + * | |
| 134 | + * @param isPrivate 是否为独有的组件,如果是就不添加到控制器中 | |
| 135 | + */ | |
| 136 | + public void addControlComponent(IControlComponent component, boolean isPrivate) { | |
| 137 | + mControlComponents.put(component, isPrivate); | |
| 138 | + if (mControlWrapper != null) { | |
| 139 | + component.attach(mControlWrapper); | |
| 140 | + } | |
| 141 | + View view = component.getView(); | |
| 142 | + if (view != null && !isPrivate) { | |
| 143 | + addView(view, 0); | |
| 144 | + } | |
| 145 | + } | |
| 146 | + | |
| 147 | + /** | |
| 148 | + * 移除控制组件 | |
| 149 | + */ | |
| 150 | + public void removeControlComponent(IControlComponent component) { | |
| 151 | + removeView(component.getView()); | |
| 152 | + mControlComponents.remove(component); | |
| 153 | + } | |
| 154 | + | |
| 155 | + /** | |
| 156 | + * 移除所有控制组件 | |
| 157 | + */ | |
| 158 | + public void removeAllControlComponent() { | |
| 159 | + for (Map.Entry<IControlComponent, Boolean> next : mControlComponents.entrySet()) { | |
| 160 | + removeView(next.getKey().getView()); | |
| 161 | + } | |
| 162 | + mControlComponents.clear(); | |
| 163 | + } | |
| 164 | + | |
| 165 | + /** | |
| 166 | + * 移除所有私有控制组件 | |
| 167 | + */ | |
| 168 | + public void removeAllPrivateComponents() { | |
| 169 | + Iterator<Map.Entry<IControlComponent, Boolean>> it = mControlComponents.entrySet().iterator(); | |
| 170 | + while (it.hasNext()) { | |
| 171 | + Map.Entry<IControlComponent, Boolean> next = it.next(); | |
| 172 | + if (next.getValue()) { | |
| 173 | + it.remove(); | |
| 174 | + } | |
| 175 | + } | |
| 176 | + } | |
| 177 | + | |
| 178 | + /** | |
| 179 | + * {@link CNVideoView}调用此方法向控制器设置播放状态 | |
| 180 | + */ | |
| 181 | + @CallSuper | |
| 182 | + public void setPlayState(int playState) { | |
| 183 | + handlePlayStateChanged(playState); | |
| 184 | + } | |
| 185 | + | |
| 186 | + /** | |
| 187 | + * {@link CNVideoView}调用此方法向控制器设置播放器状态 | |
| 188 | + */ | |
| 189 | + @CallSuper | |
| 190 | + public void setPlayerState(final int playerState) { | |
| 191 | + handlePlayerStateChanged(playerState); | |
| 192 | + } | |
| 193 | + | |
| 194 | + /** | |
| 195 | + * 设置播放视图自动隐藏超时 | |
| 196 | + */ | |
| 197 | + public void setDismissTimeout(int timeout) { | |
| 198 | + if (timeout > 0) { | |
| 199 | + mDefaultTimeout = timeout; | |
| 200 | + } | |
| 201 | + } | |
| 202 | + | |
| 203 | + /** | |
| 204 | + * 隐藏播放视图 | |
| 205 | + */ | |
| 206 | + @Override | |
| 207 | + public void hide() { | |
| 208 | + if (mShowing) { | |
| 209 | + stopFadeOut(); | |
| 210 | + handleVisibilityChanged(false, mHideAnim); | |
| 211 | + mShowing = false; | |
| 212 | + } | |
| 213 | + } | |
| 214 | + | |
| 215 | + /** | |
| 216 | + * 显示播放视图 | |
| 217 | + */ | |
| 218 | + @Override | |
| 219 | + public void show() { | |
| 220 | + if (!mShowing) { | |
| 221 | + handleVisibilityChanged(true, mShowAnim); | |
| 222 | + startFadeOut(); | |
| 223 | + mShowing = true; | |
| 224 | + } | |
| 225 | + } | |
| 226 | + | |
| 227 | + @Override | |
| 228 | + public boolean isShowing() { | |
| 229 | + return mShowing; | |
| 230 | + } | |
| 231 | + | |
| 232 | + /** | |
| 233 | + * 开始计时 | |
| 234 | + */ | |
| 235 | + @Override | |
| 236 | + public void startFadeOut() { | |
| 237 | + //重新开始计时 | |
| 238 | + stopFadeOut(); | |
| 239 | + postDelayed(mFadeOut, mDefaultTimeout); | |
| 240 | + } | |
| 241 | + | |
| 242 | + /** | |
| 243 | + * 取消计时 | |
| 244 | + */ | |
| 245 | + @Override | |
| 246 | + public void stopFadeOut() { | |
| 247 | + removeCallbacks(mFadeOut); | |
| 248 | + } | |
| 249 | + | |
| 250 | + /** | |
| 251 | + * 隐藏播放视图Runnable | |
| 252 | + */ | |
| 253 | + public final Runnable mFadeOut = new Runnable() { | |
| 254 | + @Override | |
| 255 | + public void run() { | |
| 256 | + hide(); | |
| 257 | + } | |
| 258 | + }; | |
| 259 | + | |
| 260 | + @Override | |
| 261 | + public void setLocked(boolean locked) { | |
| 262 | + mIsLocked = locked; | |
| 263 | + handleLockStateChanged(locked); | |
| 264 | + } | |
| 265 | + | |
| 266 | + @Override | |
| 267 | + public boolean isLocked() { | |
| 268 | + return mIsLocked; | |
| 269 | + } | |
| 270 | + | |
| 271 | + /** | |
| 272 | + * 开始刷新进度,注意:需在STATE_PLAYING时调用才会开始刷新进度 | |
| 273 | + */ | |
| 274 | + @Override | |
| 275 | + public void startProgress() { | |
| 276 | + if (mIsStartProgress) return; | |
| 277 | + post(mShowProgress); | |
| 278 | + mIsStartProgress = true; | |
| 279 | + } | |
| 280 | + | |
| 281 | + /** | |
| 282 | + * 停止刷新进度 | |
| 283 | + */ | |
| 284 | + @Override | |
| 285 | + public void stopProgress() { | |
| 286 | + if (!mIsStartProgress) return; | |
| 287 | + removeCallbacks(mShowProgress); | |
| 288 | + mIsStartProgress = false; | |
| 289 | + } | |
| 290 | + | |
| 291 | + /** | |
| 292 | + * 刷新进度Runnable | |
| 293 | + */ | |
| 294 | + private Runnable mShowProgress = new Runnable() { | |
| 295 | + @Override | |
| 296 | + public void run() { | |
| 297 | + int pos = setProgress(); | |
| 298 | + if (mControlWrapper.isPlaying()) { | |
| 299 | + postDelayed(mShowProgress, 1000 - (pos % 1000)); | |
| 300 | + } else { | |
| 301 | + mIsStartProgress = false; | |
| 302 | + } | |
| 303 | + } | |
| 304 | + }; | |
| 305 | + | |
| 306 | + private int setProgress() { | |
| 307 | + int position = (int) mControlWrapper.getCurrentPosition(); | |
| 308 | + int duration = (int) mControlWrapper.getDuration(); | |
| 309 | + handleSetProgress(duration, position); | |
| 310 | + return position; | |
| 311 | + } | |
| 312 | + | |
| 313 | + /** | |
| 314 | + * 设置是否适配刘海屏 | |
| 315 | + */ | |
| 316 | + public void setAdaptCutout(boolean adaptCutout) { | |
| 317 | + mAdaptCutout = adaptCutout; | |
| 318 | + } | |
| 319 | + | |
| 320 | + @Override | |
| 321 | + protected void onAttachedToWindow() { | |
| 322 | + super.onAttachedToWindow(); | |
| 323 | + checkCutout(); | |
| 324 | + } | |
| 325 | + | |
| 326 | + /** | |
| 327 | + * 检查是否需要适配刘海 | |
| 328 | + */ | |
| 329 | + private void checkCutout() { | |
| 330 | + if (!mAdaptCutout) return; | |
| 331 | + if (mActivity != null && mHasCutout == null) { | |
| 332 | + mHasCutout = CutoutUtil.allowDisplayToCutout(mActivity); | |
| 333 | + if (mHasCutout) { | |
| 334 | + //竖屏下的状态栏高度可认为是刘海的高度 | |
| 335 | + mCutoutHeight = (int) PlayerUtils.getStatusBarHeightPortrait(mActivity); | |
| 336 | + } | |
| 337 | + } | |
| 338 | + L.d("hasCutout: " + mHasCutout + " cutout height: " + mCutoutHeight); | |
| 339 | + } | |
| 340 | + | |
| 341 | + /** | |
| 342 | + * 是否有刘海屏 | |
| 343 | + */ | |
| 344 | + @Override | |
| 345 | + public boolean hasCutout() { | |
| 346 | + return mHasCutout != null && mHasCutout; | |
| 347 | + } | |
| 348 | + | |
| 349 | + /** | |
| 350 | + * 刘海的高度 | |
| 351 | + */ | |
| 352 | + @Override | |
| 353 | + public int getCutoutHeight() { | |
| 354 | + return mCutoutHeight; | |
| 355 | + } | |
| 356 | + | |
| 357 | + /** | |
| 358 | + * 显示移动网络播放提示 | |
| 359 | + * | |
| 360 | + * @return 返回显示移动网络播放提示的条件,false:不显示, true显示 | |
| 361 | + * 此处默认根据手机网络类型来决定是否显示,开发者可以重写相关逻辑 | |
| 362 | + */ | |
| 363 | + public boolean showNetWarning() { | |
| 364 | + return PlayerUtils.getNetworkType(getContext()) == PlayerUtils.NETWORK_MOBILE | |
| 365 | + && !VideoViewManager.instance().playOnMobileNetwork(); | |
| 366 | + } | |
| 367 | + | |
| 368 | + /** | |
| 369 | + * 播放和暂停 | |
| 370 | + */ | |
| 371 | + protected void togglePlay() { | |
| 372 | + mControlWrapper.togglePlay(); | |
| 373 | + } | |
| 374 | + | |
| 375 | + /** | |
| 376 | + * 横竖屏切换 | |
| 377 | + */ | |
| 378 | + protected void toggleFullScreen() { | |
| 379 | + mControlWrapper.toggleFullScreen(mActivity); | |
| 380 | + } | |
| 381 | + | |
| 382 | + /** | |
| 383 | + * 子类中请使用此方法来进入全屏 | |
| 384 | + * | |
| 385 | + * @return 是否成功进入全屏 | |
| 386 | + */ | |
| 387 | + protected boolean startFullScreen() { | |
| 388 | + if (mActivity == null || mActivity.isFinishing()) return false; | |
| 389 | + mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); | |
| 390 | + mControlWrapper.startFullScreen(); | |
| 391 | + return true; | |
| 392 | + } | |
| 393 | + | |
| 394 | + /** | |
| 395 | + * 子类中请使用此方法来退出全屏 | |
| 396 | + * | |
| 397 | + * @return 是否成功退出全屏 | |
| 398 | + */ | |
| 399 | + protected boolean stopFullScreen() { | |
| 400 | + if (mActivity == null || mActivity.isFinishing()) return false; | |
| 401 | + mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); | |
| 402 | + mControlWrapper.stopFullScreen(); | |
| 403 | + return true; | |
| 404 | + } | |
| 405 | + | |
| 406 | + /** | |
| 407 | + * 改变返回键逻辑,用于activity | |
| 408 | + */ | |
| 409 | + public boolean onBackPressed() { | |
| 410 | + return false; | |
| 411 | + } | |
| 412 | + | |
| 413 | + @Override | |
| 414 | + public void onWindowFocusChanged(boolean hasWindowFocus) { | |
| 415 | + super.onWindowFocusChanged(hasWindowFocus); | |
| 416 | + if (mControlWrapper.isPlaying() | |
| 417 | + && (mEnableOrientation || mControlWrapper.isFullScreen())) { | |
| 418 | + if (hasWindowFocus) { | |
| 419 | + postDelayed(new Runnable() { | |
| 420 | + @Override | |
| 421 | + public void run() { | |
| 422 | + mOrientationHelper.enable(); | |
| 423 | + } | |
| 424 | + }, 800); | |
| 425 | + } else { | |
| 426 | + mOrientationHelper.disable(); | |
| 427 | + } | |
| 428 | + } | |
| 429 | + } | |
| 430 | + | |
| 431 | + /** | |
| 432 | + * 是否自动旋转, 默认不自动旋转 | |
| 433 | + */ | |
| 434 | + public void setEnableOrientation(boolean enableOrientation) { | |
| 435 | + mEnableOrientation = enableOrientation; | |
| 436 | + } | |
| 437 | + | |
| 438 | + private int mOrientation = 0; | |
| 439 | + | |
| 440 | + @CallSuper | |
| 441 | + @Override | |
| 442 | + public void onOrientationChanged(int orientation) { | |
| 443 | + if (mActivity == null || mActivity.isFinishing()) return; | |
| 444 | + | |
| 445 | + //记录用户手机上一次放置的位置 | |
| 446 | + int lastOrientation = mOrientation; | |
| 447 | + | |
| 448 | + if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) { | |
| 449 | + //手机平放时,检测不到有效的角度 | |
| 450 | + //重置为原始位置 -1 | |
| 451 | + mOrientation = -1; | |
| 452 | + return; | |
| 453 | + } | |
| 454 | + | |
| 455 | + if (orientation > 350 || orientation < 10) { | |
| 456 | + int o = mActivity.getRequestedOrientation(); | |
| 457 | + //手动切换横竖屏 | |
| 458 | + if (o == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE && lastOrientation == 0) return; | |
| 459 | + if (mOrientation == 0) return; | |
| 460 | + //0度,用户竖直拿着手机 | |
| 461 | + mOrientation = 0; | |
| 462 | + onOrientationPortrait(mActivity); | |
| 463 | + } else if (orientation > 80 && orientation < 100) { | |
| 464 | + | |
| 465 | + int o = mActivity.getRequestedOrientation(); | |
| 466 | + //手动切换横竖屏 | |
| 467 | + if (o == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT && lastOrientation == 90) return; | |
| 468 | + if (mOrientation == 90) return; | |
| 469 | + //90度,用户右侧横屏拿着手机 | |
| 470 | + mOrientation = 90; | |
| 471 | + onOrientationReverseLandscape(mActivity); | |
| 472 | + } else if (orientation > 260 && orientation < 280) { | |
| 473 | + int o = mActivity.getRequestedOrientation(); | |
| 474 | + //手动切换横竖屏 | |
| 475 | + if (o == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT && lastOrientation == 270) return; | |
| 476 | + if (mOrientation == 270) return; | |
| 477 | + //270度,用户左侧横屏拿着手机 | |
| 478 | + mOrientation = 270; | |
| 479 | + onOrientationLandscape(mActivity); | |
| 480 | + } | |
| 481 | + } | |
| 482 | + | |
| 483 | + /** | |
| 484 | + * 竖屏 | |
| 485 | + */ | |
| 486 | + protected void onOrientationPortrait(Activity activity) { | |
| 487 | + //屏幕锁定的情况 | |
| 488 | + if (mIsLocked) return; | |
| 489 | + //没有开启设备方向监听的情况 | |
| 490 | + if (!mEnableOrientation) return; | |
| 491 | + | |
| 492 | + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); | |
| 493 | + mControlWrapper.stopFullScreen(); | |
| 494 | + } | |
| 495 | + | |
| 496 | + /** | |
| 497 | + * 横屏 | |
| 498 | + */ | |
| 499 | + protected void onOrientationLandscape(Activity activity) { | |
| 500 | + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); | |
| 501 | + if (mControlWrapper.isFullScreen()) { | |
| 502 | + handlePlayerStateChanged(CNVideoView.PLAYER_FULL_SCREEN); | |
| 503 | + } else { | |
| 504 | + mControlWrapper.startFullScreen(); | |
| 505 | + } | |
| 506 | + } | |
| 507 | + | |
| 508 | + /** | |
| 509 | + * 反向横屏 | |
| 510 | + */ | |
| 511 | + protected void onOrientationReverseLandscape(Activity activity) { | |
| 512 | + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE); | |
| 513 | + if (mControlWrapper.isFullScreen()) { | |
| 514 | + handlePlayerStateChanged(CNVideoView.PLAYER_FULL_SCREEN); | |
| 515 | + } else { | |
| 516 | + mControlWrapper.startFullScreen(); | |
| 517 | + } | |
| 518 | + } | |
| 519 | + | |
| 520 | + //------------------------ start handle event change ------------------------// | |
| 521 | + | |
| 522 | + private void handleVisibilityChanged(boolean isVisible, Animation anim) { | |
| 523 | + if (!mIsLocked) { //没锁住时才向ControlComponent下发此事件 | |
| 524 | + for (Map.Entry<IControlComponent, Boolean> next | |
| 525 | + : mControlComponents.entrySet()) { | |
| 526 | + IControlComponent component = next.getKey(); | |
| 527 | + component.onVisibilityChanged(isVisible, anim); | |
| 528 | + } | |
| 529 | + } | |
| 530 | + onVisibilityChanged(isVisible, anim); | |
| 531 | + } | |
| 532 | + | |
| 533 | + /** | |
| 534 | + * 子类重写此方法监听控制的显示和隐藏 | |
| 535 | + * | |
| 536 | + * @param isVisible 是否可见 | |
| 537 | + * @param anim 显示/隐藏动画 | |
| 538 | + */ | |
| 539 | + protected void onVisibilityChanged(boolean isVisible, Animation anim) { | |
| 540 | + | |
| 541 | + } | |
| 542 | + | |
| 543 | + private void handlePlayStateChanged(int playState) { | |
| 544 | + for (Map.Entry<IControlComponent, Boolean> next | |
| 545 | + : mControlComponents.entrySet()) { | |
| 546 | + IControlComponent component = next.getKey(); | |
| 547 | + component.onPlayStateChanged(playState); | |
| 548 | + } | |
| 549 | + onPlayStateChanged(playState); | |
| 550 | + } | |
| 551 | + | |
| 552 | + /** | |
| 553 | + * 子类重写此方法并在其中更新控制器在不同播放状态下的ui | |
| 554 | + */ | |
| 555 | + @CallSuper | |
| 556 | + protected void onPlayStateChanged(int playState) { | |
| 557 | + switch (playState) { | |
| 558 | + case CNVideoView.STATE_IDLE: | |
| 559 | + mOrientationHelper.disable(); | |
| 560 | + mOrientation = 0; | |
| 561 | + mIsLocked = false; | |
| 562 | + mShowing = false; | |
| 563 | + removeAllPrivateComponents(); | |
| 564 | + break; | |
| 565 | + case CNVideoView.STATE_PLAYBACK_COMPLETED: | |
| 566 | + mIsLocked = false; | |
| 567 | + mShowing = false; | |
| 568 | + break; | |
| 569 | + case CNVideoView.STATE_ERROR: | |
| 570 | + mShowing = false; | |
| 571 | + break; | |
| 572 | + } | |
| 573 | + } | |
| 574 | + | |
| 575 | + private void handlePlayerStateChanged(int playerState) { | |
| 576 | + for (Map.Entry<IControlComponent, Boolean> next | |
| 577 | + : mControlComponents.entrySet()) { | |
| 578 | + IControlComponent component = next.getKey(); | |
| 579 | + component.onPlayerStateChanged(playerState); | |
| 580 | + } | |
| 581 | + onPlayerStateChanged(playerState); | |
| 582 | + } | |
| 583 | + | |
| 584 | + /** | |
| 585 | + * 子类重写此方法并在其中更新控制器在不同播放器状态下的ui | |
| 586 | + */ | |
| 587 | + @CallSuper | |
| 588 | + protected void onPlayerStateChanged(int playerState) { | |
| 589 | + switch (playerState) { | |
| 590 | + case CNVideoView.PLAYER_NORMAL: | |
| 591 | + if (mEnableOrientation) { | |
| 592 | + mOrientationHelper.enable(); | |
| 593 | + } else { | |
| 594 | + mOrientationHelper.disable(); | |
| 595 | + } | |
| 596 | + if (hasCutout()) { | |
| 597 | + CutoutUtil.adaptCutoutAboveAndroidP(getContext(), false); | |
| 598 | + } | |
| 599 | + break; | |
| 600 | + case CNVideoView.PLAYER_FULL_SCREEN: | |
| 601 | + //在全屏时强制监听设备方向 | |
| 602 | + mOrientationHelper.enable(); | |
| 603 | + if (hasCutout()) { | |
| 604 | + CutoutUtil.adaptCutoutAboveAndroidP(getContext(), true); | |
| 605 | + } | |
| 606 | + break; | |
| 607 | + case CNVideoView.PLAYER_TINY_SCREEN: | |
| 608 | + mOrientationHelper.disable(); | |
| 609 | + break; | |
| 610 | + } | |
| 611 | + } | |
| 612 | + | |
| 613 | + private void handleSetProgress(int duration, int position) { | |
| 614 | + for (Map.Entry<IControlComponent, Boolean> next | |
| 615 | + : mControlComponents.entrySet()) { | |
| 616 | + IControlComponent component = next.getKey(); | |
| 617 | + component.setProgress(duration, position); | |
| 618 | + } | |
| 619 | + setProgress(duration, position); | |
| 620 | + } | |
| 621 | + | |
| 622 | + /** | |
| 623 | + * 刷新进度回调,子类可在此方法监听进度刷新,然后更新ui | |
| 624 | + * | |
| 625 | + * @param duration 视频总时长 | |
| 626 | + * @param position 视频当前时长 | |
| 627 | + */ | |
| 628 | + protected void setProgress(int duration, int position) { | |
| 629 | + | |
| 630 | + } | |
| 631 | + | |
| 632 | + private void handleLockStateChanged(boolean isLocked) { | |
| 633 | + for (Map.Entry<IControlComponent, Boolean> next | |
| 634 | + : mControlComponents.entrySet()) { | |
| 635 | + IControlComponent component = next.getKey(); | |
| 636 | + component.onLockStateChanged(isLocked); | |
| 637 | + } | |
| 638 | + onLockStateChanged(isLocked); | |
| 639 | + } | |
| 640 | + | |
| 641 | + /** | |
| 642 | + * 子类可重写此方法监听锁定状态发生改变,然后更新ui | |
| 643 | + */ | |
| 644 | + protected void onLockStateChanged(boolean isLocked) { | |
| 645 | + | |
| 646 | + } | |
| 647 | + | |
| 648 | + //------------------------ end handle event change ------------------------// | |
| 649 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/controller/ControlWrapper.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.controller; | |
| 2 | + | |
| 3 | +import android.app.Activity; | |
| 4 | +import android.content.pm.ActivityInfo; | |
| 5 | +import android.graphics.Bitmap; | |
| 6 | + | |
| 7 | +import androidx.annotation.NonNull; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * 此类的目的是为了在ControlComponent中既能调用VideoView的api又能调用BaseVideoController的api, | |
| 11 | + * 并对部分api做了封装,方便使用 | |
| 12 | + */ | |
| 13 | +public class ControlWrapper implements MediaPlayerControl, IVideoController { | |
| 14 | + | |
| 15 | + private MediaPlayerControl mPlayerControl; | |
| 16 | + private IVideoController mController; | |
| 17 | + | |
| 18 | + public ControlWrapper(@NonNull MediaPlayerControl playerControl, @NonNull IVideoController controller) { | |
| 19 | + mPlayerControl = playerControl; | |
| 20 | + mController = controller; | |
| 21 | + } | |
| 22 | + | |
| 23 | + @Override | |
| 24 | + public int getMediaCount() { | |
| 25 | + return mPlayerControl.getMediaCount(); | |
| 26 | + } | |
| 27 | + | |
| 28 | + @Override | |
| 29 | + public void start() { | |
| 30 | + mPlayerControl.start(); | |
| 31 | + } | |
| 32 | + | |
| 33 | + @Override | |
| 34 | + public void pause() { | |
| 35 | + mPlayerControl.pause(); | |
| 36 | + } | |
| 37 | + | |
| 38 | + @Override | |
| 39 | + public long getDuration() { | |
| 40 | + return mPlayerControl.getDuration(); | |
| 41 | + } | |
| 42 | + | |
| 43 | + @Override | |
| 44 | + public long getCurrentPosition() { | |
| 45 | + return mPlayerControl.getCurrentPosition(); | |
| 46 | + } | |
| 47 | + | |
| 48 | + @Override | |
| 49 | + public void seekTo(long pos) { | |
| 50 | + mPlayerControl.seekTo(pos); | |
| 51 | + } | |
| 52 | + | |
| 53 | + @Override | |
| 54 | + public boolean isPlaying() { | |
| 55 | + return mPlayerControl.isPlaying(); | |
| 56 | + } | |
| 57 | + | |
| 58 | + @Override | |
| 59 | + public int getBufferedPercentage() { | |
| 60 | + return mPlayerControl.getBufferedPercentage(); | |
| 61 | + } | |
| 62 | + | |
| 63 | + @Override | |
| 64 | + public void startFullScreen() { | |
| 65 | + mPlayerControl.startFullScreen(); | |
| 66 | + } | |
| 67 | + | |
| 68 | + @Override | |
| 69 | + public void stopFullScreen() { | |
| 70 | + mPlayerControl.stopFullScreen(); | |
| 71 | + } | |
| 72 | + | |
| 73 | + @Override | |
| 74 | + public boolean isFullScreen() { | |
| 75 | + return mPlayerControl.isFullScreen(); | |
| 76 | + } | |
| 77 | + | |
| 78 | + /** | |
| 79 | + * 设置是否静音 | |
| 80 | + * | |
| 81 | + * @param isMute | |
| 82 | + */ | |
| 83 | + @Override | |
| 84 | + public void setMute(boolean isMute) { | |
| 85 | + mPlayerControl.setMute(isMute); | |
| 86 | + } | |
| 87 | + | |
| 88 | + @Override | |
| 89 | + public boolean isMute() { | |
| 90 | + return mPlayerControl.isMute(); | |
| 91 | + } | |
| 92 | + | |
| 93 | + @Override | |
| 94 | + public void setScreenScaleType(int screenScaleType) { | |
| 95 | + mPlayerControl.setScreenScaleType(screenScaleType); | |
| 96 | + } | |
| 97 | + | |
| 98 | + @Override | |
| 99 | + public void setSpeed(float speed) { | |
| 100 | + mPlayerControl.setSpeed(speed); | |
| 101 | + } | |
| 102 | + | |
| 103 | + @Override | |
| 104 | + public long getTcpSpeed() { | |
| 105 | + return mPlayerControl.getTcpSpeed(); | |
| 106 | + } | |
| 107 | + | |
| 108 | + @Override | |
| 109 | + public void replay(boolean resetPosition) { | |
| 110 | + mPlayerControl.replay(resetPosition); | |
| 111 | + } | |
| 112 | + | |
| 113 | + @Override | |
| 114 | + public void setMirrorRotation(boolean enable) { | |
| 115 | + mPlayerControl.setMirrorRotation(enable); | |
| 116 | + } | |
| 117 | + | |
| 118 | + @Override | |
| 119 | + public Bitmap doScreenShot() { | |
| 120 | + return mPlayerControl.doScreenShot(); | |
| 121 | + } | |
| 122 | + | |
| 123 | + @Override | |
| 124 | + public int[] getVideoSize() { | |
| 125 | + return mPlayerControl.getVideoSize(); | |
| 126 | + } | |
| 127 | + | |
| 128 | + @Override | |
| 129 | + public void setRotation(float rotation) { | |
| 130 | + mPlayerControl.setRotation(rotation); | |
| 131 | + } | |
| 132 | + | |
| 133 | + @Override | |
| 134 | + public void startTinyScreen() { | |
| 135 | + mPlayerControl.startTinyScreen(); | |
| 136 | + } | |
| 137 | + | |
| 138 | + @Override | |
| 139 | + public void stopTinyScreen() { | |
| 140 | + mPlayerControl.stopTinyScreen(); | |
| 141 | + } | |
| 142 | + | |
| 143 | + @Override | |
| 144 | + public boolean isTinyScreen() { | |
| 145 | + return mPlayerControl.isTinyScreen(); | |
| 146 | + } | |
| 147 | + | |
| 148 | + /** | |
| 149 | + * 播放和暂停 | |
| 150 | + */ | |
| 151 | + public void togglePlay() { | |
| 152 | + if (isPlaying()) { | |
| 153 | + pause(); | |
| 154 | + } else { | |
| 155 | + start(); | |
| 156 | + } | |
| 157 | + } | |
| 158 | + | |
| 159 | + /** | |
| 160 | + * 横竖屏切换,会旋转屏幕 | |
| 161 | + */ | |
| 162 | + public void toggleFullScreen(Activity activity) { | |
| 163 | + if (activity == null || activity.isFinishing()) | |
| 164 | + return; | |
| 165 | + if (isFullScreen()) { | |
| 166 | + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); | |
| 167 | + stopFullScreen(); | |
| 168 | + } else { | |
| 169 | + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); | |
| 170 | + startFullScreen(); | |
| 171 | + } | |
| 172 | + } | |
| 173 | + | |
| 174 | + /** | |
| 175 | + * 横竖屏切换,不会旋转屏幕 | |
| 176 | + */ | |
| 177 | + public void toggleFullScreen() { | |
| 178 | + if (isFullScreen()) { | |
| 179 | + stopFullScreen(); | |
| 180 | + } else { | |
| 181 | + startFullScreen(); | |
| 182 | + } | |
| 183 | + } | |
| 184 | + | |
| 185 | + /** | |
| 186 | + * 横竖屏切换,根据适配宽高决定是否旋转屏幕 | |
| 187 | + */ | |
| 188 | + public void toggleFullScreenByVideoSize(Activity activity) { | |
| 189 | + if (activity == null || activity.isFinishing()) | |
| 190 | + return; | |
| 191 | + int[] size = getVideoSize(); | |
| 192 | + int width = size[0]; | |
| 193 | + int height = size[1]; | |
| 194 | + if (isFullScreen()) { | |
| 195 | + stopFullScreen(); | |
| 196 | + if (width > height) { | |
| 197 | + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); | |
| 198 | + } | |
| 199 | + } else { | |
| 200 | + startFullScreen(); | |
| 201 | + if (width > height) { | |
| 202 | + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); | |
| 203 | + } | |
| 204 | + } | |
| 205 | + } | |
| 206 | + | |
| 207 | + @Override | |
| 208 | + public void startFadeOut() { | |
| 209 | + mController.startFadeOut(); | |
| 210 | + } | |
| 211 | + | |
| 212 | + @Override | |
| 213 | + public void stopFadeOut() { | |
| 214 | + mController.stopFadeOut(); | |
| 215 | + } | |
| 216 | + | |
| 217 | + @Override | |
| 218 | + public boolean isShowing() { | |
| 219 | + return mController.isShowing(); | |
| 220 | + } | |
| 221 | + | |
| 222 | + @Override | |
| 223 | + public void setLocked(boolean locked) { | |
| 224 | + mController.setLocked(locked); | |
| 225 | + } | |
| 226 | + | |
| 227 | + @Override | |
| 228 | + public boolean isLocked() { | |
| 229 | + return mController.isLocked(); | |
| 230 | + } | |
| 231 | + | |
| 232 | + @Override | |
| 233 | + public void startProgress() { | |
| 234 | + mController.startProgress(); | |
| 235 | + } | |
| 236 | + | |
| 237 | + @Override | |
| 238 | + public void stopProgress() { | |
| 239 | + mController.stopProgress(); | |
| 240 | + } | |
| 241 | + | |
| 242 | + @Override | |
| 243 | + public void hide() { | |
| 244 | + mController.hide(); | |
| 245 | + } | |
| 246 | + | |
| 247 | + @Override | |
| 248 | + public void show() { | |
| 249 | + mController.show(); | |
| 250 | + } | |
| 251 | + | |
| 252 | + @Override | |
| 253 | + public boolean hasCutout() { | |
| 254 | + return mController.hasCutout(); | |
| 255 | + } | |
| 256 | + | |
| 257 | + @Override | |
| 258 | + public int getCutoutHeight() { | |
| 259 | + return mController.getCutoutHeight(); | |
| 260 | + } | |
| 261 | + | |
| 262 | + /** | |
| 263 | + * 切换锁定状态 | |
| 264 | + */ | |
| 265 | + public void toggleLockState() { | |
| 266 | + setLocked(!isLocked()); | |
| 267 | + } | |
| 268 | + | |
| 269 | + | |
| 270 | + /** | |
| 271 | + * 切换显示/隐藏状态 | |
| 272 | + */ | |
| 273 | + public void toggleShowState() { | |
| 274 | + if (isShowing()) { | |
| 275 | + hide(); | |
| 276 | + } else { | |
| 277 | + show(); | |
| 278 | + } | |
| 279 | + } | |
| 280 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/controller/GestureVideoController.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.controller; | |
| 2 | + | |
| 3 | +import android.app.Activity; | |
| 4 | +import android.content.Context; | |
| 5 | +import android.media.AudioManager; | |
| 6 | +import android.util.AttributeSet; | |
| 7 | +import android.view.GestureDetector; | |
| 8 | +import android.view.MotionEvent; | |
| 9 | +import android.view.View; | |
| 10 | +import android.view.Window; | |
| 11 | +import android.view.WindowManager; | |
| 12 | + | |
| 13 | + | |
| 14 | +import com.libs.video.video.exoplayer.play.CNVideoView; | |
| 15 | +import com.libs.video.video.exoplayer.util.PlayerUtils; | |
| 16 | + | |
| 17 | +import java.util.Map; | |
| 18 | + | |
| 19 | +import androidx.annotation.NonNull; | |
| 20 | +import androidx.annotation.Nullable; | |
| 21 | + | |
| 22 | +/** | |
| 23 | + * 包含手势操作的VideoController | |
| 24 | + */ | |
| 25 | + | |
| 26 | +public abstract class GestureVideoController extends BaseVideoController implements | |
| 27 | + GestureDetector.OnGestureListener, | |
| 28 | + GestureDetector.OnDoubleTapListener, | |
| 29 | + View.OnTouchListener { | |
| 30 | + | |
| 31 | + private GestureDetector mGestureDetector; | |
| 32 | + private AudioManager mAudioManager; | |
| 33 | + private boolean mIsGestureEnabled = true; | |
| 34 | + private int mStreamVolume; | |
| 35 | + private float mBrightness; | |
| 36 | + private int mPosition; | |
| 37 | + private boolean mNeedSeek; | |
| 38 | + private boolean mFirstTouch; | |
| 39 | + private boolean mChangePosition; | |
| 40 | + private boolean mChangeBrightness; | |
| 41 | + private boolean mChangeVolume; | |
| 42 | + | |
| 43 | + private boolean mCanChangePosition = true; | |
| 44 | + | |
| 45 | + private boolean mEnableInNormal; | |
| 46 | + | |
| 47 | + private boolean mCanSlide; | |
| 48 | + | |
| 49 | + private int mCurPlayState; | |
| 50 | + | |
| 51 | + | |
| 52 | + public GestureVideoController(@NonNull Context context) { | |
| 53 | + super(context); | |
| 54 | + } | |
| 55 | + | |
| 56 | + public GestureVideoController(@NonNull Context context, @Nullable AttributeSet attrs) { | |
| 57 | + super(context, attrs); | |
| 58 | + } | |
| 59 | + | |
| 60 | + public GestureVideoController(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | |
| 61 | + super(context, attrs, defStyleAttr); | |
| 62 | + } | |
| 63 | + | |
| 64 | + @Override | |
| 65 | + protected void initView() { | |
| 66 | + super.initView(); | |
| 67 | + mAudioManager = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE); | |
| 68 | + mGestureDetector = new GestureDetector(getContext(), this); | |
| 69 | + setOnTouchListener(this); | |
| 70 | + } | |
| 71 | + | |
| 72 | + /** | |
| 73 | + * 设置是否可以滑动调节进度,默认可以 | |
| 74 | + */ | |
| 75 | + public void setCanChangePosition(boolean canChangePosition) { | |
| 76 | + mCanChangePosition = canChangePosition; | |
| 77 | + } | |
| 78 | + | |
| 79 | + /** | |
| 80 | + * 是否在竖屏模式下开始手势控制,默认关闭 | |
| 81 | + */ | |
| 82 | + public void setEnableInNormal(boolean enableInNormal) { | |
| 83 | + mEnableInNormal = enableInNormal; | |
| 84 | + } | |
| 85 | + | |
| 86 | + /** | |
| 87 | + * 是否开启手势空控制,默认开启,关闭之后,双击播放暂停以及手势调节进度,音量,亮度功能将关闭 | |
| 88 | + */ | |
| 89 | + public void setGestureEnabled(boolean gestureEnabled) { | |
| 90 | + mIsGestureEnabled = gestureEnabled; | |
| 91 | + } | |
| 92 | + | |
| 93 | + @Override | |
| 94 | + public void setPlayerState(int playerState) { | |
| 95 | + super.setPlayerState(playerState); | |
| 96 | + if (playerState == CNVideoView.PLAYER_NORMAL) { | |
| 97 | + mCanSlide = mEnableInNormal; | |
| 98 | + } else if (playerState == CNVideoView.PLAYER_FULL_SCREEN) { | |
| 99 | + mCanSlide = true; | |
| 100 | + } | |
| 101 | + } | |
| 102 | + | |
| 103 | + @Override | |
| 104 | + public void setPlayState(int playState) { | |
| 105 | + super.setPlayState(playState); | |
| 106 | + mCurPlayState = playState; | |
| 107 | + } | |
| 108 | + | |
| 109 | + private boolean isInPlaybackState() { | |
| 110 | + return mControlWrapper != null | |
| 111 | + && mCurPlayState != CNVideoView.STATE_ERROR | |
| 112 | + && mCurPlayState != CNVideoView.STATE_IDLE | |
| 113 | + && mCurPlayState != CNVideoView.STATE_PREPARING | |
| 114 | + && mCurPlayState != CNVideoView.STATE_PREPARED | |
| 115 | + && mCurPlayState != CNVideoView.STATE_START_ABORT | |
| 116 | + && mCurPlayState != CNVideoView.STATE_PLAYBACK_COMPLETED; | |
| 117 | + } | |
| 118 | + | |
| 119 | + @Override | |
| 120 | + public boolean onTouch(View v, MotionEvent event) { | |
| 121 | + return mGestureDetector.onTouchEvent(event); | |
| 122 | + } | |
| 123 | + | |
| 124 | + /** | |
| 125 | + * 手指按下的瞬间 | |
| 126 | + */ | |
| 127 | + @Override | |
| 128 | + public boolean onDown(MotionEvent e) { | |
| 129 | + if (!isInPlaybackState() //不处于播放状态 | |
| 130 | + || !mIsGestureEnabled //关闭了手势 | |
| 131 | + || PlayerUtils.isEdge(getContext(), e)) //处于屏幕边沿 | |
| 132 | + return true; | |
| 133 | + mStreamVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC); | |
| 134 | + Activity activity = PlayerUtils.scanForActivity(getContext()); | |
| 135 | + if (activity == null) { | |
| 136 | + mBrightness = 0; | |
| 137 | + } else { | |
| 138 | + mBrightness = activity.getWindow().getAttributes().screenBrightness; | |
| 139 | + } | |
| 140 | + mFirstTouch = true; | |
| 141 | + mChangePosition = false; | |
| 142 | + mChangeBrightness = false; | |
| 143 | + mChangeVolume = false; | |
| 144 | + return true; | |
| 145 | + } | |
| 146 | + | |
| 147 | + /** | |
| 148 | + * 单击 | |
| 149 | + */ | |
| 150 | + @Override | |
| 151 | + public boolean onSingleTapConfirmed(MotionEvent e) { | |
| 152 | + if (isInPlaybackState()) { | |
| 153 | + mControlWrapper.toggleShowState(); | |
| 154 | + } | |
| 155 | + return true; | |
| 156 | + } | |
| 157 | + | |
| 158 | + /** | |
| 159 | + * 双击 | |
| 160 | + */ | |
| 161 | + @Override | |
| 162 | + public boolean onDoubleTap(MotionEvent e) { | |
| 163 | + if (!isLocked() && isInPlaybackState()) togglePlay(); | |
| 164 | + return true; | |
| 165 | + } | |
| 166 | + | |
| 167 | + /** | |
| 168 | + * 在屏幕上滑动 | |
| 169 | + */ | |
| 170 | + @Override | |
| 171 | + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { | |
| 172 | + if (!isInPlaybackState() //不处于播放状态 | |
| 173 | + || !mIsGestureEnabled //关闭了手势 | |
| 174 | + || !mCanSlide //关闭了滑动手势 | |
| 175 | + || isLocked() //锁住了屏幕 | |
| 176 | + || PlayerUtils.isEdge(getContext(), e1)) //处于屏幕边沿 | |
| 177 | + return true; | |
| 178 | + float deltaX = e1.getX() - e2.getX(); | |
| 179 | + float deltaY = e1.getY() - e2.getY(); | |
| 180 | + if (mFirstTouch) { | |
| 181 | + mChangePosition = Math.abs(distanceX) >= Math.abs(distanceY); | |
| 182 | + if (!mChangePosition) { | |
| 183 | + //半屏宽度 | |
| 184 | + int halfScreen = PlayerUtils.getScreenWidth(getContext(), true) / 2; | |
| 185 | + if (e2.getX() > halfScreen) { | |
| 186 | + mChangeVolume = true; | |
| 187 | + } else { | |
| 188 | + mChangeBrightness = true; | |
| 189 | + } | |
| 190 | + } | |
| 191 | + | |
| 192 | + if (mChangePosition) { | |
| 193 | + //根据用户设置是否可以滑动调节进度来决定最终是否可以滑动调节进度 | |
| 194 | + mChangePosition = mCanChangePosition; | |
| 195 | + } | |
| 196 | + | |
| 197 | + if (mChangePosition || mChangeBrightness || mChangeVolume) { | |
| 198 | + for (Map.Entry<IControlComponent, Boolean> next : mControlComponents.entrySet()) { | |
| 199 | + IControlComponent component = next.getKey(); | |
| 200 | + if (component instanceof IGestureComponent) { | |
| 201 | + ((IGestureComponent) component).onStartSlide(); | |
| 202 | + } | |
| 203 | + } | |
| 204 | + } | |
| 205 | + mFirstTouch = false; | |
| 206 | + } | |
| 207 | + if (mChangePosition) { | |
| 208 | + slideToChangePosition(deltaX); | |
| 209 | + } else if (mChangeBrightness) { | |
| 210 | + slideToChangeBrightness(deltaY); | |
| 211 | + } else if (mChangeVolume) { | |
| 212 | + slideToChangeVolume(deltaY); | |
| 213 | + } | |
| 214 | + return true; | |
| 215 | + } | |
| 216 | + | |
| 217 | + protected void slideToChangePosition(float deltaX) { | |
| 218 | + deltaX = -deltaX; | |
| 219 | + int width = getMeasuredWidth(); | |
| 220 | + int duration = (int) mControlWrapper.getDuration(); | |
| 221 | + int currentPosition = (int) mControlWrapper.getCurrentPosition(); | |
| 222 | + int position = (int) (deltaX / width * 120000 + currentPosition); | |
| 223 | + if (position > duration) position = duration; | |
| 224 | + if (position < 0) position = 0; | |
| 225 | + for (Map.Entry<IControlComponent, Boolean> next : mControlComponents.entrySet()) { | |
| 226 | + IControlComponent component = next.getKey(); | |
| 227 | + if (component instanceof IGestureComponent) { | |
| 228 | + ((IGestureComponent) component).onPositionChange(position, currentPosition, duration); | |
| 229 | + } | |
| 230 | + } | |
| 231 | + mPosition = position; | |
| 232 | + mNeedSeek = true; | |
| 233 | + } | |
| 234 | + | |
| 235 | + protected void slideToChangeBrightness(float deltaY) { | |
| 236 | + Activity activity = PlayerUtils.scanForActivity(getContext()); | |
| 237 | + if (activity == null) return; | |
| 238 | + Window window = activity.getWindow(); | |
| 239 | + WindowManager.LayoutParams attributes = window.getAttributes(); | |
| 240 | + int height = getMeasuredHeight(); | |
| 241 | + if (mBrightness == -1.0f) mBrightness = 0.5f; | |
| 242 | + float brightness = deltaY * 2 / height * 1.0f + mBrightness; | |
| 243 | + if (brightness < 0) { | |
| 244 | + brightness = 0f; | |
| 245 | + } | |
| 246 | + if (brightness > 1.0f) brightness = 1.0f; | |
| 247 | + int percent = (int) (brightness * 100); | |
| 248 | + attributes.screenBrightness = brightness; | |
| 249 | + window.setAttributes(attributes); | |
| 250 | + for (Map.Entry<IControlComponent, Boolean> next : mControlComponents.entrySet()) { | |
| 251 | + IControlComponent component = next.getKey(); | |
| 252 | + if (component instanceof IGestureComponent) { | |
| 253 | + ((IGestureComponent) component).onBrightnessChange(percent); | |
| 254 | + } | |
| 255 | + } | |
| 256 | + } | |
| 257 | + | |
| 258 | + protected void slideToChangeVolume(float deltaY) { | |
| 259 | + int streamMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC); | |
| 260 | + int height = getMeasuredHeight(); | |
| 261 | + float deltaV = deltaY * 2 / height * streamMaxVolume; | |
| 262 | + float index = mStreamVolume + deltaV; | |
| 263 | + if (index > streamMaxVolume) index = streamMaxVolume; | |
| 264 | + if (index < 0) index = 0; | |
| 265 | + int percent = (int) (index / streamMaxVolume * 100); | |
| 266 | + mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, (int) index, 0); | |
| 267 | + for (Map.Entry<IControlComponent, Boolean> next : mControlComponents.entrySet()) { | |
| 268 | + IControlComponent component = next.getKey(); | |
| 269 | + if (component instanceof IGestureComponent) { | |
| 270 | + ((IGestureComponent) component).onVolumeChange(percent); | |
| 271 | + } | |
| 272 | + } | |
| 273 | + } | |
| 274 | + | |
| 275 | + @Override | |
| 276 | + public boolean onTouchEvent(MotionEvent event) { | |
| 277 | + boolean detectedUp = event.getAction() == MotionEvent.ACTION_UP; | |
| 278 | + if (!mGestureDetector.onTouchEvent(event) && detectedUp) { | |
| 279 | + for (Map.Entry<IControlComponent, Boolean> next : mControlComponents.entrySet()) { | |
| 280 | + IControlComponent component = next.getKey(); | |
| 281 | + if (component instanceof IGestureComponent) { | |
| 282 | + ((IGestureComponent) component).onStopSlide(); | |
| 283 | + } | |
| 284 | + } | |
| 285 | + if (mNeedSeek) { | |
| 286 | + mControlWrapper.seekTo(mPosition); | |
| 287 | + mNeedSeek = false; | |
| 288 | + } | |
| 289 | + } | |
| 290 | + return super.onTouchEvent(event); | |
| 291 | + } | |
| 292 | + | |
| 293 | + @Override | |
| 294 | + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { | |
| 295 | + return false; | |
| 296 | + } | |
| 297 | + | |
| 298 | + @Override | |
| 299 | + public void onLongPress(MotionEvent e) { | |
| 300 | + | |
| 301 | + } | |
| 302 | + | |
| 303 | + @Override | |
| 304 | + public void onShowPress(MotionEvent e) { | |
| 305 | + | |
| 306 | + } | |
| 307 | + | |
| 308 | + @Override | |
| 309 | + public boolean onDoubleTapEvent(MotionEvent e) { | |
| 310 | + return false; | |
| 311 | + } | |
| 312 | + | |
| 313 | + | |
| 314 | + @Override | |
| 315 | + public boolean onSingleTapUp(MotionEvent e) { | |
| 316 | + return false; | |
| 317 | + } | |
| 318 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/controller/IControlComponent.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.controller; | |
| 2 | + | |
| 3 | +import android.view.View; | |
| 4 | +import android.view.animation.Animation; | |
| 5 | + | |
| 6 | +import androidx.annotation.NonNull; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 视频控制组件接口 | |
| 10 | + */ | |
| 11 | +public interface IControlComponent { | |
| 12 | + | |
| 13 | + void attach(@NonNull ControlWrapper controlWrapper); | |
| 14 | + | |
| 15 | + /** | |
| 16 | + * 获取当前控制组件对应的View | |
| 17 | + * | |
| 18 | + * @return | |
| 19 | + */ | |
| 20 | + View getView(); | |
| 21 | + | |
| 22 | + /** | |
| 23 | + * UI显示与隐藏 | |
| 24 | + * | |
| 25 | + * @param isVisible | |
| 26 | + * @param anim | |
| 27 | + */ | |
| 28 | + void onVisibilityChanged(boolean isVisible, Animation anim); | |
| 29 | + | |
| 30 | + /** | |
| 31 | + * 播放状态改变 | |
| 32 | + * | |
| 33 | + * @param playState | |
| 34 | + */ | |
| 35 | + void onPlayStateChanged(int playState); | |
| 36 | + | |
| 37 | + /** | |
| 38 | + * 播放器状态改变 | |
| 39 | + * | |
| 40 | + * @param playerState | |
| 41 | + */ | |
| 42 | + void onPlayerStateChanged(int playerState); | |
| 43 | + | |
| 44 | + /** | |
| 45 | + * 设置进度 | |
| 46 | + * | |
| 47 | + * @param duration | |
| 48 | + * @param position | |
| 49 | + */ | |
| 50 | + void setProgress(int duration, int position); | |
| 51 | + | |
| 52 | + /** | |
| 53 | + * 设置是否锁定 | |
| 54 | + * | |
| 55 | + * @param isLocked | |
| 56 | + */ | |
| 57 | + void onLockStateChanged(boolean isLocked); | |
| 58 | + | |
| 59 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/controller/IGestureComponent.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.controller; | |
| 2 | + | |
| 3 | +public interface IGestureComponent extends IControlComponent { | |
| 4 | + /** | |
| 5 | + * 开始滑动 | |
| 6 | + */ | |
| 7 | + void onStartSlide(); | |
| 8 | + | |
| 9 | + /** | |
| 10 | + * 结束滑动 | |
| 11 | + */ | |
| 12 | + void onStopSlide(); | |
| 13 | + | |
| 14 | + /** | |
| 15 | + * 滑动调整进度 | |
| 16 | + * @param slidePosition 滑动进度 | |
| 17 | + * @param currentPosition 当前播放进度 | |
| 18 | + * @param duration 视频总长度 | |
| 19 | + */ | |
| 20 | + void onPositionChange(int slidePosition, int currentPosition, int duration); | |
| 21 | + | |
| 22 | + /** | |
| 23 | + * 滑动调整亮度 | |
| 24 | + * @param percent 亮度百分比 | |
| 25 | + */ | |
| 26 | + void onBrightnessChange(int percent); | |
| 27 | + | |
| 28 | + /** | |
| 29 | + * 滑动调整音量 | |
| 30 | + * @param percent 音量百分比 | |
| 31 | + */ | |
| 32 | + void onVolumeChange(int percent); | |
| 33 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/controller/IVideoController.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.controller; | |
| 2 | + | |
| 3 | +public interface IVideoController { | |
| 4 | + /** | |
| 5 | + * 开始控制视图自动隐藏倒计时 | |
| 6 | + */ | |
| 7 | + void startFadeOut(); | |
| 8 | + | |
| 9 | + /** | |
| 10 | + * 取消控制视图自动隐藏倒计时 | |
| 11 | + */ | |
| 12 | + void stopFadeOut(); | |
| 13 | + | |
| 14 | + /** | |
| 15 | + * 控制视图是否处于显示状态 | |
| 16 | + */ | |
| 17 | + boolean isShowing(); | |
| 18 | + | |
| 19 | + /** | |
| 20 | + * 设置锁定状态 | |
| 21 | + * @param locked 是否锁定 | |
| 22 | + */ | |
| 23 | + void setLocked(boolean locked); | |
| 24 | + | |
| 25 | + /** | |
| 26 | + * 是否处于锁定状态 | |
| 27 | + */ | |
| 28 | + boolean isLocked(); | |
| 29 | + | |
| 30 | + /** | |
| 31 | + * 开始刷新进度 | |
| 32 | + */ | |
| 33 | + void startProgress(); | |
| 34 | + | |
| 35 | + /** | |
| 36 | + * 停止刷新进度 | |
| 37 | + */ | |
| 38 | + void stopProgress(); | |
| 39 | + | |
| 40 | + /** | |
| 41 | + * 显示控制视图 | |
| 42 | + */ | |
| 43 | + void hide(); | |
| 44 | + | |
| 45 | + /** | |
| 46 | + * 隐藏控制视图 | |
| 47 | + */ | |
| 48 | + void show(); | |
| 49 | + | |
| 50 | + /** | |
| 51 | + * 是否需要适配刘海 | |
| 52 | + */ | |
| 53 | + boolean hasCutout(); | |
| 54 | + | |
| 55 | + /** | |
| 56 | + * 获取刘海的高度 | |
| 57 | + */ | |
| 58 | + int getCutoutHeight(); | |
| 59 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/controller/MediaPlayerControl.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.controller; | |
| 2 | + | |
| 3 | +import android.graphics.Bitmap; | |
| 4 | + | |
| 5 | +/** | |
| 6 | + * 播放器控制接口 | |
| 7 | + */ | |
| 8 | +public interface MediaPlayerControl { | |
| 9 | + int getMediaCount(); | |
| 10 | + | |
| 11 | + void start(); | |
| 12 | + | |
| 13 | + void pause(); | |
| 14 | + | |
| 15 | + long getDuration(); | |
| 16 | + | |
| 17 | + long getCurrentPosition(); | |
| 18 | + | |
| 19 | + void seekTo(long pos); | |
| 20 | + | |
| 21 | + boolean isPlaying(); | |
| 22 | + | |
| 23 | + int getBufferedPercentage(); | |
| 24 | + | |
| 25 | + void startFullScreen(); | |
| 26 | + | |
| 27 | + void stopFullScreen(); | |
| 28 | + | |
| 29 | + boolean isFullScreen(); | |
| 30 | + | |
| 31 | + void setMute(boolean isMute); | |
| 32 | + | |
| 33 | + boolean isMute(); | |
| 34 | + | |
| 35 | + void setScreenScaleType(int screenScaleType); | |
| 36 | + | |
| 37 | + void setSpeed(float speed); | |
| 38 | + | |
| 39 | + long getTcpSpeed(); | |
| 40 | + | |
| 41 | + void replay(boolean resetPosition); | |
| 42 | + | |
| 43 | + void setMirrorRotation(boolean enable); | |
| 44 | + | |
| 45 | + Bitmap doScreenShot(); | |
| 46 | + | |
| 47 | + int[] getVideoSize(); | |
| 48 | + | |
| 49 | + void setRotation(float rotation); | |
| 50 | + | |
| 51 | + void startTinyScreen(); | |
| 52 | + | |
| 53 | + void stopTinyScreen(); | |
| 54 | + | |
| 55 | + boolean isTinyScreen(); | |
| 56 | +} | |
| 0 | 57 | \ No newline at end of file | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/controller/OrientationHelper.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.controller; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.view.OrientationEventListener; | |
| 5 | + | |
| 6 | +/** | |
| 7 | + * 设备方向监听 | |
| 8 | + */ | |
| 9 | +public class OrientationHelper extends OrientationEventListener { | |
| 10 | + | |
| 11 | + private long mLastTime; | |
| 12 | + | |
| 13 | + private OnOrientationChangeListener mOnOrientationChangeListener; | |
| 14 | + | |
| 15 | + public OrientationHelper(Context context) { | |
| 16 | + super(context); | |
| 17 | + } | |
| 18 | + | |
| 19 | + @Override | |
| 20 | + public void onOrientationChanged(int orientation) { | |
| 21 | + long currentTime = System.currentTimeMillis(); | |
| 22 | + if (currentTime - mLastTime < 300) return;//300毫秒检测一次 | |
| 23 | + if (mOnOrientationChangeListener != null) { | |
| 24 | + mOnOrientationChangeListener.onOrientationChanged(orientation); | |
| 25 | + } | |
| 26 | + mLastTime = currentTime; | |
| 27 | + } | |
| 28 | + | |
| 29 | + | |
| 30 | + public interface OnOrientationChangeListener { | |
| 31 | + void onOrientationChanged(int orientation); | |
| 32 | + } | |
| 33 | + | |
| 34 | + public void setOnOrientationChangeListener(OnOrientationChangeListener onOrientationChangeListener) { | |
| 35 | + mOnOrientationChangeListener = onOrientationChangeListener; | |
| 36 | + } | |
| 37 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/controller/StandardVideoController.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.controller; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.content.pm.ActivityInfo; | |
| 5 | +import android.util.AttributeSet; | |
| 6 | +import android.view.View; | |
| 7 | +import android.view.ViewGroup; | |
| 8 | +import android.view.animation.Animation; | |
| 9 | +import android.widget.FrameLayout; | |
| 10 | +import android.widget.ImageView; | |
| 11 | +import android.widget.ProgressBar; | |
| 12 | +import android.widget.Toast; | |
| 13 | + | |
| 14 | +import com.cnlive.cloud.video.R; | |
| 15 | +import com.libs.video.video.exoplayer.component.DefaultCompleteView; | |
| 16 | +import com.libs.video.video.exoplayer.component.DefaultErrorView; | |
| 17 | +import com.libs.video.video.exoplayer.component.GestureView; | |
| 18 | +import com.libs.video.video.exoplayer.component.LiveControlView; | |
| 19 | +import com.libs.video.video.exoplayer.component.DefaultPrepareView; | |
| 20 | +import com.libs.video.video.exoplayer.component.DefaultTitleView; | |
| 21 | +import com.libs.video.video.exoplayer.component.DefaultVodControlView; | |
| 22 | +import com.libs.video.video.exoplayer.play.CNVideoView; | |
| 23 | +import com.libs.video.video.exoplayer.util.PlayerUtils; | |
| 24 | + | |
| 25 | +import androidx.annotation.AttrRes; | |
| 26 | +import androidx.annotation.NonNull; | |
| 27 | +import androidx.annotation.Nullable; | |
| 28 | + | |
| 29 | +/** | |
| 30 | + * 直播/点播控制器 | |
| 31 | + */ | |
| 32 | + | |
| 33 | +public class StandardVideoController extends GestureVideoController implements View.OnClickListener { | |
| 34 | + | |
| 35 | + protected ImageView mLockButton;//锁屏按钮 | |
| 36 | + | |
| 37 | + protected ProgressBar mLoadingProgress; | |
| 38 | + | |
| 39 | + public StandardVideoController(@NonNull Context context) { | |
| 40 | + this(context, null); | |
| 41 | + } | |
| 42 | + | |
| 43 | + public StandardVideoController(@NonNull Context context, @Nullable AttributeSet attrs) { | |
| 44 | + this(context, attrs, 0); | |
| 45 | + } | |
| 46 | + | |
| 47 | + public StandardVideoController(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) { | |
| 48 | + super(context, attrs, defStyleAttr); | |
| 49 | + } | |
| 50 | + | |
| 51 | + @Override | |
| 52 | + protected int getLayoutId() { | |
| 53 | + return R.layout.cnvideo_layout_standard_controller; | |
| 54 | + } | |
| 55 | + | |
| 56 | + @Override | |
| 57 | + protected void initView() { | |
| 58 | + super.initView(); | |
| 59 | + mLockButton = findViewById(R.id.lock); | |
| 60 | + mLockButton.setOnClickListener(this); | |
| 61 | + mLoadingProgress = findViewById(R.id.loading); | |
| 62 | + } | |
| 63 | + | |
| 64 | + /** | |
| 65 | + * 快速添加各个组件 | |
| 66 | + * | |
| 67 | + * @param title 标题 | |
| 68 | + * @param isLive 是否为直播 | |
| 69 | + */ | |
| 70 | + public void addDefaultControlComponent(String title, boolean isLive) { | |
| 71 | + DefaultCompleteView completeView = new DefaultCompleteView(getContext()); | |
| 72 | + DefaultErrorView errorView = new DefaultErrorView(getContext()); | |
| 73 | + DefaultPrepareView prepareView = new DefaultPrepareView(getContext()); | |
| 74 | + prepareView.setClickStart(); | |
| 75 | + DefaultTitleView titleView = new DefaultTitleView(getContext()); | |
| 76 | + titleView.setTitle(title); | |
| 77 | + addControlComponent(completeView, errorView, prepareView, titleView); | |
| 78 | + if (isLive) { | |
| 79 | + addControlComponent(new LiveControlView(getContext())); | |
| 80 | + } else { | |
| 81 | + addControlComponent(new DefaultVodControlView(getContext())); | |
| 82 | + } | |
| 83 | + addControlComponent(new GestureView(getContext())); | |
| 84 | + setCanChangePosition(!isLive); | |
| 85 | + } | |
| 86 | + | |
| 87 | + @Override | |
| 88 | + public void onClick(View v) { | |
| 89 | + int i = v.getId(); | |
| 90 | + if (i == R.id.lock) { | |
| 91 | + mControlWrapper.toggleLockState(); | |
| 92 | + } | |
| 93 | + } | |
| 94 | + | |
| 95 | + @Override | |
| 96 | + protected void onLockStateChanged(boolean isLocked) { | |
| 97 | + if (isLocked) { | |
| 98 | + mLockButton.setSelected(true); | |
| 99 | + Toast.makeText(getContext(), R.string.cnvideo_locked, Toast.LENGTH_SHORT).show(); | |
| 100 | + } else { | |
| 101 | + mLockButton.setSelected(false); | |
| 102 | + Toast.makeText(getContext(), R.string.cnvideo_unlocked, Toast.LENGTH_SHORT).show(); | |
| 103 | + } | |
| 104 | + } | |
| 105 | + | |
| 106 | + @Override | |
| 107 | + protected void onVisibilityChanged(boolean isVisible, Animation anim) { | |
| 108 | + if (mControlWrapper.isFullScreen()) { | |
| 109 | + if (isVisible) { | |
| 110 | + if (mLockButton.getVisibility() == GONE) { | |
| 111 | + mLockButton.setVisibility(VISIBLE); | |
| 112 | + if (anim != null) { | |
| 113 | + mLockButton.startAnimation(anim); | |
| 114 | + } | |
| 115 | + } | |
| 116 | + } else { | |
| 117 | + mLockButton.setVisibility(GONE); | |
| 118 | + if (anim != null) { | |
| 119 | + mLockButton.startAnimation(anim); | |
| 120 | + } | |
| 121 | + } | |
| 122 | + } | |
| 123 | + } | |
| 124 | + | |
| 125 | + @Override | |
| 126 | + protected void onPlayerStateChanged(int playerState) { | |
| 127 | + super.onPlayerStateChanged(playerState); | |
| 128 | + switch (playerState) { | |
| 129 | + case CNVideoView.PLAYER_NORMAL: | |
| 130 | + setLayoutParams(new FrameLayout.LayoutParams( | |
| 131 | + ViewGroup.LayoutParams.MATCH_PARENT, | |
| 132 | + ViewGroup.LayoutParams.MATCH_PARENT)); | |
| 133 | + mLockButton.setVisibility(GONE); | |
| 134 | + break; | |
| 135 | + case CNVideoView.PLAYER_FULL_SCREEN: | |
| 136 | + if (isShowing()) { | |
| 137 | + mLockButton.setVisibility(VISIBLE); | |
| 138 | + } else { | |
| 139 | + mLockButton.setVisibility(GONE); | |
| 140 | + } | |
| 141 | + break; | |
| 142 | + } | |
| 143 | + | |
| 144 | + if (mActivity != null && hasCutout()) { | |
| 145 | + int orientation = mActivity.getRequestedOrientation(); | |
| 146 | + int dp24 = PlayerUtils.dp2px(getContext(), 24); | |
| 147 | + int cutoutHeight = getCutoutHeight(); | |
| 148 | + if (orientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { | |
| 149 | + FrameLayout.LayoutParams lblp = (FrameLayout.LayoutParams) mLockButton.getLayoutParams(); | |
| 150 | + lblp.setMargins(dp24, 0, dp24, 0); | |
| 151 | + } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { | |
| 152 | + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mLockButton.getLayoutParams(); | |
| 153 | + layoutParams.setMargins(dp24 + cutoutHeight, 0, dp24 + cutoutHeight, 0); | |
| 154 | + } else if (orientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) { | |
| 155 | + FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) mLockButton.getLayoutParams(); | |
| 156 | + layoutParams.setMargins(dp24, 0, dp24, 0); | |
| 157 | + } | |
| 158 | + } | |
| 159 | + | |
| 160 | + } | |
| 161 | + | |
| 162 | + @Override | |
| 163 | + protected void onPlayStateChanged(int playState) { | |
| 164 | + super.onPlayStateChanged(playState); | |
| 165 | + switch (playState) { | |
| 166 | + //调用release方法会回到此状态 | |
| 167 | + case CNVideoView.STATE_IDLE: | |
| 168 | + mLockButton.setSelected(false); | |
| 169 | + mLoadingProgress.setVisibility(GONE); | |
| 170 | + break; | |
| 171 | + case CNVideoView.STATE_PLAYING: | |
| 172 | + case CNVideoView.STATE_PAUSED: | |
| 173 | + case CNVideoView.STATE_PREPARED: | |
| 174 | + case CNVideoView.STATE_ERROR: | |
| 175 | + case CNVideoView.STATE_BUFFERED: | |
| 176 | + mLoadingProgress.setVisibility(GONE); | |
| 177 | + break; | |
| 178 | + case CNVideoView.STATE_PREPARING: | |
| 179 | + case CNVideoView.STATE_BUFFERING: | |
| 180 | + mLoadingProgress.setVisibility(VISIBLE); | |
| 181 | + break; | |
| 182 | + case CNVideoView.STATE_PLAYBACK_COMPLETED: | |
| 183 | + mLoadingProgress.setVisibility(GONE); | |
| 184 | + mLockButton.setVisibility(GONE); | |
| 185 | + mLockButton.setSelected(false); | |
| 186 | + break; | |
| 187 | + } | |
| 188 | + } | |
| 189 | + | |
| 190 | + @Override | |
| 191 | + public boolean onBackPressed() { | |
| 192 | + if (isLocked()) { | |
| 193 | + show(); | |
| 194 | + Toast.makeText(getContext(), R.string.cnvideo_lock_tip, Toast.LENGTH_SHORT).show(); | |
| 195 | + return true; | |
| 196 | + } | |
| 197 | + if (mControlWrapper.isFullScreen()) { | |
| 198 | + return stopFullScreen(); | |
| 199 | + } | |
| 200 | + return super.onBackPressed(); | |
| 201 | + } | |
| 202 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/play/AbstractPlayer.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.play; | |
| 2 | + | |
| 3 | +import android.content.res.AssetFileDescriptor; | |
| 4 | +import android.view.Surface; | |
| 5 | +import android.view.SurfaceHolder; | |
| 6 | + | |
| 7 | +import java.util.Map; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * 抽象播放器 | |
| 11 | + * Created by ShinnyYang | |
| 12 | + */ | |
| 13 | + | |
| 14 | +public abstract class AbstractPlayer { | |
| 15 | + | |
| 16 | + /** | |
| 17 | + * 开始渲染视频画面 | |
| 18 | + */ | |
| 19 | + public static final int MEDIA_INFO_VIDEO_RENDERING_START = 3; | |
| 20 | + | |
| 21 | + /** | |
| 22 | + * 缓冲开始 | |
| 23 | + */ | |
| 24 | + public static final int MEDIA_INFO_BUFFERING_START = 701; | |
| 25 | + | |
| 26 | + /** | |
| 27 | + * 缓冲结束 | |
| 28 | + */ | |
| 29 | + public static final int MEDIA_INFO_BUFFERING_END = 702; | |
| 30 | + | |
| 31 | + /** | |
| 32 | + * 视频旋转信息 | |
| 33 | + */ | |
| 34 | + public static final int MEDIA_INFO_VIDEO_ROTATION_CHANGED = 10001; | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * 播放器事件回调 | |
| 38 | + */ | |
| 39 | + protected PlayerEventListener mPlayerEventListener; | |
| 40 | + | |
| 41 | + /** | |
| 42 | + * 初始化播放器实例 | |
| 43 | + */ | |
| 44 | + public abstract void initPlayer(); | |
| 45 | + | |
| 46 | + /** | |
| 47 | + * 设置播放地址 | |
| 48 | + * | |
| 49 | + * @param path 播放地址 | |
| 50 | + * @param headers 播放地址请求头 | |
| 51 | + */ | |
| 52 | + public abstract void setDataSource(String path, Map<String, String> headers); | |
| 53 | + | |
| 54 | + /** | |
| 55 | + * 用于播放raw和asset里面的视频文件 | |
| 56 | + */ | |
| 57 | + public abstract void setDataSource(AssetFileDescriptor fd); | |
| 58 | + | |
| 59 | + /** | |
| 60 | + * 播放 | |
| 61 | + */ | |
| 62 | + public abstract void start(); | |
| 63 | + | |
| 64 | + /** | |
| 65 | + * 暂停 | |
| 66 | + */ | |
| 67 | + public abstract void pause(); | |
| 68 | + | |
| 69 | + /** | |
| 70 | + * 停止 | |
| 71 | + */ | |
| 72 | + public abstract void stop(); | |
| 73 | + | |
| 74 | + /** | |
| 75 | + * 准备开始播放(异步) | |
| 76 | + */ | |
| 77 | + public abstract void prepareAsync(); | |
| 78 | + | |
| 79 | + /** | |
| 80 | + * 重置播放器 | |
| 81 | + */ | |
| 82 | + public abstract void reset(); | |
| 83 | + | |
| 84 | + /** | |
| 85 | + * 是否正在播放 | |
| 86 | + */ | |
| 87 | + public abstract boolean isPlaying(); | |
| 88 | + | |
| 89 | + /** | |
| 90 | + * 调整进度 | |
| 91 | + */ | |
| 92 | + public abstract void seekTo(long time); | |
| 93 | + | |
| 94 | + /** | |
| 95 | + * 释放播放器 | |
| 96 | + */ | |
| 97 | + public abstract void release(); | |
| 98 | + | |
| 99 | + /** | |
| 100 | + * 获取当前播放的位置 | |
| 101 | + */ | |
| 102 | + public abstract long getCurrentPosition(); | |
| 103 | + | |
| 104 | + /** | |
| 105 | + * 获取视频总时长 | |
| 106 | + */ | |
| 107 | + public abstract long getDuration(); | |
| 108 | + | |
| 109 | + /** | |
| 110 | + * 获取缓冲百分比 | |
| 111 | + */ | |
| 112 | + public abstract int getBufferedPercentage(); | |
| 113 | + | |
| 114 | + /** | |
| 115 | + * 设置渲染视频的View,主要用于TextureView | |
| 116 | + */ | |
| 117 | + public abstract void setSurface(Surface surface); | |
| 118 | + | |
| 119 | + /** | |
| 120 | + * 设置渲染视频的View,主要用于SurfaceView | |
| 121 | + */ | |
| 122 | + public abstract void setDisplay(SurfaceHolder holder); | |
| 123 | + | |
| 124 | + /** | |
| 125 | + * 设置音量 | |
| 126 | + */ | |
| 127 | + public abstract void setVolume(float v1, float v2); | |
| 128 | + | |
| 129 | + /** | |
| 130 | + * 设置是否循环播放 | |
| 131 | + */ | |
| 132 | + public abstract void setLooping(boolean isLooping); | |
| 133 | + | |
| 134 | + /** | |
| 135 | + * 设置其他播放配置 | |
| 136 | + */ | |
| 137 | + public abstract void setOptions(); | |
| 138 | + | |
| 139 | + /** | |
| 140 | + * 设置播放速度 | |
| 141 | + */ | |
| 142 | + public abstract void setSpeed(float speed); | |
| 143 | + | |
| 144 | + /** | |
| 145 | + * 获取当前缓冲的网速 | |
| 146 | + */ | |
| 147 | + public abstract long getTcpSpeed(); | |
| 148 | + | |
| 149 | + /** | |
| 150 | + * 绑定VideoView | |
| 151 | + */ | |
| 152 | + public void setPlayerEventListener(PlayerEventListener playerEventListener) { | |
| 153 | + this.mPlayerEventListener = playerEventListener; | |
| 154 | + } | |
| 155 | + | |
| 156 | + public interface PlayerEventListener { | |
| 157 | + | |
| 158 | + void onError(); | |
| 159 | + | |
| 160 | + void onCompletion(); | |
| 161 | + | |
| 162 | + void onInfo(int what, int extra); | |
| 163 | + | |
| 164 | + void onPrepared(); | |
| 165 | + | |
| 166 | + void onVideoSizeChanged(int width, int height); | |
| 167 | + | |
| 168 | + } | |
| 169 | + | |
| 170 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/play/AndroidMediaPlayer.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.play; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.content.res.AssetFileDescriptor; | |
| 5 | +import android.media.AudioManager; | |
| 6 | +import android.media.MediaPlayer; | |
| 7 | +import android.net.Uri; | |
| 8 | +import android.os.Build; | |
| 9 | +import android.view.Surface; | |
| 10 | +import android.view.SurfaceHolder; | |
| 11 | + | |
| 12 | +import java.util.Map; | |
| 13 | + | |
| 14 | +public class AndroidMediaPlayer extends AbstractPlayer { | |
| 15 | + | |
| 16 | + protected MediaPlayer mMediaPlayer; | |
| 17 | + private int mBufferedPercent; | |
| 18 | + private Context mAppContext; | |
| 19 | + private boolean mIsPreparing; | |
| 20 | + | |
| 21 | + public AndroidMediaPlayer(Context context) { | |
| 22 | + mAppContext = context.getApplicationContext(); | |
| 23 | + } | |
| 24 | + | |
| 25 | + @Override | |
| 26 | + public void initPlayer() { | |
| 27 | + mMediaPlayer = new MediaPlayer(); | |
| 28 | + setOptions(); | |
| 29 | + mMediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); | |
| 30 | + mMediaPlayer.setOnErrorListener(onErrorListener); | |
| 31 | + mMediaPlayer.setOnCompletionListener(onCompletionListener); | |
| 32 | + mMediaPlayer.setOnInfoListener(onInfoListener); | |
| 33 | + mMediaPlayer.setOnBufferingUpdateListener(onBufferingUpdateListener); | |
| 34 | + mMediaPlayer.setOnPreparedListener(onPreparedListener); | |
| 35 | + mMediaPlayer.setOnVideoSizeChangedListener(onVideoSizeChangedListener); | |
| 36 | + } | |
| 37 | + | |
| 38 | + @Override | |
| 39 | + public void setDataSource(String path, Map<String, String> headers) { | |
| 40 | + try { | |
| 41 | + mMediaPlayer.setDataSource(mAppContext, Uri.parse(path), headers); | |
| 42 | + } catch (Exception e) { | |
| 43 | + mPlayerEventListener.onError(); | |
| 44 | + } | |
| 45 | + } | |
| 46 | + | |
| 47 | + @Override | |
| 48 | + public void setDataSource(AssetFileDescriptor fd) { | |
| 49 | + try { | |
| 50 | + mMediaPlayer.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength()); | |
| 51 | + } catch (Exception e) { | |
| 52 | + mPlayerEventListener.onError(); | |
| 53 | + } | |
| 54 | + } | |
| 55 | + | |
| 56 | + @Override | |
| 57 | + public void start() { | |
| 58 | + try { | |
| 59 | + mMediaPlayer.start(); | |
| 60 | + } catch (IllegalStateException e) { | |
| 61 | + mPlayerEventListener.onError(); | |
| 62 | + } | |
| 63 | + } | |
| 64 | + | |
| 65 | + @Override | |
| 66 | + public void pause() { | |
| 67 | + try { | |
| 68 | + mMediaPlayer.pause(); | |
| 69 | + } catch (IllegalStateException e) { | |
| 70 | + mPlayerEventListener.onError(); | |
| 71 | + } | |
| 72 | + } | |
| 73 | + | |
| 74 | + @Override | |
| 75 | + public void stop() { | |
| 76 | + try { | |
| 77 | + mMediaPlayer.stop(); | |
| 78 | + } catch (IllegalStateException e) { | |
| 79 | + mPlayerEventListener.onError(); | |
| 80 | + } | |
| 81 | + } | |
| 82 | + | |
| 83 | + @Override | |
| 84 | + public void prepareAsync() { | |
| 85 | + try { | |
| 86 | + mIsPreparing = true; | |
| 87 | + mMediaPlayer.prepareAsync(); | |
| 88 | + } catch (IllegalStateException e) { | |
| 89 | + mPlayerEventListener.onError(); | |
| 90 | + } | |
| 91 | + } | |
| 92 | + | |
| 93 | + @Override | |
| 94 | + public void reset() { | |
| 95 | + mMediaPlayer.reset(); | |
| 96 | + mMediaPlayer.setSurface(null); | |
| 97 | + mMediaPlayer.setDisplay(null); | |
| 98 | + mMediaPlayer.setVolume(1, 1); | |
| 99 | + } | |
| 100 | + | |
| 101 | + @Override | |
| 102 | + public boolean isPlaying() { | |
| 103 | + return mMediaPlayer.isPlaying(); | |
| 104 | + } | |
| 105 | + | |
| 106 | + @Override | |
| 107 | + public void seekTo(long time) { | |
| 108 | + try { | |
| 109 | + mMediaPlayer.seekTo((int) time); | |
| 110 | + } catch (IllegalStateException e) { | |
| 111 | + mPlayerEventListener.onError(); | |
| 112 | + } | |
| 113 | + } | |
| 114 | + | |
| 115 | + @Override | |
| 116 | + public void release() { | |
| 117 | + mMediaPlayer.setOnErrorListener(null); | |
| 118 | + mMediaPlayer.setOnCompletionListener(null); | |
| 119 | + mMediaPlayer.setOnInfoListener(null); | |
| 120 | + mMediaPlayer.setOnBufferingUpdateListener(null); | |
| 121 | + mMediaPlayer.setOnPreparedListener(null); | |
| 122 | + mMediaPlayer.setOnVideoSizeChangedListener(null); | |
| 123 | + new Thread() { | |
| 124 | + @Override | |
| 125 | + public void run() { | |
| 126 | + try { | |
| 127 | + mMediaPlayer.release(); | |
| 128 | + } catch (Exception e) { | |
| 129 | + e.printStackTrace(); | |
| 130 | + } | |
| 131 | + } | |
| 132 | + }.start(); | |
| 133 | + } | |
| 134 | + | |
| 135 | + @Override | |
| 136 | + public long getCurrentPosition() { | |
| 137 | + return mMediaPlayer.getCurrentPosition(); | |
| 138 | + } | |
| 139 | + | |
| 140 | + @Override | |
| 141 | + public long getDuration() { | |
| 142 | + return mMediaPlayer.getDuration(); | |
| 143 | + } | |
| 144 | + | |
| 145 | + @Override | |
| 146 | + public int getBufferedPercentage() { | |
| 147 | + return mBufferedPercent; | |
| 148 | + } | |
| 149 | + | |
| 150 | + @Override | |
| 151 | + public void setSurface(Surface surface) { | |
| 152 | + try { | |
| 153 | + mMediaPlayer.setSurface(surface); | |
| 154 | + } catch (Exception e) { | |
| 155 | + mPlayerEventListener.onError(); | |
| 156 | + } | |
| 157 | + } | |
| 158 | + | |
| 159 | + @Override | |
| 160 | + public void setDisplay(SurfaceHolder holder) { | |
| 161 | + try { | |
| 162 | + mMediaPlayer.setDisplay(holder); | |
| 163 | + } catch (Exception e) { | |
| 164 | + mPlayerEventListener.onError(); | |
| 165 | + } | |
| 166 | + } | |
| 167 | + | |
| 168 | + @Override | |
| 169 | + public void setVolume(float v1, float v2) { | |
| 170 | + mMediaPlayer.setVolume(v1, v2); | |
| 171 | + } | |
| 172 | + | |
| 173 | + @Override | |
| 174 | + public void setLooping(boolean isLooping) { | |
| 175 | + mMediaPlayer.setLooping(isLooping); | |
| 176 | + } | |
| 177 | + | |
| 178 | + @Override | |
| 179 | + public void setOptions() { | |
| 180 | + } | |
| 181 | + | |
| 182 | + @Override | |
| 183 | + public void setSpeed(float speed) { | |
| 184 | + // only support above Android M | |
| 185 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { | |
| 186 | + try { | |
| 187 | + mMediaPlayer.setPlaybackParams(mMediaPlayer.getPlaybackParams().setSpeed(speed)); | |
| 188 | + } catch (Exception e) { | |
| 189 | + mPlayerEventListener.onError(); | |
| 190 | + } | |
| 191 | + } | |
| 192 | + } | |
| 193 | + | |
| 194 | + @Override | |
| 195 | + public long getTcpSpeed() { | |
| 196 | + // no support | |
| 197 | + return 0; | |
| 198 | + } | |
| 199 | + | |
| 200 | + private MediaPlayer.OnErrorListener onErrorListener = new MediaPlayer.OnErrorListener() { | |
| 201 | + @Override | |
| 202 | + public boolean onError(MediaPlayer mp, int what, int extra) { | |
| 203 | + mPlayerEventListener.onError(); | |
| 204 | + return true; | |
| 205 | + } | |
| 206 | + }; | |
| 207 | + | |
| 208 | + private MediaPlayer.OnCompletionListener onCompletionListener = new MediaPlayer.OnCompletionListener() { | |
| 209 | + @Override | |
| 210 | + public void onCompletion(MediaPlayer mp) { | |
| 211 | + mPlayerEventListener.onCompletion(); | |
| 212 | + } | |
| 213 | + }; | |
| 214 | + | |
| 215 | + private MediaPlayer.OnInfoListener onInfoListener = new MediaPlayer.OnInfoListener() { | |
| 216 | + @Override | |
| 217 | + public boolean onInfo(MediaPlayer mp, int what, int extra) { | |
| 218 | + //解决MEDIA_INFO_VIDEO_RENDERING_START多次回调问题 | |
| 219 | + if (what == AbstractPlayer.MEDIA_INFO_VIDEO_RENDERING_START) { | |
| 220 | + if (mIsPreparing) { | |
| 221 | + mPlayerEventListener.onInfo(what, extra); | |
| 222 | + mIsPreparing = false; | |
| 223 | + } | |
| 224 | + } else { | |
| 225 | + mPlayerEventListener.onInfo(what, extra); | |
| 226 | + } | |
| 227 | + return true; | |
| 228 | + } | |
| 229 | + }; | |
| 230 | + | |
| 231 | + private MediaPlayer.OnBufferingUpdateListener onBufferingUpdateListener = new MediaPlayer.OnBufferingUpdateListener() { | |
| 232 | + @Override | |
| 233 | + public void onBufferingUpdate(MediaPlayer mp, int percent) { | |
| 234 | + mBufferedPercent = percent; | |
| 235 | + } | |
| 236 | + }; | |
| 237 | + | |
| 238 | + | |
| 239 | + private MediaPlayer.OnPreparedListener onPreparedListener = new MediaPlayer.OnPreparedListener() { | |
| 240 | + @Override | |
| 241 | + public void onPrepared(MediaPlayer mp) { | |
| 242 | + mPlayerEventListener.onPrepared(); | |
| 243 | + start(); | |
| 244 | + } | |
| 245 | + }; | |
| 246 | + | |
| 247 | + private MediaPlayer.OnVideoSizeChangedListener onVideoSizeChangedListener = new MediaPlayer.OnVideoSizeChangedListener() { | |
| 248 | + @Override | |
| 249 | + public void onVideoSizeChanged(MediaPlayer mp, int width, int height) { | |
| 250 | + int videoWidth = mp.getVideoWidth(); | |
| 251 | + int videoHeight = mp.getVideoHeight(); | |
| 252 | + if (videoWidth != 0 && videoHeight != 0) { | |
| 253 | + mPlayerEventListener.onVideoSizeChanged(videoWidth, videoHeight); | |
| 254 | + } | |
| 255 | + } | |
| 256 | + }; | |
| 257 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/play/AndroidMediaPlayerFactory.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.play; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | + | |
| 5 | +public class AndroidMediaPlayerFactory extends PlayerFactory<AndroidMediaPlayer> { | |
| 6 | + | |
| 7 | + public static AndroidMediaPlayerFactory create() { | |
| 8 | + return new AndroidMediaPlayerFactory(); | |
| 9 | + } | |
| 10 | + | |
| 11 | + @Override | |
| 12 | + public AndroidMediaPlayer createPlayer(Context context) { | |
| 13 | + return new AndroidMediaPlayer(context); | |
| 14 | + } | |
| 15 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/play/AudioFocusHelper.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.play; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.media.AudioManager; | |
| 5 | +import android.os.Handler; | |
| 6 | +import android.os.Looper; | |
| 7 | + | |
| 8 | +import java.lang.ref.WeakReference; | |
| 9 | + | |
| 10 | +import androidx.annotation.NonNull; | |
| 11 | + | |
| 12 | +/** | |
| 13 | + * 音频焦点改变监听 | |
| 14 | + */ | |
| 15 | +final class AudioFocusHelper implements AudioManager.OnAudioFocusChangeListener { | |
| 16 | + | |
| 17 | + private Handler mHandler = new Handler(Looper.getMainLooper()); | |
| 18 | + | |
| 19 | + private WeakReference<CNVideoView> mWeakVideoView; | |
| 20 | + | |
| 21 | + private AudioManager mAudioManager; | |
| 22 | + | |
| 23 | + private boolean mStartRequested = false; | |
| 24 | + private boolean mPausedForLoss = false; | |
| 25 | + private int mCurrentFocus = 0; | |
| 26 | + | |
| 27 | + AudioFocusHelper(@NonNull CNVideoView videoView) { | |
| 28 | + mWeakVideoView = new WeakReference<>(videoView); | |
| 29 | + mAudioManager = (AudioManager) videoView.getContext().getApplicationContext().getSystemService(Context.AUDIO_SERVICE); | |
| 30 | + } | |
| 31 | + | |
| 32 | + @Override | |
| 33 | + public void onAudioFocusChange(final int focusChange) { | |
| 34 | + if (mCurrentFocus == focusChange) { | |
| 35 | + return; | |
| 36 | + } | |
| 37 | + | |
| 38 | + //由于onAudioFocusChange有可能在子线程调用, | |
| 39 | + //故通过此方式切换到主线程去执行 | |
| 40 | + mHandler.post(new Runnable() { | |
| 41 | + @Override | |
| 42 | + public void run() { | |
| 43 | + handleAudioFocusChange(focusChange); | |
| 44 | + } | |
| 45 | + }); | |
| 46 | + | |
| 47 | + mCurrentFocus = focusChange; | |
| 48 | + } | |
| 49 | + | |
| 50 | + private void handleAudioFocusChange(int focusChange) { | |
| 51 | + final CNVideoView videoView = mWeakVideoView.get(); | |
| 52 | + if (videoView == null) { | |
| 53 | + return; | |
| 54 | + } | |
| 55 | + switch (focusChange) { | |
| 56 | + case AudioManager.AUDIOFOCUS_GAIN://获得焦点 | |
| 57 | + case AudioManager.AUDIOFOCUS_GAIN_TRANSIENT://暂时获得焦点 | |
| 58 | + if (mStartRequested || mPausedForLoss) { | |
| 59 | + videoView.start(); | |
| 60 | + mStartRequested = false; | |
| 61 | + mPausedForLoss = false; | |
| 62 | + } | |
| 63 | + if (!videoView.isMute())//恢复音量 | |
| 64 | + videoView.setVolume(1.0f, 1.0f); | |
| 65 | + break; | |
| 66 | + case AudioManager.AUDIOFOCUS_LOSS://焦点丢失 | |
| 67 | + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT://焦点暂时丢失 | |
| 68 | + if (videoView.isPlaying()) { | |
| 69 | + mPausedForLoss = true; | |
| 70 | + videoView.pause(); | |
| 71 | + } | |
| 72 | + break; | |
| 73 | + case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK://此时需降低音量 | |
| 74 | + if (videoView.isPlaying() && !videoView.isMute()) { | |
| 75 | + videoView.setVolume(0.1f, 0.1f); | |
| 76 | + } | |
| 77 | + break; | |
| 78 | + } | |
| 79 | + } | |
| 80 | + | |
| 81 | + /** | |
| 82 | + * Requests to obtain the audio focus | |
| 83 | + */ | |
| 84 | + void requestFocus() { | |
| 85 | + if (mCurrentFocus == AudioManager.AUDIOFOCUS_GAIN) { | |
| 86 | + return; | |
| 87 | + } | |
| 88 | + | |
| 89 | + if (mAudioManager == null) { | |
| 90 | + return; | |
| 91 | + } | |
| 92 | + | |
| 93 | + int status = mAudioManager.requestAudioFocus(this, AudioManager.STREAM_MUSIC, AudioManager.AUDIOFOCUS_GAIN); | |
| 94 | + if (AudioManager.AUDIOFOCUS_REQUEST_GRANTED == status) { | |
| 95 | + mCurrentFocus = AudioManager.AUDIOFOCUS_GAIN; | |
| 96 | + return; | |
| 97 | + } | |
| 98 | + | |
| 99 | + mStartRequested = true; | |
| 100 | + } | |
| 101 | + | |
| 102 | + /** | |
| 103 | + * Requests the system to drop the audio focus | |
| 104 | + */ | |
| 105 | + void abandonFocus() { | |
| 106 | + | |
| 107 | + if (mAudioManager == null) { | |
| 108 | + return; | |
| 109 | + } | |
| 110 | + | |
| 111 | + mStartRequested = false; | |
| 112 | + mAudioManager.abandonAudioFocus(this); | |
| 113 | + } | |
| 114 | +} | |
| 0 | 115 | \ No newline at end of file | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/play/CNVideoView.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.play; | |
| 2 | + | |
| 3 | +import android.app.Activity; | |
| 4 | +import android.content.ContentResolver; | |
| 5 | +import android.content.Context; | |
| 6 | +import android.content.res.AssetFileDescriptor; | |
| 7 | +import android.content.res.TypedArray; | |
| 8 | +import android.graphics.Bitmap; | |
| 9 | +import android.graphics.Color; | |
| 10 | +import android.net.Uri; | |
| 11 | +import android.os.Build; | |
| 12 | +import android.os.Parcelable; | |
| 13 | +import android.text.TextUtils; | |
| 14 | +import android.util.AttributeSet; | |
| 15 | +import android.view.Gravity; | |
| 16 | +import android.view.View; | |
| 17 | +import android.view.ViewGroup; | |
| 18 | +import android.view.WindowManager; | |
| 19 | +import android.widget.FrameLayout; | |
| 20 | + | |
| 21 | +import com.cnlive.cloud.video.R; | |
| 22 | +import com.libs.video.video.exoplayer.controller.BaseVideoController; | |
| 23 | +import com.libs.video.video.exoplayer.controller.MediaPlayerControl; | |
| 24 | +import com.libs.video.video.exoplayer.util.L; | |
| 25 | +import com.libs.video.video.exoplayer.util.PlayerUtils; | |
| 26 | +import com.libs.video.video.exoplayer.render.IRenderView; | |
| 27 | +import com.libs.video.video.exoplayer.render.RenderViewFactory; | |
| 28 | + | |
| 29 | +import java.io.IOException; | |
| 30 | +import java.util.ArrayList; | |
| 31 | +import java.util.List; | |
| 32 | +import java.util.Map; | |
| 33 | + | |
| 34 | +import androidx.annotation.NonNull; | |
| 35 | +import androidx.annotation.Nullable; | |
| 36 | + | |
| 37 | +/** | |
| 38 | + * 视频播放视图 | |
| 39 | + */ | |
| 40 | + | |
| 41 | +public class CNVideoView<P extends AbstractPlayer> extends FrameLayout | |
| 42 | + implements MediaPlayerControl, AbstractPlayer.PlayerEventListener { | |
| 43 | + | |
| 44 | + protected P mMediaPlayer;//播放器 | |
| 45 | + protected PlayerFactory<P> mPlayerFactory;//工厂类,用于实例化播放核心 | |
| 46 | + @Nullable | |
| 47 | + protected BaseVideoController mVideoController;//控制器 | |
| 48 | + | |
| 49 | + /** | |
| 50 | + * 真正承载播放器视图的容器 | |
| 51 | + */ | |
| 52 | + protected FrameLayout mPlayerContainer; | |
| 53 | + | |
| 54 | + protected IRenderView mRenderView;//渲染视图 | |
| 55 | + protected RenderViewFactory mRenderViewFactory; | |
| 56 | + | |
| 57 | + public static final int SCREEN_SCALE_DEFAULT = 0;//默认全屏 | |
| 58 | + public static final int SCREEN_SCALE_16_9 = 1;//16:9 | |
| 59 | + public static final int SCREEN_SCALE_4_3 = 2;//4:3 | |
| 60 | + public static final int SCREEN_SCALE_MATCH_PARENT = 3; | |
| 61 | + public static final int SCREEN_SCALE_ORIGINAL = 4; | |
| 62 | + public static final int SCREEN_SCALE_CENTER_CROP = 5; | |
| 63 | + protected int mCurrentScreenScaleType; | |
| 64 | + | |
| 65 | + protected int[] mVideoSize = {0, 0}; | |
| 66 | + | |
| 67 | + protected boolean mIsMute;//是否静音 | |
| 68 | + | |
| 69 | + //--------- data sources ---------// | |
| 70 | + protected String mUrl;//当前播放视频的地址 | |
| 71 | + protected Map<String, String> mHeaders;//当前视频地址的请求头 | |
| 72 | + protected AssetFileDescriptor mAssetFileDescriptor;//assets文件 | |
| 73 | + | |
| 74 | + protected long mCurrentPosition;//当前正在播放视频的位置 | |
| 75 | + | |
| 76 | + //播放器的各种状态 | |
| 77 | + public static final int STATE_ERROR = -1;//错误 | |
| 78 | + public static final int STATE_IDLE = 0;//空闲 | |
| 79 | + public static final int STATE_PREPARING = 1;//准备中 | |
| 80 | + public static final int STATE_PREPARED = 2;//准备完成 | |
| 81 | + public static final int STATE_PLAYING = 3;//播放中 | |
| 82 | + public static final int STATE_PAUSED = 4;//暂停 | |
| 83 | + public static final int STATE_PLAYBACK_COMPLETED = 5; | |
| 84 | + public static final int STATE_BUFFERING = 6;//正在缓冲 | |
| 85 | + public static final int STATE_BUFFERED = 7;//缓冲完成 | |
| 86 | + public static final int STATE_START_ABORT = 8;//开始播放中止 | |
| 87 | + protected int mCurrentPlayState = STATE_IDLE;//当前播放器的状态 | |
| 88 | + | |
| 89 | + public static final int PLAYER_NORMAL = 10; // 普通播放器 | |
| 90 | + public static final int PLAYER_FULL_SCREEN = 11; // 全屏播放器 | |
| 91 | + public static final int PLAYER_TINY_SCREEN = 12; // 小屏播放器 | |
| 92 | + protected int mCurrentPlayerState = PLAYER_NORMAL;//当前播放状态 | |
| 93 | + | |
| 94 | + protected boolean mIsFullScreen;//是否处于全屏状态 | |
| 95 | + | |
| 96 | + protected boolean mIsTinyScreen;//是否处于小屏状态 | |
| 97 | + protected int[] mTinyScreenSize = {0, 0}; | |
| 98 | + | |
| 99 | + /** | |
| 100 | + * 监听系统中音频焦点改变,见{@link #setEnableAudioFocus(boolean)} | |
| 101 | + */ | |
| 102 | + protected boolean mEnableAudioFocus; | |
| 103 | + @Nullable | |
| 104 | + protected AudioFocusHelper mAudioFocusHelper; | |
| 105 | + | |
| 106 | + /** | |
| 107 | + * OnStateChangeListener集合,保存了所有开发者设置的监听器 | |
| 108 | + */ | |
| 109 | + protected List<OnStateChangeListener> mOnStateChangeListeners; | |
| 110 | + | |
| 111 | + /** | |
| 112 | + * 进度管理器,设置之后播放器会记录播放进度,以便下次播放恢复进度 | |
| 113 | + */ | |
| 114 | + @Nullable | |
| 115 | + protected ProgressManager mProgressManager; | |
| 116 | + | |
| 117 | + /** | |
| 118 | + * 循环播放 | |
| 119 | + */ | |
| 120 | + protected boolean mIsLooping; | |
| 121 | + | |
| 122 | + /** | |
| 123 | + * {@link #mPlayerContainer}背景色,默认黑色 | |
| 124 | + */ | |
| 125 | + private int mPlayerBackgroundColor; | |
| 126 | + | |
| 127 | + private List<String> dataSource; | |
| 128 | + private int curentPlayIndex; | |
| 129 | + | |
| 130 | + public CNVideoView(@NonNull Context context) { | |
| 131 | + this(context, null); | |
| 132 | + } | |
| 133 | + | |
| 134 | + public CNVideoView(@NonNull Context context, @Nullable AttributeSet attrs) { | |
| 135 | + this(context, attrs, 0); | |
| 136 | + } | |
| 137 | + | |
| 138 | + public CNVideoView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | |
| 139 | + super(context, attrs, defStyleAttr); | |
| 140 | + dataSource = new ArrayList<>(); | |
| 141 | + //读取全局配置 | |
| 142 | + VideoViewConfig config = VideoViewManager.getConfig(); | |
| 143 | + mEnableAudioFocus = config.mEnableAudioFocus; | |
| 144 | + mProgressManager = config.mProgressManager; | |
| 145 | + mPlayerFactory = config.mPlayerFactory; | |
| 146 | + mCurrentScreenScaleType = config.mScreenScaleType; | |
| 147 | + mRenderViewFactory = config.mRenderViewFactory; | |
| 148 | + | |
| 149 | + //读取xml中的配置,并综合全局配置 | |
| 150 | + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CNVideoView); | |
| 151 | + mEnableAudioFocus = a.getBoolean(R.styleable.CNVideoView_enableAudioFocus, mEnableAudioFocus); | |
| 152 | + mIsLooping = a.getBoolean(R.styleable.CNVideoView_looping, false); | |
| 153 | + mCurrentScreenScaleType = a.getInt(R.styleable.CNVideoView_screenScaleType, mCurrentScreenScaleType); | |
| 154 | + mPlayerBackgroundColor = a.getColor(R.styleable.CNVideoView_playerBackgroundColor, Color.BLACK); | |
| 155 | + a.recycle(); | |
| 156 | + | |
| 157 | + initView(); | |
| 158 | + } | |
| 159 | + | |
| 160 | + /** | |
| 161 | + * 初始化播放器视图 | |
| 162 | + */ | |
| 163 | + protected void initView() { | |
| 164 | + mPlayerContainer = new FrameLayout(getContext()); | |
| 165 | + mPlayerContainer.setBackgroundColor(mPlayerBackgroundColor); | |
| 166 | + LayoutParams params = new LayoutParams( | |
| 167 | + ViewGroup.LayoutParams.MATCH_PARENT, | |
| 168 | + ViewGroup.LayoutParams.MATCH_PARENT); | |
| 169 | + this.addView(mPlayerContainer, params); | |
| 170 | + } | |
| 171 | + | |
| 172 | + /** | |
| 173 | + * 设置{@link #mPlayerContainer}的背景色 | |
| 174 | + */ | |
| 175 | + public void setPlayerBackgroundColor(int color) { | |
| 176 | + mPlayerContainer.setBackgroundColor(color); | |
| 177 | + } | |
| 178 | + | |
| 179 | + | |
| 180 | + /** | |
| 181 | + * 当前媒体数量 | |
| 182 | + * | |
| 183 | + * @return | |
| 184 | + */ | |
| 185 | + @Override | |
| 186 | + public int getMediaCount() { | |
| 187 | + return dataSource.size(); | |
| 188 | + } | |
| 189 | + | |
| 190 | + /** | |
| 191 | + * 开始播放,注意:调用此方法后必须调用{@link #release()}释放播放器,否则会导致内存泄漏 | |
| 192 | + */ | |
| 193 | + @Override | |
| 194 | + public void start() { | |
| 195 | + boolean isStarted = false; | |
| 196 | + if (isInIdleState() || isInStartAbortState()) { | |
| 197 | + isStarted = startPlay(); | |
| 198 | + } else if (isInPlaybackState()) { | |
| 199 | + startInPlaybackState(); | |
| 200 | + isStarted = true; | |
| 201 | + } | |
| 202 | + if (isStarted) { | |
| 203 | + mPlayerContainer.setKeepScreenOn(true); | |
| 204 | + if (mAudioFocusHelper != null) | |
| 205 | + mAudioFocusHelper.requestFocus(); | |
| 206 | + } | |
| 207 | + } | |
| 208 | + | |
| 209 | + /** | |
| 210 | + * 第一次播放 | |
| 211 | + * | |
| 212 | + * @return 是否成功开始播放 | |
| 213 | + */ | |
| 214 | + protected boolean startPlay() { | |
| 215 | + //如果要显示移动网络提示则不继续播放 | |
| 216 | + if (showNetWarning()) { | |
| 217 | + //中止播放 | |
| 218 | + setPlayState(STATE_START_ABORT); | |
| 219 | + return false; | |
| 220 | + } | |
| 221 | + //监听音频焦点改变 | |
| 222 | + if (mEnableAudioFocus) { | |
| 223 | + mAudioFocusHelper = new AudioFocusHelper(this); | |
| 224 | + } | |
| 225 | + //读取播放进度 | |
| 226 | + if (mProgressManager != null) { | |
| 227 | + mCurrentPosition = mProgressManager.getSavedProgress(mUrl); | |
| 228 | + } | |
| 229 | + //初始化播放器 | |
| 230 | + initPlayer(); | |
| 231 | + //添加渲染视图 | |
| 232 | + addDisplay(); | |
| 233 | + startPrepare(false); | |
| 234 | + return true; | |
| 235 | + } | |
| 236 | + | |
| 237 | + /** | |
| 238 | + * 是否显示移动网络提示,可在Controller中配置 | |
| 239 | + */ | |
| 240 | + protected boolean showNetWarning() { | |
| 241 | + //播放本地数据源时不检测网络 | |
| 242 | + if (isLocalDataSource()) return false; | |
| 243 | + return mVideoController != null && mVideoController.showNetWarning(); | |
| 244 | + } | |
| 245 | + | |
| 246 | + /** | |
| 247 | + * 判断是否为本地数据源,包括 本地文件、Asset、raw | |
| 248 | + */ | |
| 249 | + protected boolean isLocalDataSource() { | |
| 250 | + if (mAssetFileDescriptor != null) { | |
| 251 | + return true; | |
| 252 | + } else if (!TextUtils.isEmpty(mUrl)) { | |
| 253 | + Uri uri = Uri.parse(mUrl); | |
| 254 | + return ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme()) | |
| 255 | + || ContentResolver.SCHEME_FILE.equals(uri.getScheme()) | |
| 256 | + || "rawresource".equals(uri.getScheme()); | |
| 257 | + } | |
| 258 | + return false; | |
| 259 | + } | |
| 260 | + | |
| 261 | + /** | |
| 262 | + * 初始化播放器 | |
| 263 | + */ | |
| 264 | + protected void initPlayer() { | |
| 265 | + mMediaPlayer = mPlayerFactory.createPlayer(getContext()); | |
| 266 | + mMediaPlayer.setPlayerEventListener(this); | |
| 267 | + setInitOptions(); | |
| 268 | + mMediaPlayer.initPlayer(); | |
| 269 | + setOptions(); | |
| 270 | + } | |
| 271 | + | |
| 272 | + /** | |
| 273 | + * 初始化之前的配置项 | |
| 274 | + */ | |
| 275 | + protected void setInitOptions() { | |
| 276 | + } | |
| 277 | + | |
| 278 | + /** | |
| 279 | + * 初始化之后的配置项 | |
| 280 | + */ | |
| 281 | + protected void setOptions() { | |
| 282 | + mMediaPlayer.setLooping(mIsLooping); | |
| 283 | + } | |
| 284 | + | |
| 285 | + /** | |
| 286 | + * 初始化视频渲染View | |
| 287 | + */ | |
| 288 | + protected void addDisplay() { | |
| 289 | + //先移除之前的渲染视图 | |
| 290 | + if (mRenderView != null) { | |
| 291 | + mPlayerContainer.removeView(mRenderView.getView()); | |
| 292 | + mRenderView.release(); | |
| 293 | + } | |
| 294 | + //创建新的渲染视图 | |
| 295 | + mRenderView = mRenderViewFactory.createRenderView(getContext()); | |
| 296 | + mRenderView.attachToPlayer(mMediaPlayer); | |
| 297 | + LayoutParams params = new LayoutParams( | |
| 298 | + ViewGroup.LayoutParams.MATCH_PARENT, | |
| 299 | + ViewGroup.LayoutParams.MATCH_PARENT, | |
| 300 | + Gravity.CENTER); | |
| 301 | + //添加渲染视图 | |
| 302 | + mPlayerContainer.addView(mRenderView.getView(), 0, params); | |
| 303 | + } | |
| 304 | + | |
| 305 | + /** | |
| 306 | + * 开始准备播放(直接播放) | |
| 307 | + */ | |
| 308 | + protected void startPrepare(boolean reset) { | |
| 309 | + if (reset) { | |
| 310 | + mMediaPlayer.reset(); | |
| 311 | + //重新设置option,media player reset之后,option会失效 | |
| 312 | + setOptions(); | |
| 313 | + } | |
| 314 | + if (prepareDataSource()) { | |
| 315 | + mMediaPlayer.prepareAsync(); | |
| 316 | + setPlayState(STATE_PREPARING); | |
| 317 | + setPlayerState(isFullScreen() ? PLAYER_FULL_SCREEN : isTinyScreen() ? PLAYER_TINY_SCREEN : PLAYER_NORMAL); | |
| 318 | + } | |
| 319 | + } | |
| 320 | + | |
| 321 | + /** | |
| 322 | + * 设置播放数据 | |
| 323 | + * | |
| 324 | + * @return 播放数据是否设置成功 | |
| 325 | + */ | |
| 326 | + protected boolean prepareDataSource() { | |
| 327 | + if (mAssetFileDescriptor != null) { | |
| 328 | + mMediaPlayer.setDataSource(mAssetFileDescriptor); | |
| 329 | + return true; | |
| 330 | + } else if (!TextUtils.isEmpty(mUrl)) { | |
| 331 | + mMediaPlayer.setDataSource(mUrl, mHeaders); | |
| 332 | + return true; | |
| 333 | + } | |
| 334 | + return false; | |
| 335 | + } | |
| 336 | + | |
| 337 | + /** | |
| 338 | + * 播放状态下开始播放 | |
| 339 | + */ | |
| 340 | + protected void startInPlaybackState() { | |
| 341 | + mMediaPlayer.start(); | |
| 342 | + setPlayState(STATE_PLAYING); | |
| 343 | + } | |
| 344 | + | |
| 345 | + /** | |
| 346 | + * 暂停播放 | |
| 347 | + */ | |
| 348 | + @Override | |
| 349 | + public void pause() { | |
| 350 | + if (isInPlaybackState() | |
| 351 | + && mMediaPlayer.isPlaying()) { | |
| 352 | + mMediaPlayer.pause(); | |
| 353 | + setPlayState(STATE_PAUSED); | |
| 354 | + if (mAudioFocusHelper != null) { | |
| 355 | + mAudioFocusHelper.abandonFocus(); | |
| 356 | + } | |
| 357 | + mPlayerContainer.setKeepScreenOn(false); | |
| 358 | + } | |
| 359 | + } | |
| 360 | + | |
| 361 | + /** | |
| 362 | + * 继续播放 | |
| 363 | + */ | |
| 364 | + public void resume() { | |
| 365 | + if (isInPlaybackState() | |
| 366 | + && !mMediaPlayer.isPlaying()) { | |
| 367 | + mMediaPlayer.start(); | |
| 368 | + setPlayState(STATE_PLAYING); | |
| 369 | + if (mAudioFocusHelper != null) { | |
| 370 | + mAudioFocusHelper.requestFocus(); | |
| 371 | + } | |
| 372 | + mPlayerContainer.setKeepScreenOn(true); | |
| 373 | + } | |
| 374 | + } | |
| 375 | + | |
| 376 | + /** | |
| 377 | + * 停止播放 | |
| 378 | + * | |
| 379 | + * @deprecated 使用 {@link #release()} 代替 | |
| 380 | + */ | |
| 381 | + @Deprecated | |
| 382 | + public void stopPlayback() { | |
| 383 | + release(); | |
| 384 | + } | |
| 385 | + | |
| 386 | + | |
| 387 | + /** | |
| 388 | + * 释放播放器 | |
| 389 | + */ | |
| 390 | + public void release() { | |
| 391 | + if (!isInIdleState()) { | |
| 392 | + //释放播放器 | |
| 393 | + if (mMediaPlayer != null) { | |
| 394 | + mMediaPlayer.release(); | |
| 395 | + mMediaPlayer = null; | |
| 396 | + } | |
| 397 | + //释放renderView | |
| 398 | + if (mRenderView != null) { | |
| 399 | + mPlayerContainer.removeView(mRenderView.getView()); | |
| 400 | + mRenderView.release(); | |
| 401 | + mRenderView = null; | |
| 402 | + } | |
| 403 | + //释放Assets资源 | |
| 404 | + if (mAssetFileDescriptor != null) { | |
| 405 | + try { | |
| 406 | + mAssetFileDescriptor.close(); | |
| 407 | + } catch (IOException e) { | |
| 408 | + e.printStackTrace(); | |
| 409 | + } | |
| 410 | + } | |
| 411 | + //关闭AudioFocus监听 | |
| 412 | + if (mAudioFocusHelper != null) { | |
| 413 | + mAudioFocusHelper.abandonFocus(); | |
| 414 | + mAudioFocusHelper = null; | |
| 415 | + } | |
| 416 | + //关闭屏幕常亮 | |
| 417 | + mPlayerContainer.setKeepScreenOn(false); | |
| 418 | + //保存播放进度 | |
| 419 | + saveProgress(); | |
| 420 | + //重置播放进度 | |
| 421 | + mCurrentPosition = 0; | |
| 422 | + //切换转态 | |
| 423 | + setPlayState(STATE_IDLE); | |
| 424 | + } | |
| 425 | + } | |
| 426 | + | |
| 427 | + /** | |
| 428 | + * 保存播放进度 | |
| 429 | + */ | |
| 430 | + protected void saveProgress() { | |
| 431 | + if (mProgressManager != null && mCurrentPosition > 0) { | |
| 432 | + L.d("saveProgress: " + mCurrentPosition); | |
| 433 | + mProgressManager.saveProgress(mUrl, mCurrentPosition); | |
| 434 | + } | |
| 435 | + } | |
| 436 | + | |
| 437 | + /** | |
| 438 | + * 是否处于播放状态 | |
| 439 | + */ | |
| 440 | + protected boolean isInPlaybackState() { | |
| 441 | + return mMediaPlayer != null | |
| 442 | + && mCurrentPlayState != STATE_ERROR | |
| 443 | + && mCurrentPlayState != STATE_IDLE | |
| 444 | + && mCurrentPlayState != STATE_PREPARING | |
| 445 | + && mCurrentPlayState != STATE_START_ABORT | |
| 446 | + && mCurrentPlayState != STATE_PLAYBACK_COMPLETED; | |
| 447 | + } | |
| 448 | + | |
| 449 | + /** | |
| 450 | + * 是否处于未播放状态 | |
| 451 | + */ | |
| 452 | + protected boolean isInIdleState() { | |
| 453 | + return mCurrentPlayState == STATE_IDLE; | |
| 454 | + } | |
| 455 | + | |
| 456 | + /** | |
| 457 | + * 播放中止状态 | |
| 458 | + */ | |
| 459 | + private boolean isInStartAbortState() { | |
| 460 | + return mCurrentPlayState == STATE_START_ABORT; | |
| 461 | + } | |
| 462 | + | |
| 463 | + /** | |
| 464 | + * 重新播放 | |
| 465 | + * | |
| 466 | + * @param resetPosition 是否从头开始播放 | |
| 467 | + */ | |
| 468 | + @Override | |
| 469 | + public void replay(boolean resetPosition) { | |
| 470 | + if (resetPosition) { | |
| 471 | + mCurrentPosition = 0; | |
| 472 | + } | |
| 473 | + addDisplay(); | |
| 474 | + startPrepare(true); | |
| 475 | + mPlayerContainer.setKeepScreenOn(true); | |
| 476 | + } | |
| 477 | + | |
| 478 | + /** | |
| 479 | + * 获取视频总时长 | |
| 480 | + */ | |
| 481 | + @Override | |
| 482 | + public long getDuration() { | |
| 483 | + if (isInPlaybackState()) { | |
| 484 | + return mMediaPlayer.getDuration(); | |
| 485 | + } | |
| 486 | + return 0; | |
| 487 | + } | |
| 488 | + | |
| 489 | + /** | |
| 490 | + * 获取当前播放的位置 | |
| 491 | + */ | |
| 492 | + @Override | |
| 493 | + public long getCurrentPosition() { | |
| 494 | + if (isInPlaybackState()) { | |
| 495 | + mCurrentPosition = mMediaPlayer.getCurrentPosition(); | |
| 496 | + return mCurrentPosition; | |
| 497 | + } | |
| 498 | + return 0; | |
| 499 | + } | |
| 500 | + | |
| 501 | + /** | |
| 502 | + * 调整播放进度 | |
| 503 | + */ | |
| 504 | + @Override | |
| 505 | + public void seekTo(long pos) { | |
| 506 | + if (isInPlaybackState()) { | |
| 507 | + mMediaPlayer.seekTo(pos); | |
| 508 | + } | |
| 509 | + } | |
| 510 | + | |
| 511 | + /** | |
| 512 | + * 是否处于播放状态 | |
| 513 | + */ | |
| 514 | + @Override | |
| 515 | + public boolean isPlaying() { | |
| 516 | + return isInPlaybackState() && mMediaPlayer.isPlaying(); | |
| 517 | + } | |
| 518 | + | |
| 519 | + /** | |
| 520 | + * 获取当前缓冲百分比 | |
| 521 | + */ | |
| 522 | + @Override | |
| 523 | + public int getBufferedPercentage() { | |
| 524 | + return mMediaPlayer != null ? mMediaPlayer.getBufferedPercentage() : 0; | |
| 525 | + } | |
| 526 | + | |
| 527 | + /** | |
| 528 | + * 设置静音 | |
| 529 | + */ | |
| 530 | + @Override | |
| 531 | + public void setMute(boolean isMute) { | |
| 532 | + if (mMediaPlayer != null) { | |
| 533 | + this.mIsMute = isMute; | |
| 534 | + float volume = isMute ? 0.0f : 1.0f; | |
| 535 | + mMediaPlayer.setVolume(volume, volume); | |
| 536 | + } | |
| 537 | + } | |
| 538 | + | |
| 539 | + /** | |
| 540 | + * 是否处于静音状态 | |
| 541 | + */ | |
| 542 | + @Override | |
| 543 | + public boolean isMute() { | |
| 544 | + return mIsMute; | |
| 545 | + } | |
| 546 | + | |
| 547 | + /** | |
| 548 | + * 视频播放出错回调 | |
| 549 | + */ | |
| 550 | + @Override | |
| 551 | + public void onError() { | |
| 552 | + mPlayerContainer.setKeepScreenOn(false); | |
| 553 | + setPlayState(STATE_ERROR); | |
| 554 | + } | |
| 555 | + | |
| 556 | + /** | |
| 557 | + * 视频播放完成回调 | |
| 558 | + */ | |
| 559 | + @Override | |
| 560 | + public void onCompletion() { | |
| 561 | + mPlayerContainer.setKeepScreenOn(false); | |
| 562 | + mCurrentPosition = 0; | |
| 563 | + if (mProgressManager != null) { | |
| 564 | + //播放完成,清除进度 | |
| 565 | + mProgressManager.saveProgress(mUrl, 0); | |
| 566 | + } | |
| 567 | + setPlayState(STATE_PLAYBACK_COMPLETED); | |
| 568 | + | |
| 569 | + if (curentPlayIndex + 1 <= dataSource.size() - 1) { | |
| 570 | + curentPlayIndex+=1; | |
| 571 | + mUrl = dataSource.get(curentPlayIndex); | |
| 572 | + replay(true); | |
| 573 | + } | |
| 574 | + } | |
| 575 | + | |
| 576 | + @Override | |
| 577 | + public void onInfo(int what, int extra) { | |
| 578 | + switch (what) { | |
| 579 | + case AbstractPlayer.MEDIA_INFO_BUFFERING_START: | |
| 580 | + setPlayState(STATE_BUFFERING); | |
| 581 | + break; | |
| 582 | + case AbstractPlayer.MEDIA_INFO_BUFFERING_END: | |
| 583 | + setPlayState(STATE_BUFFERED); | |
| 584 | + break; | |
| 585 | + case AbstractPlayer.MEDIA_INFO_VIDEO_RENDERING_START: // 视频开始渲染 | |
| 586 | + setPlayState(STATE_PLAYING); | |
| 587 | + if (mPlayerContainer.getWindowVisibility() != VISIBLE) { | |
| 588 | + pause(); | |
| 589 | + } | |
| 590 | + break; | |
| 591 | + case AbstractPlayer.MEDIA_INFO_VIDEO_ROTATION_CHANGED: | |
| 592 | + if (mRenderView != null) | |
| 593 | + mRenderView.setVideoRotation(extra); | |
| 594 | + break; | |
| 595 | + } | |
| 596 | + } | |
| 597 | + | |
| 598 | + /** | |
| 599 | + * 视频缓冲完毕,准备开始播放时回调 | |
| 600 | + */ | |
| 601 | + @Override | |
| 602 | + public void onPrepared() { | |
| 603 | + setPlayState(STATE_PREPARED); | |
| 604 | + if (mCurrentPosition > 0) { | |
| 605 | + seekTo(mCurrentPosition); | |
| 606 | + } | |
| 607 | + } | |
| 608 | + | |
| 609 | + /** | |
| 610 | + * 获取当前播放器的状态 | |
| 611 | + */ | |
| 612 | + public int getCurrentPlayerState() { | |
| 613 | + return mCurrentPlayerState; | |
| 614 | + } | |
| 615 | + | |
| 616 | + /** | |
| 617 | + * 获取当前的播放状态 | |
| 618 | + */ | |
| 619 | + public int getCurrentPlayState() { | |
| 620 | + return mCurrentPlayState; | |
| 621 | + } | |
| 622 | + | |
| 623 | + /** | |
| 624 | + * 获取缓冲速度 | |
| 625 | + */ | |
| 626 | + @Override | |
| 627 | + public long getTcpSpeed() { | |
| 628 | + return mMediaPlayer != null ? mMediaPlayer.getTcpSpeed() : 0; | |
| 629 | + } | |
| 630 | + | |
| 631 | + /** | |
| 632 | + * 设置播放速度 | |
| 633 | + */ | |
| 634 | + @Override | |
| 635 | + public void setSpeed(float speed) { | |
| 636 | + if (isInPlaybackState()) { | |
| 637 | + mMediaPlayer.setSpeed(speed); | |
| 638 | + } | |
| 639 | + } | |
| 640 | + | |
| 641 | + /** | |
| 642 | + * 设置视频地址 | |
| 643 | + */ | |
| 644 | + public void setUrl(String url) { | |
| 645 | + dataSource.clear(); | |
| 646 | + dataSource.add(url); | |
| 647 | + curentPlayIndex = 0; | |
| 648 | + setUrl(dataSource.get(0), null); | |
| 649 | + } | |
| 650 | + | |
| 651 | + public void setDataSource(List<String> url) { | |
| 652 | + dataSource.clear(); | |
| 653 | + dataSource.addAll(url); | |
| 654 | + curentPlayIndex = 0; | |
| 655 | + setUrl(dataSource.get(0), null); | |
| 656 | + | |
| 657 | + } | |
| 658 | + | |
| 659 | + /** | |
| 660 | + * 设置包含请求头信息的视频地址 | |
| 661 | + * | |
| 662 | + * @param url 视频地址 | |
| 663 | + * @param headers 请求头 | |
| 664 | + */ | |
| 665 | + public void setUrl(String url, Map<String, String> headers) { | |
| 666 | + mAssetFileDescriptor = null; | |
| 667 | + mUrl = url; | |
| 668 | + mHeaders = headers; | |
| 669 | + } | |
| 670 | + | |
| 671 | + /** | |
| 672 | + * 用于播放assets里面的视频文件 | |
| 673 | + */ | |
| 674 | + public void setAssetFileDescriptor(AssetFileDescriptor fd) { | |
| 675 | + mUrl = null; | |
| 676 | + this.mAssetFileDescriptor = fd; | |
| 677 | + } | |
| 678 | + | |
| 679 | + /** | |
| 680 | + * 一开始播放就seek到预先设置好的位置 | |
| 681 | + */ | |
| 682 | + public void skipPositionWhenPlay(int position) { | |
| 683 | + this.mCurrentPosition = position; | |
| 684 | + } | |
| 685 | + | |
| 686 | + /** | |
| 687 | + * 设置音量 0.0f-1.0f 之间 | |
| 688 | + * | |
| 689 | + * @param v1 左声道音量 | |
| 690 | + * @param v2 右声道音量 | |
| 691 | + */ | |
| 692 | + public void setVolume(float v1, float v2) { | |
| 693 | + if (mMediaPlayer != null) { | |
| 694 | + mMediaPlayer.setVolume(v1, v2); | |
| 695 | + } | |
| 696 | + } | |
| 697 | + | |
| 698 | + /** | |
| 699 | + * 设置进度管理器,用于保存播放进度 | |
| 700 | + */ | |
| 701 | + public void setProgressManager(@Nullable ProgressManager progressManager) { | |
| 702 | + this.mProgressManager = progressManager; | |
| 703 | + } | |
| 704 | + | |
| 705 | + /** | |
| 706 | + * 循环播放, 默认不循环播放 | |
| 707 | + */ | |
| 708 | + public void setLooping(boolean looping) { | |
| 709 | + mIsLooping = looping; | |
| 710 | + if (mMediaPlayer != null) { | |
| 711 | + mMediaPlayer.setLooping(looping); | |
| 712 | + } | |
| 713 | + } | |
| 714 | + | |
| 715 | + /** | |
| 716 | + * 是否开启AudioFocus监听, 默认开启,用于监听其它地方是否获取音频焦点,如果有其它地方获取了 | |
| 717 | + * 音频焦点,此播放器将做出相应反应,具体实现见{@link AudioFocusHelper} | |
| 718 | + */ | |
| 719 | + public void setEnableAudioFocus(boolean enableAudioFocus) { | |
| 720 | + mEnableAudioFocus = enableAudioFocus; | |
| 721 | + } | |
| 722 | + | |
| 723 | + /** | |
| 724 | + * 自定义播放核心,继承{@link PlayerFactory}实现自己的播放核心 | |
| 725 | + */ | |
| 726 | + public void setPlayerFactory(PlayerFactory<P> playerFactory) { | |
| 727 | + if (playerFactory == null) { | |
| 728 | + throw new IllegalArgumentException("PlayerFactory can not be null!"); | |
| 729 | + } | |
| 730 | + mPlayerFactory = playerFactory; | |
| 731 | + } | |
| 732 | + | |
| 733 | + /** | |
| 734 | + * 自定义RenderView,继承{@link RenderViewFactory}实现自己的RenderView | |
| 735 | + */ | |
| 736 | + public void setRenderViewFactory(RenderViewFactory renderViewFactory) { | |
| 737 | + if (renderViewFactory == null) { | |
| 738 | + throw new IllegalArgumentException("RenderViewFactory can not be null!"); | |
| 739 | + } | |
| 740 | + mRenderViewFactory = renderViewFactory; | |
| 741 | + } | |
| 742 | + | |
| 743 | + /** | |
| 744 | + * 进入全屏 | |
| 745 | + */ | |
| 746 | + @Override | |
| 747 | + public void startFullScreen() { | |
| 748 | + if (mIsFullScreen) | |
| 749 | + return; | |
| 750 | + | |
| 751 | + ViewGroup decorView = getDecorView(); | |
| 752 | + if (decorView == null) | |
| 753 | + return; | |
| 754 | + | |
| 755 | + mIsFullScreen = true; | |
| 756 | + | |
| 757 | + //隐藏NavigationBar和StatusBar | |
| 758 | + hideSysBar(decorView); | |
| 759 | + | |
| 760 | + //从当前FrameLayout中移除播放器视图 | |
| 761 | + this.removeView(mPlayerContainer); | |
| 762 | + //将播放器视图添加到DecorView中即实现了全屏 | |
| 763 | + decorView.addView(mPlayerContainer); | |
| 764 | + | |
| 765 | + setPlayerState(PLAYER_FULL_SCREEN); | |
| 766 | + } | |
| 767 | + | |
| 768 | + private void hideSysBar(ViewGroup decorView) { | |
| 769 | + int uiOptions = decorView.getSystemUiVisibility(); | |
| 770 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { | |
| 771 | + uiOptions |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; | |
| 772 | + } | |
| 773 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | |
| 774 | + uiOptions |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; | |
| 775 | + } | |
| 776 | + decorView.setSystemUiVisibility(uiOptions); | |
| 777 | + getActivity().getWindow().setFlags( | |
| 778 | + WindowManager.LayoutParams.FLAG_FULLSCREEN, | |
| 779 | + WindowManager.LayoutParams.FLAG_FULLSCREEN); | |
| 780 | + } | |
| 781 | + | |
| 782 | + @Override | |
| 783 | + public void onWindowFocusChanged(boolean hasWindowFocus) { | |
| 784 | + super.onWindowFocusChanged(hasWindowFocus); | |
| 785 | + if (hasWindowFocus && mIsFullScreen) { | |
| 786 | + //重新获得焦点时保持全屏状态 | |
| 787 | + hideSysBar(getDecorView()); | |
| 788 | + } | |
| 789 | + } | |
| 790 | + | |
| 791 | + /** | |
| 792 | + * 退出全屏 | |
| 793 | + */ | |
| 794 | + @Override | |
| 795 | + public void stopFullScreen() { | |
| 796 | + if (!mIsFullScreen) | |
| 797 | + return; | |
| 798 | + | |
| 799 | + ViewGroup decorView = getDecorView(); | |
| 800 | + if (decorView == null) | |
| 801 | + return; | |
| 802 | + | |
| 803 | + mIsFullScreen = false; | |
| 804 | + | |
| 805 | + //显示NavigationBar和StatusBar | |
| 806 | + showSysBar(decorView); | |
| 807 | + | |
| 808 | + //把播放器视图从DecorView中移除并添加到当前FrameLayout中即退出了全屏 | |
| 809 | + decorView.removeView(mPlayerContainer); | |
| 810 | + this.addView(mPlayerContainer); | |
| 811 | + | |
| 812 | + setPlayerState(PLAYER_NORMAL); | |
| 813 | + } | |
| 814 | + | |
| 815 | + private void showSysBar(ViewGroup decorView) { | |
| 816 | + int uiOptions = decorView.getSystemUiVisibility(); | |
| 817 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { | |
| 818 | + uiOptions &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; | |
| 819 | + } | |
| 820 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { | |
| 821 | + uiOptions &= ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; | |
| 822 | + } | |
| 823 | + decorView.setSystemUiVisibility(uiOptions); | |
| 824 | + getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); | |
| 825 | + } | |
| 826 | + | |
| 827 | + /** | |
| 828 | + * 获取DecorView | |
| 829 | + */ | |
| 830 | + protected ViewGroup getDecorView() { | |
| 831 | + Activity activity = getActivity(); | |
| 832 | + if (activity == null) return null; | |
| 833 | + return (ViewGroup) activity.getWindow().getDecorView(); | |
| 834 | + } | |
| 835 | + | |
| 836 | + /** | |
| 837 | + * 获取activity中的content view,其id为android.R.id.content | |
| 838 | + */ | |
| 839 | + protected ViewGroup getContentView() { | |
| 840 | + Activity activity = getActivity(); | |
| 841 | + if (activity == null) return null; | |
| 842 | + return activity.findViewById(android.R.id.content); | |
| 843 | + } | |
| 844 | + | |
| 845 | + /** | |
| 846 | + * 获取Activity,优先通过Controller去获取Activity | |
| 847 | + */ | |
| 848 | + protected Activity getActivity() { | |
| 849 | + Activity activity; | |
| 850 | + if (mVideoController != null) { | |
| 851 | + activity = PlayerUtils.scanForActivity(mVideoController.getContext()); | |
| 852 | + if (activity == null) { | |
| 853 | + activity = PlayerUtils.scanForActivity(getContext()); | |
| 854 | + } | |
| 855 | + } else { | |
| 856 | + activity = PlayerUtils.scanForActivity(getContext()); | |
| 857 | + } | |
| 858 | + return activity; | |
| 859 | + } | |
| 860 | + | |
| 861 | + /** | |
| 862 | + * 判断是否处于全屏状态 | |
| 863 | + */ | |
| 864 | + @Override | |
| 865 | + public boolean isFullScreen() { | |
| 866 | + return mIsFullScreen; | |
| 867 | + } | |
| 868 | + | |
| 869 | + /** | |
| 870 | + * 开启小屏 | |
| 871 | + */ | |
| 872 | + public void startTinyScreen() { | |
| 873 | + if (mIsTinyScreen) return; | |
| 874 | + ViewGroup contentView = getContentView(); | |
| 875 | + if (contentView == null) return; | |
| 876 | + this.removeView(mPlayerContainer); | |
| 877 | + int width = mTinyScreenSize[0]; | |
| 878 | + if (width <= 0) { | |
| 879 | + width = PlayerUtils.getScreenWidth(getContext(), false) / 2; | |
| 880 | + } | |
| 881 | + | |
| 882 | + int height = mTinyScreenSize[1]; | |
| 883 | + if (height <= 0) { | |
| 884 | + height = width * 9 / 16; | |
| 885 | + } | |
| 886 | + | |
| 887 | + LayoutParams params = new LayoutParams(width, height); | |
| 888 | + params.gravity = Gravity.BOTTOM | Gravity.END; | |
| 889 | + contentView.addView(mPlayerContainer, params); | |
| 890 | + mIsTinyScreen = true; | |
| 891 | + setPlayerState(PLAYER_TINY_SCREEN); | |
| 892 | + } | |
| 893 | + | |
| 894 | + /** | |
| 895 | + * 退出小屏 | |
| 896 | + */ | |
| 897 | + public void stopTinyScreen() { | |
| 898 | + if (!mIsTinyScreen) return; | |
| 899 | + | |
| 900 | + ViewGroup contentView = getContentView(); | |
| 901 | + if (contentView == null) return; | |
| 902 | + contentView.removeView(mPlayerContainer); | |
| 903 | + LayoutParams params = new LayoutParams( | |
| 904 | + ViewGroup.LayoutParams.MATCH_PARENT, | |
| 905 | + ViewGroup.LayoutParams.MATCH_PARENT); | |
| 906 | + this.addView(mPlayerContainer, params); | |
| 907 | + | |
| 908 | + mIsTinyScreen = false; | |
| 909 | + setPlayerState(PLAYER_NORMAL); | |
| 910 | + } | |
| 911 | + | |
| 912 | + public boolean isTinyScreen() { | |
| 913 | + return mIsTinyScreen; | |
| 914 | + } | |
| 915 | + | |
| 916 | + @Override | |
| 917 | + public void onVideoSizeChanged(int videoWidth, int videoHeight) { | |
| 918 | + mVideoSize[0] = videoWidth; | |
| 919 | + mVideoSize[1] = videoHeight; | |
| 920 | + | |
| 921 | + if (mRenderView != null) { | |
| 922 | + mRenderView.setScaleType(mCurrentScreenScaleType); | |
| 923 | + mRenderView.setVideoSize(videoWidth, videoHeight); | |
| 924 | + } | |
| 925 | + } | |
| 926 | + | |
| 927 | + /** | |
| 928 | + * 设置控制器,传null表示移除控制器 | |
| 929 | + */ | |
| 930 | + public void setVideoController(@Nullable BaseVideoController mediaController) { | |
| 931 | + mPlayerContainer.removeView(mVideoController); | |
| 932 | + mVideoController = mediaController; | |
| 933 | + if (mediaController != null) { | |
| 934 | + mediaController.setMediaPlayer(this); | |
| 935 | + LayoutParams params = new LayoutParams( | |
| 936 | + ViewGroup.LayoutParams.MATCH_PARENT, | |
| 937 | + ViewGroup.LayoutParams.MATCH_PARENT); | |
| 938 | + mPlayerContainer.addView(mVideoController, params); | |
| 939 | + } | |
| 940 | + } | |
| 941 | + | |
| 942 | + /** | |
| 943 | + * 设置视频比例 | |
| 944 | + */ | |
| 945 | + @Override | |
| 946 | + public void setScreenScaleType(int screenScaleType) { | |
| 947 | + mCurrentScreenScaleType = screenScaleType; | |
| 948 | + if (mRenderView != null) { | |
| 949 | + mRenderView.setScaleType(screenScaleType); | |
| 950 | + } | |
| 951 | + } | |
| 952 | + | |
| 953 | + /** | |
| 954 | + * 设置镜像旋转,暂不支持SurfaceView | |
| 955 | + */ | |
| 956 | + @Override | |
| 957 | + public void setMirrorRotation(boolean enable) { | |
| 958 | + if (mRenderView != null) { | |
| 959 | + mRenderView.getView().setScaleX(enable ? -1 : 1); | |
| 960 | + } | |
| 961 | + } | |
| 962 | + | |
| 963 | + /** | |
| 964 | + * 截图,暂不支持SurfaceView | |
| 965 | + */ | |
| 966 | + @Override | |
| 967 | + public Bitmap doScreenShot() { | |
| 968 | + if (mRenderView != null) { | |
| 969 | + return mRenderView.doScreenShot(); | |
| 970 | + } | |
| 971 | + return null; | |
| 972 | + } | |
| 973 | + | |
| 974 | + /** | |
| 975 | + * 获取视频宽高,其中width: mVideoSize[0], height: mVideoSize[1] | |
| 976 | + */ | |
| 977 | + @Override | |
| 978 | + public int[] getVideoSize() { | |
| 979 | + return mVideoSize; | |
| 980 | + } | |
| 981 | + | |
| 982 | + /** | |
| 983 | + * 旋转视频画面 | |
| 984 | + * | |
| 985 | + * @param rotation 角度 | |
| 986 | + */ | |
| 987 | + @Override | |
| 988 | + public void setRotation(float rotation) { | |
| 989 | + if (mRenderView != null) { | |
| 990 | + mRenderView.setVideoRotation((int) rotation); | |
| 991 | + } | |
| 992 | + } | |
| 993 | + | |
| 994 | + /** | |
| 995 | + * 设置小屏的宽高 | |
| 996 | + * | |
| 997 | + * @param tinyScreenSize 其中tinyScreenSize[0]是宽,tinyScreenSize[1]是高 | |
| 998 | + */ | |
| 999 | + public void setTinyScreenSize(int[] tinyScreenSize) { | |
| 1000 | + this.mTinyScreenSize = tinyScreenSize; | |
| 1001 | + } | |
| 1002 | + | |
| 1003 | + /** | |
| 1004 | + * 向Controller设置播放状态,用于控制Controller的ui展示 | |
| 1005 | + */ | |
| 1006 | + protected void setPlayState(int playState) { | |
| 1007 | + mCurrentPlayState = playState; | |
| 1008 | + if (mVideoController != null) { | |
| 1009 | + mVideoController.setPlayState(playState); | |
| 1010 | + } | |
| 1011 | + if (mOnStateChangeListeners != null) { | |
| 1012 | + for (OnStateChangeListener l : PlayerUtils.getSnapshot(mOnStateChangeListeners)) { | |
| 1013 | + if (l != null) { | |
| 1014 | + l.onPlayStateChanged(playState); | |
| 1015 | + } | |
| 1016 | + } | |
| 1017 | + } | |
| 1018 | + } | |
| 1019 | + | |
| 1020 | + /** | |
| 1021 | + * 向Controller设置播放器状态,包含全屏状态和非全屏状态 | |
| 1022 | + */ | |
| 1023 | + protected void setPlayerState(int playerState) { | |
| 1024 | + mCurrentPlayerState = playerState; | |
| 1025 | + if (mVideoController != null) { | |
| 1026 | + mVideoController.setPlayerState(playerState); | |
| 1027 | + } | |
| 1028 | + if (mOnStateChangeListeners != null) { | |
| 1029 | + for (OnStateChangeListener l : PlayerUtils.getSnapshot(mOnStateChangeListeners)) { | |
| 1030 | + if (l != null) { | |
| 1031 | + l.onPlayerStateChanged(playerState); | |
| 1032 | + } | |
| 1033 | + } | |
| 1034 | + } | |
| 1035 | + | |
| 1036 | + } | |
| 1037 | + | |
| 1038 | + /** | |
| 1039 | + * 播放状态改变监听器 | |
| 1040 | + */ | |
| 1041 | + public interface OnStateChangeListener { | |
| 1042 | + void onPlayerStateChanged(int playerState); | |
| 1043 | + | |
| 1044 | + void onPlayStateChanged(int playState); | |
| 1045 | + } | |
| 1046 | + | |
| 1047 | + /** | |
| 1048 | + * OnStateChangeListener的空实现。用的时候只需要重写需要的方法 | |
| 1049 | + */ | |
| 1050 | + public static class SimpleOnStateChangeListener implements OnStateChangeListener { | |
| 1051 | + @Override | |
| 1052 | + public void onPlayerStateChanged(int playerState) { | |
| 1053 | + } | |
| 1054 | + | |
| 1055 | + @Override | |
| 1056 | + public void onPlayStateChanged(int playState) { | |
| 1057 | + } | |
| 1058 | + } | |
| 1059 | + | |
| 1060 | + /** | |
| 1061 | + * 添加一个播放状态监听器,播放状态发生变化时将会调用。 | |
| 1062 | + */ | |
| 1063 | + public void addOnStateChangeListener(@NonNull OnStateChangeListener listener) { | |
| 1064 | + if (mOnStateChangeListeners == null) { | |
| 1065 | + mOnStateChangeListeners = new ArrayList<>(); | |
| 1066 | + } | |
| 1067 | + mOnStateChangeListeners.add(listener); | |
| 1068 | + } | |
| 1069 | + | |
| 1070 | + /** | |
| 1071 | + * 移除某个播放状态监听 | |
| 1072 | + */ | |
| 1073 | + public void removeOnStateChangeListener(@NonNull OnStateChangeListener listener) { | |
| 1074 | + if (mOnStateChangeListeners != null) { | |
| 1075 | + mOnStateChangeListeners.remove(listener); | |
| 1076 | + } | |
| 1077 | + } | |
| 1078 | + | |
| 1079 | + /** | |
| 1080 | + * 设置一个播放状态监听器,播放状态发生变化时将会调用, | |
| 1081 | + * 如果你想同时设置多个监听器,推荐 {@link #addOnStateChangeListener(OnStateChangeListener)}。 | |
| 1082 | + */ | |
| 1083 | + public void setOnStateChangeListener(@NonNull OnStateChangeListener listener) { | |
| 1084 | + if (mOnStateChangeListeners == null) { | |
| 1085 | + mOnStateChangeListeners = new ArrayList<>(); | |
| 1086 | + } else { | |
| 1087 | + mOnStateChangeListeners.clear(); | |
| 1088 | + } | |
| 1089 | + mOnStateChangeListeners.add(listener); | |
| 1090 | + } | |
| 1091 | + | |
| 1092 | + /** | |
| 1093 | + * 移除所有播放状态监听 | |
| 1094 | + */ | |
| 1095 | + public void clearOnStateChangeListeners() { | |
| 1096 | + if (mOnStateChangeListeners != null) { | |
| 1097 | + mOnStateChangeListeners.clear(); | |
| 1098 | + } | |
| 1099 | + } | |
| 1100 | + | |
| 1101 | + /** | |
| 1102 | + * 改变返回键逻辑,用于activity | |
| 1103 | + */ | |
| 1104 | + public boolean isBackPressed() { | |
| 1105 | + return mVideoController != null && mVideoController.onBackPressed(); | |
| 1106 | + } | |
| 1107 | + | |
| 1108 | + @Override | |
| 1109 | + protected Parcelable onSaveInstanceState() { | |
| 1110 | + L.d("onSaveInstanceState: " + mCurrentPosition); | |
| 1111 | + //activity切到后台后可能被系统回收,故在此处进行进度保存 | |
| 1112 | + saveProgress(); | |
| 1113 | + return super.onSaveInstanceState(); | |
| 1114 | + } | |
| 1115 | + | |
| 1116 | + | |
| 1117 | + public void onResume() { | |
| 1118 | + resume(); | |
| 1119 | + } | |
| 1120 | + | |
| 1121 | + public void onPause() { | |
| 1122 | + pause(); | |
| 1123 | + } | |
| 1124 | + | |
| 1125 | + public void onDestroy() { | |
| 1126 | + release(); | |
| 1127 | + } | |
| 1128 | + | |
| 1129 | + | |
| 1130 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/play/PlayerFactory.java
0 → 100644
cloud/video/src/main/java/com/libs/video/video/exoplayer/play/ProgressManager.java
0 → 100644
cloud/video/src/main/java/com/libs/video/video/exoplayer/play/VideoViewConfig.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.play; | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | +import com.libs.video.video.exoplayer.render.RenderViewFactory; | |
| 6 | +import com.libs.video.video.exoplayer.render.texture.TextureRenderViewFactory; | |
| 7 | + | |
| 8 | +import androidx.annotation.Nullable; | |
| 9 | + | |
| 10 | +/** | |
| 11 | + * 播放器全局配置 | |
| 12 | + */ | |
| 13 | +public class VideoViewConfig { | |
| 14 | + | |
| 15 | + public static Builder newBuilder() { | |
| 16 | + return new Builder(); | |
| 17 | + } | |
| 18 | + | |
| 19 | + public final boolean mPlayOnMobileNetwork; | |
| 20 | + | |
| 21 | + public final boolean mEnableOrientation; | |
| 22 | + | |
| 23 | + public final boolean mEnableAudioFocus; | |
| 24 | + | |
| 25 | + public final boolean mEnableParallelPlay; | |
| 26 | + | |
| 27 | + public final boolean mIsEnableLog; | |
| 28 | + | |
| 29 | + public final ProgressManager mProgressManager; | |
| 30 | + | |
| 31 | + public final PlayerFactory mPlayerFactory; | |
| 32 | + | |
| 33 | + public final int mScreenScaleType; | |
| 34 | + | |
| 35 | + public final RenderViewFactory mRenderViewFactory; | |
| 36 | + | |
| 37 | + public final boolean mAdaptCutout; | |
| 38 | + | |
| 39 | + private VideoViewConfig(Builder builder) { | |
| 40 | + mIsEnableLog = builder.mIsEnableLog; | |
| 41 | + mEnableOrientation = builder.mEnableOrientation; | |
| 42 | + mPlayOnMobileNetwork = builder.mPlayOnMobileNetwork; | |
| 43 | + mEnableAudioFocus = builder.mEnableAudioFocus; | |
| 44 | + mProgressManager = builder.mProgressManager; | |
| 45 | + mEnableParallelPlay = builder.mEnableParallelPlay; | |
| 46 | + mScreenScaleType = builder.mScreenScaleType; | |
| 47 | + if (builder.mPlayerFactory == null) { | |
| 48 | + //默认为AndroidMediaPlayer | |
| 49 | + mPlayerFactory = AndroidMediaPlayerFactory.create(); | |
| 50 | + } else { | |
| 51 | + mPlayerFactory = builder.mPlayerFactory; | |
| 52 | + } | |
| 53 | + if (builder.mRenderViewFactory == null) { | |
| 54 | + //默认使用TextureView渲染视频 | |
| 55 | + mRenderViewFactory = TextureRenderViewFactory.create(); | |
| 56 | + } else { | |
| 57 | + mRenderViewFactory = builder.mRenderViewFactory; | |
| 58 | + } | |
| 59 | + mAdaptCutout = builder.mAdaptCutout; | |
| 60 | + } | |
| 61 | + | |
| 62 | + | |
| 63 | + public final static class Builder { | |
| 64 | + | |
| 65 | + private boolean mIsEnableLog; | |
| 66 | + private boolean mPlayOnMobileNetwork; | |
| 67 | + private boolean mEnableOrientation; | |
| 68 | + private boolean mEnableAudioFocus = true; | |
| 69 | + private boolean mEnableParallelPlay; | |
| 70 | + private ProgressManager mProgressManager; | |
| 71 | + private PlayerFactory mPlayerFactory; | |
| 72 | + private int mScreenScaleType; | |
| 73 | + private RenderViewFactory mRenderViewFactory; | |
| 74 | + private boolean mAdaptCutout = true; | |
| 75 | + | |
| 76 | + /** | |
| 77 | + * 是否监听设备方向来切换全屏/半屏, 默认不开启 | |
| 78 | + */ | |
| 79 | + public Builder setEnableOrientation(boolean enableOrientation) { | |
| 80 | + mEnableOrientation = enableOrientation; | |
| 81 | + return this; | |
| 82 | + } | |
| 83 | + | |
| 84 | + /** | |
| 85 | + * 在移动环境下调用start()后是否继续播放,默认不继续播放 | |
| 86 | + */ | |
| 87 | + public Builder setPlayOnMobileNetwork(boolean playOnMobileNetwork) { | |
| 88 | + mPlayOnMobileNetwork = playOnMobileNetwork; | |
| 89 | + return this; | |
| 90 | + } | |
| 91 | + | |
| 92 | + /** | |
| 93 | + * 是否开启AudioFocus监听, 默认开启 | |
| 94 | + */ | |
| 95 | + public Builder setEnableAudioFocus(boolean enableAudioFocus) { | |
| 96 | + mEnableAudioFocus = enableAudioFocus; | |
| 97 | + return this; | |
| 98 | + } | |
| 99 | + | |
| 100 | + /** | |
| 101 | + * 设置进度管理器,用于保存播放进度 | |
| 102 | + */ | |
| 103 | + public Builder setProgressManager(@Nullable ProgressManager progressManager) { | |
| 104 | + mProgressManager = progressManager; | |
| 105 | + return this; | |
| 106 | + } | |
| 107 | + | |
| 108 | + /** | |
| 109 | + * 支持多开 | |
| 110 | + * @deprecated 此api已经无效,你需要自己去控制同时只有一个VideoView在播放的效果 | |
| 111 | + */ | |
| 112 | + @Deprecated | |
| 113 | + public Builder setEnableParallelPlay(boolean enableParallelPlay) { | |
| 114 | + mEnableParallelPlay = enableParallelPlay; | |
| 115 | + return this; | |
| 116 | + } | |
| 117 | + | |
| 118 | + /** | |
| 119 | + * 是否打印日志 | |
| 120 | + */ | |
| 121 | + public Builder setLogEnabled(boolean enableLog) { | |
| 122 | + mIsEnableLog = enableLog; | |
| 123 | + return this; | |
| 124 | + } | |
| 125 | + | |
| 126 | + /** | |
| 127 | + * 自定义播放核心 | |
| 128 | + */ | |
| 129 | + public Builder setPlayerFactory(PlayerFactory playerFactory) { | |
| 130 | + mPlayerFactory = playerFactory; | |
| 131 | + return this; | |
| 132 | + } | |
| 133 | + | |
| 134 | + /** | |
| 135 | + * 设置视频比例 | |
| 136 | + */ | |
| 137 | + public Builder setScreenScaleType(int screenScaleType) { | |
| 138 | + mScreenScaleType = screenScaleType; | |
| 139 | + return this; | |
| 140 | + } | |
| 141 | + | |
| 142 | + /** | |
| 143 | + * 自定义RenderView | |
| 144 | + */ | |
| 145 | + public Builder setRenderViewFactory(RenderViewFactory renderViewFactory) { | |
| 146 | + mRenderViewFactory = renderViewFactory; | |
| 147 | + return this; | |
| 148 | + } | |
| 149 | + | |
| 150 | + /** | |
| 151 | + * 是否适配刘海屏,默认适配 | |
| 152 | + */ | |
| 153 | + public Builder setAdaptCutout(boolean adaptCutout) { | |
| 154 | + mAdaptCutout = adaptCutout; | |
| 155 | + return this; | |
| 156 | + } | |
| 157 | + | |
| 158 | + public VideoViewConfig build() { | |
| 159 | + return new VideoViewConfig(this); | |
| 160 | + } | |
| 161 | + } | |
| 162 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/play/VideoViewManager.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.play; | |
| 2 | + | |
| 3 | +import android.app.Application; | |
| 4 | +import com.libs.video.video.exoplayer.util.L; | |
| 5 | + | |
| 6 | +import java.util.LinkedHashMap; | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 视频播放器管理器,管理当前正在播放的VideoView,以及播放器配置 | |
| 10 | + * 你也可以用来保存常驻内存的VideoView,但是要注意通过Application Context创建, | |
| 11 | + * 以免内存泄漏 | |
| 12 | + */ | |
| 13 | +public class VideoViewManager { | |
| 14 | + | |
| 15 | + /** | |
| 16 | + * 保存VideoView的容器 | |
| 17 | + */ | |
| 18 | + private LinkedHashMap<String, CNVideoView> mVideoViews = new LinkedHashMap<>(); | |
| 19 | + | |
| 20 | + /** | |
| 21 | + * 是否在移动网络下直接播放视频 | |
| 22 | + */ | |
| 23 | + private boolean mPlayOnMobileNetwork; | |
| 24 | + | |
| 25 | + /** | |
| 26 | + * VideoViewManager实例 | |
| 27 | + */ | |
| 28 | + private static VideoViewManager sInstance; | |
| 29 | + | |
| 30 | + /** | |
| 31 | + * VideoViewConfig实例 | |
| 32 | + */ | |
| 33 | + private static VideoViewConfig sConfig; | |
| 34 | + | |
| 35 | + private VideoViewManager() { | |
| 36 | + mPlayOnMobileNetwork = getConfig().mPlayOnMobileNetwork; | |
| 37 | + } | |
| 38 | + | |
| 39 | + /** | |
| 40 | + * 设置VideoViewConfig | |
| 41 | + */ | |
| 42 | + public static void setConfig(VideoViewConfig config) { | |
| 43 | + if (sConfig == null) { | |
| 44 | + synchronized (VideoViewConfig.class) { | |
| 45 | + if (sConfig == null) { | |
| 46 | + sConfig = config == null ? VideoViewConfig.newBuilder().build() : config; | |
| 47 | + } | |
| 48 | + } | |
| 49 | + } | |
| 50 | + } | |
| 51 | + | |
| 52 | + /** | |
| 53 | + * 获取VideoViewConfig | |
| 54 | + */ | |
| 55 | + public static VideoViewConfig getConfig() { | |
| 56 | + setConfig(null); | |
| 57 | + return sConfig; | |
| 58 | + } | |
| 59 | + | |
| 60 | + /** | |
| 61 | + * 获取是否在移动网络下直接播放视频配置 | |
| 62 | + */ | |
| 63 | + public boolean playOnMobileNetwork() { | |
| 64 | + return mPlayOnMobileNetwork; | |
| 65 | + } | |
| 66 | + | |
| 67 | + /** | |
| 68 | + * 设置是否在移动网络下直接播放视频 | |
| 69 | + */ | |
| 70 | + public void setPlayOnMobileNetwork(boolean playOnMobileNetwork) { | |
| 71 | + mPlayOnMobileNetwork = playOnMobileNetwork; | |
| 72 | + } | |
| 73 | + | |
| 74 | + public static VideoViewManager instance() { | |
| 75 | + if (sInstance == null) { | |
| 76 | + synchronized (VideoViewManager.class) { | |
| 77 | + if (sInstance == null) { | |
| 78 | + sInstance = new VideoViewManager(); | |
| 79 | + } | |
| 80 | + } | |
| 81 | + } | |
| 82 | + return sInstance; | |
| 83 | + } | |
| 84 | + | |
| 85 | + /** | |
| 86 | + * 添加VideoView | |
| 87 | + * @param tag 相同tag的VideoView只会保存一个,如果tag相同则会release并移除前一个 | |
| 88 | + */ | |
| 89 | + public void add(CNVideoView videoView, String tag) { | |
| 90 | + if (!(videoView.getContext() instanceof Application)) { | |
| 91 | + L.w("The Context of this VideoView is not an Application Context," + | |
| 92 | + "you must remove it after release,or it will lead to memory leek."); | |
| 93 | + } | |
| 94 | + CNVideoView old = get(tag); | |
| 95 | + if (old != null) { | |
| 96 | + old.release(); | |
| 97 | + remove(tag); | |
| 98 | + } | |
| 99 | + mVideoViews.put(tag, videoView); | |
| 100 | + } | |
| 101 | + | |
| 102 | + public CNVideoView get(String tag) { | |
| 103 | + return mVideoViews.get(tag); | |
| 104 | + } | |
| 105 | + | |
| 106 | + public void remove(String tag) { | |
| 107 | + mVideoViews.remove(tag); | |
| 108 | + } | |
| 109 | + | |
| 110 | + public void removeAll() { | |
| 111 | + mVideoViews.clear(); | |
| 112 | + } | |
| 113 | + | |
| 114 | + /** | |
| 115 | + * 释放掉和tag关联的VideoView,并将其从VideoViewManager中移除 | |
| 116 | + */ | |
| 117 | + public void releaseByTag(String tag) { | |
| 118 | + releaseByTag(tag, true); | |
| 119 | + } | |
| 120 | + | |
| 121 | + public void releaseByTag(String tag, boolean isRemove) { | |
| 122 | + CNVideoView videoView = get(tag); | |
| 123 | + if (videoView != null) { | |
| 124 | + videoView.release(); | |
| 125 | + if (isRemove) { | |
| 126 | + remove(tag); | |
| 127 | + } | |
| 128 | + } | |
| 129 | + } | |
| 130 | + | |
| 131 | + public boolean onBackPress(String tag) { | |
| 132 | + CNVideoView videoView = get(tag); | |
| 133 | + if (videoView == null) return false; | |
| 134 | + return videoView.isBackPressed(); | |
| 135 | + } | |
| 136 | + | |
| 137 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/playerCore/exoplayer/ExoMediaPlayer.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.playerCore.exoplayer; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.content.res.AssetFileDescriptor; | |
| 5 | +import android.os.Handler; | |
| 6 | +import android.view.Surface; | |
| 7 | +import android.view.SurfaceHolder; | |
| 8 | + | |
| 9 | +import com.google.android.exoplayer2.DefaultLoadControl; | |
| 10 | +import com.google.android.exoplayer2.DefaultRenderersFactory; | |
| 11 | +import com.google.android.exoplayer2.ExoPlaybackException; | |
| 12 | +import com.google.android.exoplayer2.LoadControl; | |
| 13 | +import com.google.android.exoplayer2.PlaybackParameters; | |
| 14 | +import com.google.android.exoplayer2.Player; | |
| 15 | +import com.google.android.exoplayer2.RenderersFactory; | |
| 16 | +import com.google.android.exoplayer2.SimpleExoPlayer; | |
| 17 | +import com.google.android.exoplayer2.analytics.AnalyticsCollector; | |
| 18 | +import com.google.android.exoplayer2.source.DefaultMediaSourceEventListener; | |
| 19 | +import com.google.android.exoplayer2.source.MediaSource; | |
| 20 | +import com.google.android.exoplayer2.source.MediaSourceEventListener; | |
| 21 | +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; | |
| 22 | +import com.google.android.exoplayer2.trackselection.MappingTrackSelector; | |
| 23 | +import com.google.android.exoplayer2.trackselection.TrackSelector; | |
| 24 | +import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; | |
| 25 | +import com.google.android.exoplayer2.util.Clock; | |
| 26 | +import com.google.android.exoplayer2.util.EventLogger; | |
| 27 | +import com.google.android.exoplayer2.util.Util; | |
| 28 | +import com.google.android.exoplayer2.video.VideoListener; | |
| 29 | +import com.libs.video.video.exoplayer.play.AbstractPlayer; | |
| 30 | +import com.libs.video.video.exoplayer.play.VideoViewManager; | |
| 31 | + | |
| 32 | +import java.util.Map; | |
| 33 | + | |
| 34 | + | |
| 35 | +/** | |
| 36 | + * ExoPlayer播放 | |
| 37 | + */ | |
| 38 | +public class ExoMediaPlayer extends AbstractPlayer implements VideoListener, Player.EventListener { | |
| 39 | + | |
| 40 | + protected Context mAppContext; | |
| 41 | + protected SimpleExoPlayer exoPlayer; | |
| 42 | + protected MediaSource mMediaSource; | |
| 43 | + protected ExoMediaSourceHelper mMediaSourceHelper; | |
| 44 | + //播放参数控制 | |
| 45 | + private PlaybackParameters mSpeedPlaybackParameters; | |
| 46 | + //最后播放状态 | |
| 47 | + private int mLastReportedPlaybackState = Player.STATE_IDLE; | |
| 48 | + private boolean mLastReportedPlayWhenReady = false; | |
| 49 | + private boolean mIsPreparing;//是否准备 | |
| 50 | + private boolean mIsBuffering;//是否缓冲 | |
| 51 | + | |
| 52 | + private LoadControl mLoadControl; | |
| 53 | + private RenderersFactory mRenderersFactory; | |
| 54 | + private TrackSelector mTrackSelector; | |
| 55 | + | |
| 56 | + public ExoMediaPlayer(Context context) { | |
| 57 | + mAppContext = context.getApplicationContext(); | |
| 58 | + mMediaSourceHelper = ExoMediaSourceHelper.getInstance(context); | |
| 59 | + } | |
| 60 | + | |
| 61 | + @Override | |
| 62 | + public void initPlayer() { | |
| 63 | + //创建exoplayer对象 | |
| 64 | + exoPlayer = new SimpleExoPlayer.Builder( | |
| 65 | + mAppContext, | |
| 66 | + mRenderersFactory == null ? mRenderersFactory = new DefaultRenderersFactory(mAppContext) : mRenderersFactory, | |
| 67 | + mTrackSelector == null ? mTrackSelector = new DefaultTrackSelector(mAppContext) : mTrackSelector, | |
| 68 | + mLoadControl == null ? mLoadControl = new DefaultLoadControl() : mLoadControl, | |
| 69 | + DefaultBandwidthMeter.getSingletonInstance(mAppContext), | |
| 70 | + Util.getLooper(), | |
| 71 | + new AnalyticsCollector(Clock.DEFAULT), | |
| 72 | + /* useLazyPreparation= */ true, | |
| 73 | + Clock.DEFAULT) | |
| 74 | + .build(); | |
| 75 | + setOptions(); | |
| 76 | + | |
| 77 | + //播放器日志 | |
| 78 | + if (VideoViewManager.getConfig().mIsEnableLog && mTrackSelector instanceof MappingTrackSelector) { | |
| 79 | + exoPlayer.addAnalyticsListener(new EventLogger((MappingTrackSelector) mTrackSelector, "ExoPlayer")); | |
| 80 | + } | |
| 81 | + | |
| 82 | + exoPlayer.addListener(this); | |
| 83 | + exoPlayer.addVideoListener(this); | |
| 84 | + } | |
| 85 | + | |
| 86 | + public void setTrackSelector(TrackSelector trackSelector) { | |
| 87 | + mTrackSelector = trackSelector; | |
| 88 | + } | |
| 89 | + | |
| 90 | + public void setRenderersFactory(RenderersFactory renderersFactory) { | |
| 91 | + mRenderersFactory = renderersFactory; | |
| 92 | + } | |
| 93 | + | |
| 94 | + public void setLoadControl(LoadControl loadControl) { | |
| 95 | + mLoadControl = loadControl; | |
| 96 | + } | |
| 97 | + | |
| 98 | + /** | |
| 99 | + * 设置播放资源 | |
| 100 | + * | |
| 101 | + * @param path 播放地址 | |
| 102 | + * @param headers 播放地址请求头 | |
| 103 | + */ | |
| 104 | + @Override | |
| 105 | + public void setDataSource(String path, Map<String, String> headers) { | |
| 106 | + mMediaSource = mMediaSourceHelper.getMediaSource(path, headers); | |
| 107 | + } | |
| 108 | + | |
| 109 | + @Override | |
| 110 | + public void setDataSource(AssetFileDescriptor fd) { | |
| 111 | + //no support | |
| 112 | + } | |
| 113 | + | |
| 114 | + @Override | |
| 115 | + public void start() { | |
| 116 | + if (exoPlayer == null) | |
| 117 | + return; | |
| 118 | + exoPlayer.setPlayWhenReady(true); | |
| 119 | + } | |
| 120 | + | |
| 121 | + @Override | |
| 122 | + public void pause() { | |
| 123 | + if (exoPlayer == null) | |
| 124 | + return; | |
| 125 | + exoPlayer.setPlayWhenReady(false); | |
| 126 | + } | |
| 127 | + | |
| 128 | + @Override | |
| 129 | + public void stop() { | |
| 130 | + if (exoPlayer == null) | |
| 131 | + return; | |
| 132 | + exoPlayer.stop(); | |
| 133 | + } | |
| 134 | + | |
| 135 | + /** | |
| 136 | + * 预加载资源 | |
| 137 | + */ | |
| 138 | + @Override | |
| 139 | + public void prepareAsync() { | |
| 140 | + if (exoPlayer == null) | |
| 141 | + return; | |
| 142 | + if (mMediaSource == null) return; | |
| 143 | + if (mSpeedPlaybackParameters != null) { | |
| 144 | + exoPlayer.setPlaybackParameters(mSpeedPlaybackParameters); | |
| 145 | + } | |
| 146 | + mIsPreparing = true; | |
| 147 | + mMediaSource.addEventListener(new Handler(), mMediaSourceEventListener); | |
| 148 | + exoPlayer.prepare(mMediaSource); | |
| 149 | + } | |
| 150 | + | |
| 151 | + private MediaSourceEventListener mMediaSourceEventListener = new DefaultMediaSourceEventListener() { | |
| 152 | + @Override | |
| 153 | + public void onReadingStarted(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) { | |
| 154 | + super.onReadingStarted(windowIndex, mediaPeriodId); | |
| 155 | + if (mPlayerEventListener != null && mIsPreparing) { | |
| 156 | + mPlayerEventListener.onPrepared(); | |
| 157 | + } | |
| 158 | + } | |
| 159 | + }; | |
| 160 | + | |
| 161 | + @Override | |
| 162 | + public void reset() { | |
| 163 | + if (exoPlayer != null) { | |
| 164 | + exoPlayer.stop(true); | |
| 165 | + exoPlayer.setVideoSurface(null); | |
| 166 | + mIsPreparing = false; | |
| 167 | + mIsBuffering = false; | |
| 168 | + mLastReportedPlaybackState = Player.STATE_IDLE; | |
| 169 | + mLastReportedPlayWhenReady = false; | |
| 170 | + } | |
| 171 | + } | |
| 172 | + | |
| 173 | + @Override | |
| 174 | + public boolean isPlaying() { | |
| 175 | + if (exoPlayer == null) | |
| 176 | + return false; | |
| 177 | + //获取当前播放状态 | |
| 178 | + int state = exoPlayer.getPlaybackState(); | |
| 179 | + switch (state) { | |
| 180 | + case Player.STATE_BUFFERING: | |
| 181 | + case Player.STATE_READY: | |
| 182 | + return exoPlayer.getPlayWhenReady(); | |
| 183 | + case Player.STATE_IDLE: | |
| 184 | + case Player.STATE_ENDED: | |
| 185 | + default: | |
| 186 | + return false; | |
| 187 | + } | |
| 188 | + } | |
| 189 | + | |
| 190 | + @Override | |
| 191 | + public void seekTo(long time) { | |
| 192 | + if (exoPlayer == null) | |
| 193 | + return; | |
| 194 | + exoPlayer.seekTo(time); | |
| 195 | + } | |
| 196 | + | |
| 197 | + @Override | |
| 198 | + public void release() { | |
| 199 | + if (exoPlayer != null) { | |
| 200 | + exoPlayer.removeListener(this); | |
| 201 | + exoPlayer.removeVideoListener(this); | |
| 202 | + final SimpleExoPlayer player = exoPlayer; | |
| 203 | + exoPlayer = null; | |
| 204 | + new Thread() { | |
| 205 | + @Override | |
| 206 | + public void run() { | |
| 207 | + //异步释放,防止卡顿 | |
| 208 | + player.release(); | |
| 209 | + } | |
| 210 | + }.start(); | |
| 211 | + } | |
| 212 | + | |
| 213 | + mIsPreparing = false; | |
| 214 | + mIsBuffering = false; | |
| 215 | + mLastReportedPlaybackState = Player.STATE_IDLE; | |
| 216 | + mLastReportedPlayWhenReady = false; | |
| 217 | + mSpeedPlaybackParameters = null; | |
| 218 | + } | |
| 219 | + | |
| 220 | + @Override | |
| 221 | + public long getCurrentPosition() { | |
| 222 | + if (exoPlayer == null) | |
| 223 | + return 0; | |
| 224 | + return exoPlayer.getCurrentPosition(); | |
| 225 | + } | |
| 226 | + | |
| 227 | + /** | |
| 228 | + * 获取播放时长 | |
| 229 | + * | |
| 230 | + * @return | |
| 231 | + */ | |
| 232 | + @Override | |
| 233 | + public long getDuration() { | |
| 234 | + if (exoPlayer == null) | |
| 235 | + return 0; | |
| 236 | + return exoPlayer.getDuration(); | |
| 237 | + } | |
| 238 | + | |
| 239 | + /** | |
| 240 | + * 获取当前缓冲百分比 | |
| 241 | + * | |
| 242 | + * @return | |
| 243 | + */ | |
| 244 | + @Override | |
| 245 | + public int getBufferedPercentage() { | |
| 246 | + return exoPlayer == null ? 0 : exoPlayer.getBufferedPercentage(); | |
| 247 | + } | |
| 248 | + | |
| 249 | + /** | |
| 250 | + * 设置渲染视频的View,主要用于TextureView | |
| 251 | + * | |
| 252 | + * @param surface | |
| 253 | + */ | |
| 254 | + @Override | |
| 255 | + public void setSurface(Surface surface) { | |
| 256 | + if (exoPlayer != null) { | |
| 257 | + exoPlayer.setVideoSurface(surface); | |
| 258 | + } | |
| 259 | + } | |
| 260 | + | |
| 261 | + /** | |
| 262 | + * 设置渲染视频的View,主要用于SurfaceView | |
| 263 | + * | |
| 264 | + * @param holder | |
| 265 | + */ | |
| 266 | + @Override | |
| 267 | + public void setDisplay(SurfaceHolder holder) { | |
| 268 | + if (holder == null) | |
| 269 | + setSurface(null); | |
| 270 | + else | |
| 271 | + setSurface(holder.getSurface()); | |
| 272 | + } | |
| 273 | + | |
| 274 | + /** | |
| 275 | + * 设置左右音量大小 | |
| 276 | + * | |
| 277 | + * @param leftVolume | |
| 278 | + * @param rightVolume | |
| 279 | + */ | |
| 280 | + @Override | |
| 281 | + public void setVolume(float leftVolume, float rightVolume) { | |
| 282 | + if (exoPlayer != null) | |
| 283 | + exoPlayer.setVolume((leftVolume + rightVolume) / 2); | |
| 284 | + } | |
| 285 | + | |
| 286 | + /** | |
| 287 | + * 设置是否循环播放 | |
| 288 | + * | |
| 289 | + * @param isLooping | |
| 290 | + */ | |
| 291 | + @Override | |
| 292 | + public void setLooping(boolean isLooping) { | |
| 293 | + if (exoPlayer != null) | |
| 294 | + exoPlayer.setRepeatMode(isLooping ? Player.REPEAT_MODE_ALL : Player.REPEAT_MODE_OFF); | |
| 295 | + } | |
| 296 | + | |
| 297 | + @Override | |
| 298 | + public void setOptions() { | |
| 299 | + //准备好就开始播放 | |
| 300 | + exoPlayer.setPlayWhenReady(true); | |
| 301 | + } | |
| 302 | + | |
| 303 | + /** | |
| 304 | + * 设置播放速度 | |
| 305 | + * | |
| 306 | + * @param speed | |
| 307 | + */ | |
| 308 | + @Override | |
| 309 | + public void setSpeed(float speed) { | |
| 310 | + PlaybackParameters playbackParameters = new PlaybackParameters(speed); | |
| 311 | + mSpeedPlaybackParameters = playbackParameters; | |
| 312 | + if (exoPlayer != null) { | |
| 313 | + exoPlayer.setPlaybackParameters(playbackParameters); | |
| 314 | + } | |
| 315 | + } | |
| 316 | + | |
| 317 | + /** | |
| 318 | + * 不支持 | |
| 319 | + * | |
| 320 | + * @return | |
| 321 | + */ | |
| 322 | + @Deprecated | |
| 323 | + @Override | |
| 324 | + public long getTcpSpeed() { | |
| 325 | + // no support | |
| 326 | + return 0; | |
| 327 | + } | |
| 328 | + | |
| 329 | + /** | |
| 330 | + * 播放状态改变 | |
| 331 | + * | |
| 332 | + * @param playWhenReady | |
| 333 | + * @param playbackState | |
| 334 | + */ | |
| 335 | + @Override | |
| 336 | + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { | |
| 337 | + if (mPlayerEventListener == null) return; | |
| 338 | + if (mIsPreparing) return; | |
| 339 | + if (mLastReportedPlayWhenReady != playWhenReady || mLastReportedPlaybackState != playbackState) { | |
| 340 | + switch (playbackState) { | |
| 341 | + case Player.STATE_BUFFERING: | |
| 342 | + mPlayerEventListener.onInfo(MEDIA_INFO_BUFFERING_START, getBufferedPercentage()); | |
| 343 | + mIsBuffering = true; | |
| 344 | + break; | |
| 345 | + case Player.STATE_READY: | |
| 346 | + if (mIsBuffering) { | |
| 347 | + mPlayerEventListener.onInfo(MEDIA_INFO_BUFFERING_END, getBufferedPercentage()); | |
| 348 | + mIsBuffering = false; | |
| 349 | + } | |
| 350 | + break; | |
| 351 | + case Player.STATE_ENDED: | |
| 352 | + mPlayerEventListener.onCompletion(); | |
| 353 | + break; | |
| 354 | + } | |
| 355 | + mLastReportedPlaybackState = playbackState; | |
| 356 | + mLastReportedPlayWhenReady = playWhenReady; | |
| 357 | + } | |
| 358 | + } | |
| 359 | + | |
| 360 | + @Override | |
| 361 | + public void onPlayerError(ExoPlaybackException error) { | |
| 362 | + if (mPlayerEventListener != null) { | |
| 363 | + mPlayerEventListener.onError(); | |
| 364 | + } | |
| 365 | + } | |
| 366 | + | |
| 367 | + @Override | |
| 368 | + public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { | |
| 369 | + if (mPlayerEventListener != null) { | |
| 370 | + mPlayerEventListener.onVideoSizeChanged(width, height); | |
| 371 | + if (unappliedRotationDegrees > 0) { | |
| 372 | + mPlayerEventListener.onInfo(MEDIA_INFO_VIDEO_ROTATION_CHANGED, unappliedRotationDegrees); | |
| 373 | + } | |
| 374 | + } | |
| 375 | + } | |
| 376 | + | |
| 377 | + /** | |
| 378 | + * 渲染第一帧 | |
| 379 | + */ | |
| 380 | + @Override | |
| 381 | + public void onRenderedFirstFrame() { | |
| 382 | + if (mPlayerEventListener != null && mIsPreparing) { | |
| 383 | + mPlayerEventListener.onInfo(MEDIA_INFO_VIDEO_RENDERING_START, 0); | |
| 384 | + mIsPreparing = false; | |
| 385 | + } | |
| 386 | + } | |
| 387 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/playerCore/exoplayer/ExoMediaPlayerFactory.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.playerCore.exoplayer; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | + | |
| 5 | +import com.libs.video.video.exoplayer.play.PlayerFactory; | |
| 6 | + | |
| 7 | +public class ExoMediaPlayerFactory extends PlayerFactory<ExoMediaPlayer> { | |
| 8 | + | |
| 9 | + public static ExoMediaPlayerFactory create() { | |
| 10 | + return new ExoMediaPlayerFactory(); | |
| 11 | + } | |
| 12 | + | |
| 13 | + @Override | |
| 14 | + public ExoMediaPlayer createPlayer(Context context) { | |
| 15 | + return new ExoMediaPlayer(context); | |
| 16 | + } | |
| 17 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/playerCore/exoplayer/ExoMediaSourceHelper.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.playerCore.exoplayer; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.net.Uri; | |
| 5 | +import android.text.TextUtils; | |
| 6 | + | |
| 7 | +import com.google.android.exoplayer2.C; | |
| 8 | +import com.google.android.exoplayer2.database.ExoDatabaseProvider; | |
| 9 | +import com.google.android.exoplayer2.ext.rtmp.RtmpDataSourceFactory; | |
| 10 | +import com.google.android.exoplayer2.source.MediaSource; | |
| 11 | +import com.google.android.exoplayer2.source.ProgressiveMediaSource; | |
| 12 | +import com.google.android.exoplayer2.source.dash.DashMediaSource; | |
| 13 | +import com.google.android.exoplayer2.source.hls.HlsMediaSource; | |
| 14 | +import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; | |
| 15 | +import com.google.android.exoplayer2.upstream.DataSource; | |
| 16 | +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; | |
| 17 | +import com.google.android.exoplayer2.upstream.DefaultHttpDataSource; | |
| 18 | +import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; | |
| 19 | +import com.google.android.exoplayer2.upstream.HttpDataSource; | |
| 20 | +import com.google.android.exoplayer2.upstream.cache.Cache; | |
| 21 | +import com.google.android.exoplayer2.upstream.cache.CacheDataSource; | |
| 22 | +import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory; | |
| 23 | +import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor; | |
| 24 | +import com.google.android.exoplayer2.upstream.cache.SimpleCache; | |
| 25 | +import com.google.android.exoplayer2.util.Util; | |
| 26 | + | |
| 27 | +import java.io.File; | |
| 28 | +import java.lang.reflect.Field; | |
| 29 | +import java.util.Map; | |
| 30 | + | |
| 31 | +public final class ExoMediaSourceHelper { | |
| 32 | + | |
| 33 | + private static ExoMediaSourceHelper sInstance; | |
| 34 | + | |
| 35 | + private final String mUserAgent; | |
| 36 | + private Context mAppContext; | |
| 37 | + private HttpDataSource.Factory mHttpDataSourceFactory; | |
| 38 | + private Cache mCache; | |
| 39 | + | |
| 40 | + private ExoMediaSourceHelper(Context context) { | |
| 41 | + mAppContext = context.getApplicationContext(); | |
| 42 | + mUserAgent = Util.getUserAgent(mAppContext, mAppContext.getApplicationInfo().name); | |
| 43 | + } | |
| 44 | + | |
| 45 | + public static ExoMediaSourceHelper getInstance(Context context) { | |
| 46 | + if (sInstance == null) { | |
| 47 | + synchronized (ExoMediaSourceHelper.class) { | |
| 48 | + if (sInstance == null) { | |
| 49 | + sInstance = new ExoMediaSourceHelper(context); | |
| 50 | + } | |
| 51 | + } | |
| 52 | + } | |
| 53 | + return sInstance; | |
| 54 | + } | |
| 55 | + | |
| 56 | + public MediaSource getMediaSource(String uri) { | |
| 57 | + return getMediaSource(uri, null, false); | |
| 58 | + } | |
| 59 | + | |
| 60 | + public MediaSource getMediaSource(String uri, Map<String, String> headers) { | |
| 61 | + return getMediaSource(uri, headers, false); | |
| 62 | + } | |
| 63 | + | |
| 64 | + public MediaSource getMediaSource(String uri, boolean isCache) { | |
| 65 | + return getMediaSource(uri, null, isCache); | |
| 66 | + } | |
| 67 | + | |
| 68 | + public MediaSource getMediaSource(String uri, Map<String, String> headers, boolean isCache) { | |
| 69 | + Uri contentUri = Uri.parse(uri); | |
| 70 | + if ("rtmp".equals(contentUri.getScheme())) { | |
| 71 | + return new ProgressiveMediaSource.Factory(new RtmpDataSourceFactory(null)) | |
| 72 | + .createMediaSource(contentUri); | |
| 73 | + } | |
| 74 | + int contentType = inferContentType(uri); | |
| 75 | + DataSource.Factory factory; | |
| 76 | + if (isCache) { | |
| 77 | + factory = getCacheDataSourceFactory(); | |
| 78 | + } else { | |
| 79 | + factory = getDataSourceFactory(); | |
| 80 | + } | |
| 81 | + if (mHttpDataSourceFactory != null) { | |
| 82 | + setHeaders(headers); | |
| 83 | + } | |
| 84 | + switch (contentType) { | |
| 85 | + case C.TYPE_DASH: | |
| 86 | + return new DashMediaSource.Factory(factory).createMediaSource(contentUri); | |
| 87 | + case C.TYPE_SS: | |
| 88 | + return new SsMediaSource.Factory(factory).createMediaSource(contentUri); | |
| 89 | + case C.TYPE_HLS: | |
| 90 | + return new HlsMediaSource.Factory(factory).createMediaSource(contentUri); | |
| 91 | + default: | |
| 92 | + case C.TYPE_OTHER: | |
| 93 | + return new ProgressiveMediaSource.Factory(factory).createMediaSource(contentUri); | |
| 94 | + } | |
| 95 | + } | |
| 96 | + | |
| 97 | + private int inferContentType(String fileName) { | |
| 98 | + fileName = Util.toLowerInvariant(fileName); | |
| 99 | + if (fileName.contains(".mpd")) { | |
| 100 | + return C.TYPE_DASH; | |
| 101 | + } else if (fileName.contains(".m3u8")) { | |
| 102 | + return C.TYPE_HLS; | |
| 103 | + } else if (fileName.matches(".*\\.ism(l)?(/manifest(\\(.+\\))?)?")) { | |
| 104 | + return C.TYPE_SS; | |
| 105 | + } else { | |
| 106 | + return C.TYPE_OTHER; | |
| 107 | + } | |
| 108 | + } | |
| 109 | + | |
| 110 | + private DataSource.Factory getCacheDataSourceFactory() { | |
| 111 | + if (mCache == null) { | |
| 112 | + mCache = newCache(); | |
| 113 | + } | |
| 114 | + return new CacheDataSourceFactory( | |
| 115 | + mCache, | |
| 116 | + getDataSourceFactory(), | |
| 117 | + CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR); | |
| 118 | + } | |
| 119 | + | |
| 120 | + private Cache newCache() { | |
| 121 | + return new SimpleCache( | |
| 122 | + new File(mAppContext.getExternalCacheDir(), "exo-video-cache"),//缓存目录 | |
| 123 | + new LeastRecentlyUsedCacheEvictor(512 * 1024 * 1024),//缓存大小,默认512M,使用LRU算法实现 | |
| 124 | + new ExoDatabaseProvider(mAppContext)); | |
| 125 | + } | |
| 126 | + | |
| 127 | + /** | |
| 128 | + * Returns a new DataSource factory. | |
| 129 | + * | |
| 130 | + * @return A new DataSource factory. | |
| 131 | + */ | |
| 132 | + private DataSource.Factory getDataSourceFactory() { | |
| 133 | + return new DefaultDataSourceFactory(mAppContext, getHttpDataSourceFactory()); | |
| 134 | + } | |
| 135 | + | |
| 136 | + /** | |
| 137 | + * Returns a new HttpDataSource factory. | |
| 138 | + * | |
| 139 | + * @return A new HttpDataSource factory. | |
| 140 | + */ | |
| 141 | + private DataSource.Factory getHttpDataSourceFactory() { | |
| 142 | + if (mHttpDataSourceFactory == null) { | |
| 143 | + mHttpDataSourceFactory = new DefaultHttpDataSourceFactory( | |
| 144 | + mUserAgent, | |
| 145 | + null, | |
| 146 | + DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS, | |
| 147 | + DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS, | |
| 148 | + //http->https重定向支持 | |
| 149 | + true); | |
| 150 | + } | |
| 151 | + return mHttpDataSourceFactory; | |
| 152 | + } | |
| 153 | + | |
| 154 | + private void setHeaders(Map<String, String> headers) { | |
| 155 | + if (headers != null && headers.size() > 0) { | |
| 156 | + for (Map.Entry<String, String> header : headers.entrySet()) { | |
| 157 | + String key = header.getKey(); | |
| 158 | + String value = header.getValue(); | |
| 159 | + //如果发现用户通过header传递了UA,则强行将HttpDataSourceFactory里面的userAgent字段替换成用户的 | |
| 160 | + if (TextUtils.equals(key, "User-Agent")) { | |
| 161 | + if (!TextUtils.isEmpty(value)) { | |
| 162 | + try { | |
| 163 | + Field userAgentField = mHttpDataSourceFactory.getClass().getDeclaredField("userAgent"); | |
| 164 | + userAgentField.setAccessible(true); | |
| 165 | + userAgentField.set(mHttpDataSourceFactory, value); | |
| 166 | + } catch (Exception e) { | |
| 167 | + //ignore | |
| 168 | + } | |
| 169 | + } | |
| 170 | + } else { | |
| 171 | + mHttpDataSourceFactory.getDefaultRequestProperties().set(key, value); | |
| 172 | + } | |
| 173 | + } | |
| 174 | + } | |
| 175 | + } | |
| 176 | + | |
| 177 | + public void setCache(Cache cache) { | |
| 178 | + this.mCache = cache; | |
| 179 | + } | |
| 180 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/render/IRenderView.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.render; | |
| 2 | + | |
| 3 | +import android.graphics.Bitmap; | |
| 4 | +import android.view.View; | |
| 5 | + | |
| 6 | + | |
| 7 | +import com.libs.video.video.exoplayer.play.AbstractPlayer; | |
| 8 | + | |
| 9 | +import androidx.annotation.NonNull; | |
| 10 | + | |
| 11 | +public interface IRenderView { | |
| 12 | + | |
| 13 | + /** | |
| 14 | + * 关联AbstractPlayer | |
| 15 | + */ | |
| 16 | + void attachToPlayer(@NonNull AbstractPlayer player); | |
| 17 | + | |
| 18 | + /** | |
| 19 | + * 设置视频宽高 | |
| 20 | + * @param videoWidth 宽 | |
| 21 | + * @param videoHeight 高 | |
| 22 | + */ | |
| 23 | + void setVideoSize(int videoWidth, int videoHeight); | |
| 24 | + | |
| 25 | + /** | |
| 26 | + * 设置视频旋转角度 | |
| 27 | + * @param degree 角度值 | |
| 28 | + */ | |
| 29 | + void setVideoRotation(int degree); | |
| 30 | + | |
| 31 | + /** | |
| 32 | + * 设置screen scale type | |
| 33 | + * @param scaleType 类型 | |
| 34 | + */ | |
| 35 | + void setScaleType(int scaleType); | |
| 36 | + | |
| 37 | + /** | |
| 38 | + * 获取真实的RenderView | |
| 39 | + */ | |
| 40 | + View getView(); | |
| 41 | + | |
| 42 | + /** | |
| 43 | + * 截图 | |
| 44 | + */ | |
| 45 | + Bitmap doScreenShot(); | |
| 46 | + | |
| 47 | + /** | |
| 48 | + * 释放资源 | |
| 49 | + */ | |
| 50 | + void release(); | |
| 51 | + | |
| 52 | +} | |
| 0 | 53 | \ No newline at end of file | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/render/MeasureHelper.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.render; | |
| 2 | + | |
| 3 | +import android.view.View; | |
| 4 | + | |
| 5 | +import com.libs.video.video.exoplayer.play.CNVideoView; | |
| 6 | + | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 测量工具 | |
| 10 | + */ | |
| 11 | +public class MeasureHelper { | |
| 12 | + | |
| 13 | + private int mVideoWidth; | |
| 14 | + | |
| 15 | + private int mVideoHeight; | |
| 16 | + | |
| 17 | + private int mCurrentScreenScale; | |
| 18 | + | |
| 19 | + private int mVideoRotationDegree; | |
| 20 | + | |
| 21 | + public void setVideoRotation(int videoRotationDegree) { | |
| 22 | + mVideoRotationDegree = videoRotationDegree; | |
| 23 | + } | |
| 24 | + | |
| 25 | + | |
| 26 | + public void setVideoSize(int width, int height) { | |
| 27 | + mVideoWidth = width; | |
| 28 | + mVideoHeight = height; | |
| 29 | + } | |
| 30 | + | |
| 31 | + public void setScreenScale(int screenScale) { | |
| 32 | + mCurrentScreenScale = screenScale; | |
| 33 | + } | |
| 34 | + | |
| 35 | + public int[] doMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
| 36 | + if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) { // 软解码时处理旋转信息,交换宽高 | |
| 37 | + widthMeasureSpec = widthMeasureSpec + heightMeasureSpec; | |
| 38 | + heightMeasureSpec = widthMeasureSpec - heightMeasureSpec; | |
| 39 | + widthMeasureSpec = widthMeasureSpec - heightMeasureSpec; | |
| 40 | + } | |
| 41 | + | |
| 42 | + int width = View.getDefaultSize(mVideoWidth, widthMeasureSpec); | |
| 43 | + int height = View.getDefaultSize(mVideoHeight, heightMeasureSpec); | |
| 44 | + | |
| 45 | + //如果设置了比例 | |
| 46 | + switch (mCurrentScreenScale) { | |
| 47 | + case CNVideoView.SCREEN_SCALE_ORIGINAL: | |
| 48 | + width = mVideoWidth; | |
| 49 | + height = mVideoHeight; | |
| 50 | + break; | |
| 51 | + case CNVideoView.SCREEN_SCALE_16_9: | |
| 52 | + if (height > width / 16 * 9) { | |
| 53 | + height = width / 16 * 9; | |
| 54 | + } else { | |
| 55 | + width = height / 9 * 16; | |
| 56 | + } | |
| 57 | + break; | |
| 58 | + case CNVideoView.SCREEN_SCALE_4_3: | |
| 59 | + if (height > width / 4 * 3) { | |
| 60 | + height = width / 4 * 3; | |
| 61 | + } else { | |
| 62 | + width = height / 3 * 4; | |
| 63 | + } | |
| 64 | +// Log.d("@@@@", "onMeasure 4:3 : width" + width + " height:" + height); | |
| 65 | + break; | |
| 66 | + case CNVideoView.SCREEN_SCALE_MATCH_PARENT: | |
| 67 | + width = widthMeasureSpec; | |
| 68 | + height = heightMeasureSpec; | |
| 69 | + break; | |
| 70 | + case CNVideoView.SCREEN_SCALE_CENTER_CROP: | |
| 71 | + if (mVideoWidth > 0 && mVideoHeight > 0) { | |
| 72 | + if (mVideoWidth * height > width * mVideoHeight) { | |
| 73 | + width = height * mVideoWidth / mVideoHeight; | |
| 74 | + } else { | |
| 75 | + height = width * mVideoHeight / mVideoWidth; | |
| 76 | + } | |
| 77 | + } | |
| 78 | + break; | |
| 79 | + case CNVideoView.SCREEN_SCALE_DEFAULT: | |
| 80 | + default: | |
| 81 | + if (mVideoWidth > 0 && mVideoHeight > 0) { | |
| 82 | + | |
| 83 | + int widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec); | |
| 84 | + int widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec); | |
| 85 | + int heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec); | |
| 86 | + int heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec); | |
| 87 | + | |
| 88 | + if (widthSpecMode == View.MeasureSpec.EXACTLY && heightSpecMode == View.MeasureSpec.EXACTLY) { | |
| 89 | + // the size is fixed | |
| 90 | + width = widthSpecSize; | |
| 91 | + height = heightSpecSize; | |
| 92 | + | |
| 93 | + // for compatibility, we adjust size based on aspect ratio | |
| 94 | + if (mVideoWidth * height < width * mVideoHeight) { | |
| 95 | + //Log.i("@@@", "image too wide, correcting"); | |
| 96 | + width = height * mVideoWidth / mVideoHeight; | |
| 97 | + } else if (mVideoWidth * height > width * mVideoHeight) { | |
| 98 | + //Log.i("@@@", "image too tall, correcting"); | |
| 99 | + height = width * mVideoHeight / mVideoWidth; | |
| 100 | + } | |
| 101 | + } else if (widthSpecMode == View.MeasureSpec.EXACTLY) { | |
| 102 | + // only the width is fixed, adjust the height to match aspect ratio if possible | |
| 103 | + width = widthSpecSize; | |
| 104 | + height = width * mVideoHeight / mVideoWidth; | |
| 105 | + if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) { | |
| 106 | + // couldn't match aspect ratio within the constraints | |
| 107 | + height = heightSpecSize; | |
| 108 | + } | |
| 109 | + } else if (heightSpecMode == View.MeasureSpec.EXACTLY) { | |
| 110 | + // only the height is fixed, adjust the width to match aspect ratio if possible | |
| 111 | + height = heightSpecSize; | |
| 112 | + width = height * mVideoWidth / mVideoHeight; | |
| 113 | + if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) { | |
| 114 | + // couldn't match aspect ratio within the constraints | |
| 115 | + width = widthSpecSize; | |
| 116 | + } | |
| 117 | + } else { | |
| 118 | + // neither the width nor the height are fixed, try to use actual video size | |
| 119 | + width = mVideoWidth; | |
| 120 | + height = mVideoHeight; | |
| 121 | + if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) { | |
| 122 | + // too tall, decrease both width and height | |
| 123 | + height = heightSpecSize; | |
| 124 | + width = height * mVideoWidth / mVideoHeight; | |
| 125 | + } | |
| 126 | + if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) { | |
| 127 | + // too wide, decrease both width and height | |
| 128 | + width = widthSpecSize; | |
| 129 | + height = width * mVideoHeight / mVideoWidth; | |
| 130 | + } | |
| 131 | + } | |
| 132 | + } else { | |
| 133 | + // no size yet, just adopt the given spec sizes | |
| 134 | + } | |
| 135 | + break; | |
| 136 | + } | |
| 137 | + | |
| 138 | + return new int[]{width, height}; | |
| 139 | + } | |
| 140 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/render/RenderViewFactory.java
0 → 100644
cloud/video/src/main/java/com/libs/video/video/exoplayer/render/surface/SurfaceRenderView.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.render.surface; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.graphics.Bitmap; | |
| 5 | +import android.graphics.PixelFormat; | |
| 6 | +import android.util.AttributeSet; | |
| 7 | +import android.view.SurfaceHolder; | |
| 8 | +import android.view.SurfaceView; | |
| 9 | +import android.view.View; | |
| 10 | + | |
| 11 | + | |
| 12 | +import com.libs.video.video.exoplayer.play.AbstractPlayer; | |
| 13 | +import com.libs.video.video.exoplayer.render.IRenderView; | |
| 14 | +import com.libs.video.video.exoplayer.render.MeasureHelper; | |
| 15 | + | |
| 16 | +import androidx.annotation.NonNull; | |
| 17 | + | |
| 18 | +public class SurfaceRenderView extends SurfaceView implements IRenderView, SurfaceHolder.Callback { | |
| 19 | + private MeasureHelper mMeasureHelper; | |
| 20 | + | |
| 21 | + private AbstractPlayer mMediaPlayer; | |
| 22 | + | |
| 23 | + public SurfaceRenderView(Context context) { | |
| 24 | + super(context); | |
| 25 | + } | |
| 26 | + | |
| 27 | + public SurfaceRenderView(Context context, AttributeSet attrs) { | |
| 28 | + super(context, attrs); | |
| 29 | + } | |
| 30 | + | |
| 31 | + public SurfaceRenderView(Context context, AttributeSet attrs, int defStyleAttr) { | |
| 32 | + super(context, attrs, defStyleAttr); | |
| 33 | + } | |
| 34 | + | |
| 35 | + { | |
| 36 | + mMeasureHelper = new MeasureHelper(); | |
| 37 | + SurfaceHolder surfaceHolder = getHolder(); | |
| 38 | + surfaceHolder.addCallback(this); | |
| 39 | + surfaceHolder.setFormat(PixelFormat.RGBA_8888); | |
| 40 | + } | |
| 41 | + | |
| 42 | + @Override | |
| 43 | + public void attachToPlayer(@NonNull AbstractPlayer player) { | |
| 44 | + this.mMediaPlayer = player; | |
| 45 | + } | |
| 46 | + | |
| 47 | + @Override | |
| 48 | + public void setVideoSize(int videoWidth, int videoHeight) { | |
| 49 | + if (videoWidth > 0 && videoHeight > 0) { | |
| 50 | + mMeasureHelper.setVideoSize(videoWidth, videoHeight); | |
| 51 | + requestLayout(); | |
| 52 | + } | |
| 53 | + } | |
| 54 | + | |
| 55 | + @Override | |
| 56 | + public void setVideoRotation(int degree) { | |
| 57 | + mMeasureHelper.setVideoRotation(degree); | |
| 58 | + setRotation(degree); | |
| 59 | + } | |
| 60 | + | |
| 61 | + @Override | |
| 62 | + public void setScaleType(int scaleType) { | |
| 63 | + mMeasureHelper.setScreenScale(scaleType); | |
| 64 | + requestLayout(); | |
| 65 | + } | |
| 66 | + | |
| 67 | + @Override | |
| 68 | + public View getView() { | |
| 69 | + return this; | |
| 70 | + } | |
| 71 | + | |
| 72 | + @Override | |
| 73 | + public Bitmap doScreenShot() { | |
| 74 | + return null; | |
| 75 | + } | |
| 76 | + | |
| 77 | + @Override | |
| 78 | + public void release() { | |
| 79 | + | |
| 80 | + } | |
| 81 | + | |
| 82 | + @Override | |
| 83 | + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
| 84 | + int[] measuredSize = mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec); | |
| 85 | + setMeasuredDimension(measuredSize[0], measuredSize[1]); | |
| 86 | + } | |
| 87 | + | |
| 88 | + @Override | |
| 89 | + public void surfaceCreated(SurfaceHolder holder) { | |
| 90 | + | |
| 91 | + } | |
| 92 | + | |
| 93 | + @Override | |
| 94 | + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { | |
| 95 | + if (mMediaPlayer != null) { | |
| 96 | + mMediaPlayer.setDisplay(holder); | |
| 97 | + } | |
| 98 | + } | |
| 99 | + | |
| 100 | + @Override | |
| 101 | + public void surfaceDestroyed(SurfaceHolder holder) { | |
| 102 | + | |
| 103 | + } | |
| 104 | +} | |
| 0 | 105 | \ No newline at end of file | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/render/surface/SurfaceRenderViewFactory.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.render.surface; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | + | |
| 5 | +import com.libs.video.video.exoplayer.render.IRenderView; | |
| 6 | +import com.libs.video.video.exoplayer.render.RenderViewFactory; | |
| 7 | + | |
| 8 | + | |
| 9 | +public class SurfaceRenderViewFactory extends RenderViewFactory { | |
| 10 | + | |
| 11 | + public static SurfaceRenderViewFactory create() { | |
| 12 | + return new SurfaceRenderViewFactory(); | |
| 13 | + } | |
| 14 | + | |
| 15 | + @Override | |
| 16 | + public IRenderView createRenderView(Context context) { | |
| 17 | + return new SurfaceRenderView(context); | |
| 18 | + } | |
| 19 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/render/texture/TextureRenderView.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.render.texture; | |
| 2 | + | |
| 3 | +import android.annotation.SuppressLint; | |
| 4 | +import android.content.Context; | |
| 5 | +import android.graphics.Bitmap; | |
| 6 | +import android.graphics.SurfaceTexture; | |
| 7 | +import android.view.Surface; | |
| 8 | +import android.view.TextureView; | |
| 9 | +import android.view.View; | |
| 10 | + | |
| 11 | + | |
| 12 | +import com.libs.video.video.exoplayer.play.AbstractPlayer; | |
| 13 | +import com.libs.video.video.exoplayer.render.IRenderView; | |
| 14 | +import com.libs.video.video.exoplayer.render.MeasureHelper; | |
| 15 | + | |
| 16 | +import androidx.annotation.NonNull; | |
| 17 | +import androidx.annotation.Nullable; | |
| 18 | + | |
| 19 | +@SuppressLint("ViewConstructor") | |
| 20 | +public class TextureRenderView extends TextureView implements IRenderView, TextureView.SurfaceTextureListener { | |
| 21 | + private MeasureHelper mMeasureHelper; | |
| 22 | + private SurfaceTexture mSurfaceTexture; | |
| 23 | + | |
| 24 | + @Nullable | |
| 25 | + private AbstractPlayer mMediaPlayer; | |
| 26 | + private Surface mSurface; | |
| 27 | + | |
| 28 | + public TextureRenderView(Context context) { | |
| 29 | + super(context); | |
| 30 | + } | |
| 31 | + | |
| 32 | + { | |
| 33 | + mMeasureHelper = new MeasureHelper(); | |
| 34 | + setSurfaceTextureListener(this); | |
| 35 | + } | |
| 36 | + | |
| 37 | + @Override | |
| 38 | + public void attachToPlayer(@NonNull AbstractPlayer player) { | |
| 39 | + this.mMediaPlayer = player; | |
| 40 | + } | |
| 41 | + | |
| 42 | + @Override | |
| 43 | + public void setVideoSize(int videoWidth, int videoHeight) { | |
| 44 | + if (videoWidth > 0 && videoHeight > 0) { | |
| 45 | + mMeasureHelper.setVideoSize(videoWidth, videoHeight); | |
| 46 | + requestLayout(); | |
| 47 | + } | |
| 48 | + } | |
| 49 | + | |
| 50 | + @Override | |
| 51 | + public void setVideoRotation(int degree) { | |
| 52 | + mMeasureHelper.setVideoRotation(degree); | |
| 53 | + setRotation(degree); | |
| 54 | + } | |
| 55 | + | |
| 56 | + @Override | |
| 57 | + public void setScaleType(int scaleType) { | |
| 58 | + mMeasureHelper.setScreenScale(scaleType); | |
| 59 | + requestLayout(); | |
| 60 | + } | |
| 61 | + | |
| 62 | + @Override | |
| 63 | + public View getView() { | |
| 64 | + return this; | |
| 65 | + } | |
| 66 | + | |
| 67 | + @Override | |
| 68 | + public Bitmap doScreenShot() { | |
| 69 | + return getBitmap(); | |
| 70 | + } | |
| 71 | + | |
| 72 | + @Override | |
| 73 | + public void release() { | |
| 74 | + if (mSurface != null) | |
| 75 | + mSurface.release(); | |
| 76 | + | |
| 77 | + if (mSurfaceTexture != null) | |
| 78 | + mSurfaceTexture.release(); | |
| 79 | + } | |
| 80 | + | |
| 81 | + @Override | |
| 82 | + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
| 83 | + int[] measuredSize = mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec); | |
| 84 | + setMeasuredDimension(measuredSize[0], measuredSize[1]); | |
| 85 | + } | |
| 86 | + | |
| 87 | + @Override | |
| 88 | + public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) { | |
| 89 | + if (mSurfaceTexture != null) { | |
| 90 | + setSurfaceTexture(mSurfaceTexture); | |
| 91 | + } else { | |
| 92 | + mSurfaceTexture = surfaceTexture; | |
| 93 | + mSurface = new Surface(surfaceTexture); | |
| 94 | + if (mMediaPlayer != null) { | |
| 95 | + mMediaPlayer.setSurface(mSurface); | |
| 96 | + } | |
| 97 | + } | |
| 98 | + } | |
| 99 | + | |
| 100 | + @Override | |
| 101 | + public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { | |
| 102 | + | |
| 103 | + } | |
| 104 | + | |
| 105 | + @Override | |
| 106 | + public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { | |
| 107 | + return false; | |
| 108 | + } | |
| 109 | + | |
| 110 | + @Override | |
| 111 | + public void onSurfaceTextureUpdated(SurfaceTexture surface) { | |
| 112 | + | |
| 113 | + } | |
| 114 | +} | |
| 0 | 115 | \ No newline at end of file | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/render/texture/TextureRenderViewFactory.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.render.texture; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | + | |
| 5 | +import com.libs.video.video.exoplayer.render.IRenderView; | |
| 6 | +import com.libs.video.video.exoplayer.render.RenderViewFactory; | |
| 7 | + | |
| 8 | +public class TextureRenderViewFactory extends RenderViewFactory { | |
| 9 | + | |
| 10 | + public static TextureRenderViewFactory create() { | |
| 11 | + return new TextureRenderViewFactory(); | |
| 12 | + } | |
| 13 | + | |
| 14 | + @Override | |
| 15 | + public IRenderView createRenderView(Context context) { | |
| 16 | + return new TextureRenderView(context); | |
| 17 | + } | |
| 18 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/util/CutoutUtil.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.util; | |
| 2 | + | |
| 3 | +import android.annotation.SuppressLint; | |
| 4 | +import android.app.Activity; | |
| 5 | +import android.content.Context; | |
| 6 | +import android.graphics.Rect; | |
| 7 | +import android.os.Build; | |
| 8 | +import android.view.DisplayCutout; | |
| 9 | +import android.view.Window; | |
| 10 | +import android.view.WindowInsets; | |
| 11 | +import android.view.WindowManager; | |
| 12 | + | |
| 13 | +import java.lang.reflect.Method; | |
| 14 | +import java.util.List; | |
| 15 | + | |
| 16 | +/** | |
| 17 | + * 刘海屏工具 | |
| 18 | + */ | |
| 19 | +public final class CutoutUtil { | |
| 20 | + | |
| 21 | + private CutoutUtil() { | |
| 22 | + } | |
| 23 | + | |
| 24 | + /** | |
| 25 | + * 是否为允许全屏界面显示内容到刘海区域的刘海屏机型(与AndroidManifest中配置对应) | |
| 26 | + */ | |
| 27 | + public static boolean allowDisplayToCutout(Activity activity) { | |
| 28 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { | |
| 29 | + // 9.0系统全屏界面默认会保留黑边,不允许显示内容到刘海区域 | |
| 30 | + Window window = activity.getWindow(); | |
| 31 | + WindowInsets windowInsets = window.getDecorView().getRootWindowInsets(); | |
| 32 | + if (windowInsets == null) { | |
| 33 | + return false; | |
| 34 | + } | |
| 35 | + DisplayCutout displayCutout = windowInsets.getDisplayCutout(); | |
| 36 | + if (displayCutout == null) { | |
| 37 | + return false; | |
| 38 | + } | |
| 39 | + List<Rect> boundingRects = displayCutout.getBoundingRects(); | |
| 40 | + return boundingRects.size() > 0; | |
| 41 | + } else { | |
| 42 | + return hasCutoutHuawei(activity) | |
| 43 | + || hasCutoutOPPO(activity) | |
| 44 | + || hasCutoutVIVO(activity) | |
| 45 | + || hasCutoutXIAOMI(activity); | |
| 46 | + } | |
| 47 | + } | |
| 48 | + | |
| 49 | + /** | |
| 50 | + * 是否是华为刘海屏机型 | |
| 51 | + */ | |
| 52 | + @SuppressWarnings("unchecked") | |
| 53 | + private static boolean hasCutoutHuawei(Activity activity) { | |
| 54 | + if (!Build.MANUFACTURER.equalsIgnoreCase("HUAWEI")) { | |
| 55 | + return false; | |
| 56 | + } | |
| 57 | + try { | |
| 58 | + ClassLoader cl = activity.getClassLoader(); | |
| 59 | + Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil"); | |
| 60 | + if (HwNotchSizeUtil != null) { | |
| 61 | + Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen"); | |
| 62 | + return (boolean) get.invoke(HwNotchSizeUtil); | |
| 63 | + } | |
| 64 | + return false; | |
| 65 | + } catch (Exception e) { | |
| 66 | + return false; | |
| 67 | + } | |
| 68 | + } | |
| 69 | + | |
| 70 | + /** | |
| 71 | + * 是否是oppo刘海屏机型 | |
| 72 | + */ | |
| 73 | + private static boolean hasCutoutOPPO(Activity activity) { | |
| 74 | + if (!Build.MANUFACTURER.equalsIgnoreCase("oppo")) { | |
| 75 | + return false; | |
| 76 | + } | |
| 77 | + return activity.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism"); | |
| 78 | + } | |
| 79 | + | |
| 80 | + /** | |
| 81 | + * 是否是vivo刘海屏机型 | |
| 82 | + */ | |
| 83 | + @SuppressWarnings("unchecked") | |
| 84 | + @SuppressLint("PrivateApi") | |
| 85 | + private static boolean hasCutoutVIVO(Activity activity) { | |
| 86 | + if (!Build.MANUFACTURER.equalsIgnoreCase("vivo")) { | |
| 87 | + return false; | |
| 88 | + } | |
| 89 | + try { | |
| 90 | + ClassLoader cl = activity.getClassLoader(); | |
| 91 | + Class ftFeatureUtil = cl.loadClass("android.util.FtFeature"); | |
| 92 | + if (ftFeatureUtil != null) { | |
| 93 | + Method get = ftFeatureUtil.getMethod("isFeatureSupport", int.class); | |
| 94 | + return (boolean) get.invoke(ftFeatureUtil, 0x00000020); | |
| 95 | + } | |
| 96 | + return false; | |
| 97 | + } catch (Exception e) { | |
| 98 | + return false; | |
| 99 | + } | |
| 100 | + } | |
| 101 | + | |
| 102 | + /** | |
| 103 | + * 是否是小米刘海屏机型 | |
| 104 | + */ | |
| 105 | + @SuppressWarnings("unchecked") | |
| 106 | + @SuppressLint("PrivateApi") | |
| 107 | + private static boolean hasCutoutXIAOMI(Activity activity) { | |
| 108 | + if (!Build.MANUFACTURER.equalsIgnoreCase("xiaomi")) { | |
| 109 | + return false; | |
| 110 | + } | |
| 111 | + try { | |
| 112 | + ClassLoader cl = activity.getClassLoader(); | |
| 113 | + Class SystemProperties = cl.loadClass("android.os.SystemProperties"); | |
| 114 | + Class[] paramTypes = new Class[2]; | |
| 115 | + paramTypes[0] = String.class; | |
| 116 | + paramTypes[1] = int.class; | |
| 117 | + Method getInt = SystemProperties.getMethod("getInt", paramTypes); | |
| 118 | + //参数 | |
| 119 | + Object[] params = new Object[2]; | |
| 120 | + params[0] = "ro.miui.notch"; | |
| 121 | + params[1] = 0; | |
| 122 | + int hasCutout = (int) getInt.invoke(SystemProperties, params); | |
| 123 | + return hasCutout == 1; | |
| 124 | + } catch (Exception e) { | |
| 125 | + return false; | |
| 126 | + } | |
| 127 | + } | |
| 128 | + | |
| 129 | + /** | |
| 130 | + * 适配刘海屏,针对Android P以上系统 | |
| 131 | + */ | |
| 132 | + public static void adaptCutoutAboveAndroidP(Context context, boolean isAdapt) { | |
| 133 | + Activity activity = PlayerUtils.scanForActivity(context); | |
| 134 | + if (activity == null) return; | |
| 135 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { | |
| 136 | + WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); | |
| 137 | + if (isAdapt) { | |
| 138 | + lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; | |
| 139 | + } else { | |
| 140 | + lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT; | |
| 141 | + } | |
| 142 | + activity.getWindow().setAttributes(lp); | |
| 143 | + } | |
| 144 | + } | |
| 145 | + | |
| 146 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/util/L.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.util; | |
| 2 | + | |
| 3 | +import android.util.Log; | |
| 4 | + | |
| 5 | +import com.libs.video.video.exoplayer.play.VideoViewManager; | |
| 6 | + | |
| 7 | + | |
| 8 | +/** | |
| 9 | + * 日志类 | |
| 10 | + */ | |
| 11 | + | |
| 12 | +public final class L { | |
| 13 | + | |
| 14 | + private L() { | |
| 15 | + } | |
| 16 | + | |
| 17 | + private static final String TAG = "CNLiveVideoPlayer"; | |
| 18 | + | |
| 19 | + private static boolean isDebug = VideoViewManager.getConfig().mIsEnableLog; | |
| 20 | + | |
| 21 | + | |
| 22 | + public static void d(String msg) { | |
| 23 | + if (isDebug) { | |
| 24 | + Log.d(TAG, msg); | |
| 25 | + } | |
| 26 | + } | |
| 27 | + | |
| 28 | + public static void e(String msg) { | |
| 29 | + if (isDebug) { | |
| 30 | + Log.e(TAG, msg); | |
| 31 | + } | |
| 32 | + } | |
| 33 | + | |
| 34 | + public static void i(String msg) { | |
| 35 | + if (isDebug) { | |
| 36 | + Log.i(TAG, msg); | |
| 37 | + } | |
| 38 | + } | |
| 39 | + | |
| 40 | + public static void w(String msg) { | |
| 41 | + if (isDebug) { | |
| 42 | + Log.w(TAG, msg); | |
| 43 | + } | |
| 44 | + } | |
| 45 | + | |
| 46 | + public static void setDebug(boolean isDebug) { | |
| 47 | + L.isDebug = isDebug; | |
| 48 | + } | |
| 49 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/util/PlayerFactoryUtils.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.util; | |
| 2 | + | |
| 3 | +import android.view.View; | |
| 4 | +import android.view.ViewParent; | |
| 5 | +import android.widget.FrameLayout; | |
| 6 | + | |
| 7 | + | |
| 8 | +import com.libs.video.video.exoplayer.play.CNVideoView; | |
| 9 | +import com.libs.video.video.exoplayer.play.VideoViewConfig; | |
| 10 | +import com.libs.video.video.exoplayer.play.VideoViewManager; | |
| 11 | + | |
| 12 | +import java.lang.reflect.Field; | |
| 13 | + | |
| 14 | +public final class PlayerFactoryUtils { | |
| 15 | + | |
| 16 | + private PlayerFactoryUtils() { | |
| 17 | + } | |
| 18 | + | |
| 19 | + | |
| 20 | + /** | |
| 21 | + * 获取当前的播放核心 | |
| 22 | + */ | |
| 23 | + public static Object getCurrentPlayerFactory() { | |
| 24 | + VideoViewConfig config = VideoViewManager.getConfig(); | |
| 25 | + Object playerFactory = null; | |
| 26 | + try { | |
| 27 | + Field mPlayerFactoryField = config.getClass().getDeclaredField("mPlayerFactory"); | |
| 28 | + mPlayerFactoryField.setAccessible(true); | |
| 29 | + playerFactory = mPlayerFactoryField.get(config); | |
| 30 | + } catch (Exception e) { | |
| 31 | + e.printStackTrace(); | |
| 32 | + } | |
| 33 | + return playerFactory; | |
| 34 | + } | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * 将View从父控件中移除 | |
| 38 | + */ | |
| 39 | + public static void removeViewFormParent(View v) { | |
| 40 | + if (v == null) return; | |
| 41 | + ViewParent parent = v.getParent(); | |
| 42 | + if (parent instanceof FrameLayout) { | |
| 43 | + ((FrameLayout) parent).removeView(v); | |
| 44 | + } | |
| 45 | + } | |
| 46 | + | |
| 47 | + /** | |
| 48 | + * Returns a string containing player state debugging information. | |
| 49 | + */ | |
| 50 | + public static String playState2str(int state) { | |
| 51 | + String playStateString; | |
| 52 | + switch (state) { | |
| 53 | + default: | |
| 54 | + case CNVideoView.STATE_IDLE: | |
| 55 | + playStateString = "idle"; | |
| 56 | + break; | |
| 57 | + case CNVideoView.STATE_PREPARING: | |
| 58 | + playStateString = "preparing"; | |
| 59 | + break; | |
| 60 | + case CNVideoView.STATE_PREPARED: | |
| 61 | + playStateString = "prepared"; | |
| 62 | + break; | |
| 63 | + case CNVideoView.STATE_PLAYING: | |
| 64 | + playStateString = "playing"; | |
| 65 | + break; | |
| 66 | + case CNVideoView.STATE_PAUSED: | |
| 67 | + playStateString = "pause"; | |
| 68 | + break; | |
| 69 | + case CNVideoView.STATE_BUFFERING: | |
| 70 | + playStateString = "buffering"; | |
| 71 | + break; | |
| 72 | + case CNVideoView.STATE_BUFFERED: | |
| 73 | + playStateString = "buffered"; | |
| 74 | + break; | |
| 75 | + case CNVideoView.STATE_PLAYBACK_COMPLETED: | |
| 76 | + playStateString = "playback completed"; | |
| 77 | + break; | |
| 78 | + case CNVideoView.STATE_ERROR: | |
| 79 | + playStateString = "error"; | |
| 80 | + break; | |
| 81 | + } | |
| 82 | + return String.format("playState: %s", playStateString); | |
| 83 | + } | |
| 84 | + | |
| 85 | + /** | |
| 86 | + * Returns a string containing player state debugging information. | |
| 87 | + */ | |
| 88 | + public static String playerState2str(int state) { | |
| 89 | + String playerStateString; | |
| 90 | + switch (state) { | |
| 91 | + default: | |
| 92 | + case CNVideoView.PLAYER_NORMAL: | |
| 93 | + playerStateString = "normal"; | |
| 94 | + break; | |
| 95 | + case CNVideoView.PLAYER_FULL_SCREEN: | |
| 96 | + playerStateString = "full screen"; | |
| 97 | + break; | |
| 98 | + case CNVideoView.PLAYER_TINY_SCREEN: | |
| 99 | + playerStateString = "tiny screen"; | |
| 100 | + break; | |
| 101 | + } | |
| 102 | + return String.format("playerState: %s", playerStateString); | |
| 103 | + } | |
| 104 | + | |
| 105 | + | |
| 106 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/util/PlayerUtils.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer.util; | |
| 2 | + | |
| 3 | +import android.annotation.SuppressLint; | |
| 4 | +import android.app.Activity; | |
| 5 | +import android.app.Application; | |
| 6 | +import android.content.Context; | |
| 7 | +import android.content.ContextWrapper; | |
| 8 | +import android.content.res.Resources; | |
| 9 | +import android.graphics.Point; | |
| 10 | +import android.net.ConnectivityManager; | |
| 11 | +import android.net.NetworkInfo; | |
| 12 | +import android.os.Build; | |
| 13 | +import android.telephony.TelephonyManager; | |
| 14 | +import android.util.TypedValue; | |
| 15 | +import android.view.Display; | |
| 16 | +import android.view.KeyCharacterMap; | |
| 17 | +import android.view.KeyEvent; | |
| 18 | +import android.view.MotionEvent; | |
| 19 | +import android.view.ViewConfiguration; | |
| 20 | +import android.view.WindowManager; | |
| 21 | + | |
| 22 | +import java.text.SimpleDateFormat; | |
| 23 | +import java.util.ArrayList; | |
| 24 | +import java.util.Collection; | |
| 25 | +import java.util.Date; | |
| 26 | +import java.util.List; | |
| 27 | +import java.util.Locale; | |
| 28 | + | |
| 29 | +import androidx.annotation.NonNull; | |
| 30 | + | |
| 31 | +/** | |
| 32 | + * 播放器相关工具类 | |
| 33 | + */ | |
| 34 | + | |
| 35 | +public final class PlayerUtils { | |
| 36 | + | |
| 37 | + private PlayerUtils() { | |
| 38 | + } | |
| 39 | + | |
| 40 | + /** | |
| 41 | + * 获取状态栏高度 | |
| 42 | + */ | |
| 43 | + public static double getStatusBarHeight(Context context) { | |
| 44 | + int statusBarHeight = 0; | |
| 45 | + //获取status_bar_height资源的ID | |
| 46 | + int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android"); | |
| 47 | + if (resourceId > 0) { | |
| 48 | + //根据资源ID获取响应的尺寸值 | |
| 49 | + statusBarHeight = context.getResources().getDimensionPixelSize(resourceId); | |
| 50 | + } | |
| 51 | + return statusBarHeight; | |
| 52 | + } | |
| 53 | + | |
| 54 | + /** | |
| 55 | + * 获取竖屏下状态栏高度 | |
| 56 | + */ | |
| 57 | + public static double getStatusBarHeightPortrait(Context context) { | |
| 58 | + int statusBarHeight = 0; | |
| 59 | + //获取status_bar_height_portrait资源的ID | |
| 60 | + int resourceId = context.getResources().getIdentifier("status_bar_height_portrait", "dimen", "android"); | |
| 61 | + if (resourceId > 0) { | |
| 62 | + //根据资源ID获取响应的尺寸值 | |
| 63 | + statusBarHeight = context.getResources().getDimensionPixelSize(resourceId); | |
| 64 | + } | |
| 65 | + return statusBarHeight; | |
| 66 | + } | |
| 67 | + | |
| 68 | + /** | |
| 69 | + * 获取NavigationBar的高度 | |
| 70 | + */ | |
| 71 | + public static int getNavigationBarHeight(Context context) { | |
| 72 | + if (!hasNavigationBar(context)) { | |
| 73 | + return 0; | |
| 74 | + } | |
| 75 | + Resources resources = context.getResources(); | |
| 76 | + int resourceId = resources.getIdentifier("navigation_bar_height", | |
| 77 | + "dimen", "android"); | |
| 78 | + //获取NavigationBar的高度 | |
| 79 | + return resources.getDimensionPixelSize(resourceId); | |
| 80 | + } | |
| 81 | + | |
| 82 | + /** | |
| 83 | + * 是否存在NavigationBar | |
| 84 | + */ | |
| 85 | + public static boolean hasNavigationBar(Context context) { | |
| 86 | + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { | |
| 87 | + Display display = getWindowManager(context).getDefaultDisplay(); | |
| 88 | + Point size = new Point(); | |
| 89 | + Point realSize = new Point(); | |
| 90 | + display.getSize(size); | |
| 91 | + display.getRealSize(realSize); | |
| 92 | + return realSize.x != size.x || realSize.y != size.y; | |
| 93 | + } else { | |
| 94 | + boolean menu = ViewConfiguration.get(context).hasPermanentMenuKey(); | |
| 95 | + boolean back = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK); | |
| 96 | + return !(menu || back); | |
| 97 | + } | |
| 98 | + } | |
| 99 | + | |
| 100 | + /** | |
| 101 | + * 获取屏幕宽度 | |
| 102 | + */ | |
| 103 | + public static int getScreenWidth(Context context, boolean isIncludeNav) { | |
| 104 | + if (isIncludeNav) { | |
| 105 | + return context.getResources().getDisplayMetrics().widthPixels + getNavigationBarHeight(context); | |
| 106 | + } else { | |
| 107 | + return context.getResources().getDisplayMetrics().widthPixels; | |
| 108 | + } | |
| 109 | + } | |
| 110 | + | |
| 111 | + /** | |
| 112 | + * 获取屏幕高度 | |
| 113 | + */ | |
| 114 | + public static int getScreenHeight(Context context, boolean isIncludeNav) { | |
| 115 | + if (isIncludeNav) { | |
| 116 | + return context.getResources().getDisplayMetrics().heightPixels + getNavigationBarHeight(context); | |
| 117 | + } else { | |
| 118 | + return context.getResources().getDisplayMetrics().heightPixels; | |
| 119 | + } | |
| 120 | + } | |
| 121 | + | |
| 122 | + /** | |
| 123 | + * 获取Activity | |
| 124 | + */ | |
| 125 | + public static Activity scanForActivity(Context context) { | |
| 126 | + if (context == null) return null; | |
| 127 | + if (context instanceof Activity) { | |
| 128 | + return (Activity) context; | |
| 129 | + } else if (context instanceof ContextWrapper) { | |
| 130 | + return scanForActivity(((ContextWrapper) context).getBaseContext()); | |
| 131 | + } | |
| 132 | + return null; | |
| 133 | + } | |
| 134 | + | |
| 135 | + /** | |
| 136 | + * dp转为px | |
| 137 | + */ | |
| 138 | + public static int dp2px(Context context, float dpValue) { | |
| 139 | + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, context.getResources().getDisplayMetrics()); | |
| 140 | + } | |
| 141 | + | |
| 142 | + /** | |
| 143 | + * sp转为px | |
| 144 | + */ | |
| 145 | + public static int sp2px(Context context, float dpValue) { | |
| 146 | + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, dpValue, context.getResources().getDisplayMetrics()); | |
| 147 | + } | |
| 148 | + | |
| 149 | + /** | |
| 150 | + * 如果WindowManager还未创建,则创建一个新的WindowManager返回。否则返回当前已创建的WindowManager。 | |
| 151 | + */ | |
| 152 | + public static WindowManager getWindowManager(Context context) { | |
| 153 | + return (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); | |
| 154 | + } | |
| 155 | + | |
| 156 | + /** | |
| 157 | + * 边缘检测 | |
| 158 | + */ | |
| 159 | + public static boolean isEdge(Context context, MotionEvent e) { | |
| 160 | + int edgeSize = dp2px(context, 40); | |
| 161 | + return e.getRawX() < edgeSize | |
| 162 | + || e.getRawX() > getScreenWidth(context, true) - edgeSize | |
| 163 | + || e.getRawY() < edgeSize | |
| 164 | + || e.getRawY() > getScreenHeight(context, true) - edgeSize; | |
| 165 | + } | |
| 166 | + | |
| 167 | + | |
| 168 | + public static final int NO_NETWORK = 0; | |
| 169 | + public static final int NETWORK_CLOSED = 1; | |
| 170 | + public static final int NETWORK_ETHERNET = 2; | |
| 171 | + public static final int NETWORK_WIFI = 3; | |
| 172 | + public static final int NETWORK_MOBILE = 4; | |
| 173 | + public static final int NETWORK_UNKNOWN = -1; | |
| 174 | + | |
| 175 | + /** | |
| 176 | + * 判断当前网络类型 | |
| 177 | + */ | |
| 178 | + public static int getNetworkType(Context context) { | |
| 179 | + //改为context.getApplicationContext(),防止在Android 6.0上发生内存泄漏 | |
| 180 | + ConnectivityManager connectMgr = (ConnectivityManager) context.getApplicationContext() | |
| 181 | + .getSystemService(Context.CONNECTIVITY_SERVICE); | |
| 182 | + | |
| 183 | + if (connectMgr == null) { | |
| 184 | + return NO_NETWORK; | |
| 185 | + } | |
| 186 | + | |
| 187 | + NetworkInfo networkInfo = connectMgr.getActiveNetworkInfo(); | |
| 188 | + if (networkInfo == null) { | |
| 189 | + // 没有任何网络 | |
| 190 | + return NO_NETWORK; | |
| 191 | + } | |
| 192 | + if (!networkInfo.isConnected()) { | |
| 193 | + // 网络断开或关闭 | |
| 194 | + return NETWORK_CLOSED; | |
| 195 | + } | |
| 196 | + if (networkInfo.getType() == ConnectivityManager.TYPE_ETHERNET) { | |
| 197 | + // 以太网网络 | |
| 198 | + return NETWORK_ETHERNET; | |
| 199 | + } else if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) { | |
| 200 | + // wifi网络,当激活时,默认情况下,所有的数据流量将使用此连接 | |
| 201 | + return NETWORK_WIFI; | |
| 202 | + } else if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) { | |
| 203 | + // 移动数据连接,不能与连接共存,如果wifi打开,则自动关闭 | |
| 204 | + switch (networkInfo.getSubtype()) { | |
| 205 | + // 2G | |
| 206 | + case TelephonyManager.NETWORK_TYPE_GPRS: | |
| 207 | + case TelephonyManager.NETWORK_TYPE_EDGE: | |
| 208 | + case TelephonyManager.NETWORK_TYPE_CDMA: | |
| 209 | + case TelephonyManager.NETWORK_TYPE_1xRTT: | |
| 210 | + case TelephonyManager.NETWORK_TYPE_IDEN: | |
| 211 | + // 3G | |
| 212 | + case TelephonyManager.NETWORK_TYPE_UMTS: | |
| 213 | + case TelephonyManager.NETWORK_TYPE_EVDO_0: | |
| 214 | + case TelephonyManager.NETWORK_TYPE_EVDO_A: | |
| 215 | + case TelephonyManager.NETWORK_TYPE_HSDPA: | |
| 216 | + case TelephonyManager.NETWORK_TYPE_HSUPA: | |
| 217 | + case TelephonyManager.NETWORK_TYPE_HSPA: | |
| 218 | + case TelephonyManager.NETWORK_TYPE_EVDO_B: | |
| 219 | + case TelephonyManager.NETWORK_TYPE_EHRPD: | |
| 220 | + case TelephonyManager.NETWORK_TYPE_HSPAP: | |
| 221 | + // 4G | |
| 222 | + case TelephonyManager.NETWORK_TYPE_LTE: | |
| 223 | + // 5G | |
| 224 | + case TelephonyManager.NETWORK_TYPE_NR: | |
| 225 | + return NETWORK_MOBILE; | |
| 226 | + } | |
| 227 | + } | |
| 228 | + // 未知网络 | |
| 229 | + return NETWORK_UNKNOWN; | |
| 230 | + } | |
| 231 | + | |
| 232 | + /** | |
| 233 | + * 通过反射获取Application | |
| 234 | + * | |
| 235 | + * @deprecated 不在使用,后期谷歌可能封掉改接口 | |
| 236 | + */ | |
| 237 | + @SuppressLint("PrivateApi") | |
| 238 | + @Deprecated | |
| 239 | + public static Application getApplication() { | |
| 240 | + try { | |
| 241 | + return (Application) Class.forName("android.app.ActivityThread") | |
| 242 | + .getMethod("currentApplication").invoke(null); | |
| 243 | + } catch (Exception e) { | |
| 244 | + e.printStackTrace(); | |
| 245 | + } | |
| 246 | + return null; | |
| 247 | + } | |
| 248 | + | |
| 249 | + /** | |
| 250 | + * 获取当前系统时间 | |
| 251 | + */ | |
| 252 | + public static String getCurrentSystemTime() { | |
| 253 | + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm", Locale.getDefault()); | |
| 254 | + Date date = new Date(); | |
| 255 | + return simpleDateFormat.format(date); | |
| 256 | + } | |
| 257 | + | |
| 258 | + /** | |
| 259 | + * 格式化时间 | |
| 260 | + */ | |
| 261 | + public static String stringForTime(int timeMs) { | |
| 262 | + int totalSeconds = timeMs / 1000; | |
| 263 | + | |
| 264 | + int seconds = totalSeconds % 60; | |
| 265 | + int minutes = (totalSeconds / 60) % 60; | |
| 266 | + int hours = totalSeconds / 3600; | |
| 267 | + | |
| 268 | + if (hours > 0) { | |
| 269 | + return String.format(Locale.getDefault(), "%d:%02d:%02d", hours, minutes, seconds); | |
| 270 | + } else { | |
| 271 | + return String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds); | |
| 272 | + } | |
| 273 | + } | |
| 274 | + | |
| 275 | + /** | |
| 276 | + * 获取集合的快照 | |
| 277 | + */ | |
| 278 | + @NonNull | |
| 279 | + public static <T> List<T> getSnapshot(@NonNull Collection<T> other) { | |
| 280 | + List<T> result = new ArrayList<>(other.size()); | |
| 281 | + for (T item : other) { | |
| 282 | + if (item != null) { | |
| 283 | + result.add(item); | |
| 284 | + } | |
| 285 | + } | |
| 286 | + return result; | |
| 287 | + } | |
| 288 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/BaseTimerBar.java renamed to cloud/video/src/main/java/com/libs/video/video/test/BaseTimerBar.java
cloud/video/src/main/java/com/libs/video/video/exoplayer/CNLiveBaseVideoControlView.java renamed to cloud/video/src/main/java/com/libs/video/video/test/BaseVideoController.java
| 1 | -package com.libs.video.video.exoplayer; | |
| 1 | +package com.libs.video.video.test; | |
| 2 | 2 | |
| 3 | 3 | import android.annotation.SuppressLint; |
| 4 | 4 | import android.app.Activity; |
| ... | ... | @@ -16,10 +16,8 @@ import android.view.Surface; |
| 16 | 16 | import android.view.View; |
| 17 | 17 | import android.view.ViewGroup; |
| 18 | 18 | import android.widget.FrameLayout; |
| 19 | -import android.widget.LinearLayout; | |
| 20 | 19 | |
| 21 | 20 | import androidx.annotation.Nullable; |
| 22 | -import androidx.core.content.ContextCompat; | |
| 23 | 21 | |
| 24 | 22 | import com.cnlive.cloud.video.R; |
| 25 | 23 | import com.google.android.exoplayer2.C; |
| ... | ... | @@ -48,7 +46,7 @@ import java.util.concurrent.CopyOnWriteArrayList; |
| 48 | 46 | /** |
| 49 | 47 | * 视频控制视图基类 |
| 50 | 48 | */ |
| 51 | -public abstract class CNLiveBaseVideoControlView extends FrameLayout implements AnalyticsListener { | |
| 49 | +public abstract class BaseVideoController extends FrameLayout implements AnalyticsListener { | |
| 52 | 50 | |
| 53 | 51 | static { |
| 54 | 52 | ExoPlayerLibraryInfo.registerModule("goog.exo.ui"); |
| ... | ... | @@ -115,8 +113,8 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements |
| 115 | 113 | */ |
| 116 | 114 | private static final int MAX_UPDATE_INTERVAL_MS = 1000; |
| 117 | 115 | |
| 118 | - private CNLiveBaseVideoControlView.ComponentListener componentListener; | |
| 119 | - private CopyOnWriteArrayList<CNLiveBaseVideoControlView.VisibilityListener> visibilityListeners; | |
| 116 | + private BaseVideoController.ComponentListener componentListener; | |
| 117 | + private CopyOnWriteArrayList<BaseVideoController.VisibilityListener> visibilityListeners; | |
| 120 | 118 | private StringBuilder formatBuilder; |
| 121 | 119 | private Formatter formatter; |
| 122 | 120 | private Timeline.Period period; |
| ... | ... | @@ -144,7 +142,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements |
| 144 | 142 | //延时隐藏当前播放视图 |
| 145 | 143 | private Runnable delayHideViewAction; |
| 146 | 144 | |
| 147 | - public CNLiveBaseVideoControlView(Context context) { | |
| 145 | + public BaseVideoController(Context context) { | |
| 148 | 146 | this(context, /* attrs= */ null, 0, null); |
| 149 | 147 | this.context = context; |
| 150 | 148 | visibilityListeners = new CopyOnWriteArrayList<>(); |
| ... | ... | @@ -180,7 +178,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements |
| 180 | 178 | "nullness:method.invocation.invalid", |
| 181 | 179 | "nullness:methodref.receiver.bound.invalid" |
| 182 | 180 | }) |
| 183 | - public CNLiveBaseVideoControlView( | |
| 181 | + public BaseVideoController( | |
| 184 | 182 | Context context, |
| 185 | 183 | @Nullable AttributeSet attrs, |
| 186 | 184 | int defStyleAttr, |
| ... | ... | @@ -196,7 +194,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements |
| 196 | 194 | window = new Timeline.Window(); |
| 197 | 195 | //初始化相关监听器 |
| 198 | 196 | visibilityListeners = new CopyOnWriteArrayList<>(); |
| 199 | - componentListener = new CNLiveBaseVideoControlView.ComponentListener(); | |
| 197 | + componentListener = new BaseVideoController.ComponentListener(); | |
| 200 | 198 | // controlDispatcher = new CNLiveDefaultControlDispatcher(); |
| 201 | 199 | controllerLayoutId = R.layout.layout_cnlive_control_view; |
| 202 | 200 | |
| ... | ... | @@ -339,7 +337,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements |
| 339 | 337 | } |
| 340 | 338 | } |
| 341 | 339 | |
| 342 | - protected abstract void initPortraitView(View basePortraitView, CNLiveBaseVideoControlView.ComponentListener onClickListener); | |
| 340 | + protected abstract void initPortraitView(View basePortraitView, BaseVideoController.ComponentListener onClickListener); | |
| 343 | 341 | |
| 344 | 342 | @SuppressWarnings("ResourceType") |
| 345 | 343 | private static @RepeatModeUtil.RepeatToggleModes |
| ... | ... | @@ -406,31 +404,31 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements |
| 406 | 404 | } |
| 407 | 405 | |
| 408 | 406 | /** |
| 409 | - * Adds a {@link CNLiveBaseVideoControlView.VisibilityListener}. | |
| 407 | + * Adds a {@link BaseVideoController.VisibilityListener}. | |
| 410 | 408 | * |
| 411 | 409 | * @param listener The listener to be notified about visibility changes. |
| 412 | 410 | */ |
| 413 | - public void addVisibilityListener(CNLiveBaseVideoControlView.VisibilityListener listener) { | |
| 411 | + public void addVisibilityListener(BaseVideoController.VisibilityListener listener) { | |
| 414 | 412 | if (null != listener) { |
| 415 | 413 | visibilityListeners.add(listener); |
| 416 | 414 | } |
| 417 | 415 | } |
| 418 | 416 | |
| 419 | 417 | /** |
| 420 | - * Removes a {@link CNLiveBaseVideoControlView.VisibilityListener}. | |
| 418 | + * Removes a {@link BaseVideoController.VisibilityListener}. | |
| 421 | 419 | * |
| 422 | 420 | * @param listener The listener to be removed. |
| 423 | 421 | */ |
| 424 | - public void removeVisibilityListener(CNLiveBaseVideoControlView.VisibilityListener listener) { | |
| 422 | + public void removeVisibilityListener(BaseVideoController.VisibilityListener listener) { | |
| 425 | 423 | visibilityListeners.remove(listener); |
| 426 | 424 | } |
| 427 | 425 | |
| 428 | 426 | /** |
| 429 | - * Sets the {@link CNLiveBaseVideoControlView.ProgressUpdateListener}. | |
| 427 | + * Sets the {@link BaseVideoController.ProgressUpdateListener}. | |
| 430 | 428 | * |
| 431 | 429 | * @param listener The listener to be notified about when progress is updated. |
| 432 | 430 | */ |
| 433 | - public void setProgressUpdateListener(@Nullable CNLiveBaseVideoControlView.ProgressUpdateListener listener) { | |
| 431 | + public void setProgressUpdateListener(@Nullable BaseVideoController.ProgressUpdateListener listener) { | |
| 434 | 432 | // this.progressUpdateListener = listener; |
| 435 | 433 | |
| 436 | 434 | } |
| ... | ... | @@ -492,7 +490,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements |
| 492 | 490 | basePortraitView.setVisibility(VISIBLE); |
| 493 | 491 | } |
| 494 | 492 | if (null != visibilityListeners && visibilityListeners.size() > 0) { |
| 495 | - for (CNLiveBaseVideoControlView.VisibilityListener visibilityListener : visibilityListeners) { | |
| 493 | + for (BaseVideoController.VisibilityListener visibilityListener : visibilityListeners) { | |
| 496 | 494 | visibilityListener.onVisibilityChange(getVisibility()); |
| 497 | 495 | } |
| 498 | 496 | } |
| ... | ... | @@ -513,7 +511,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements |
| 513 | 511 | basePortraitView.setVisibility(GONE); |
| 514 | 512 | } |
| 515 | 513 | if (null != visibilityListeners && visibilityListeners.size() > 0) { |
| 516 | - for (CNLiveBaseVideoControlView.VisibilityListener visibilityListener : visibilityListeners) { | |
| 514 | + for (BaseVideoController.VisibilityListener visibilityListener : visibilityListeners) { | |
| 517 | 515 | visibilityListener.onVisibilityChange(getVisibility()); |
| 518 | 516 | } |
| 519 | 517 | } |
| ... | ... | @@ -836,16 +834,6 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements |
| 836 | 834 | // removeCallbacks(hideAction); |
| 837 | 835 | } |
| 838 | 836 | |
| 839 | -// @Override | |
| 840 | -// public final boolean dispatchTouchEvent(MotionEvent ev) { | |
| 841 | -// if (ev.getAction() == MotionEvent.ACTION_DOWN) { | |
| 842 | -// removeCallbacks(hideAction); | |
| 843 | -// } else if (ev.getAction() == MotionEvent.ACTION_UP) { | |
| 844 | -// hideAfterTimeout(); | |
| 845 | -// } | |
| 846 | -// return super.dispatchTouchEvent(ev); | |
| 847 | -// } | |
| 848 | - | |
| 849 | 837 | @Override |
| 850 | 838 | public boolean dispatchKeyEvent(KeyEvent event) { |
| 851 | 839 | return dispatchMediaKeyEvent(event) || super.dispatchKeyEvent(event); |
| ... | ... | @@ -915,25 +903,6 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements |
| 915 | 903 | || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS; |
| 916 | 904 | } |
| 917 | 905 | |
| 918 | - /** | |
| 919 | - * Returns whether the specified {@code timeline} can be shown on a multi-window time bar. | |
| 920 | - * | |
| 921 | - * @param timeline The {@link Timeline} to check. | |
| 922 | - * @param window A scratch {@link Timeline.Window} instance. | |
| 923 | - * @return Whether the specified timeline can be shown on a multi-window time bar. | |
| 924 | - */ | |
| 925 | - private static boolean canShowMultiWindowTimeBar(Timeline timeline, Timeline.Window window) { | |
| 926 | - if (timeline.getWindowCount() > MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR) { | |
| 927 | - return false; | |
| 928 | - } | |
| 929 | - int windowCount = timeline.getWindowCount(); | |
| 930 | - for (int i = 0; i < windowCount; i++) { | |
| 931 | - if (timeline.getWindow(i, window).durationUs == C.TIME_UNSET) { | |
| 932 | - return false; | |
| 933 | - } | |
| 934 | - } | |
| 935 | - return true; | |
| 936 | - } | |
| 937 | 906 | |
| 938 | 907 | public final class ComponentListener |
| 939 | 908 | implements OnClickListener { | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/CNLiveControlDispatcher.java renamed to cloud/video/src/main/java/com/libs/video/video/test/CNLiveControlDispatcher.java
cloud/video/src/main/java/com/libs/video/video/exoplayer/CNLiveDefaultControlDispatcher.java renamed to cloud/video/src/main/java/com/libs/video/video/test/CNLiveDefaultControlDispatcher.java
cloud/video/src/main/java/com/libs/video/video/exoplayer/CNLiveVideoView.java renamed to cloud/video/src/main/java/com/libs/video/video/test/CNLiveVideoView.java
| 1 | -package com.libs.video.video.exoplayer; | |
| 1 | +package com.libs.video.video.test; | |
| 2 | 2 | |
| 3 | 3 | import android.annotation.SuppressLint; |
| 4 | 4 | import android.annotation.TargetApi; |
| ... | ... | @@ -30,9 +30,7 @@ import android.widget.FrameLayout; |
| 30 | 30 | import android.widget.ImageView; |
| 31 | 31 | import android.widget.LinearLayout; |
| 32 | 32 | import android.widget.TextView; |
| 33 | -import android.widget.Toast; | |
| 34 | 33 | |
| 35 | -import androidx.annotation.ColorInt; | |
| 36 | 34 | import androidx.annotation.IntDef; |
| 37 | 35 | import androidx.annotation.Nullable; |
| 38 | 36 | import androidx.core.content.ContextCompat; |
| ... | ... | @@ -43,7 +41,6 @@ import com.google.android.exoplayer2.C; |
| 43 | 41 | import com.google.android.exoplayer2.ControlDispatcher; |
| 44 | 42 | import com.google.android.exoplayer2.DefaultControlDispatcher; |
| 45 | 43 | import com.google.android.exoplayer2.ExoPlaybackException; |
| 46 | -import com.google.android.exoplayer2.PlaybackPreparer; | |
| 47 | 44 | import com.google.android.exoplayer2.Player; |
| 48 | 45 | import com.google.android.exoplayer2.analytics.AnalyticsListener; |
| 49 | 46 | import com.google.android.exoplayer2.metadata.Metadata; |
| ... | ... | @@ -68,8 +65,6 @@ import com.google.android.exoplayer2.util.Util; |
| 68 | 65 | import com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView; |
| 69 | 66 | import com.google.android.exoplayer2.video.VideoListener; |
| 70 | 67 | |
| 71 | -import org.w3c.dom.Text; | |
| 72 | - | |
| 73 | 68 | import java.io.IOException; |
| 74 | 69 | import java.lang.annotation.Documented; |
| 75 | 70 | import java.lang.annotation.Retention; |
| ... | ... | @@ -136,14 +131,14 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv |
| 136 | 131 | @Nullable |
| 137 | 132 | private final TextView tvErrorMsg;//错误信息 |
| 138 | 133 | @Nullable |
| 139 | - private CNLiveBaseVideoControlView controller; | |
| 134 | + private BaseVideoController controller; | |
| 140 | 135 | @Nullable |
| 141 | 136 | private final FrameLayout adOverlayFrameLayout; |
| 142 | 137 | @Nullable |
| 143 | 138 | private final FrameLayout overlayFrameLayout; |
| 144 | 139 | private boolean useController; |
| 145 | 140 | @Nullable |
| 146 | - private CNLiveBaseVideoControlView.VisibilityListener controllerVisibilityListener; | |
| 141 | + private BaseVideoController.VisibilityListener controllerVisibilityListener; | |
| 147 | 142 | private boolean useArtwork; |
| 148 | 143 | @Nullable |
| 149 | 144 | private Drawable defaultArtwork; |
| ... | ... | @@ -229,7 +224,7 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv |
| 229 | 224 | boolean useController = true; |
| 230 | 225 | int surfaceType = SURFACE_TYPE_SURFACE_VIEW; |
| 231 | 226 | int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT; |
| 232 | - int controllerShowTimeoutMs = CNLiveBaseVideoControlView.DEFAULT_SHOW_TIMEOUT_MS; | |
| 227 | + int controllerShowTimeoutMs = BaseVideoController.DEFAULT_SHOW_TIMEOUT_MS; | |
| 233 | 228 | boolean controllerHideOnTouch = true; |
| 234 | 229 | boolean controllerAutoShow = true; |
| 235 | 230 | boolean controllerHideDuringAds = true; |
| ... | ... | @@ -350,7 +345,7 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv |
| 350 | 345 | } |
| 351 | 346 | }); |
| 352 | 347 | // Playback control view. |
| 353 | - CNLiveBaseVideoControlView customController = findViewById(R.id.cnlive_controller); | |
| 348 | + BaseVideoController customController = findViewById(R.id.cnlive_controller); | |
| 354 | 349 | controllerPlaceholder = findViewById(R.id.cnlive_controller_placeholder); |
| 355 | 350 | if (customController != null) { |
| 356 | 351 | this.controller = customController; |
| ... | ... | @@ -409,7 +404,7 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv |
| 409 | 404 | |
| 410 | 405 | } |
| 411 | 406 | |
| 412 | - public void setCustomControllView(CNLiveBaseVideoControlView controllerView) { | |
| 407 | + public void setCustomControllView(BaseVideoController controllerView) { | |
| 413 | 408 | if (null != controllerPlaceholder && null != controllerView) { |
| 414 | 409 | ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent()); |
| 415 | 410 | if (null != parent) { |
| ... | ... | @@ -880,13 +875,13 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv |
| 880 | 875 | } |
| 881 | 876 | |
| 882 | 877 | /** |
| 883 | - * Set the {@link CNLiveBaseVideoControlView.VisibilityListener}. | |
| 878 | + * Set the {@link BaseVideoController.VisibilityListener}. | |
| 884 | 879 | * |
| 885 | 880 | * @param listener The listener to be notified about visibility changes, or null to remove the |
| 886 | 881 | * current listener. |
| 887 | 882 | */ |
| 888 | 883 | public void setControllerVisibilityListener( |
| 889 | - @Nullable CNLiveBaseVideoControlView.VisibilityListener listener) { | |
| 884 | + @Nullable BaseVideoController.VisibilityListener listener) { | |
| 890 | 885 | Assertions.checkStateNotNull(controller); |
| 891 | 886 | if (this.controllerVisibilityListener == listener) { |
| 892 | 887 | return; |
| ... | ... | @@ -1399,7 +1394,7 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv |
| 1399 | 1394 | VideoListener, |
| 1400 | 1395 | OnLayoutChangeListener, |
| 1401 | 1396 | SingleTapListener, |
| 1402 | - CNLiveBaseVideoControlView.VisibilityListener { | |
| 1397 | + BaseVideoController.VisibilityListener { | |
| 1403 | 1398 | |
| 1404 | 1399 | // TextOutput implementation |
| 1405 | 1400 | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/CacheUtil.java renamed to cloud/video/src/main/java/com/libs/video/video/test/CacheUtil.java
| 1 | -package com.libs.video.video.exoplayer; | |
| 1 | +package com.libs.video.video.test; | |
| 2 | 2 | |
| 3 | 3 | import android.content.ContentValues; |
| 4 | 4 | import android.content.Context; |
| ... | ... | @@ -10,7 +10,6 @@ import android.text.TextUtils; |
| 10 | 10 | import android.util.Log; |
| 11 | 11 | |
| 12 | 12 | import com.libs.video.cache.utils.DesUtil; |
| 13 | -import com.libs.video.util.Config; | |
| 14 | 13 | |
| 15 | 14 | import java.security.InvalidKeyException; |
| 16 | 15 | import java.security.spec.InvalidKeySpecException; | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/IVideoDataSource.java renamed to cloud/video/src/main/java/com/libs/video/video/test/IVideoDataSource.java
cloud/video/src/main/java/com/libs/video/video/exoplayer/IVideoMultDataSource.java renamed to cloud/video/src/main/java/com/libs/video/video/test/IVideoMultDataSource.java
cloud/video/src/main/java/com/libs/video/video/exoplayer/IVideoPlayer.java renamed to cloud/video/src/main/java/com/libs/video/video/test/IVideoPlayer.java
| 1 | -package com.libs.video.video.exoplayer; | |
| 1 | +package com.libs.video.video.test; | |
| 2 | 2 | |
| 3 | 3 | import android.net.Uri; |
| 4 | 4 | import android.os.Looper; |
| ... | ... | @@ -12,12 +12,8 @@ import com.google.android.exoplayer2.Player; |
| 12 | 12 | import com.google.android.exoplayer2.Renderer; |
| 13 | 13 | import com.google.android.exoplayer2.Timeline; |
| 14 | 14 | import com.google.android.exoplayer2.analytics.AnalyticsListener; |
| 15 | -import com.google.android.exoplayer2.source.MediaSource; | |
| 16 | 15 | import com.google.android.exoplayer2.source.TrackGroupArray; |
| 17 | 16 | import com.google.android.exoplayer2.trackselection.TrackSelectionArray; |
| 18 | -import com.google.android.exoplayer2.ui.PlayerView; | |
| 19 | -import com.libs.video.video.base.IDataSource; | |
| 20 | -import com.libs.video.video.base.IMediaPlayer; | |
| 21 | 17 | |
| 22 | 18 | import java.util.List; |
| 23 | 19 | ... | ... |
cloud/video/src/main/java/com/libs/video/video/MediaPlayer.java renamed to cloud/video/src/main/java/com/libs/video/video/test/MediaPlayer.java
cloud/video/src/main/java/com/libs/video/video/exoplayer/MyTestControlView.java renamed to cloud/video/src/main/java/com/libs/video/video/test/MyTestControlView.java
| 1 | -package com.libs.video.video.exoplayer; | |
| 1 | +package com.libs.video.video.test; | |
| 2 | 2 | |
| 3 | 3 | import android.content.Context; |
| 4 | 4 | import android.util.AttributeSet; |
| ... | ... | @@ -10,12 +10,11 @@ import android.widget.TextView; |
| 10 | 10 | |
| 11 | 11 | import androidx.annotation.Nullable; |
| 12 | 12 | import androidx.appcompat.widget.AppCompatSeekBar; |
| 13 | -import androidx.core.content.ContextCompat; | |
| 14 | 13 | |
| 15 | 14 | import com.cnlive.cloud.video.R; |
| 16 | 15 | import com.google.android.exoplayer2.source.MediaSourceEventListener; |
| 17 | 16 | |
| 18 | -public class MyTestControlView extends CNLiveBaseVideoControlView { | |
| 17 | +public class MyTestControlView extends BaseVideoController { | |
| 19 | 18 | private View playButton; |
| 20 | 19 | private ImageView ivPortraitPlay; |
| 21 | 20 | private String TAG = getClass().getName(); | ... | ... |
cloud/video/src/main/java/com/libs/video/video/StatisticsDuration.java renamed to cloud/video/src/main/java/com/libs/video/video/test/StatisticsDuration.java
cloud/video/src/main/java/com/libs/video/video/SwitchVideoView.java renamed to cloud/video/src/main/java/com/libs/video/video/test/SwitchVideoView.java
cloud/video/src/main/java/com/libs/video/video/exoplayer/VideoPlayer.java renamed to cloud/video/src/main/java/com/libs/video/video/test/VideoPlayer.java
| 1 | -package com.libs.video.video.exoplayer; | |
| 1 | +package com.libs.video.video.test; | |
| 2 | 2 | |
| 3 | 3 | import android.content.Context; |
| 4 | 4 | import android.net.Uri; |
| ... | ... | @@ -14,33 +14,21 @@ import com.google.android.exoplayer2.Player; |
| 14 | 14 | import com.google.android.exoplayer2.SimpleExoPlayer; |
| 15 | 15 | import com.google.android.exoplayer2.Timeline; |
| 16 | 16 | import com.google.android.exoplayer2.analytics.AnalyticsListener; |
| 17 | -import com.google.android.exoplayer2.ext.ima.ImaAdsLoader; | |
| 18 | 17 | import com.google.android.exoplayer2.ext.rtmp.RtmpDataSourceFactory; |
| 19 | 18 | import com.google.android.exoplayer2.metadata.Metadata; |
| 20 | -import com.google.android.exoplayer2.metadata.MetadataOutput; | |
| 21 | 19 | import com.google.android.exoplayer2.source.ConcatenatingMediaSource; |
| 22 | -import com.google.android.exoplayer2.source.ExtractorMediaSource; | |
| 23 | 20 | import com.google.android.exoplayer2.source.MediaSource; |
| 24 | 21 | import com.google.android.exoplayer2.source.ProgressiveMediaSource; |
| 25 | 22 | import com.google.android.exoplayer2.source.TrackGroupArray; |
| 26 | -import com.google.android.exoplayer2.source.ads.AdsLoader; | |
| 27 | -import com.google.android.exoplayer2.source.ads.AdsMediaSource; | |
| 28 | 23 | import com.google.android.exoplayer2.source.dash.DashMediaSource; |
| 29 | 24 | import com.google.android.exoplayer2.source.hls.HlsMediaSource; |
| 30 | 25 | import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; |
| 31 | -import com.google.android.exoplayer2.text.Cue; | |
| 32 | -import com.google.android.exoplayer2.text.TextOutput; | |
| 33 | 26 | import com.google.android.exoplayer2.trackselection.TrackSelectionArray; |
| 34 | -import com.google.android.exoplayer2.ui.PlayerView; | |
| 35 | 27 | import com.google.android.exoplayer2.upstream.DataSource; |
| 36 | 28 | import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; |
| 37 | -import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; | |
| 38 | 29 | import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; |
| 39 | 30 | import com.google.android.exoplayer2.util.Util; |
| 40 | -import com.google.android.exoplayer2.video.VideoDecoderOutputBuffer; | |
| 41 | -import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer; | |
| 42 | 31 | import com.google.android.exoplayer2.video.VideoListener; |
| 43 | -import com.google.android.exoplayer2.video.spherical.CameraMotionListener; | |
| 44 | 32 | import com.libs.video.data.network.Callback; |
| 45 | 33 | import com.libs.video.data.network.DataLoader; |
| 46 | 34 | import com.libs.video.util.AuthUtil; | ... | ... |
cloud/video/src/main/java/com/libs/video/video/VideoStatusReceiver.java renamed to cloud/video/src/main/java/com/libs/video/video/test/VideoStatusReceiver.java
cloud/video/src/main/java/com/libs/video/video/VideoStatusUtil.java renamed to cloud/video/src/main/java/com/libs/video/video/test/VideoStatusUtil.java
cloud/video/src/main/java/com/libs/video/video/VideoView.java renamed to cloud/video/src/main/java/com/libs/video/video/test/VideoView.java
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_arrow_back.png
0 → 100644
472 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_autorenew.png
0 → 100644
937 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery.png
0 → 100644
282 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_10.png
0 → 100644
329 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_20.png
0 → 100644
339 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_30.png
0 → 100644
342 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_40.png
0 → 100644
329 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_50.png
0 → 100644
318 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_60.png
0 → 100644
307 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_70.png
0 → 100644
323 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_80.png
0 → 100644
310 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_90.png
0 → 100644
319 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_brightness.png
0 → 100644
717 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_close.png
0 → 100644
491 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_fast_forward.png
0 → 100644
556 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_fast_rewind.png
0 → 100644
557 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_fullscreen.png
0 → 100644
319 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_fullscreen_exit.png
0 → 100644
314 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_lock_close.png
0 → 100644
673 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_lock_open.png
0 → 100644
655 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_pause.png
0 → 100644
237 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_play_arrow.png
0 → 100644
410 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_replay.png
0 → 100644
869 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_volume_off.png
0 → 100644
960 Bytes
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_volume_up.png
0 → 100644
752 Bytes
cloud/video/src/main/res/drawable/cnvideo_battery_level.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<level-list xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 3 | + | |
| 4 | + <item | |
| 5 | + android:drawable="@drawable/cnvideo_ic_action_battery" | |
| 6 | + android:maxLevel="100" | |
| 7 | + android:minLevel="95" /> | |
| 8 | + <item | |
| 9 | + android:drawable="@drawable/cnvideo_ic_action_battery_10" | |
| 10 | + android:maxLevel="15" | |
| 11 | + android:minLevel="0" /> | |
| 12 | + <item | |
| 13 | + android:drawable="@drawable/cnvideo_ic_action_battery_20" | |
| 14 | + android:maxLevel="25" | |
| 15 | + android:minLevel="15" /> | |
| 16 | + <item | |
| 17 | + android:drawable="@drawable/cnvideo_ic_action_battery_30" | |
| 18 | + android:maxLevel="35" | |
| 19 | + android:minLevel="25" /> | |
| 20 | + <item | |
| 21 | + android:drawable="@drawable/cnvideo_ic_action_battery_40" | |
| 22 | + android:maxLevel="45" | |
| 23 | + android:minLevel="35" /> | |
| 24 | + <item | |
| 25 | + android:drawable="@drawable/cnvideo_ic_action_battery_50" | |
| 26 | + android:maxLevel="55" | |
| 27 | + android:minLevel="45" /> | |
| 28 | + <item | |
| 29 | + android:drawable="@drawable/cnvideo_ic_action_battery_60" | |
| 30 | + android:maxLevel="65" | |
| 31 | + android:minLevel="55" /> | |
| 32 | + <item | |
| 33 | + android:drawable="@drawable/cnvideo_ic_action_battery_70" | |
| 34 | + android:maxLevel="75" | |
| 35 | + android:minLevel="65" /> | |
| 36 | + <item | |
| 37 | + android:drawable="@drawable/cnvideo_ic_action_battery_80" | |
| 38 | + android:maxLevel="85" | |
| 39 | + android:minLevel="75" /> | |
| 40 | + <item | |
| 41 | + android:drawable="@drawable/cnvideo_ic_action_battery_90" | |
| 42 | + android:maxLevel="95" | |
| 43 | + android:minLevel="85" /> | |
| 44 | +</level-list> | |
| 0 | 45 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/drawable/cnvideo_ic_action_fullscreen.png
0 → 100644
319 Bytes
cloud/video/src/main/res/drawable/cnvideo_layer_progress_bar.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 3 | + <item android:id="@android:id/background"> | |
| 4 | + <shape> | |
| 5 | + <solid android:color="#7FFFFFFF"/> | |
| 6 | + </shape> | |
| 7 | + </item> | |
| 8 | + <item android:id="@android:id/secondaryProgress"> | |
| 9 | + <clip> | |
| 10 | + <shape> | |
| 11 | + <solid android:color="@android:color/white"/> | |
| 12 | + </shape> | |
| 13 | + </clip> | |
| 14 | + </item> | |
| 15 | + <item android:id="@android:id/progress"> | |
| 16 | + <clip> | |
| 17 | + <shape> | |
| 18 | + <solid android:color="@color/cnvideo_theme_color"/> | |
| 19 | + </shape> | |
| 20 | + </clip> | |
| 21 | + </item> | |
| 22 | +</layer-list> | ... | ... |
cloud/video/src/main/res/drawable/cnvideo_progress_loading.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<rotate xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:fromDegrees="0" | |
| 4 | + android:pivotX="50%" | |
| 5 | + android:pivotY="50%" | |
| 6 | + android:toDegrees="360"> | |
| 7 | + | |
| 8 | + <shape | |
| 9 | + android:innerRadiusRatio="2.8" | |
| 10 | + android:shape="ring" | |
| 11 | + android:thicknessRatio="15" | |
| 12 | + android:useLevel="false"> | |
| 13 | + <gradient | |
| 14 | + android:endColor="@android:color/white" | |
| 15 | + android:startColor="@android:color/transparent" | |
| 16 | + android:type="sweep" | |
| 17 | + android:useLevel="false" /> | |
| 18 | + </shape> | |
| 19 | + | |
| 20 | +</rotate> | ... | ... |
cloud/video/src/main/res/drawable/cnvideo_seekbar_thumb.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<selector xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 3 | + <item android:state_pressed="true" android:state_focused="true" android:drawable="@drawable/cnvideo_seekbar_thumb_pressed"/> | |
| 4 | + <item android:state_pressed="true" android:state_focused="false" android:drawable="@drawable/cnvideo_seekbar_thumb_pressed"/> | |
| 5 | + <item android:state_pressed="false" android:state_focused="true" android:drawable="@drawable/cnvideo_seekbar_thumb_normal"/> | |
| 6 | + <item android:drawable="@drawable/cnvideo_seekbar_thumb_normal"/> | |
| 7 | +</selector> | ... | ... |
cloud/video/src/main/res/drawable/cnvideo_seekbar_thumb_normal.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<shape android:shape="oval" | |
| 3 | + xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 4 | + <solid android:color="@color/cnvideo_theme_color"/> | |
| 5 | + <size | |
| 6 | + android:height="@dimen/cnvideo_controller_seekbar_size_n" | |
| 7 | + android:width="@dimen/cnvideo_controller_seekbar_size_n"/> | |
| 8 | +</shape> | ... | ... |
cloud/video/src/main/res/drawable/cnvideo_seekbar_thumb_pressed.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<shape android:shape="oval" | |
| 3 | + xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 4 | + <solid android:color="@color/cnvideo_theme_color"/> | |
| 5 | + <size | |
| 6 | + android:height="@dimen/cnvideo_controller_seekbar_size_n" | |
| 7 | + android:width="@dimen/cnvideo_controller_seekbar_size_n"/> | |
| 8 | +</shape> | ... | ... |
cloud/video/src/main/res/drawable/cnvideo_selector_full_screen_button.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<selector xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 3 | + | |
| 4 | + <item android:state_selected="true" android:drawable="@drawable/cnvideo_ic_action_fullscreen_exit"/> | |
| 5 | + <item android:drawable="@drawable/cnvideo_ic_action_fullscreen"/> | |
| 6 | + | |
| 7 | +</selector> | |
| 0 | 8 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/drawable/cnvideo_selector_lock_button.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<selector xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 3 | + | |
| 4 | + <item android:state_selected="true" android:drawable="@drawable/cnvideo_ic_action_lock_open"/> | |
| 5 | + <item android:drawable="@drawable/cnvideo_ic_action_lock_close"/> | |
| 6 | + | |
| 7 | +</selector> | |
| 0 | 8 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/drawable/cnvideo_selector_play_button.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<selector xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 3 | + | |
| 4 | + <item android:state_selected="true" android:drawable="@drawable/cnvideo_ic_action_pause"/> | |
| 5 | + <item android:drawable="@drawable/cnvideo_ic_action_play_arrow"/> | |
| 6 | + | |
| 7 | +</selector> | |
| 0 | 8 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/drawable/cnvideo_shape_ad_bg.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<shape xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:shape="rectangle"> | |
| 4 | + <solid android:color="@color/cnvideo_background_color" /> | |
| 5 | + <size | |
| 6 | + android:width="80dp" | |
| 7 | + android:height="40dp" /> | |
| 8 | + <corners android:radius="20dp" /> | |
| 9 | +</shape> | ... | ... |
cloud/video/src/main/res/drawable/cnvideo_shape_back_bg.xml
0 → 100644
cloud/video/src/main/res/drawable/cnvideo_shape_play_bg.xml
0 → 100644
cloud/video/src/main/res/drawable/cnvideo_shape_standard_controller_top_bg.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<shape xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 3 | + | |
| 4 | + <gradient android:startColor="@color/cnvideo_background_color" | |
| 5 | + android:endColor="@android:color/transparent" | |
| 6 | + android:angle="270"/> | |
| 7 | + | |
| 8 | +</shape> | |
| 0 | 9 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/drawable/cnvideo_shape_stardard_controller_bottom_bg.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<shape xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 3 | + | |
| 4 | + <gradient android:startColor="@color/cnvideo_background_color" | |
| 5 | + android:endColor="@android:color/transparent" | |
| 6 | + android:angle="90"/> | |
| 7 | + | |
| 8 | +</shape> | |
| 0 | 9 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/drawable/cnvideo_shape_status_view_btn.xml
0 → 100644
cloud/video/src/main/res/layout/cnvideo_layout_complete_view.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + xmlns:tools="http://schemas.android.com/tools" | |
| 4 | + android:id="@+id/complete_container" | |
| 5 | + android:layout_width="match_parent" | |
| 6 | + android:layout_height="match_parent" | |
| 7 | + android:background="#33000000"> | |
| 8 | + | |
| 9 | + <ImageView | |
| 10 | + android:id="@+id/stop_fullscreen" | |
| 11 | + android:layout_width="@dimen/cnvideo_controller_height" | |
| 12 | + android:layout_height="@dimen/cnvideo_controller_height" | |
| 13 | + android:padding="@dimen/cnvideo_controller_icon_padding" | |
| 14 | + android:src="@drawable/cnvideo_ic_action_arrow_back" | |
| 15 | + android:visibility="gone" | |
| 16 | + tools:visibility="visible" /> | |
| 17 | + | |
| 18 | + <LinearLayout | |
| 19 | + android:layout_width="wrap_content" | |
| 20 | + android:layout_height="wrap_content" | |
| 21 | + android:layout_gravity="center" | |
| 22 | + android:orientation="vertical"> | |
| 23 | + | |
| 24 | + <ImageView | |
| 25 | + android:id="@+id/iv_replay" | |
| 26 | + android:layout_width="@dimen/cnvideo_play_btn_size" | |
| 27 | + android:layout_height="@dimen/cnvideo_play_btn_size" | |
| 28 | + android:layout_gravity="center" | |
| 29 | + android:background="@drawable/cnvideo_shape_play_bg" | |
| 30 | + android:padding="@dimen/cnvideo_controller_icon_padding" | |
| 31 | + android:src="@drawable/cnvideo_ic_action_replay" /> | |
| 32 | + | |
| 33 | + <TextView | |
| 34 | + android:layout_width="wrap_content" | |
| 35 | + android:layout_height="wrap_content" | |
| 36 | + android:text="@string/cnvideo_replay" | |
| 37 | + android:layout_marginTop="12dp" | |
| 38 | + android:textColor="@android:color/white" /> | |
| 39 | + | |
| 40 | + </LinearLayout> | |
| 41 | +</FrameLayout> | |
| 0 | 42 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/layout/cnvideo_layout_error_view.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="match_parent" | |
| 5 | + android:background="@android:color/black"> | |
| 6 | + | |
| 7 | + <LinearLayout | |
| 8 | + android:layout_width="match_parent" | |
| 9 | + android:layout_height="match_parent" | |
| 10 | + android:gravity="center" | |
| 11 | + android:orientation="vertical"> | |
| 12 | + | |
| 13 | + <TextView | |
| 14 | + android:id="@+id/message" | |
| 15 | + android:layout_width="wrap_content" | |
| 16 | + android:layout_height="wrap_content" | |
| 17 | + android:gravity="center" | |
| 18 | + android:text="@string/cnvideo_error_message" | |
| 19 | + android:textColor="@android:color/white" /> | |
| 20 | + | |
| 21 | + <TextView | |
| 22 | + android:id="@+id/status_btn" | |
| 23 | + android:layout_width="wrap_content" | |
| 24 | + android:layout_height="wrap_content" | |
| 25 | + android:layout_marginTop="16dp" | |
| 26 | + android:background="@drawable/cnvideo_shape_status_view_btn" | |
| 27 | + android:gravity="center" | |
| 28 | + android:paddingLeft="18dp" | |
| 29 | + android:paddingTop="4dp" | |
| 30 | + android:paddingRight="18dp" | |
| 31 | + android:paddingBottom="4dp" | |
| 32 | + android:text="@string/cnvideo_retry" | |
| 33 | + android:textColor="@android:color/white" /> | |
| 34 | + | |
| 35 | + </LinearLayout> | |
| 36 | +</FrameLayout> | |
| 0 | 37 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/layout/cnvideo_layout_gesture_control_view.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + xmlns:tools="http://schemas.android.com/tools" | |
| 4 | + android:layout_width="match_parent" | |
| 5 | + android:layout_height="match_parent"> | |
| 6 | + | |
| 7 | + <LinearLayout | |
| 8 | + android:id="@+id/center_container" | |
| 9 | + android:layout_width="160dp" | |
| 10 | + android:layout_height="120dp" | |
| 11 | + android:layout_gravity="center" | |
| 12 | + android:background="#96000000" | |
| 13 | + android:gravity="center" | |
| 14 | + android:orientation="vertical" | |
| 15 | + android:visibility="gone" | |
| 16 | + tools:visibility="visible"> | |
| 17 | + | |
| 18 | + <ImageView | |
| 19 | + android:id="@+id/iv_icon" | |
| 20 | + android:layout_width="36dp" | |
| 21 | + android:layout_height="36dp" | |
| 22 | + tools:src="@drawable/cnvideo_ic_action_volume_up" /> | |
| 23 | + | |
| 24 | + <TextView | |
| 25 | + android:id="@+id/tv_percent" | |
| 26 | + android:layout_width="wrap_content" | |
| 27 | + android:layout_height="wrap_content" | |
| 28 | + android:layout_margin="10dp" | |
| 29 | + android:textColor="@android:color/white" | |
| 30 | + android:textSize="14sp" | |
| 31 | + tools:text="100" /> | |
| 32 | + | |
| 33 | + <ProgressBar | |
| 34 | + android:id="@+id/pro_percent" | |
| 35 | + style="?android:attr/progressBarStyleHorizontal" | |
| 36 | + android:layout_width="100dp" | |
| 37 | + android:layout_height="3dp" | |
| 38 | + android:max="100" | |
| 39 | + android:progressDrawable="@drawable/cnvideo_layer_progress_bar" /> | |
| 40 | + | |
| 41 | + </LinearLayout> | |
| 42 | +</FrameLayout> | ... | ... |
cloud/video/src/main/res/layout/cnvideo_layout_live_control_view.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="match_parent"> | |
| 5 | + | |
| 6 | + <LinearLayout | |
| 7 | + android:id="@+id/bottom_container" | |
| 8 | + android:layout_width="match_parent" | |
| 9 | + android:layout_height="@dimen/cnvideo_controller_height" | |
| 10 | + android:layout_gravity="bottom" | |
| 11 | + android:gravity="center_vertical" | |
| 12 | + android:background="@drawable/cnvideo_shape_stardard_controller_bottom_bg" | |
| 13 | + android:orientation="horizontal"> | |
| 14 | + | |
| 15 | + <ImageView | |
| 16 | + android:id="@+id/iv_play" | |
| 17 | + android:layout_width="@dimen/cnvideo_controller_height" | |
| 18 | + android:layout_height="@dimen/cnvideo_controller_height" | |
| 19 | + android:padding="@dimen/cnvideo_controller_icon_padding" | |
| 20 | + android:src="@drawable/cnvideo_selector_play_button" /> | |
| 21 | + | |
| 22 | + <ImageView | |
| 23 | + android:id="@+id/iv_refresh" | |
| 24 | + android:layout_width="@dimen/cnvideo_controller_height" | |
| 25 | + android:layout_height="@dimen/cnvideo_controller_height" | |
| 26 | + android:padding="@dimen/cnvideo_controller_icon_padding" | |
| 27 | + android:src="@drawable/cnvideo_ic_action_autorenew" /> | |
| 28 | + | |
| 29 | + <Space | |
| 30 | + android:layout_width="0dp" | |
| 31 | + android:layout_height="match_parent" | |
| 32 | + android:layout_weight="1"/> | |
| 33 | + | |
| 34 | + <ImageView | |
| 35 | + android:id="@+id/fullscreen" | |
| 36 | + android:layout_width="@dimen/cnvideo_controller_height" | |
| 37 | + android:layout_height="@dimen/cnvideo_controller_height" | |
| 38 | + android:padding="@dimen/cnvideo_controller_icon_padding" | |
| 39 | + android:src="@drawable/cnvideo_selector_full_screen_button" /> | |
| 40 | + </LinearLayout> | |
| 41 | + | |
| 42 | +</FrameLayout> | |
| 0 | 43 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/layout/cnvideo_layout_prepare_view.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + xmlns:tools="http://schemas.android.com/tools" | |
| 4 | + android:layout_width="match_parent" | |
| 5 | + android:layout_height="match_parent"> | |
| 6 | + | |
| 7 | + <ImageView | |
| 8 | + android:id="@+id/thumb" | |
| 9 | + android:layout_width="match_parent" | |
| 10 | + android:layout_height="match_parent" | |
| 11 | + android:scaleType="centerCrop" /> | |
| 12 | + | |
| 13 | + <ImageView | |
| 14 | + android:id="@+id/start_play" | |
| 15 | + android:layout_width="@dimen/cnvideo_play_btn_size" | |
| 16 | + android:layout_height="@dimen/cnvideo_play_btn_size" | |
| 17 | + android:layout_gravity="center" | |
| 18 | + android:background="@drawable/cnvideo_shape_play_bg" | |
| 19 | + android:padding="@dimen/cnvideo_default_spacing" | |
| 20 | + android:src="@drawable/cnvideo_selector_play_button" /> | |
| 21 | + | |
| 22 | + <ProgressBar | |
| 23 | + android:id="@+id/loading" | |
| 24 | + android:layout_width="@dimen/cnvideo_play_btn_size" | |
| 25 | + android:layout_height="@dimen/cnvideo_play_btn_size" | |
| 26 | + android:layout_gravity="center" | |
| 27 | + android:indeterminateDrawable="@drawable/cnvideo_progress_loading" | |
| 28 | + android:indeterminateDuration="2000" | |
| 29 | + android:visibility="gone" | |
| 30 | + tools:visibility="visible" /> | |
| 31 | + | |
| 32 | + <FrameLayout | |
| 33 | + android:id="@+id/net_warning_layout" | |
| 34 | + android:layout_width="match_parent" | |
| 35 | + android:layout_height="match_parent" | |
| 36 | + android:background="@android:color/black" | |
| 37 | + android:clickable="true" | |
| 38 | + android:focusable="true" | |
| 39 | + android:visibility="gone"> | |
| 40 | + | |
| 41 | + <LinearLayout | |
| 42 | + android:layout_width="match_parent" | |
| 43 | + android:layout_height="match_parent" | |
| 44 | + android:gravity="center" | |
| 45 | + android:orientation="vertical"> | |
| 46 | + | |
| 47 | + <TextView | |
| 48 | + android:id="@+id/message" | |
| 49 | + android:layout_width="wrap_content" | |
| 50 | + android:layout_height="wrap_content" | |
| 51 | + android:gravity="center" | |
| 52 | + android:text="@string/cnvideo_wifi_tip" | |
| 53 | + android:textColor="@android:color/white" /> | |
| 54 | + | |
| 55 | + <TextView | |
| 56 | + android:id="@+id/status_btn" | |
| 57 | + android:layout_width="wrap_content" | |
| 58 | + android:layout_height="wrap_content" | |
| 59 | + android:layout_marginTop="16dp" | |
| 60 | + android:background="@drawable/cnvideo_shape_status_view_btn" | |
| 61 | + android:gravity="center" | |
| 62 | + android:paddingLeft="16dp" | |
| 63 | + android:paddingTop="4dp" | |
| 64 | + android:paddingRight="16dp" | |
| 65 | + android:paddingBottom="4dp" | |
| 66 | + android:text="@string/cnvideo_continue_play" | |
| 67 | + android:textColor="@android:color/white" /> | |
| 68 | + | |
| 69 | + </LinearLayout> | |
| 70 | + </FrameLayout> | |
| 71 | + | |
| 72 | +</FrameLayout> | |
| 0 | 73 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/layout/cnvideo_layout_standard_controller.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + xmlns:tools="http://schemas.android.com/tools" | |
| 4 | + android:layout_width="match_parent" | |
| 5 | + android:layout_height="match_parent"> | |
| 6 | + | |
| 7 | + <ImageView | |
| 8 | + android:id="@+id/lock" | |
| 9 | + android:layout_width="40dp" | |
| 10 | + android:layout_height="40dp" | |
| 11 | + android:layout_gravity="center|start" | |
| 12 | + android:layout_margin="24dp" | |
| 13 | + android:background="@drawable/cnvideo_shape_back_bg" | |
| 14 | + android:padding="@dimen/cnvideo_default_spacing" | |
| 15 | + android:src="@drawable/cnvideo_selector_lock_button" | |
| 16 | + android:visibility="gone" | |
| 17 | + tools:visibility="visible" /> | |
| 18 | + | |
| 19 | + <ProgressBar | |
| 20 | + android:id="@+id/loading" | |
| 21 | + android:layout_width="@dimen/cnvideo_play_btn_size" | |
| 22 | + android:layout_height="@dimen/cnvideo_play_btn_size" | |
| 23 | + android:layout_gravity="center" | |
| 24 | + android:indeterminateDrawable="@drawable/cnvideo_progress_loading" | |
| 25 | + android:indeterminateDuration="2000" | |
| 26 | + android:visibility="gone" | |
| 27 | + tools:visibility="visible" /> | |
| 28 | + | |
| 29 | +</FrameLayout> | |
| 0 | 30 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/layout/cnvideo_layout_title_view.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + xmlns:tools="http://schemas.android.com/tools" | |
| 4 | + android:layout_width="match_parent" | |
| 5 | + android:layout_height="match_parent" | |
| 6 | + android:orientation="vertical"> | |
| 7 | + | |
| 8 | + <LinearLayout | |
| 9 | + android:id="@+id/title_container" | |
| 10 | + android:layout_width="match_parent" | |
| 11 | + android:layout_height="@dimen/cnvideo_controller_height" | |
| 12 | + android:background="@drawable/cnvideo_shape_standard_controller_top_bg" | |
| 13 | + android:gravity="center_vertical" | |
| 14 | + android:orientation="horizontal"> | |
| 15 | + | |
| 16 | + <ImageView | |
| 17 | + android:id="@+id/back" | |
| 18 | + android:layout_width="@dimen/cnvideo_controller_height" | |
| 19 | + android:layout_height="@dimen/cnvideo_controller_height" | |
| 20 | + android:layout_gravity="center" | |
| 21 | + android:padding="@dimen/cnvideo_controller_icon_padding" | |
| 22 | + android:src="@drawable/cnvideo_ic_action_arrow_back" /> | |
| 23 | + | |
| 24 | + <com.libs.video.video.exoplayer.component.MarqueeTextView | |
| 25 | + android:id="@+id/title" | |
| 26 | + android:layout_width="0dp" | |
| 27 | + android:layout_height="wrap_content" | |
| 28 | + android:layout_weight="1" | |
| 29 | + android:ellipsize="marquee" | |
| 30 | + android:focusable="true" | |
| 31 | + android:focusableInTouchMode="true" | |
| 32 | + android:marqueeRepeatLimit="marquee_forever" | |
| 33 | + android:paddingLeft="6dp" | |
| 34 | + android:paddingRight="6dp" | |
| 35 | + android:singleLine="true" | |
| 36 | + android:textColor="@android:color/white" | |
| 37 | + android:textSize="@dimen/cnvideo_controller_text_size" | |
| 38 | + tools:text="这是一个标题" /> | |
| 39 | + | |
| 40 | + <ImageView | |
| 41 | + android:id="@+id/iv_battery" | |
| 42 | + android:layout_width="26dp" | |
| 43 | + android:layout_height="20dp" | |
| 44 | + android:src="@drawable/cnvideo_battery_level" /> | |
| 45 | + | |
| 46 | + <TextView | |
| 47 | + android:id="@+id/sys_time" | |
| 48 | + android:layout_width="wrap_content" | |
| 49 | + android:layout_height="wrap_content" | |
| 50 | + android:layout_marginEnd="14dp" | |
| 51 | + android:layout_marginRight="14dp" | |
| 52 | + android:textColor="@android:color/white" | |
| 53 | + tools:text="12:00" /> | |
| 54 | + | |
| 55 | + </LinearLayout> | |
| 56 | + | |
| 57 | +</FrameLayout> | |
| 0 | 58 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/layout/cnvideo_layout_vod_control_view.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="match_parent" | |
| 5 | + xmlns:tools="http://schemas.android.com/tools"> | |
| 6 | + | |
| 7 | + <LinearLayout | |
| 8 | + android:id="@+id/bottom_container" | |
| 9 | + android:layout_width="match_parent" | |
| 10 | + android:layout_height="@dimen/cnvideo_controller_height" | |
| 11 | + android:layout_gravity="bottom" | |
| 12 | + android:gravity="center_vertical" | |
| 13 | + android:background="@drawable/cnvideo_shape_stardard_controller_bottom_bg" | |
| 14 | + android:orientation="horizontal"> | |
| 15 | + | |
| 16 | + <ImageView | |
| 17 | + android:id="@+id/iv_play" | |
| 18 | + android:layout_width="@dimen/cnvideo_controller_height" | |
| 19 | + android:layout_height="@dimen/cnvideo_controller_height" | |
| 20 | + android:padding="@dimen/cnvideo_controller_icon_padding" | |
| 21 | + android:src="@drawable/cnvideo_selector_play_button" /> | |
| 22 | + | |
| 23 | + <TextView | |
| 24 | + android:id="@+id/curr_time" | |
| 25 | + android:layout_width="wrap_content" | |
| 26 | + android:layout_height="wrap_content" | |
| 27 | + android:textColor="@android:color/white" | |
| 28 | + android:textSize="@dimen/cnvideo_controller_time_text_size" | |
| 29 | + tools:text="00:00" /> | |
| 30 | + | |
| 31 | + <SeekBar | |
| 32 | + android:id="@+id/seekBar" | |
| 33 | + android:layout_width="0dp" | |
| 34 | + android:layout_height="40dp" | |
| 35 | + android:layout_gravity="center_vertical" | |
| 36 | + android:layout_marginEnd="10dp" | |
| 37 | + android:layout_marginStart="10dp" | |
| 38 | + android:layout_weight="1" | |
| 39 | + android:background="@null" | |
| 40 | + android:max="1000" | |
| 41 | + android:maxHeight="2dp" | |
| 42 | + android:minHeight="2dp" | |
| 43 | + android:paddingEnd="0dp" | |
| 44 | + android:paddingStart="0dp" | |
| 45 | + android:progressDrawable="@drawable/cnvideo_layer_progress_bar" | |
| 46 | + android:thumb="@drawable/cnvideo_seekbar_thumb" | |
| 47 | + android:thumbOffset="0dp" /> | |
| 48 | + | |
| 49 | + <TextView | |
| 50 | + android:id="@+id/total_time" | |
| 51 | + android:layout_width="wrap_content" | |
| 52 | + android:layout_height="wrap_content" | |
| 53 | + android:textColor="@android:color/white" | |
| 54 | + android:textSize="@dimen/cnvideo_controller_time_text_size" | |
| 55 | + tools:text="00:00" /> | |
| 56 | + | |
| 57 | + <ImageView | |
| 58 | + android:id="@+id/fullscreen" | |
| 59 | + android:layout_width="@dimen/cnvideo_controller_height" | |
| 60 | + android:layout_height="@dimen/cnvideo_controller_height" | |
| 61 | + android:padding="@dimen/cnvideo_controller_icon_padding" | |
| 62 | + android:src="@drawable/cnvideo_selector_full_screen_button" /> | |
| 63 | + </LinearLayout> | |
| 64 | + | |
| 65 | + <ProgressBar | |
| 66 | + android:id="@+id/bottom_progress" | |
| 67 | + style="?android:attr/progressBarStyleHorizontal" | |
| 68 | + android:layout_width="match_parent" | |
| 69 | + android:layout_height="1dp" | |
| 70 | + android:layout_gravity="bottom" | |
| 71 | + android:max="1000" | |
| 72 | + android:progressDrawable="@drawable/cnvideo_layer_progress_bar" | |
| 73 | + android:visibility="gone" /> | |
| 74 | + | |
| 75 | +</FrameLayout> | |
| 0 | 76 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/layout/layout_ad_control_view.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + xmlns:tools="http://schemas.android.com/tools" | |
| 4 | + android:layout_width="match_parent" | |
| 5 | + android:layout_height="match_parent"> | |
| 6 | + | |
| 7 | + <RelativeLayout | |
| 8 | + android:layout_width="match_parent" | |
| 9 | + android:layout_height="wrap_content"> | |
| 10 | + | |
| 11 | + <ImageView | |
| 12 | + android:id="@+id/back" | |
| 13 | + style="@style/adIcon" | |
| 14 | + android:src="@drawable/cnvideo_ic_action_arrow_back" /> | |
| 15 | + | |
| 16 | + <TextView | |
| 17 | + android:id="@+id/ad_time" | |
| 18 | + style="@style/adText" | |
| 19 | + android:layout_alignParentEnd="true" | |
| 20 | + android:layout_alignParentRight="true" | |
| 21 | + android:layout_centerVertical="true" | |
| 22 | + tools:text="2s|跳过" /> | |
| 23 | + </RelativeLayout> | |
| 24 | + | |
| 25 | + <LinearLayout | |
| 26 | + android:layout_width="match_parent" | |
| 27 | + android:layout_height="@dimen/controller_height" | |
| 28 | + android:layout_gravity="bottom|end" | |
| 29 | + android:gravity="end"> | |
| 30 | + | |
| 31 | + <ImageView | |
| 32 | + android:id="@+id/iv_play" | |
| 33 | + style="@style/adIcon" | |
| 34 | + android:src="@drawable/cnvideo_selector_play_button" /> | |
| 35 | + | |
| 36 | + <View | |
| 37 | + android:layout_width="0dp" | |
| 38 | + android:layout_height="match_parent" | |
| 39 | + android:layout_weight="1" /> | |
| 40 | + | |
| 41 | + <TextView | |
| 42 | + android:id="@+id/ad_detail" | |
| 43 | + style="@style/adText" | |
| 44 | + tools:text="了解详情>" /> | |
| 45 | + | |
| 46 | + <ImageView | |
| 47 | + android:id="@+id/iv_volume" | |
| 48 | + style="@style/adIcon" | |
| 49 | + android:src="@drawable/cnvideo_ic_action_volume_off" /> | |
| 50 | + | |
| 51 | + <ImageView | |
| 52 | + android:id="@+id/fullscreen" | |
| 53 | + style="@style/adIcon" | |
| 54 | + android:src="@drawable/cnvideo_selector_full_screen_button" /> | |
| 55 | + </LinearLayout> | |
| 56 | +</FrameLayout> | |
| 0 | 57 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/values/attrs.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<resources> | |
| 3 | + <declare-styleable name="CNVideoView"> | |
| 4 | + <attr name="looping" format="boolean"/> | |
| 5 | + <attr name="enableAudioFocus" format="boolean"/> | |
| 6 | + <attr name="screenScaleType" format="dimension"> | |
| 7 | + <enum name="type_default" value="0"/> | |
| 8 | + <enum name="type_16_9" value="1"/> | |
| 9 | + <enum name="type_4_3" value="2"/> | |
| 10 | + <enum name="type_match_parent" value="3"/> | |
| 11 | + <enum name="type_original" value="4"/> | |
| 12 | + <enum name="type_center_crop" value="5"/> | |
| 13 | + </attr> | |
| 14 | + <attr name="playerBackgroundColor" format="color"/> | |
| 15 | + </declare-styleable> | |
| 16 | +</resources> | |
| 0 | 17 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/values/colors.xml
| 1 | 1 | <?xml version="1.0" encoding="utf-8"?> |
| 2 | 2 | <resources> |
| 3 | 3 | <color name="cnvideo_color_btn_error">#505050</color> |
| 4 | - | |
| 4 | + <color name="cnvideo_background_color">#4D000000</color> | |
| 5 | + <color name="cnvideo_theme_color">#39C5BB</color> | |
| 6 | + <color name="cnvideo_theme_color_translucent">#9939C5BB</color> | |
| 5 | 7 | </resources> |
| 6 | 8 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/values/dimens.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<resources> | |
| 3 | + <dimen name="controller_height">46dp</dimen> | |
| 4 | + <dimen name="controller_icon_padding">12dp</dimen> | |
| 5 | + <dimen name="controller_text_size">16sp</dimen> | |
| 6 | + <dimen name="default_spacing">16dp</dimen> | |
| 7 | + <dimen name="controller_seekbar_size_n">14dp</dimen> | |
| 8 | + <dimen name="controller_seekbar_size_s">14dp</dimen> | |
| 9 | + <dimen name="controller_seekbar_max_size">1dp</dimen> | |
| 10 | + <dimen name="player_play_btn_size">50dp</dimen> | |
| 11 | + <dimen name="controller_time_text_size">14sp</dimen> | |
| 12 | + <dimen name="player_default_spacing">10dp</dimen> | |
| 13 | + | |
| 14 | + | |
| 15 | + <!-- ui--> | |
| 16 | + <dimen name="cnvideo_controller_height">46dp</dimen> | |
| 17 | + <dimen name="cnvideo_controller_icon_padding">12dp</dimen> | |
| 18 | + <dimen name="cnvideo_controller_text_size">16sp</dimen> | |
| 19 | + <dimen name="cnvideo_default_spacing">10dp</dimen> | |
| 20 | + <dimen name="cnvideo_controller_seekbar_size_n">14dp</dimen> | |
| 21 | + <dimen name="cnvideo_controller_seekbar_size_s">14dp</dimen> | |
| 22 | + <dimen name="cnvideo_controller_seekbar_max_size">1dp</dimen> | |
| 23 | + <dimen name="cnvideo_play_btn_size">50dp</dimen> | |
| 24 | + <dimen name="cnvideo_controller_time_text_size">14sp</dimen> | |
| 25 | +</resources> | |
| 0 | 26 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/values/strings.xml
| 1 | 1 | <resources> |
| 2 | 2 | <string name="app_name">video</string> |
| 3 | + | |
| 4 | + <string name="cnvideo_replay">重新播放</string> | |
| 5 | + <string name="cnvideo_lock_tip">请先解锁屏幕!</string> | |
| 6 | + <string name="cnvideo_unlocked">已解锁</string> | |
| 7 | + <string name="cnvideo_locked">已锁定</string> | |
| 8 | + <string name="cnvideo_error_message">出了点小问题,请稍后重试</string> | |
| 9 | + <string name="cnvideo_retry"> 重 试 </string> | |
| 10 | + <string name="cnvideo_continue_play">继续播放</string> | |
| 11 | + <string name="cnvideo_wifi_tip">您正在使用移动网络,继续播放将消耗流量</string> | |
| 3 | 12 | </resources> | ... | ... |
cloud/video/src/main/res/values/styles.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<resources> | |
| 3 | + <style name="adIcon"> | |
| 4 | + <item name="android:padding">6dp</item> | |
| 5 | + <item name="android:layout_width">30dp</item> | |
| 6 | + <item name="android:layout_height">30dp</item> | |
| 7 | + <item name="android:background">@drawable/cnvideo_shape_back_bg</item> | |
| 8 | + <item name="android:layout_margin">5dp</item> | |
| 9 | + </style> | |
| 10 | + | |
| 11 | + <style name="adText"> | |
| 12 | + <item name="android:background">@drawable/cnvideo_shape_ad_bg</item> | |
| 13 | + <item name="android:textSize">@dimen/controller_text_size</item> | |
| 14 | + <item name="android:textColor">@android:color/white</item> | |
| 15 | + <item name="android:padding">5dp</item> | |
| 16 | + <item name="android:layout_width">wrap_content</item> | |
| 17 | + <item name="android:layout_height">30dp</item> | |
| 18 | + <item name="android:layout_margin">5dp</item> | |
| 19 | + <item name="android:gravity">center</item> | |
| 20 | + </style> | |
| 21 | +</resources> | |
| 0 | 22 | \ No newline at end of file | ... | ... |
module/shanghaishop/build.gradle
| ... | ... | @@ -17,13 +17,11 @@ dependencies { |
| 17 | 17 | testImplementation 'junit:junit:4.12' |
| 18 | 18 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' |
| 19 | 19 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' |
| 20 | - api('cn.cnlive.b2c:cnliveb2c:1.0.0-20191217.063321-17') { | |
| 21 | - exclude group: 'com.github.bumptech.glide' | |
| 22 | - } | |
| 23 | -// api 'cn.cnlive.b2c:cnliveb2c:1.0.0-20191217.063321-17' | |
| 24 | - | |
| 25 | - api 'cn.cnlive.b2c:nicevideoplayer:1.0.0-20191206.090854-2' | |
| 26 | - api 'cn.cnlive.b2c:udesk-sdk-ui:1.0.0' | |
| 20 | +// api('cn.cnlive.b2c:cnliveb2c:1.0.0-20191217.063321-17') { | |
| 21 | +// exclude group: 'com.github.bumptech.glide' | |
| 22 | +// } | |
| 23 | +// api 'cn.cnlive.b2c:nicevideoplayer:1.0.0-20191206.090854-2' | |
| 24 | +// api 'cn.cnlive.b2c:udesk-sdk-ui:1.0.0' | |
| 27 | 25 | |
| 28 | 26 | |
| 29 | 27 | /** 圆形图片处理 androidx **/ | ... | ... |
module/shopcity/src/main/java/com/clive/happychina/shopcity/ShopServiceImpl.java
| ... | ... | @@ -83,8 +83,8 @@ public class ShopServiceImpl implements ShopService { |
| 83 | 83 | @Override |
| 84 | 84 | public void jumpToHQYWShopHomeActivity(@NotNull Activity activity) { |
| 85 | 85 | //跳转环球优物 ---商城首页 |
| 86 | - Intent intent = new Intent(activity, cn.cnlive.b2c.MainActivity.class); | |
| 87 | - activity.startActivity(intent); | |
| 86 | +// Intent intent = new Intent(activity, cn.cnlive.b2c.MainActivity.class); | |
| 87 | +// activity.startActivity(intent); | |
| 88 | 88 | } |
| 89 | 89 | |
| 90 | 90 | @Override | ... | ... |