Commit eb47ac608b5e6dc7c812a7975fe42badde9be6a1

Authored by 杨帅
1 parent beebfcb8

1 封装视频库

Showing 124 changed files with 7600 additions and 259 deletions
app/strike/src/main/java/com/cnlive/strike/demo/activity/CNplayerActivity.java
1 package com.cnlive.strike.demo.activity; 1 package com.cnlive.strike.demo.activity;
2 2
3 import android.content.Intent; 3 import android.content.Intent;
4 -import android.os.Handler;  
5 -import android.widget.Toast;  
6 4
7 -import androidx.annotation.Nullable;  
8 import androidx.databinding.ViewDataBinding; 5 import androidx.databinding.ViewDataBinding;
9 6
10 import com.alibaba.android.arouter.facade.annotation.Route; 7 import com.alibaba.android.arouter.facade.annotation.Route;
11 import com.cnlive.cloud.BuildConfig; 8 import com.cnlive.cloud.BuildConfig;
12 import com.cnlive.cloud.R; 9 import com.cnlive.cloud.R;
13 import com.cnlive.cloud.databinding.ActivityCnvideoBinding; 10 import com.cnlive.cloud.databinding.ActivityCnvideoBinding;
14 -import com.cnlive.cloud.user.auth.UserService;  
15 import com.cnlive.core.libs.base.frame.activity.BaseActivity; 11 import com.cnlive.core.libs.base.frame.activity.BaseActivity;
16 -import com.cnlive.core.libs.base.util.AlertUtil;  
17 import com.cnlive.core.libs.base.util.ChannelUtil; 12 import com.cnlive.core.libs.base.util.ChannelUtil;
18 import com.cnlive.core.libs.base.util.QMUITipDialogUtil; 13 import com.cnlive.core.libs.base.util.QMUITipDialogUtil;
19 import com.libs.video.util.Config; 14 import com.libs.video.util.Config;
20 -import com.libs.video.util.ProbeUtil;  
21 -import com.libs.video.video.VideoView;  
22 import com.libs.video.video.base.IMediaPlayer; 15 import com.libs.video.video.base.IMediaPlayer;
23 16
24 -import org.greenrobot.eventbus.Subscribe;  
25 -import org.greenrobot.eventbus.ThreadMode;  
26 -  
27 @Route(path = CNplayerActivity.PATH) 17 @Route(path = CNplayerActivity.PATH)
28 public class CNplayerActivity extends BaseActivity implements IMediaPlayer.OnPreparedListener, 18 public class CNplayerActivity extends BaseActivity implements IMediaPlayer.OnPreparedListener,
29 IMediaPlayer.OnCompletionListener, IMediaPlayer.OnErrorListener, IMediaPlayer.OnInfoListener, 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,32 +3,31 @@ package com.cnlive.strike.demo.activity;
3 import android.net.Uri; 3 import android.net.Uri;
4 import android.view.Surface; 4 import android.view.Surface;
5 import android.view.SurfaceControl; 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 import androidx.databinding.ViewDataBinding; 8 import androidx.databinding.ViewDataBinding;
12 9
  10 +import com.cnlive.cloud.BuildConfig;
