Commit eb47ac608b5e6dc7c812a7975fe42badde9be6a1

Authored by 杨帅
1 parent beebfcb8

1 封装视频库

Showing 31 changed files with 3769 additions and 148 deletions

Too many changes to show.

To preserve performance only 31 of 124 files are displayed.

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
... ... @@ -262,7 +262,7 @@ public class LoginDialogPresenter extends BasePresenter&lt;LoginDialogView&gt; {
262 262 /**
263 263 * 销毁
264 264 */
265   - public void destroy() {
  265 + public void onDestroy() {
266 266 if (timerUtil != null) {
267 267 timerUtil.cancel();
268 268 }
... ...
cloud/user/src/main/java/com/cnlive/cloud/user/frame/presenter/QuickLoginPresenter.java
... ... @@ -322,6 +322,9 @@ public class QuickLoginPresenter extends BasePresenter&lt;QuickLoginView&gt; {
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&lt;QuickLoginView&gt; {
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&lt;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&lt;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
1 1 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2 2 package="com.cnlive.cloud.video">
  3 +
  4 + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
3 5 <application>
4 6 </application>
5 7  
... ...
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
... ...