13 import com.cnlive.cloud.R; 11 import com.cnlive.cloud.R;
14 import com.cnlive.cloud.databinding.ActivityExplayerBinding; 12 import com.cnlive.cloud.databinding.ActivityExplayerBinding;
15 import com.cnlive.core.libs.base.frame.activity.BaseActivity; 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 import com.google.android.exoplayer2.Player; 14 import com.google.android.exoplayer2.Player;
20 import com.google.android.exoplayer2.SimpleExoPlayer; 15 import com.google.android.exoplayer2.SimpleExoPlayer;
21 -import com.google.android.exoplayer2.Timeline;  
22 -import com.google.android.exoplayer2.source.TrackGroupArray;  
23 import com.google.android.exoplayer2.source.hls.HlsMediaSource; 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 import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; 17 import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
27 import com.google.android.exoplayer2.util.Util; 18 import com.google.android.exoplayer2.util.Util;
28 import com.google.android.exoplayer2.video.VideoListener; 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 public class ExoPlayerActivity extends BaseActivity implements VideoListener, Player.EventListener { 32 public class ExoPlayerActivity extends BaseActivity implements VideoListener, Player.EventListener {
34 ActivityExplayerBinding binding; 33 ActivityExplayerBinding binding;
@@ -37,6 +36,9 @@ public class ExoPlayerActivity extends BaseActivity implements VideoListener, Pl @@ -37,6 +36,9 @@ public class ExoPlayerActivity extends BaseActivity implements VideoListener, Pl
37 SurfaceControl surfaceControl; 36 SurfaceControl surfaceControl;
38 Surface videoSurface; 37 Surface videoSurface;
39 private VideoPlayer videoPlayer; 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 @Override 43 @Override
42 protected int getBindLayoutId() { 44 protected int getBindLayoutId() {
@@ -51,9 +53,6 @@ public class ExoPlayerActivity extends BaseActivity implements VideoListener, Pl @@ -51,9 +53,6 @@ public class ExoPlayerActivity extends BaseActivity implements VideoListener, Pl
51 @Override 53 @Override
52 protected void initView() { 54 protected void initView() {
53 initVideoPlayer(); 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 DefaultHttpDataSourceFactory dataSourceFactory = 56 DefaultHttpDataSourceFactory dataSourceFactory =
58 new DefaultHttpDataSourceFactory(Util.getUserAgent(context, "app-name")); 57 new DefaultHttpDataSourceFactory(Util.getUserAgent(context, "app-name"));
59 58
@@ -76,50 +75,99 @@ public class ExoPlayerActivity extends BaseActivity implements VideoListener, Pl @@ -76,50 +75,99 @@ public class ExoPlayerActivity extends BaseActivity implements VideoListener, Pl
76 // player.addListener(this); 75 // player.addListener(this);
77 // // Prepare the player with the source. 76 // // Prepare the player with the source.
78 player.prepare(hlsMediaSource); 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 // @Override 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 private void initVideoPlayer() { 169 private void initVideoPlayer() {
122 - binding.playerView.setCustomControllView(new MyTestControlView(context)); 170 +// binding.playerView.setCustomControllView(new MyTestControlView(context));
123 // Uri dashUri = Uri.parse("rtmp://tyzg.rtmps2.cnlive.com/live-tyzg-577/577_999"); 171 // Uri dashUri = Uri.parse("rtmp://tyzg.rtmps2.cnlive.com/live-tyzg-577/577_999");
124 172
125 // Uri mp4VideoUri = Uri.parse("http://ffyl.vods2.cnlive.com/223/vod/2018/1120/223_88c98ab1395f4762b703fbdd9d59d2e5/ff80808166c2a45201672f14a9933131_800.m3u8"); 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,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,9 +6,9 @@
6 android:layout_height="match_parent" 6 android:layout_height="match_parent"
7 android:orientation="vertical"> 7 android:orientation="vertical">
8 8
9 - <com.libs.video.video.VideoView 9 + <com.libs.video.video.test.VideoView
10 android:id="@+id/videoview" 10 android:id="@+id/videoview"
11 android:layout_width="match_parent" 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 </LinearLayout> 13 </LinearLayout>
14 </layout> 14 </layout>
15 \ No newline at end of file 15 \ No newline at end of file
app/strike/src/main/res/layout/activity_explayer.xml
@@ -7,14 +7,18 @@ @@ -7,14 +7,18 @@
7 android:background="@color/black" 7 android:background="@color/black"
8 android:orientation="vertical"> 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 android:id="@+id/playerView" 16 android:id="@+id/playerView"
12 android:layout_width="match_parent" 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 </LinearLayout> 23 </LinearLayout>
20 </layout> 24 </layout>
21 \ No newline at end of file 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,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 if (timerUtil != null) { 266 if (timerUtil != null) {
267 timerUtil.cancel(); 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,6 +322,9 @@ public class QuickLoginPresenter extends BasePresenter&lt;QuickLoginView&gt; {
322 // }); 322 // });
323 // } 323 // }
324 public void cancelLoad() { 324 public void cancelLoad() {
  325 + if (timerUtil != null) {
  326 + timerUtil.cancel();
  327 + }
325 if (event != null) { 328 if (event != null) {
326 if (!event.isDisposed()) { 329 if (!event.isDisposed()) {
327 event.dispose(); 330 event.dispose();
@@ -336,4 +339,5 @@ public class QuickLoginPresenter extends BasePresenter&lt;QuickLoginView&gt; { @@ -336,4 +339,5 @@ public class QuickLoginPresenter extends BasePresenter&lt;QuickLoginView&gt; {
336 otherEvent = null; 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,7 +223,7 @@ public class LoginDialogActivity extends BaseActivity&lt;LoginDialogView, LoginDial
223 public void onDestroy() { 223 public void onDestroy() {
224 super.onDestroy(); 224 super.onDestroy();
225 if (getMvpView() != null) getMvpView().hideProgress(); 225 if (getMvpView() != null) getMvpView().hideProgress();
226 - if (getPresenter() != null) getPresenter().destroy(); 226 + if (getPresenter() != null) getPresenter().onDestroy();
227 227
228 WechatLoginObservable.getInstance().deleteObserver(this); 228 WechatLoginObservable.getInstance().deleteObserver(this);
229 ThirdLoginObservable.getInstance().deleteObserver(this); 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,7 +11,6 @@ import androidx.databinding.ViewDataBinding;
11 11
12 import com.cnlive.cloud.user.databinding.FragmentQuickLoginBinding; 12 import com.cnlive.cloud.user.databinding.FragmentQuickLoginBinding;
13 import com.cnlive.cloud.user.frame.presenter.QuickLoginPresenter; 13 import com.cnlive.cloud.user.frame.presenter.QuickLoginPresenter;
14 -import com.cnlive.cloud.user.ui.activity.LoginDialogActivity;  
15 import com.cnlive.core.libs.base.application.AppConfig; 14 import com.cnlive.core.libs.base.application.AppConfig;
16 import com.cnlive.core.libs.base.frame.fragment.BaseFragment; 15 import com.cnlive.core.libs.base.frame.fragment.BaseFragment;
17 import com.cnlive.core.libs.base.util.DoublePressed; 16 import com.cnlive.core.libs.base.util.DoublePressed;
@@ -28,7 +27,6 @@ import com.cnlive.login.model.ThirdLoginInfo; @@ -28,7 +27,6 @@ import com.cnlive.login.model.ThirdLoginInfo;
28 import com.cnlive.login.observer.ThirdLoginObservable; 27 import com.cnlive.login.observer.ThirdLoginObservable;
29 import com.cnlive.login.observer.WechatLoginObservable; 28 import com.cnlive.login.observer.WechatLoginObservable;
30 import com.cnlive.login.utils.ThirdLoginUtil; 29 import com.cnlive.login.utils.ThirdLoginUtil;
31 -import com.cnlive.share.BuildConfig;  
32 import com.cnlive.share.data.WeChatInfo; 30 import com.cnlive.share.data.WeChatInfo;
33 import com.cnlive.share.util.Util; 31 import com.cnlive.share.util.Util;
34 32
@@ -238,7 +236,7 @@ public class QuickLoginFragment extends BaseFragment&lt;QuickLoginView, QuickLoginP @@ -238,7 +236,7 @@ public class QuickLoginFragment extends BaseFragment&lt;QuickLoginView, QuickLoginP
238 public void onDestroy() { 236 public void onDestroy() {
239 super.onDestroy(); 237 super.onDestroy();
240 if (getMvpView() != null) getMvpView().hideProgress(); 238 if (getMvpView() != null) getMvpView().hideProgress();
241 - if (getPresenter() != null) getPresenter().destroy(); 239 + if (getPresenter() != null) getPresenter().cancelLoad();
242 WechatLoginObservable.getInstance().deleteObserver(this); 240 WechatLoginObservable.getInstance().deleteObserver(this);
243 ThirdLoginObservable.getInstance().deleteObserver(this); 241 ThirdLoginObservable.getInstance().deleteObserver(this);
244 } 242 }
cloud/video/src/main/AndroidManifest.xml
1 <manifest xmlns:android="http://schemas.android.com/apk/res/android" 1 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2 package="com.cnlive.cloud.video"> 2 package="com.cnlive.cloud.video">
  3 +
  4 + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
3 <application> 5 <application>
4 </application> 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 \ No newline at end of file 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 \ No newline at end of file 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 \ No newline at end of file 115 \ No newline at end of file
cloud/video/src/main/java/com/libs/video/video/exoplayer/play/CNVideoView.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.play;
  2 +
  3 +import android.app.Activity;
  4 +import android.content.ContentResolver;
  5 +import android.content.Context;
  6 +import android.content.res.AssetFileDescriptor;
  7 +import android.content.res.TypedArray;
  8 +import android.graphics.Bitmap;
  9 +import android.graphics.Color;
  10 +import android.net.Uri;
  11 +import android.os.Build;
  12 +import android.os.Parcelable;
  13 +import android.text.TextUtils;
  14 +import android.util.AttributeSet;
  15 +import android.view.Gravity;
  16 +import android.view.View;
  17 +import android.view.ViewGroup;
  18 +import android.view.WindowManager;
  19 +import android.widget.FrameLayout;
  20 +
  21 +import com.cnlive.cloud.video.R;
  22 +import com.libs.video.video.exoplayer.controller.BaseVideoController;
  23 +import com.libs.video.video.exoplayer.controller.MediaPlayerControl;
  24 +import com.libs.video.video.exoplayer.util.L;
  25 +import com.libs.video.video.exoplayer.util.PlayerUtils;
  26 +import com.libs.video.video.exoplayer.render.IRenderView;
  27 +import com.libs.video.video.exoplayer.render.RenderViewFactory;
  28 +
  29 +import java.io.IOException;
  30 +import java.util.ArrayList;
  31 +import java.util.List;
  32 +import java.util.Map;
  33 +
  34 +import androidx.annotation.NonNull;
  35 +import androidx.annotation.Nullable;
  36 +
  37 +/**
  38 + * 视频播放视图
  39 + */
  40 +
  41 +public class CNVideoView<P extends AbstractPlayer> extends FrameLayout
  42 + implements MediaPlayerControl, AbstractPlayer.PlayerEventListener {
  43 +
  44 + protected P mMediaPlayer;//播放器
  45 + protected PlayerFactory<P> mPlayerFactory;//工厂类,用于实例化播放核心
  46 + @Nullable
  47 + protected BaseVideoController mVideoController;//控制器
  48 +
  49 + /**
  50 + * 真正承载播放器视图的容器
  51 + */
  52 + protected FrameLayout mPlayerContainer;
  53 +
  54 + protected IRenderView mRenderView;//渲染视图
  55 + protected RenderViewFactory mRenderViewFactory;
  56 +
  57 + public static final int SCREEN_SCALE_DEFAULT = 0;//默认全屏
  58 + public static final int SCREEN_SCALE_16_9 = 1;//16:9
  59 + public static final int SCREEN_SCALE_4_3 = 2;//4:3
  60 + public static final int SCREEN_SCALE_MATCH_PARENT = 3;
  61 + public static final int SCREEN_SCALE_ORIGINAL = 4;
  62 + public static final int SCREEN_SCALE_CENTER_CROP = 5;
  63 + protected int mCurrentScreenScaleType;
  64 +
  65 + protected int[] mVideoSize = {0, 0};
  66 +
  67 + protected boolean mIsMute;//是否静音
  68 +
  69 + //--------- data sources ---------//
  70 + protected String mUrl;//当前播放视频的地址
  71 + protected Map<String, String> mHeaders;//当前视频地址的请求头
  72 + protected AssetFileDescriptor mAssetFileDescriptor;//assets文件
  73 +
  74 + protected long mCurrentPosition;//当前正在播放视频的位置
  75 +
  76 + //播放器的各种状态
  77 + public static final int STATE_ERROR = -1;//错误
  78 + public static final int STATE_IDLE = 0;//空闲
  79 + public static final int STATE_PREPARING = 1;//准备中
  80 + public static final int STATE_PREPARED = 2;//准备完成
  81 + public static final int STATE_PLAYING = 3;//播放中
  82 + public static final int STATE_PAUSED = 4;//暂停
  83 + public static final int STATE_PLAYBACK_COMPLETED = 5;
  84 + public static final int STATE_BUFFERING = 6;//正在缓冲
  85 + public static final int STATE_BUFFERED = 7;//缓冲完成
  86 + public static final int STATE_START_ABORT = 8;//开始播放中止
  87 + protected int mCurrentPlayState = STATE_IDLE;//当前播放器的状态
  88 +
  89 + public static final int PLAYER_NORMAL = 10; // 普通播放器
  90 + public static final int PLAYER_FULL_SCREEN = 11; // 全屏播放器
  91 + public static final int PLAYER_TINY_SCREEN = 12; // 小屏播放器
  92 + protected int mCurrentPlayerState = PLAYER_NORMAL;//当前播放状态
  93 +
  94 + protected boolean mIsFullScreen;//是否处于全屏状态
  95 +
  96 + protected boolean mIsTinyScreen;//是否处于小屏状态
  97 + protected int[] mTinyScreenSize = {0, 0};
  98 +
  99 + /**
  100 + * 监听系统中音频焦点改变,见{@link #setEnableAudioFocus(boolean)}
  101 + */
  102 + protected boolean mEnableAudioFocus;
  103 + @Nullable
  104 + protected AudioFocusHelper mAudioFocusHelper;
  105 +
  106 + /**
  107 + * OnStateChangeListener集合,保存了所有开发者设置的监听器
  108 + */
  109 + protected List<OnStateChangeListener> mOnStateChangeListeners;
  110 +
  111 + /**
  112 + * 进度管理器,设置之后播放器会记录播放进度,以便下次播放恢复进度
  113 + */
  114 + @Nullable
  115 + protected ProgressManager mProgressManager;
  116 +
  117 + /**
  118 + * 循环播放
  119 + */
  120 + protected boolean mIsLooping;
  121 +
  122 + /**
  123 + * {@link #mPlayerContainer}背景色,默认黑色
  124 + */
  125 + private int mPlayerBackgroundColor;
  126 +
  127 + private List<String> dataSource;
  128 + private int curentPlayIndex;
  129 +
  130 + public CNVideoView(@NonNull Context context) {
  131 + this(context, null);
  132 + }
  133 +
  134 + public CNVideoView(@NonNull Context context, @Nullable AttributeSet attrs) {
  135 + this(context, attrs, 0);
  136 + }
  137 +
  138 + public CNVideoView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
  139 + super(context, attrs, defStyleAttr);
  140 + dataSource = new ArrayList<>();
  141 + //读取全局配置
  142 + VideoViewConfig config = VideoViewManager.getConfig();
  143 + mEnableAudioFocus = config.mEnableAudioFocus;
  144 + mProgressManager = config.mProgressManager;
  145 + mPlayerFactory = config.mPlayerFactory;
  146 + mCurrentScreenScaleType = config.mScreenScaleType;
  147 + mRenderViewFactory = config.mRenderViewFactory;
  148 +
  149 + //读取xml中的配置,并综合全局配置
  150 + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CNVideoView);
  151 + mEnableAudioFocus = a.getBoolean(R.styleable.CNVideoView_enableAudioFocus, mEnableAudioFocus);
  152 + mIsLooping = a.getBoolean(R.styleable.CNVideoView_looping, false);
  153 + mCurrentScreenScaleType = a.getInt(R.styleable.CNVideoView_screenScaleType, mCurrentScreenScaleType);
  154 + mPlayerBackgroundColor = a.getColor(R.styleable.CNVideoView_playerBackgroundColor, Color.BLACK);
  155 + a.recycle();
  156 +
  157 + initView();
  158 + }
  159 +
  160 + /**
  161 + * 初始化播放器视图
  162 + */
  163 + protected void initView() {
  164 + mPlayerContainer = new FrameLayout(getContext());
  165 + mPlayerContainer.setBackgroundColor(mPlayerBackgroundColor);
  166 + LayoutParams params = new LayoutParams(
  167 + ViewGroup.LayoutParams.MATCH_PARENT,
  168 + ViewGroup.LayoutParams.MATCH_PARENT);
  169 + this.addView(mPlayerContainer, params);
  170 + }
  171 +
  172 + /**
  173 + * 设置{@link #mPlayerContainer}的背景色
  174 + */
  175 + public void setPlayerBackgroundColor(int color) {
  176 + mPlayerContainer.setBackgroundColor(color);
  177 + }
  178 +
  179 +
  180 + /**
  181 + * 当前媒体数量
  182 + *
  183 + * @return
  184 + */
  185 + @Override
  186 + public int getMediaCount() {
  187 + return dataSource.size();
  188 + }
  189 +
  190 + /**
  191 + * 开始播放,注意:调用此方法后必须调用{@link #release()}释放播放器,否则会导致内存泄漏
  192 + */
  193 + @Override
  194 + public void start() {
  195 + boolean isStarted = false;
  196 + if (isInIdleState() || isInStartAbortState()) {
  197 + isStarted = startPlay();
  198 + } else if (isInPlaybackState()) {
  199 + startInPlaybackState();
  200 + isStarted = true;
  201 + }
  202 + if (isStarted) {
  203 + mPlayerContainer.setKeepScreenOn(true);
  204 + if (mAudioFocusHelper != null)
  205 + mAudioFocusHelper.requestFocus();
  206 + }
  207 + }
  208 +
  209 + /**
  210 + * 第一次播放
  211 + *
  212 + * @return 是否成功开始播放
  213 + */
  214 + protected boolean startPlay() {
  215 + //如果要显示移动网络提示则不继续播放
  216 + if (showNetWarning()) {
  217 + //中止播放
  218 + setPlayState(STATE_START_ABORT);
  219 + return false;
  220 + }
  221 + //监听音频焦点改变
  222 + if (mEnableAudioFocus) {
  223 + mAudioFocusHelper = new AudioFocusHelper(this);
  224 + }
  225 + //读取播放进度
  226 + if (mProgressManager != null) {
  227 + mCurrentPosition = mProgressManager.getSavedProgress(mUrl);
  228 + }
  229 + //初始化播放器
  230 + initPlayer();
  231 + //添加渲染视图
  232 + addDisplay();
  233 + startPrepare(false);
  234 + return true;
  235 + }
  236 +
  237 + /**
  238 + * 是否显示移动网络提示,可在Controller中配置
  239 + */
  240 + protected boolean showNetWarning() {
  241 + //播放本地数据源时不检测网络
  242 + if (isLocalDataSource()) return false;
  243 + return mVideoController != null && mVideoController.showNetWarning();
  244 + }
  245 +
  246 + /**
  247 + * 判断是否为本地数据源,包括 本地文件、Asset、raw
  248 + */
  249 + protected boolean isLocalDataSource() {
  250 + if (mAssetFileDescriptor != null) {
  251 + return true;
  252 + } else if (!TextUtils.isEmpty(mUrl)) {
  253 + Uri uri = Uri.parse(mUrl);
  254 + return ContentResolver.SCHEME_ANDROID_RESOURCE.equals(uri.getScheme())
  255 + || ContentResolver.SCHEME_FILE.equals(uri.getScheme())
  256 + || "rawresource".equals(uri.getScheme());
  257 + }
  258 + return false;
  259 + }
  260 +
  261 + /**
  262 + * 初始化播放器
  263 + */
  264 + protected void initPlayer() {
  265 + mMediaPlayer = mPlayerFactory.createPlayer(getContext());
  266 + mMediaPlayer.setPlayerEventListener(this);
  267 + setInitOptions();
  268 + mMediaPlayer.initPlayer();
  269 + setOptions();
  270 + }
  271 +
  272 + /**
  273 + * 初始化之前的配置项
  274 + */
  275 + protected void setInitOptions() {
  276 + }
  277 +
  278 + /**
  279 + * 初始化之后的配置项
  280 + */
  281 + protected void setOptions() {
  282 + mMediaPlayer.setLooping(mIsLooping);
  283 + }
  284 +
  285 + /**
  286 + * 初始化视频渲染View
  287 + */
  288 + protected void addDisplay() {
  289 + //先移除之前的渲染视图
  290 + if (mRenderView != null) {
  291 + mPlayerContainer.removeView(mRenderView.getView());
  292 + mRenderView.release();
  293 + }
  294 + //创建新的渲染视图
  295 + mRenderView = mRenderViewFactory.createRenderView(getContext());
  296 + mRenderView.attachToPlayer(mMediaPlayer);
  297 + LayoutParams params = new LayoutParams(
  298 + ViewGroup.LayoutParams.MATCH_PARENT,
  299 + ViewGroup.LayoutParams.MATCH_PARENT,
  300 + Gravity.CENTER);
  301 + //添加渲染视图
  302 + mPlayerContainer.addView(mRenderView.getView(), 0, params);
  303 + }
  304 +
  305 + /**
  306 + * 开始准备播放(直接播放)
  307 + */
  308 + protected void startPrepare(boolean reset) {
  309 + if (reset) {
  310 + mMediaPlayer.reset();
  311 + //重新设置option,media player reset之后,option会失效
  312 + setOptions();
  313 + }
  314 + if (prepareDataSource()) {
  315 + mMediaPlayer.prepareAsync();
  316 + setPlayState(STATE_PREPARING);
  317 + setPlayerState(isFullScreen() ? PLAYER_FULL_SCREEN : isTinyScreen() ? PLAYER_TINY_SCREEN : PLAYER_NORMAL);
  318 + }
  319 + }
  320 +
  321 + /**
  322 + * 设置播放数据
  323 + *
  324 + * @return 播放数据是否设置成功
  325 + */
  326 + protected boolean prepareDataSource() {
  327 + if (mAssetFileDescriptor != null) {
  328 + mMediaPlayer.setDataSource(mAssetFileDescriptor);
  329 + return true;
  330 + } else if (!TextUtils.isEmpty(mUrl)) {
  331 + mMediaPlayer.setDataSource(mUrl, mHeaders);
  332 + return true;
  333 + }
  334 + return false;
  335 + }
  336 +
  337 + /**
  338 + * 播放状态下开始播放
  339 + */
  340 + protected void startInPlaybackState() {
  341 + mMediaPlayer.start();
  342 + setPlayState(STATE_PLAYING);
  343 + }
  344 +
  345 + /**
  346 + * 暂停播放
  347 + */
  348 + @Override
  349 + public void pause() {
  350 + if (isInPlaybackState()
  351 + && mMediaPlayer.isPlaying()) {
  352 + mMediaPlayer.pause();
  353 + setPlayState(STATE_PAUSED);
  354 + if (mAudioFocusHelper != null) {
  355 + mAudioFocusHelper.abandonFocus();
  356 + }
  357 + mPlayerContainer.setKeepScreenOn(false);
  358 + }
  359 + }
  360 +
  361 + /**
  362 + * 继续播放
  363 + */
  364 + public void resume() {
  365 + if (isInPlaybackState()
  366 + && !mMediaPlayer.isPlaying()) {
  367 + mMediaPlayer.start();
  368 + setPlayState(STATE_PLAYING);
  369 + if (mAudioFocusHelper != null) {
  370 + mAudioFocusHelper.requestFocus();
  371 + }
  372 + mPlayerContainer.setKeepScreenOn(true);
  373 + }
  374 + }
  375 +
  376 + /**
  377 + * 停止播放
  378 + *
  379 + * @deprecated 使用 {@link #release()} 代替
  380 + */
  381 + @Deprecated
  382 + public void stopPlayback() {
  383 + release();
  384 + }
  385 +
  386 +
  387 + /**
  388 + * 释放播放器
  389 + */
  390 + public void release() {
  391 + if (!isInIdleState()) {
  392 + //释放播放器
  393 + if (mMediaPlayer != null) {
  394 + mMediaPlayer.release();
  395 + mMediaPlayer = null;
  396 + }
  397 + //释放renderView
  398 + if (mRenderView != null) {
  399 + mPlayerContainer.removeView(mRenderView.getView());
  400 + mRenderView.release();
  401 + mRenderView = null;
  402 + }
  403 + //释放Assets资源
  404 + if (mAssetFileDescriptor != null) {
  405 + try {
  406 + mAssetFileDescriptor.close();
  407 + } catch (IOException e) {
  408 + e.printStackTrace();
  409 + }
  410 + }
  411 + //关闭AudioFocus监听
  412 + if (mAudioFocusHelper != null) {
  413 + mAudioFocusHelper.abandonFocus();
  414 + mAudioFocusHelper = null;
  415 + }
  416 + //关闭屏幕常亮
  417 + mPlayerContainer.setKeepScreenOn(false);
  418 + //保存播放进度
  419 + saveProgress();
  420 + //重置播放进度
  421 + mCurrentPosition = 0;
  422 + //切换转态
  423 + setPlayState(STATE_IDLE);
  424 + }
  425 + }
  426 +
  427 + /**
  428 + * 保存播放进度
  429 + */
  430 + protected void saveProgress() {
  431 + if (mProgressManager != null && mCurrentPosition > 0) {
  432 + L.d("saveProgress: " + mCurrentPosition);
  433 + mProgressManager.saveProgress(mUrl, mCurrentPosition);
  434 + }
  435 + }
  436 +
  437 + /**
  438 + * 是否处于播放状态
  439 + */
  440 + protected boolean isInPlaybackState() {
  441 + return mMediaPlayer != null
  442 + && mCurrentPlayState != STATE_ERROR
  443 + && mCurrentPlayState != STATE_IDLE
  444 + && mCurrentPlayState != STATE_PREPARING
  445 + && mCurrentPlayState != STATE_START_ABORT
  446 + && mCurrentPlayState != STATE_PLAYBACK_COMPLETED;
  447 + }
  448 +
  449 + /**
  450 + * 是否处于未播放状态
  451 + */
  452 + protected boolean isInIdleState() {
  453 + return mCurrentPlayState == STATE_IDLE;
  454 + }
  455 +
  456 + /**
  457 + * 播放中止状态
  458 + */
  459 + private boolean isInStartAbortState() {
  460 + return mCurrentPlayState == STATE_START_ABORT;
  461 + }
  462 +
  463 + /**
  464 + * 重新播放
  465 + *
  466 + * @param resetPosition 是否从头开始播放
  467 + */
  468 + @Override
  469 + public void replay(boolean resetPosition) {
  470 + if (resetPosition) {
  471 + mCurrentPosition = 0;
  472 + }
  473 + addDisplay();
  474 + startPrepare(true);
  475 + mPlayerContainer.setKeepScreenOn(true);
  476 + }
  477 +
  478 + /**
  479 + * 获取视频总时长
  480 + */
  481 + @Override
  482 + public long getDuration() {
  483 + if (isInPlaybackState()) {
  484 + return mMediaPlayer.getDuration();
  485 + }
  486 + return 0;
  487 + }
  488 +
  489 + /**
  490 + * 获取当前播放的位置
  491 + */
  492 + @Override
  493 + public long getCurrentPosition() {
  494 + if (isInPlaybackState()) {
  495 + mCurrentPosition = mMediaPlayer.getCurrentPosition();
  496 + return mCurrentPosition;
  497 + }
  498 + return 0;
  499 + }
  500 +
  501 + /**
  502 + * 调整播放进度
  503 + */
  504 + @Override
  505 + public void seekTo(long pos) {
  506 + if (isInPlaybackState()) {
  507 + mMediaPlayer.seekTo(pos);
  508 + }
  509 + }
  510 +
  511 + /**
  512 + * 是否处于播放状态
  513 + */
  514 + @Override
  515 + public boolean isPlaying() {
  516 + return isInPlaybackState() && mMediaPlayer.isPlaying();
  517 + }
  518 +
  519 + /**
  520 + * 获取当前缓冲百分比
  521 + */
  522 + @Override
  523 + public int getBufferedPercentage() {
  524 + return mMediaPlayer != null ? mMediaPlayer.getBufferedPercentage() : 0;
  525 + }
  526 +
  527 + /**
  528 + * 设置静音
  529 + */
  530 + @Override
  531 + public void setMute(boolean isMute) {
  532 + if (mMediaPlayer != null) {
  533 + this.mIsMute = isMute;
  534 + float volume = isMute ? 0.0f : 1.0f;
  535 + mMediaPlayer.setVolume(volume, volume);
  536 + }
  537 + }
  538 +
  539 + /**
  540 + * 是否处于静音状态
  541 + */
  542 + @Override
  543 + public boolean isMute() {
  544 + return mIsMute;
  545 + }
  546 +
  547 + /**
  548 + * 视频播放出错回调
  549 + */
  550 + @Override
  551 + public void onError() {
  552 + mPlayerContainer.setKeepScreenOn(false);
  553 + setPlayState(STATE_ERROR);
  554 + }
  555 +
  556 + /**
  557 + * 视频播放完成回调
  558 + */
  559 + @Override
  560 + public void onCompletion() {
  561 + mPlayerContainer.setKeepScreenOn(false);
  562 + mCurrentPosition = 0;
  563 + if (mProgressManager != null) {
  564 + //播放完成,清除进度
  565 + mProgressManager.saveProgress(mUrl, 0);
  566 + }
  567 + setPlayState(STATE_PLAYBACK_COMPLETED);
  568 +
  569 + if (curentPlayIndex + 1 <= dataSource.size() - 1) {
  570 + curentPlayIndex+=1;
  571 + mUrl = dataSource.get(curentPlayIndex);
  572 + replay(true);
  573 + }
  574 + }
  575 +
  576 + @Override
  577 + public void onInfo(int what, int extra) {
  578 + switch (what) {
  579 + case AbstractPlayer.MEDIA_INFO_BUFFERING_START:
  580 + setPlayState(STATE_BUFFERING);
  581 + break;
  582 + case AbstractPlayer.MEDIA_INFO_BUFFERING_END:
  583 + setPlayState(STATE_BUFFERED);
  584 + break;
  585 + case AbstractPlayer.MEDIA_INFO_VIDEO_RENDERING_START: // 视频开始渲染
  586 + setPlayState(STATE_PLAYING);
  587 + if (mPlayerContainer.getWindowVisibility() != VISIBLE) {
  588 + pause();
  589 + }
  590 + break;
  591 + case AbstractPlayer.MEDIA_INFO_VIDEO_ROTATION_CHANGED:
  592 + if (mRenderView != null)
  593 + mRenderView.setVideoRotation(extra);
  594 + break;
  595 + }
  596 + }
  597 +
  598 + /**
  599 + * 视频缓冲完毕,准备开始播放时回调
  600 + */
  601 + @Override
  602 + public void onPrepared() {
  603 + setPlayState(STATE_PREPARED);
  604 + if (mCurrentPosition > 0) {
  605 + seekTo(mCurrentPosition);
  606 + }
  607 + }
  608 +
  609 + /**
  610 + * 获取当前播放器的状态
  611 + */
  612 + public int getCurrentPlayerState() {
  613 + return mCurrentPlayerState;
  614 + }
  615 +
  616 + /**
  617 + * 获取当前的播放状态
  618 + */
  619 + public int getCurrentPlayState() {
  620 + return mCurrentPlayState;
  621 + }
  622 +
  623 + /**
  624 + * 获取缓冲速度
  625 + */
  626 + @Override
  627 + public long getTcpSpeed() {
  628 + return mMediaPlayer != null ? mMediaPlayer.getTcpSpeed() : 0;
  629 + }
  630 +
  631 + /**
  632 + * 设置播放速度
  633 + */
  634 + @Override
  635 + public void setSpeed(float speed) {
  636 + if (isInPlaybackState()) {
  637 + mMediaPlayer.setSpeed(speed);
  638 + }
  639 + }
  640 +
  641 + /**
  642 + * 设置视频地址
  643 + */
  644 + public void setUrl(String url) {
  645 + dataSource.clear();
  646 + dataSource.add(url);
  647 + curentPlayIndex = 0;
  648 + setUrl(dataSource.get(0), null);
  649 + }
  650 +
  651 + public void setDataSource(List<String> url) {
  652 + dataSource.clear();
  653 + dataSource.addAll(url);
  654 + curentPlayIndex = 0;
  655 + setUrl(dataSource.get(0), null);
  656 +
  657 + }
  658 +
  659 + /**
  660 + * 设置包含请求头信息的视频地址
  661 + *
  662 + * @param url 视频地址
  663 + * @param headers 请求头
  664 + */
  665 + public void setUrl(String url, Map<String, String> headers) {
  666 + mAssetFileDescriptor = null;
  667 + mUrl = url;
  668 + mHeaders = headers;
  669 + }
  670 +
  671 + /**
  672 + * 用于播放assets里面的视频文件
  673 + */
  674 + public void setAssetFileDescriptor(AssetFileDescriptor fd) {
  675 + mUrl = null;
  676 + this.mAssetFileDescriptor = fd;
  677 + }
  678 +
  679 + /**
  680 + * 一开始播放就seek到预先设置好的位置
  681 + */
  682 + public void skipPositionWhenPlay(int position) {
  683 + this.mCurrentPosition = position;
  684 + }
  685 +
  686 + /**
  687 + * 设置音量 0.0f-1.0f 之间
  688 + *
  689 + * @param v1 左声道音量
  690 + * @param v2 右声道音量
  691 + */
  692 + public void setVolume(float v1, float v2) {
  693 + if (mMediaPlayer != null) {
  694 + mMediaPlayer.setVolume(v1, v2);
  695 + }
  696 + }
  697 +
  698 + /**
  699 + * 设置进度管理器,用于保存播放进度
  700 + */
  701 + public void setProgressManager(@Nullable ProgressManager progressManager) {
  702 + this.mProgressManager = progressManager;
  703 + }
  704 +
  705 + /**
  706 + * 循环播放, 默认不循环播放
  707 + */
  708 + public void setLooping(boolean looping) {
  709 + mIsLooping = looping;
  710 + if (mMediaPlayer != null) {
  711 + mMediaPlayer.setLooping(looping);
  712 + }
  713 + }
  714 +
  715 + /**
  716 + * 是否开启AudioFocus监听, 默认开启,用于监听其它地方是否获取音频焦点,如果有其它地方获取了
  717 + * 音频焦点,此播放器将做出相应反应,具体实现见{@link AudioFocusHelper}
  718 + */
  719 + public void setEnableAudioFocus(boolean enableAudioFocus) {
  720 + mEnableAudioFocus = enableAudioFocus;
  721 + }
  722 +
  723 + /**
  724 + * 自定义播放核心,继承{@link PlayerFactory}实现自己的播放核心
  725 + */
  726 + public void setPlayerFactory(PlayerFactory<P> playerFactory) {
  727 + if (playerFactory == null) {
  728 + throw new IllegalArgumentException("PlayerFactory can not be null!");
  729 + }
  730 + mPlayerFactory = playerFactory;
  731 + }
  732 +
  733 + /**
  734 + * 自定义RenderView,继承{@link RenderViewFactory}实现自己的RenderView
  735 + */
  736 + public void setRenderViewFactory(RenderViewFactory renderViewFactory) {
  737 + if (renderViewFactory == null) {
  738 + throw new IllegalArgumentException("RenderViewFactory can not be null!");
  739 + }
  740 + mRenderViewFactory = renderViewFactory;
  741 + }
  742 +
  743 + /**
  744 + * 进入全屏
  745 + */
  746 + @Override
  747 + public void startFullScreen() {
  748 + if (mIsFullScreen)
  749 + return;
  750 +
  751 + ViewGroup decorView = getDecorView();
  752 + if (decorView == null)
  753 + return;
  754 +
  755 + mIsFullScreen = true;
  756 +
  757 + //隐藏NavigationBar和StatusBar
  758 + hideSysBar(decorView);
  759 +
  760 + //从当前FrameLayout中移除播放器视图
  761 + this.removeView(mPlayerContainer);
  762 + //将播放器视图添加到DecorView中即实现了全屏
  763 + decorView.addView(mPlayerContainer);
  764 +
  765 + setPlayerState(PLAYER_FULL_SCREEN);
  766 + }
  767 +
  768 + private void hideSysBar(ViewGroup decorView) {
  769 + int uiOptions = decorView.getSystemUiVisibility();
  770 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
  771 + uiOptions |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
  772 + }
  773 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  774 + uiOptions |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
  775 + }
  776 + decorView.setSystemUiVisibility(uiOptions);
  777 + getActivity().getWindow().setFlags(
  778 + WindowManager.LayoutParams.FLAG_FULLSCREEN,
  779 + WindowManager.LayoutParams.FLAG_FULLSCREEN);
  780 + }
  781 +
  782 + @Override
  783 + public void onWindowFocusChanged(boolean hasWindowFocus) {
  784 + super.onWindowFocusChanged(hasWindowFocus);
  785 + if (hasWindowFocus && mIsFullScreen) {
  786 + //重新获得焦点时保持全屏状态
  787 + hideSysBar(getDecorView());
  788 + }
  789 + }
  790 +
  791 + /**
  792 + * 退出全屏
  793 + */
  794 + @Override
  795 + public void stopFullScreen() {
  796 + if (!mIsFullScreen)
  797 + return;
  798 +
  799 + ViewGroup decorView = getDecorView();
  800 + if (decorView == null)
  801 + return;
  802 +
  803 + mIsFullScreen = false;
  804 +
  805 + //显示NavigationBar和StatusBar
  806 + showSysBar(decorView);
  807 +
  808 + //把播放器视图从DecorView中移除并添加到当前FrameLayout中即退出了全屏
  809 + decorView.removeView(mPlayerContainer);
  810 + this.addView(mPlayerContainer);
  811 +
  812 + setPlayerState(PLAYER_NORMAL);
  813 + }
  814 +
  815 + private void showSysBar(ViewGroup decorView) {
  816 + int uiOptions = decorView.getSystemUiVisibility();
  817 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
  818 + uiOptions &= ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
  819 + }
  820 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
  821 + uiOptions &= ~View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
  822 + }
  823 + decorView.setSystemUiVisibility(uiOptions);
  824 + getActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
  825 + }
  826 +
  827 + /**
  828 + * 获取DecorView
  829 + */
  830 + protected ViewGroup getDecorView() {
  831 + Activity activity = getActivity();
  832 + if (activity == null) return null;
  833 + return (ViewGroup) activity.getWindow().getDecorView();
  834 + }
  835 +
  836 + /**
  837 + * 获取activity中的content view,其id为android.R.id.content
  838 + */
  839 + protected ViewGroup getContentView() {
  840 + Activity activity = getActivity();
  841 + if (activity == null) return null;
  842 + return activity.findViewById(android.R.id.content);
  843 + }
  844 +
  845 + /**
  846 + * 获取Activity,优先通过Controller去获取Activity
  847 + */
  848 + protected Activity getActivity() {
  849 + Activity activity;
  850 + if (mVideoController != null) {
  851 + activity = PlayerUtils.scanForActivity(mVideoController.getContext());
  852 + if (activity == null) {
  853 + activity = PlayerUtils.scanForActivity(getContext());
  854 + }
  855 + } else {
  856 + activity = PlayerUtils.scanForActivity(getContext());
  857 + }
  858 + return activity;
  859 + }
  860 +
  861 + /**
  862 + * 判断是否处于全屏状态
  863 + */
  864 + @Override
  865 + public boolean isFullScreen() {
  866 + return mIsFullScreen;
  867 + }
  868 +
  869 + /**
  870 + * 开启小屏
  871 + */
  872 + public void startTinyScreen() {
  873 + if (mIsTinyScreen) return;
  874 + ViewGroup contentView = getContentView();
  875 + if (contentView == null) return;
  876 + this.removeView(mPlayerContainer);
  877 + int width = mTinyScreenSize[0];
  878 + if (width <= 0) {
  879 + width = PlayerUtils.getScreenWidth(getContext(), false) / 2;
  880 + }
  881 +
  882 + int height = mTinyScreenSize[1];
  883 + if (height <= 0) {
  884 + height = width * 9 / 16;
  885 + }
  886 +
  887 + LayoutParams params = new LayoutParams(width, height);
  888 + params.gravity = Gravity.BOTTOM | Gravity.END;
  889 + contentView.addView(mPlayerContainer, params);
  890 + mIsTinyScreen = true;
  891 + setPlayerState(PLAYER_TINY_SCREEN);
  892 + }
  893 +
  894 + /**
  895 + * 退出小屏
  896 + */
  897 + public void stopTinyScreen() {
  898 + if (!mIsTinyScreen) return;
  899 +
  900 + ViewGroup contentView = getContentView();
  901 + if (contentView == null) return;
  902 + contentView.removeView(mPlayerContainer);
  903 + LayoutParams params = new LayoutParams(
  904 + ViewGroup.LayoutParams.MATCH_PARENT,
  905 + ViewGroup.LayoutParams.MATCH_PARENT);
  906 + this.addView(mPlayerContainer, params);
  907 +
  908 + mIsTinyScreen = false;
  909 + setPlayerState(PLAYER_NORMAL);
  910 + }
  911 +
  912 + public boolean isTinyScreen() {
  913 + return mIsTinyScreen;
  914 + }
  915 +
  916 + @Override
  917 + public void onVideoSizeChanged(int videoWidth, int videoHeight) {
  918 + mVideoSize[0] = videoWidth;
  919 + mVideoSize[1] = videoHeight;
  920 +
  921 + if (mRenderView != null) {
  922 + mRenderView.setScaleType(mCurrentScreenScaleType);
  923 + mRenderView.setVideoSize(videoWidth, videoHeight);
  924 + }
  925 + }
  926 +
  927 + /**
  928 + * 设置控制器,传null表示移除控制器
  929 + */
  930 + public void setVideoController(@Nullable BaseVideoController mediaController) {
  931 + mPlayerContainer.removeView(mVideoController);
  932 + mVideoController = mediaController;
  933 + if (mediaController != null) {
  934 + mediaController.setMediaPlayer(this);
  935 + LayoutParams params = new LayoutParams(
  936 + ViewGroup.LayoutParams.MATCH_PARENT,
  937 + ViewGroup.LayoutParams.MATCH_PARENT);
  938 + mPlayerContainer.addView(mVideoController, params);
  939 + }
  940 + }
  941 +
  942 + /**
  943 + * 设置视频比例
  944 + */
  945 + @Override
  946 + public void setScreenScaleType(int screenScaleType) {
  947 + mCurrentScreenScaleType = screenScaleType;
  948 + if (mRenderView != null) {
  949 + mRenderView.setScaleType(screenScaleType);
  950 + }
  951 + }
  952 +
  953 + /**
  954 + * 设置镜像旋转,暂不支持SurfaceView
  955 + */
  956 + @Override
  957 + public void setMirrorRotation(boolean enable) {
  958 + if (mRenderView != null) {
  959 + mRenderView.getView().setScaleX(enable ? -1 : 1);
  960 + }
  961 + }
  962 +
  963 + /**
  964 + * 截图,暂不支持SurfaceView
  965 + */
  966 + @Override
  967 + public Bitmap doScreenShot() {
  968 + if (mRenderView != null) {
  969 + return mRenderView.doScreenShot();
  970 + }
  971 + return null;
  972 + }
  973 +
  974 + /**
  975 + * 获取视频宽高,其中width: mVideoSize[0], height: mVideoSize[1]
  976 + */
  977 + @Override
  978 + public int[] getVideoSize() {
  979 + return mVideoSize;
  980 + }
  981 +
  982 + /**
  983 + * 旋转视频画面
  984 + *
  985 + * @param rotation 角度
  986 + */
  987 + @Override
  988 + public void setRotation(float rotation) {
  989 + if (mRenderView != null) {
  990 + mRenderView.setVideoRotation((int) rotation);
  991 + }
  992 + }
  993 +
  994 + /**
  995 + * 设置小屏的宽高
  996 + *
  997 + * @param tinyScreenSize 其中tinyScreenSize[0]是宽,tinyScreenSize[1]是高
  998 + */
  999 + public void setTinyScreenSize(int[] tinyScreenSize) {
  1000 + this.mTinyScreenSize = tinyScreenSize;
  1001 + }
  1002 +
  1003 + /**
  1004 + * 向Controller设置播放状态,用于控制Controller的ui展示
  1005 + */
  1006 + protected void setPlayState(int playState) {
  1007 + mCurrentPlayState = playState;
  1008 + if (mVideoController != null) {
  1009 + mVideoController.setPlayState(playState);
  1010 + }
  1011 + if (mOnStateChangeListeners != null) {
  1012 + for (OnStateChangeListener l : PlayerUtils.getSnapshot(mOnStateChangeListeners)) {
  1013 + if (l != null) {
  1014 + l.onPlayStateChanged(playState);
  1015 + }
  1016 + }
  1017 + }
  1018 + }
  1019 +
  1020 + /**
  1021 + * 向Controller设置播放器状态,包含全屏状态和非全屏状态
  1022 + */
  1023 + protected void setPlayerState(int playerState) {
  1024 + mCurrentPlayerState = playerState;
  1025 + if (mVideoController != null) {
  1026 + mVideoController.setPlayerState(playerState);
  1027 + }
  1028 + if (mOnStateChangeListeners != null) {
  1029 + for (OnStateChangeListener l : PlayerUtils.getSnapshot(mOnStateChangeListeners)) {
  1030 + if (l != null) {
  1031 + l.onPlayerStateChanged(playerState);
  1032 + }
  1033 + }
  1034 + }
  1035 +
  1036 + }
  1037 +
  1038 + /**
  1039 + * 播放状态改变监听器
  1040 + */
  1041 + public interface OnStateChangeListener {
  1042 + void onPlayerStateChanged(int playerState);
  1043 +
  1044 + void onPlayStateChanged(int playState);
  1045 + }
  1046 +
  1047 + /**
  1048 + * OnStateChangeListener的空实现。用的时候只需要重写需要的方法
  1049 + */
  1050 + public static class SimpleOnStateChangeListener implements OnStateChangeListener {
  1051 + @Override
  1052 + public void onPlayerStateChanged(int playerState) {
  1053 + }
  1054 +
  1055 + @Override
  1056 + public void onPlayStateChanged(int playState) {
  1057 + }
  1058 + }
  1059 +
  1060 + /**
  1061 + * 添加一个播放状态监听器,播放状态发生变化时将会调用。
  1062 + */
  1063 + public void addOnStateChangeListener(@NonNull OnStateChangeListener listener) {
  1064 + if (mOnStateChangeListeners == null) {
  1065 + mOnStateChangeListeners = new ArrayList<>();
  1066 + }
  1067 + mOnStateChangeListeners.add(listener);
  1068 + }
  1069 +
  1070 + /**
  1071 + * 移除某个播放状态监听
  1072 + */
  1073 + public void removeOnStateChangeListener(@NonNull OnStateChangeListener listener) {
  1074 + if (mOnStateChangeListeners != null) {
  1075 + mOnStateChangeListeners.remove(listener);
  1076 + }
  1077 + }
  1078 +
  1079 + /**
  1080 + * 设置一个播放状态监听器,播放状态发生变化时将会调用,
  1081 + * 如果你想同时设置多个监听器,推荐 {@link #addOnStateChangeListener(OnStateChangeListener)}。
  1082 + */
  1083 + public void setOnStateChangeListener(@NonNull OnStateChangeListener listener) {
  1084 + if (mOnStateChangeListeners == null) {
  1085 + mOnStateChangeListeners = new ArrayList<>();
  1086 + } else {
  1087 + mOnStateChangeListeners.clear();
  1088 + }
  1089 + mOnStateChangeListeners.add(listener);
  1090 + }
  1091 +
  1092 + /**
  1093 + * 移除所有播放状态监听
  1094 + */
  1095 + public void clearOnStateChangeListeners() {
  1096 + if (mOnStateChangeListeners != null) {
  1097 + mOnStateChangeListeners.clear();
  1098 + }
  1099 + }
  1100 +
  1101 + /**
  1102 + * 改变返回键逻辑,用于activity
  1103 + */
  1104 + public boolean isBackPressed() {
  1105 + return mVideoController != null && mVideoController.onBackPressed();
  1106 + }
  1107 +
  1108 + @Override
  1109 + protected Parcelable onSaveInstanceState() {
  1110 + L.d("onSaveInstanceState: " + mCurrentPosition);
  1111 + //activity切到后台后可能被系统回收,故在此处进行进度保存
  1112 + saveProgress();
  1113 + return super.onSaveInstanceState();
  1114 + }
  1115 +
  1116 +
  1117 + public void onResume() {
  1118 + resume();
  1119 + }
  1120 +
  1121 + public void onPause() {
  1122 + pause();
  1123 + }
  1124 +
  1125 + public void onDestroy() {
  1126 + release();
  1127 + }
  1128 +
  1129 +
  1130 +}
cloud/video/src/main/java/com/libs/video/video/exoplayer/play/PlayerFactory.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.play;
  2 +
  3 +import android.content.Context;
  4 +
  5 +/**
  6 + * 创建播放器核心
  7 + *
  8 + * @param <P>
  9 + */
  10 +public abstract class PlayerFactory<P extends AbstractPlayer> {
  11 +
  12 + public abstract P createPlayer(Context context);
  13 +}
cloud/video/src/main/java/com/libs/video/video/exoplayer/play/ProgressManager.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.play;
  2 +
  3 +/**
  4 + * 进度管理
  5 + */
  6 +public abstract class ProgressManager {
  7 +
  8 + public abstract void saveProgress(String url, long progress);
  9 +
  10 + public abstract long getSavedProgress(String url);
  11 +
  12 +}
cloud/video/src/main/java/com/libs/video/video/exoplayer/play/VideoViewConfig.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.play;
  2 +
  3 +
  4 +
  5 +import com.libs.video.video.exoplayer.render.RenderViewFactory;
  6 +import com.libs.video.video.exoplayer.render.texture.TextureRenderViewFactory;
  7 +
  8 +import androidx.annotation.Nullable;
  9 +
  10 +/**
  11 + * 播放器全局配置
  12 + */
  13 +public class VideoViewConfig {
  14 +
  15 + public static Builder newBuilder() {
  16 + return new Builder();
  17 + }
  18 +
  19 + public final boolean mPlayOnMobileNetwork;
  20 +
  21 + public final boolean mEnableOrientation;
  22 +
  23 + public final boolean mEnableAudioFocus;
  24 +
  25 + public final boolean mEnableParallelPlay;
  26 +
  27 + public final boolean mIsEnableLog;
  28 +
  29 + public final ProgressManager mProgressManager;
  30 +
  31 + public final PlayerFactory mPlayerFactory;
  32 +
  33 + public final int mScreenScaleType;
  34 +
  35 + public final RenderViewFactory mRenderViewFactory;
  36 +
  37 + public final boolean mAdaptCutout;
  38 +
  39 + private VideoViewConfig(Builder builder) {
  40 + mIsEnableLog = builder.mIsEnableLog;
  41 + mEnableOrientation = builder.mEnableOrientation;
  42 + mPlayOnMobileNetwork = builder.mPlayOnMobileNetwork;
  43 + mEnableAudioFocus = builder.mEnableAudioFocus;
  44 + mProgressManager = builder.mProgressManager;
  45 + mEnableParallelPlay = builder.mEnableParallelPlay;
  46 + mScreenScaleType = builder.mScreenScaleType;
  47 + if (builder.mPlayerFactory == null) {
  48 + //默认为AndroidMediaPlayer
  49 + mPlayerFactory = AndroidMediaPlayerFactory.create();
  50 + } else {
  51 + mPlayerFactory = builder.mPlayerFactory;
  52 + }
  53 + if (builder.mRenderViewFactory == null) {
  54 + //默认使用TextureView渲染视频
  55 + mRenderViewFactory = TextureRenderViewFactory.create();
  56 + } else {
  57 + mRenderViewFactory = builder.mRenderViewFactory;
  58 + }
  59 + mAdaptCutout = builder.mAdaptCutout;
  60 + }
  61 +
  62 +
  63 + public final static class Builder {
  64 +
  65 + private boolean mIsEnableLog;
  66 + private boolean mPlayOnMobileNetwork;
  67 + private boolean mEnableOrientation;
  68 + private boolean mEnableAudioFocus = true;
  69 + private boolean mEnableParallelPlay;
  70 + private ProgressManager mProgressManager;
  71 + private PlayerFactory mPlayerFactory;
  72 + private int mScreenScaleType;
  73 + private RenderViewFactory mRenderViewFactory;
  74 + private boolean mAdaptCutout = true;
  75 +
  76 + /**
  77 + * 是否监听设备方向来切换全屏/半屏, 默认不开启
  78 + */
  79 + public Builder setEnableOrientation(boolean enableOrientation) {
  80 + mEnableOrientation = enableOrientation;
  81 + return this;
  82 + }
  83 +
  84 + /**
  85 + * 在移动环境下调用start()后是否继续播放,默认不继续播放
  86 + */
  87 + public Builder setPlayOnMobileNetwork(boolean playOnMobileNetwork) {
  88 + mPlayOnMobileNetwork = playOnMobileNetwork;
  89 + return this;
  90 + }
  91 +
  92 + /**
  93 + * 是否开启AudioFocus监听, 默认开启
  94 + */
  95 + public Builder setEnableAudioFocus(boolean enableAudioFocus) {
  96 + mEnableAudioFocus = enableAudioFocus;
  97 + return this;
  98 + }
  99 +
  100 + /**
  101 + * 设置进度管理器,用于保存播放进度
  102 + */
  103 + public Builder setProgressManager(@Nullable ProgressManager progressManager) {
  104 + mProgressManager = progressManager;
  105 + return this;
  106 + }
  107 +
  108 + /**
  109 + * 支持多开
  110 + * @deprecated 此api已经无效,你需要自己去控制同时只有一个VideoView在播放的效果
  111 + */
  112 + @Deprecated
  113 + public Builder setEnableParallelPlay(boolean enableParallelPlay) {
  114 + mEnableParallelPlay = enableParallelPlay;
  115 + return this;
  116 + }
  117 +
  118 + /**
  119 + * 是否打印日志
  120 + */
  121 + public Builder setLogEnabled(boolean enableLog) {
  122 + mIsEnableLog = enableLog;
  123 + return this;
  124 + }
  125 +
  126 + /**
  127 + * 自定义播放核心
  128 + */
  129 + public Builder setPlayerFactory(PlayerFactory playerFactory) {
  130 + mPlayerFactory = playerFactory;
  131 + return this;
  132 + }
  133 +
  134 + /**
  135 + * 设置视频比例
  136 + */
  137 + public Builder setScreenScaleType(int screenScaleType) {
  138 + mScreenScaleType = screenScaleType;
  139 + return this;
  140 + }
  141 +
  142 + /**
  143 + * 自定义RenderView
  144 + */
  145 + public Builder setRenderViewFactory(RenderViewFactory renderViewFactory) {
  146 + mRenderViewFactory = renderViewFactory;
  147 + return this;
  148 + }
  149 +
  150 + /**
  151 + * 是否适配刘海屏,默认适配
  152 + */
  153 + public Builder setAdaptCutout(boolean adaptCutout) {
  154 + mAdaptCutout = adaptCutout;
  155 + return this;
  156 + }
  157 +
  158 + public VideoViewConfig build() {
  159 + return new VideoViewConfig(this);
  160 + }
  161 + }
  162 +}
cloud/video/src/main/java/com/libs/video/video/exoplayer/play/VideoViewManager.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.play;
  2 +
  3 +import android.app.Application;
  4 +import com.libs.video.video.exoplayer.util.L;
  5 +
  6 +import java.util.LinkedHashMap;
  7 +
  8 +/**
  9 + * 视频播放器管理器,管理当前正在播放的VideoView,以及播放器配置
  10 + * 你也可以用来保存常驻内存的VideoView,但是要注意通过Application Context创建,
  11 + * 以免内存泄漏
  12 + */
  13 +public class VideoViewManager {
  14 +
  15 + /**
  16 + * 保存VideoView的容器
  17 + */
  18 + private LinkedHashMap<String, CNVideoView> mVideoViews = new LinkedHashMap<>();
  19 +
  20 + /**
  21 + * 是否在移动网络下直接播放视频
  22 + */
  23 + private boolean mPlayOnMobileNetwork;
  24 +
  25 + /**
  26 + * VideoViewManager实例
  27 + */
  28 + private static VideoViewManager sInstance;
  29 +
  30 + /**
  31 + * VideoViewConfig实例
  32 + */
  33 + private static VideoViewConfig sConfig;
  34 +
  35 + private VideoViewManager() {
  36 + mPlayOnMobileNetwork = getConfig().mPlayOnMobileNetwork;
  37 + }
  38 +
  39 + /**
  40 + * 设置VideoViewConfig
  41 + */
  42 + public static void setConfig(VideoViewConfig config) {
  43 + if (sConfig == null) {
  44 + synchronized (VideoViewConfig.class) {
  45 + if (sConfig == null) {
  46 + sConfig = config == null ? VideoViewConfig.newBuilder().build() : config;
  47 + }
  48 + }
  49 + }
  50 + }
  51 +
  52 + /**
  53 + * 获取VideoViewConfig
  54 + */
  55 + public static VideoViewConfig getConfig() {
  56 + setConfig(null);
  57 + return sConfig;
  58 + }
  59 +
  60 + /**
  61 + * 获取是否在移动网络下直接播放视频配置
  62 + */
  63 + public boolean playOnMobileNetwork() {
  64 + return mPlayOnMobileNetwork;
  65 + }
  66 +
  67 + /**
  68 + * 设置是否在移动网络下直接播放视频
  69 + */
  70 + public void setPlayOnMobileNetwork(boolean playOnMobileNetwork) {
  71 + mPlayOnMobileNetwork = playOnMobileNetwork;
  72 + }
  73 +
  74 + public static VideoViewManager instance() {
  75 + if (sInstance == null) {
  76 + synchronized (VideoViewManager.class) {
  77 + if (sInstance == null) {
  78 + sInstance = new VideoViewManager();
  79 + }
  80 + }
  81 + }
  82 + return sInstance;
  83 + }
  84 +
  85 + /**
  86 + * 添加VideoView
  87 + * @param tag 相同tag的VideoView只会保存一个,如果tag相同则会release并移除前一个
  88 + */
  89 + public void add(CNVideoView videoView, String tag) {
  90 + if (!(videoView.getContext() instanceof Application)) {
  91 + L.w("The Context of this VideoView is not an Application Context," +
  92 + "you must remove it after release,or it will lead to memory leek.");
  93 + }
  94 + CNVideoView old = get(tag);
  95 + if (old != null) {
  96 + old.release();
  97 + remove(tag);
  98 + }
  99 + mVideoViews.put(tag, videoView);
  100 + }
  101 +
  102 + public CNVideoView get(String tag) {
  103 + return mVideoViews.get(tag);
  104 + }
  105 +
  106 + public void remove(String tag) {
  107 + mVideoViews.remove(tag);
  108 + }
  109 +
  110 + public void removeAll() {
  111 + mVideoViews.clear();
  112 + }
  113 +
  114 + /**
  115 + * 释放掉和tag关联的VideoView,并将其从VideoViewManager中移除
  116 + */
  117 + public void releaseByTag(String tag) {
  118 + releaseByTag(tag, true);
  119 + }
  120 +
  121 + public void releaseByTag(String tag, boolean isRemove) {
  122 + CNVideoView videoView = get(tag);
  123 + if (videoView != null) {
  124 + videoView.release();
  125 + if (isRemove) {
  126 + remove(tag);
  127 + }
  128 + }
  129 + }
  130 +
  131 + public boolean onBackPress(String tag) {
  132 + CNVideoView videoView = get(tag);
  133 + if (videoView == null) return false;
  134 + return videoView.isBackPressed();
  135 + }
  136 +
  137 +}
cloud/video/src/main/java/com/libs/video/video/exoplayer/playerCore/exoplayer/ExoMediaPlayer.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.playerCore.exoplayer;
  2 +
  3 +import android.content.Context;
  4 +import android.content.res.AssetFileDescriptor;
  5 +import android.os.Handler;
  6 +import android.view.Surface;
  7 +import android.view.SurfaceHolder;
  8 +
  9 +import com.google.android.exoplayer2.DefaultLoadControl;
  10 +import com.google.android.exoplayer2.DefaultRenderersFactory;
  11 +import com.google.android.exoplayer2.ExoPlaybackException;
  12 +import com.google.android.exoplayer2.LoadControl;
  13 +import com.google.android.exoplayer2.PlaybackParameters;
  14 +import com.google.android.exoplayer2.Player;
  15 +import com.google.android.exoplayer2.RenderersFactory;
  16 +import com.google.android.exoplayer2.SimpleExoPlayer;
  17 +import com.google.android.exoplayer2.analytics.AnalyticsCollector;
  18 +import com.google.android.exoplayer2.source.DefaultMediaSourceEventListener;
  19 +import com.google.android.exoplayer2.source.MediaSource;
  20 +import com.google.android.exoplayer2.source.MediaSourceEventListener;
  21 +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
  22 +import com.google.android.exoplayer2.trackselection.MappingTrackSelector;
  23 +import com.google.android.exoplayer2.trackselection.TrackSelector;
  24 +import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
  25 +import com.google.android.exoplayer2.util.Clock;
  26 +import com.google.android.exoplayer2.util.EventLogger;
  27 +import com.google.android.exoplayer2.util.Util;
  28 +import com.google.android.exoplayer2.video.VideoListener;
  29 +import com.libs.video.video.exoplayer.play.AbstractPlayer;
  30 +import com.libs.video.video.exoplayer.play.VideoViewManager;
  31 +
  32 +import java.util.Map;
  33 +
  34 +
  35 +/**
  36 + * ExoPlayer播放
  37 + */
  38 +public class ExoMediaPlayer extends AbstractPlayer implements VideoListener, Player.EventListener {
  39 +
  40 + protected Context mAppContext;
  41 + protected SimpleExoPlayer exoPlayer;
  42 + protected MediaSource mMediaSource;
  43 + protected ExoMediaSourceHelper mMediaSourceHelper;
  44 + //播放参数控制
  45 + private PlaybackParameters mSpeedPlaybackParameters;
  46 + //最后播放状态
  47 + private int mLastReportedPlaybackState = Player.STATE_IDLE;
  48 + private boolean mLastReportedPlayWhenReady = false;
  49 + private boolean mIsPreparing;//是否准备
  50 + private boolean mIsBuffering;//是否缓冲
  51 +
  52 + private LoadControl mLoadControl;
  53 + private RenderersFactory mRenderersFactory;
  54 + private TrackSelector mTrackSelector;
  55 +
  56 + public ExoMediaPlayer(Context context) {
  57 + mAppContext = context.getApplicationContext();
  58 + mMediaSourceHelper = ExoMediaSourceHelper.getInstance(context);
  59 + }
  60 +
  61 + @Override
  62 + public void initPlayer() {
  63 + //创建exoplayer对象
  64 + exoPlayer = new SimpleExoPlayer.Builder(
  65 + mAppContext,
  66 + mRenderersFactory == null ? mRenderersFactory = new DefaultRenderersFactory(mAppContext) : mRenderersFactory,
  67 + mTrackSelector == null ? mTrackSelector = new DefaultTrackSelector(mAppContext) : mTrackSelector,
  68 + mLoadControl == null ? mLoadControl = new DefaultLoadControl() : mLoadControl,
  69 + DefaultBandwidthMeter.getSingletonInstance(mAppContext),
  70 + Util.getLooper(),
  71 + new AnalyticsCollector(Clock.DEFAULT),
  72 + /* useLazyPreparation= */ true,
  73 + Clock.DEFAULT)
  74 + .build();
  75 + setOptions();
  76 +
  77 + //播放器日志
  78 + if (VideoViewManager.getConfig().mIsEnableLog && mTrackSelector instanceof MappingTrackSelector) {
  79 + exoPlayer.addAnalyticsListener(new EventLogger((MappingTrackSelector) mTrackSelector, "ExoPlayer"));
  80 + }
  81 +
  82 + exoPlayer.addListener(this);
  83 + exoPlayer.addVideoListener(this);
  84 + }
  85 +
  86 + public void setTrackSelector(TrackSelector trackSelector) {
  87 + mTrackSelector = trackSelector;
  88 + }
  89 +
  90 + public void setRenderersFactory(RenderersFactory renderersFactory) {
  91 + mRenderersFactory = renderersFactory;
  92 + }
  93 +
  94 + public void setLoadControl(LoadControl loadControl) {
  95 + mLoadControl = loadControl;
  96 + }
  97 +
  98 + /**
  99 + * 设置播放资源
  100 + *
  101 + * @param path 播放地址
  102 + * @param headers 播放地址请求头
  103 + */
  104 + @Override
  105 + public void setDataSource(String path, Map<String, String> headers) {
  106 + mMediaSource = mMediaSourceHelper.getMediaSource(path, headers);
  107 + }
  108 +
  109 + @Override
  110 + public void setDataSource(AssetFileDescriptor fd) {
  111 + //no support
  112 + }
  113 +
  114 + @Override
  115 + public void start() {
  116 + if (exoPlayer == null)
  117 + return;
  118 + exoPlayer.setPlayWhenReady(true);
  119 + }
  120 +
  121 + @Override
  122 + public void pause() {
  123 + if (exoPlayer == null)
  124 + return;
  125 + exoPlayer.setPlayWhenReady(false);
  126 + }
  127 +
  128 + @Override
  129 + public void stop() {
  130 + if (exoPlayer == null)
  131 + return;
  132 + exoPlayer.stop();
  133 + }
  134 +
  135 + /**
  136 + * 预加载资源
  137 + */
  138 + @Override
  139 + public void prepareAsync() {
  140 + if (exoPlayer == null)
  141 + return;
  142 + if (mMediaSource == null) return;
  143 + if (mSpeedPlaybackParameters != null) {
  144 + exoPlayer.setPlaybackParameters(mSpeedPlaybackParameters);
  145 + }
  146 + mIsPreparing = true;
  147 + mMediaSource.addEventListener(new Handler(), mMediaSourceEventListener);
  148 + exoPlayer.prepare(mMediaSource);
  149 + }
  150 +
  151 + private MediaSourceEventListener mMediaSourceEventListener = new DefaultMediaSourceEventListener() {
  152 + @Override
  153 + public void onReadingStarted(int windowIndex, MediaSource.MediaPeriodId mediaPeriodId) {
  154 + super.onReadingStarted(windowIndex, mediaPeriodId);
  155 + if (mPlayerEventListener != null && mIsPreparing) {
  156 + mPlayerEventListener.onPrepared();
  157 + }
  158 + }
  159 + };
  160 +
  161 + @Override
  162 + public void reset() {
  163 + if (exoPlayer != null) {
  164 + exoPlayer.stop(true);
  165 + exoPlayer.setVideoSurface(null);
  166 + mIsPreparing = false;
  167 + mIsBuffering = false;
  168 + mLastReportedPlaybackState = Player.STATE_IDLE;
  169 + mLastReportedPlayWhenReady = false;
  170 + }
  171 + }
  172 +
  173 + @Override
  174 + public boolean isPlaying() {
  175 + if (exoPlayer == null)
  176 + return false;
  177 + //获取当前播放状态
  178 + int state = exoPlayer.getPlaybackState();
  179 + switch (state) {
  180 + case Player.STATE_BUFFERING:
  181 + case Player.STATE_READY:
  182 + return exoPlayer.getPlayWhenReady();
  183 + case Player.STATE_IDLE:
  184 + case Player.STATE_ENDED:
  185 + default:
  186 + return false;
  187 + }
  188 + }
  189 +
  190 + @Override
  191 + public void seekTo(long time) {
  192 + if (exoPlayer == null)
  193 + return;
  194 + exoPlayer.seekTo(time);
  195 + }
  196 +
  197 + @Override
  198 + public void release() {
  199 + if (exoPlayer != null) {
  200 + exoPlayer.removeListener(this);
  201 + exoPlayer.removeVideoListener(this);
  202 + final SimpleExoPlayer player = exoPlayer;
  203 + exoPlayer = null;
  204 + new Thread() {
  205 + @Override
  206 + public void run() {
  207 + //异步释放,防止卡顿
  208 + player.release();
  209 + }
  210 + }.start();
  211 + }
  212 +
  213 + mIsPreparing = false;
  214 + mIsBuffering = false;
  215 + mLastReportedPlaybackState = Player.STATE_IDLE;
  216 + mLastReportedPlayWhenReady = false;
  217 + mSpeedPlaybackParameters = null;
  218 + }
  219 +
  220 + @Override
  221 + public long getCurrentPosition() {
  222 + if (exoPlayer == null)
  223 + return 0;
  224 + return exoPlayer.getCurrentPosition();
  225 + }
  226 +
  227 + /**
  228 + * 获取播放时长
  229 + *
  230 + * @return
  231 + */
  232 + @Override
  233 + public long getDuration() {
  234 + if (exoPlayer == null)
  235 + return 0;
  236 + return exoPlayer.getDuration();
  237 + }
  238 +
  239 + /**
  240 + * 获取当前缓冲百分比
  241 + *
  242 + * @return
  243 + */
  244 + @Override
  245 + public int getBufferedPercentage() {
  246 + return exoPlayer == null ? 0 : exoPlayer.getBufferedPercentage();
  247 + }
  248 +
  249 + /**
  250 + * 设置渲染视频的View,主要用于TextureView
  251 + *
  252 + * @param surface
  253 + */
  254 + @Override
  255 + public void setSurface(Surface surface) {
  256 + if (exoPlayer != null) {
  257 + exoPlayer.setVideoSurface(surface);
  258 + }
  259 + }
  260 +
  261 + /**
  262 + * 设置渲染视频的View,主要用于SurfaceView
  263 + *
  264 + * @param holder
  265 + */
  266 + @Override
  267 + public void setDisplay(SurfaceHolder holder) {
  268 + if (holder == null)
  269 + setSurface(null);
  270 + else
  271 + setSurface(holder.getSurface());
  272 + }
  273 +
  274 + /**
  275 + * 设置左右音量大小
  276 + *
  277 + * @param leftVolume
  278 + * @param rightVolume
  279 + */
  280 + @Override
  281 + public void setVolume(float leftVolume, float rightVolume) {
  282 + if (exoPlayer != null)
  283 + exoPlayer.setVolume((leftVolume + rightVolume) / 2);
  284 + }
  285 +
  286 + /**
  287 + * 设置是否循环播放
  288 + *
  289 + * @param isLooping
  290 + */
  291 + @Override
  292 + public void setLooping(boolean isLooping) {
  293 + if (exoPlayer != null)
  294 + exoPlayer.setRepeatMode(isLooping ? Player.REPEAT_MODE_ALL : Player.REPEAT_MODE_OFF);
  295 + }
  296 +
  297 + @Override
  298 + public void setOptions() {
  299 + //准备好就开始播放
  300 + exoPlayer.setPlayWhenReady(true);
  301 + }
  302 +
  303 + /**
  304 + * 设置播放速度
  305 + *
  306 + * @param speed
  307 + */
  308 + @Override
  309 + public void setSpeed(float speed) {
  310 + PlaybackParameters playbackParameters = new PlaybackParameters(speed);
  311 + mSpeedPlaybackParameters = playbackParameters;
  312 + if (exoPlayer != null) {
  313 + exoPlayer.setPlaybackParameters(playbackParameters);
  314 + }
  315 + }
  316 +
  317 + /**
  318 + * 不支持
  319 + *
  320 + * @return
  321 + */
  322 + @Deprecated
  323 + @Override
  324 + public long getTcpSpeed() {
  325 + // no support
  326 + return 0;
  327 + }
  328 +
  329 + /**
  330 + * 播放状态改变
  331 + *
  332 + * @param playWhenReady
  333 + * @param playbackState
  334 + */
  335 + @Override
  336 + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
  337 + if (mPlayerEventListener == null) return;
  338 + if (mIsPreparing) return;
  339 + if (mLastReportedPlayWhenReady != playWhenReady || mLastReportedPlaybackState != playbackState) {
  340 + switch (playbackState) {
  341 + case Player.STATE_BUFFERING:
  342 + mPlayerEventListener.onInfo(MEDIA_INFO_BUFFERING_START, getBufferedPercentage());
  343 + mIsBuffering = true;
  344 + break;
  345 + case Player.STATE_READY:
  346 + if (mIsBuffering) {
  347 + mPlayerEventListener.onInfo(MEDIA_INFO_BUFFERING_END, getBufferedPercentage());
  348 + mIsBuffering = false;
  349 + }
  350 + break;
  351 + case Player.STATE_ENDED:
  352 + mPlayerEventListener.onCompletion();
  353 + break;
  354 + }
  355 + mLastReportedPlaybackState = playbackState;
  356 + mLastReportedPlayWhenReady = playWhenReady;
  357 + }
  358 + }
  359 +
  360 + @Override
  361 + public void onPlayerError(ExoPlaybackException error) {
  362 + if (mPlayerEventListener != null) {
  363 + mPlayerEventListener.onError();
  364 + }
  365 + }
  366 +
  367 + @Override
  368 + public void onVideoSizeChanged(int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {
  369 + if (mPlayerEventListener != null) {
  370 + mPlayerEventListener.onVideoSizeChanged(width, height);
  371 + if (unappliedRotationDegrees > 0) {
  372 + mPlayerEventListener.onInfo(MEDIA_INFO_VIDEO_ROTATION_CHANGED, unappliedRotationDegrees);
  373 + }
  374 + }
  375 + }
  376 +
  377 + /**
  378 + * 渲染第一帧
  379 + */
  380 + @Override
  381 + public void onRenderedFirstFrame() {
  382 + if (mPlayerEventListener != null && mIsPreparing) {
  383 + mPlayerEventListener.onInfo(MEDIA_INFO_VIDEO_RENDERING_START, 0);
  384 + mIsPreparing = false;
  385 + }
  386 + }
  387 +}
cloud/video/src/main/java/com/libs/video/video/exoplayer/playerCore/exoplayer/ExoMediaPlayerFactory.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.playerCore.exoplayer;
  2 +
  3 +import android.content.Context;
  4 +
  5 +import com.libs.video.video.exoplayer.play.PlayerFactory;
  6 +
  7 +public class ExoMediaPlayerFactory extends PlayerFactory<ExoMediaPlayer> {
  8 +
  9 + public static ExoMediaPlayerFactory create() {
  10 + return new ExoMediaPlayerFactory();
  11 + }
  12 +
  13 + @Override
  14 + public ExoMediaPlayer createPlayer(Context context) {
  15 + return new ExoMediaPlayer(context);
  16 + }
  17 +}
cloud/video/src/main/java/com/libs/video/video/exoplayer/playerCore/exoplayer/ExoMediaSourceHelper.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.playerCore.exoplayer;
  2 +
  3 +import android.content.Context;
  4 +import android.net.Uri;
  5 +import android.text.TextUtils;
  6 +
  7 +import com.google.android.exoplayer2.C;
  8 +import com.google.android.exoplayer2.database.ExoDatabaseProvider;
  9 +import com.google.android.exoplayer2.ext.rtmp.RtmpDataSourceFactory;
  10 +import com.google.android.exoplayer2.source.MediaSource;
  11 +import com.google.android.exoplayer2.source.ProgressiveMediaSource;
  12 +import com.google.android.exoplayer2.source.dash.DashMediaSource;
  13 +import com.google.android.exoplayer2.source.hls.HlsMediaSource;
  14 +import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
  15 +import com.google.android.exoplayer2.upstream.DataSource;
  16 +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
  17 +import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
  18 +import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
  19 +import com.google.android.exoplayer2.upstream.HttpDataSource;
  20 +import com.google.android.exoplayer2.upstream.cache.Cache;
  21 +import com.google.android.exoplayer2.upstream.cache.CacheDataSource;
  22 +import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
  23 +import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
  24 +import com.google.android.exoplayer2.upstream.cache.SimpleCache;
  25 +import com.google.android.exoplayer2.util.Util;
  26 +
  27 +import java.io.File;
  28 +import java.lang.reflect.Field;
  29 +import java.util.Map;
  30 +
  31 +public final class ExoMediaSourceHelper {
  32 +
  33 + private static ExoMediaSourceHelper sInstance;
  34 +
  35 + private final String mUserAgent;
  36 + private Context mAppContext;
  37 + private HttpDataSource.Factory mHttpDataSourceFactory;
  38 + private Cache mCache;
  39 +
  40 + private ExoMediaSourceHelper(Context context) {
  41 + mAppContext = context.getApplicationContext();
  42 + mUserAgent = Util.getUserAgent(mAppContext, mAppContext.getApplicationInfo().name);
  43 + }
  44 +
  45 + public static ExoMediaSourceHelper getInstance(Context context) {
  46 + if (sInstance == null) {
  47 + synchronized (ExoMediaSourceHelper.class) {
  48 + if (sInstance == null) {
  49 + sInstance = new ExoMediaSourceHelper(context);
  50 + }
  51 + }
  52 + }
  53 + return sInstance;
  54 + }
  55 +
  56 + public MediaSource getMediaSource(String uri) {
  57 + return getMediaSource(uri, null, false);
  58 + }
  59 +
  60 + public MediaSource getMediaSource(String uri, Map<String, String> headers) {
  61 + return getMediaSource(uri, headers, false);
  62 + }
  63 +
  64 + public MediaSource getMediaSource(String uri, boolean isCache) {
  65 + return getMediaSource(uri, null, isCache);
  66 + }
  67 +
  68 + public MediaSource getMediaSource(String uri, Map<String, String> headers, boolean isCache) {
  69 + Uri contentUri = Uri.parse(uri);
  70 + if ("rtmp".equals(contentUri.getScheme())) {
  71 + return new ProgressiveMediaSource.Factory(new RtmpDataSourceFactory(null))
  72 + .createMediaSource(contentUri);
  73 + }
  74 + int contentType = inferContentType(uri);
  75 + DataSource.Factory factory;
  76 + if (isCache) {
  77 + factory = getCacheDataSourceFactory();
  78 + } else {
  79 + factory = getDataSourceFactory();
  80 + }
  81 + if (mHttpDataSourceFactory != null) {
  82 + setHeaders(headers);
  83 + }
  84 + switch (contentType) {
  85 + case C.TYPE_DASH:
  86 + return new DashMediaSource.Factory(factory).createMediaSource(contentUri);
  87 + case C.TYPE_SS:
  88 + return new SsMediaSource.Factory(factory).createMediaSource(contentUri);
  89 + case C.TYPE_HLS:
  90 + return new HlsMediaSource.Factory(factory).createMediaSource(contentUri);
  91 + default:
  92 + case C.TYPE_OTHER:
  93 + return new ProgressiveMediaSource.Factory(factory).createMediaSource(contentUri);
  94 + }
  95 + }
  96 +
  97 + private int inferContentType(String fileName) {
  98 + fileName = Util.toLowerInvariant(fileName);
  99 + if (fileName.contains(".mpd")) {
  100 + return C.TYPE_DASH;
  101 + } else if (fileName.contains(".m3u8")) {
  102 + return C.TYPE_HLS;
  103 + } else if (fileName.matches(".*\\.ism(l)?(/manifest(\\(.+\\))?)?")) {
  104 + return C.TYPE_SS;
  105 + } else {
  106 + return C.TYPE_OTHER;
  107 + }
  108 + }
  109 +
  110 + private DataSource.Factory getCacheDataSourceFactory() {
  111 + if (mCache == null) {
  112 + mCache = newCache();
  113 + }
  114 + return new CacheDataSourceFactory(
  115 + mCache,
  116 + getDataSourceFactory(),
  117 + CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR);
  118 + }
  119 +
  120 + private Cache newCache() {
  121 + return new SimpleCache(
  122 + new File(mAppContext.getExternalCacheDir(), "exo-video-cache"),//缓存目录
  123 + new LeastRecentlyUsedCacheEvictor(512 * 1024 * 1024),//缓存大小,默认512M,使用LRU算法实现
  124 + new ExoDatabaseProvider(mAppContext));
  125 + }
  126 +
  127 + /**
  128 + * Returns a new DataSource factory.
  129 + *
  130 + * @return A new DataSource factory.
  131 + */
  132 + private DataSource.Factory getDataSourceFactory() {
  133 + return new DefaultDataSourceFactory(mAppContext, getHttpDataSourceFactory());
  134 + }
  135 +
  136 + /**
  137 + * Returns a new HttpDataSource factory.
  138 + *
  139 + * @return A new HttpDataSource factory.
  140 + */
  141 + private DataSource.Factory getHttpDataSourceFactory() {
  142 + if (mHttpDataSourceFactory == null) {
  143 + mHttpDataSourceFactory = new DefaultHttpDataSourceFactory(
  144 + mUserAgent,
  145 + null,
  146 + DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS,
  147 + DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS,
  148 + //http->https重定向支持
  149 + true);
  150 + }
  151 + return mHttpDataSourceFactory;
  152 + }
  153 +
  154 + private void setHeaders(Map<String, String> headers) {
  155 + if (headers != null && headers.size() > 0) {
  156 + for (Map.Entry<String, String> header : headers.entrySet()) {
  157 + String key = header.getKey();
  158 + String value = header.getValue();
  159 + //如果发现用户通过header传递了UA,则强行将HttpDataSourceFactory里面的userAgent字段替换成用户的
  160 + if (TextUtils.equals(key, "User-Agent")) {
  161 + if (!TextUtils.isEmpty(value)) {
  162 + try {
  163 + Field userAgentField = mHttpDataSourceFactory.getClass().getDeclaredField("userAgent");
  164 + userAgentField.setAccessible(true);
  165 + userAgentField.set(mHttpDataSourceFactory, value);
  166 + } catch (Exception e) {
  167 + //ignore
  168 + }
  169 + }
  170 + } else {
  171 + mHttpDataSourceFactory.getDefaultRequestProperties().set(key, value);
  172 + }
  173 + }
  174 + }
  175 + }
  176 +
  177 + public void setCache(Cache cache) {
  178 + this.mCache = cache;
  179 + }
  180 +}
cloud/video/src/main/java/com/libs/video/video/exoplayer/render/IRenderView.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.render;
  2 +
  3 +import android.graphics.Bitmap;
  4 +import android.view.View;
  5 +
  6 +
  7 +import com.libs.video.video.exoplayer.play.AbstractPlayer;
  8 +
  9 +import androidx.annotation.NonNull;
  10 +
  11 +public interface IRenderView {
  12 +
  13 + /**
  14 + * 关联AbstractPlayer
  15 + */
  16 + void attachToPlayer(@NonNull AbstractPlayer player);
  17 +
  18 + /**
  19 + * 设置视频宽高
  20 + * @param videoWidth 宽
  21 + * @param videoHeight 高
  22 + */
  23 + void setVideoSize(int videoWidth, int videoHeight);
  24 +
  25 + /**
  26 + * 设置视频旋转角度
  27 + * @param degree 角度值
  28 + */
  29 + void setVideoRotation(int degree);
  30 +
  31 + /**
  32 + * 设置screen scale type
  33 + * @param scaleType 类型
  34 + */
  35 + void setScaleType(int scaleType);
  36 +
  37 + /**
  38 + * 获取真实的RenderView
  39 + */
  40 + View getView();
  41 +
  42 + /**
  43 + * 截图
  44 + */
  45 + Bitmap doScreenShot();
  46 +
  47 + /**
  48 + * 释放资源
  49 + */
  50 + void release();
  51 +
  52 +}
0 \ No newline at end of file 53 \ No newline at end of file
cloud/video/src/main/java/com/libs/video/video/exoplayer/render/MeasureHelper.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.render;
  2 +
  3 +import android.view.View;
  4 +
  5 +import com.libs.video.video.exoplayer.play.CNVideoView;
  6 +
  7 +
  8 +/**
  9 + * 测量工具
  10 + */
  11 +public class MeasureHelper {
  12 +
  13 + private int mVideoWidth;
  14 +
  15 + private int mVideoHeight;
  16 +
  17 + private int mCurrentScreenScale;
  18 +
  19 + private int mVideoRotationDegree;
  20 +
  21 + public void setVideoRotation(int videoRotationDegree) {
  22 + mVideoRotationDegree = videoRotationDegree;
  23 + }
  24 +
  25 +
  26 + public void setVideoSize(int width, int height) {
  27 + mVideoWidth = width;
  28 + mVideoHeight = height;
  29 + }
  30 +
  31 + public void setScreenScale(int screenScale) {
  32 + mCurrentScreenScale = screenScale;
  33 + }
  34 +
  35 + public int[] doMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  36 + if (mVideoRotationDegree == 90 || mVideoRotationDegree == 270) { // 软解码时处理旋转信息,交换宽高
  37 + widthMeasureSpec = widthMeasureSpec + heightMeasureSpec;
  38 + heightMeasureSpec = widthMeasureSpec - heightMeasureSpec;
  39 + widthMeasureSpec = widthMeasureSpec - heightMeasureSpec;
  40 + }
  41 +
  42 + int width = View.getDefaultSize(mVideoWidth, widthMeasureSpec);
  43 + int height = View.getDefaultSize(mVideoHeight, heightMeasureSpec);
  44 +
  45 + //如果设置了比例
  46 + switch (mCurrentScreenScale) {
  47 + case CNVideoView.SCREEN_SCALE_ORIGINAL:
  48 + width = mVideoWidth;
  49 + height = mVideoHeight;
  50 + break;
  51 + case CNVideoView.SCREEN_SCALE_16_9:
  52 + if (height > width / 16 * 9) {
  53 + height = width / 16 * 9;
  54 + } else {
  55 + width = height / 9 * 16;
  56 + }
  57 + break;
  58 + case CNVideoView.SCREEN_SCALE_4_3:
  59 + if (height > width / 4 * 3) {
  60 + height = width / 4 * 3;
  61 + } else {
  62 + width = height / 3 * 4;
  63 + }
  64 +// Log.d("@@@@", "onMeasure 4:3 : width" + width + " height:" + height);
  65 + break;
  66 + case CNVideoView.SCREEN_SCALE_MATCH_PARENT:
  67 + width = widthMeasureSpec;
  68 + height = heightMeasureSpec;
  69 + break;
  70 + case CNVideoView.SCREEN_SCALE_CENTER_CROP:
  71 + if (mVideoWidth > 0 && mVideoHeight > 0) {
  72 + if (mVideoWidth * height > width * mVideoHeight) {
  73 + width = height * mVideoWidth / mVideoHeight;
  74 + } else {
  75 + height = width * mVideoHeight / mVideoWidth;
  76 + }
  77 + }
  78 + break;
  79 + case CNVideoView.SCREEN_SCALE_DEFAULT:
  80 + default:
  81 + if (mVideoWidth > 0 && mVideoHeight > 0) {
  82 +
  83 + int widthSpecMode = View.MeasureSpec.getMode(widthMeasureSpec);
  84 + int widthSpecSize = View.MeasureSpec.getSize(widthMeasureSpec);
  85 + int heightSpecMode = View.MeasureSpec.getMode(heightMeasureSpec);
  86 + int heightSpecSize = View.MeasureSpec.getSize(heightMeasureSpec);
  87 +
  88 + if (widthSpecMode == View.MeasureSpec.EXACTLY && heightSpecMode == View.MeasureSpec.EXACTLY) {
  89 + // the size is fixed
  90 + width = widthSpecSize;
  91 + height = heightSpecSize;
  92 +
  93 + // for compatibility, we adjust size based on aspect ratio
  94 + if (mVideoWidth * height < width * mVideoHeight) {
  95 + //Log.i("@@@", "image too wide, correcting");
  96 + width = height * mVideoWidth / mVideoHeight;
  97 + } else if (mVideoWidth * height > width * mVideoHeight) {
  98 + //Log.i("@@@", "image too tall, correcting");
  99 + height = width * mVideoHeight / mVideoWidth;
  100 + }
  101 + } else if (widthSpecMode == View.MeasureSpec.EXACTLY) {
  102 + // only the width is fixed, adjust the height to match aspect ratio if possible
  103 + width = widthSpecSize;
  104 + height = width * mVideoHeight / mVideoWidth;
  105 + if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {
  106 + // couldn't match aspect ratio within the constraints
  107 + height = heightSpecSize;
  108 + }
  109 + } else if (heightSpecMode == View.MeasureSpec.EXACTLY) {
  110 + // only the height is fixed, adjust the width to match aspect ratio if possible
  111 + height = heightSpecSize;
  112 + width = height * mVideoWidth / mVideoHeight;
  113 + if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {
  114 + // couldn't match aspect ratio within the constraints
  115 + width = widthSpecSize;
  116 + }
  117 + } else {
  118 + // neither the width nor the height are fixed, try to use actual video size
  119 + width = mVideoWidth;
  120 + height = mVideoHeight;
  121 + if (heightSpecMode == View.MeasureSpec.AT_MOST && height > heightSpecSize) {
  122 + // too tall, decrease both width and height
  123 + height = heightSpecSize;
  124 + width = height * mVideoWidth / mVideoHeight;
  125 + }
  126 + if (widthSpecMode == View.MeasureSpec.AT_MOST && width > widthSpecSize) {
  127 + // too wide, decrease both width and height
  128 + width = widthSpecSize;
  129 + height = width * mVideoHeight / mVideoWidth;
  130 + }
  131 + }
  132 + } else {
  133 + // no size yet, just adopt the given spec sizes
  134 + }
  135 + break;
  136 + }
  137 +
  138 + return new int[]{width, height};
  139 + }
  140 +}
cloud/video/src/main/java/com/libs/video/video/exoplayer/render/RenderViewFactory.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.render;
  2 +
  3 +import android.content.Context;
  4 +
  5 +public abstract class RenderViewFactory {
  6 +
  7 + public abstract IRenderView createRenderView(Context context);
  8 +
  9 +}
cloud/video/src/main/java/com/libs/video/video/exoplayer/render/surface/SurfaceRenderView.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.render.surface;
  2 +
  3 +import android.content.Context;
  4 +import android.graphics.Bitmap;
  5 +import android.graphics.PixelFormat;
  6 +import android.util.AttributeSet;
  7 +import android.view.SurfaceHolder;
  8 +import android.view.SurfaceView;
  9 +import android.view.View;
  10 +
  11 +
  12 +import com.libs.video.video.exoplayer.play.AbstractPlayer;
  13 +import com.libs.video.video.exoplayer.render.IRenderView;
  14 +import com.libs.video.video.exoplayer.render.MeasureHelper;
  15 +
  16 +import androidx.annotation.NonNull;
  17 +
  18 +public class SurfaceRenderView extends SurfaceView implements IRenderView, SurfaceHolder.Callback {
  19 + private MeasureHelper mMeasureHelper;
  20 +
  21 + private AbstractPlayer mMediaPlayer;
  22 +
  23 + public SurfaceRenderView(Context context) {
  24 + super(context);
  25 + }
  26 +
  27 + public SurfaceRenderView(Context context, AttributeSet attrs) {
  28 + super(context, attrs);
  29 + }
  30 +
  31 + public SurfaceRenderView(Context context, AttributeSet attrs, int defStyleAttr) {
  32 + super(context, attrs, defStyleAttr);
  33 + }
  34 +
  35 + {
  36 + mMeasureHelper = new MeasureHelper();
  37 + SurfaceHolder surfaceHolder = getHolder();
  38 + surfaceHolder.addCallback(this);
  39 + surfaceHolder.setFormat(PixelFormat.RGBA_8888);
  40 + }
  41 +
  42 + @Override
  43 + public void attachToPlayer(@NonNull AbstractPlayer player) {
  44 + this.mMediaPlayer = player;
  45 + }
  46 +
  47 + @Override
  48 + public void setVideoSize(int videoWidth, int videoHeight) {
  49 + if (videoWidth > 0 && videoHeight > 0) {
  50 + mMeasureHelper.setVideoSize(videoWidth, videoHeight);
  51 + requestLayout();
  52 + }
  53 + }
  54 +
  55 + @Override
  56 + public void setVideoRotation(int degree) {
  57 + mMeasureHelper.setVideoRotation(degree);
  58 + setRotation(degree);
  59 + }
  60 +
  61 + @Override
  62 + public void setScaleType(int scaleType) {
  63 + mMeasureHelper.setScreenScale(scaleType);
  64 + requestLayout();
  65 + }
  66 +
  67 + @Override
  68 + public View getView() {
  69 + return this;
  70 + }
  71 +
  72 + @Override
  73 + public Bitmap doScreenShot() {
  74 + return null;
  75 + }
  76 +
  77 + @Override
  78 + public void release() {
  79 +
  80 + }
  81 +
  82 + @Override
  83 + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  84 + int[] measuredSize = mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec);
  85 + setMeasuredDimension(measuredSize[0], measuredSize[1]);
  86 + }
  87 +
  88 + @Override
  89 + public void surfaceCreated(SurfaceHolder holder) {
  90 +
  91 + }
  92 +
  93 + @Override
  94 + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
  95 + if (mMediaPlayer != null) {
  96 + mMediaPlayer.setDisplay(holder);
  97 + }
  98 + }
  99 +
  100 + @Override
  101 + public void surfaceDestroyed(SurfaceHolder holder) {
  102 +
  103 + }
  104 +}
0 \ No newline at end of file 105 \ No newline at end of file
cloud/video/src/main/java/com/libs/video/video/exoplayer/render/surface/SurfaceRenderViewFactory.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.render.surface;
  2 +
  3 +import android.content.Context;
  4 +
  5 +import com.libs.video.video.exoplayer.render.IRenderView;
  6 +import com.libs.video.video.exoplayer.render.RenderViewFactory;
  7 +
  8 +
  9 +public class SurfaceRenderViewFactory extends RenderViewFactory {
  10 +
  11 + public static SurfaceRenderViewFactory create() {
  12 + return new SurfaceRenderViewFactory();
  13 + }
  14 +
  15 + @Override
  16 + public IRenderView createRenderView(Context context) {
  17 + return new SurfaceRenderView(context);
  18 + }
  19 +}
cloud/video/src/main/java/com/libs/video/video/exoplayer/render/texture/TextureRenderView.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.render.texture;
  2 +
  3 +import android.annotation.SuppressLint;
  4 +import android.content.Context;
  5 +import android.graphics.Bitmap;
  6 +import android.graphics.SurfaceTexture;
  7 +import android.view.Surface;
  8 +import android.view.TextureView;
  9 +import android.view.View;
  10 +
  11 +
  12 +import com.libs.video.video.exoplayer.play.AbstractPlayer;
  13 +import com.libs.video.video.exoplayer.render.IRenderView;
  14 +import com.libs.video.video.exoplayer.render.MeasureHelper;
  15 +
  16 +import androidx.annotation.NonNull;
  17 +import androidx.annotation.Nullable;
  18 +
  19 +@SuppressLint("ViewConstructor")
  20 +public class TextureRenderView extends TextureView implements IRenderView, TextureView.SurfaceTextureListener {
  21 + private MeasureHelper mMeasureHelper;
  22 + private SurfaceTexture mSurfaceTexture;
  23 +
  24 + @Nullable
  25 + private AbstractPlayer mMediaPlayer;
  26 + private Surface mSurface;
  27 +
  28 + public TextureRenderView(Context context) {
  29 + super(context);
  30 + }
  31 +
  32 + {
  33 + mMeasureHelper = new MeasureHelper();
  34 + setSurfaceTextureListener(this);
  35 + }
  36 +
  37 + @Override
  38 + public void attachToPlayer(@NonNull AbstractPlayer player) {
  39 + this.mMediaPlayer = player;
  40 + }
  41 +
  42 + @Override
  43 + public void setVideoSize(int videoWidth, int videoHeight) {
  44 + if (videoWidth > 0 && videoHeight > 0) {
  45 + mMeasureHelper.setVideoSize(videoWidth, videoHeight);
  46 + requestLayout();
  47 + }
  48 + }
  49 +
  50 + @Override
  51 + public void setVideoRotation(int degree) {
  52 + mMeasureHelper.setVideoRotation(degree);
  53 + setRotation(degree);
  54 + }
  55 +
  56 + @Override
  57 + public void setScaleType(int scaleType) {
  58 + mMeasureHelper.setScreenScale(scaleType);
  59 + requestLayout();
  60 + }
  61 +
  62 + @Override
  63 + public View getView() {
  64 + return this;
  65 + }
  66 +
  67 + @Override
  68 + public Bitmap doScreenShot() {
  69 + return getBitmap();
  70 + }
  71 +
  72 + @Override
  73 + public void release() {
  74 + if (mSurface != null)
  75 + mSurface.release();
  76 +
  77 + if (mSurfaceTexture != null)
  78 + mSurfaceTexture.release();
  79 + }
  80 +
  81 + @Override
  82 + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  83 + int[] measuredSize = mMeasureHelper.doMeasure(widthMeasureSpec, heightMeasureSpec);
  84 + setMeasuredDimension(measuredSize[0], measuredSize[1]);
  85 + }
  86 +
  87 + @Override
  88 + public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int width, int height) {
  89 + if (mSurfaceTexture != null) {
  90 + setSurfaceTexture(mSurfaceTexture);
  91 + } else {
  92 + mSurfaceTexture = surfaceTexture;
  93 + mSurface = new Surface(surfaceTexture);
  94 + if (mMediaPlayer != null) {
  95 + mMediaPlayer.setSurface(mSurface);
  96 + }
  97 + }
  98 + }
  99 +
  100 + @Override
  101 + public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
  102 +
  103 + }
  104 +
  105 + @Override
  106 + public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
  107 + return false;
  108 + }
  109 +
  110 + @Override
  111 + public void onSurfaceTextureUpdated(SurfaceTexture surface) {
  112 +
  113 + }
  114 +}
0 \ No newline at end of file 115 \ No newline at end of file
cloud/video/src/main/java/com/libs/video/video/exoplayer/render/texture/TextureRenderViewFactory.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.render.texture;
  2 +
  3 +import android.content.Context;
  4 +
  5 +import com.libs.video.video.exoplayer.render.IRenderView;
  6 +import com.libs.video.video.exoplayer.render.RenderViewFactory;
  7 +
  8 +public class TextureRenderViewFactory extends RenderViewFactory {
  9 +
  10 + public static TextureRenderViewFactory create() {
  11 + return new TextureRenderViewFactory();
  12 + }
  13 +
  14 + @Override
  15 + public IRenderView createRenderView(Context context) {
  16 + return new TextureRenderView(context);
  17 + }
  18 +}
cloud/video/src/main/java/com/libs/video/video/exoplayer/util/CutoutUtil.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.util;
  2 +
  3 +import android.annotation.SuppressLint;
  4 +import android.app.Activity;
  5 +import android.content.Context;
  6 +import android.graphics.Rect;
  7 +import android.os.Build;
  8 +import android.view.DisplayCutout;
  9 +import android.view.Window;
  10 +import android.view.WindowInsets;
  11 +import android.view.WindowManager;
  12 +
  13 +import java.lang.reflect.Method;
  14 +import java.util.List;
  15 +
  16 +/**
  17 + * 刘海屏工具
  18 + */
  19 +public final class CutoutUtil {
  20 +
  21 + private CutoutUtil() {
  22 + }
  23 +
  24 + /**
  25 + * 是否为允许全屏界面显示内容到刘海区域的刘海屏机型(与AndroidManifest中配置对应)
  26 + */
  27 + public static boolean allowDisplayToCutout(Activity activity) {
  28 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
  29 + // 9.0系统全屏界面默认会保留黑边,不允许显示内容到刘海区域
  30 + Window window = activity.getWindow();
  31 + WindowInsets windowInsets = window.getDecorView().getRootWindowInsets();
  32 + if (windowInsets == null) {
  33 + return false;
  34 + }
  35 + DisplayCutout displayCutout = windowInsets.getDisplayCutout();
  36 + if (displayCutout == null) {
  37 + return false;
  38 + }
  39 + List<Rect> boundingRects = displayCutout.getBoundingRects();
  40 + return boundingRects.size() > 0;
  41 + } else {
  42 + return hasCutoutHuawei(activity)
  43 + || hasCutoutOPPO(activity)
  44 + || hasCutoutVIVO(activity)
  45 + || hasCutoutXIAOMI(activity);
  46 + }
  47 + }
  48 +
  49 + /**
  50 + * 是否是华为刘海屏机型
  51 + */
  52 + @SuppressWarnings("unchecked")
  53 + private static boolean hasCutoutHuawei(Activity activity) {
  54 + if (!Build.MANUFACTURER.equalsIgnoreCase("HUAWEI")) {
  55 + return false;
  56 + }
  57 + try {
  58 + ClassLoader cl = activity.getClassLoader();
  59 + Class HwNotchSizeUtil = cl.loadClass("com.huawei.android.util.HwNotchSizeUtil");
  60 + if (HwNotchSizeUtil != null) {
  61 + Method get = HwNotchSizeUtil.getMethod("hasNotchInScreen");
  62 + return (boolean) get.invoke(HwNotchSizeUtil);
  63 + }
  64 + return false;
  65 + } catch (Exception e) {
  66 + return false;
  67 + }
  68 + }
  69 +
  70 + /**
  71 + * 是否是oppo刘海屏机型
  72 + */
  73 + private static boolean hasCutoutOPPO(Activity activity) {
  74 + if (!Build.MANUFACTURER.equalsIgnoreCase("oppo")) {
  75 + return false;
  76 + }
  77 + return activity.getPackageManager().hasSystemFeature("com.oppo.feature.screen.heteromorphism");
  78 + }
  79 +
  80 + /**
  81 + * 是否是vivo刘海屏机型
  82 + */
  83 + @SuppressWarnings("unchecked")
  84 + @SuppressLint("PrivateApi")
  85 + private static boolean hasCutoutVIVO(Activity activity) {
  86 + if (!Build.MANUFACTURER.equalsIgnoreCase("vivo")) {
  87 + return false;
  88 + }
  89 + try {
  90 + ClassLoader cl = activity.getClassLoader();
  91 + Class ftFeatureUtil = cl.loadClass("android.util.FtFeature");
  92 + if (ftFeatureUtil != null) {
  93 + Method get = ftFeatureUtil.getMethod("isFeatureSupport", int.class);
  94 + return (boolean) get.invoke(ftFeatureUtil, 0x00000020);
  95 + }
  96 + return false;
  97 + } catch (Exception e) {
  98 + return false;
  99 + }
  100 + }
  101 +
  102 + /**
  103 + * 是否是小米刘海屏机型
  104 + */
  105 + @SuppressWarnings("unchecked")
  106 + @SuppressLint("PrivateApi")
  107 + private static boolean hasCutoutXIAOMI(Activity activity) {
  108 + if (!Build.MANUFACTURER.equalsIgnoreCase("xiaomi")) {
  109 + return false;
  110 + }
  111 + try {
  112 + ClassLoader cl = activity.getClassLoader();
  113 + Class SystemProperties = cl.loadClass("android.os.SystemProperties");
  114 + Class[] paramTypes = new Class[2];
  115 + paramTypes[0] = String.class;
  116 + paramTypes[1] = int.class;
  117 + Method getInt = SystemProperties.getMethod("getInt", paramTypes);
  118 + //参数
  119 + Object[] params = new Object[2];
  120 + params[0] = "ro.miui.notch";
  121 + params[1] = 0;
  122 + int hasCutout = (int) getInt.invoke(SystemProperties, params);
  123 + return hasCutout == 1;
  124 + } catch (Exception e) {
  125 + return false;
  126 + }
  127 + }
  128 +
  129 + /**
  130 + * 适配刘海屏,针对Android P以上系统
  131 + */
  132 + public static void adaptCutoutAboveAndroidP(Context context, boolean isAdapt) {
  133 + Activity activity = PlayerUtils.scanForActivity(context);
  134 + if (activity == null) return;
  135 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
  136 + WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
  137 + if (isAdapt) {
  138 + lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
  139 + } else {
  140 + lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT;
  141 + }
  142 + activity.getWindow().setAttributes(lp);
  143 + }
  144 + }
  145 +
  146 +}
cloud/video/src/main/java/com/libs/video/video/exoplayer/util/L.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.util;
  2 +
  3 +import android.util.Log;
  4 +
  5 +import com.libs.video.video.exoplayer.play.VideoViewManager;
  6 +
  7 +
  8 +/**
  9 + * 日志类
  10 + */
  11 +
  12 +public final class L {
  13 +
  14 + private L() {
  15 + }
  16 +
  17 + private static final String TAG = "CNLiveVideoPlayer";
  18 +
  19 + private static boolean isDebug = VideoViewManager.getConfig().mIsEnableLog;
  20 +
  21 +
  22 + public static void d(String msg) {
  23 + if (isDebug) {
  24 + Log.d(TAG, msg);
  25 + }
  26 + }
  27 +
  28 + public static void e(String msg) {
  29 + if (isDebug) {
  30 + Log.e(TAG, msg);
  31 + }
  32 + }
  33 +
  34 + public static void i(String msg) {
  35 + if (isDebug) {
  36 + Log.i(TAG, msg);
  37 + }
  38 + }
  39 +
  40 + public static void w(String msg) {
  41 + if (isDebug) {
  42 + Log.w(TAG, msg);
  43 + }
  44 + }
  45 +
  46 + public static void setDebug(boolean isDebug) {
  47 + L.isDebug = isDebug;
  48 + }
  49 +}
cloud/video/src/main/java/com/libs/video/video/exoplayer/util/PlayerFactoryUtils.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.util;
  2 +
  3 +import android.view.View;
  4 +import android.view.ViewParent;
  5 +import android.widget.FrameLayout;
  6 +
  7 +
  8 +import com.libs.video.video.exoplayer.play.CNVideoView;
  9 +import com.libs.video.video.exoplayer.play.VideoViewConfig;
  10 +import com.libs.video.video.exoplayer.play.VideoViewManager;
  11 +
  12 +import java.lang.reflect.Field;
  13 +
  14 +public final class PlayerFactoryUtils {
  15 +
  16 + private PlayerFactoryUtils() {
  17 + }
  18 +
  19 +
  20 + /**
  21 + * 获取当前的播放核心
  22 + */
  23 + public static Object getCurrentPlayerFactory() {
  24 + VideoViewConfig config = VideoViewManager.getConfig();
  25 + Object playerFactory = null;
  26 + try {
  27 + Field mPlayerFactoryField = config.getClass().getDeclaredField("mPlayerFactory");
  28 + mPlayerFactoryField.setAccessible(true);
  29 + playerFactory = mPlayerFactoryField.get(config);
  30 + } catch (Exception e) {
  31 + e.printStackTrace();
  32 + }
  33 + return playerFactory;
  34 + }
  35 +
  36 + /**
  37 + * 将View从父控件中移除
  38 + */
  39 + public static void removeViewFormParent(View v) {
  40 + if (v == null) return;
  41 + ViewParent parent = v.getParent();
  42 + if (parent instanceof FrameLayout) {
  43 + ((FrameLayout) parent).removeView(v);
  44 + }
  45 + }
  46 +
  47 + /**
  48 + * Returns a string containing player state debugging information.
  49 + */
  50 + public static String playState2str(int state) {
  51 + String playStateString;
  52 + switch (state) {
  53 + default:
  54 + case CNVideoView.STATE_IDLE:
  55 + playStateString = "idle";
  56 + break;
  57 + case CNVideoView.STATE_PREPARING:
  58 + playStateString = "preparing";
  59 + break;
  60 + case CNVideoView.STATE_PREPARED:
  61 + playStateString = "prepared";
  62 + break;
  63 + case CNVideoView.STATE_PLAYING:
  64 + playStateString = "playing";
  65 + break;
  66 + case CNVideoView.STATE_PAUSED:
  67 + playStateString = "pause";
  68 + break;
  69 + case CNVideoView.STATE_BUFFERING:
  70 + playStateString = "buffering";
  71 + break;
  72 + case CNVideoView.STATE_BUFFERED:
  73 + playStateString = "buffered";
  74 + break;
  75 + case CNVideoView.STATE_PLAYBACK_COMPLETED:
  76 + playStateString = "playback completed";
  77 + break;
  78 + case CNVideoView.STATE_ERROR:
  79 + playStateString = "error";
  80 + break;
  81 + }
  82 + return String.format("playState: %s", playStateString);
  83 + }
  84 +
  85 + /**
  86 + * Returns a string containing player state debugging information.
  87 + */
  88 + public static String playerState2str(int state) {
  89 + String playerStateString;
  90 + switch (state) {
  91 + default:
  92 + case CNVideoView.PLAYER_NORMAL:
  93 + playerStateString = "normal";
  94 + break;
  95 + case CNVideoView.PLAYER_FULL_SCREEN:
  96 + playerStateString = "full screen";
  97 + break;
  98 + case CNVideoView.PLAYER_TINY_SCREEN:
  99 + playerStateString = "tiny screen";
  100 + break;
  101 + }
  102 + return String.format("playerState: %s", playerStateString);
  103 + }
  104 +
  105 +
  106 +}
cloud/video/src/main/java/com/libs/video/video/exoplayer/util/PlayerUtils.java 0 → 100644
  1 +package com.libs.video.video.exoplayer.util;
  2 +
  3 +import android.annotation.SuppressLint;
  4 +import android.app.Activity;
  5 +import android.app.Application;
  6 +import android.content.Context;
  7 +import android.content.ContextWrapper;
  8 +import android.content.res.Resources;
  9 +import android.graphics.Point;
  10 +import android.net.ConnectivityManager;
  11 +import android.net.NetworkInfo;
  12 +import android.os.Build;
  13 +import android.telephony.TelephonyManager;
  14 +import android.util.TypedValue;
  15 +import android.view.Display;
  16 +import android.view.KeyCharacterMap;
  17 +import android.view.KeyEvent;
  18 +import android.view.MotionEvent;
  19 +import android.view.ViewConfiguration;
  20 +import android.view.WindowManager;
  21 +
  22 +import java.text.SimpleDateFormat;
  23 +import java.util.ArrayList;
  24 +import java.util.Collection;
  25 +import java.util.Date;
  26 +import java.util.List;
  27 +import java.util.Locale;
  28 +
  29 +import androidx.annotation.NonNull;
  30 +
  31 +/**
  32 + * 播放器相关工具类
  33 + */
  34 +
  35 +public final class PlayerUtils {
  36 +
  37 + private PlayerUtils() {
  38 + }
  39 +
  40 + /**
  41 + * 获取状态栏高度
  42 + */
  43 + public static double getStatusBarHeight(Context context) {
  44 + int statusBarHeight = 0;
  45 + //获取status_bar_height资源的ID
  46 + int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");
  47 + if (resourceId > 0) {
  48 + //根据资源ID获取响应的尺寸值
  49 + statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
  50 + }
  51 + return statusBarHeight;
  52 + }
  53 +
  54 + /**
  55 + * 获取竖屏下状态栏高度
  56 + */
  57 + public static double getStatusBarHeightPortrait(Context context) {
  58 + int statusBarHeight = 0;
  59 + //获取status_bar_height_portrait资源的ID
  60 + int resourceId = context.getResources().getIdentifier("status_bar_height_portrait", "dimen", "android");
  61 + if (resourceId > 0) {
  62 + //根据资源ID获取响应的尺寸值
  63 + statusBarHeight = context.getResources().getDimensionPixelSize(resourceId);
  64 + }
  65 + return statusBarHeight;
  66 + }
  67 +
  68 + /**
  69 + * 获取NavigationBar的高度
  70 + */
  71 + public static int getNavigationBarHeight(Context context) {
  72 + if (!hasNavigationBar(context)) {
  73 + return 0;
  74 + }
  75 + Resources resources = context.getResources();
  76 + int resourceId = resources.getIdentifier("navigation_bar_height",
  77 + "dimen", "android");
  78 + //获取NavigationBar的高度
  79 + return resources.getDimensionPixelSize(resourceId);
  80 + }
  81 +
  82 + /**
  83 + * 是否存在NavigationBar
  84 + */
  85 + public static boolean hasNavigationBar(Context context) {
  86 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
  87 + Display display = getWindowManager(context).getDefaultDisplay();
  88 + Point size = new Point();
  89 + Point realSize = new Point();
  90 + display.getSize(size);
  91 + display.getRealSize(realSize);
  92 + return realSize.x != size.x || realSize.y != size.y;
  93 + } else {
  94 + boolean menu = ViewConfiguration.get(context).hasPermanentMenuKey();
  95 + boolean back = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
  96 + return !(menu || back);
  97 + }
  98 + }
  99 +
  100 + /**
  101 + * 获取屏幕宽度
  102 + */
  103 + public static int getScreenWidth(Context context, boolean isIncludeNav) {
  104 + if (isIncludeNav) {
  105 + return context.getResources().getDisplayMetrics().widthPixels + getNavigationBarHeight(context);
  106 + } else {
  107 + return context.getResources().getDisplayMetrics().widthPixels;
  108 + }
  109 + }
  110 +
  111 + /**
  112 + * 获取屏幕高度
  113 + */
  114 + public static int getScreenHeight(Context context, boolean isIncludeNav) {
  115 + if (isIncludeNav) {
  116 + return context.getResources().getDisplayMetrics().heightPixels + getNavigationBarHeight(context);
  117 + } else {
  118 + return context.getResources().getDisplayMetrics().heightPixels;
  119 + }
  120 + }
  121 +
  122 + /**
  123 + * 获取Activity
  124 + */
  125 + public static Activity scanForActivity(Context context) {
  126 + if (context == null) return null;
  127 + if (context instanceof Activity) {
  128 + return (Activity) context;
  129 + } else if (context instanceof ContextWrapper) {
  130 + return scanForActivity(((ContextWrapper) context).getBaseContext());
  131 + }
  132 + return null;
  133 + }
  134 +
  135 + /**
  136 + * dp转为px
  137 + */
  138 + public static int dp2px(Context context, float dpValue) {
  139 + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, context.getResources().getDisplayMetrics());
  140 + }
  141 +
  142 + /**
  143 + * sp转为px
  144 + */
  145 + public static int sp2px(Context context, float dpValue) {
  146 + return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, dpValue, context.getResources().getDisplayMetrics());
  147 + }
  148 +
  149 + /**
  150 + * 如果WindowManager还未创建,则创建一个新的WindowManager返回。否则返回当前已创建的WindowManager。
  151 + */
  152 + public static WindowManager getWindowManager(Context context) {
  153 + return (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
  154 + }
  155 +
  156 + /**
  157 + * 边缘检测
  158 + */
  159 + public static boolean isEdge(Context context, MotionEvent e) {
  160 + int edgeSize = dp2px(context, 40);
  161 + return e.getRawX() < edgeSize
  162 + || e.getRawX() > getScreenWidth(context, true) - edgeSize
  163 + || e.getRawY() < edgeSize
  164 + || e.getRawY() > getScreenHeight(context, true) - edgeSize;
  165 + }
  166 +
  167 +
  168 + public static final int NO_NETWORK = 0;
  169 + public static final int NETWORK_CLOSED = 1;
  170 + public static final int NETWORK_ETHERNET = 2;
  171 + public static final int NETWORK_WIFI = 3;
  172 + public static final int NETWORK_MOBILE = 4;
  173 + public static final int NETWORK_UNKNOWN = -1;
  174 +
  175 + /**
  176 + * 判断当前网络类型
  177 + */
  178 + public static int getNetworkType(Context context) {
  179 + //改为context.getApplicationContext(),防止在Android 6.0上发生内存泄漏
  180 + ConnectivityManager connectMgr = (ConnectivityManager) context.getApplicationContext()
  181 + .getSystemService(Context.CONNECTIVITY_SERVICE);
  182 +
  183 + if (connectMgr == null) {
  184 + return NO_NETWORK;
  185 + }
  186 +
  187 + NetworkInfo networkInfo = connectMgr.getActiveNetworkInfo();
  188 + if (networkInfo == null) {
  189 + // 没有任何网络
  190 + return NO_NETWORK;
  191 + }
  192 + if (!networkInfo.isConnected()) {
  193 + // 网络断开或关闭
  194 + return NETWORK_CLOSED;
  195 + }
  196 + if (networkInfo.getType() == ConnectivityManager.TYPE_ETHERNET) {
  197 + // 以太网网络
  198 + return NETWORK_ETHERNET;
  199 + } else if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
  200 + // wifi网络,当激活时,默认情况下,所有的数据流量将使用此连接
  201 + return NETWORK_WIFI;
  202 + } else if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
  203 + // 移动数据连接,不能与连接共存,如果wifi打开,则自动关闭
  204 + switch (networkInfo.getSubtype()) {
  205 + // 2G
  206 + case TelephonyManager.NETWORK_TYPE_GPRS:
  207 + case TelephonyManager.NETWORK_TYPE_EDGE:
  208 + case TelephonyManager.NETWORK_TYPE_CDMA:
  209 + case TelephonyManager.NETWORK_TYPE_1xRTT:
  210 + case TelephonyManager.NETWORK_TYPE_IDEN:
  211 + // 3G
  212 + case TelephonyManager.NETWORK_TYPE_UMTS:
  213 + case TelephonyManager.NETWORK_TYPE_EVDO_0:
  214 + case TelephonyManager.NETWORK_TYPE_EVDO_A:
  215 + case TelephonyManager.NETWORK_TYPE_HSDPA:
  216 + case TelephonyManager.NETWORK_TYPE_HSUPA:
  217 + case TelephonyManager.NETWORK_TYPE_HSPA:
  218 + case TelephonyManager.NETWORK_TYPE_EVDO_B:
  219 + case TelephonyManager.NETWORK_TYPE_EHRPD:
  220 + case TelephonyManager.NETWORK_TYPE_HSPAP:
  221 + // 4G
  222 + case TelephonyManager.NETWORK_TYPE_LTE:
  223 + // 5G
  224 + case TelephonyManager.NETWORK_TYPE_NR:
  225 + return NETWORK_MOBILE;
  226 + }
  227 + }
  228 + // 未知网络
  229 + return NETWORK_UNKNOWN;
  230 + }
  231 +
  232 + /**
  233 + * 通过反射获取Application
  234 + *
  235 + * @deprecated 不在使用,后期谷歌可能封掉改接口
  236 + */
  237 + @SuppressLint("PrivateApi")
  238 + @Deprecated
  239 + public static Application getApplication() {
  240 + try {
  241 + return (Application) Class.forName("android.app.ActivityThread")
  242 + .getMethod("currentApplication").invoke(null);
  243 + } catch (Exception e) {
  244 + e.printStackTrace();
  245 + }
  246 + return null;
  247 + }
  248 +
  249 + /**
  250 + * 获取当前系统时间
  251 + */
  252 + public static String getCurrentSystemTime() {
  253 + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm", Locale.getDefault());
  254 + Date date = new Date();
  255 + return simpleDateFormat.format(date);
  256 + }
  257 +
  258 + /**
  259 + * 格式化时间
  260 + */
  261 + public static String stringForTime(int timeMs) {
  262 + int totalSeconds = timeMs / 1000;
  263 +
  264 + int seconds = totalSeconds % 60;
  265 + int minutes = (totalSeconds / 60) % 60;
  266 + int hours = totalSeconds / 3600;
  267 +
  268 + if (hours > 0) {
  269 + return String.format(Locale.getDefault(), "%d:%02d:%02d", hours, minutes, seconds);
  270 + } else {
  271 + return String.format(Locale.getDefault(), "%02d:%02d", minutes, seconds);
  272 + }
  273 + }
  274 +
  275 + /**
  276 + * 获取集合的快照
  277 + */
  278 + @NonNull
  279 + public static <T> List<T> getSnapshot(@NonNull Collection<T> other) {
  280 + List<T> result = new ArrayList<>(other.size());
  281 + for (T item : other) {
  282 + if (item != null) {
  283 + result.add(item);
  284 + }
  285 + }
  286 + return result;
  287 + }
  288 +}
cloud/video/src/main/java/com/libs/video/video/exoplayer/BaseTimerBar.java renamed to cloud/video/src/main/java/com/libs/video/video/test/BaseTimerBar.java
1 -package com.libs.video.video.exoplayer; 1 +package com.libs.video.video.test;
2 2
3 import android.annotation.TargetApi; 3 import android.annotation.TargetApi;
4 import android.content.Context; 4 import android.content.Context;
cloud/video/src/main/java/com/libs/video/video/exoplayer/CNLiveBaseVideoControlView.java renamed to cloud/video/src/main/java/com/libs/video/video/test/BaseVideoController.java
1 -package com.libs.video.video.exoplayer; 1 +package com.libs.video.video.test;
2 2
3 import android.annotation.SuppressLint; 3 import android.annotation.SuppressLint;
4 import android.app.Activity; 4 import android.app.Activity;
@@ -16,10 +16,8 @@ import android.view.Surface; @@ -16,10 +16,8 @@ import android.view.Surface;
16 import android.view.View; 16 import android.view.View;
17 import android.view.ViewGroup; 17 import android.view.ViewGroup;
18 import android.widget.FrameLayout; 18 import android.widget.FrameLayout;
19 -import android.widget.LinearLayout;  
20 19
21 import androidx.annotation.Nullable; 20 import androidx.annotation.Nullable;
22 -import androidx.core.content.ContextCompat;  
23 21
24 import com.cnlive.cloud.video.R; 22 import com.cnlive.cloud.video.R;
25 import com.google.android.exoplayer2.C; 23 import com.google.android.exoplayer2.C;
@@ -48,7 +46,7 @@ import java.util.concurrent.CopyOnWriteArrayList; @@ -48,7 +46,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
48 /** 46 /**
49 * 视频控制视图基类 47 * 视频控制视图基类
50 */ 48 */
51 -public abstract class CNLiveBaseVideoControlView extends FrameLayout implements AnalyticsListener { 49 +public abstract class BaseVideoController extends FrameLayout implements AnalyticsListener {
52 50
53 static { 51 static {
54 ExoPlayerLibraryInfo.registerModule("goog.exo.ui"); 52 ExoPlayerLibraryInfo.registerModule("goog.exo.ui");
@@ -115,8 +113,8 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements @@ -115,8 +113,8 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements
115 */ 113 */
116 private static final int MAX_UPDATE_INTERVAL_MS = 1000; 114 private static final int MAX_UPDATE_INTERVAL_MS = 1000;
117 115
118 - private CNLiveBaseVideoControlView.ComponentListener componentListener;  
119 - private CopyOnWriteArrayList<CNLiveBaseVideoControlView.VisibilityListener> visibilityListeners; 116 + private BaseVideoController.ComponentListener componentListener;
  117 + private CopyOnWriteArrayList<BaseVideoController.VisibilityListener> visibilityListeners;
120 private StringBuilder formatBuilder; 118 private StringBuilder formatBuilder;
121 private Formatter formatter; 119 private Formatter formatter;
122 private Timeline.Period period; 120 private Timeline.Period period;
@@ -144,7 +142,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements @@ -144,7 +142,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements
144 //延时隐藏当前播放视图 142 //延时隐藏当前播放视图
145 private Runnable delayHideViewAction; 143 private Runnable delayHideViewAction;
146 144
147 - public CNLiveBaseVideoControlView(Context context) { 145 + public BaseVideoController(Context context) {
148 this(context, /* attrs= */ null, 0, null); 146 this(context, /* attrs= */ null, 0, null);
149 this.context = context; 147 this.context = context;
150 visibilityListeners = new CopyOnWriteArrayList<>(); 148 visibilityListeners = new CopyOnWriteArrayList<>();
@@ -180,7 +178,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements @@ -180,7 +178,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements
180 "nullness:method.invocation.invalid", 178 "nullness:method.invocation.invalid",
181 "nullness:methodref.receiver.bound.invalid" 179 "nullness:methodref.receiver.bound.invalid"
182 }) 180 })
183 - public CNLiveBaseVideoControlView( 181 + public BaseVideoController(
184 Context context, 182 Context context,
185 @Nullable AttributeSet attrs, 183 @Nullable AttributeSet attrs,
186 int defStyleAttr, 184 int defStyleAttr,
@@ -196,7 +194,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements @@ -196,7 +194,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements
196 window = new Timeline.Window(); 194 window = new Timeline.Window();
197 //初始化相关监听器 195 //初始化相关监听器
198 visibilityListeners = new CopyOnWriteArrayList<>(); 196 visibilityListeners = new CopyOnWriteArrayList<>();
199 - componentListener = new CNLiveBaseVideoControlView.ComponentListener(); 197 + componentListener = new BaseVideoController.ComponentListener();
200 // controlDispatcher = new CNLiveDefaultControlDispatcher(); 198 // controlDispatcher = new CNLiveDefaultControlDispatcher();
201 controllerLayoutId = R.layout.layout_cnlive_control_view; 199 controllerLayoutId = R.layout.layout_cnlive_control_view;
202 200
@@ -339,7 +337,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements @@ -339,7 +337,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements
339 } 337 }
340 } 338 }
341 339
342 - protected abstract void initPortraitView(View basePortraitView, CNLiveBaseVideoControlView.ComponentListener onClickListener); 340 + protected abstract void initPortraitView(View basePortraitView, BaseVideoController.ComponentListener onClickListener);
343 341
344 @SuppressWarnings("ResourceType") 342 @SuppressWarnings("ResourceType")
345 private static @RepeatModeUtil.RepeatToggleModes 343 private static @RepeatModeUtil.RepeatToggleModes
@@ -406,31 +404,31 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements @@ -406,31 +404,31 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements
406 } 404 }
407 405
408 /** 406 /**
409 - * Adds a {@link CNLiveBaseVideoControlView.VisibilityListener}. 407 + * Adds a {@link BaseVideoController.VisibilityListener}.
410 * 408 *
411 * @param listener The listener to be notified about visibility changes. 409 * @param listener The listener to be notified about visibility changes.
412 */ 410 */
413 - public void addVisibilityListener(CNLiveBaseVideoControlView.VisibilityListener listener) { 411 + public void addVisibilityListener(BaseVideoController.VisibilityListener listener) {
414 if (null != listener) { 412 if (null != listener) {
415 visibilityListeners.add(listener); 413 visibilityListeners.add(listener);
416 } 414 }
417 } 415 }
418 416
419 /** 417 /**
420 - * Removes a {@link CNLiveBaseVideoControlView.VisibilityListener}. 418 + * Removes a {@link BaseVideoController.VisibilityListener}.
421 * 419 *
422 * @param listener The listener to be removed. 420 * @param listener The listener to be removed.
423 */ 421 */
424 - public void removeVisibilityListener(CNLiveBaseVideoControlView.VisibilityListener listener) { 422 + public void removeVisibilityListener(BaseVideoController.VisibilityListener listener) {
425 visibilityListeners.remove(listener); 423 visibilityListeners.remove(listener);
426 } 424 }
427 425
428 /** 426 /**
429 - * Sets the {@link CNLiveBaseVideoControlView.ProgressUpdateListener}. 427 + * Sets the {@link BaseVideoController.ProgressUpdateListener}.
430 * 428 *
431 * @param listener The listener to be notified about when progress is updated. 429 * @param listener The listener to be notified about when progress is updated.
432 */ 430 */
433 - public void setProgressUpdateListener(@Nullable CNLiveBaseVideoControlView.ProgressUpdateListener listener) { 431 + public void setProgressUpdateListener(@Nullable BaseVideoController.ProgressUpdateListener listener) {
434 // this.progressUpdateListener = listener; 432 // this.progressUpdateListener = listener;
435 433
436 } 434 }
@@ -492,7 +490,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements @@ -492,7 +490,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements
492 basePortraitView.setVisibility(VISIBLE); 490 basePortraitView.setVisibility(VISIBLE);
493 } 491 }
494 if (null != visibilityListeners && visibilityListeners.size() > 0) { 492 if (null != visibilityListeners && visibilityListeners.size() > 0) {
495 - for (CNLiveBaseVideoControlView.VisibilityListener visibilityListener : visibilityListeners) { 493 + for (BaseVideoController.VisibilityListener visibilityListener : visibilityListeners) {
496 visibilityListener.onVisibilityChange(getVisibility()); 494 visibilityListener.onVisibilityChange(getVisibility());
497 } 495 }
498 } 496 }
@@ -513,7 +511,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements @@ -513,7 +511,7 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements
513 basePortraitView.setVisibility(GONE); 511 basePortraitView.setVisibility(GONE);
514 } 512 }
515 if (null != visibilityListeners && visibilityListeners.size() > 0) { 513 if (null != visibilityListeners && visibilityListeners.size() > 0) {
516 - for (CNLiveBaseVideoControlView.VisibilityListener visibilityListener : visibilityListeners) { 514 + for (BaseVideoController.VisibilityListener visibilityListener : visibilityListeners) {
517 visibilityListener.onVisibilityChange(getVisibility()); 515 visibilityListener.onVisibilityChange(getVisibility());
518 } 516 }
519 } 517 }
@@ -836,16 +834,6 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements @@ -836,16 +834,6 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements
836 // removeCallbacks(hideAction); 834 // removeCallbacks(hideAction);
837 } 835 }
838 836
839 -// @Override  
840 -// public final boolean dispatchTouchEvent(MotionEvent ev) {  
841 -// if (ev.getAction() == MotionEvent.ACTION_DOWN) {  
842 -// removeCallbacks(hideAction);  
843 -// } else if (ev.getAction() == MotionEvent.ACTION_UP) {  
844 -// hideAfterTimeout();  
845 -// }  
846 -// return super.dispatchTouchEvent(ev);  
847 -// }  
848 -  
849 @Override 837 @Override
850 public boolean dispatchKeyEvent(KeyEvent event) { 838 public boolean dispatchKeyEvent(KeyEvent event) {
851 return dispatchMediaKeyEvent(event) || super.dispatchKeyEvent(event); 839 return dispatchMediaKeyEvent(event) || super.dispatchKeyEvent(event);
@@ -915,25 +903,6 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements @@ -915,25 +903,6 @@ public abstract class CNLiveBaseVideoControlView extends FrameLayout implements
915 || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS; 903 || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS;
916 } 904 }
917 905
918 - /**  
919 - * Returns whether the specified {@code timeline} can be shown on a multi-window time bar.  
920 - *  
921 - * @param timeline The {@link Timeline} to check.  
922 - * @param window A scratch {@link Timeline.Window} instance.  
923 - * @return Whether the specified timeline can be shown on a multi-window time bar.  
924 - */  
925 - private static boolean canShowMultiWindowTimeBar(Timeline timeline, Timeline.Window window) {  
926 - if (timeline.getWindowCount() > MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR) {  
927 - return false;  
928 - }  
929 - int windowCount = timeline.getWindowCount();  
930 - for (int i = 0; i < windowCount; i++) {  
931 - if (timeline.getWindow(i, window).durationUs == C.TIME_UNSET) {  
932 - return false;  
933 - }  
934 - }  
935 - return true;  
936 - }  
937 906
938 public final class ComponentListener 907 public final class ComponentListener
939 implements OnClickListener { 908 implements OnClickListener {
cloud/video/src/main/java/com/libs/video/video/exoplayer/CNLiveControlDispatcher.java renamed to cloud/video/src/main/java/com/libs/video/video/test/CNLiveControlDispatcher.java
1 -package com.libs.video.video.exoplayer; 1 +package com.libs.video.video.test;
2 2
3 3
4 import com.google.android.exoplayer2.Player; 4 import com.google.android.exoplayer2.Player;
cloud/video/src/main/java/com/libs/video/video/exoplayer/CNLiveDefaultControlDispatcher.java renamed to cloud/video/src/main/java/com/libs/video/video/test/CNLiveDefaultControlDispatcher.java
1 -package com.libs.video.video.exoplayer; 1 +package com.libs.video.video.test;
2 2
3 -import com.google.android.exoplayer2.ControlDispatcher;  
4 import com.google.android.exoplayer2.Player; 3 import com.google.android.exoplayer2.Player;
5 4
6 public class CNLiveDefaultControlDispatcher implements CNLiveControlDispatcher { 5 public class CNLiveDefaultControlDispatcher implements CNLiveControlDispatcher {
cloud/video/src/main/java/com/libs/video/video/exoplayer/CNLiveVideoView.java renamed to cloud/video/src/main/java/com/libs/video/video/test/CNLiveVideoView.java
1 -package com.libs.video.video.exoplayer; 1 +package com.libs.video.video.test;
2 2
3 import android.annotation.SuppressLint; 3 import android.annotation.SuppressLint;
4 import android.annotation.TargetApi; 4 import android.annotation.TargetApi;
@@ -30,9 +30,7 @@ import android.widget.FrameLayout; @@ -30,9 +30,7 @@ import android.widget.FrameLayout;
30 import android.widget.ImageView; 30 import android.widget.ImageView;
31 import android.widget.LinearLayout; 31 import android.widget.LinearLayout;
32 import android.widget.TextView; 32 import android.widget.TextView;
33 -import android.widget.Toast;  
34 33
35 -import androidx.annotation.ColorInt;  
36 import androidx.annotation.IntDef; 34 import androidx.annotation.IntDef;
37 import androidx.annotation.Nullable; 35 import androidx.annotation.Nullable;
38 import androidx.core.content.ContextCompat; 36 import androidx.core.content.ContextCompat;
@@ -43,7 +41,6 @@ import com.google.android.exoplayer2.C; @@ -43,7 +41,6 @@ import com.google.android.exoplayer2.C;
43 import com.google.android.exoplayer2.ControlDispatcher; 41 import com.google.android.exoplayer2.ControlDispatcher;
44 import com.google.android.exoplayer2.DefaultControlDispatcher; 42 import com.google.android.exoplayer2.DefaultControlDispatcher;
45 import com.google.android.exoplayer2.ExoPlaybackException; 43 import com.google.android.exoplayer2.ExoPlaybackException;
46 -import com.google.android.exoplayer2.PlaybackPreparer;  
47 import com.google.android.exoplayer2.Player; 44 import com.google.android.exoplayer2.Player;
48 import com.google.android.exoplayer2.analytics.AnalyticsListener; 45 import com.google.android.exoplayer2.analytics.AnalyticsListener;
49 import com.google.android.exoplayer2.metadata.Metadata; 46 import com.google.android.exoplayer2.metadata.Metadata;
@@ -68,8 +65,6 @@ import com.google.android.exoplayer2.util.Util; @@ -68,8 +65,6 @@ import com.google.android.exoplayer2.util.Util;
68 import com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView; 65 import com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView;
69 import com.google.android.exoplayer2.video.VideoListener; 66 import com.google.android.exoplayer2.video.VideoListener;
70 67
71 -import org.w3c.dom.Text;  
72 -  
73 import java.io.IOException; 68 import java.io.IOException;
74 import java.lang.annotation.Documented; 69 import java.lang.annotation.Documented;
75 import java.lang.annotation.Retention; 70 import java.lang.annotation.Retention;
@@ -136,14 +131,14 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv @@ -136,14 +131,14 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv
136 @Nullable 131 @Nullable
137 private final TextView tvErrorMsg;//错误信息 132 private final TextView tvErrorMsg;//错误信息
138 @Nullable 133 @Nullable
139 - private CNLiveBaseVideoControlView controller; 134 + private BaseVideoController controller;
140 @Nullable 135 @Nullable
141 private final FrameLayout adOverlayFrameLayout; 136 private final FrameLayout adOverlayFrameLayout;
142 @Nullable 137 @Nullable
143 private final FrameLayout overlayFrameLayout; 138 private final FrameLayout overlayFrameLayout;
144 private boolean useController; 139 private boolean useController;
145 @Nullable 140 @Nullable
146 - private CNLiveBaseVideoControlView.VisibilityListener controllerVisibilityListener; 141 + private BaseVideoController.VisibilityListener controllerVisibilityListener;
147 private boolean useArtwork; 142 private boolean useArtwork;
148 @Nullable 143 @Nullable
149 private Drawable defaultArtwork; 144 private Drawable defaultArtwork;
@@ -229,7 +224,7 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv @@ -229,7 +224,7 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv
229 boolean useController = true; 224 boolean useController = true;
230 int surfaceType = SURFACE_TYPE_SURFACE_VIEW; 225 int surfaceType = SURFACE_TYPE_SURFACE_VIEW;
231 int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT; 226 int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
232 - int controllerShowTimeoutMs = CNLiveBaseVideoControlView.DEFAULT_SHOW_TIMEOUT_MS; 227 + int controllerShowTimeoutMs = BaseVideoController.DEFAULT_SHOW_TIMEOUT_MS;
233 boolean controllerHideOnTouch = true; 228 boolean controllerHideOnTouch = true;
234 boolean controllerAutoShow = true; 229 boolean controllerAutoShow = true;
235 boolean controllerHideDuringAds = true; 230 boolean controllerHideDuringAds = true;
@@ -350,7 +345,7 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv @@ -350,7 +345,7 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv
350 } 345 }
351 }); 346 });
352 // Playback control view. 347 // Playback control view.
353 - CNLiveBaseVideoControlView customController = findViewById(R.id.cnlive_controller); 348 + BaseVideoController customController = findViewById(R.id.cnlive_controller);
354 controllerPlaceholder = findViewById(R.id.cnlive_controller_placeholder); 349 controllerPlaceholder = findViewById(R.id.cnlive_controller_placeholder);
355 if (customController != null) { 350 if (customController != null) {
356 this.controller = customController; 351 this.controller = customController;
@@ -409,7 +404,7 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv @@ -409,7 +404,7 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv
409 404
410 } 405 }
411 406
412 - public void setCustomControllView(CNLiveBaseVideoControlView controllerView) { 407 + public void setCustomControllView(BaseVideoController controllerView) {
413 if (null != controllerPlaceholder && null != controllerView) { 408 if (null != controllerPlaceholder && null != controllerView) {
414 ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent()); 409 ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent());
415 if (null != parent) { 410 if (null != parent) {
@@ -880,13 +875,13 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv @@ -880,13 +875,13 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv
880 } 875 }
881 876
882 /** 877 /**
883 - * Set the {@link CNLiveBaseVideoControlView.VisibilityListener}. 878 + * Set the {@link BaseVideoController.VisibilityListener}.
884 * 879 *
885 * @param listener The listener to be notified about visibility changes, or null to remove the 880 * @param listener The listener to be notified about visibility changes, or null to remove the
886 * current listener. 881 * current listener.
887 */ 882 */
888 public void setControllerVisibilityListener( 883 public void setControllerVisibilityListener(
889 - @Nullable CNLiveBaseVideoControlView.VisibilityListener listener) { 884 + @Nullable BaseVideoController.VisibilityListener listener) {
890 Assertions.checkStateNotNull(controller); 885 Assertions.checkStateNotNull(controller);
891 if (this.controllerVisibilityListener == listener) { 886 if (this.controllerVisibilityListener == listener) {
892 return; 887 return;
@@ -1399,7 +1394,7 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv @@ -1399,7 +1394,7 @@ public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProv
1399 VideoListener, 1394 VideoListener,
1400 OnLayoutChangeListener, 1395 OnLayoutChangeListener,
1401 SingleTapListener, 1396 SingleTapListener,
1402 - CNLiveBaseVideoControlView.VisibilityListener { 1397 + BaseVideoController.VisibilityListener {
1403 1398
1404 // TextOutput implementation 1399 // TextOutput implementation
1405 1400
cloud/video/src/main/java/com/libs/video/video/exoplayer/CacheUtil.java renamed to cloud/video/src/main/java/com/libs/video/video/test/CacheUtil.java
1 -package com.libs.video.video.exoplayer; 1 +package com.libs.video.video.test;
2 2
3 import android.content.ContentValues; 3 import android.content.ContentValues;
4 import android.content.Context; 4 import android.content.Context;
@@ -10,7 +10,6 @@ import android.text.TextUtils; @@ -10,7 +10,6 @@ import android.text.TextUtils;
10 import android.util.Log; 10 import android.util.Log;
11 11
12 import com.libs.video.cache.utils.DesUtil; 12 import com.libs.video.cache.utils.DesUtil;
13 -import com.libs.video.util.Config;  
14 13
15 import java.security.InvalidKeyException; 14 import java.security.InvalidKeyException;
16 import java.security.spec.InvalidKeySpecException; 15 import java.security.spec.InvalidKeySpecException;
cloud/video/src/main/java/com/libs/video/video/exoplayer/IVideoDataSource.java renamed to cloud/video/src/main/java/com/libs/video/video/test/IVideoDataSource.java
1 -package com.libs.video.video.exoplayer; 1 +package com.libs.video.video.test;
2 2
3 import android.net.Uri; 3 import android.net.Uri;
4 4
cloud/video/src/main/java/com/libs/video/video/exoplayer/IVideoMultDataSource.java renamed to cloud/video/src/main/java/com/libs/video/video/test/IVideoMultDataSource.java
1 -package com.libs.video.video.exoplayer; 1 +package com.libs.video.video.test;
2 2
3 import android.net.Uri; 3 import android.net.Uri;
4 4
cloud/video/src/main/java/com/libs/video/video/exoplayer/IVideoPlayer.java renamed to cloud/video/src/main/java/com/libs/video/video/test/IVideoPlayer.java
1 -package com.libs.video.video.exoplayer; 1 +package com.libs.video.video.test;
2 2
3 import android.net.Uri; 3 import android.net.Uri;
4 import android.os.Looper; 4 import android.os.Looper;
@@ -12,12 +12,8 @@ import com.google.android.exoplayer2.Player; @@ -12,12 +12,8 @@ import com.google.android.exoplayer2.Player;
12 import com.google.android.exoplayer2.Renderer; 12 import com.google.android.exoplayer2.Renderer;
13 import com.google.android.exoplayer2.Timeline; 13 import com.google.android.exoplayer2.Timeline;
14 import com.google.android.exoplayer2.analytics.AnalyticsListener; 14 import com.google.android.exoplayer2.analytics.AnalyticsListener;
15 -import com.google.android.exoplayer2.source.MediaSource;  
16 import com.google.android.exoplayer2.source.TrackGroupArray; 15 import com.google.android.exoplayer2.source.TrackGroupArray;
17 import com.google.android.exoplayer2.trackselection.TrackSelectionArray; 16 import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
18 -import com.google.android.exoplayer2.ui.PlayerView;  
19 -import com.libs.video.video.base.IDataSource;  
20 -import com.libs.video.video.base.IMediaPlayer;  
21 17
22 import java.util.List; 18 import java.util.List;
23 19
cloud/video/src/main/java/com/libs/video/video/MediaPlayer.java renamed to cloud/video/src/main/java/com/libs/video/video/test/MediaPlayer.java
1 -package com.libs.video.video; 1 +package com.libs.video.video.test;
2 2
3 import android.content.Context; 3 import android.content.Context;
4 import android.graphics.Bitmap; 4 import android.graphics.Bitmap;
cloud/video/src/main/java/com/libs/video/video/exoplayer/MyTestControlView.java renamed to cloud/video/src/main/java/com/libs/video/video/test/MyTestControlView.java
1 -package com.libs.video.video.exoplayer; 1 +package com.libs.video.video.test;
2 2
3 import android.content.Context; 3 import android.content.Context;
4 import android.util.AttributeSet; 4 import android.util.AttributeSet;
@@ -10,12 +10,11 @@ import android.widget.TextView; @@ -10,12 +10,11 @@ import android.widget.TextView;
10 10
11 import androidx.annotation.Nullable; 11 import androidx.annotation.Nullable;
12 import androidx.appcompat.widget.AppCompatSeekBar; 12 import androidx.appcompat.widget.AppCompatSeekBar;
13 -import androidx.core.content.ContextCompat;  
14 13
15 import com.cnlive.cloud.video.R; 14 import com.cnlive.cloud.video.R;
16 import com.google.android.exoplayer2.source.MediaSourceEventListener; 15 import com.google.android.exoplayer2.source.MediaSourceEventListener;
17 16
18 -public class MyTestControlView extends CNLiveBaseVideoControlView { 17 +public class MyTestControlView extends BaseVideoController {
19 private View playButton; 18 private View playButton;
20 private ImageView ivPortraitPlay; 19 private ImageView ivPortraitPlay;
21 private String TAG = getClass().getName(); 20 private String TAG = getClass().getName();
cloud/video/src/main/java/com/libs/video/video/StatisticsDuration.java renamed to cloud/video/src/main/java/com/libs/video/video/test/StatisticsDuration.java
1 -package com.libs.video.video; 1 +package com.libs.video.video.test;
2 2
3 import android.os.Handler; 3 import android.os.Handler;
4 import android.os.Message; 4 import android.os.Message;
cloud/video/src/main/java/com/libs/video/video/SwitchVideoView.java renamed to cloud/video/src/main/java/com/libs/video/video/test/SwitchVideoView.java
1 -package com.libs.video.video; 1 +package com.libs.video.video.test;
2 2
3 import android.content.Context; 3 import android.content.Context;
4 import android.net.Uri; 4 import android.net.Uri;
cloud/video/src/main/java/com/libs/video/video/exoplayer/VideoPlayer.java renamed to cloud/video/src/main/java/com/libs/video/video/test/VideoPlayer.java
1 -package com.libs.video.video.exoplayer; 1 +package com.libs.video.video.test;
2 2
3 import android.content.Context; 3 import android.content.Context;
4 import android.net.Uri; 4 import android.net.Uri;
@@ -14,33 +14,21 @@ import com.google.android.exoplayer2.Player; @@ -14,33 +14,21 @@ import com.google.android.exoplayer2.Player;
14 import com.google.android.exoplayer2.SimpleExoPlayer; 14 import com.google.android.exoplayer2.SimpleExoPlayer;
15 import com.google.android.exoplayer2.Timeline; 15 import com.google.android.exoplayer2.Timeline;
16 import com.google.android.exoplayer2.analytics.AnalyticsListener; 16 import com.google.android.exoplayer2.analytics.AnalyticsListener;
17 -import com.google.android.exoplayer2.ext.ima.ImaAdsLoader;  
18 import com.google.android.exoplayer2.ext.rtmp.RtmpDataSourceFactory; 17 import com.google.android.exoplayer2.ext.rtmp.RtmpDataSourceFactory;
19 import com.google.android.exoplayer2.metadata.Metadata; 18 import com.google.android.exoplayer2.metadata.Metadata;
20 -import com.google.android.exoplayer2.metadata.MetadataOutput;  
21 import com.google.android.exoplayer2.source.ConcatenatingMediaSource; 19 import com.google.android.exoplayer2.source.ConcatenatingMediaSource;
22 -import com.google.android.exoplayer2.source.ExtractorMediaSource;  
23 import com.google.android.exoplayer2.source.MediaSource; 20 import com.google.android.exoplayer2.source.MediaSource;
24 import com.google.android.exoplayer2.source.ProgressiveMediaSource; 21 import com.google.android.exoplayer2.source.ProgressiveMediaSource;
25 import com.google.android.exoplayer2.source.TrackGroupArray; 22 import com.google.android.exoplayer2.source.TrackGroupArray;
26 -import com.google.android.exoplayer2.source.ads.AdsLoader;  
27 -import com.google.android.exoplayer2.source.ads.AdsMediaSource;  
28 import com.google.android.exoplayer2.source.dash.DashMediaSource; 23 import com.google.android.exoplayer2.source.dash.DashMediaSource;
29 import com.google.android.exoplayer2.source.hls.HlsMediaSource; 24 import com.google.android.exoplayer2.source.hls.HlsMediaSource;
30 import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; 25 import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource;
31 -import com.google.android.exoplayer2.text.Cue;  
32 -import com.google.android.exoplayer2.text.TextOutput;  
33 import com.google.android.exoplayer2.trackselection.TrackSelectionArray; 26 import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
34 -import com.google.android.exoplayer2.ui.PlayerView;  
35 import com.google.android.exoplayer2.upstream.DataSource; 27 import com.google.android.exoplayer2.upstream.DataSource;
36 import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; 28 import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
37 -import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;  
38 import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; 29 import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
39 import com.google.android.exoplayer2.util.Util; 30 import com.google.android.exoplayer2.util.Util;
40 -import com.google.android.exoplayer2.video.VideoDecoderOutputBuffer;  
41 -import com.google.android.exoplayer2.video.VideoDecoderOutputBufferRenderer;  
42 import com.google.android.exoplayer2.video.VideoListener; 31 import com.google.android.exoplayer2.video.VideoListener;
43 -import com.google.android.exoplayer2.video.spherical.CameraMotionListener;  
44 import com.libs.video.data.network.Callback; 32 import com.libs.video.data.network.Callback;
45 import com.libs.video.data.network.DataLoader; 33 import com.libs.video.data.network.DataLoader;
46 import com.libs.video.util.AuthUtil; 34 import com.libs.video.util.AuthUtil;
cloud/video/src/main/java/com/libs/video/video/VideoStatusReceiver.java renamed to cloud/video/src/main/java/com/libs/video/video/test/VideoStatusReceiver.java
1 -package com.libs.video.video; 1 +package com.libs.video.video.test;
2 2
3 import android.os.Handler; 3 import android.os.Handler;
4 4
cloud/video/src/main/java/com/libs/video/video/VideoStatusUtil.java renamed to cloud/video/src/main/java/com/libs/video/video/test/VideoStatusUtil.java
1 -package com.libs.video.video; 1 +package com.libs.video.video.test;
2 2
3 -import android.os.Handler;  
4 -import android.text.TextUtils;  
5 -import android.util.Log;  
6 -  
7 -import com.libs.video.util.Config;  
8 import com.libs.video.video.base.IVideoStatus; 3 import com.libs.video.video.base.IVideoStatus;
9 4
10 5
cloud/video/src/main/java/com/libs/video/video/VideoView.java renamed to cloud/video/src/main/java/com/libs/video/video/test/VideoView.java
1 -package com.libs.video.video; 1 +package com.libs.video.video.test;
2 2
3 import android.content.Context; 3 import android.content.Context;
4 import android.graphics.Bitmap; 4 import android.graphics.Bitmap;
cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_arrow_back.png 0 → 100644

472 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_autorenew.png 0 → 100644

937 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery.png 0 → 100644

282 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_10.png 0 → 100644

329 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_20.png 0 → 100644

339 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_30.png 0 → 100644

342 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_40.png 0 → 100644

329 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_50.png 0 → 100644

318 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_60.png 0 → 100644

307 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_70.png 0 → 100644

323 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_80.png 0 → 100644

310 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_battery_90.png 0 → 100644

319 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_brightness.png 0 → 100644

717 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_close.png 0 → 100644

491 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_fast_forward.png 0 → 100644

556 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_fast_rewind.png 0 → 100644

557 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_fullscreen.png 0 → 100644

319 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_fullscreen_exit.png 0 → 100644

314 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_lock_close.png 0 → 100644

673 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_lock_open.png 0 → 100644

655 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_pause.png 0 → 100644

237 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_play_arrow.png 0 → 100644

410 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_replay.png 0 → 100644

869 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_volume_off.png 0 → 100644

960 Bytes

cloud/video/src/main/res/drawable-hdpi/cnvideo_ic_action_volume_up.png 0 → 100644

752 Bytes

cloud/video/src/main/res/drawable/cnvideo_battery_level.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<level-list xmlns:android="http://schemas.android.com/apk/res/android">
  3 +
  4 + <item
  5 + android:drawable="@drawable/cnvideo_ic_action_battery"
  6 + android:maxLevel="100"
  7 + android:minLevel="95" />
  8 + <item
  9 + android:drawable="@drawable/cnvideo_ic_action_battery_10"
  10 + android:maxLevel="15"
  11 + android:minLevel="0" />
  12 + <item
  13 + android:drawable="@drawable/cnvideo_ic_action_battery_20"
  14 + android:maxLevel="25"
  15 + android:minLevel="15" />
  16 + <item
  17 + android:drawable="@drawable/cnvideo_ic_action_battery_30"
  18 + android:maxLevel="35"
  19 + android:minLevel="25" />
  20 + <item
  21 + android:drawable="@drawable/cnvideo_ic_action_battery_40"
  22 + android:maxLevel="45"
  23 + android:minLevel="35" />
  24 + <item
  25 + android:drawable="@drawable/cnvideo_ic_action_battery_50"
  26 + android:maxLevel="55"
  27 + android:minLevel="45" />
  28 + <item
  29 + android:drawable="@drawable/cnvideo_ic_action_battery_60"
  30 + android:maxLevel="65"
  31 + android:minLevel="55" />
  32 + <item
  33 + android:drawable="@drawable/cnvideo_ic_action_battery_70"
  34 + android:maxLevel="75"
  35 + android:minLevel="65" />
  36 + <item
  37 + android:drawable="@drawable/cnvideo_ic_action_battery_80"
  38 + android:maxLevel="85"
  39 + android:minLevel="75" />
  40 + <item
  41 + android:drawable="@drawable/cnvideo_ic_action_battery_90"
  42 + android:maxLevel="95"
  43 + android:minLevel="85" />
  44 +</level-list>
0 \ No newline at end of file 45 \ No newline at end of file
cloud/video/src/main/res/drawable/cnvideo_ic_action_fullscreen.png 0 → 100644

319 Bytes

cloud/video/src/main/res/drawable/cnvideo_layer_progress_bar.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
  3 + <item android:id="@android:id/background">
  4 + <shape>
  5 + <solid android:color="#7FFFFFFF"/>
  6 + </shape>
  7 + </item>
  8 + <item android:id="@android:id/secondaryProgress">
  9 + <clip>
  10 + <shape>
  11 + <solid android:color="@android:color/white"/>
  12 + </shape>
  13 + </clip>
  14 + </item>
  15 + <item android:id="@android:id/progress">
  16 + <clip>
  17 + <shape>
  18 + <solid android:color="@color/cnvideo_theme_color"/>
  19 + </shape>
  20 + </clip>
  21 + </item>
  22 +</layer-list>
cloud/video/src/main/res/drawable/cnvideo_progress_loading.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<rotate xmlns:android="http://schemas.android.com/apk/res/android"
  3 + android:fromDegrees="0"
  4 + android:pivotX="50%"
  5 + android:pivotY="50%"
  6 + android:toDegrees="360">
  7 +
  8 + <shape
  9 + android:innerRadiusRatio="2.8"
  10 + android:shape="ring"
  11 + android:thicknessRatio="15"
  12 + android:useLevel="false">
  13 + <gradient
  14 + android:endColor="@android:color/white"
  15 + android:startColor="@android:color/transparent"
  16 + android:type="sweep"
  17 + android:useLevel="false" />
  18 + </shape>
  19 +
  20 +</rotate>
cloud/video/src/main/res/drawable/cnvideo_seekbar_thumb.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<selector xmlns:android="http://schemas.android.com/apk/res/android">
  3 + <item android:state_pressed="true" android:state_focused="true" android:drawable="@drawable/cnvideo_seekbar_thumb_pressed"/>
  4 + <item android:state_pressed="true" android:state_focused="false" android:drawable="@drawable/cnvideo_seekbar_thumb_pressed"/>
  5 + <item android:state_pressed="false" android:state_focused="true" android:drawable="@drawable/cnvideo_seekbar_thumb_normal"/>
  6 + <item android:drawable="@drawable/cnvideo_seekbar_thumb_normal"/>
  7 +</selector>
cloud/video/src/main/res/drawable/cnvideo_seekbar_thumb_normal.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<shape android:shape="oval"
  3 + xmlns:android="http://schemas.android.com/apk/res/android">
  4 + <solid android:color="@color/cnvideo_theme_color"/>
  5 + <size
  6 + android:height="@dimen/cnvideo_controller_seekbar_size_n"
  7 + android:width="@dimen/cnvideo_controller_seekbar_size_n"/>
  8 +</shape>
cloud/video/src/main/res/drawable/cnvideo_seekbar_thumb_pressed.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<shape android:shape="oval"
  3 + xmlns:android="http://schemas.android.com/apk/res/android">
  4 + <solid android:color="@color/cnvideo_theme_color"/>
  5 + <size
  6 + android:height="@dimen/cnvideo_controller_seekbar_size_n"
  7 + android:width="@dimen/cnvideo_controller_seekbar_size_n"/>
  8 +</shape>
cloud/video/src/main/res/drawable/cnvideo_selector_full_screen_button.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<selector xmlns:android="http://schemas.android.com/apk/res/android">
  3 +
  4 + <item android:state_selected="true" android:drawable="@drawable/cnvideo_ic_action_fullscreen_exit"/>
  5 + <item android:drawable="@drawable/cnvideo_ic_action_fullscreen"/>
  6 +
  7 +</selector>
0 \ No newline at end of file 8 \ No newline at end of file
cloud/video/src/main/res/drawable/cnvideo_selector_lock_button.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<selector xmlns:android="http://schemas.android.com/apk/res/android">
  3 +
  4 + <item android:state_selected="true" android:drawable="@drawable/cnvideo_ic_action_lock_open"/>
  5 + <item android:drawable="@drawable/cnvideo_ic_action_lock_close"/>
  6 +
  7 +</selector>
0 \ No newline at end of file 8 \ No newline at end of file
cloud/video/src/main/res/drawable/cnvideo_selector_play_button.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<selector xmlns:android="http://schemas.android.com/apk/res/android">
  3 +
  4 + <item android:state_selected="true" android:drawable="@drawable/cnvideo_ic_action_pause"/>
  5 + <item android:drawable="@drawable/cnvideo_ic_action_play_arrow"/>
  6 +
  7 +</selector>
0 \ No newline at end of file 8 \ No newline at end of file
cloud/video/src/main/res/drawable/cnvideo_shape_ad_bg.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<shape xmlns:android="http://schemas.android.com/apk/res/android"
  3 + android:shape="rectangle">
  4 + <solid android:color="@color/cnvideo_background_color" />
  5 + <size
  6 + android:width="80dp"
  7 + android:height="40dp" />
  8 + <corners android:radius="20dp" />
  9 +</shape>
cloud/video/src/main/res/drawable/cnvideo_shape_back_bg.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<shape android:shape="oval"
  3 + xmlns:android="http://schemas.android.com/apk/res/android">
  4 + <solid android:color="@color/cnvideo_background_color"/>
  5 + <size
  6 + android:height="40dp"
  7 + android:width="40dp"/>
  8 +</shape>
cloud/video/src/main/res/drawable/cnvideo_shape_play_bg.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<shape android:shape="oval"
  3 + xmlns:android="http://schemas.android.com/apk/res/android">
  4 + <solid android:color="@color/cnvideo_theme_color_translucent"/>
  5 + <size
  6 + android:height="40dp"
  7 + android:width="40dp"/>
  8 +</shape>
cloud/video/src/main/res/drawable/cnvideo_shape_standard_controller_top_bg.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<shape xmlns:android="http://schemas.android.com/apk/res/android">
  3 +
  4 + <gradient android:startColor="@color/cnvideo_background_color"
  5 + android:endColor="@android:color/transparent"
  6 + android:angle="270"/>
  7 +
  8 +</shape>
0 \ No newline at end of file 9 \ No newline at end of file
cloud/video/src/main/res/drawable/cnvideo_shape_stardard_controller_bottom_bg.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<shape xmlns:android="http://schemas.android.com/apk/res/android">
  3 +
  4 + <gradient android:startColor="@color/cnvideo_background_color"
  5 + android:endColor="@android:color/transparent"
  6 + android:angle="90"/>
  7 +
  8 +</shape>
0 \ No newline at end of file 9 \ No newline at end of file
cloud/video/src/main/res/drawable/cnvideo_shape_status_view_btn.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<shape xmlns:android="http://schemas.android.com/apk/res/android">
  3 + <solid android:color="@color/cnvideo_theme_color"/>
  4 + <corners android:radius="30dp"/>
  5 +</shape>
0 \ No newline at end of file 6 \ No newline at end of file
cloud/video/src/main/res/layout/cnvideo_layout_complete_view.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3 + xmlns:tools="http://schemas.android.com/tools"
  4 + android:id="@+id/complete_container"
  5 + android:layout_width="match_parent"
  6 + android:layout_height="match_parent"
  7 + android:background="#33000000">
  8 +
  9 + <ImageView
  10 + android:id="@+id/stop_fullscreen"
  11 + android:layout_width="@dimen/cnvideo_controller_height"
  12 + android:layout_height="@dimen/cnvideo_controller_height"
  13 + android:padding="@dimen/cnvideo_controller_icon_padding"
  14 + android:src="@drawable/cnvideo_ic_action_arrow_back"
  15 + android:visibility="gone"
  16 + tools:visibility="visible" />
  17 +
  18 + <LinearLayout
  19 + android:layout_width="wrap_content"
  20 + android:layout_height="wrap_content"
  21 + android:layout_gravity="center"
  22 + android:orientation="vertical">
  23 +
  24 + <ImageView
  25 + android:id="@+id/iv_replay"
  26 + android:layout_width="@dimen/cnvideo_play_btn_size"
  27 + android:layout_height="@dimen/cnvideo_play_btn_size"
  28 + android:layout_gravity="center"
  29 + android:background="@drawable/cnvideo_shape_play_bg"
  30 + android:padding="@dimen/cnvideo_controller_icon_padding"
  31 + android:src="@drawable/cnvideo_ic_action_replay" />
  32 +
  33 + <TextView
  34 + android:layout_width="wrap_content"
  35 + android:layout_height="wrap_content"
  36 + android:text="@string/cnvideo_replay"
  37 + android:layout_marginTop="12dp"
  38 + android:textColor="@android:color/white" />
  39 +
  40 + </LinearLayout>
  41 +</FrameLayout>
0 \ No newline at end of file 42 \ No newline at end of file
cloud/video/src/main/res/layout/cnvideo_layout_error_view.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3 + android:layout_width="match_parent"
  4 + android:layout_height="match_parent"
  5 + android:background="@android:color/black">
  6 +
  7 + <LinearLayout
  8 + android:layout_width="match_parent"
  9 + android:layout_height="match_parent"
  10 + android:gravity="center"
  11 + android:orientation="vertical">
  12 +
  13 + <TextView
  14 + android:id="@+id/message"
  15 + android:layout_width="wrap_content"
  16 + android:layout_height="wrap_content"
  17 + android:gravity="center"
  18 + android:text="@string/cnvideo_error_message"
  19 + android:textColor="@android:color/white" />
  20 +
  21 + <TextView
  22 + android:id="@+id/status_btn"
  23 + android:layout_width="wrap_content"
  24 + android:layout_height="wrap_content"
  25 + android:layout_marginTop="16dp"
  26 + android:background="@drawable/cnvideo_shape_status_view_btn"
  27 + android:gravity="center"
  28 + android:paddingLeft="18dp"
  29 + android:paddingTop="4dp"
  30 + android:paddingRight="18dp"
  31 + android:paddingBottom="4dp"
  32 + android:text="@string/cnvideo_retry"
  33 + android:textColor="@android:color/white" />
  34 +
  35 + </LinearLayout>
  36 +</FrameLayout>
0 \ No newline at end of file 37 \ No newline at end of file
cloud/video/src/main/res/layout/cnvideo_layout_gesture_control_view.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3 + xmlns:tools="http://schemas.android.com/tools"
  4 + android:layout_width="match_parent"
  5 + android:layout_height="match_parent">
  6 +
  7 + <LinearLayout
  8 + android:id="@+id/center_container"
  9 + android:layout_width="160dp"
  10 + android:layout_height="120dp"
  11 + android:layout_gravity="center"
  12 + android:background="#96000000"
  13 + android:gravity="center"
  14 + android:orientation="vertical"
  15 + android:visibility="gone"
  16 + tools:visibility="visible">
  17 +
  18 + <ImageView
  19 + android:id="@+id/iv_icon"
  20 + android:layout_width="36dp"
  21 + android:layout_height="36dp"
  22 + tools:src="@drawable/cnvideo_ic_action_volume_up" />
  23 +
  24 + <TextView
  25 + android:id="@+id/tv_percent"
  26 + android:layout_width="wrap_content"
  27 + android:layout_height="wrap_content"
  28 + android:layout_margin="10dp"
  29 + android:textColor="@android:color/white"
  30 + android:textSize="14sp"
  31 + tools:text="100" />
  32 +
  33 + <ProgressBar
  34 + android:id="@+id/pro_percent"
  35 + style="?android:attr/progressBarStyleHorizontal"
  36 + android:layout_width="100dp"
  37 + android:layout_height="3dp"
  38 + android:max="100"
  39 + android:progressDrawable="@drawable/cnvideo_layer_progress_bar" />
  40 +
  41 + </LinearLayout>
  42 +</FrameLayout>
cloud/video/src/main/res/layout/cnvideo_layout_live_control_view.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3 + android:layout_width="match_parent"
  4 + android:layout_height="match_parent">
  5 +
  6 + <LinearLayout
  7 + android:id="@+id/bottom_container"
  8 + android:layout_width="match_parent"
  9 + android:layout_height="@dimen/cnvideo_controller_height"
  10 + android:layout_gravity="bottom"
  11 + android:gravity="center_vertical"
  12 + android:background="@drawable/cnvideo_shape_stardard_controller_bottom_bg"
  13 + android:orientation="horizontal">
  14 +
  15 + <ImageView
  16 + android:id="@+id/iv_play"
  17 + android:layout_width="@dimen/cnvideo_controller_height"
  18 + android:layout_height="@dimen/cnvideo_controller_height"
  19 + android:padding="@dimen/cnvideo_controller_icon_padding"
  20 + android:src="@drawable/cnvideo_selector_play_button" />
  21 +
  22 + <ImageView
  23 + android:id="@+id/iv_refresh"
  24 + android:layout_width="@dimen/cnvideo_controller_height"
  25 + android:layout_height="@dimen/cnvideo_controller_height"
  26 + android:padding="@dimen/cnvideo_controller_icon_padding"
  27 + android:src="@drawable/cnvideo_ic_action_autorenew" />
  28 +
  29 + <Space
  30 + android:layout_width="0dp"
  31 + android:layout_height="match_parent"
  32 + android:layout_weight="1"/>
  33 +
  34 + <ImageView
  35 + android:id="@+id/fullscreen"
  36 + android:layout_width="@dimen/cnvideo_controller_height"
  37 + android:layout_height="@dimen/cnvideo_controller_height"
  38 + android:padding="@dimen/cnvideo_controller_icon_padding"
  39 + android:src="@drawable/cnvideo_selector_full_screen_button" />
  40 + </LinearLayout>
  41 +
  42 +</FrameLayout>
0 \ No newline at end of file 43 \ No newline at end of file
cloud/video/src/main/res/layout/cnvideo_layout_prepare_view.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3 + xmlns:tools="http://schemas.android.com/tools"
  4 + android:layout_width="match_parent"
  5 + android:layout_height="match_parent">
  6 +
  7 + <ImageView
  8 + android:id="@+id/thumb"
  9 + android:layout_width="match_parent"
  10 + android:layout_height="match_parent"
  11 + android:scaleType="centerCrop" />
  12 +
  13 + <ImageView
  14 + android:id="@+id/start_play"
  15 + android:layout_width="@dimen/cnvideo_play_btn_size"
  16 + android:layout_height="@dimen/cnvideo_play_btn_size"
  17 + android:layout_gravity="center"
  18 + android:background="@drawable/cnvideo_shape_play_bg"
  19 + android:padding="@dimen/cnvideo_default_spacing"
  20 + android:src="@drawable/cnvideo_selector_play_button" />
  21 +
  22 + <ProgressBar
  23 + android:id="@+id/loading"
  24 + android:layout_width="@dimen/cnvideo_play_btn_size"
  25 + android:layout_height="@dimen/cnvideo_play_btn_size"
  26 + android:layout_gravity="center"
  27 + android:indeterminateDrawable="@drawable/cnvideo_progress_loading"
  28 + android:indeterminateDuration="2000"
  29 + android:visibility="gone"
  30 + tools:visibility="visible" />
  31 +
  32 + <FrameLayout
  33 + android:id="@+id/net_warning_layout"
  34 + android:layout_width="match_parent"
  35 + android:layout_height="match_parent"
  36 + android:background="@android:color/black"
  37 + android:clickable="true"
  38 + android:focusable="true"
  39 + android:visibility="gone">
  40 +
  41 + <LinearLayout
  42 + android:layout_width="match_parent"
  43 + android:layout_height="match_parent"
  44 + android:gravity="center"
  45 + android:orientation="vertical">
  46 +
  47 + <TextView
  48 + android:id="@+id/message"
  49 + android:layout_width="wrap_content"
  50 + android:layout_height="wrap_content"
  51 + android:gravity="center"
  52 + android:text="@string/cnvideo_wifi_tip"
  53 + android:textColor="@android:color/white" />
  54 +
  55 + <TextView
  56 + android:id="@+id/status_btn"
  57 + android:layout_width="wrap_content"
  58 + android:layout_height="wrap_content"
  59 + android:layout_marginTop="16dp"
  60 + android:background="@drawable/cnvideo_shape_status_view_btn"
  61 + android:gravity="center"
  62 + android:paddingLeft="16dp"
  63 + android:paddingTop="4dp"
  64 + android:paddingRight="16dp"
  65 + android:paddingBottom="4dp"
  66 + android:text="@string/cnvideo_continue_play"
  67 + android:textColor="@android:color/white" />
  68 +
  69 + </LinearLayout>
  70 + </FrameLayout>
  71 +
  72 +</FrameLayout>
0 \ No newline at end of file 73 \ No newline at end of file
cloud/video/src/main/res/layout/cnvideo_layout_standard_controller.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3 + xmlns:tools="http://schemas.android.com/tools"
  4 + android:layout_width="match_parent"
  5 + android:layout_height="match_parent">
  6 +
  7 + <ImageView
  8 + android:id="@+id/lock"
  9 + android:layout_width="40dp"
  10 + android:layout_height="40dp"
  11 + android:layout_gravity="center|start"
  12 + android:layout_margin="24dp"
  13 + android:background="@drawable/cnvideo_shape_back_bg"
  14 + android:padding="@dimen/cnvideo_default_spacing"
  15 + android:src="@drawable/cnvideo_selector_lock_button"
  16 + android:visibility="gone"
  17 + tools:visibility="visible" />
  18 +
  19 + <ProgressBar
  20 + android:id="@+id/loading"
  21 + android:layout_width="@dimen/cnvideo_play_btn_size"
  22 + android:layout_height="@dimen/cnvideo_play_btn_size"
  23 + android:layout_gravity="center"
  24 + android:indeterminateDrawable="@drawable/cnvideo_progress_loading"
  25 + android:indeterminateDuration="2000"
  26 + android:visibility="gone"
  27 + tools:visibility="visible" />
  28 +
  29 +</FrameLayout>
0 \ No newline at end of file 30 \ No newline at end of file
cloud/video/src/main/res/layout/cnvideo_layout_title_view.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3 + xmlns:tools="http://schemas.android.com/tools"
  4 + android:layout_width="match_parent"
  5 + android:layout_height="match_parent"
  6 + android:orientation="vertical">
  7 +
  8 + <LinearLayout
  9 + android:id="@+id/title_container"
  10 + android:layout_width="match_parent"
  11 + android:layout_height="@dimen/cnvideo_controller_height"
  12 + android:background="@drawable/cnvideo_shape_standard_controller_top_bg"
  13 + android:gravity="center_vertical"
  14 + android:orientation="horizontal">
  15 +
  16 + <ImageView
  17 + android:id="@+id/back"
  18 + android:layout_width="@dimen/cnvideo_controller_height"
  19 + android:layout_height="@dimen/cnvideo_controller_height"
  20 + android:layout_gravity="center"
  21 + android:padding="@dimen/cnvideo_controller_icon_padding"
  22 + android:src="@drawable/cnvideo_ic_action_arrow_back" />
  23 +
  24 + <com.libs.video.video.exoplayer.component.MarqueeTextView
  25 + android:id="@+id/title"
  26 + android:layout_width="0dp"
  27 + android:layout_height="wrap_content"
  28 + android:layout_weight="1"
  29 + android:ellipsize="marquee"
  30 + android:focusable="true"
  31 + android:focusableInTouchMode="true"
  32 + android:marqueeRepeatLimit="marquee_forever"
  33 + android:paddingLeft="6dp"
  34 + android:paddingRight="6dp"
  35 + android:singleLine="true"
  36 + android:textColor="@android:color/white"
  37 + android:textSize="@dimen/cnvideo_controller_text_size"
  38 + tools:text="这是一个标题" />
  39 +
  40 + <ImageView
  41 + android:id="@+id/iv_battery"
  42 + android:layout_width="26dp"
  43 + android:layout_height="20dp"
  44 + android:src="@drawable/cnvideo_battery_level" />
  45 +
  46 + <TextView
  47 + android:id="@+id/sys_time"
  48 + android:layout_width="wrap_content"
  49 + android:layout_height="wrap_content"
  50 + android:layout_marginEnd="14dp"
  51 + android:layout_marginRight="14dp"
  52 + android:textColor="@android:color/white"
  53 + tools:text="12:00" />
  54 +
  55 + </LinearLayout>
  56 +
  57 +</FrameLayout>
0 \ No newline at end of file 58 \ No newline at end of file
cloud/video/src/main/res/layout/cnvideo_layout_vod_control_view.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3 + android:layout_width="match_parent"
  4 + android:layout_height="match_parent"
  5 + xmlns:tools="http://schemas.android.com/tools">
  6 +
  7 + <LinearLayout
  8 + android:id="@+id/bottom_container"
  9 + android:layout_width="match_parent"
  10 + android:layout_height="@dimen/cnvideo_controller_height"
  11 + android:layout_gravity="bottom"
  12 + android:gravity="center_vertical"
  13 + android:background="@drawable/cnvideo_shape_stardard_controller_bottom_bg"
  14 + android:orientation="horizontal">
  15 +
  16 + <ImageView
  17 + android:id="@+id/iv_play"
  18 + android:layout_width="@dimen/cnvideo_controller_height"
  19 + android:layout_height="@dimen/cnvideo_controller_height"
  20 + android:padding="@dimen/cnvideo_controller_icon_padding"
  21 + android:src="@drawable/cnvideo_selector_play_button" />
  22 +
  23 + <TextView
  24 + android:id="@+id/curr_time"
  25 + android:layout_width="wrap_content"
  26 + android:layout_height="wrap_content"
  27 + android:textColor="@android:color/white"
  28 + android:textSize="@dimen/cnvideo_controller_time_text_size"
  29 + tools:text="00:00" />
  30 +
  31 + <SeekBar
  32 + android:id="@+id/seekBar"
  33 + android:layout_width="0dp"
  34 + android:layout_height="40dp"
  35 + android:layout_gravity="center_vertical"
  36 + android:layout_marginEnd="10dp"
  37 + android:layout_marginStart="10dp"
  38 + android:layout_weight="1"
  39 + android:background="@null"
  40 + android:max="1000"
  41 + android:maxHeight="2dp"
  42 + android:minHeight="2dp"
  43 + android:paddingEnd="0dp"
  44 + android:paddingStart="0dp"
  45 + android:progressDrawable="@drawable/cnvideo_layer_progress_bar"
  46 + android:thumb="@drawable/cnvideo_seekbar_thumb"
  47 + android:thumbOffset="0dp" />
  48 +
  49 + <TextView
  50 + android:id="@+id/total_time"
  51 + android:layout_width="wrap_content"
  52 + android:layout_height="wrap_content"
  53 + android:textColor="@android:color/white"
  54 + android:textSize="@dimen/cnvideo_controller_time_text_size"
  55 + tools:text="00:00" />
  56 +
  57 + <ImageView
  58 + android:id="@+id/fullscreen"
  59 + android:layout_width="@dimen/cnvideo_controller_height"
  60 + android:layout_height="@dimen/cnvideo_controller_height"
  61 + android:padding="@dimen/cnvideo_controller_icon_padding"
  62 + android:src="@drawable/cnvideo_selector_full_screen_button" />
  63 + </LinearLayout>
  64 +
  65 + <ProgressBar
  66 + android:id="@+id/bottom_progress"
  67 + style="?android:attr/progressBarStyleHorizontal"
  68 + android:layout_width="match_parent"
  69 + android:layout_height="1dp"
  70 + android:layout_gravity="bottom"
  71 + android:max="1000"
  72 + android:progressDrawable="@drawable/cnvideo_layer_progress_bar"
  73 + android:visibility="gone" />
  74 +
  75 +</FrameLayout>
0 \ No newline at end of file 76 \ No newline at end of file
cloud/video/src/main/res/layout/layout_ad_control_view.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3 + xmlns:tools="http://schemas.android.com/tools"
  4 + android:layout_width="match_parent"
  5 + android:layout_height="match_parent">
  6 +
  7 + <RelativeLayout
  8 + android:layout_width="match_parent"
  9 + android:layout_height="wrap_content">
  10 +
  11 + <ImageView
  12 + android:id="@+id/back"
  13 + style="@style/adIcon"
  14 + android:src="@drawable/cnvideo_ic_action_arrow_back" />
  15 +
  16 + <TextView
  17 + android:id="@+id/ad_time"
  18 + style="@style/adText"
  19 + android:layout_alignParentEnd="true"
  20 + android:layout_alignParentRight="true"
  21 + android:layout_centerVertical="true"
  22 + tools:text="2s|跳过" />
  23 + </RelativeLayout>
  24 +
  25 + <LinearLayout
  26 + android:layout_width="match_parent"
  27 + android:layout_height="@dimen/controller_height"
  28 + android:layout_gravity="bottom|end"
  29 + android:gravity="end">
  30 +
  31 + <ImageView
  32 + android:id="@+id/iv_play"
  33 + style="@style/adIcon"
  34 + android:src="@drawable/cnvideo_selector_play_button" />
  35 +
  36 + <View
  37 + android:layout_width="0dp"
  38 + android:layout_height="match_parent"
  39 + android:layout_weight="1" />
  40 +
  41 + <TextView
  42 + android:id="@+id/ad_detail"
  43 + style="@style/adText"
  44 + tools:text="了解详情>" />
  45 +
  46 + <ImageView
  47 + android:id="@+id/iv_volume"
  48 + style="@style/adIcon"
  49 + android:src="@drawable/cnvideo_ic_action_volume_off" />
  50 +
  51 + <ImageView
  52 + android:id="@+id/fullscreen"
  53 + style="@style/adIcon"
  54 + android:src="@drawable/cnvideo_selector_full_screen_button" />
  55 + </LinearLayout>
  56 +</FrameLayout>
0 \ No newline at end of file 57 \ No newline at end of file
cloud/video/src/main/res/values/attrs.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<resources>
  3 + <declare-styleable name="CNVideoView">
  4 + <attr name="looping" format="boolean"/>
  5 + <attr name="enableAudioFocus" format="boolean"/>
  6 + <attr name="screenScaleType" format="dimension">
  7 + <enum name="type_default" value="0"/>
  8 + <enum name="type_16_9" value="1"/>
  9 + <enum name="type_4_3" value="2"/>
  10 + <enum name="type_match_parent" value="3"/>
  11 + <enum name="type_original" value="4"/>
  12 + <enum name="type_center_crop" value="5"/>
  13 + </attr>
  14 + <attr name="playerBackgroundColor" format="color"/>
  15 + </declare-styleable>
  16 +</resources>
0 \ No newline at end of file 17 \ No newline at end of file
cloud/video/src/main/res/values/colors.xml
1 <?xml version="1.0" encoding="utf-8"?> 1 <?xml version="1.0" encoding="utf-8"?>
2 <resources> 2 <resources>
3 <color name="cnvideo_color_btn_error">#505050</color> 3 <color name="cnvideo_color_btn_error">#505050</color>
4 - 4 + <color name="cnvideo_background_color">#4D000000</color>
  5 + <color name="cnvideo_theme_color">#39C5BB</color>
  6 + <color name="cnvideo_theme_color_translucent">#9939C5BB</color>
5 </resources> 7 </resources>
6 \ No newline at end of file 8 \ No newline at end of file
cloud/video/src/main/res/values/dimens.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<resources>
  3 + <dimen name="controller_height">46dp</dimen>
  4 + <dimen name="controller_icon_padding">12dp</dimen>
  5 + <dimen name="controller_text_size">16sp</dimen>
  6 + <dimen name="default_spacing">16dp</dimen>
  7 + <dimen name="controller_seekbar_size_n">14dp</dimen>
  8 + <dimen name="controller_seekbar_size_s">14dp</dimen>
  9 + <dimen name="controller_seekbar_max_size">1dp</dimen>
  10 + <dimen name="player_play_btn_size">50dp</dimen>
  11 + <dimen name="controller_time_text_size">14sp</dimen>
  12 + <dimen name="player_default_spacing">10dp</dimen>
  13 +
  14 +
  15 + <!-- ui-->
  16 + <dimen name="cnvideo_controller_height">46dp</dimen>
  17 + <dimen name="cnvideo_controller_icon_padding">12dp</dimen>
  18 + <dimen name="cnvideo_controller_text_size">16sp</dimen>
  19 + <dimen name="cnvideo_default_spacing">10dp</dimen>
  20 + <dimen name="cnvideo_controller_seekbar_size_n">14dp</dimen>
  21 + <dimen name="cnvideo_controller_seekbar_size_s">14dp</dimen>
  22 + <dimen name="cnvideo_controller_seekbar_max_size">1dp</dimen>
  23 + <dimen name="cnvideo_play_btn_size">50dp</dimen>
  24 + <dimen name="cnvideo_controller_time_text_size">14sp</dimen>
  25 +</resources>
0 \ No newline at end of file 26 \ No newline at end of file
cloud/video/src/main/res/values/strings.xml
1 <resources> 1 <resources>
2 <string name="app_name">video</string> 2 <string name="app_name">video</string>
  3 +
  4 + <string name="cnvideo_replay">重新播放</string>
  5 + <string name="cnvideo_lock_tip">请先解锁屏幕!</string>
  6 + <string name="cnvideo_unlocked">已解锁</string>
  7 + <string name="cnvideo_locked">已锁定</string>
  8 + <string name="cnvideo_error_message">出了点小问题,请稍后重试</string>
  9 + <string name="cnvideo_retry"> 重 试 </string>
  10 + <string name="cnvideo_continue_play">继续播放</string>
  11 + <string name="cnvideo_wifi_tip">您正在使用移动网络,继续播放将消耗流量</string>
3 </resources> 12 </resources>
cloud/video/src/main/res/values/styles.xml 0 → 100644
  1 +<?xml version="1.0" encoding="utf-8"?>
  2 +<resources>
  3 + <style name="adIcon">
  4 + <item name="android:padding">6dp</item>
  5 + <item name="android:layout_width">30dp</item>
  6 + <item name="android:layout_height">30dp</item>
  7 + <item name="android:background">@drawable/cnvideo_shape_back_bg</item>
  8 + <item name="android:layout_margin">5dp</item>
  9 + </style>
  10 +
  11 + <style name="adText">
  12 + <item name="android:background">@drawable/cnvideo_shape_ad_bg</item>
  13 + <item name="android:textSize">@dimen/controller_text_size</item>
  14 + <item name="android:textColor">@android:color/white</item>
  15 + <item name="android:padding">5dp</item>
  16 + <item name="android:layout_width">wrap_content</item>
  17 + <item name="android:layout_height">30dp</item>
  18 + <item name="android:layout_margin">5dp</item>
  19 + <item name="android:gravity">center</item>
  20 + </style>
  21 +</resources>
0 \ No newline at end of file 22 \ No newline at end of file
module/shanghaishop/build.gradle
@@ -17,13 +17,11 @@ dependencies { @@ -17,13 +17,11 @@ dependencies {
17 testImplementation 'junit:junit:4.12' 17 testImplementation 'junit:junit:4.12'
18 androidTestImplementation 'androidx.test.ext:junit:1.1.1' 18 androidTestImplementation 'androidx.test.ext:junit:1.1.1'
19 androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 19 androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
20 - api('cn.cnlive.b2c:cnliveb2c:1.0.0-20191217.063321-17') {  
21 - exclude group: 'com.github.bumptech.glide'  
22 - }  
23 -// api 'cn.cnlive.b2c:cnliveb2c:1.0.0-20191217.063321-17'  
24 -  
25 - api 'cn.cnlive.b2c:nicevideoplayer:1.0.0-20191206.090854-2'  
26 - api 'cn.cnlive.b2c:udesk-sdk-ui:1.0.0' 20 +// api('cn.cnlive.b2c:cnliveb2c:1.0.0-20191217.063321-17') {
  21 +// exclude group: 'com.github.bumptech.glide'
  22 +// }
  23 +// api 'cn.cnlive.b2c:nicevideoplayer:1.0.0-20191206.090854-2'
  24 +// api 'cn.cnlive.b2c:udesk-sdk-ui:1.0.0'
27 25
28 26
29 /** 圆形图片处理 androidx **/ 27 /** 圆形图片处理 androidx **/
module/shopcity/src/main/java/com/clive/happychina/shopcity/ShopServiceImpl.java
@@ -83,8 +83,8 @@ public class ShopServiceImpl implements ShopService { @@ -83,8 +83,8 @@ public class ShopServiceImpl implements ShopService {
83 @Override 83 @Override
84 public void jumpToHQYWShopHomeActivity(@NotNull Activity activity) { 84 public void jumpToHQYWShopHomeActivity(@NotNull Activity activity) {
85 //跳转环球优物 ---商城首页 85 //跳转环球优物 ---商城首页
86 - Intent intent = new Intent(activity, cn.cnlive.b2c.MainActivity.class);  
87 - activity.startActivity(intent); 86 +// Intent intent = new Intent(activity, cn.cnlive.b2c.MainActivity.class);
  87 +// activity.startActivity(intent);
88 } 88 }
89 89
90 @Override 90 @Override