Commit ba42588e9145d0e8efdcb7538a12713734546021
Merge branch 'master' of bj.gitlab.cnlive.com:cnlive-team/newModuleStrike
Showing
96 changed files
with
8960 additions
and
2939 deletions
app/strike/build.gradle
| ... | ... | @@ -179,7 +179,7 @@ dependencies { |
| 179 | 179 | implementation project(path: ':core:imageload') |
| 180 | 180 | implementation project(path: ':module:xiaojiasoundbox') |
| 181 | 181 | implementation project(path: ':module:pedometer') |
| 182 | -// implementation project(path: ':module:shanghaishop') | |
| 182 | + implementation project(path: ':module:shanghaishop') | |
| 183 | 183 | implementation project(path: ':library_baidumap') |
| 184 | 184 | implementation 'com.jakewharton.rxbinding3:rxbinding:3.1.0' |
| 185 | 185 | // implementation project(path: ':core:util') | ... | ... |
app/strike/src/main/AndroidManifest.xml
| ... | ... | @@ -9,7 +9,7 @@ |
| 9 | 9 | <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> |
| 10 | 10 | |
| 11 | 11 | <application |
| 12 | - android:name="com.cnlive.strike.MyApp" | |
| 12 | + android:name="com.cnlive.strike.demo.exoplayer.demo.DemoApplication" | |
| 13 | 13 | android:allowBackup="true" |
| 14 | 14 | android:icon="@mipmap/ic_launcher" |
| 15 | 15 | android:label="@string/app_name" |
| ... | ... | @@ -17,6 +17,24 @@ |
| 17 | 17 | android:roundIcon="@mipmap/ic_launcher_round" |
| 18 | 18 | android:supportsRtl="true" |
| 19 | 19 | android:theme="@style/AppTheme"> |
| 20 | + <provider | |
| 21 | + android:name="androidx.core.content.FileProvider" | |
| 22 | + android:authorities="${applicationId}.fileprovider" | |
| 23 | + android:exported="false" | |
| 24 | + android:grantUriPermissions="true" | |
| 25 | + tools:replace="android:authorities"> | |
| 26 | + <meta-data | |
| 27 | + android:name="android.support.FILE_PROVIDER_PATHS" | |
| 28 | + android:resource="@xml/app_files" | |
| 29 | + tools:replace="android:resource" /> | |
| 30 | + </provider> | |
| 31 | + | |
| 32 | + <activity android:name="com.cnlive.strike.demo.exoplayer.demo.PlayerActivity" /> | |
| 33 | + <activity android:name="com.cnlive.strike.demo.exoplayer.demo.SampleChooserActivity" /> | |
| 34 | + <activity | |
| 35 | + android:name="com.cnlive.strike.demo.activity.ExoPlayerActivity" | |
| 36 | + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" | |
| 37 | + android:screenOrientation="portrait" /> | |
| 20 | 38 | <activity android:name="com.cnlive.strike.demo.activity.CNplayerActivity" /> |
| 21 | 39 | <activity android:name="com.cnlive.strike.demo.activity.DemoAnimActivity" /> |
| 22 | 40 | <activity |
| ... | ... | @@ -37,7 +55,8 @@ |
| 37 | 55 | |
| 38 | 56 | <meta-data |
| 39 | 57 | android:name="com.baidu.lbsapi.API_KEY" |
| 40 | - android:value="${BAIDU_MAP_APP_KEY}" /> | |
| 58 | + android:value="${BAIDU_MAP_APP_KEY}" | |
| 59 | + tools:replace="android:value" /> | |
| 41 | 60 | <meta-data |
| 42 | 61 | android:name="com.baidu.speech.APP_ID" |
| 43 | 62 | android:value="${BAIDU_APP_ID}" /> | ... | ... |
app/strike/src/main/java/com/cnlive/strike/MyApp.java
| ... | ... | @@ -29,6 +29,9 @@ public class MyApp extends BaseApplication { |
| 29 | 29 | //自4.3.0起,百度地图SDK所有接口均支持百度坐标和国测局坐标,用此方法设置您使用的坐标类型. |
| 30 | 30 | //包括BD09LL和GCJ02两种坐标,默认是BD09LL坐标。 |
| 31 | 31 | SDKInitializer.setCoordType(CoordType.GCJ02); |
| 32 | + //初始化上海电商 | |
| 33 | + cn.cnlive.b2c.AppApplication.initSDK(this); | |
| 34 | + | |
| 32 | 35 | } |
| 33 | 36 | |
| 34 | 37 | ... | ... |
app/strike/src/main/java/com/cnlive/strike/demo/activity/CNplayerActivity.java
| 1 | 1 | package com.cnlive.strike.demo.activity; |
| 2 | 2 | |
| 3 | 3 | import android.content.Intent; |
| 4 | +import android.os.Handler; | |
| 5 | +import android.widget.Toast; | |
| 4 | 6 | |
| 7 | +import androidx.annotation.Nullable; | |
| 5 | 8 | import androidx.databinding.ViewDataBinding; |
| 6 | 9 | |
| 10 | +import com.alibaba.android.arouter.facade.annotation.Route; | |
| 7 | 11 | import com.cnlive.cloud.BuildConfig; |
| 8 | 12 | import com.cnlive.cloud.R; |
| 9 | 13 | import com.cnlive.cloud.databinding.ActivityCnvideoBinding; |
| ... | ... | @@ -17,11 +21,21 @@ import com.libs.video.util.ProbeUtil; |
| 17 | 21 | import com.libs.video.video.VideoView; |
| 18 | 22 | import com.libs.video.video.base.IMediaPlayer; |
| 19 | 23 | |
| 24 | +import org.greenrobot.eventbus.Subscribe; | |
| 25 | +import org.greenrobot.eventbus.ThreadMode; | |
| 26 | + | |
| 27 | +@Route(path = CNplayerActivity.PATH) | |
| 20 | 28 | public class CNplayerActivity extends BaseActivity implements IMediaPlayer.OnPreparedListener, |
| 21 | 29 | IMediaPlayer.OnCompletionListener, IMediaPlayer.OnErrorListener, IMediaPlayer.OnInfoListener, |
| 22 | 30 | IMediaPlayer.OnPlayStateListener { |
| 23 | 31 | private ActivityCnvideoBinding binding; |
| 24 | 32 | private String type = "0"; |
| 33 | + public static final String PATH = "/test/CNplayerActivity"; | |
| 34 | + | |
| 35 | + @Override | |
| 36 | + protected void onDestroy() { | |
| 37 | + super.onDestroy(); | |
| 38 | + } | |
| 25 | 39 | |
| 26 | 40 | @Override |
| 27 | 41 | protected int getBindLayoutId() { |
| ... | ... | @@ -51,23 +65,17 @@ public class CNplayerActivity extends BaseActivity implements IMediaPlayer.OnPre |
| 51 | 65 | binding.videoview.setOnErrorListener(this); |
| 52 | 66 | binding.videoview.setOnInfoListener(this); |
| 53 | 67 | if ("0".equals(type)) { |
| 54 | - QMUITipDialogUtil.loadingDialog(me,"点播"); | |
| 68 | + QMUITipDialogUtil.loadingDialog(me, "点播"); | |
| 55 | 69 | binding.videoview.setDataSource(new DataSource("223_88c98ab1395f4762b703fbdd9d59d2e5", Config.TYPE_VOD, Config.TYPE_PLAY_RATE_2)); |
| 56 | 70 | } else { |
| 57 | - QMUITipDialogUtil.loadingDialog(me,"直播"); | |
| 71 | +// QMUITipDialogUtil.loadingDialog(me, "直播"); | |
| 58 | 72 | // binding.videoview.setDataSource(new DataSource("604_233216be9552476b93d2c0cac7819222", Config.TYPE_LIVE, Config.TYPE_PLAY_RATE_2)); |
| 59 | 73 | // binding.videoview.setDataSource(new DataSource("223_b66053d9eaf147efa58eb1ae4b47661e", Config.TYPE_VOD, Config.TYPE_PLAY_RATE_2)); |
| 60 | - binding.videoview.setDataSource(new DataSource("604_75ca27cce3a043b1876f694dd1125234", Config.TYPE_LIVE, Config.TYPE_PLAY_RATE_2)); | |
| 74 | + binding.videoview.setDataSource(new DataSource("604_maishicaijing", Config.TYPE_LIVE, Config.TYPE_PLAY_RATE_2)); | |
| 61 | 75 | |
| 62 | 76 | } |
| 63 | 77 | binding.videoview.start(); |
| 64 | 78 | |
| 65 | - | |
| 66 | - } | |
| 67 | - | |
| 68 | - @Override | |
| 69 | - protected void onDestroy() { | |
| 70 | - super.onDestroy(); | |
| 71 | 79 | } |
| 72 | 80 | |
| 73 | 81 | @Override |
| ... | ... | @@ -83,27 +91,29 @@ public class CNplayerActivity extends BaseActivity implements IMediaPlayer.OnPre |
| 83 | 91 | |
| 84 | 92 | @Override |
| 85 | 93 | public boolean onError(IMediaPlayer mediaplayer, int what, int extra) { |
| 86 | - AlertUtil.showDeftToast(me, "onError"); | |
| 94 | +// AlertUtil.showDeftToast(me, "onError"); | |
| 87 | 95 | |
| 88 | 96 | return false; |
| 89 | 97 | } |
| 90 | 98 | |
| 91 | 99 | @Override |
| 92 | 100 | public void onCompletion(IMediaPlayer mediaplayer) { |
| 93 | - AlertUtil.showDeftToast(me, "onCompletion"); | |
| 101 | +// AlertUtil.showDeftToast(me, "onCompletion"); | |
| 94 | 102 | mediaplayer.start(); |
| 95 | 103 | } |
| 96 | 104 | |
| 97 | 105 | @Override |
| 98 | 106 | public void onPrepared(IMediaPlayer mediaplayer) { |
| 99 | - AlertUtil.showDeftToast(me, "onPrepared"); | |
| 100 | - QMUITipDialogUtil.dismessDialog(); | |
| 107 | +// AlertUtil.showDeftToast(me, "onPrepared"); | |
| 108 | +// QMUITipDialogUtil.dismessDialog(); | |
| 101 | 109 | mediaplayer.start(); |
| 102 | 110 | } |
| 103 | 111 | |
| 104 | 112 | @Override |
| 105 | 113 | public void onPlayState(boolean isPlay) { |
| 106 | - AlertUtil.showDeftToast(me, "onPlayState:" + isPlay); | |
| 114 | +// AlertUtil.showDeftToast(me, "onPlayState:" + isPlay); | |
| 107 | 115 | |
| 108 | 116 | } |
| 117 | + | |
| 118 | + | |
| 109 | 119 | } | ... | ... |
app/strike/src/main/java/com/cnlive/strike/demo/activity/DemoAnimActivity.java
| ... | ... | @@ -82,7 +82,7 @@ public class DemoAnimActivity extends BaseActivity { |
| 82 | 82 | @Override |
| 83 | 83 | public void onWindowFocusChanged(boolean hasFocus) { |
| 84 | 84 | if (hasFocus) { |
| 85 | - rope = YoYo.with(Techniques.FadeIn).duration(1000).playOn(mTarget);// after start,just click mTarget view, rope is not init | |
| 85 | + rope = YoYo.with(Techniques.FadeIn).duration(1000).playOn(mTarget);// after play,just click mTarget view, rope is not init | |
| 86 | 86 | } |
| 87 | 87 | } |
| 88 | 88 | ... | ... |
app/strike/src/main/java/com/cnlive/strike/demo/activity/DemoMvpActivity.java
| ... | ... | @@ -5,14 +5,13 @@ import android.content.Intent; |
| 5 | 5 | import android.os.Handler; |
| 6 | 6 | import android.os.Message; |
| 7 | 7 | import android.text.TextUtils; |
| 8 | -import android.view.LayoutInflater; | |
| 9 | 8 | import android.view.View; |
| 10 | -import android.widget.LinearLayout; | |
| 11 | -import android.widget.TextView; | |
| 12 | 9 | |
| 13 | 10 | import androidx.annotation.NonNull; |
| 11 | +import androidx.annotation.Nullable; | |
| 14 | 12 | import androidx.databinding.ViewDataBinding; |
| 15 | 13 | |
| 14 | +import com.alibaba.android.arouter.launcher.ARouter; | |
| 16 | 15 | import com.cnlive.cloud.R; |
| 17 | 16 | import com.cnlive.cloud.databinding.ActivityDemoMvpMainBinding; |
| 18 | 17 | import com.cnlive.cloud.user.auth.UserService; |
| ... | ... | @@ -23,8 +22,11 @@ import com.cnlive.cloud.user.ui.activity.PersonalDataActivity; |
| 23 | 22 | import com.cnlive.cloud.wjjpay.pay.WjjPay; |
| 24 | 23 | import com.cnlive.cloud.xiaojiaspeaker.commonInfo.XiaoJIaCommInfo; |
| 25 | 24 | import com.cnlive.cloud.xiaojiaspeaker.ui.activity.AllDeviceActivity; |
| 25 | +import com.cnlive.core.libs.base.arouter.user.CNLiveUserService; | |
| 26 | 26 | import com.cnlive.core.libs.base.frame.activity.BaseActivity; |
| 27 | 27 | import com.cnlive.core.libs.base.interfaces.OnPermissionResponseListener; |
| 28 | +import com.cnlive.core.libs.base.util.AlertUtil; | |
| 29 | +import com.cnlive.core.libs.base.util.OpenServiceHelpUtil; | |
| 28 | 30 | import com.cnlive.core.libs.base.util.QMUITipDialogUtil; |
| 29 | 31 | import com.cnlive.core.libs.base.util.StatusBarConfig; |
| 30 | 32 | import com.cnlive.core.network.HttpConn; |
| ... | ... | @@ -33,9 +35,10 @@ import com.cnlive.core.network.model.BaseResult; |
| 33 | 35 | import com.cnlive.core.network.request.BaseRequest; |
| 34 | 36 | import com.cnlive.moudle.pedometer.commonInfo.CommonInfo; |
| 35 | 37 | import com.cnlive.moudle.pedometer.ui.activity.RunLineActivity; |
| 36 | -import com.cnlive.strike.MyApp; | |
| 37 | 38 | import com.cnlive.strike.demo.activity.presenter.DemoActivityPresenter; |
| 38 | 39 | import com.cnlive.strike.demo.activity.view.DemoActivityView; |
| 40 | +import com.cnlive.strike.demo.exoplayer.demo.PlayerActivity; | |
| 41 | +import com.cnlive.strike.demo.exoplayer.demo.SampleChooserActivity; | |
| 39 | 42 | import com.cnlive.strike.network.api.ApiService; |
| 40 | 43 | import com.cnlive.strike.network.api.ApiTimeService; |
| 41 | 44 | import com.cnlive.strike.network.api.TimeServiceBean; |
| ... | ... | @@ -399,9 +402,47 @@ public class DemoMvpActivity extends BaseActivity<DemoActivityView, DemoActivity |
| 399 | 402 | binding.btnLivePlayer.setOnClickListener(new View.OnClickListener() { |
| 400 | 403 | @Override |
| 401 | 404 | public void onClick(View v) { |
| 402 | - Intent intent=new Intent(me,CNplayerActivity.class); | |
| 403 | - intent.putExtra("type","1"); | |
| 404 | - startActivity(intent); } | |
| 405 | + | |
| 406 | +// Intent intent=new Intent(me,CNplayerActivity.class); | |
| 407 | +// intent.putExtra("type","1"); | |
| 408 | +// startActivityForResult(intent,100); | |
| 409 | + | |
| 410 | + ARouter.getInstance().build(CNplayerActivity.PATH).navigation(me,300); | |
| 411 | + | |
| 412 | + } | |
| 413 | + }); | |
| 414 | + binding.btnExo.setOnClickListener(new View.OnClickListener() { | |
| 415 | + @Override | |
| 416 | + public void onClick(View v) { | |
| 417 | + Intent intent=new Intent(me,ExoPlayerActivity.class); | |
| 418 | + startActivity(intent); | |
| 419 | + } | |
| 420 | + }); | |
| 421 | + //exo官方 | |
| 422 | + binding.btnExoHome.setOnClickListener(new View.OnClickListener() { | |
| 423 | + @Override | |
| 424 | + public void onClick(View v) { | |
| 425 | + Intent intent=new Intent(me, SampleChooserActivity.class); | |
| 426 | + startActivity(intent); | |
| 427 | + } | |
| 428 | + }); | |
| 429 | + //获取uid | |
| 430 | + binding.btnUid.setOnClickListener(new View.OnClickListener() { | |
| 431 | + @Override | |
| 432 | + public void onClick(View v) { | |
| 433 | + CNLiveUserService userService= OpenServiceHelpUtil.getUserService(me); | |
| 434 | + if (null!=userService){ | |
| 435 | + QMUITipDialogUtil.textDialog(me,userService.getUid(me)); | |
| 436 | + } | |
| 437 | + } | |
| 438 | + }); | |
| 439 | + //上海电商 | |
| 440 | + binding.btnShanghai.setOnClickListener(new View.OnClickListener() { | |
| 441 | + @Override | |
| 442 | + public void onClick(View v) { | |
| 443 | + Intent intent = new Intent(me, cn.cnlive.b2c.MainActivity.class); | |
| 444 | + startActivity(intent); | |
| 445 | + } | |
| 405 | 446 | }); |
| 406 | 447 | |
| 407 | 448 | } |
| ... | ... | @@ -493,4 +534,5 @@ public class DemoMvpActivity extends BaseActivity<DemoActivityView, DemoActivity |
| 493 | 534 | // /*添加View到当前显示的Activity*/ |
| 494 | 535 | // MyApp.getInstance().showBottomView(view); |
| 495 | 536 | } |
| 537 | + | |
| 496 | 538 | } | ... | ... |
app/strike/src/main/java/com/cnlive/strike/demo/activity/ExoPlayerActivity.java
0 → 100644
| 1 | +package com.cnlive.strike.demo.activity; | |
| 2 | + | |
| 3 | +import android.net.Uri; | |
| 4 | +import android.view.Surface; | |
| 5 | +import android.view.SurfaceControl; | |
| 6 | +import android.view.SurfaceView; | |
| 7 | +import android.view.View; | |
| 8 | + | |
| 9 | +import androidx.annotation.Nullable; | |
| 10 | +import androidx.core.content.ContextCompat; | |
| 11 | +import androidx.databinding.ViewDataBinding; | |
| 12 | + | |
| 13 | +import com.cnlive.cloud.R; | |
| 14 | +import com.cnlive.cloud.databinding.ActivityExplayerBinding; | |
| 15 | +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; | |
| 20 | +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.trackselection.TrackSelectionArray; | |
| 24 | +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; | |
| 25 | +import com.google.android.exoplayer2.video.VideoListener; | |
| 26 | +import com.libs.video.video.exoplayer.CNLiveBaseVideoControlView; | |
| 27 | +import com.libs.video.video.exoplayer.MyTestControlView; | |
| 28 | +import com.libs.video.video.exoplayer.VideoPlayer; | |
| 29 | + | |
| 30 | +public class ExoPlayerActivity extends BaseActivity implements VideoListener, Player.EventListener { | |
| 31 | + ActivityExplayerBinding binding; | |
| 32 | + //step2. 创建播放器 | |
| 33 | +// public SimpleExoPlayer player; | |
| 34 | + SurfaceControl surfaceControl; | |
| 35 | + Surface videoSurface; | |
| 36 | + private VideoPlayer videoPlayer; | |
| 37 | + | |
| 38 | + @Override | |
| 39 | + protected int getBindLayoutId() { | |
| 40 | + return R.layout.activity_explayer; | |
| 41 | + } | |
| 42 | + | |
| 43 | + @Override | |
| 44 | + protected void initBindingLayoutData(ViewDataBinding baseBinding) { | |
| 45 | + binding = (ActivityExplayerBinding) baseBinding; | |
| 46 | + } | |
| 47 | + | |
| 48 | + @Override | |
| 49 | + protected void initView() { | |
| 50 | + initVideoPlayer(); | |
| 51 | + // Produces DataSource instances through which media data is loaded. | |
| 52 | +// DataSource dataSource =new DefaultDataSource(context,Util.getUserAgent(context, "yourApplicationName"),true); | |
| 53 | +// Create a data source factory. | |
| 54 | +// DataSource.Factory dataSourceFactory = | |
| 55 | +// new DefaultHttpDataSourceFactory(Util.getUserAgent(context, "app-name")); | |
| 56 | +// | |
| 57 | + Uri mp4VideoUri = Uri.parse("http://ffyl.vods2.cnlive.com/223/vod/2018/1120/223_88c98ab1395f4762b703fbdd9d59d2e5/ff80808166c2a45201672f14a9933131_800.m3u8"); | |
| 58 | +//// HlsMediaSource hlsMediaSource = | |
| 59 | +//// new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mp4VideoUri); | |
| 60 | +// Uri dashUri = Uri.parse("rtmp://tyzg.rtmps2.cnlive.com/live-tyzg-577/577_999"); | |
| 61 | +// | |
| 62 | +// RtmpDataSourceFactory rtmpDataSourceFactory=new RtmpDataSourceFactory(); | |
| 63 | +// | |
| 64 | +// MediaSource mediaSource = new ProgressiveMediaSource.Factory(rtmpDataSourceFactory) | |
| 65 | +// .createMediaSource(dashUri); | |
| 66 | +// HlsMediaSource hlsMediaSource = | |
| 67 | +// new HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mp4VideoUri); | |
| 68 | +// SsMediaSource ssMediaSource = new SsMediaSource.Factory(dataSourceFactory).createMediaSource(dashUri); | |
| 69 | +// | |
| 70 | +// //step2. 创建播放器 | |
| 71 | +// player = new SimpleExoPlayer.Builder(context).build(); | |
| 72 | +// player.addVideoListener(this); | |
| 73 | +// player.addListener(this); | |
| 74 | +// // Prepare the player with the source. | |
| 75 | +// player.prepare(hlsMediaSource); | |
| 76 | +// //绑定视图 | |
| 77 | +// binding.playerView.setPlayer(player); | |
| 78 | + | |
| 79 | + //全屏 | |
| 80 | +// player.setVideoScalingMode(C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING); | |
| 81 | + //按比例放大 | |
| 82 | +// player.setVideoScalingMode(C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING); | |
| 83 | +// player.setForegroundMode(true); | |
| 84 | +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { | |
| 85 | +// surfaceControl = | |
| 86 | +// new SurfaceControl.Builder() | |
| 87 | +// .setName("测试") | |
| 88 | +// .setBufferSize(/* width= */ 0, /* height= */ 0) | |
| 89 | +// .build(); | |
| 90 | +// videoSurface = new Surface(surfaceControl); | |
| 91 | +// player.setVideoSurface(videoSurface); | |
| 92 | +// } | |
| 93 | +// CacheUtil cacheUtil=new CacheUtil(this); | |
| 94 | +// try { | |
| 95 | +// cacheUtil.getVideoUri("123456"); | |
| 96 | +// cacheUtil.add("123456","test"); | |
| 97 | +// cacheUtil.getVideoUri("123456"); | |
| 98 | +// } catch (IllegalBlockSizeException e) { | |
| 99 | +// e.printStackTrace(); | |
| 100 | +// } catch (NoSuchPaddingException e) { | |
| 101 | +// e.printStackTrace(); | |
| 102 | +// } catch (BadPaddingException e) { | |
| 103 | +// e.printStackTrace(); | |
| 104 | +// } catch (InvalidKeySpecException e) { | |
| 105 | +// e.printStackTrace(); | |
| 106 | +// } catch (InvalidKeyException e) { | |
| 107 | +// e.printStackTrace(); | |
| 108 | +// } | |
| 109 | +// | |
| 110 | +// binding.playerView.setOnClickListener(new View.OnClickListener() { | |
| 111 | +// @Override | |
| 112 | +// public void onClick(View v) { | |
| 113 | +//// player.seekTo(player.getCurrentWindowIndex(),5000); | |
| 114 | +// } | |
| 115 | +// }); | |
| 116 | + } | |
| 117 | + | |
| 118 | + private void initVideoPlayer() { | |
| 119 | +// Uri dashUri = Uri.parse("rtmp://tyzg.rtmps2.cnlive.com/live-tyzg-577/577_999"); | |
| 120 | + | |
| 121 | +// Uri mp4VideoUri = Uri.parse("http://ffyl.vods2.cnlive.com/223/vod/2018/1120/223_88c98ab1395f4762b703fbdd9d59d2e5/ff80808166c2a45201672f14a9933131_800.m3u8"); | |
| 122 | +// videoPlayer = new VideoPlayer(this); | |
| 123 | +// videoPlayer.setDataSource(mp4VideoUri); | |
| 124 | +// videoPlayer.setPlayView(binding.playerView); | |
| 125 | +// videoPlayer.prepare(true); | |
| 126 | +// MyTestControlView myTestControlView = new MyTestControlView(context); | |
| 127 | +// myTestControlView.setPlayer(videoPlayer.player); | |
| 128 | + binding.playerView.setCustomControllView(new MyTestControlView(context)); | |
| 129 | +// binding.playerView.setOnClickListener(new View.OnClickListener() { | |
| 130 | +// @Override | |
| 131 | +// public void onClick(View v) { | |
| 132 | +// videoPlayer.pause(); | |
| 133 | +// } | |
| 134 | +// }); | |
| 135 | + } | |
| 136 | + | |
| 137 | + @Override | |
| 138 | + public void onTimelineChanged(Timeline timeline, int reason) { | |
| 139 | + | |
| 140 | + } | |
| 141 | + | |
| 142 | + @Override | |
| 143 | + public void onTimelineChanged(Timeline timeline, @Nullable Object manifest, int reason) { | |
| 144 | + | |
| 145 | + } | |
| 146 | + | |
| 147 | + @Override | |
| 148 | + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { | |
| 149 | + | |
| 150 | + } | |
| 151 | + | |
| 152 | + @Override | |
| 153 | + public void onLoadingChanged(boolean isLoading) { | |
| 154 | + | |
| 155 | + } | |
| 156 | + | |
| 157 | + @Override | |
| 158 | + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { | |
| 159 | + logError(playbackState + ""); | |
| 160 | + } | |
| 161 | + | |
| 162 | + @Override | |
| 163 | + public void onPlaybackSuppressionReasonChanged(int playbackSuppressionReason) { | |
| 164 | + | |
| 165 | + } | |
| 166 | + | |
| 167 | + @Override | |
| 168 | + public void onIsPlayingChanged(boolean isPlaying) { | |
| 169 | + | |
| 170 | + } | |
| 171 | + | |
| 172 | + @Override | |
| 173 | + public void onRepeatModeChanged(int repeatMode) { | |
| 174 | + | |
| 175 | + } | |
| 176 | + | |
| 177 | + @Override | |
| 178 | + public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { | |
| 179 | + | |
| 180 | + } | |
| 181 | + | |
| 182 | + @Override | |
| 183 | + public void onPlayerError(ExoPlaybackException error) { | |
| 184 | + | |
| 185 | + } | |
| 186 | + | |
| 187 | + @Override | |
| 188 | + public void onPositionDiscontinuity(int reason) { | |
| 189 | + | |
| 190 | + } | |
| 191 | + | |
| 192 | + @Override | |
| 193 | + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { | |
| 194 | + | |
| 195 | + } | |
| 196 | + | |
| 197 | + @Override | |
| 198 | + public void onSeekProcessed() { | |
| 199 | + | |
| 200 | + } | |
| 201 | +} | ... | ... |
app/strike/src/main/java/com/cnlive/strike/demo/activity/IVideoPlayer.java
0 → 100644
app/strike/src/main/java/com/cnlive/strike/demo/exoplayer/demo/DemoApplication.java
0 → 100644
| 1 | +/* | |
| 2 | + * Copyright (C) 2016 The Android Open Source Project | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + */ | |
| 16 | +package com.cnlive.strike.demo.exoplayer.demo; | |
| 17 | + | |
| 18 | +import android.app.Application; | |
| 19 | + | |
| 20 | +import com.alibaba.android.arouter.launcher.ARouter; | |
| 21 | +import com.baidu.mapapi.CoordType; | |
| 22 | +import com.baidu.mapapi.SDKInitializer; | |
| 23 | +import com.cnlive.core.libs.base.application.AppConfig; | |
| 24 | +import com.cnlive.core.libs.base.application.BaseApplication; | |
| 25 | +import com.google.android.exoplayer2.BuildConfig; | |
| 26 | +import com.google.android.exoplayer2.DefaultRenderersFactory; | |
| 27 | +import com.google.android.exoplayer2.RenderersFactory; | |
| 28 | +import com.google.android.exoplayer2.database.DatabaseProvider; | |
| 29 | +import com.google.android.exoplayer2.database.ExoDatabaseProvider; | |
| 30 | +import com.google.android.exoplayer2.offline.ActionFileUpgradeUtil; | |
| 31 | +import com.google.android.exoplayer2.offline.DefaultDownloadIndex; | |
| 32 | +import com.google.android.exoplayer2.offline.DefaultDownloaderFactory; | |
| 33 | +import com.google.android.exoplayer2.offline.DownloadManager; | |
| 34 | +import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; | |
| 35 | +import com.google.android.exoplayer2.upstream.DataSource; | |
| 36 | +import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; | |
| 37 | +import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; | |
| 38 | +import com.google.android.exoplayer2.upstream.FileDataSource; | |
| 39 | +import com.google.android.exoplayer2.upstream.HttpDataSource; | |
| 40 | +import com.google.android.exoplayer2.upstream.cache.Cache; | |
| 41 | +import com.google.android.exoplayer2.upstream.cache.CacheDataSource; | |
| 42 | +import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory; | |
| 43 | +import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor; | |
| 44 | +import com.google.android.exoplayer2.upstream.cache.SimpleCache; | |
| 45 | +import com.google.android.exoplayer2.util.Log; | |
| 46 | +import com.google.android.exoplayer2.util.Util; | |
| 47 | +import java.io.File; | |
| 48 | +import java.io.IOException; | |
| 49 | + | |
| 50 | +/** | |
| 51 | + * Placeholder application to facilitate overriding Application methods for debugging and testing. | |
| 52 | + */ | |
| 53 | +public class DemoApplication extends BaseApplication { | |
| 54 | + | |
| 55 | + private static final String TAG = "DemoApplication"; | |
| 56 | + private static final String DOWNLOAD_ACTION_FILE = "actions"; | |
| 57 | + private static final String DOWNLOAD_TRACKER_ACTION_FILE = "tracked_actions"; | |
| 58 | + private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads"; | |
| 59 | + | |
| 60 | + protected String userAgent; | |
| 61 | + | |
| 62 | + private DatabaseProvider databaseProvider; | |
| 63 | + private File downloadDirectory; | |
| 64 | + private Cache downloadCache; | |
| 65 | + private DownloadManager downloadManager; | |
| 66 | + private DownloadTracker downloadTracker; | |
| 67 | + | |
| 68 | + @Override | |
| 69 | + public void onCreate() { | |
| 70 | + super.onCreate(); | |
| 71 | + init(); | |
| 72 | + userAgent = Util.getUserAgent(this, "ExoPlayerDemo"); | |
| 73 | + } | |
| 74 | + | |
| 75 | + private void init() { | |
| 76 | + if (com.cnlive.cloud.BuildConfig.DEBUG) { // 这两行必须写在init之前,否则这些配置在init过程中将无效 | |
| 77 | + ARouter.openLog(); // 打印日志 | |
| 78 | + ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险) | |
| 79 | + ARouter.printStackTrace(); // 打印日志的时候打印线程堆栈 | |
| 80 | + } | |
| 81 | + ARouter.init(this); // 尽可能早,推荐在Application中初始化 | |
| 82 | + AppConfig.init(this, com.cnlive.cloud.BuildConfig.APP_ID, com.cnlive.cloud.BuildConfig.APP_KEY, | |
| 83 | + com.cnlive.cloud.BuildConfig.APP_SCERET, com.cnlive.cloud.BuildConfig.SPECIAL_GROUP_MEMBER_LIMIT, | |
| 84 | + com.cnlive.cloud.BuildConfig.VERSION_AS_DEBUG, com.cnlive.cloud.BuildConfig.BAIDU_MAP_APP_KEY, "", | |
| 85 | + "10514333", "", | |
| 86 | + "", "", "", | |
| 87 | + "", "", "", | |
| 88 | + com.cnlive.cloud.BuildConfig.BAIDU_MAP_APP_KEY, ""); | |
| 89 | +// if (AppConfig.isDebug()) { | |
| 90 | +// Constant.SERVICE_ID = 213511; | |
| 91 | +// } else { | |
| 92 | +// Constant.SERVICE_ID = 214690; | |
| 93 | +// } | |
| 94 | + //在使用SDK各组件之前初始化context信息,传入ApplicationContext | |
| 95 | + SDKInitializer.initialize(getApplicationContext()); | |
| 96 | + //自4.3.0起,百度地图SDK所有接口均支持百度坐标和国测局坐标,用此方法设置您使用的坐标类型. | |
| 97 | + //包括BD09LL和GCJ02两种坐标,默认是BD09LL坐标。 | |
| 98 | + SDKInitializer.setCoordType(CoordType.GCJ02); | |
| 99 | + //初始化上海电商 | |
| 100 | + cn.cnlive.b2c.AppApplication.initSDK(this); | |
| 101 | + } | |
| 102 | + | |
| 103 | + /** Returns a {@link DataSource.Factory}. */ | |
| 104 | + public DataSource.Factory buildDataSourceFactory() { | |
| 105 | + DefaultDataSourceFactory upstreamFactory = | |
| 106 | + new DefaultDataSourceFactory(this, buildHttpDataSourceFactory()); | |
| 107 | + return buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache()); | |
| 108 | + } | |
| 109 | + | |
| 110 | + /** Returns a {@link HttpDataSource.Factory}. */ | |
| 111 | + public HttpDataSource.Factory buildHttpDataSourceFactory() { | |
| 112 | + return new DefaultHttpDataSourceFactory(userAgent); | |
| 113 | + } | |
| 114 | + | |
| 115 | + /** Returns whether extension renderers should be used. */ | |
| 116 | + public boolean useExtensionRenderers() { | |
| 117 | + return "withExtensions".equals(BuildConfig.FLAVOR); | |
| 118 | + } | |
| 119 | + | |
| 120 | + public RenderersFactory buildRenderersFactory(boolean preferExtensionRenderer) { | |
| 121 | + @DefaultRenderersFactory.ExtensionRendererMode | |
| 122 | + int extensionRendererMode = | |
| 123 | + useExtensionRenderers() | |
| 124 | + ? (preferExtensionRenderer | |
| 125 | + ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER | |
| 126 | + : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON) | |
| 127 | + : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF; | |
| 128 | + return new DefaultRenderersFactory(/* context= */ this) | |
| 129 | + .setExtensionRendererMode(extensionRendererMode); | |
| 130 | + } | |
| 131 | + | |
| 132 | + public DownloadManager getDownloadManager() { | |
| 133 | + initDownloadManager(); | |
| 134 | + return downloadManager; | |
| 135 | + } | |
| 136 | + | |
| 137 | + public DownloadTracker getDownloadTracker() { | |
| 138 | + initDownloadManager(); | |
| 139 | + return downloadTracker; | |
| 140 | + } | |
| 141 | + | |
| 142 | + protected synchronized Cache getDownloadCache() { | |
| 143 | + if (downloadCache == null) { | |
| 144 | + File downloadContentDirectory = new File(getDownloadDirectory(), DOWNLOAD_CONTENT_DIRECTORY); | |
| 145 | + downloadCache = | |
| 146 | + new SimpleCache(downloadContentDirectory, new NoOpCacheEvictor(), getDatabaseProvider()); | |
| 147 | + } | |
| 148 | + return downloadCache; | |
| 149 | + } | |
| 150 | + | |
| 151 | + private synchronized void initDownloadManager() { | |
| 152 | + if (downloadManager == null) { | |
| 153 | + DefaultDownloadIndex downloadIndex = new DefaultDownloadIndex(getDatabaseProvider()); | |
| 154 | + upgradeActionFile( | |
| 155 | + DOWNLOAD_ACTION_FILE, downloadIndex, /* addNewDownloadsAsCompleted= */ false); | |
| 156 | + upgradeActionFile( | |
| 157 | + DOWNLOAD_TRACKER_ACTION_FILE, downloadIndex, /* addNewDownloadsAsCompleted= */ true); | |
| 158 | + DownloaderConstructorHelper downloaderConstructorHelper = | |
| 159 | + new DownloaderConstructorHelper(getDownloadCache(), buildHttpDataSourceFactory()); | |
| 160 | + downloadManager = | |
| 161 | + new DownloadManager( | |
| 162 | + this, downloadIndex, new DefaultDownloaderFactory(downloaderConstructorHelper)); | |
| 163 | + downloadTracker = | |
| 164 | + new DownloadTracker(/* context= */ this, buildDataSourceFactory(), downloadManager); | |
| 165 | + } | |
| 166 | + } | |
| 167 | + | |
| 168 | + private void upgradeActionFile( | |
| 169 | + String fileName, DefaultDownloadIndex downloadIndex, boolean addNewDownloadsAsCompleted) { | |
| 170 | + try { | |
| 171 | + ActionFileUpgradeUtil.upgradeAndDelete( | |
| 172 | + new File(getDownloadDirectory(), fileName), | |
| 173 | + /* downloadIdProvider= */ null, | |
| 174 | + downloadIndex, | |
| 175 | + /* deleteOnFailure= */ true, | |
| 176 | + addNewDownloadsAsCompleted); | |
| 177 | + } catch (IOException e) { | |
| 178 | + Log.e(TAG, "Failed to upgrade action file: " + fileName, e); | |
| 179 | + } | |
| 180 | + } | |
| 181 | + | |
| 182 | + private DatabaseProvider getDatabaseProvider() { | |
| 183 | + if (databaseProvider == null) { | |
| 184 | + databaseProvider = new ExoDatabaseProvider(this); | |
| 185 | + } | |
| 186 | + return databaseProvider; | |
| 187 | + } | |
| 188 | + | |
| 189 | + private File getDownloadDirectory() { | |
| 190 | + if (downloadDirectory == null) { | |
| 191 | + downloadDirectory = getExternalFilesDir(null); | |
| 192 | + if (downloadDirectory == null) { | |
| 193 | + downloadDirectory = getFilesDir(); | |
| 194 | + } | |
| 195 | + } | |
| 196 | + return downloadDirectory; | |
| 197 | + } | |
| 198 | + | |
| 199 | + protected static CacheDataSourceFactory buildReadOnlyCacheDataSource( | |
| 200 | + DataSource.Factory upstreamFactory, Cache cache) { | |
| 201 | + return new CacheDataSourceFactory( | |
| 202 | + cache, | |
| 203 | + upstreamFactory, | |
| 204 | + new FileDataSource.Factory(), | |
| 205 | + /* cacheWriteDataSinkFactory= */ null, | |
| 206 | + CacheDataSource.FLAG_IGNORE_CACHE_ON_ERROR, | |
| 207 | + /* eventListener= */ null); | |
| 208 | + } | |
| 209 | +} | ... | ... |
app/strike/src/main/java/com/cnlive/strike/demo/exoplayer/demo/DemoDownloadService.java
0 → 100644
| 1 | +/* | |
| 2 | + * Copyright (C) 2017 The Android Open Source Project | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + */ | |
| 16 | +package com.cnlive.strike.demo.exoplayer.demo; | |
| 17 | + | |
| 18 | +import android.app.Notification; | |
| 19 | + | |
| 20 | +import com.cnlive.cloud.R; | |
| 21 | +import com.google.android.exoplayer2.offline.Download; | |
| 22 | +import com.google.android.exoplayer2.offline.DownloadManager; | |
| 23 | +import com.google.android.exoplayer2.offline.DownloadService; | |
| 24 | +import com.google.android.exoplayer2.scheduler.PlatformScheduler; | |
| 25 | +import com.google.android.exoplayer2.ui.DownloadNotificationHelper; | |
| 26 | +import com.google.android.exoplayer2.util.NotificationUtil; | |
| 27 | +import com.google.android.exoplayer2.util.Util; | |
| 28 | +import java.util.List; | |
| 29 | + | |
| 30 | +/** A service for downloading media. */ | |
| 31 | +public class DemoDownloadService extends DownloadService { | |
| 32 | + | |
| 33 | + private static final String CHANNEL_ID = "download_channel"; | |
| 34 | + private static final int JOB_ID = 1; | |
| 35 | + private static final int FOREGROUND_NOTIFICATION_ID = 1; | |
| 36 | + | |
| 37 | + private static int nextNotificationId = FOREGROUND_NOTIFICATION_ID + 1; | |
| 38 | + | |
| 39 | + private DownloadNotificationHelper notificationHelper; | |
| 40 | + | |
| 41 | + public DemoDownloadService() { | |
| 42 | + super( | |
| 43 | + FOREGROUND_NOTIFICATION_ID, | |
| 44 | + DEFAULT_FOREGROUND_NOTIFICATION_UPDATE_INTERVAL, | |
| 45 | + CHANNEL_ID, | |
| 46 | + R.string.exo_download_notification_channel_name, | |
| 47 | + /* channelDescriptionResourceId= */ 0); | |
| 48 | + nextNotificationId = FOREGROUND_NOTIFICATION_ID + 1; | |
| 49 | + } | |
| 50 | + | |
| 51 | + @Override | |
| 52 | + public void onCreate() { | |
| 53 | + super.onCreate(); | |
| 54 | + notificationHelper = new DownloadNotificationHelper(this, CHANNEL_ID); | |
| 55 | + } | |
| 56 | + | |
| 57 | + @Override | |
| 58 | + protected DownloadManager getDownloadManager() { | |
| 59 | + return ((DemoApplication) getApplication()).getDownloadManager(); | |
| 60 | + } | |
| 61 | + | |
| 62 | + @Override | |
| 63 | + protected PlatformScheduler getScheduler() { | |
| 64 | + return Util.SDK_INT >= 21 ? new PlatformScheduler(this, JOB_ID) : null; | |
| 65 | + } | |
| 66 | + | |
| 67 | + @Override | |
| 68 | + protected Notification getForegroundNotification(List<Download> downloads) { | |
| 69 | + return notificationHelper.buildProgressNotification( | |
| 70 | + R.drawable.ic_download, /* contentIntent= */ null, /* message= */ null, downloads); | |
| 71 | + } | |
| 72 | + | |
| 73 | + @Override | |
| 74 | + protected void onDownloadChanged(Download download) { | |
| 75 | + Notification notification; | |
| 76 | + if (download.state == Download.STATE_COMPLETED) { | |
| 77 | + notification = | |
| 78 | + notificationHelper.buildDownloadCompletedNotification( | |
| 79 | + R.drawable.ic_download_done, | |
| 80 | + /* contentIntent= */ null, | |
| 81 | + Util.fromUtf8Bytes(download.request.data)); | |
| 82 | + } else if (download.state == Download.STATE_FAILED) { | |
| 83 | + notification = | |
| 84 | + notificationHelper.buildDownloadFailedNotification( | |
| 85 | + R.drawable.ic_download_done, | |
| 86 | + /* contentIntent= */ null, | |
| 87 | + Util.fromUtf8Bytes(download.request.data)); | |
| 88 | + } else { | |
| 89 | + return; | |
| 90 | + } | |
| 91 | + NotificationUtil.setNotification(this, nextNotificationId++, notification); | |
| 92 | + } | |
| 93 | +} | ... | ... |
app/strike/src/main/java/com/cnlive/strike/demo/exoplayer/demo/DownloadTracker.java
0 → 100644
| 1 | +/* | |
| 2 | + * Copyright (C) 2017 The Android Open Source Project | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + */ | |
| 16 | +package com.cnlive.strike.demo.exoplayer.demo; | |
| 17 | + | |
| 18 | +import android.content.Context; | |
| 19 | +import android.content.DialogInterface; | |
| 20 | +import android.net.Uri; | |
| 21 | +import android.widget.Toast; | |
| 22 | +import androidx.annotation.Nullable; | |
| 23 | +import androidx.fragment.app.FragmentManager; | |
| 24 | + | |
| 25 | +import com.cnlive.cloud.R; | |
| 26 | +import com.google.android.exoplayer2.C; | |
| 27 | +import com.google.android.exoplayer2.RenderersFactory; | |
| 28 | +import com.google.android.exoplayer2.offline.Download; | |
| 29 | +import com.google.android.exoplayer2.offline.DownloadCursor; | |
| 30 | +import com.google.android.exoplayer2.offline.DownloadHelper; | |
| 31 | +import com.google.android.exoplayer2.offline.DownloadIndex; | |
| 32 | +import com.google.android.exoplayer2.offline.DownloadManager; | |
| 33 | +import com.google.android.exoplayer2.offline.DownloadRequest; | |
| 34 | +import com.google.android.exoplayer2.offline.DownloadService; | |
| 35 | +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; | |
| 36 | +import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; | |
| 37 | +import com.google.android.exoplayer2.upstream.DataSource; | |
| 38 | +import com.google.android.exoplayer2.util.Log; | |
| 39 | +import com.google.android.exoplayer2.util.Util; | |
| 40 | +import java.io.IOException; | |
| 41 | +import java.util.HashMap; | |
| 42 | +import java.util.concurrent.CopyOnWriteArraySet; | |
| 43 | + | |
| 44 | +/** Tracks media that has been downloaded. */ | |
| 45 | +public class DownloadTracker { | |
| 46 | + | |
| 47 | + /** Listens for changes in the tracked downloads. */ | |
| 48 | + public interface Listener { | |
| 49 | + | |
| 50 | + /** Called when the tracked downloads changed. */ | |
| 51 | + void onDownloadsChanged(); | |
| 52 | + } | |
| 53 | + | |
| 54 | + private static final String TAG = "DownloadTracker"; | |
| 55 | + | |
| 56 | + private final Context context; | |
| 57 | + private final DataSource.Factory dataSourceFactory; | |
| 58 | + private final CopyOnWriteArraySet<Listener> listeners; | |
| 59 | + private final HashMap<Uri, Download> downloads; | |
| 60 | + private final DownloadIndex downloadIndex; | |
| 61 | + private final DefaultTrackSelector.Parameters trackSelectorParameters; | |
| 62 | + | |
| 63 | + @Nullable private StartDownloadDialogHelper startDownloadDialogHelper; | |
| 64 | + | |
| 65 | + public DownloadTracker( | |
| 66 | + Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) { | |
| 67 | + this.context = context.getApplicationContext(); | |
| 68 | + this.dataSourceFactory = dataSourceFactory; | |
| 69 | + listeners = new CopyOnWriteArraySet<>(); | |
| 70 | + downloads = new HashMap<>(); | |
| 71 | + downloadIndex = downloadManager.getDownloadIndex(); | |
| 72 | + trackSelectorParameters = DownloadHelper.getDefaultTrackSelectorParameters(context); | |
| 73 | + downloadManager.addListener(new DownloadManagerListener()); | |
| 74 | + loadDownloads(); | |
| 75 | + } | |
| 76 | + | |
| 77 | + public void addListener(Listener listener) { | |
| 78 | + listeners.add(listener); | |
| 79 | + } | |
| 80 | + | |
| 81 | + public void removeListener(Listener listener) { | |
| 82 | + listeners.remove(listener); | |
| 83 | + } | |
| 84 | + | |
| 85 | + public boolean isDownloaded(Uri uri) { | |
| 86 | + Download download = downloads.get(uri); | |
| 87 | + return download != null && download.state != Download.STATE_FAILED; | |
| 88 | + } | |
| 89 | + | |
| 90 | + public DownloadRequest getDownloadRequest(Uri uri) { | |
| 91 | + Download download = downloads.get(uri); | |
| 92 | + return download != null && download.state != Download.STATE_FAILED ? download.request : null; | |
| 93 | + } | |
| 94 | + | |
| 95 | + public void toggleDownload( | |
| 96 | + FragmentManager fragmentManager, | |
| 97 | + String name, | |
| 98 | + Uri uri, | |
| 99 | + String extension, | |
| 100 | + RenderersFactory renderersFactory) { | |
| 101 | + Download download = downloads.get(uri); | |
| 102 | + if (download != null) { | |
| 103 | + DownloadService.sendRemoveDownload( | |
| 104 | + context, DemoDownloadService.class, download.request.id, /* foreground= */ false); | |
| 105 | + } else { | |
| 106 | + if (startDownloadDialogHelper != null) { | |
| 107 | + startDownloadDialogHelper.release(); | |
| 108 | + } | |
| 109 | + startDownloadDialogHelper = | |
| 110 | + new StartDownloadDialogHelper( | |
| 111 | + fragmentManager, getDownloadHelper(uri, extension, renderersFactory), name); | |
| 112 | + } | |
| 113 | + } | |
| 114 | + | |
| 115 | + private void loadDownloads() { | |
| 116 | + try (DownloadCursor loadedDownloads = downloadIndex.getDownloads()) { | |
| 117 | + while (loadedDownloads.moveToNext()) { | |
| 118 | + Download download = loadedDownloads.getDownload(); | |
| 119 | + downloads.put(download.request.uri, download); | |
| 120 | + } | |
| 121 | + } catch (IOException e) { | |
| 122 | + Log.w(TAG, "Failed to query downloads", e); | |
| 123 | + } | |
| 124 | + } | |
| 125 | + | |
| 126 | + private DownloadHelper getDownloadHelper( | |
| 127 | + Uri uri, String extension, RenderersFactory renderersFactory) { | |
| 128 | + int type = Util.inferContentType(uri, extension); | |
| 129 | + switch (type) { | |
| 130 | + case C.TYPE_DASH: | |
| 131 | + return DownloadHelper.forDash(context, uri, dataSourceFactory, renderersFactory); | |
| 132 | + case C.TYPE_SS: | |
| 133 | + return DownloadHelper.forSmoothStreaming(context, uri, dataSourceFactory, renderersFactory); | |
| 134 | + case C.TYPE_HLS: | |
| 135 | + return DownloadHelper.forHls(context, uri, dataSourceFactory, renderersFactory); | |
| 136 | + case C.TYPE_OTHER: | |
| 137 | + return DownloadHelper.forProgressive(context, uri); | |
| 138 | + default: | |
| 139 | + throw new IllegalStateException("Unsupported type: " + type); | |
| 140 | + } | |
| 141 | + } | |
| 142 | + | |
| 143 | + private class DownloadManagerListener implements DownloadManager.Listener { | |
| 144 | + | |
| 145 | + @Override | |
| 146 | + public void onDownloadChanged(DownloadManager downloadManager, Download download) { | |
| 147 | + downloads.put(download.request.uri, download); | |
| 148 | + for (Listener listener : listeners) { | |
| 149 | + listener.onDownloadsChanged(); | |
| 150 | + } | |
| 151 | + } | |
| 152 | + | |
| 153 | + @Override | |
| 154 | + public void onDownloadRemoved(DownloadManager downloadManager, Download download) { | |
| 155 | + downloads.remove(download.request.uri); | |
| 156 | + for (Listener listener : listeners) { | |
| 157 | + listener.onDownloadsChanged(); | |
| 158 | + } | |
| 159 | + } | |
| 160 | + } | |
| 161 | + | |
| 162 | + private final class StartDownloadDialogHelper | |
| 163 | + implements DownloadHelper.Callback, | |
| 164 | + DialogInterface.OnClickListener, | |
| 165 | + DialogInterface.OnDismissListener { | |
| 166 | + | |
| 167 | + private final FragmentManager fragmentManager; | |
| 168 | + private final DownloadHelper downloadHelper; | |
| 169 | + private final String name; | |
| 170 | + | |
| 171 | + private TrackSelectionDialog trackSelectionDialog; | |
| 172 | + private MappedTrackInfo mappedTrackInfo; | |
| 173 | + | |
| 174 | + public StartDownloadDialogHelper( | |
| 175 | + FragmentManager fragmentManager, DownloadHelper downloadHelper, String name) { | |
| 176 | + this.fragmentManager = fragmentManager; | |
| 177 | + this.downloadHelper = downloadHelper; | |
| 178 | + this.name = name; | |
| 179 | + downloadHelper.prepare(this); | |
| 180 | + } | |
| 181 | + | |
| 182 | + public void release() { | |
| 183 | + downloadHelper.release(); | |
| 184 | + if (trackSelectionDialog != null) { | |
| 185 | + trackSelectionDialog.dismiss(); | |
| 186 | + } | |
| 187 | + } | |
| 188 | + | |
| 189 | + // DownloadHelper.Callback implementation. | |
| 190 | + | |
| 191 | + @Override | |
| 192 | + public void onPrepared(DownloadHelper helper) { | |
| 193 | + if (helper.getPeriodCount() == 0) { | |
| 194 | + Log.d(TAG, "No periods found. Downloading entire stream."); | |
| 195 | + startDownload(); | |
| 196 | + downloadHelper.release(); | |
| 197 | + return; | |
| 198 | + } | |
| 199 | + mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0); | |
| 200 | + if (!TrackSelectionDialog.willHaveContent(mappedTrackInfo)) { | |
| 201 | + Log.d(TAG, "No dialog content. Downloading entire stream."); | |
| 202 | + startDownload(); | |
| 203 | + downloadHelper.release(); | |
| 204 | + return; | |
| 205 | + } | |
| 206 | + trackSelectionDialog = | |
| 207 | + TrackSelectionDialog.createForMappedTrackInfoAndParameters( | |
| 208 | + /* titleId= */ R.string.exo_download_description, | |
| 209 | + mappedTrackInfo, | |
| 210 | + trackSelectorParameters, | |
| 211 | + /* allowAdaptiveSelections =*/ false, | |
| 212 | + /* allowMultipleOverrides= */ true, | |
| 213 | + /* onClickListener= */ this, | |
| 214 | + /* onDismissListener= */ this); | |
| 215 | + trackSelectionDialog.show(fragmentManager, /* tag= */ null); | |
| 216 | + } | |
| 217 | + | |
| 218 | + @Override | |
| 219 | + public void onPrepareError(DownloadHelper helper, IOException e) { | |
| 220 | + Toast.makeText(context, R.string.download_start_error, Toast.LENGTH_LONG).show(); | |
| 221 | + Log.e( | |
| 222 | + TAG, | |
| 223 | + e instanceof DownloadHelper.LiveContentUnsupportedException | |
| 224 | + ? "Downloading live content unsupported" | |
| 225 | + : "Failed to start download", | |
| 226 | + e); | |
| 227 | + } | |
| 228 | + | |
| 229 | + // DialogInterface.OnClickListener implementation. | |
| 230 | + | |
| 231 | + @Override | |
| 232 | + public void onClick(DialogInterface dialog, int which) { | |
| 233 | + for (int periodIndex = 0; periodIndex < downloadHelper.getPeriodCount(); periodIndex++) { | |
| 234 | + downloadHelper.clearTrackSelections(periodIndex); | |
| 235 | + for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) { | |
| 236 | + if (!trackSelectionDialog.getIsDisabled(/* rendererIndex= */ i)) { | |
| 237 | + downloadHelper.addTrackSelectionForSingleRenderer( | |
| 238 | + periodIndex, | |
| 239 | + /* rendererIndex= */ i, | |
| 240 | + trackSelectorParameters, | |
| 241 | + trackSelectionDialog.getOverrides(/* rendererIndex= */ i)); | |
| 242 | + } | |
| 243 | + } | |
| 244 | + } | |
| 245 | + DownloadRequest downloadRequest = buildDownloadRequest(); | |
| 246 | + if (downloadRequest.streamKeys.isEmpty()) { | |
| 247 | + // All tracks were deselected in the dialog. Don't start the download. | |
| 248 | + return; | |
| 249 | + } | |
| 250 | + startDownload(downloadRequest); | |
| 251 | + } | |
| 252 | + | |
| 253 | + // DialogInterface.OnDismissListener implementation. | |
| 254 | + | |
| 255 | + @Override | |
| 256 | + public void onDismiss(DialogInterface dialogInterface) { | |
| 257 | + trackSelectionDialog = null; | |
| 258 | + downloadHelper.release(); | |
| 259 | + } | |
| 260 | + | |
| 261 | + // Internal methods. | |
| 262 | + | |
| 263 | + private void startDownload() { | |
| 264 | + startDownload(buildDownloadRequest()); | |
| 265 | + } | |
| 266 | + | |
| 267 | + private void startDownload(DownloadRequest downloadRequest) { | |
| 268 | + DownloadService.sendAddDownload( | |
| 269 | + context, DemoDownloadService.class, downloadRequest, /* foreground= */ false); | |
| 270 | + } | |
| 271 | + | |
| 272 | + private DownloadRequest buildDownloadRequest() { | |
| 273 | + return downloadHelper.getDownloadRequest(Util.getUtf8Bytes(name)); | |
| 274 | + } | |
| 275 | + } | |
| 276 | +} | ... | ... |
app/strike/src/main/java/com/cnlive/strike/demo/exoplayer/demo/PlayerActivity.java
0 → 100644
| 1 | +/* | |
| 2 | + * Copyright (C) 2016 The Android Open Source Project | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + */ | |
| 16 | +package com.cnlive.strike.demo.exoplayer.demo; | |
| 17 | + | |
| 18 | +import android.content.Intent; | |
| 19 | +import android.content.pm.PackageManager; | |
| 20 | +import android.media.MediaDrm; | |
| 21 | +import android.net.Uri; | |
| 22 | +import android.os.Bundle; | |
| 23 | +import android.util.Pair; | |
| 24 | +import android.view.KeyEvent; | |
| 25 | +import android.view.View; | |
| 26 | +import android.view.View.OnClickListener; | |
| 27 | +import android.widget.Button; | |
| 28 | +import android.widget.LinearLayout; | |
| 29 | +import android.widget.TextView; | |
| 30 | +import android.widget.Toast; | |
| 31 | +import androidx.annotation.NonNull; | |
| 32 | +import androidx.annotation.Nullable; | |
| 33 | +import androidx.appcompat.app.AppCompatActivity; | |
| 34 | + | |
| 35 | +import com.cnlive.cloud.R; | |
| 36 | +import com.google.android.exoplayer2.C; | |
| 37 | +import com.google.android.exoplayer2.C.ContentType; | |
| 38 | +import com.google.android.exoplayer2.ExoPlaybackException; | |
| 39 | +import com.google.android.exoplayer2.Format; | |
| 40 | +import com.google.android.exoplayer2.PlaybackPreparer; | |
| 41 | +import com.google.android.exoplayer2.Player; | |
| 42 | +import com.google.android.exoplayer2.RenderersFactory; | |
| 43 | +import com.google.android.exoplayer2.SimpleExoPlayer; | |
| 44 | +import com.google.android.exoplayer2.drm.DefaultDrmSessionManager; | |
| 45 | +import com.google.android.exoplayer2.drm.DrmSessionManager; | |
| 46 | +import com.google.android.exoplayer2.drm.ExoMediaCrypto; | |
| 47 | +import com.google.android.exoplayer2.drm.FrameworkMediaDrm; | |
| 48 | +import com.google.android.exoplayer2.drm.HttpMediaDrmCallback; | |
| 49 | +import com.google.android.exoplayer2.drm.MediaDrmCallback; | |
| 50 | +import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.DecoderInitializationException; | |
| 51 | +import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException; | |
| 52 | +import com.google.android.exoplayer2.offline.DownloadHelper; | |
| 53 | +import com.google.android.exoplayer2.offline.DownloadRequest; | |
| 54 | +import com.google.android.exoplayer2.source.BehindLiveWindowException; | |
| 55 | +import com.google.android.exoplayer2.source.ConcatenatingMediaSource; | |
| 56 | +import com.google.android.exoplayer2.source.MediaSource; | |
| 57 | +import com.google.android.exoplayer2.source.MediaSourceFactory; | |
| 58 | +import com.google.android.exoplayer2.source.MergingMediaSource; | |
| 59 | +import com.google.android.exoplayer2.source.ProgressiveMediaSource; | |
| 60 | +import com.google.android.exoplayer2.source.SingleSampleMediaSource; | |
| 61 | +import com.google.android.exoplayer2.source.TrackGroupArray; | |
| 62 | +import com.google.android.exoplayer2.source.ads.AdsLoader; | |
| 63 | +import com.google.android.exoplayer2.source.ads.AdsMediaSource; | |
| 64 | +import com.google.android.exoplayer2.source.dash.DashMediaSource; | |
| 65 | +import com.google.android.exoplayer2.source.hls.HlsMediaSource; | |
| 66 | +import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; | |
| 67 | +import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection; | |
| 68 | +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; | |
| 69 | +import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; | |
| 70 | +import com.google.android.exoplayer2.trackselection.RandomTrackSelection; | |
| 71 | +import com.google.android.exoplayer2.trackselection.TrackSelection; | |
| 72 | +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; | |
| 73 | +import com.google.android.exoplayer2.ui.DebugTextViewHelper; | |
| 74 | +import com.google.android.exoplayer2.ui.PlayerControlView; | |
| 75 | +import com.google.android.exoplayer2.ui.PlayerView; | |
| 76 | +import com.google.android.exoplayer2.ui.spherical.SphericalGLSurfaceView; | |
| 77 | +import com.google.android.exoplayer2.upstream.DataSource; | |
| 78 | +import com.google.android.exoplayer2.upstream.HttpDataSource; | |
| 79 | +import com.google.android.exoplayer2.util.ErrorMessageProvider; | |
| 80 | +import com.google.android.exoplayer2.util.EventLogger; | |
| 81 | +import com.google.android.exoplayer2.util.Util; | |
| 82 | +import java.lang.reflect.Constructor; | |
| 83 | +import java.net.CookieHandler; | |
| 84 | +import java.net.CookieManager; | |
| 85 | +import java.net.CookiePolicy; | |
| 86 | + | |
| 87 | +/** An activity that plays media using {@link SimpleExoPlayer}. */ | |
| 88 | +public class PlayerActivity extends AppCompatActivity | |
| 89 | + implements OnClickListener, PlaybackPreparer, PlayerControlView.VisibilityListener { | |
| 90 | + | |
| 91 | + // Activity extras. | |
| 92 | + | |
| 93 | + public static final String SPHERICAL_STEREO_MODE_EXTRA = "spherical_stereo_mode"; | |
| 94 | + public static final String SPHERICAL_STEREO_MODE_MONO = "mono"; | |
| 95 | + public static final String SPHERICAL_STEREO_MODE_TOP_BOTTOM = "top_bottom"; | |
| 96 | + public static final String SPHERICAL_STEREO_MODE_LEFT_RIGHT = "left_right"; | |
| 97 | + | |
| 98 | + // Actions. | |
| 99 | + | |
| 100 | + public static final String ACTION_VIEW = "com.google.android.exoplayer.demo.action.VIEW"; | |
| 101 | + public static final String ACTION_VIEW_LIST = | |
| 102 | + "com.google.android.exoplayer.demo.action.VIEW_LIST"; | |
| 103 | + | |
| 104 | + // Player configuration extras. | |
| 105 | + | |
| 106 | + public static final String ABR_ALGORITHM_EXTRA = "abr_algorithm"; | |
| 107 | + public static final String ABR_ALGORITHM_DEFAULT = "default"; | |
| 108 | + public static final String ABR_ALGORITHM_RANDOM = "random"; | |
| 109 | + | |
| 110 | + // Media item configuration extras. | |
| 111 | + | |
| 112 | + public static final String URI_EXTRA = "uri"; | |
| 113 | + public static final String EXTENSION_EXTRA = "extension"; | |
| 114 | + public static final String IS_LIVE_EXTRA = "is_live"; | |
| 115 | + | |
| 116 | + public static final String DRM_SCHEME_EXTRA = "drm_scheme"; | |
| 117 | + public static final String DRM_LICENSE_URL_EXTRA = "drm_license_url"; | |
| 118 | + public static final String DRM_KEY_REQUEST_PROPERTIES_EXTRA = "drm_key_request_properties"; | |
| 119 | + public static final String DRM_MULTI_SESSION_EXTRA = "drm_multi_session"; | |
| 120 | + public static final String PREFER_EXTENSION_DECODERS_EXTRA = "prefer_extension_decoders"; | |
| 121 | + public static final String TUNNELING_EXTRA = "tunneling"; | |
| 122 | + public static final String AD_TAG_URI_EXTRA = "ad_tag_uri"; | |
| 123 | + public static final String SUBTITLE_URI_EXTRA = "subtitle_uri"; | |
| 124 | + public static final String SUBTITLE_MIME_TYPE_EXTRA = "subtitle_mime_type"; | |
| 125 | + public static final String SUBTITLE_LANGUAGE_EXTRA = "subtitle_language"; | |
| 126 | + // For backwards compatibility only. | |
| 127 | + public static final String DRM_SCHEME_UUID_EXTRA = "drm_scheme_uuid"; | |
| 128 | + | |
| 129 | + // Saved instance state keys. | |
| 130 | + | |
| 131 | + private static final String KEY_TRACK_SELECTOR_PARAMETERS = "track_selector_parameters"; | |
| 132 | + private static final String KEY_WINDOW = "window"; | |
| 133 | + private static final String KEY_POSITION = "position"; | |
| 134 | + private static final String KEY_AUTO_PLAY = "auto_play"; | |
| 135 | + | |
| 136 | + private static final CookieManager DEFAULT_COOKIE_MANAGER; | |
| 137 | + static { | |
| 138 | + DEFAULT_COOKIE_MANAGER = new CookieManager(); | |
| 139 | + DEFAULT_COOKIE_MANAGER.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER); | |
| 140 | + } | |
| 141 | + | |
| 142 | + private PlayerView playerView; | |
| 143 | + private LinearLayout debugRootView; | |
| 144 | + private Button selectTracksButton; | |
| 145 | + private TextView debugTextView; | |
| 146 | + private boolean isShowingTrackSelectionDialog; | |
| 147 | + | |
| 148 | + private DataSource.Factory dataSourceFactory; | |
| 149 | + private SimpleExoPlayer player; | |
| 150 | + private MediaSource mediaSource; | |
| 151 | + private DefaultTrackSelector trackSelector; | |
| 152 | + private DefaultTrackSelector.Parameters trackSelectorParameters; | |
| 153 | + private DebugTextViewHelper debugViewHelper; | |
| 154 | + private TrackGroupArray lastSeenTrackGroupArray; | |
| 155 | + | |
| 156 | + private boolean startAutoPlay; | |
| 157 | + private int startWindow; | |
| 158 | + private long startPosition; | |
| 159 | + | |
| 160 | + // Fields used only for ad playback. The ads loader is loaded via reflection. | |
| 161 | + | |
| 162 | + private AdsLoader adsLoader; | |
| 163 | + private Uri loadedAdTagUri; | |
| 164 | + | |
| 165 | + // Activity lifecycle | |
| 166 | + | |
| 167 | + @Override | |
| 168 | + public void onCreate(Bundle savedInstanceState) { | |
| 169 | + Intent intent = getIntent(); | |
| 170 | + String sphericalStereoMode = intent.getStringExtra(SPHERICAL_STEREO_MODE_EXTRA); | |
| 171 | + if (sphericalStereoMode != null) { | |
| 172 | + setTheme(R.style.PlayerTheme_Spherical); | |
| 173 | + } | |
| 174 | + super.onCreate(savedInstanceState); | |
| 175 | + dataSourceFactory = buildDataSourceFactory(); | |
| 176 | + if (CookieHandler.getDefault() != DEFAULT_COOKIE_MANAGER) { | |
| 177 | + CookieHandler.setDefault(DEFAULT_COOKIE_MANAGER); | |
| 178 | + } | |
| 179 | + | |
| 180 | + setContentView(R.layout.player_activity); | |
| 181 | + debugRootView = findViewById(R.id.controls_root); | |
| 182 | + debugTextView = findViewById(R.id.debug_text_view); | |
| 183 | + selectTracksButton = findViewById(R.id.select_tracks_button); | |
| 184 | + selectTracksButton.setOnClickListener(this); | |
| 185 | + | |
| 186 | + playerView = findViewById(R.id.player_view); | |
| 187 | + playerView.setControllerVisibilityListener(this); | |
| 188 | + playerView.setErrorMessageProvider(new PlayerErrorMessageProvider()); | |
| 189 | + playerView.requestFocus(); | |
| 190 | + if (sphericalStereoMode != null) { | |
| 191 | + int stereoMode; | |
| 192 | + if (SPHERICAL_STEREO_MODE_MONO.equals(sphericalStereoMode)) { | |
| 193 | + stereoMode = C.STEREO_MODE_MONO; | |
| 194 | + } else if (SPHERICAL_STEREO_MODE_TOP_BOTTOM.equals(sphericalStereoMode)) { | |
| 195 | + stereoMode = C.STEREO_MODE_TOP_BOTTOM; | |
| 196 | + } else if (SPHERICAL_STEREO_MODE_LEFT_RIGHT.equals(sphericalStereoMode)) { | |
| 197 | + stereoMode = C.STEREO_MODE_LEFT_RIGHT; | |
| 198 | + } else { | |
| 199 | + showToast(R.string.error_unrecognized_stereo_mode); | |
| 200 | + finish(); | |
| 201 | + return; | |
| 202 | + } | |
| 203 | + ((SphericalGLSurfaceView) playerView.getVideoSurfaceView()).setDefaultStereoMode(stereoMode); | |
| 204 | + } | |
| 205 | + | |
| 206 | + if (savedInstanceState != null) { | |
| 207 | + trackSelectorParameters = savedInstanceState.getParcelable(KEY_TRACK_SELECTOR_PARAMETERS); | |
| 208 | + startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY); | |
| 209 | + startWindow = savedInstanceState.getInt(KEY_WINDOW); | |
| 210 | + startPosition = savedInstanceState.getLong(KEY_POSITION); | |
| 211 | + } else { | |
| 212 | + DefaultTrackSelector.ParametersBuilder builder = | |
| 213 | + new DefaultTrackSelector.ParametersBuilder(/* context= */ this); | |
| 214 | + boolean tunneling = intent.getBooleanExtra(TUNNELING_EXTRA, false); | |
| 215 | + if (Util.SDK_INT >= 21 && tunneling) { | |
| 216 | + builder.setTunnelingAudioSessionId(C.generateAudioSessionIdV21(/* context= */ this)); | |
| 217 | + } | |
| 218 | + trackSelectorParameters = builder.build(); | |
| 219 | + clearStartPosition(); | |
| 220 | + } | |
| 221 | + } | |
| 222 | + | |
| 223 | + @Override | |
| 224 | + public void onNewIntent(Intent intent) { | |
| 225 | + super.onNewIntent(intent); | |
| 226 | + releasePlayer(); | |
| 227 | + releaseAdsLoader(); | |
| 228 | + clearStartPosition(); | |
| 229 | + setIntent(intent); | |
| 230 | + } | |
| 231 | + | |
| 232 | + @Override | |
| 233 | + public void onStart() { | |
| 234 | + super.onStart(); | |
| 235 | + if (Util.SDK_INT > 23) { | |
| 236 | + initializePlayer(); | |
| 237 | + if (playerView != null) { | |
| 238 | + playerView.onResume(); | |
| 239 | + } | |
| 240 | + } | |
| 241 | + } | |
| 242 | + | |
| 243 | + @Override | |
| 244 | + public void onResume() { | |
| 245 | + super.onResume(); | |
| 246 | + if (Util.SDK_INT <= 23 || player == null) { | |
| 247 | + initializePlayer(); | |
| 248 | + if (playerView != null) { | |
| 249 | + playerView.onResume(); | |
| 250 | + } | |
| 251 | + } | |
| 252 | + } | |
| 253 | + | |
| 254 | + @Override | |
| 255 | + public void onPause() { | |
| 256 | + super.onPause(); | |
| 257 | + if (Util.SDK_INT <= 23) { | |
| 258 | + if (playerView != null) { | |
| 259 | + playerView.onPause(); | |
| 260 | + } | |
| 261 | + releasePlayer(); | |
| 262 | + } | |
| 263 | + } | |
| 264 | + | |
| 265 | + @Override | |
| 266 | + public void onStop() { | |
| 267 | + super.onStop(); | |
| 268 | + if (Util.SDK_INT > 23) { | |
| 269 | + if (playerView != null) { | |
| 270 | + playerView.onPause(); | |
| 271 | + } | |
| 272 | + releasePlayer(); | |
| 273 | + } | |
| 274 | + } | |
| 275 | + | |
| 276 | + @Override | |
| 277 | + public void onDestroy() { | |
| 278 | + super.onDestroy(); | |
| 279 | + releaseAdsLoader(); | |
| 280 | + } | |
| 281 | + | |
| 282 | + @Override | |
| 283 | + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, | |
| 284 | + @NonNull int[] grantResults) { | |
| 285 | + if (grantResults.length == 0) { | |
| 286 | + // Empty results are triggered if a permission is requested while another request was already | |
| 287 | + // pending and can be safely ignored in this case. | |
| 288 | + return; | |
| 289 | + } | |
| 290 | + if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { | |
| 291 | + initializePlayer(); | |
| 292 | + } else { | |
| 293 | + showToast(R.string.storage_permission_denied); | |
| 294 | + finish(); | |
| 295 | + } | |
| 296 | + } | |
| 297 | + | |
| 298 | + @Override | |
| 299 | + public void onSaveInstanceState(Bundle outState) { | |
| 300 | + super.onSaveInstanceState(outState); | |
| 301 | + updateTrackSelectorParameters(); | |
| 302 | + updateStartPosition(); | |
| 303 | + outState.putParcelable(KEY_TRACK_SELECTOR_PARAMETERS, trackSelectorParameters); | |
| 304 | + outState.putBoolean(KEY_AUTO_PLAY, startAutoPlay); | |
| 305 | + outState.putInt(KEY_WINDOW, startWindow); | |
| 306 | + outState.putLong(KEY_POSITION, startPosition); | |
| 307 | + } | |
| 308 | + | |
| 309 | + // Activity input | |
| 310 | + | |
| 311 | + @Override | |
| 312 | + public boolean dispatchKeyEvent(KeyEvent event) { | |
| 313 | + // See whether the player view wants to handle media or DPAD keys events. | |
| 314 | + return playerView.dispatchKeyEvent(event) || super.dispatchKeyEvent(event); | |
| 315 | + } | |
| 316 | + | |
| 317 | + // OnClickListener methods | |
| 318 | + | |
| 319 | + @Override | |
| 320 | + public void onClick(View view) { | |
| 321 | + if (view == selectTracksButton | |
| 322 | + && !isShowingTrackSelectionDialog | |
| 323 | + && TrackSelectionDialog.willHaveContent(trackSelector)) { | |
| 324 | + isShowingTrackSelectionDialog = true; | |
| 325 | + TrackSelectionDialog trackSelectionDialog = | |
| 326 | + TrackSelectionDialog.createForTrackSelector( | |
| 327 | + trackSelector, | |
| 328 | + /* onDismissListener= */ dismissedDialog -> isShowingTrackSelectionDialog = false); | |
| 329 | + trackSelectionDialog.show(getSupportFragmentManager(), /* tag= */ null); | |
| 330 | + } | |
| 331 | + } | |
| 332 | + | |
| 333 | + // PlaybackControlView.PlaybackPreparer implementation | |
| 334 | + | |
| 335 | + @Override | |
| 336 | + public void preparePlayback() { | |
| 337 | + player.retry(); | |
| 338 | + } | |
| 339 | + | |
| 340 | + // PlaybackControlView.VisibilityListener implementation | |
| 341 | + | |
| 342 | + @Override | |
| 343 | + public void onVisibilityChange(int visibility) { | |
| 344 | + debugRootView.setVisibility(visibility); | |
| 345 | + } | |
| 346 | + | |
| 347 | + // Internal methods | |
| 348 | + | |
| 349 | + private void initializePlayer() { | |
| 350 | + if (player == null) { | |
| 351 | + Intent intent = getIntent(); | |
| 352 | + | |
| 353 | + mediaSource = createTopLevelMediaSource(intent); | |
| 354 | + if (mediaSource == null) { | |
| 355 | + return; | |
| 356 | + } | |
| 357 | + | |
| 358 | + TrackSelection.Factory trackSelectionFactory; | |
| 359 | + String abrAlgorithm = intent.getStringExtra(ABR_ALGORITHM_EXTRA); | |
| 360 | + if (abrAlgorithm == null || ABR_ALGORITHM_DEFAULT.equals(abrAlgorithm)) { | |
| 361 | + trackSelectionFactory = new AdaptiveTrackSelection.Factory(); | |
| 362 | + } else if (ABR_ALGORITHM_RANDOM.equals(abrAlgorithm)) { | |
| 363 | + trackSelectionFactory = new RandomTrackSelection.Factory(); | |
| 364 | + } else { | |
| 365 | + showToast(R.string.error_unrecognized_abr_algorithm); | |
| 366 | + finish(); | |
| 367 | + return; | |
| 368 | + } | |
| 369 | + | |
| 370 | + boolean preferExtensionDecoders = | |
| 371 | + intent.getBooleanExtra(PREFER_EXTENSION_DECODERS_EXTRA, false); | |
| 372 | + RenderersFactory renderersFactory = | |
| 373 | + ((DemoApplication) getApplication()).buildRenderersFactory(preferExtensionDecoders); | |
| 374 | + | |
| 375 | + trackSelector = new DefaultTrackSelector(/* context= */ this, trackSelectionFactory); | |
| 376 | + trackSelector.setParameters(trackSelectorParameters); | |
| 377 | + lastSeenTrackGroupArray = null; | |
| 378 | + | |
| 379 | + player = | |
| 380 | + new SimpleExoPlayer.Builder(/* context= */ this, renderersFactory) | |
| 381 | + .setTrackSelector(trackSelector) | |
| 382 | + .build(); | |
| 383 | + player.addListener(new PlayerEventListener()); | |
| 384 | + player.setPlayWhenReady(startAutoPlay); | |
| 385 | + player.addAnalyticsListener(new EventLogger(trackSelector)); | |
| 386 | + playerView.setPlayer(player); | |
| 387 | + playerView.setPlaybackPreparer(this); | |
| 388 | + debugViewHelper = new DebugTextViewHelper(player, debugTextView); | |
| 389 | + debugViewHelper.start(); | |
| 390 | + if (adsLoader != null) { | |
| 391 | + adsLoader.setPlayer(player); | |
| 392 | + } | |
| 393 | + } | |
| 394 | + boolean haveStartPosition = startWindow != C.INDEX_UNSET; | |
| 395 | + if (haveStartPosition) { | |
| 396 | + player.seekTo(startWindow, startPosition); | |
| 397 | + } | |
| 398 | + player.prepare(mediaSource, !haveStartPosition, false); | |
| 399 | + updateButtonVisibility(); | |
| 400 | + } | |
| 401 | + | |
| 402 | + @Nullable | |
| 403 | + private MediaSource createTopLevelMediaSource(Intent intent) { | |
| 404 | + String action = intent.getAction(); | |
| 405 | + boolean actionIsListView = ACTION_VIEW_LIST.equals(action); | |
| 406 | + if (!actionIsListView && !ACTION_VIEW.equals(action)) { | |
| 407 | + showToast(getString(R.string.unexpected_intent_action, action)); | |
| 408 | + finish(); | |
| 409 | + return null; | |
| 410 | + } | |
| 411 | + | |
| 412 | + Sample intentAsSample = Sample.createFromIntent(intent); | |
| 413 | + Sample.UriSample[] samples = | |
| 414 | + intentAsSample instanceof Sample.PlaylistSample | |
| 415 | + ? ((Sample.PlaylistSample) intentAsSample).children | |
| 416 | + : new Sample.UriSample[] {(Sample.UriSample) intentAsSample}; | |
| 417 | + | |
| 418 | + boolean seenAdsTagUri = false; | |
| 419 | + for (Sample.UriSample sample : samples) { | |
| 420 | + seenAdsTagUri |= sample.adTagUri != null; | |
| 421 | + if (!Util.checkCleartextTrafficPermitted(sample.uri)) { | |
| 422 | + showToast(R.string.error_cleartext_not_permitted); | |
| 423 | + return null; | |
| 424 | + } | |
| 425 | + if (Util.maybeRequestReadExternalStoragePermission(/* activity= */ this, sample.uri)) { | |
| 426 | + // The player will be reinitialized if the permission is granted. | |
| 427 | + return null; | |
| 428 | + } | |
| 429 | + } | |
| 430 | + | |
| 431 | + MediaSource[] mediaSources = new MediaSource[samples.length]; | |
| 432 | + for (int i = 0; i < samples.length; i++) { | |
| 433 | + mediaSources[i] = createLeafMediaSource(samples[i]); | |
| 434 | + Sample.SubtitleInfo subtitleInfo = samples[i].subtitleInfo; | |
| 435 | + if (subtitleInfo != null) { | |
| 436 | + Format subtitleFormat = | |
| 437 | + Format.createTextSampleFormat( | |
| 438 | + /* id= */ null, | |
| 439 | + subtitleInfo.mimeType, | |
| 440 | + C.SELECTION_FLAG_DEFAULT, | |
| 441 | + subtitleInfo.language); | |
| 442 | + MediaSource subtitleMediaSource = | |
| 443 | + new SingleSampleMediaSource.Factory(dataSourceFactory) | |
| 444 | + .createMediaSource(subtitleInfo.uri, subtitleFormat, C.TIME_UNSET); | |
| 445 | + mediaSources[i] = new MergingMediaSource(mediaSources[i], subtitleMediaSource); | |
| 446 | + } | |
| 447 | + } | |
| 448 | + MediaSource mediaSource = | |
| 449 | + mediaSources.length == 1 ? mediaSources[0] : new ConcatenatingMediaSource(mediaSources); | |
| 450 | + | |
| 451 | + if (seenAdsTagUri) { | |
| 452 | + Uri adTagUri = samples[0].adTagUri; | |
| 453 | + if (actionIsListView) { | |
| 454 | + showToast(R.string.unsupported_ads_in_concatenation); | |
| 455 | + } else { | |
| 456 | + if (!adTagUri.equals(loadedAdTagUri)) { | |
| 457 | + releaseAdsLoader(); | |
| 458 | + loadedAdTagUri = adTagUri; | |
| 459 | + } | |
| 460 | + MediaSource adsMediaSource = createAdsMediaSource(mediaSource, adTagUri); | |
| 461 | + if (adsMediaSource != null) { | |
| 462 | + mediaSource = adsMediaSource; | |
| 463 | + } else { | |
| 464 | + showToast(R.string.ima_not_loaded); | |
| 465 | + } | |
| 466 | + } | |
| 467 | + } else { | |
| 468 | + releaseAdsLoader(); | |
| 469 | + } | |
| 470 | + | |
| 471 | + return mediaSource; | |
| 472 | + } | |
| 473 | + | |
| 474 | + private MediaSource createLeafMediaSource(Sample.UriSample parameters) { | |
| 475 | + Sample.DrmInfo drmInfo = parameters.drmInfo; | |
| 476 | + int errorStringId = R.string.error_drm_unknown; | |
| 477 | + DrmSessionManager<ExoMediaCrypto> drmSessionManager = null; | |
| 478 | + if (drmInfo == null) { | |
| 479 | + drmSessionManager = DrmSessionManager.getDummyDrmSessionManager(); | |
| 480 | + } else if (Util.SDK_INT < 18) { | |
| 481 | + errorStringId = R.string.error_drm_unsupported_before_api_18; | |
| 482 | + } else if (!MediaDrm.isCryptoSchemeSupported(drmInfo.drmScheme)) { | |
| 483 | + errorStringId = R.string.error_drm_unsupported_scheme; | |
| 484 | + } else { | |
| 485 | + MediaDrmCallback mediaDrmCallback = | |
| 486 | + createMediaDrmCallback(drmInfo.drmLicenseUrl, drmInfo.drmKeyRequestProperties); | |
| 487 | + drmSessionManager = | |
| 488 | + new DefaultDrmSessionManager.Builder() | |
| 489 | + .setUuidAndExoMediaDrmProvider(drmInfo.drmScheme, FrameworkMediaDrm.DEFAULT_PROVIDER) | |
| 490 | + .setMultiSession(drmInfo.drmMultiSession) | |
| 491 | + .build(mediaDrmCallback); | |
| 492 | + } | |
| 493 | + | |
| 494 | + if (drmSessionManager == null) { | |
| 495 | + showToast(errorStringId); | |
| 496 | + finish(); | |
| 497 | + return null; | |
| 498 | + } | |
| 499 | + | |
| 500 | + DownloadRequest downloadRequest = | |
| 501 | + ((DemoApplication) getApplication()) | |
| 502 | + .getDownloadTracker() | |
| 503 | + .getDownloadRequest(parameters.uri); | |
| 504 | + if (downloadRequest != null) { | |
| 505 | + return DownloadHelper.createMediaSource(downloadRequest, dataSourceFactory); | |
| 506 | + } | |
| 507 | + return createLeafMediaSource(parameters.uri, parameters.extension, drmSessionManager); | |
| 508 | + } | |
| 509 | + | |
| 510 | + private MediaSource createLeafMediaSource( | |
| 511 | + Uri uri, String extension, DrmSessionManager<ExoMediaCrypto> drmSessionManager) { | |
| 512 | + @ContentType int type = Util.inferContentType(uri, extension); | |
| 513 | + switch (type) { | |
| 514 | + case C.TYPE_DASH: | |
| 515 | + return new DashMediaSource.Factory(dataSourceFactory) | |
| 516 | + .setDrmSessionManager(drmSessionManager) | |
| 517 | + .createMediaSource(uri); | |
| 518 | + case C.TYPE_SS: | |
| 519 | + return new SsMediaSource.Factory(dataSourceFactory) | |
| 520 | + .setDrmSessionManager(drmSessionManager) | |
| 521 | + .createMediaSource(uri); | |
| 522 | + case C.TYPE_HLS: | |
| 523 | + return new HlsMediaSource.Factory(dataSourceFactory) | |
| 524 | + .setDrmSessionManager(drmSessionManager) | |
| 525 | + .createMediaSource(uri); | |
| 526 | + case C.TYPE_OTHER: | |
| 527 | + return new ProgressiveMediaSource.Factory(dataSourceFactory) | |
| 528 | + .setDrmSessionManager(drmSessionManager) | |
| 529 | + .createMediaSource(uri); | |
| 530 | + default: | |
| 531 | + throw new IllegalStateException("Unsupported type: " + type); | |
| 532 | + } | |
| 533 | + } | |
| 534 | + | |
| 535 | + private HttpMediaDrmCallback createMediaDrmCallback( | |
| 536 | + String licenseUrl, String[] keyRequestPropertiesArray) { | |
| 537 | + HttpDataSource.Factory licenseDataSourceFactory = | |
| 538 | + ((DemoApplication) getApplication()).buildHttpDataSourceFactory(); | |
| 539 | + HttpMediaDrmCallback drmCallback = | |
| 540 | + new HttpMediaDrmCallback(licenseUrl, licenseDataSourceFactory); | |
| 541 | + if (keyRequestPropertiesArray != null) { | |
| 542 | + for (int i = 0; i < keyRequestPropertiesArray.length - 1; i += 2) { | |
| 543 | + drmCallback.setKeyRequestProperty(keyRequestPropertiesArray[i], | |
| 544 | + keyRequestPropertiesArray[i + 1]); | |
| 545 | + } | |
| 546 | + } | |
| 547 | + return drmCallback; | |
| 548 | + } | |
| 549 | + | |
| 550 | + private void releasePlayer() { | |
| 551 | + if (player != null) { | |
| 552 | + updateTrackSelectorParameters(); | |
| 553 | + updateStartPosition(); | |
| 554 | + debugViewHelper.stop(); | |
| 555 | + debugViewHelper = null; | |
| 556 | + player.release(); | |
| 557 | + player = null; | |
| 558 | + mediaSource = null; | |
| 559 | + trackSelector = null; | |
| 560 | + } | |
| 561 | + if (adsLoader != null) { | |
| 562 | + adsLoader.setPlayer(null); | |
| 563 | + } | |
| 564 | + } | |
| 565 | + | |
| 566 | + private void releaseAdsLoader() { | |
| 567 | + if (adsLoader != null) { | |
| 568 | + adsLoader.release(); | |
| 569 | + adsLoader = null; | |
| 570 | + loadedAdTagUri = null; | |
| 571 | + playerView.getOverlayFrameLayout().removeAllViews(); | |
| 572 | + } | |
| 573 | + } | |
| 574 | + | |
| 575 | + private void updateTrackSelectorParameters() { | |
| 576 | + if (trackSelector != null) { | |
| 577 | + trackSelectorParameters = trackSelector.getParameters(); | |
| 578 | + } | |
| 579 | + } | |
| 580 | + | |
| 581 | + private void updateStartPosition() { | |
| 582 | + if (player != null) { | |
| 583 | + startAutoPlay = player.getPlayWhenReady(); | |
| 584 | + startWindow = player.getCurrentWindowIndex(); | |
| 585 | + startPosition = Math.max(0, player.getContentPosition()); | |
| 586 | + } | |
| 587 | + } | |
| 588 | + | |
| 589 | + private void clearStartPosition() { | |
| 590 | + startAutoPlay = true; | |
| 591 | + startWindow = C.INDEX_UNSET; | |
| 592 | + startPosition = C.TIME_UNSET; | |
| 593 | + } | |
| 594 | + | |
| 595 | + /** Returns a new DataSource factory. */ | |
| 596 | + private DataSource.Factory buildDataSourceFactory() { | |
| 597 | + return ((DemoApplication) getApplication()).buildDataSourceFactory(); | |
| 598 | + } | |
| 599 | + | |
| 600 | + /** Returns an ads media source, reusing the ads loader if one exists. */ | |
| 601 | + @Nullable | |
| 602 | + private MediaSource createAdsMediaSource(MediaSource mediaSource, Uri adTagUri) { | |
| 603 | + // Load the extension source using reflection so the demo app doesn't have to depend on it. | |
| 604 | + // The ads loader is reused for multiple playbacks, so that ad playback can resume. | |
| 605 | + try { | |
| 606 | + Class<?> loaderClass = Class.forName("com.google.android.exoplayer2.ext.ima.ImaAdsLoader"); | |
| 607 | + if (adsLoader == null) { | |
| 608 | + // Full class names used so the LINT.IfChange rule triggers should any of the classes move. | |
| 609 | + // LINT.IfChange | |
| 610 | + Constructor<? extends AdsLoader> loaderConstructor = | |
| 611 | + loaderClass | |
| 612 | + .asSubclass(AdsLoader.class) | |
| 613 | + .getConstructor(android.content.Context.class, Uri.class); | |
| 614 | + // LINT.ThenChange(../../../../../../../../proguard-rules.txt) | |
| 615 | + adsLoader = loaderConstructor.newInstance(this, adTagUri); | |
| 616 | + } | |
| 617 | + MediaSourceFactory adMediaSourceFactory = | |
| 618 | + new MediaSourceFactory() { | |
| 619 | + @Override | |
| 620 | + public MediaSource createMediaSource(Uri uri) { | |
| 621 | + return PlayerActivity.this.createLeafMediaSource( | |
| 622 | + uri, /* extension=*/ null, DrmSessionManager.getDummyDrmSessionManager()); | |
| 623 | + } | |
| 624 | + | |
| 625 | + @Override | |
| 626 | + public int[] getSupportedTypes() { | |
| 627 | + return new int[] {C.TYPE_DASH, C.TYPE_SS, C.TYPE_HLS, C.TYPE_OTHER}; | |
| 628 | + } | |
| 629 | + }; | |
| 630 | + return new AdsMediaSource(mediaSource, adMediaSourceFactory, adsLoader, playerView); | |
| 631 | + } catch (ClassNotFoundException e) { | |
| 632 | + // IMA extension not loaded. | |
| 633 | + return null; | |
| 634 | + } catch (Exception e) { | |
| 635 | + throw new RuntimeException(e); | |
| 636 | + } | |
| 637 | + } | |
| 638 | + | |
| 639 | + // User controls | |
| 640 | + | |
| 641 | + private void updateButtonVisibility() { | |
| 642 | + selectTracksButton.setEnabled( | |
| 643 | + player != null && TrackSelectionDialog.willHaveContent(trackSelector)); | |
| 644 | + } | |
| 645 | + | |
| 646 | + private void showControls() { | |
| 647 | + debugRootView.setVisibility(View.VISIBLE); | |
| 648 | + } | |
| 649 | + | |
| 650 | + private void showToast(int messageId) { | |
| 651 | + showToast(getString(messageId)); | |
| 652 | + } | |
| 653 | + | |
| 654 | + private void showToast(String message) { | |
| 655 | + Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); | |
| 656 | + } | |
| 657 | + | |
| 658 | + private static boolean isBehindLiveWindow(ExoPlaybackException e) { | |
| 659 | + if (e.type != ExoPlaybackException.TYPE_SOURCE) { | |
| 660 | + return false; | |
| 661 | + } | |
| 662 | + Throwable cause = e.getSourceException(); | |
| 663 | + while (cause != null) { | |
| 664 | + if (cause instanceof BehindLiveWindowException) { | |
| 665 | + return true; | |
| 666 | + } | |
| 667 | + cause = cause.getCause(); | |
| 668 | + } | |
| 669 | + return false; | |
| 670 | + } | |
| 671 | + | |
| 672 | + private class PlayerEventListener implements Player.EventListener { | |
| 673 | + | |
| 674 | + @Override | |
| 675 | + public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) { | |
| 676 | + if (playbackState == Player.STATE_ENDED) { | |
| 677 | + showControls(); | |
| 678 | + } | |
| 679 | + updateButtonVisibility(); | |
| 680 | + } | |
| 681 | + | |
| 682 | + @Override | |
| 683 | + public void onPlayerError(ExoPlaybackException e) { | |
| 684 | + if (isBehindLiveWindow(e)) { | |
| 685 | + clearStartPosition(); | |
| 686 | + initializePlayer(); | |
| 687 | + } else { | |
| 688 | + updateButtonVisibility(); | |
| 689 | + showControls(); | |
| 690 | + } | |
| 691 | + } | |
| 692 | + | |
| 693 | + @Override | |
| 694 | + @SuppressWarnings("ReferenceEquality") | |
| 695 | + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { | |
| 696 | + updateButtonVisibility(); | |
| 697 | + if (trackGroups != lastSeenTrackGroupArray) { | |
| 698 | + MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo(); | |
| 699 | + if (mappedTrackInfo != null) { | |
| 700 | + if (mappedTrackInfo.getTypeSupport(C.TRACK_TYPE_VIDEO) | |
| 701 | + == MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) { | |
| 702 | + showToast(R.string.error_unsupported_video); | |
| 703 | + } | |
| 704 | + if (mappedTrackInfo.getTypeSupport(C.TRACK_TYPE_AUDIO) | |
| 705 | + == MappedTrackInfo.RENDERER_SUPPORT_UNSUPPORTED_TRACKS) { | |
| 706 | + showToast(R.string.error_unsupported_audio); | |
| 707 | + } | |
| 708 | + } | |
| 709 | + lastSeenTrackGroupArray = trackGroups; | |
| 710 | + } | |
| 711 | + } | |
| 712 | + } | |
| 713 | + | |
| 714 | + private class PlayerErrorMessageProvider implements ErrorMessageProvider<ExoPlaybackException> { | |
| 715 | + | |
| 716 | + @Override | |
| 717 | + public Pair<Integer, String> getErrorMessage(ExoPlaybackException e) { | |
| 718 | + String errorString = getString(R.string.error_generic); | |
| 719 | + if (e.type == ExoPlaybackException.TYPE_RENDERER) { | |
| 720 | + Exception cause = e.getRendererException(); | |
| 721 | + if (cause instanceof DecoderInitializationException) { | |
| 722 | + // Special case for decoder initialization failures. | |
| 723 | + DecoderInitializationException decoderInitializationException = | |
| 724 | + (DecoderInitializationException) cause; | |
| 725 | + if (decoderInitializationException.codecInfo == null) { | |
| 726 | + if (decoderInitializationException.getCause() instanceof DecoderQueryException) { | |
| 727 | + errorString = getString(R.string.error_querying_decoders); | |
| 728 | + } else if (decoderInitializationException.secureDecoderRequired) { | |
| 729 | + errorString = | |
| 730 | + getString( | |
| 731 | + R.string.error_no_secure_decoder, decoderInitializationException.mimeType); | |
| 732 | + } else { | |
| 733 | + errorString = | |
| 734 | + getString(R.string.error_no_decoder, decoderInitializationException.mimeType); | |
| 735 | + } | |
| 736 | + } else { | |
| 737 | + errorString = | |
| 738 | + getString( | |
| 739 | + R.string.error_instantiating_decoder, | |
| 740 | + decoderInitializationException.codecInfo.name); | |
| 741 | + } | |
| 742 | + } | |
| 743 | + } | |
| 744 | + return Pair.create(0, errorString); | |
| 745 | + } | |
| 746 | + } | |
| 747 | +} | ... | ... |
app/strike/src/main/java/com/cnlive/strike/demo/exoplayer/demo/Sample.java
0 → 100644
| 1 | +/* | |
| 2 | + * Copyright (C) 2019 The Android Open Source Project | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + */ | |
| 16 | +package com.cnlive.strike.demo.exoplayer.demo; | |
| 17 | + | |
| 18 | +import static com.cnlive.strike.demo.exoplayer.demo.PlayerActivity.ACTION_VIEW_LIST; | |
| 19 | +import static com.cnlive.strike.demo.exoplayer.demo.PlayerActivity.AD_TAG_URI_EXTRA; | |
| 20 | +import static com.cnlive.strike.demo.exoplayer.demo.PlayerActivity.DRM_KEY_REQUEST_PROPERTIES_EXTRA; | |
| 21 | +import static com.cnlive.strike.demo.exoplayer.demo.PlayerActivity.DRM_LICENSE_URL_EXTRA; | |
| 22 | +import static com.cnlive.strike.demo.exoplayer.demo.PlayerActivity.DRM_MULTI_SESSION_EXTRA; | |
| 23 | +import static com.cnlive.strike.demo.exoplayer.demo.PlayerActivity.DRM_SCHEME_EXTRA; | |
| 24 | +import static com.cnlive.strike.demo.exoplayer.demo.PlayerActivity.DRM_SCHEME_UUID_EXTRA; | |
| 25 | +import static com.cnlive.strike.demo.exoplayer.demo.PlayerActivity.EXTENSION_EXTRA; | |
| 26 | +import static com.cnlive.strike.demo.exoplayer.demo.PlayerActivity.IS_LIVE_EXTRA; | |
| 27 | +import static com.cnlive.strike.demo.exoplayer.demo.PlayerActivity.SUBTITLE_LANGUAGE_EXTRA; | |
| 28 | +import static com.cnlive.strike.demo.exoplayer.demo.PlayerActivity.SUBTITLE_MIME_TYPE_EXTRA; | |
| 29 | +import static com.cnlive.strike.demo.exoplayer.demo.PlayerActivity.SUBTITLE_URI_EXTRA; | |
| 30 | +import static com.cnlive.strike.demo.exoplayer.demo.PlayerActivity.URI_EXTRA; | |
| 31 | + | |
| 32 | +import android.content.Intent; | |
| 33 | +import android.net.Uri; | |
| 34 | +import androidx.annotation.Nullable; | |
| 35 | +import com.google.android.exoplayer2.util.Assertions; | |
| 36 | +import com.google.android.exoplayer2.util.Util; | |
| 37 | +import java.util.ArrayList; | |
| 38 | +import java.util.UUID; | |
| 39 | + | |
| 40 | +/* package */public abstract class Sample { | |
| 41 | + | |
| 42 | + public static final class UriSample extends Sample { | |
| 43 | + | |
| 44 | + public static UriSample createFromIntent(Uri uri, Intent intent, String extrasKeySuffix) { | |
| 45 | + String extension = intent.getStringExtra(EXTENSION_EXTRA + extrasKeySuffix); | |
| 46 | + String adsTagUriString = intent.getStringExtra(AD_TAG_URI_EXTRA + extrasKeySuffix); | |
| 47 | + boolean isLive = | |
| 48 | + intent.getBooleanExtra(IS_LIVE_EXTRA + extrasKeySuffix, /* defaultValue= */ false); | |
| 49 | + Uri adTagUri = adsTagUriString != null ? Uri.parse(adsTagUriString) : null; | |
| 50 | + return new UriSample( | |
| 51 | + /* name= */ null, | |
| 52 | + uri, | |
| 53 | + extension, | |
| 54 | + isLive, | |
| 55 | + DrmInfo.createFromIntent(intent, extrasKeySuffix), | |
| 56 | + adTagUri, | |
| 57 | + /* sphericalStereoMode= */ null, | |
| 58 | + SubtitleInfo.createFromIntent(intent, extrasKeySuffix)); | |
| 59 | + } | |
| 60 | + | |
| 61 | + public final Uri uri; | |
| 62 | + public final String extension; | |
| 63 | + public final boolean isLive; | |
| 64 | + public final DrmInfo drmInfo; | |
| 65 | + public final Uri adTagUri; | |
| 66 | + @Nullable public final String sphericalStereoMode; | |
| 67 | + @Nullable SubtitleInfo subtitleInfo; | |
| 68 | + | |
| 69 | + public UriSample( | |
| 70 | + String name, | |
| 71 | + Uri uri, | |
| 72 | + String extension, | |
| 73 | + boolean isLive, | |
| 74 | + DrmInfo drmInfo, | |
| 75 | + Uri adTagUri, | |
| 76 | + @Nullable String sphericalStereoMode, | |
| 77 | + @Nullable SubtitleInfo subtitleInfo) { | |
| 78 | + super(name); | |
| 79 | + this.uri = uri; | |
| 80 | + this.extension = extension; | |
| 81 | + this.isLive = isLive; | |
| 82 | + this.drmInfo = drmInfo; | |
| 83 | + this.adTagUri = adTagUri; | |
| 84 | + this.sphericalStereoMode = sphericalStereoMode; | |
| 85 | + this.subtitleInfo = subtitleInfo; | |
| 86 | + } | |
| 87 | + | |
| 88 | + @Override | |
| 89 | + public void addToIntent(Intent intent) { | |
| 90 | + intent.setAction(PlayerActivity.ACTION_VIEW).setData(uri); | |
| 91 | + intent.putExtra(IS_LIVE_EXTRA, isLive); | |
| 92 | + intent.putExtra(PlayerActivity.SPHERICAL_STEREO_MODE_EXTRA, sphericalStereoMode); | |
| 93 | + addPlayerConfigToIntent(intent, /* extrasKeySuffix= */ ""); | |
| 94 | + } | |
| 95 | + | |
| 96 | + public void addToPlaylistIntent(Intent intent, String extrasKeySuffix) { | |
| 97 | + intent.putExtra(URI_EXTRA + extrasKeySuffix, uri.toString()); | |
| 98 | + intent.putExtra(IS_LIVE_EXTRA + extrasKeySuffix, isLive); | |
| 99 | + addPlayerConfigToIntent(intent, extrasKeySuffix); | |
| 100 | + } | |
| 101 | + | |
| 102 | + private void addPlayerConfigToIntent(Intent intent, String extrasKeySuffix) { | |
| 103 | + intent | |
| 104 | + .putExtra(EXTENSION_EXTRA + extrasKeySuffix, extension) | |
| 105 | + .putExtra( | |
| 106 | + AD_TAG_URI_EXTRA + extrasKeySuffix, adTagUri != null ? adTagUri.toString() : null); | |
| 107 | + if (drmInfo != null) { | |
| 108 | + drmInfo.addToIntent(intent, extrasKeySuffix); | |
| 109 | + } | |
| 110 | + if (subtitleInfo != null) { | |
| 111 | + subtitleInfo.addToIntent(intent, extrasKeySuffix); | |
| 112 | + } | |
| 113 | + } | |
| 114 | + } | |
| 115 | + | |
| 116 | + public static final class PlaylistSample extends Sample { | |
| 117 | + | |
| 118 | + public final UriSample[] children; | |
| 119 | + | |
| 120 | + public PlaylistSample(String name, UriSample... children) { | |
| 121 | + super(name); | |
| 122 | + this.children = children; | |
| 123 | + } | |
| 124 | + | |
| 125 | + @Override | |
| 126 | + public void addToIntent(Intent intent) { | |
| 127 | + intent.setAction(ACTION_VIEW_LIST); | |
| 128 | + for (int i = 0; i < children.length; i++) { | |
| 129 | + children[i].addToPlaylistIntent(intent, /* extrasKeySuffix= */ "_" + i); | |
| 130 | + } | |
| 131 | + } | |
| 132 | + } | |
| 133 | + | |
| 134 | + public static final class DrmInfo { | |
| 135 | + | |
| 136 | + public static DrmInfo createFromIntent(Intent intent, String extrasKeySuffix) { | |
| 137 | + String schemeKey = DRM_SCHEME_EXTRA + extrasKeySuffix; | |
| 138 | + String schemeUuidKey = DRM_SCHEME_UUID_EXTRA + extrasKeySuffix; | |
| 139 | + if (!intent.hasExtra(schemeKey) && !intent.hasExtra(schemeUuidKey)) { | |
| 140 | + return null; | |
| 141 | + } | |
| 142 | + String drmSchemeExtra = | |
| 143 | + intent.hasExtra(schemeKey) | |
| 144 | + ? intent.getStringExtra(schemeKey) | |
| 145 | + : intent.getStringExtra(schemeUuidKey); | |
| 146 | + UUID drmScheme = Util.getDrmUuid(drmSchemeExtra); | |
| 147 | + String drmLicenseUrl = intent.getStringExtra(DRM_LICENSE_URL_EXTRA + extrasKeySuffix); | |
| 148 | + String[] keyRequestPropertiesArray = | |
| 149 | + intent.getStringArrayExtra(DRM_KEY_REQUEST_PROPERTIES_EXTRA + extrasKeySuffix); | |
| 150 | + boolean drmMultiSession = | |
| 151 | + intent.getBooleanExtra(DRM_MULTI_SESSION_EXTRA + extrasKeySuffix, false); | |
| 152 | + return new DrmInfo(drmScheme, drmLicenseUrl, keyRequestPropertiesArray, drmMultiSession); | |
| 153 | + } | |
| 154 | + | |
| 155 | + public final UUID drmScheme; | |
| 156 | + public final String drmLicenseUrl; | |
| 157 | + public final String[] drmKeyRequestProperties; | |
| 158 | + public final boolean drmMultiSession; | |
| 159 | + | |
| 160 | + public DrmInfo( | |
| 161 | + UUID drmScheme, | |
| 162 | + String drmLicenseUrl, | |
| 163 | + String[] drmKeyRequestProperties, | |
| 164 | + boolean drmMultiSession) { | |
| 165 | + this.drmScheme = drmScheme; | |
| 166 | + this.drmLicenseUrl = drmLicenseUrl; | |
| 167 | + this.drmKeyRequestProperties = drmKeyRequestProperties; | |
| 168 | + this.drmMultiSession = drmMultiSession; | |
| 169 | + } | |
| 170 | + | |
| 171 | + public void addToIntent(Intent intent, String extrasKeySuffix) { | |
| 172 | + Assertions.checkNotNull(intent); | |
| 173 | + intent.putExtra(DRM_SCHEME_EXTRA + extrasKeySuffix, drmScheme.toString()); | |
| 174 | + intent.putExtra(DRM_LICENSE_URL_EXTRA + extrasKeySuffix, drmLicenseUrl); | |
| 175 | + intent.putExtra(DRM_KEY_REQUEST_PROPERTIES_EXTRA + extrasKeySuffix, drmKeyRequestProperties); | |
| 176 | + intent.putExtra(DRM_MULTI_SESSION_EXTRA + extrasKeySuffix, drmMultiSession); | |
| 177 | + } | |
| 178 | + } | |
| 179 | + | |
| 180 | + public static final class SubtitleInfo { | |
| 181 | + | |
| 182 | + @Nullable | |
| 183 | + public static SubtitleInfo createFromIntent(Intent intent, String extrasKeySuffix) { | |
| 184 | + if (!intent.hasExtra(SUBTITLE_URI_EXTRA + extrasKeySuffix)) { | |
| 185 | + return null; | |
| 186 | + } | |
| 187 | + return new SubtitleInfo( | |
| 188 | + Uri.parse(intent.getStringExtra(SUBTITLE_URI_EXTRA + extrasKeySuffix)), | |
| 189 | + intent.getStringExtra(SUBTITLE_MIME_TYPE_EXTRA + extrasKeySuffix), | |
| 190 | + intent.getStringExtra(SUBTITLE_LANGUAGE_EXTRA + extrasKeySuffix)); | |
| 191 | + } | |
| 192 | + | |
| 193 | + public final Uri uri; | |
| 194 | + public final String mimeType; | |
| 195 | + @Nullable public final String language; | |
| 196 | + | |
| 197 | + public SubtitleInfo(Uri uri, String mimeType, @Nullable String language) { | |
| 198 | + this.uri = Assertions.checkNotNull(uri); | |
| 199 | + this.mimeType = Assertions.checkNotNull(mimeType); | |
| 200 | + this.language = language; | |
| 201 | + } | |
| 202 | + | |
| 203 | + public void addToIntent(Intent intent, String extrasKeySuffix) { | |
| 204 | + intent.putExtra(SUBTITLE_URI_EXTRA + extrasKeySuffix, uri.toString()); | |
| 205 | + intent.putExtra(SUBTITLE_MIME_TYPE_EXTRA + extrasKeySuffix, mimeType); | |
| 206 | + intent.putExtra(SUBTITLE_LANGUAGE_EXTRA + extrasKeySuffix, language); | |
| 207 | + } | |
| 208 | + } | |
| 209 | + | |
| 210 | + public static Sample createFromIntent(Intent intent) { | |
| 211 | + if (ACTION_VIEW_LIST.equals(intent.getAction())) { | |
| 212 | + ArrayList<String> intentUris = new ArrayList<>(); | |
| 213 | + int index = 0; | |
| 214 | + while (intent.hasExtra(URI_EXTRA + "_" + index)) { | |
| 215 | + intentUris.add(intent.getStringExtra(URI_EXTRA + "_" + index)); | |
| 216 | + index++; | |
| 217 | + } | |
| 218 | + UriSample[] children = new UriSample[intentUris.size()]; | |
| 219 | + for (int i = 0; i < children.length; i++) { | |
| 220 | + Uri uri = Uri.parse(intentUris.get(i)); | |
| 221 | + children[i] = UriSample.createFromIntent(uri, intent, /* extrasKeySuffix= */ "_" + i); | |
| 222 | + } | |
| 223 | + return new PlaylistSample(/* name= */ null, children); | |
| 224 | + } else { | |
| 225 | + return UriSample.createFromIntent(intent.getData(), intent, /* extrasKeySuffix= */ ""); | |
| 226 | + } | |
| 227 | + } | |
| 228 | + | |
| 229 | + @Nullable public final String name; | |
| 230 | + | |
| 231 | + public Sample(String name) { | |
| 232 | + this.name = name; | |
| 233 | + } | |
| 234 | + | |
| 235 | + public abstract void addToIntent(Intent intent); | |
| 236 | +} | ... | ... |
app/strike/src/main/java/com/cnlive/strike/demo/exoplayer/demo/SampleChooserActivity.java
0 → 100644
| 1 | +/* | |
| 2 | + * Copyright (C) 2016 The Android Open Source Project | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + */ | |
| 16 | +package com.cnlive.strike.demo.exoplayer.demo; | |
| 17 | + | |
| 18 | +import android.content.Context; | |
| 19 | +import android.content.Intent; | |
| 20 | +import android.content.res.AssetManager; | |
| 21 | +import android.net.Uri; | |
| 22 | +import android.os.AsyncTask; | |
| 23 | +import android.os.Bundle; | |
| 24 | +import android.util.JsonReader; | |
| 25 | +import android.view.Menu; | |
| 26 | +import android.view.MenuInflater; | |
| 27 | +import android.view.MenuItem; | |
| 28 | +import android.view.View; | |
| 29 | +import android.view.View.OnClickListener; | |
| 30 | +import android.view.ViewGroup; | |
| 31 | +import android.widget.BaseExpandableListAdapter; | |
| 32 | +import android.widget.ExpandableListView; | |
| 33 | +import android.widget.ExpandableListView.OnChildClickListener; | |
| 34 | +import android.widget.ImageButton; | |
| 35 | +import android.widget.TextView; | |
| 36 | +import android.widget.Toast; | |
| 37 | +import androidx.annotation.Nullable; | |
| 38 | +import androidx.appcompat.app.AppCompatActivity; | |
| 39 | + | |
| 40 | +import com.cnlive.cloud.R; | |
| 41 | +import com.google.android.exoplayer2.ParserException; | |
| 42 | +import com.google.android.exoplayer2.RenderersFactory; | |
| 43 | +import com.google.android.exoplayer2.offline.DownloadService; | |
| 44 | +import com.google.android.exoplayer2.upstream.DataSource; | |
| 45 | +import com.google.android.exoplayer2.upstream.DataSourceInputStream; | |
| 46 | +import com.google.android.exoplayer2.upstream.DataSpec; | |
| 47 | +import com.google.android.exoplayer2.upstream.DefaultDataSource; | |
| 48 | +import com.google.android.exoplayer2.util.Assertions; | |
| 49 | +import com.google.android.exoplayer2.util.Log; | |
| 50 | +import com.google.android.exoplayer2.util.Util; | |
| 51 | +import java.io.IOException; | |
| 52 | +import java.io.InputStream; | |
| 53 | +import java.io.InputStreamReader; | |
| 54 | +import java.util.ArrayList; | |
| 55 | +import java.util.Arrays; | |
| 56 | +import java.util.Collections; | |
| 57 | +import java.util.List; | |
| 58 | +import com.cnlive.strike.demo.exoplayer.demo.Sample; | |
| 59 | +/** An activity for selecting from a list of media samples. */ | |
| 60 | +public class SampleChooserActivity extends AppCompatActivity | |
| 61 | + implements DownloadTracker.Listener, OnChildClickListener { | |
| 62 | + | |
| 63 | + private static final String TAG = "SampleChooserActivity"; | |
| 64 | + | |
| 65 | + private boolean useExtensionRenderers; | |
| 66 | + private DownloadTracker downloadTracker; | |
| 67 | + private SampleAdapter sampleAdapter; | |
| 68 | + private MenuItem preferExtensionDecodersMenuItem; | |
| 69 | + private MenuItem randomAbrMenuItem; | |
| 70 | + private MenuItem tunnelingMenuItem; | |
| 71 | + | |
| 72 | + @Override | |
| 73 | + public void onCreate(Bundle savedInstanceState) { | |
| 74 | + super.onCreate(savedInstanceState); | |
| 75 | + setContentView(R.layout.sample_chooser_activity); | |
| 76 | + sampleAdapter = new SampleAdapter(); | |
| 77 | + ExpandableListView sampleListView = findViewById(R.id.sample_list); | |
| 78 | + sampleListView.setAdapter(sampleAdapter); | |
| 79 | + sampleListView.setOnChildClickListener(this); | |
| 80 | + | |
| 81 | + Intent intent = getIntent(); | |
| 82 | + String dataUri = intent.getDataString(); | |
| 83 | + String[] uris; | |
| 84 | + if (dataUri != null) { | |
| 85 | + uris = new String[] {dataUri}; | |
| 86 | + } else { | |
| 87 | + ArrayList<String> uriList = new ArrayList<>(); | |
| 88 | + AssetManager assetManager = getAssets(); | |
| 89 | + try { | |
| 90 | + for (String asset : assetManager.list("")) { | |
| 91 | + if (asset.endsWith(".exolist.json")) { | |
| 92 | + uriList.add("asset:///" + asset); | |
| 93 | + } | |
| 94 | + } | |
| 95 | + } catch (IOException e) { | |
| 96 | + Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG) | |
| 97 | + .show(); | |
| 98 | + } | |
| 99 | + uris = new String[uriList.size()]; | |
| 100 | + uriList.toArray(uris); | |
| 101 | + Arrays.sort(uris); | |
| 102 | + } | |
| 103 | + | |
| 104 | + DemoApplication application = (DemoApplication) getApplication(); | |
| 105 | + useExtensionRenderers = application.useExtensionRenderers(); | |
| 106 | + downloadTracker = application.getDownloadTracker(); | |
| 107 | + SampleListLoader loaderTask = new SampleListLoader(); | |
| 108 | + loaderTask.execute(uris); | |
| 109 | + | |
| 110 | + // Start the download service if it should be running but it's not currently. | |
| 111 | + // Starting the service in the foreground causes notification flicker if there is no scheduled | |
| 112 | + // action. Starting it in the background throws an exception if the app is in the background too | |
| 113 | + // (e.g. if device screen is locked). | |
| 114 | + try { | |
| 115 | + DownloadService.start(this, DemoDownloadService.class); | |
| 116 | + } catch (IllegalStateException e) { | |
| 117 | + DownloadService.startForeground(this, DemoDownloadService.class); | |
| 118 | + } | |
| 119 | + } | |
| 120 | + | |
| 121 | + @Override | |
| 122 | + public boolean onCreateOptionsMenu(Menu menu) { | |
| 123 | + MenuInflater inflater = getMenuInflater(); | |
| 124 | + inflater.inflate(R.menu.sample_chooser_menu, menu); | |
| 125 | + preferExtensionDecodersMenuItem = menu.findItem(R.id.prefer_extension_decoders); | |
| 126 | + preferExtensionDecodersMenuItem.setVisible(useExtensionRenderers); | |
| 127 | + randomAbrMenuItem = menu.findItem(R.id.random_abr); | |
| 128 | + tunnelingMenuItem = menu.findItem(R.id.tunneling); | |
| 129 | + if (Util.SDK_INT < 21) { | |
| 130 | + tunnelingMenuItem.setEnabled(false); | |
| 131 | + } | |
| 132 | + return true; | |
| 133 | + } | |
| 134 | + | |
| 135 | + @Override | |
| 136 | + public boolean onOptionsItemSelected(MenuItem item) { | |
| 137 | + item.setChecked(!item.isChecked()); | |
| 138 | + return true; | |
| 139 | + } | |
| 140 | + | |
| 141 | + @Override | |
| 142 | + public void onStart() { | |
| 143 | + super.onStart(); | |
| 144 | + downloadTracker.addListener(this); | |
| 145 | + sampleAdapter.notifyDataSetChanged(); | |
| 146 | + } | |
| 147 | + | |
| 148 | + @Override | |
| 149 | + public void onStop() { | |
| 150 | + downloadTracker.removeListener(this); | |
| 151 | + super.onStop(); | |
| 152 | + } | |
| 153 | + | |
| 154 | + @Override | |
| 155 | + public void onDownloadsChanged() { | |
| 156 | + sampleAdapter.notifyDataSetChanged(); | |
| 157 | + } | |
| 158 | + | |
| 159 | + private void onSampleGroups(final List<SampleGroup> groups, boolean sawError) { | |
| 160 | + if (sawError) { | |
| 161 | + Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG) | |
| 162 | + .show(); | |
| 163 | + } | |
| 164 | + sampleAdapter.setSampleGroups(groups); | |
| 165 | + } | |
| 166 | + | |
| 167 | + @Override | |
| 168 | + public boolean onChildClick( | |
| 169 | + ExpandableListView parent, View view, int groupPosition, int childPosition, long id) { | |
| 170 | + Sample sample = (Sample) view.getTag(); | |
| 171 | + Intent intent = new Intent(this, PlayerActivity.class); | |
| 172 | + intent.putExtra( | |
| 173 | + PlayerActivity.PREFER_EXTENSION_DECODERS_EXTRA, | |
| 174 | + isNonNullAndChecked(preferExtensionDecodersMenuItem)); | |
| 175 | + String abrAlgorithm = | |
| 176 | + isNonNullAndChecked(randomAbrMenuItem) | |
| 177 | + ? PlayerActivity.ABR_ALGORITHM_RANDOM | |
| 178 | + : PlayerActivity.ABR_ALGORITHM_DEFAULT; | |
| 179 | + intent.putExtra(PlayerActivity.ABR_ALGORITHM_EXTRA, abrAlgorithm); | |
| 180 | + intent.putExtra(PlayerActivity.TUNNELING_EXTRA, isNonNullAndChecked(tunnelingMenuItem)); | |
| 181 | + sample.addToIntent(intent); | |
| 182 | + startActivity(intent); | |
| 183 | + return true; | |
| 184 | + } | |
| 185 | + | |
| 186 | + private void onSampleDownloadButtonClicked(Sample sample) { | |
| 187 | + int downloadUnsupportedStringId = getDownloadUnsupportedStringId(sample); | |
| 188 | + if (downloadUnsupportedStringId != 0) { | |
| 189 | + Toast.makeText(getApplicationContext(), downloadUnsupportedStringId, Toast.LENGTH_LONG) | |
| 190 | + .show(); | |
| 191 | + } else { | |
| 192 | + Sample.UriSample UriSample = (Sample.UriSample) sample; | |
| 193 | + RenderersFactory renderersFactory = | |
| 194 | + ((DemoApplication) getApplication()) | |
| 195 | + .buildRenderersFactory(isNonNullAndChecked(preferExtensionDecodersMenuItem)); | |
| 196 | + downloadTracker.toggleDownload( | |
| 197 | + getSupportFragmentManager(), | |
| 198 | + sample.name, | |
| 199 | + UriSample.uri, | |
| 200 | + UriSample.extension, | |
| 201 | + renderersFactory); | |
| 202 | + } | |
| 203 | + } | |
| 204 | + | |
| 205 | + private int getDownloadUnsupportedStringId(Sample sample) { | |
| 206 | + if (sample instanceof Sample.PlaylistSample) { | |
| 207 | + return R.string.download_playlist_unsupported; | |
| 208 | + } | |
| 209 | + Sample.UriSample UriSample = (Sample.UriSample) sample; | |
| 210 | + if (UriSample.drmInfo != null) { | |
| 211 | + return R.string.download_drm_unsupported; | |
| 212 | + } | |
| 213 | + if (UriSample.isLive) { | |
| 214 | + return R.string.download_live_unsupported; | |
| 215 | + } | |
| 216 | + if (UriSample.adTagUri != null) { | |
| 217 | + return R.string.download_ads_unsupported; | |
| 218 | + } | |
| 219 | + String scheme = UriSample.uri.getScheme(); | |
| 220 | + if (!("http".equals(scheme) || "https".equals(scheme))) { | |
| 221 | + return R.string.download_scheme_unsupported; | |
| 222 | + } | |
| 223 | + return 0; | |
| 224 | + } | |
| 225 | + | |
| 226 | + private static boolean isNonNullAndChecked(@Nullable MenuItem menuItem) { | |
| 227 | + // Temporary workaround for layouts that do not inflate the options menu. | |
| 228 | + return menuItem != null && menuItem.isChecked(); | |
| 229 | + } | |
| 230 | + | |
| 231 | + private final class SampleListLoader extends AsyncTask<String, Void, List<SampleGroup>> { | |
| 232 | + | |
| 233 | + private boolean sawError; | |
| 234 | + | |
| 235 | + @Override | |
| 236 | + protected List<SampleGroup> doInBackground(String... uris) { | |
| 237 | + List<SampleGroup> result = new ArrayList<>(); | |
| 238 | + Context context = getApplicationContext(); | |
| 239 | + String userAgent = Util.getUserAgent(context, "ExoPlayerDemo"); | |
| 240 | + DataSource dataSource = | |
| 241 | + new DefaultDataSource(context, userAgent, /* allowCrossProtocolRedirects= */ false); | |
| 242 | + for (String uri : uris) { | |
| 243 | + DataSpec dataSpec = new DataSpec(Uri.parse(uri)); | |
| 244 | + InputStream inputStream = new DataSourceInputStream(dataSource, dataSpec); | |
| 245 | + try { | |
| 246 | + readSampleGroups(new JsonReader(new InputStreamReader(inputStream, "UTF-8")), result); | |
| 247 | + } catch (Exception e) { | |
| 248 | + Log.e(TAG, "Error loading sample list: " + uri, e); | |
| 249 | + sawError = true; | |
| 250 | + } finally { | |
| 251 | + Util.closeQuietly(dataSource); | |
| 252 | + } | |
| 253 | + } | |
| 254 | + return result; | |
| 255 | + } | |
| 256 | + | |
| 257 | + @Override | |
| 258 | + protected void onPostExecute(List<SampleGroup> result) { | |
| 259 | + onSampleGroups(result, sawError); | |
| 260 | + } | |
| 261 | + | |
| 262 | + private void readSampleGroups(JsonReader reader, List<SampleGroup> groups) throws IOException { | |
| 263 | + reader.beginArray(); | |
| 264 | + while (reader.hasNext()) { | |
| 265 | + readSampleGroup(reader, groups); | |
| 266 | + } | |
| 267 | + reader.endArray(); | |
| 268 | + } | |
| 269 | + | |
| 270 | + private void readSampleGroup(JsonReader reader, List<SampleGroup> groups) throws IOException { | |
| 271 | + String groupName = ""; | |
| 272 | + ArrayList<Sample> samples = new ArrayList<>(); | |
| 273 | + | |
| 274 | + reader.beginObject(); | |
| 275 | + while (reader.hasNext()) { | |
| 276 | + String name = reader.nextName(); | |
| 277 | + switch (name) { | |
| 278 | + case "name": | |
| 279 | + groupName = reader.nextString(); | |
| 280 | + break; | |
| 281 | + case "samples": | |
| 282 | + reader.beginArray(); | |
| 283 | + while (reader.hasNext()) { | |
| 284 | + samples.add(readEntry(reader, false)); | |
| 285 | + } | |
| 286 | + reader.endArray(); | |
| 287 | + break; | |
| 288 | + case "_comment": | |
| 289 | + reader.nextString(); // Ignore. | |
| 290 | + break; | |
| 291 | + default: | |
| 292 | + throw new ParserException("Unsupported name: " + name); | |
| 293 | + } | |
| 294 | + } | |
| 295 | + reader.endObject(); | |
| 296 | + | |
| 297 | + SampleGroup group = getGroup(groupName, groups); | |
| 298 | + group.samples.addAll(samples); | |
| 299 | + } | |
| 300 | + | |
| 301 | + private Sample readEntry(JsonReader reader, boolean insidePlaylist) throws IOException { | |
| 302 | + String sampleName = null; | |
| 303 | + Uri uri = null; | |
| 304 | + String extension = null; | |
| 305 | + boolean isLive = false; | |
| 306 | + String drmScheme = null; | |
| 307 | + String drmLicenseUrl = null; | |
| 308 | + String[] drmKeyRequestProperties = null; | |
| 309 | + boolean drmMultiSession = false; | |
| 310 | + ArrayList<Sample.UriSample> playlistSamples = null; | |
| 311 | + String adTagUri = null; | |
| 312 | + String sphericalStereoMode = null; | |
| 313 | + List<Sample.SubtitleInfo> subtitleInfos = new ArrayList<>(); | |
| 314 | + Uri subtitleUri = null; | |
| 315 | + String subtitleMimeType = null; | |
| 316 | + String subtitleLanguage = null; | |
| 317 | + | |
| 318 | + reader.beginObject(); | |
| 319 | + while (reader.hasNext()) { | |
| 320 | + String name = reader.nextName(); | |
| 321 | + switch (name) { | |
| 322 | + case "name": | |
| 323 | + sampleName = reader.nextString(); | |
| 324 | + break; | |
| 325 | + case "uri": | |
| 326 | + uri = Uri.parse(reader.nextString()); | |
| 327 | + break; | |
| 328 | + case "extension": | |
| 329 | + extension = reader.nextString(); | |
| 330 | + break; | |
| 331 | + case "drm_scheme": | |
| 332 | + drmScheme = reader.nextString(); | |
| 333 | + break; | |
| 334 | + case "is_live": | |
| 335 | + isLive = reader.nextBoolean(); | |
| 336 | + break; | |
| 337 | + case "drm_license_url": | |
| 338 | + drmLicenseUrl = reader.nextString(); | |
| 339 | + break; | |
| 340 | + case "drm_key_request_properties": | |
| 341 | + ArrayList<String> drmKeyRequestPropertiesList = new ArrayList<>(); | |
| 342 | + reader.beginObject(); | |
| 343 | + while (reader.hasNext()) { | |
| 344 | + drmKeyRequestPropertiesList.add(reader.nextName()); | |
| 345 | + drmKeyRequestPropertiesList.add(reader.nextString()); | |
| 346 | + } | |
| 347 | + reader.endObject(); | |
| 348 | + drmKeyRequestProperties = drmKeyRequestPropertiesList.toArray(new String[0]); | |
| 349 | + break; | |
| 350 | + case "drm_multi_session": | |
| 351 | + drmMultiSession = reader.nextBoolean(); | |
| 352 | + break; | |
| 353 | + case "playlist": | |
| 354 | + Assertions.checkState(!insidePlaylist, "Invalid nesting of playlists"); | |
| 355 | + playlistSamples = new ArrayList<Sample.UriSample>(); | |
| 356 | + reader.beginArray(); | |
| 357 | + while (reader.hasNext()) { | |
| 358 | + playlistSamples.add((Sample.UriSample) readEntry(reader, /* insidePlaylist= */ true)); | |
| 359 | + } | |
| 360 | + reader.endArray(); | |
| 361 | + break; | |
| 362 | + case "ad_tag_uri": | |
| 363 | + adTagUri = reader.nextString(); | |
| 364 | + break; | |
| 365 | + case "spherical_stereo_mode": | |
| 366 | + Assertions.checkState( | |
| 367 | + !insidePlaylist, "Invalid attribute on nested item: spherical_stereo_mode"); | |
| 368 | + sphericalStereoMode = reader.nextString(); | |
| 369 | + break; | |
| 370 | + case "subtitle_uri": | |
| 371 | + subtitleUri = Uri.parse(reader.nextString()); | |
| 372 | + break; | |
| 373 | + case "subtitle_mime_type": | |
| 374 | + subtitleMimeType = reader.nextString(); | |
| 375 | + break; | |
| 376 | + case "subtitle_language": | |
| 377 | + subtitleLanguage = reader.nextString(); | |
| 378 | + break; | |
| 379 | + default: | |
| 380 | + throw new ParserException("Unsupported attribute name: " + name); | |
| 381 | + } | |
| 382 | + } | |
| 383 | + reader.endObject(); | |
| 384 | + Sample.DrmInfo drmInfo = | |
| 385 | + drmScheme == null | |
| 386 | + ? null | |
| 387 | + : new Sample.DrmInfo( | |
| 388 | + Util.getDrmUuid(drmScheme), | |
| 389 | + drmLicenseUrl, | |
| 390 | + drmKeyRequestProperties, | |
| 391 | + drmMultiSession); | |
| 392 | + Sample.SubtitleInfo subtitleInfo = | |
| 393 | + subtitleUri == null | |
| 394 | + ? null | |
| 395 | + : new Sample.SubtitleInfo( | |
| 396 | + subtitleUri, | |
| 397 | + Assertions.checkNotNull( | |
| 398 | + subtitleMimeType, "subtitle_mime_type is required if subtitle_uri is set."), | |
| 399 | + subtitleLanguage); | |
| 400 | + if (playlistSamples != null) { | |
| 401 | + Sample.UriSample[] playlistSamplesArray = playlistSamples.toArray(new Sample.UriSample[0]); | |
| 402 | + return new Sample.PlaylistSample(sampleName, playlistSamplesArray); | |
| 403 | + } else { | |
| 404 | + return new Sample.UriSample( | |
| 405 | + sampleName, | |
| 406 | + uri, | |
| 407 | + extension, | |
| 408 | + isLive, | |
| 409 | + drmInfo, | |
| 410 | + adTagUri != null ? Uri.parse(adTagUri) : null, | |
| 411 | + sphericalStereoMode, | |
| 412 | + subtitleInfo); | |
| 413 | + } | |
| 414 | + } | |
| 415 | + | |
| 416 | + private SampleGroup getGroup(String groupName, List<SampleGroup> groups) { | |
| 417 | + for (int i = 0; i < groups.size(); i++) { | |
| 418 | + if (Util.areEqual(groupName, groups.get(i).title)) { | |
| 419 | + return groups.get(i); | |
| 420 | + } | |
| 421 | + } | |
| 422 | + SampleGroup group = new SampleGroup(groupName); | |
| 423 | + groups.add(group); | |
| 424 | + return group; | |
| 425 | + } | |
| 426 | + | |
| 427 | + } | |
| 428 | + | |
| 429 | + private final class SampleAdapter extends BaseExpandableListAdapter implements OnClickListener { | |
| 430 | + | |
| 431 | + private List<SampleGroup> sampleGroups; | |
| 432 | + | |
| 433 | + public SampleAdapter() { | |
| 434 | + sampleGroups = Collections.emptyList(); | |
| 435 | + } | |
| 436 | + | |
| 437 | + public void setSampleGroups(List<SampleGroup> sampleGroups) { | |
| 438 | + this.sampleGroups = sampleGroups; | |
| 439 | + notifyDataSetChanged(); | |
| 440 | + } | |
| 441 | + | |
| 442 | + @Override | |
| 443 | + public Sample getChild(int groupPosition, int childPosition) { | |
| 444 | + return getGroup(groupPosition).samples.get(childPosition); | |
| 445 | + } | |
| 446 | + | |
| 447 | + @Override | |
| 448 | + public long getChildId(int groupPosition, int childPosition) { | |
| 449 | + return childPosition; | |
| 450 | + } | |
| 451 | + | |
| 452 | + @Override | |
| 453 | + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, | |
| 454 | + View convertView, ViewGroup parent) { | |
| 455 | + View view = convertView; | |
| 456 | + if (view == null) { | |
| 457 | + view = getLayoutInflater().inflate(R.layout.sample_list_item, parent, false); | |
| 458 | + View downloadButton = view.findViewById(R.id.download_button); | |
| 459 | + downloadButton.setOnClickListener(this); | |
| 460 | + downloadButton.setFocusable(false); | |
| 461 | + } | |
| 462 | + initializeChildView(view, getChild(groupPosition, childPosition)); | |
| 463 | + return view; | |
| 464 | + } | |
| 465 | + | |
| 466 | + @Override | |
| 467 | + public int getChildrenCount(int groupPosition) { | |
| 468 | + return getGroup(groupPosition).samples.size(); | |
| 469 | + } | |
| 470 | + | |
| 471 | + @Override | |
| 472 | + public SampleGroup getGroup(int groupPosition) { | |
| 473 | + return sampleGroups.get(groupPosition); | |
| 474 | + } | |
| 475 | + | |
| 476 | + @Override | |
| 477 | + public long getGroupId(int groupPosition) { | |
| 478 | + return groupPosition; | |
| 479 | + } | |
| 480 | + | |
| 481 | + @Override | |
| 482 | + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, | |
| 483 | + ViewGroup parent) { | |
| 484 | + View view = convertView; | |
| 485 | + if (view == null) { | |
| 486 | + view = | |
| 487 | + getLayoutInflater() | |
| 488 | + .inflate(android.R.layout.simple_expandable_list_item_1, parent, false); | |
| 489 | + } | |
| 490 | + ((TextView) view).setText(getGroup(groupPosition).title); | |
| 491 | + return view; | |
| 492 | + } | |
| 493 | + | |
| 494 | + @Override | |
| 495 | + public int getGroupCount() { | |
| 496 | + return sampleGroups.size(); | |
| 497 | + } | |
| 498 | + | |
| 499 | + @Override | |
| 500 | + public boolean hasStableIds() { | |
| 501 | + return false; | |
| 502 | + } | |
| 503 | + | |
| 504 | + @Override | |
| 505 | + public boolean isChildSelectable(int groupPosition, int childPosition) { | |
| 506 | + return true; | |
| 507 | + } | |
| 508 | + | |
| 509 | + @Override | |
| 510 | + public void onClick(View view) { | |
| 511 | + onSampleDownloadButtonClicked((Sample) view.getTag()); | |
| 512 | + } | |
| 513 | + | |
| 514 | + private void initializeChildView(View view, Sample sample) { | |
| 515 | + view.setTag(sample); | |
| 516 | + TextView sampleTitle = view.findViewById(R.id.sample_title); | |
| 517 | + sampleTitle.setText(sample.name); | |
| 518 | + | |
| 519 | + boolean canDownload = getDownloadUnsupportedStringId(sample) == 0; | |
| 520 | + boolean isDownloaded = canDownload && downloadTracker.isDownloaded(((Sample.UriSample) sample).uri); | |
| 521 | + ImageButton downloadButton = view.findViewById(R.id.download_button); | |
| 522 | + downloadButton.setTag(sample); | |
| 523 | + downloadButton.setColorFilter( | |
| 524 | + canDownload ? (isDownloaded ? 0xFF42A5F5 : 0xFFBDBDBD) : 0xFF666666); | |
| 525 | + downloadButton.setImageResource( | |
| 526 | + isDownloaded ? R.drawable.ic_download_done : R.drawable.ic_download); | |
| 527 | + } | |
| 528 | + } | |
| 529 | + | |
| 530 | + private static final class SampleGroup { | |
| 531 | + | |
| 532 | + public final String title; | |
| 533 | + public final List<Sample> samples; | |
| 534 | + | |
| 535 | + public SampleGroup(String title) { | |
| 536 | + this.title = title; | |
| 537 | + this.samples = new ArrayList<>(); | |
| 538 | + } | |
| 539 | + | |
| 540 | + } | |
| 541 | +} | ... | ... |
app/strike/src/main/java/com/cnlive/strike/demo/exoplayer/demo/TrackSelectionDialog.java
0 → 100644
| 1 | +/* | |
| 2 | + * Copyright (C) 2019 The Android Open Source Project | |
| 3 | + * | |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + * you may not use this file except in compliance with the License. | |
| 6 | + * You may obtain a copy of the License at | |
| 7 | + * | |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + * | |
| 10 | + * Unless required by applicable law or agreed to in writing, software | |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + * See the License for the specific language governing permissions and | |
| 14 | + * limitations under the License. | |
| 15 | + */ | |
| 16 | +package com.cnlive.strike.demo.exoplayer.demo; | |
| 17 | + | |
| 18 | +import android.app.Dialog; | |
| 19 | +import android.content.DialogInterface; | |
| 20 | +import android.content.res.Resources; | |
| 21 | +import android.os.Bundle; | |
| 22 | +import android.util.SparseArray; | |
| 23 | +import android.view.LayoutInflater; | |
| 24 | +import android.view.View; | |
| 25 | +import android.view.ViewGroup; | |
| 26 | +import android.widget.Button; | |
| 27 | +import androidx.annotation.Nullable; | |
| 28 | +import androidx.appcompat.app.AppCompatDialog; | |
| 29 | +import androidx.fragment.app.DialogFragment; | |
| 30 | +import androidx.fragment.app.Fragment; | |
| 31 | +import androidx.fragment.app.FragmentManager; | |
| 32 | +import androidx.fragment.app.FragmentPagerAdapter; | |
| 33 | +import androidx.viewpager.widget.ViewPager; | |
| 34 | + | |
| 35 | +import com.cnlive.cloud.R; | |
| 36 | +import com.google.android.exoplayer2.C; | |
| 37 | +import com.google.android.exoplayer2.source.TrackGroupArray; | |
| 38 | +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; | |
| 39 | +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride; | |
| 40 | +import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; | |
| 41 | +import com.google.android.exoplayer2.ui.TrackSelectionView; | |
| 42 | +import com.google.android.exoplayer2.util.Assertions; | |
| 43 | +import com.google.android.material.tabs.TabLayout; | |
| 44 | +import java.util.ArrayList; | |
| 45 | +import java.util.Collections; | |
| 46 | +import java.util.List; | |
| 47 | + | |
| 48 | +/** Dialog to select tracks. */ | |
| 49 | +public final class TrackSelectionDialog extends DialogFragment { | |
| 50 | + | |
| 51 | + private final SparseArray<TrackSelectionViewFragment> tabFragments; | |
| 52 | + private final ArrayList<Integer> tabTrackTypes; | |
| 53 | + | |
| 54 | + private int titleId; | |
| 55 | + private DialogInterface.OnClickListener onClickListener; | |
| 56 | + private DialogInterface.OnDismissListener onDismissListener; | |
| 57 | + | |
| 58 | + /** | |
| 59 | + * Returns whether a track selection dialog will have content to display if initialized with the | |
| 60 | + * specified {@link DefaultTrackSelector} in its current state. | |
| 61 | + */ | |
| 62 | + public static boolean willHaveContent(DefaultTrackSelector trackSelector) { | |
| 63 | + MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo(); | |
| 64 | + return mappedTrackInfo != null && willHaveContent(mappedTrackInfo); | |
| 65 | + } | |
| 66 | + | |
| 67 | + /** | |
| 68 | + * Returns whether a track selection dialog will have content to display if initialized with the | |
| 69 | + * specified {@link MappedTrackInfo}. | |
| 70 | + */ | |
| 71 | + public static boolean willHaveContent(MappedTrackInfo mappedTrackInfo) { | |
| 72 | + for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) { | |
| 73 | + if (showTabForRenderer(mappedTrackInfo, i)) { | |
| 74 | + return true; | |
| 75 | + } | |
| 76 | + } | |
| 77 | + return false; | |
| 78 | + } | |
| 79 | + | |
| 80 | + /** | |
| 81 | + * Creates a dialog for a given {@link DefaultTrackSelector}, whose parameters will be | |
| 82 | + * automatically updated when tracks are selected. | |
| 83 | + * | |
| 84 | + * @param trackSelector The {@link DefaultTrackSelector}. | |
| 85 | + * @param onDismissListener A {@link DialogInterface.OnDismissListener} to call when the dialog is | |
| 86 | + * dismissed. | |
| 87 | + */ | |
| 88 | + public static TrackSelectionDialog createForTrackSelector( | |
| 89 | + DefaultTrackSelector trackSelector, DialogInterface.OnDismissListener onDismissListener) { | |
| 90 | + MappedTrackInfo mappedTrackInfo = | |
| 91 | + Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo()); | |
| 92 | + TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog(); | |
| 93 | + DefaultTrackSelector.Parameters parameters = trackSelector.getParameters(); | |
| 94 | + trackSelectionDialog.init( | |
| 95 | + /* titleId= */ R.string.track_selection_title, | |
| 96 | + mappedTrackInfo, | |
| 97 | + /* initialParameters = */ parameters, | |
| 98 | + /* allowAdaptiveSelections =*/ true, | |
| 99 | + /* allowMultipleOverrides= */ false, | |
| 100 | + /* onClickListener= */ (dialog, which) -> { | |
| 101 | + DefaultTrackSelector.ParametersBuilder builder = parameters.buildUpon(); | |
| 102 | + for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) { | |
| 103 | + builder | |
| 104 | + .clearSelectionOverrides(/* rendererIndex= */ i) | |
| 105 | + .setRendererDisabled( | |
| 106 | + /* rendererIndex= */ i, | |
| 107 | + trackSelectionDialog.getIsDisabled(/* rendererIndex= */ i)); | |
| 108 | + List<SelectionOverride> overrides = | |
| 109 | + trackSelectionDialog.getOverrides(/* rendererIndex= */ i); | |
| 110 | + if (!overrides.isEmpty()) { | |
| 111 | + builder.setSelectionOverride( | |
| 112 | + /* rendererIndex= */ i, | |
| 113 | + mappedTrackInfo.getTrackGroups(/* rendererIndex= */ i), | |
| 114 | + overrides.get(0)); | |
| 115 | + } | |
| 116 | + } | |
| 117 | + trackSelector.setParameters(builder); | |
| 118 | + }, | |
| 119 | + onDismissListener); | |
| 120 | + return trackSelectionDialog; | |
| 121 | + } | |
| 122 | + | |
| 123 | + /** | |
| 124 | + * Creates a dialog for given {@link MappedTrackInfo} and {@link DefaultTrackSelector.Parameters}. | |
| 125 | + * | |
| 126 | + * @param titleId The resource id of the dialog title. | |
| 127 | + * @param mappedTrackInfo The {@link MappedTrackInfo} to display. | |
| 128 | + * @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the initial | |
| 129 | + * track selection. | |
| 130 | + * @param allowAdaptiveSelections Whether adaptive selections (consisting of more than one track) | |
| 131 | + * can be made. | |
| 132 | + * @param allowMultipleOverrides Whether tracks from multiple track groups can be selected. | |
| 133 | + * @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are selected. | |
| 134 | + * @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog is | |
| 135 | + * dismissed. | |
| 136 | + */ | |
| 137 | + public static TrackSelectionDialog createForMappedTrackInfoAndParameters( | |
| 138 | + int titleId, | |
| 139 | + MappedTrackInfo mappedTrackInfo, | |
| 140 | + DefaultTrackSelector.Parameters initialParameters, | |
| 141 | + boolean allowAdaptiveSelections, | |
| 142 | + boolean allowMultipleOverrides, | |
| 143 | + DialogInterface.OnClickListener onClickListener, | |
| 144 | + DialogInterface.OnDismissListener onDismissListener) { | |
| 145 | + TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog(); | |
| 146 | + trackSelectionDialog.init( | |
| 147 | + titleId, | |
| 148 | + mappedTrackInfo, | |
| 149 | + initialParameters, | |
| 150 | + allowAdaptiveSelections, | |
| 151 | + allowMultipleOverrides, | |
| 152 | + onClickListener, | |
| 153 | + onDismissListener); | |
| 154 | + return trackSelectionDialog; | |
| 155 | + } | |
| 156 | + | |
| 157 | + public TrackSelectionDialog() { | |
| 158 | + tabFragments = new SparseArray<>(); | |
| 159 | + tabTrackTypes = new ArrayList<>(); | |
| 160 | + // Retain instance across activity re-creation to prevent losing access to init data. | |
| 161 | + setRetainInstance(true); | |
| 162 | + } | |
| 163 | + | |
| 164 | + private void init( | |
| 165 | + int titleId, | |
| 166 | + MappedTrackInfo mappedTrackInfo, | |
| 167 | + DefaultTrackSelector.Parameters initialParameters, | |
| 168 | + boolean allowAdaptiveSelections, | |
| 169 | + boolean allowMultipleOverrides, | |
| 170 | + DialogInterface.OnClickListener onClickListener, | |
| 171 | + DialogInterface.OnDismissListener onDismissListener) { | |
| 172 | + this.titleId = titleId; | |
| 173 | + this.onClickListener = onClickListener; | |
| 174 | + this.onDismissListener = onDismissListener; | |
| 175 | + for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) { | |
| 176 | + if (showTabForRenderer(mappedTrackInfo, i)) { | |
| 177 | + int trackType = mappedTrackInfo.getRendererType(/* rendererIndex= */ i); | |
| 178 | + TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(i); | |
| 179 | + TrackSelectionViewFragment tabFragment = new TrackSelectionViewFragment(); | |
| 180 | + tabFragment.init( | |
| 181 | + mappedTrackInfo, | |
| 182 | + /* rendererIndex= */ i, | |
| 183 | + initialParameters.getRendererDisabled(/* rendererIndex= */ i), | |
| 184 | + initialParameters.getSelectionOverride(/* rendererIndex= */ i, trackGroupArray), | |
| 185 | + allowAdaptiveSelections, | |
| 186 | + allowMultipleOverrides); | |
| 187 | + tabFragments.put(i, tabFragment); | |
| 188 | + tabTrackTypes.add(trackType); | |
| 189 | + } | |
| 190 | + } | |
| 191 | + } | |
| 192 | + | |
| 193 | + /** | |
| 194 | + * Returns whether a renderer is disabled. | |
| 195 | + * | |
| 196 | + * @param rendererIndex Renderer index. | |
| 197 | + * @return Whether the renderer is disabled. | |
| 198 | + */ | |
| 199 | + public boolean getIsDisabled(int rendererIndex) { | |
| 200 | + TrackSelectionViewFragment rendererView = tabFragments.get(rendererIndex); | |
| 201 | + return rendererView != null && rendererView.isDisabled; | |
| 202 | + } | |
| 203 | + | |
| 204 | + /** | |
| 205 | + * Returns the list of selected track selection overrides for the specified renderer. There will | |
| 206 | + * be at most one override for each track group. | |
| 207 | + * | |
| 208 | + * @param rendererIndex Renderer index. | |
| 209 | + * @return The list of track selection overrides for this renderer. | |
| 210 | + */ | |
| 211 | + public List<SelectionOverride> getOverrides(int rendererIndex) { | |
| 212 | + TrackSelectionViewFragment rendererView = tabFragments.get(rendererIndex); | |
| 213 | + return rendererView == null ? Collections.emptyList() : rendererView.overrides; | |
| 214 | + } | |
| 215 | + | |
| 216 | + @Override | |
| 217 | + public Dialog onCreateDialog(Bundle savedInstanceState) { | |
| 218 | + // We need to own the view to let tab layout work correctly on all API levels. We can't use | |
| 219 | + // AlertDialog because it owns the view itself, so we use AppCompatDialog instead, themed using | |
| 220 | + // the AlertDialog theme overlay with force-enabled title. | |
| 221 | + AppCompatDialog dialog = | |
| 222 | + new AppCompatDialog(getActivity(), R.style.TrackSelectionDialogThemeOverlay); | |
| 223 | + dialog.setTitle(titleId); | |
| 224 | + return dialog; | |
| 225 | + } | |
| 226 | + | |
| 227 | + @Override | |
| 228 | + public void onDismiss(DialogInterface dialog) { | |
| 229 | + super.onDismiss(dialog); | |
| 230 | + onDismissListener.onDismiss(dialog); | |
| 231 | + } | |
| 232 | + | |
| 233 | + @Nullable | |
| 234 | + @Override | |
| 235 | + public View onCreateView( | |
| 236 | + LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { | |
| 237 | + | |
| 238 | + View dialogView = inflater.inflate(R.layout.track_selection_dialog, container, false); | |
| 239 | + TabLayout tabLayout = dialogView.findViewById(R.id.track_selection_dialog_tab_layout); | |
| 240 | + ViewPager viewPager = dialogView.findViewById(R.id.track_selection_dialog_view_pager); | |
| 241 | + Button cancelButton = dialogView.findViewById(R.id.track_selection_dialog_cancel_button); | |
| 242 | + Button okButton = dialogView.findViewById(R.id.track_selection_dialog_ok_button); | |
| 243 | + viewPager.setAdapter(new FragmentAdapter(getChildFragmentManager())); | |
| 244 | + tabLayout.setupWithViewPager(viewPager); | |
| 245 | + tabLayout.setVisibility(tabFragments.size() > 1 ? View.VISIBLE : View.GONE); | |
| 246 | + cancelButton.setOnClickListener(view -> dismiss()); | |
| 247 | + okButton.setOnClickListener( | |
| 248 | + view -> { | |
| 249 | + onClickListener.onClick(getDialog(), DialogInterface.BUTTON_POSITIVE); | |
| 250 | + dismiss(); | |
| 251 | + }); | |
| 252 | + return dialogView; | |
| 253 | + } | |
| 254 | + | |
| 255 | + private static boolean showTabForRenderer(MappedTrackInfo mappedTrackInfo, int rendererIndex) { | |
| 256 | + TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(rendererIndex); | |
| 257 | + if (trackGroupArray.length == 0) { | |
| 258 | + return false; | |
| 259 | + } | |
| 260 | + int trackType = mappedTrackInfo.getRendererType(rendererIndex); | |
| 261 | + return isSupportedTrackType(trackType); | |
| 262 | + } | |
| 263 | + | |
| 264 | + private static boolean isSupportedTrackType(int trackType) { | |
| 265 | + switch (trackType) { | |
| 266 | + case C.TRACK_TYPE_VIDEO: | |
| 267 | + case C.TRACK_TYPE_AUDIO: | |
| 268 | + case C.TRACK_TYPE_TEXT: | |
| 269 | + return true; | |
| 270 | + default: | |
| 271 | + return false; | |
| 272 | + } | |
| 273 | + } | |
| 274 | + | |
| 275 | + private static String getTrackTypeString(Resources resources, int trackType) { | |
| 276 | + switch (trackType) { | |
| 277 | + case C.TRACK_TYPE_VIDEO: | |
| 278 | + return resources.getString(R.string.exo_track_selection_title_video); | |
| 279 | + case C.TRACK_TYPE_AUDIO: | |
| 280 | + return resources.getString(R.string.exo_track_selection_title_audio); | |
| 281 | + case C.TRACK_TYPE_TEXT: | |
| 282 | + return resources.getString(R.string.exo_track_selection_title_text); | |
| 283 | + default: | |
| 284 | + throw new IllegalArgumentException(); | |
| 285 | + } | |
| 286 | + } | |
| 287 | + | |
| 288 | + private final class FragmentAdapter extends FragmentPagerAdapter { | |
| 289 | + | |
| 290 | + public FragmentAdapter(FragmentManager fragmentManager) { | |
| 291 | + super(fragmentManager); | |
| 292 | + } | |
| 293 | + | |
| 294 | + @Override | |
| 295 | + public Fragment getItem(int position) { | |
| 296 | + return tabFragments.valueAt(position); | |
| 297 | + } | |
| 298 | + | |
| 299 | + @Override | |
| 300 | + public int getCount() { | |
| 301 | + return tabFragments.size(); | |
| 302 | + } | |
| 303 | + | |
| 304 | + @Nullable | |
| 305 | + @Override | |
| 306 | + public CharSequence getPageTitle(int position) { | |
| 307 | + return getTrackTypeString(getResources(), tabTrackTypes.get(position)); | |
| 308 | + } | |
| 309 | + } | |
| 310 | + | |
| 311 | + /** Fragment to show a track selection in tab of the track selection dialog. */ | |
| 312 | + public static final class TrackSelectionViewFragment extends Fragment | |
| 313 | + implements TrackSelectionView.TrackSelectionListener { | |
| 314 | + | |
| 315 | + private MappedTrackInfo mappedTrackInfo; | |
| 316 | + private int rendererIndex; | |
| 317 | + private boolean allowAdaptiveSelections; | |
| 318 | + private boolean allowMultipleOverrides; | |
| 319 | + | |
| 320 | + /* package */ boolean isDisabled; | |
| 321 | + /* package */ List<SelectionOverride> overrides; | |
| 322 | + | |
| 323 | + public TrackSelectionViewFragment() { | |
| 324 | + // Retain instance across activity re-creation to prevent losing access to init data. | |
| 325 | + setRetainInstance(true); | |
| 326 | + } | |
| 327 | + | |
| 328 | + public void init( | |
| 329 | + MappedTrackInfo mappedTrackInfo, | |
| 330 | + int rendererIndex, | |
| 331 | + boolean initialIsDisabled, | |
| 332 | + @Nullable SelectionOverride initialOverride, | |
| 333 | + boolean allowAdaptiveSelections, | |
| 334 | + boolean allowMultipleOverrides) { | |
| 335 | + this.mappedTrackInfo = mappedTrackInfo; | |
| 336 | + this.rendererIndex = rendererIndex; | |
| 337 | + this.isDisabled = initialIsDisabled; | |
| 338 | + this.overrides = | |
| 339 | + initialOverride == null | |
| 340 | + ? Collections.emptyList() | |
| 341 | + : Collections.singletonList(initialOverride); | |
| 342 | + this.allowAdaptiveSelections = allowAdaptiveSelections; | |
| 343 | + this.allowMultipleOverrides = allowMultipleOverrides; | |
| 344 | + } | |
| 345 | + | |
| 346 | + @Nullable | |
| 347 | + @Override | |
| 348 | + public View onCreateView( | |
| 349 | + LayoutInflater inflater, | |
| 350 | + @Nullable ViewGroup container, | |
| 351 | + @Nullable Bundle savedInstanceState) { | |
| 352 | + View rootView = | |
| 353 | + inflater.inflate( | |
| 354 | + R.layout.exo_track_selection_dialog, container, /* attachToRoot= */ false); | |
| 355 | + TrackSelectionView trackSelectionView = rootView.findViewById(R.id.exo_track_selection_view); | |
| 356 | + trackSelectionView.setShowDisableOption(true); | |
| 357 | + trackSelectionView.setAllowMultipleOverrides(allowMultipleOverrides); | |
| 358 | + trackSelectionView.setAllowAdaptiveSelections(allowAdaptiveSelections); | |
| 359 | + trackSelectionView.init( | |
| 360 | + mappedTrackInfo, rendererIndex, isDisabled, overrides, /* listener= */ this); | |
| 361 | + return rootView; | |
| 362 | + } | |
| 363 | + | |
| 364 | + @Override | |
| 365 | + public void onTrackSelectionChanged(boolean isDisabled, List<SelectionOverride> overrides) { | |
| 366 | + this.isDisabled = isDisabled; | |
| 367 | + this.overrides = overrides; | |
| 368 | + } | |
| 369 | + } | |
| 370 | +} | ... | ... |
app/strike/src/main/java/com/cnlive/strike/imgutil/ImgActivity.java
| ... | ... | @@ -10,7 +10,10 @@ import android.os.Message; |
| 10 | 10 | import android.view.View; |
| 11 | 11 | import android.widget.ImageView; |
| 12 | 12 | |
| 13 | +import com.bumptech.glide.Glide; | |
| 14 | +import com.bumptech.glide.load.DecodeFormat; | |
| 13 | 15 | import com.bumptech.glide.load.engine.DiskCacheStrategy; |
| 16 | +import com.bumptech.glide.request.RequestOptions; | |
| 14 | 17 | import com.bumptech.glide.request.transition.ViewPropertyTransition; |
| 15 | 18 | import com.cnlive.core.imageload.callback.ImageDownLoadCallBack; |
| 16 | 19 | import com.cnlive.core.imageload.config.PriorityMode; |
| ... | ... | @@ -122,10 +125,13 @@ public class ImgActivity extends AppCompatActivity { |
| 122 | 125 | data.setShowImg(URL1); |
| 123 | 126 | iv_test1.setOnClickListener(view -> PreviewImageViewActivity.startPreviewPictrue(this, view, data)); |
| 124 | 127 | |
| 125 | - ImageLoader.with(this) | |
| 126 | - .url(URL1) | |
| 127 | - .scale(ScaleMode.FIT_CENTER) | |
| 128 | - .into(iv_test2); | |
| 128 | +// ImageLoader.with(this) | |
| 129 | +// .url(URL1) | |
| 130 | +// .scale(ScaleMode.FIT_CENTER) | |
| 131 | +// .into(iv_test2); | |
| 132 | + RequestOptions options = (new RequestOptions()).centerCrop().skipMemoryCache(true).format(DecodeFormat.PREFER_ARGB_8888).diskCacheStrategy(DiskCacheStrategy.RESOURCE); | |
| 133 | + Glide.with(this).load(URL1).apply(options).into(iv_test2); | |
| 134 | +// Glide.with(this).load(URL1).apply(new RequestOptions().centerCrop()).into(iv_test2); | |
| 129 | 135 | |
| 130 | 136 | // Glide.with(this).asGif().diskCacheStrategy(DiskCacheStrategy.RESOURCE).load(URL2).into(iv_test3); |
| 131 | 137 | ... | ... |
app/strike/src/main/res/drawable-hdpi/ic_download.png
0 → 100644
199 Bytes
app/strike/src/main/res/drawable-hdpi/ic_download_done.png
0 → 100644
218 Bytes
app/strike/src/main/res/layout/activity_demo_mvp_main.xml
| ... | ... | @@ -93,17 +93,45 @@ |
| 93 | 93 | android:text="动画特效" /> |
| 94 | 94 | |
| 95 | 95 | <Button |
| 96 | + android:id="@+id/btn_shanghai" | |
| 97 | + android:layout_width="match_parent" | |
| 98 | + android:layout_height="wrap_content" | |
| 99 | + android:layout_margin="5dp" | |
| 100 | + android:text="上海电商" /> | |
| 101 | + | |
| 102 | + <Button | |
| 103 | + android:id="@+id/btn_uid" | |
| 104 | + android:layout_width="match_parent" | |
| 105 | + android:layout_height="wrap_content" | |
| 106 | + android:layout_margin="5dp" | |
| 107 | + android:text="ARouter获取uid" /> | |
| 108 | + | |
| 109 | + <Button | |
| 96 | 110 | android:id="@+id/btn_cnPlayer" |
| 97 | 111 | android:layout_width="match_parent" |
| 98 | 112 | android:layout_height="wrap_content" |
| 99 | 113 | android:layout_margin="5dp" |
| 100 | 114 | android:text="点播" /> |
| 115 | + | |
| 101 | 116 | <Button |
| 102 | 117 | android:id="@+id/btn_livePlayer" |
| 103 | 118 | android:layout_width="match_parent" |
| 104 | 119 | android:layout_height="wrap_content" |
| 105 | 120 | android:layout_margin="5dp" |
| 106 | 121 | android:text="直播" /> |
| 122 | + | |
| 123 | + <Button | |
| 124 | + android:id="@+id/btn_exo" | |
| 125 | + android:layout_width="match_parent" | |
| 126 | + android:layout_height="wrap_content" | |
| 127 | + android:layout_margin="5dp" | |
| 128 | + android:text="Exoplayer" /> | |
| 129 | + <Button | |
| 130 | + android:id="@+id/btn_exo_home" | |
| 131 | + android:layout_width="match_parent" | |
| 132 | + android:layout_height="wrap_content" | |
| 133 | + android:layout_margin="5dp" | |
| 134 | + android:text="Exoplayer官方" /> | |
| 107 | 135 | <Button |
| 108 | 136 | android:id="@+id/btn_pay" |
| 109 | 137 | android:layout_width="match_parent" | ... | ... |
app/strike/src/main/res/layout/activity_explayer.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<layout xmlns:app="http://schemas.android.com/apk/res-auto"> | |
| 3 | + | |
| 4 | + <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 5 | + android:layout_width="match_parent" | |
| 6 | + android:layout_height="match_parent" | |
| 7 | + android:orientation="vertical"> | |
| 8 | + | |
| 9 | + <com.libs.video.video.exoplayer.CNLiveVideoView | |
| 10 | + android:id="@+id/playerView" | |
| 11 | + android:layout_width="match_parent" | |
| 12 | + android:layout_height="match_parent" | |
| 13 | + /> | |
| 14 | + </LinearLayout> | |
| 15 | +</layout> | |
| 0 | 16 | \ No newline at end of file | ... | ... |
app/strike/src/main/res/layout/activity_img.xml
| 1 | 1 | <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" |
| 2 | + xmlns:lb="http://schemas.android.com/apk/res-auto" | |
| 2 | 3 | android:layout_width="match_parent" |
| 3 | - android:layout_height="match_parent" | |
| 4 | - xmlns:lb="http://schemas.android.com/apk/res-auto"> | |
| 4 | + android:layout_height="match_parent"> | |
| 5 | 5 | |
| 6 | 6 | <LinearLayout |
| 7 | 7 | android:layout_width="match_parent" |
| ... | ... | @@ -11,40 +11,41 @@ |
| 11 | 11 | <TextView |
| 12 | 12 | android:layout_width="wrap_content" |
| 13 | 13 | android:layout_height="wrap_content" |
| 14 | - android:text="加载动效自定义:" | |
| 15 | - /> | |
| 14 | + android:text="加载动效自定义:" /> | |
| 15 | + | |
| 16 | 16 | <ImageView |
| 17 | 17 | android:id="@+id/iv_test1" |
| 18 | 18 | android:layout_width="300dp" |
| 19 | 19 | android:layout_height="300dp" |
| 20 | 20 | android:padding="10dp" /> |
| 21 | + | |
| 21 | 22 | <TextView |
| 22 | 23 | android:layout_width="wrap_content" |
| 23 | 24 | android:layout_height="wrap_content" |
| 24 | - android:text="放大缩小:" | |
| 25 | - /> | |
| 26 | - <ImageView | |
| 27 | - android:id="@+id/iv_test2" | |
| 28 | - android:layout_width="200dp" | |
| 29 | - android:layout_height="200dp" | |
| 30 | - android:padding="10dp" /> | |
| 25 | + android:text="放大缩小:" /> | |
| 26 | + | |
| 27 | + <ImageView | |
| 28 | + android:id="@+id/iv_test2" | |
| 29 | + android:layout_width="200dp" | |
| 30 | + android:layout_height="200dp" | |
| 31 | + android:padding="10dp" /> | |
| 31 | 32 | |
| 32 | 33 | <TextView |
| 33 | 34 | android:layout_width="wrap_content" |
| 34 | 35 | android:layout_height="wrap_content" |
| 35 | - android:text="网络gif:" | |
| 36 | - /> | |
| 36 | + android:text="网络gif:" /> | |
| 37 | 37 | |
| 38 | 38 | <ImageView |
| 39 | 39 | android:id="@+id/iv_test3" |
| 40 | 40 | android:layout_width="300dp" |
| 41 | 41 | android:layout_height="300dp" |
| 42 | 42 | android:padding="10dp" /> |
| 43 | + | |
| 43 | 44 | <TextView |
| 44 | 45 | android:layout_width="wrap_content" |
| 45 | 46 | android:layout_height="wrap_content" |
| 46 | - android:text="mipmap下的gif:" | |
| 47 | - /> | |
| 47 | + android:text="mipmap下的gif:" /> | |
| 48 | + | |
| 48 | 49 | <ImageView |
| 49 | 50 | android:id="@+id/iv_test4" |
| 50 | 51 | android:layout_width="300dp" |
| ... | ... | @@ -54,8 +55,8 @@ |
| 54 | 55 | <TextView |
| 55 | 56 | android:layout_width="wrap_content" |
| 56 | 57 | android:layout_height="wrap_content" |
| 57 | - android:text="mipmap下的图片:" | |
| 58 | - /> | |
| 58 | + android:text="mipmap下的图片:" /> | |
| 59 | + | |
| 59 | 60 | <ImageView |
| 60 | 61 | android:id="@+id/iv_test5" |
| 61 | 62 | android:layout_width="300dp" |
| ... | ... | @@ -65,8 +66,8 @@ |
| 65 | 66 | <TextView |
| 66 | 67 | android:layout_width="wrap_content" |
| 67 | 68 | android:layout_height="wrap_content" |
| 68 | - android:text="滤镜优先级设置" | |
| 69 | - /> | |
| 69 | + android:text="滤镜优先级设置" /> | |
| 70 | + | |
| 70 | 71 | <ImageView |
| 71 | 72 | android:id="@+id/iv_test6" |
| 72 | 73 | android:layout_width="300dp" |
| ... | ... | @@ -76,8 +77,8 @@ |
| 76 | 77 | <TextView |
| 77 | 78 | android:layout_width="wrap_content" |
| 78 | 79 | android:layout_height="wrap_content" |
| 79 | - android:text="素描" | |
| 80 | - /> | |
| 80 | + android:text="素描" /> | |
| 81 | + | |
| 81 | 82 | <ImageView |
| 82 | 83 | android:id="@+id/iv_test7" |
| 83 | 84 | android:layout_width="300dp" |
| ... | ... | @@ -87,8 +88,7 @@ |
| 87 | 88 | <TextView |
| 88 | 89 | android:layout_width="wrap_content" |
| 89 | 90 | android:layout_height="wrap_content" |
| 90 | - android:text="加载asserts下面的图片" | |
| 91 | - /> | |
| 91 | + android:text="加载asserts下面的图片" /> | |
| 92 | 92 | |
| 93 | 93 | <ImageView |
| 94 | 94 | android:id="@+id/iv_test8" |
| ... | ... | @@ -99,8 +99,8 @@ |
| 99 | 99 | <TextView |
| 100 | 100 | android:layout_width="wrap_content" |
| 101 | 101 | android:layout_height="wrap_content" |
| 102 | - android:text="加载raw下面的图片为圆" | |
| 103 | - /> | |
| 102 | + android:text="加载raw下面的图片为圆" /> | |
| 103 | + | |
| 104 | 104 | <ImageView |
| 105 | 105 | android:id="@+id/iv_test9" |
| 106 | 106 | android:layout_width="300dp" |
| ... | ... | @@ -110,8 +110,8 @@ |
| 110 | 110 | <TextView |
| 111 | 111 | android:layout_width="wrap_content" |
| 112 | 112 | android:layout_height="wrap_content" |
| 113 | - android:text="图片设置圆角" | |
| 114 | - /> | |
| 113 | + android:text="图片设置圆角" /> | |
| 114 | + | |
| 115 | 115 | <ImageView |
| 116 | 116 | android:id="@+id/iv_test10" |
| 117 | 117 | android:layout_width="300dp" |
| ... | ... | @@ -121,13 +121,13 @@ |
| 121 | 121 | <TextView |
| 122 | 122 | android:layout_width="wrap_content" |
| 123 | 123 | android:layout_height="wrap_content" |
| 124 | - android:text="图片下载显示" | |
| 125 | - /> | |
| 124 | + android:text="图片下载显示" /> | |
| 125 | + | |
| 126 | 126 | <Button |
| 127 | 127 | android:id="@+id/down" |
| 128 | 128 | android:layout_width="wrap_content" |
| 129 | 129 | android:layout_height="wrap_content" |
| 130 | - android:text="下载"/> | |
| 130 | + android:text="下载" /> | |
| 131 | 131 | |
| 132 | 132 | <ImageView |
| 133 | 133 | android:id="@+id/iv_test11" |
| ... | ... | @@ -138,45 +138,42 @@ |
| 138 | 138 | <TextView |
| 139 | 139 | android:layout_width="wrap_content" |
| 140 | 140 | android:layout_height="wrap_content" |
| 141 | - android:text="直接代码设置,五边形"/> | |
| 141 | + android:text="直接代码设置,五边形" /> | |
| 142 | 142 | |
| 143 | 143 | <com.cnlive.core.imageload.util.MultiView |
| 144 | 144 | android:layout_width="300dp" |
| 145 | 145 | android:layout_height="300dp" |
| 146 | 146 | android:layout_margin="30dp" |
| 147 | - lb:angleCount="5" | |
| 148 | - lb:type="multi" | |
| 149 | 147 | android:src="@mipmap/timgs" |
| 150 | - /> | |
| 148 | + lb:angleCount="5" | |
| 149 | + lb:shapeType="polygon" /> | |
| 151 | 150 | |
| 152 | 151 | <TextView |
| 153 | 152 | android:layout_width="wrap_content" |
| 154 | 153 | android:layout_height="wrap_content" |
| 155 | - android:text="直接代码设置,设置圆角"/> | |
| 154 | + android:text="直接代码设置,设置圆角" /> | |
| 156 | 155 | |
| 157 | 156 | <com.cnlive.core.imageload.util.MultiView |
| 158 | 157 | android:layout_width="200dp" |
| 159 | 158 | android:layout_height="200dp" |
| 160 | - android:layout_margin="30dp" | |
| 161 | 159 | android:layout_gravity="center" |
| 162 | - lb:borderRadius="10dp" | |
| 163 | - lb:type="round" | |
| 160 | + android:layout_margin="30dp" | |
| 164 | 161 | android:src="@mipmap/timgs" |
| 165 | - /> | |
| 162 | + lb:borderRadius="10dp" | |
| 163 | + lb:shapeType="round" /> | |
| 166 | 164 | |
| 167 | 165 | <TextView |
| 168 | 166 | android:layout_width="wrap_content" |
| 169 | 167 | android:layout_height="wrap_content" |
| 170 | - android:text="直接代码设置圆"/> | |
| 168 | + android:text="直接代码设置圆" /> | |
| 171 | 169 | |
| 172 | 170 | <com.cnlive.core.imageload.util.MultiView |
| 173 | - android:layout_margin="30dp" | |
| 174 | 171 | android:layout_width="200dp" |
| 175 | 172 | android:layout_height="200dp" |
| 176 | 173 | android:layout_gravity="center" |
| 174 | + android:layout_margin="30dp" | |
| 177 | 175 | android:src="@mipmap/timgs" |
| 178 | - lb:type="circle" | |
| 179 | - /> | |
| 176 | + lb:shapeType="circle" /> | |
| 180 | 177 | |
| 181 | 178 | </LinearLayout> |
| 182 | 179 | </ScrollView> | ... | ... |
app/strike/src/main/res/layout/player_activity.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<!-- Copyright (C) 2016 The Android Open Source Project | |
| 3 | + | |
| 4 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + you may not use this file except in compliance with the License. | |
| 6 | + You may obtain a copy of the License at | |
| 7 | + | |
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + | |
| 10 | + Unless required by applicable law or agreed to in writing, software | |
| 11 | + distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + See the License for the specific language governing permissions and | |
| 14 | + limitations under the License. | |
| 15 | +--> | |
| 16 | +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 17 | + xmlns:tools="http://schemas.android.com/tools" | |
| 18 | + android:id="@+id/root" | |
| 19 | + android:layout_width="match_parent" | |
| 20 | + android:layout_height="match_parent" | |
| 21 | + android:keepScreenOn="true"> | |
| 22 | + | |
| 23 | + <com.google.android.exoplayer2.ui.PlayerView android:id="@+id/player_view" | |
| 24 | + android:layout_width="match_parent" | |
| 25 | + android:layout_height="match_parent"/> | |
| 26 | + | |
| 27 | + <LinearLayout | |
| 28 | + android:layout_width="match_parent" | |
| 29 | + android:layout_height="wrap_content" | |
| 30 | + android:background="#88000000" | |
| 31 | + android:orientation="vertical"> | |
| 32 | + | |
| 33 | + <TextView android:id="@+id/debug_text_view" | |
| 34 | + android:layout_width="match_parent" | |
| 35 | + android:layout_height="wrap_content" | |
| 36 | + android:paddingLeft="4dp" | |
| 37 | + android:paddingRight="4dp" | |
| 38 | + android:textSize="10sp" | |
| 39 | + tools:ignore="SmallSp"/> | |
| 40 | + | |
| 41 | + <LinearLayout android:id="@+id/controls_root" | |
| 42 | + android:layout_width="match_parent" | |
| 43 | + android:layout_height="wrap_content" | |
| 44 | + android:orientation="horizontal" | |
| 45 | + android:visibility="gone"> | |
| 46 | + | |
| 47 | + <Button android:id="@+id/select_tracks_button" | |
| 48 | + android:layout_width="wrap_content" | |
| 49 | + android:layout_height="wrap_content" | |
| 50 | + android:text="@string/track_selection_title" | |
| 51 | + android:enabled="false"/> | |
| 52 | + | |
| 53 | + </LinearLayout> | |
| 54 | + | |
| 55 | + </LinearLayout> | |
| 56 | + | |
| 57 | +</FrameLayout> | ... | ... |
app/strike/src/main/res/layout/sample_chooser_activity.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="UTF-8"?> | |
| 2 | +<!-- Copyright (C) 2016 The Android Open Source Project | |
| 3 | + | |
| 4 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + you may not use this file except in compliance with the License. | |
| 6 | + You may obtain a copy of the License at | |
| 7 | + | |
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + | |
| 10 | + Unless required by applicable law or agreed to in writing, software | |
| 11 | + distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + See the License for the specific language governing permissions and | |
| 14 | + limitations under the License. | |
| 15 | +--> | |
| 16 | +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 17 | + android:layout_width="match_parent" | |
| 18 | + android:layout_height="match_parent" | |
| 19 | + android:orientation="vertical"> | |
| 20 | + | |
| 21 | + <ExpandableListView android:id="@+id/sample_list" | |
| 22 | + android:layout_width="match_parent" | |
| 23 | + android:layout_height="match_parent"/> | |
| 24 | + | |
| 25 | +</LinearLayout> | ... | ... |
app/strike/src/main/res/layout/sample_list_item.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<!-- Copyright (C) 2018 The Android Open Source Project | |
| 3 | + | |
| 4 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + you may not use this file except in compliance with the License. | |
| 6 | + You may obtain a copy of the License at | |
| 7 | + | |
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + | |
| 10 | + Unless required by applicable law or agreed to in writing, software | |
| 11 | + distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + See the License for the specific language governing permissions and | |
| 14 | + limitations under the License. | |
| 15 | +--> | |
| 16 | +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 17 | + android:layout_width="match_parent" | |
| 18 | + android:layout_height="wrap_content" | |
| 19 | + android:paddingStart="12dp" | |
| 20 | + android:paddingEnd="12dp" | |
| 21 | + android:gravity="center_vertical" | |
| 22 | + android:orientation="horizontal"> | |
| 23 | + | |
| 24 | + <TextView android:id="@+id/sample_title" | |
| 25 | + android:layout_width="0dp" | |
| 26 | + android:layout_height="wrap_content" | |
| 27 | + android:layout_weight="1" | |
| 28 | + android:gravity="center_vertical" | |
| 29 | + android:minHeight="?android:attr/listPreferredItemHeightSmall" | |
| 30 | + android:textAppearance="?android:attr/textAppearanceListItemSmall"/> | |
| 31 | + | |
| 32 | + <ImageButton android:id="@+id/download_button" | |
| 33 | + android:layout_width="wrap_content" | |
| 34 | + android:layout_height="wrap_content" | |
| 35 | + android:contentDescription="@string/exo_download_description" | |
| 36 | + android:background="@android:color/transparent"/> | |
| 37 | + | |
| 38 | +</LinearLayout> | ... | ... |
app/strike/src/main/res/layout/track_selection_dialog.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<!-- Copyright (C) 2018 The Android Open Source Project | |
| 3 | + | |
| 4 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + you may not use this file except in compliance with the License. | |
| 6 | + You may obtain a copy of the License at | |
| 7 | + | |
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + | |
| 10 | + Unless required by applicable law or agreed to in writing, software | |
| 11 | + distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + See the License for the specific language governing permissions and | |
| 14 | + limitations under the License. | |
| 15 | +--> | |
| 16 | +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 17 | + xmlns:app="http://schemas.android.com/apk/res-auto" | |
| 18 | + android:orientation="vertical" | |
| 19 | + android:layout_width="match_parent" | |
| 20 | + android:layout_height="match_parent"> | |
| 21 | + | |
| 22 | + <androidx.viewpager.widget.ViewPager | |
| 23 | + android:id="@+id/track_selection_dialog_view_pager" | |
| 24 | + android:layout_width="match_parent" | |
| 25 | + android:layout_height="0dp" | |
| 26 | + android:layout_weight="1"> | |
| 27 | + | |
| 28 | + <com.google.android.material.tabs.TabLayout | |
| 29 | + android:id="@+id/track_selection_dialog_tab_layout" | |
| 30 | + android:layout_width="match_parent" | |
| 31 | + android:layout_height="wrap_content" | |
| 32 | + app:tabGravity="fill" | |
| 33 | + app:tabMode="fixed"/> | |
| 34 | + | |
| 35 | + </androidx.viewpager.widget.ViewPager> | |
| 36 | + | |
| 37 | + <LinearLayout | |
| 38 | + android:orientation="horizontal" | |
| 39 | + android:layout_width="match_parent" | |
| 40 | + android:layout_height="wrap_content" | |
| 41 | + android:gravity="end"> | |
| 42 | + | |
| 43 | + <Button | |
| 44 | + android:id="@+id/track_selection_dialog_cancel_button" | |
| 45 | + android:layout_width="wrap_content" | |
| 46 | + android:layout_height="wrap_content" | |
| 47 | + android:text="@android:string/cancel" | |
| 48 | + style="?android:attr/borderlessButtonStyle"/> | |
| 49 | + | |
| 50 | + <Button | |
| 51 | + android:id="@+id/track_selection_dialog_ok_button" | |
| 52 | + android:layout_width="wrap_content" | |
| 53 | + android:layout_height="wrap_content" | |
| 54 | + android:text="@android:string/ok" | |
| 55 | + style="?android:attr/borderlessButtonStyle"/> | |
| 56 | + | |
| 57 | + </LinearLayout> | |
| 58 | + | |
| 59 | +</LinearLayout> | ... | ... |
app/strike/src/main/res/menu/sample_chooser_menu.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<!-- Copyright (C) 2018 The Android Open Source Project | |
| 3 | + | |
| 4 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + you may not use this file except in compliance with the License. | |
| 6 | + You may obtain a copy of the License at | |
| 7 | + | |
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + | |
| 10 | + Unless required by applicable law or agreed to in writing, software | |
| 11 | + distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + See the License for the specific language governing permissions and | |
| 14 | + limitations under the License. | |
| 15 | +--> | |
| 16 | +<menu xmlns:android="http://schemas.android.com/apk/res/android" | |
| 17 | + xmlns:app="http://schemas.android.com/apk/res-auto"> | |
| 18 | + <item android:id="@+id/prefer_extension_decoders" | |
| 19 | + android:title="@string/prefer_extension_decoders" | |
| 20 | + android:checkable="true" | |
| 21 | + app:showAsAction="never"/> | |
| 22 | + <item android:id="@+id/random_abr" | |
| 23 | + android:title="@string/random_abr" | |
| 24 | + android:checkable="true" | |
| 25 | + app:showAsAction="never"/> | |
| 26 | + <item android:id="@+id/tunneling" | |
| 27 | + android:title="@string/tunneling" | |
| 28 | + android:checkable="true" | |
| 29 | + app:showAsAction="never"/> | |
| 30 | +</menu> | ... | ... |
app/strike/src/main/res/values/strings.xml
| 1 | -<resources> | |
| 2 | - <!-- <string name="app_name">strike</string>--> | |
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<!-- Copyright (C) 2016 The Android Open Source Project | |
| 3 | + | |
| 4 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | + you may not use this file except in compliance with the License. | |
| 6 | + You may obtain a copy of the License at | |
| 7 | + | |
| 8 | + http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | + | |
| 10 | + Unless required by applicable law or agreed to in writing, software | |
| 11 | + distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | + See the License for the specific language governing permissions and | |
| 14 | + limitations under the License. | |
| 15 | +--> | |
| 16 | +<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> | |
| 17 | + | |
| 18 | + <string name="application_name">ExoPlayer</string> | |
| 19 | + | |
| 20 | + <string name="track_selection_title">Select tracks</string> | |
| 21 | + | |
| 22 | + <string name="unexpected_intent_action">Unexpected intent action: <xliff:g id="action">%1$s</xliff:g></string> | |
| 23 | + | |
| 24 | + <string name="error_cleartext_not_permitted">Cleartext traffic not permitted</string> | |
| 25 | + | |
| 26 | + <string name="error_generic">Playback failed</string> | |
| 27 | + | |
| 28 | + <string name="error_unrecognized_abr_algorithm">Unrecognized ABR algorithm</string> | |
| 29 | + | |
| 30 | + <string name="error_unrecognized_stereo_mode">Unrecognized stereo mode</string> | |
| 31 | + | |
| 32 | + <string name="error_drm_unsupported_before_api_18">Protected content not supported on API levels below 18</string> | |
| 33 | + | |
| 34 | + <string name="error_drm_unsupported_scheme">This device does not support the required DRM scheme</string> | |
| 35 | + | |
| 36 | + <string name="error_drm_unknown">An unknown DRM error occurred</string> | |
| 37 | + | |
| 38 | + <string name="error_no_decoder">This device does not provide a decoder for <xliff:g id="mime_type">%1$s</xliff:g></string> | |
| 39 | + | |
| 40 | + <string name="error_no_secure_decoder">This device does not provide a secure decoder for <xliff:g id="mime_type">%1$s</xliff:g></string> | |
| 41 | + | |
| 42 | + <string name="error_querying_decoders">Unable to query device decoders</string> | |
| 43 | + | |
| 44 | + <string name="error_instantiating_decoder">Unable to instantiate decoder <xliff:g id="decoder_name">%1$s</xliff:g></string> | |
| 45 | + | |
| 46 | + <string name="error_unsupported_video">Media includes video tracks, but none are playable by this device</string> | |
| 47 | + | |
| 48 | + <string name="error_unsupported_audio">Media includes audio tracks, but none are playable by this device</string> | |
| 49 | + | |
| 50 | + <string name="storage_permission_denied">Permission to access storage was denied</string> | |
| 51 | + | |
| 52 | + <string name="sample_list_load_error">One or more sample lists failed to load</string> | |
| 53 | + | |
| 54 | + <string name="ima_not_loaded">Playing sample without ads, as the IMA extension was not loaded</string> | |
| 55 | + | |
| 56 | + <string name="unsupported_ads_in_concatenation">Playing sample without ads, as ads are not supported in concatenations</string> | |
| 57 | + | |
| 58 | + <string name="download_start_error">Failed to start download</string> | |
| 59 | + | |
| 60 | + <string name="download_playlist_unsupported">This demo app does not support downloading playlists</string> | |
| 61 | + | |
| 62 | + <string name="download_drm_unsupported">This demo app does not support downloading protected content</string> | |
| 63 | + | |
| 64 | + <string name="download_scheme_unsupported">This demo app only supports downloading http streams</string> | |
| 65 | + | |
| 66 | + <string name="download_live_unsupported">This demo app does not support downloading live content</string> | |
| 67 | + | |
| 68 | + <string name="download_ads_unsupported">IMA does not support offline ads</string> | |
| 69 | + | |
| 70 | + <string name="prefer_extension_decoders">Prefer extension decoders</string> | |
| 71 | + | |
| 72 | + <string name="random_abr">Enable random ABR</string> | |
| 73 | + | |
| 74 | + <string name="tunneling">Request multimedia tunneling</string> | |
| 3 | 75 | |
| 4 | 76 | </resources> | ... | ... |
app/strike/src/main/res/values/styles.xml
| ... | ... | @@ -9,4 +9,17 @@ |
| 9 | 9 | <!-- <item name="colorAccent">@color/colorAccent</item>--> |
| 10 | 10 | </style> |
| 11 | 11 | |
| 12 | + | |
| 13 | + | |
| 14 | + <style name="TrackSelectionDialogThemeOverlay" parent="ThemeOverlay.AppCompat.Dialog.Alert"> | |
| 15 | + <item name="windowNoTitle">false</item> | |
| 16 | + </style> | |
| 17 | + | |
| 18 | + <style name="PlayerTheme" parent="Theme.AppCompat.NoActionBar"> | |
| 19 | + <item name="android:windowBackground">@android:color/black</item> | |
| 20 | + </style> | |
| 21 | + | |
| 22 | + <style name="PlayerTheme.Spherical"> | |
| 23 | + <item name="surface_type">spherical_gl_surface_view</item> | |
| 24 | + </style> | |
| 12 | 25 | </resources> | ... | ... |
app/strike/src/main/res/xml/app_files.xml
0 → 100644
build.gradle
| ... | ... | @@ -27,6 +27,12 @@ allprojects { |
| 27 | 27 | // configurations.all { |
| 28 | 28 | ////// resolutionStrategy.force 'androidx.appcompat:appcompat:1.1.0' |
| 29 | 29 | // } |
| 30 | + //添加java编译指令,防止引用时,形参变成var1 | |
| 31 | + gradle.projectsEvaluated { | |
| 32 | + tasks.withType(JavaCompile) { | |
| 33 | + options.compilerArgs.add('-parameters') | |
| 34 | + } | |
| 35 | + } | |
| 30 | 36 | afterEvaluate { |
| 31 | 37 | if (project.parent == null || project.parent == rootProject) return |
| 32 | 38 | |
| ... | ... | @@ -129,6 +135,10 @@ allprojects { |
| 129 | 135 | url maven.mixedUrl |
| 130 | 136 | credentials { username = nexusId; password = nexusPw } |
| 131 | 137 | } |
| 138 | + maven { | |
| 139 | + url 'http://android.nexus.cnlive.com/repository/cnlive-shop/' | |
| 140 | + credentials { username = 'shop'; password = 'ShangHa1' } | |
| 141 | + } | |
| 132 | 142 | //------------------ 资源库配置 END------------------ |
| 133 | 143 | maven { url 'https://dl.bintray.com/umsdk/release' } |
| 134 | 144 | //关联微博 |
| ... | ... | @@ -139,6 +149,7 @@ allprojects { |
| 139 | 149 | maven { |
| 140 | 150 | url 'https://dl.bintray.com/granzon/cnliveLibs' |
| 141 | 151 | } |
| 152 | + | |
| 142 | 153 | maven { |
| 143 | 154 | url "https://jitpack.io" |
| 144 | 155 | } | ... | ... |
cloud/user/src/main/java/com/cnlive/cloud/user/router/user/CNLiveUserServiceImpl.java
| ... | ... | @@ -6,6 +6,7 @@ import android.text.TextUtils; |
| 6 | 6 | import com.alibaba.android.arouter.facade.annotation.Route; |
| 7 | 7 | import com.alibaba.android.arouter.launcher.ARouter; |
| 8 | 8 | import com.cnlive.cloud.user.auth.UserService; |
| 9 | +import com.cnlive.core.libs.base.arouter.RouterConst; | |
| 9 | 10 | import com.cnlive.core.libs.base.arouter.user.CNLiveUserService; |
| 10 | 11 | import com.cnlive.core.libs.base.arouter.user.OpenUserInfo; |
| 11 | 12 | |
| ... | ... | @@ -14,7 +15,7 @@ import com.cnlive.core.libs.base.arouter.user.OpenUserInfo; |
| 14 | 15 | * |
| 15 | 16 | * @author ys |
| 16 | 17 | */ |
| 17 | -@Route(path = CNLiveUserService.PATH, name = CNLiveUserService.NAME) | |
| 18 | +@Route(path = RouterConst.OPEN_USER_SERVICE_PATH, name = RouterConst.OPEN_USER_SERVICE_NAME) | |
| 18 | 19 | public class CNLiveUserServiceImpl implements CNLiveUserService { |
| 19 | 20 | |
| 20 | 21 | @Override | ... | ... |
cloud/video/build.gradle
| ... | ... | @@ -13,4 +13,6 @@ dependencies { |
| 13 | 13 | implementation "com.umeng.umsdk:analytics:8.0.0" |
| 14 | 14 | implementation "com.umeng.umsdk:common:2.0.0" |
| 15 | 15 | implementation project(path: ':core:network') |
| 16 | + api 'com.google.android.exoplayer:exoplayer:2.11.0' | |
| 17 | + api 'com.google.android.exoplayer:extension-rtmp:2.11.0' | |
| 16 | 18 | } | ... | ... |
cloud/video/src/main/AndroidManifest.xml
cloud/video/src/main/assets/media.exolist.json
0 → 100644
| 1 | +[ | |
| 2 | + { | |
| 3 | + "name": "YouTube DASH", | |
| 4 | + "samples": [ | |
| 5 | + { | |
| 6 | + "name": "Google Glass (MP4,H264)", | |
| 7 | + "uri": "https://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7.8506521BFC350652163895D4C26DEE124209AA9E&key=ik0", | |
| 8 | + "extension": "mpd" | |
| 9 | + }, | |
| 10 | + { | |
| 11 | + "name": "Google Play (MP4,H264)", | |
| 12 | + "uri": "https://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=A2716F75795F5D2AF0E88962FFCD10DB79384F29.84308FF04844498CE6FBCE4731507882B8307798&key=ik0", | |
| 13 | + "extension": "mpd" | |
| 14 | + }, | |
| 15 | + { | |
| 16 | + "name": "Google Glass (WebM,VP9)", | |
| 17 | + "uri": "https://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,webm2_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=249B04F79E984D7F86B4D8DB48AE6FAF41C17AB3.7B9F0EC0505E1566E59B8E488E9419F253DDF413&key=ik0", | |
| 18 | + "extension": "mpd" | |
| 19 | + }, | |
| 20 | + { | |
| 21 | + "name": "Google Play (WebM,VP9)", | |
| 22 | + "uri": "https://www.youtube.com/api/manifest/dash/id/3aa39fa2cc27967f/source/youtube?as=fmp4_audio_clear,webm2_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=B1C2A74783AC1CC4865EB312D7DD2D48230CC9FD.BD153B9882175F1F94BFE5141A5482313EA38E8D&key=ik0", | |
| 23 | + "extension": "mpd" | |
| 24 | + } | |
| 25 | + ] | |
| 26 | + }, | |
| 27 | + { | |
| 28 | + "name": "Widevine DASH Policy Tests (GTS)", | |
| 29 | + "samples": [ | |
| 30 | + { | |
| 31 | + "name": "WV: HDCP not specified", | |
| 32 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd", | |
| 33 | + "drm_scheme": "widevine", | |
| 34 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=d286538032258a1c&provider=widevine_test" | |
| 35 | + }, | |
| 36 | + { | |
| 37 | + "name": "WV: HDCP not required", | |
| 38 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd", | |
| 39 | + "drm_scheme": "widevine", | |
| 40 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=48fcc369939ac96c&provider=widevine_test" | |
| 41 | + }, | |
| 42 | + { | |
| 43 | + "name": "WV: HDCP required", | |
| 44 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd", | |
| 45 | + "drm_scheme": "widevine", | |
| 46 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=e06c39f1151da3df&provider=widevine_test" | |
| 47 | + }, | |
| 48 | + { | |
| 49 | + "name": "WV: Secure video path required (MP4,H264)", | |
| 50 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd", | |
| 51 | + "drm_scheme": "widevine", | |
| 52 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=0894c7c8719b28a0&provider=widevine_test" | |
| 53 | + }, | |
| 54 | + { | |
| 55 | + "name": "WV: Secure video path required (WebM,VP9)", | |
| 56 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears.mpd", | |
| 57 | + "drm_scheme": "widevine", | |
| 58 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=0894c7c8719b28a0&provider=widevine_test" | |
| 59 | + }, | |
| 60 | + { | |
| 61 | + "name": "WV: Secure video path required (MP4,H265)", | |
| 62 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears.mpd", | |
| 63 | + "drm_scheme": "widevine", | |
| 64 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=0894c7c8719b28a0&provider=widevine_test" | |
| 65 | + }, | |
| 66 | + { | |
| 67 | + "name": "WV: HDCP + secure video path required", | |
| 68 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd", | |
| 69 | + "drm_scheme": "widevine", | |
| 70 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=efd045b1eb61888a&provider=widevine_test" | |
| 71 | + }, | |
| 72 | + { | |
| 73 | + "name": "WV: 30s license duration (fails at ~30s)", | |
| 74 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd", | |
| 75 | + "drm_scheme": "widevine", | |
| 76 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=f9a34cab7b05881a&provider=widevine_test" | |
| 77 | + } | |
| 78 | + ] | |
| 79 | + }, | |
| 80 | + { | |
| 81 | + "name": "Widevine HDCP Capabilities Tests", | |
| 82 | + "samples": [ | |
| 83 | + { | |
| 84 | + "name": "WV: HDCP: None (not required)", | |
| 85 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd", | |
| 86 | + "drm_scheme": "widevine", | |
| 87 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=HDCP_None&provider=widevine_test" | |
| 88 | + }, | |
| 89 | + { | |
| 90 | + "name": "WV: HDCP: 1.0 required", | |
| 91 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd", | |
| 92 | + "drm_scheme": "widevine", | |
| 93 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=HDCP_V1&provider=widevine_test" | |
| 94 | + }, | |
| 95 | + { | |
| 96 | + "name": "WV: HDCP: 2.0 required", | |
| 97 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd", | |
| 98 | + "drm_scheme": "widevine", | |
| 99 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=HDCP_V2&provider=widevine_test" | |
| 100 | + }, | |
| 101 | + { | |
| 102 | + "name": "WV: HDCP: 2.1 required", | |
| 103 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd", | |
| 104 | + "drm_scheme": "widevine", | |
| 105 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=HDCP_V2_1&provider=widevine_test" | |
| 106 | + }, | |
| 107 | + { | |
| 108 | + "name": "WV: HDCP: 2.2 required", | |
| 109 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd", | |
| 110 | + "drm_scheme": "widevine", | |
| 111 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=HDCP_V2_2&provider=widevine_test" | |
| 112 | + }, | |
| 113 | + { | |
| 114 | + "name": "WV: HDCP: No digital output", | |
| 115 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd", | |
| 116 | + "drm_scheme": "widevine", | |
| 117 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?video_id=HDCP_NO_DIGTAL_OUTPUT&provider=widevine_test" | |
| 118 | + } | |
| 119 | + ] | |
| 120 | + }, | |
| 121 | + { | |
| 122 | + "name": "Widevine DASH: MP4,H264", | |
| 123 | + "samples": [ | |
| 124 | + { | |
| 125 | + "name": "WV: Clear SD & HD (MP4,H264)", | |
| 126 | + "uri": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd" | |
| 127 | + }, | |
| 128 | + { | |
| 129 | + "name": "WV: Clear SD (MP4,H264)", | |
| 130 | + "uri": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears_sd.mpd" | |
| 131 | + }, | |
| 132 | + { | |
| 133 | + "name": "WV: Clear HD (MP4,H264)", | |
| 134 | + "uri": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears_hd.mpd" | |
| 135 | + }, | |
| 136 | + { | |
| 137 | + "name": "WV: Clear UHD (MP4,H264)", | |
| 138 | + "uri": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears_uhd.mpd" | |
| 139 | + }, | |
| 140 | + { | |
| 141 | + "name": "WV: Secure SD & HD (cenc,MP4,H264)", | |
| 142 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd", | |
| 143 | + "drm_scheme": "widevine", | |
| 144 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 145 | + }, | |
| 146 | + { | |
| 147 | + "name": "WV: Secure SD (cenc,MP4,H264)", | |
| 148 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_sd.mpd", | |
| 149 | + "drm_scheme": "widevine", | |
| 150 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 151 | + }, | |
| 152 | + { | |
| 153 | + "name": "WV: Secure HD (cenc,MP4,H264)", | |
| 154 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_hd.mpd", | |
| 155 | + "drm_scheme": "widevine", | |
| 156 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 157 | + }, | |
| 158 | + { | |
| 159 | + "name": "WV: Secure UHD (cenc,MP4,H264)", | |
| 160 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_uhd.mpd", | |
| 161 | + "drm_scheme": "widevine", | |
| 162 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 163 | + }, | |
| 164 | + { | |
| 165 | + "name": "WV: Secure SD & HD (cbc1,MP4,H264)", | |
| 166 | + "uri": "https://storage.googleapis.com/wvmedia/cbc1/h264/tears/tears_aes_cbc1.mpd", | |
| 167 | + "drm_scheme": "widevine", | |
| 168 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 169 | + }, | |
| 170 | + { | |
| 171 | + "name": "WV: Secure SD (cbc1,MP4,H264)", | |
| 172 | + "uri": "https://storage.googleapis.com/wvmedia/cbc1/h264/tears/tears_aes_cbc1_sd.mpd", | |
| 173 | + "drm_scheme": "widevine", | |
| 174 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 175 | + }, | |
| 176 | + { | |
| 177 | + "name": "WV: Secure HD (cbc1,MP4,H264)", | |
| 178 | + "uri": "https://storage.googleapis.com/wvmedia/cbc1/h264/tears/tears_aes_cbc1_hd.mpd", | |
| 179 | + "drm_scheme": "widevine", | |
| 180 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 181 | + }, | |
| 182 | + { | |
| 183 | + "name": "WV: Secure UHD (cbc1,MP4,H264)", | |
| 184 | + "uri": "https://storage.googleapis.com/wvmedia/cbc1/h264/tears/tears_aes_cbc1_uhd.mpd", | |
| 185 | + "drm_scheme": "widevine", | |
| 186 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 187 | + }, | |
| 188 | + { | |
| 189 | + "name": "WV: Secure SD & HD (cbcs,MP4,H264)", | |
| 190 | + "uri": "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs.mpd", | |
| 191 | + "drm_scheme": "widevine", | |
| 192 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 193 | + }, | |
| 194 | + { | |
| 195 | + "name": "WV: Secure SD (cbcs,MP4,H264)", | |
| 196 | + "uri": "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs_sd.mpd", | |
| 197 | + "drm_scheme": "widevine", | |
| 198 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 199 | + }, | |
| 200 | + { | |
| 201 | + "name": "WV: Secure HD (cbcs,MP4,H264)", | |
| 202 | + "uri": "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs_hd.mpd", | |
| 203 | + "drm_scheme": "widevine", | |
| 204 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 205 | + }, | |
| 206 | + { | |
| 207 | + "name": "WV: Secure UHD (cbcs,MP4,H264)", | |
| 208 | + "uri": "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs_uhd.mpd", | |
| 209 | + "drm_scheme": "widevine", | |
| 210 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 211 | + } | |
| 212 | + ] | |
| 213 | + }, | |
| 214 | + { | |
| 215 | + "name": "Widevine DASH: WebM,VP9", | |
| 216 | + "samples": [ | |
| 217 | + { | |
| 218 | + "name": "WV: Clear SD & HD (WebM,VP9)", | |
| 219 | + "uri": "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears.mpd" | |
| 220 | + }, | |
| 221 | + { | |
| 222 | + "name": "WV: Clear SD (WebM,VP9)", | |
| 223 | + "uri": "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears_sd.mpd" | |
| 224 | + }, | |
| 225 | + { | |
| 226 | + "name": "WV: Clear HD (WebM,VP9)", | |
| 227 | + "uri": "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears_hd.mpd" | |
| 228 | + }, | |
| 229 | + { | |
| 230 | + "name": "WV: Clear UHD (WebM,VP9)", | |
| 231 | + "uri": "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears_uhd.mpd" | |
| 232 | + }, | |
| 233 | + { | |
| 234 | + "name": "WV: Secure Fullsample SD & HD (WebM,VP9)", | |
| 235 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears.mpd", | |
| 236 | + "drm_scheme": "widevine", | |
| 237 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 238 | + }, | |
| 239 | + { | |
| 240 | + "name": "WV: Secure Fullsample SD (WebM,VP9)", | |
| 241 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_sd.mpd", | |
| 242 | + "drm_scheme": "widevine", | |
| 243 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 244 | + }, | |
| 245 | + { | |
| 246 | + "name": "WV: Secure Fullsample HD (WebM,VP9)", | |
| 247 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_hd.mpd", | |
| 248 | + "drm_scheme": "widevine", | |
| 249 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 250 | + }, | |
| 251 | + { | |
| 252 | + "name": "WV: Secure Fullsample UHD (WebM,VP9)", | |
| 253 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_uhd.mpd", | |
| 254 | + "drm_scheme": "widevine", | |
| 255 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 256 | + }, | |
| 257 | + { | |
| 258 | + "name": "WV: Secure Subsample SD & HD (WebM,VP9)", | |
| 259 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears.mpd", | |
| 260 | + "drm_scheme": "widevine", | |
| 261 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 262 | + }, | |
| 263 | + { | |
| 264 | + "name": "WV: Secure Subsample SD (WebM,VP9)", | |
| 265 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears_sd.mpd", | |
| 266 | + "drm_scheme": "widevine", | |
| 267 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 268 | + }, | |
| 269 | + { | |
| 270 | + "name": "WV: Secure Subsample HD (WebM,VP9)", | |
| 271 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears_hd.mpd", | |
| 272 | + "drm_scheme": "widevine", | |
| 273 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 274 | + }, | |
| 275 | + { | |
| 276 | + "name": "WV: Secure Subsample UHD (WebM,VP9)", | |
| 277 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears_uhd.mpd", | |
| 278 | + "drm_scheme": "widevine", | |
| 279 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 280 | + } | |
| 281 | + ] | |
| 282 | + }, | |
| 283 | + { | |
| 284 | + "name": "Widevine DASH: MP4,H265", | |
| 285 | + "samples": [ | |
| 286 | + { | |
| 287 | + "name": "WV: Clear SD & HD (MP4,H265)", | |
| 288 | + "uri": "https://storage.googleapis.com/wvmedia/clear/hevc/tears/tears.mpd" | |
| 289 | + }, | |
| 290 | + { | |
| 291 | + "name": "WV: Clear SD (MP4,H265)", | |
| 292 | + "uri": "https://storage.googleapis.com/wvmedia/clear/hevc/tears/tears_sd.mpd" | |
| 293 | + }, | |
| 294 | + { | |
| 295 | + "name": "WV: Clear HD (MP4,H265)", | |
| 296 | + "uri": "https://storage.googleapis.com/wvmedia/clear/hevc/tears/tears_hd.mpd" | |
| 297 | + }, | |
| 298 | + { | |
| 299 | + "name": "WV: Clear UHD (MP4,H265)", | |
| 300 | + "uri": "https://storage.googleapis.com/wvmedia/clear/hevc/tears/tears_uhd.mpd" | |
| 301 | + }, | |
| 302 | + { | |
| 303 | + "name": "WV: Secure SD & HD (MP4,H265)", | |
| 304 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears.mpd", | |
| 305 | + "drm_scheme": "widevine", | |
| 306 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 307 | + }, | |
| 308 | + { | |
| 309 | + "name": "WV: Secure SD (MP4,H265)", | |
| 310 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears_sd.mpd", | |
| 311 | + "drm_scheme": "widevine", | |
| 312 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 313 | + }, | |
| 314 | + { | |
| 315 | + "name": "WV: Secure HD (MP4,H265)", | |
| 316 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears_hd.mpd", | |
| 317 | + "drm_scheme": "widevine", | |
| 318 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 319 | + }, | |
| 320 | + { | |
| 321 | + "name": "WV: Secure UHD (MP4,H265)", | |
| 322 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/hevc/tears/tears_uhd.mpd", | |
| 323 | + "drm_scheme": "widevine", | |
| 324 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 325 | + } | |
| 326 | + ] | |
| 327 | + }, | |
| 328 | + { | |
| 329 | + "name": "SmoothStreaming", | |
| 330 | + "samples": [ | |
| 331 | + { | |
| 332 | + "name": "Super speed", | |
| 333 | + "uri": "https://playready.directtaps.net/smoothstreaming/SSWSS720H264/SuperSpeedway_720.ism/Manifest" | |
| 334 | + }, | |
| 335 | + { | |
| 336 | + "name": "Super speed (PlayReady)", | |
| 337 | + "uri": "https://playready.directtaps.net/smoothstreaming/SSWSS720H264PR/SuperSpeedway_720.ism/Manifest", | |
| 338 | + "drm_scheme": "playready" | |
| 339 | + } | |
| 340 | + ] | |
| 341 | + }, | |
| 342 | + { | |
| 343 | + "name": "HLS", | |
| 344 | + "samples": [ | |
| 345 | + { | |
| 346 | + "name": "Apple 4x3 basic stream", | |
| 347 | + "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8" | |
| 348 | + }, | |
| 349 | + { | |
| 350 | + "name": "Apple 16x9 basic stream", | |
| 351 | + "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8" | |
| 352 | + }, | |
| 353 | + { | |
| 354 | + "name": "Apple master playlist advanced (TS)", | |
| 355 | + "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_ts/master.m3u8" | |
| 356 | + }, | |
| 357 | + { | |
| 358 | + "name": "Apple master playlist advanced (fMP4)", | |
| 359 | + "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8" | |
| 360 | + }, | |
| 361 | + { | |
| 362 | + "name": "Apple TS media playlist", | |
| 363 | + "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear1/prog_index.m3u8" | |
| 364 | + }, | |
| 365 | + { | |
| 366 | + "name": "Apple AAC media playlist", | |
| 367 | + "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear0/prog_index.m3u8" | |
| 368 | + } | |
| 369 | + ] | |
| 370 | + }, | |
| 371 | + { | |
| 372 | + "name": "Misc", | |
| 373 | + "samples": [ | |
| 374 | + { | |
| 375 | + "name": "Dizzy (MP4)", | |
| 376 | + "uri": "https://html5demos.com/assets/dizzy.mp4" | |
| 377 | + }, | |
| 378 | + { | |
| 379 | + "name": "Apple 10s (AAC)", | |
| 380 | + "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear0/fileSequence0.aac" | |
| 381 | + }, | |
| 382 | + { | |
| 383 | + "name": "Apple 10s (TS)", | |
| 384 | + "uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear1/fileSequence0.ts" | |
| 385 | + }, | |
| 386 | + { | |
| 387 | + "name": "Android screens (MKV)", | |
| 388 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv" | |
| 389 | + }, | |
| 390 | + { | |
| 391 | + "name": "Screens 360p video (WebM,VP9)", | |
| 392 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-vp9-360.webm" | |
| 393 | + }, | |
| 394 | + { | |
| 395 | + "name": "Screens 480p video (FMP4,H264)", | |
| 396 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-avc-baseline-480.mp4" | |
| 397 | + }, | |
| 398 | + { | |
| 399 | + "name": "Screens 1080p video (FMP4,H264)", | |
| 400 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-137.mp4" | |
| 401 | + }, | |
| 402 | + { | |
| 403 | + "name": "Screens audio (FMP4)", | |
| 404 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4" | |
| 405 | + }, | |
| 406 | + { | |
| 407 | + "name": "Google Play (MP3)", | |
| 408 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-0/play.mp3" | |
| 409 | + }, | |
| 410 | + { | |
| 411 | + "name": "Google Play (Ogg/Vorbis)", | |
| 412 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/ogg/play.ogg" | |
| 413 | + }, | |
| 414 | + { | |
| 415 | + "name": "Big Buck Bunny video (FLV)", | |
| 416 | + "uri": "https://vod.leasewebcdn.com/bbb.flv?ri=1024&rs=150&start=0" | |
| 417 | + }, | |
| 418 | + { | |
| 419 | + "name": "Big Buck Bunny 480p video (MP4,AV1)", | |
| 420 | + "uri": "https://storage.googleapis.com/downloads.webmproject.org/av1/exoplayer/bbb-av1-480p.mp4" | |
| 421 | + } | |
| 422 | + ] | |
| 423 | + }, | |
| 424 | + { | |
| 425 | + "name": "Playlists", | |
| 426 | + "samples": [ | |
| 427 | + { | |
| 428 | + "name": "Cats -> Dogs", | |
| 429 | + "playlist": [ | |
| 430 | + { | |
| 431 | + "uri": "https://html5demos.com/assets/dizzy.mp4" | |
| 432 | + }, | |
| 433 | + { | |
| 434 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv" | |
| 435 | + } | |
| 436 | + ] | |
| 437 | + }, | |
| 438 | + { | |
| 439 | + "name": "Audio -> Video -> Audio", | |
| 440 | + "playlist": [ | |
| 441 | + { | |
| 442 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4" | |
| 443 | + }, | |
| 444 | + { | |
| 445 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv" | |
| 446 | + }, | |
| 447 | + { | |
| 448 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4" | |
| 449 | + } | |
| 450 | + ] | |
| 451 | + }, | |
| 452 | + { | |
| 453 | + "name": "Clear -> Enc -> Clear -> Enc -> Enc", | |
| 454 | + "playlist": [ | |
| 455 | + { | |
| 456 | + "uri": "https://html5demos.com/assets/dizzy.mp4" | |
| 457 | + }, | |
| 458 | + { | |
| 459 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_sd.mpd", | |
| 460 | + "drm_scheme": "widevine", | |
| 461 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 462 | + }, | |
| 463 | + { | |
| 464 | + "uri": "https://html5demos.com/assets/dizzy.mp4" | |
| 465 | + }, | |
| 466 | + { | |
| 467 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_sd.mpd", | |
| 468 | + "drm_scheme": "widevine", | |
| 469 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 470 | + }, | |
| 471 | + { | |
| 472 | + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_sd.mpd", | |
| 473 | + "drm_scheme": "widevine", | |
| 474 | + "drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test" | |
| 475 | + } | |
| 476 | + ] | |
| 477 | + } | |
| 478 | + ] | |
| 479 | + }, | |
| 480 | + { | |
| 481 | + "name": "IMA sample ad tags", | |
| 482 | + "samples": [ | |
| 483 | + { | |
| 484 | + "name": "Single inline linear", | |
| 485 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", | |
| 486 | + "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator=" | |
| 487 | + }, | |
| 488 | + { | |
| 489 | + "name": "Single skippable inline", | |
| 490 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", | |
| 491 | + "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dskippablelinear&correlator=" | |
| 492 | + }, | |
| 493 | + { | |
| 494 | + "name": "Single redirect linear", | |
| 495 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", | |
| 496 | + "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dredirectlinear&correlator=" | |
| 497 | + }, | |
| 498 | + { | |
| 499 | + "name": "Single redirect error", | |
| 500 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", | |
| 501 | + "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dredirecterror&nofb=1&correlator=" | |
| 502 | + }, | |
| 503 | + { | |
| 504 | + "name": "Single redirect broken (fallback)", | |
| 505 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", | |
| 506 | + "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dredirecterror&correlator=" | |
| 507 | + }, | |
| 508 | + { | |
| 509 | + "name": "VMAP pre-roll", | |
| 510 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", | |
| 511 | + "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpreonly&cmsid=496&vid=short_onecue&correlator=" | |
| 512 | + }, | |
| 513 | + { | |
| 514 | + "name": "VMAP pre-roll + bumper", | |
| 515 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", | |
| 516 | + "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpreonlybumper&cmsid=496&vid=short_onecue&correlator=" | |
| 517 | + }, | |
| 518 | + { | |
| 519 | + "name": "VMAP post-roll", | |
| 520 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", | |
| 521 | + "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpostonly&cmsid=496&vid=short_onecue&correlator=" | |
| 522 | + }, | |
| 523 | + { | |
| 524 | + "name": "VMAP post-roll + bumper", | |
| 525 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", | |
| 526 | + "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpostonlybumper&cmsid=496&vid=short_onecue&correlator=" | |
| 527 | + }, | |
| 528 | + { | |
| 529 | + "name": "VMAP pre-, mid- and post-rolls, single ads", | |
| 530 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", | |
| 531 | + "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpost&cmsid=496&vid=short_onecue&correlator=" | |
| 532 | + }, | |
| 533 | + { | |
| 534 | + "name": "VMAP pre-roll single ad, mid-roll standard pod with 3 ads, post-roll single ad", | |
| 535 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", | |
| 536 | + "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostpod&cmsid=496&vid=short_onecue&correlator=" | |
| 537 | + }, | |
| 538 | + { | |
| 539 | + "name": "VMAP pre-roll single ad, mid-roll optimized pod with 3 ads, post-roll single ad", | |
| 540 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", | |
| 541 | + "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostoptimizedpod&cmsid=496&vid=short_onecue&correlator=" | |
| 542 | + }, | |
| 543 | + { | |
| 544 | + "name": "VMAP pre-roll single ad, mid-roll standard pod with 3 ads, post-roll single ad (bumpers around all ad breaks)", | |
| 545 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", | |
| 546 | + "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostpodbumper&cmsid=496&vid=short_onecue&correlator=" | |
| 547 | + }, | |
| 548 | + { | |
| 549 | + "name": "VMAP pre-roll single ad, mid-roll optimized pod with 3 ads, post-roll single ad (bumpers around all ad breaks)", | |
| 550 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", | |
| 551 | + "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostoptimizedpodbumper&cmsid=496&vid=short_onecue&correlator=" | |
| 552 | + }, | |
| 553 | + { | |
| 554 | + "name": "VMAP pre-roll single ad, mid-roll standard pods with 5 ads every 10 seconds for 1:40, post-roll single ad", | |
| 555 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", | |
| 556 | + "ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostlongpod&cmsid=496&vid=short_tencue&correlator=" | |
| 557 | + }, | |
| 558 | + { | |
| 559 | + "name": "VMAP empty midroll", | |
| 560 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", | |
| 561 | + "ad_tag_uri": "https://vastsynthesizer.appspot.com/empty-midroll" | |
| 562 | + }, | |
| 563 | + { | |
| 564 | + "name": "VMAP full, empty, full midrolls", | |
| 565 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv", | |
| 566 | + "ad_tag_uri": "https://vastsynthesizer.appspot.com/empty-midroll-2" | |
| 567 | + } | |
| 568 | + ] | |
| 569 | + }, | |
| 570 | + { | |
| 571 | + "name": "360", | |
| 572 | + "samples": [ | |
| 573 | + { | |
| 574 | + "name": "Congo (360 top-bottom stereo)", | |
| 575 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/360/congo.mp4", | |
| 576 | + "spherical_stereo_mode": "top_bottom" | |
| 577 | + }, | |
| 578 | + { | |
| 579 | + "name": "Sphericalv2 (180 top-bottom stereo)", | |
| 580 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/360/sphericalv2.mp4", | |
| 581 | + "spherical_stereo_mode": "top_bottom" | |
| 582 | + }, | |
| 583 | + { | |
| 584 | + "name": "Iceland (360 top-bottom stereo ts)", | |
| 585 | + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/360/iceland0.ts", | |
| 586 | + "spherical_stereo_mode": "top_bottom" | |
| 587 | + } | |
| 588 | + ] | |
| 589 | + }, | |
| 590 | + { | |
| 591 | + "name": "Subtitles", | |
| 592 | + "samples": [ | |
| 593 | + { | |
| 594 | + "name": "TTML", | |
| 595 | + "uri": "https://html5demos.com/assets/dizzy.mp4", | |
| 596 | + "subtitle_uri": "https://storage.googleapis.com/exoplayer-test-media-1/ttml/netflix_ttml_sample.xml", | |
| 597 | + "subtitle_mime_type": "application/ttml+xml", | |
| 598 | + "subtitle_language": "en" | |
| 599 | + } | |
| 600 | + ] | |
| 601 | + } | |
| 602 | +] | ... | ... |
cloud/video/src/main/java/com/libs/video/video/base/IDataSource.java
cloud/video/src/main/java/com/libs/video/video/exoplayer/BaseTimerBar.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer; | |
| 2 | + | |
| 3 | +import android.annotation.TargetApi; | |
| 4 | +import android.content.Context; | |
| 5 | +import android.content.res.Resources; | |
| 6 | +import android.content.res.TypedArray; | |
| 7 | +import android.graphics.Canvas; | |
| 8 | +import android.graphics.Paint; | |
| 9 | +import android.graphics.Point; | |
| 10 | +import android.graphics.Rect; | |
| 11 | +import android.graphics.drawable.Drawable; | |
| 12 | +import android.os.Bundle; | |
| 13 | +import android.util.AttributeSet; | |
| 14 | +import android.util.DisplayMetrics; | |
| 15 | +import android.view.KeyEvent; | |
| 16 | +import android.view.MotionEvent; | |
| 17 | +import android.view.View; | |
| 18 | +import android.view.ViewParent; | |
| 19 | +import android.view.accessibility.AccessibilityEvent; | |
| 20 | +import android.view.accessibility.AccessibilityNodeInfo; | |
| 21 | + | |
| 22 | +import androidx.annotation.ColorInt; | |
| 23 | +import androidx.annotation.Nullable; | |
| 24 | + | |
| 25 | +import com.cnlive.cloud.video.R; | |
| 26 | +import com.google.android.exoplayer2.C; | |
| 27 | +import com.google.android.exoplayer2.ui.TimeBar; | |
| 28 | +import com.google.android.exoplayer2.util.Assertions; | |
| 29 | +import com.google.android.exoplayer2.util.Util; | |
| 30 | + | |
| 31 | +import java.util.Formatter; | |
| 32 | +import java.util.Locale; | |
| 33 | +import java.util.concurrent.CopyOnWriteArraySet; | |
| 34 | + | |
| 35 | +public class BaseTimerBar extends View implements TimeBar { | |
| 36 | + | |
| 37 | + /** Default height for the time bar, in dp. */ | |
| 38 | + public static final int DEFAULT_BAR_HEIGHT_DP = 4; | |
| 39 | + /** Default height for the touch target, in dp. */ | |
| 40 | + public static final int DEFAULT_TOUCH_TARGET_HEIGHT_DP = 26; | |
| 41 | + /** Default width for ad markers, in dp. */ | |
| 42 | + public static final int DEFAULT_AD_MARKER_WIDTH_DP = 4; | |
| 43 | + /** Default diameter for the scrubber when enabled, in dp. */ | |
| 44 | + public static final int DEFAULT_SCRUBBER_ENABLED_SIZE_DP = 12; | |
| 45 | + /** Default diameter for the scrubber when disabled, in dp. */ | |
| 46 | + public static final int DEFAULT_SCRUBBER_DISABLED_SIZE_DP = 0; | |
| 47 | + /** Default diameter for the scrubber when dragged, in dp. */ | |
| 48 | + public static final int DEFAULT_SCRUBBER_DRAGGED_SIZE_DP = 16; | |
| 49 | + /** Default color for the played portion of the time bar. */ | |
| 50 | + public static final int DEFAULT_PLAYED_COLOR = 0xFFFFFFFF; | |
| 51 | + /** Default color for the unplayed portion of the time bar. */ | |
| 52 | + public static final int DEFAULT_UNPLAYED_COLOR = 0x33FFFFFF; | |
| 53 | + /** Default color for the buffered portion of the time bar. */ | |
| 54 | + public static final int DEFAULT_BUFFERED_COLOR = 0xCCFFFFFF; | |
| 55 | + /** Default color for the scrubber handle. */ | |
| 56 | + public static final int DEFAULT_SCRUBBER_COLOR = 0xFFFFFFFF; | |
| 57 | + /** Default color for ad markers. */ | |
| 58 | + public static final int DEFAULT_AD_MARKER_COLOR = 0xB2FFFF00; | |
| 59 | + /** Default color for played ad markers. */ | |
| 60 | + public static final int DEFAULT_PLAYED_AD_MARKER_COLOR = 0x33FFFF00; | |
| 61 | + | |
| 62 | + /** The threshold in dps above the bar at which touch events trigger fine scrub mode. */ | |
| 63 | + private static final int FINE_SCRUB_Y_THRESHOLD_DP = -50; | |
| 64 | + /** The ratio by which times are reduced in fine scrub mode. */ | |
| 65 | + private static final int FINE_SCRUB_RATIO = 3; | |
| 66 | + /** | |
| 67 | + * The time after which the scrubbing listener is notified that scrubbing has stopped after | |
| 68 | + * performing an incremental scrub using key input. | |
| 69 | + */ | |
| 70 | + private static final long STOP_SCRUBBING_TIMEOUT_MS = 1000; | |
| 71 | + | |
| 72 | + private static final int DEFAULT_INCREMENT_COUNT = 20; | |
| 73 | + | |
| 74 | + /** | |
| 75 | + * The name of the Android SDK view that most closely resembles this custom view. Used as the | |
| 76 | + * class name for accessibility. | |
| 77 | + */ | |
| 78 | + private static final String ACCESSIBILITY_CLASS_NAME = "android.widget.SeekBar"; | |
| 79 | + | |
| 80 | + private final Rect seekBounds; | |
| 81 | + private final Rect progressBar; | |
| 82 | + private final Rect bufferedBar; | |
| 83 | + private final Rect scrubberBar; | |
| 84 | + private final Paint playedPaint; | |
| 85 | + private final Paint bufferedPaint; | |
| 86 | + private final Paint unplayedPaint; | |
| 87 | + private final Paint adMarkerPaint; | |
| 88 | + private final Paint playedAdMarkerPaint; | |
| 89 | + private final Paint scrubberPaint; | |
| 90 | + @Nullable | |
| 91 | + private final Drawable scrubberDrawable; | |
| 92 | + private final int barHeight; | |
| 93 | + private final int touchTargetHeight; | |
| 94 | + private final int adMarkerWidth; | |
| 95 | + private final int scrubberEnabledSize; | |
| 96 | + private final int scrubberDisabledSize; | |
| 97 | + private final int scrubberDraggedSize; | |
| 98 | + private final int scrubberPadding; | |
| 99 | + private final int fineScrubYThreshold; | |
| 100 | + private final StringBuilder formatBuilder; | |
| 101 | + private final Formatter formatter; | |
| 102 | + private final Runnable stopScrubbingRunnable; | |
| 103 | + private final CopyOnWriteArraySet<TimeBar.OnScrubListener> listeners; | |
| 104 | + private final int[] locationOnScreen; | |
| 105 | + private final Point touchPosition; | |
| 106 | + private final float density; | |
| 107 | + | |
| 108 | + private int keyCountIncrement; | |
| 109 | + private long keyTimeIncrement; | |
| 110 | + private int lastCoarseScrubXPosition; | |
| 111 | + | |
| 112 | + private boolean scrubbing; | |
| 113 | + private long scrubPosition; | |
| 114 | + private long duration; | |
| 115 | + private long position; | |
| 116 | + private long bufferedPosition; | |
| 117 | + private int adGroupCount; | |
| 118 | + @Nullable private long[] adGroupTimesMs; | |
| 119 | + @Nullable private boolean[] playedAdGroups; | |
| 120 | + | |
| 121 | + public BaseTimerBar(Context context) { | |
| 122 | + this(context, null); | |
| 123 | + } | |
| 124 | + | |
| 125 | + public BaseTimerBar(Context context, @Nullable AttributeSet attrs) { | |
| 126 | + this(context, attrs, 0); | |
| 127 | + } | |
| 128 | + | |
| 129 | + public BaseTimerBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | |
| 130 | + this(context, attrs, defStyleAttr, attrs); | |
| 131 | + } | |
| 132 | + | |
| 133 | + // Suppress warnings due to usage of View methods in the constructor. | |
| 134 | + // the constructor does not initialize fields: adGroupTimesMs, playedAdGroups | |
| 135 | + @SuppressWarnings({ | |
| 136 | + "nullness:method.invocation.invalid", | |
| 137 | + "nullness:initialization.fields.uninitialized" | |
| 138 | + }) | |
| 139 | + public BaseTimerBar( | |
| 140 | + Context context, | |
| 141 | + @Nullable AttributeSet attrs, | |
| 142 | + int defStyleAttr, | |
| 143 | + @Nullable AttributeSet timebarAttrs) { | |
| 144 | + super(context, attrs, defStyleAttr); | |
| 145 | + seekBounds = new Rect(); | |
| 146 | + progressBar = new Rect(); | |
| 147 | + bufferedBar = new Rect(); | |
| 148 | + scrubberBar = new Rect(); | |
| 149 | + playedPaint = new Paint(); | |
| 150 | + bufferedPaint = new Paint(); | |
| 151 | + unplayedPaint = new Paint(); | |
| 152 | + adMarkerPaint = new Paint(); | |
| 153 | + playedAdMarkerPaint = new Paint(); | |
| 154 | + scrubberPaint = new Paint(); | |
| 155 | + scrubberPaint.setAntiAlias(true); | |
| 156 | + listeners = new CopyOnWriteArraySet<>(); | |
| 157 | + locationOnScreen = new int[2]; | |
| 158 | + touchPosition = new Point(); | |
| 159 | + | |
| 160 | + // Calculate the dimensions and paints for drawn elements. | |
| 161 | + Resources res = context.getResources(); | |
| 162 | + DisplayMetrics displayMetrics = res.getDisplayMetrics(); | |
| 163 | + density = displayMetrics.density; | |
| 164 | + fineScrubYThreshold = dpToPx(density, FINE_SCRUB_Y_THRESHOLD_DP); | |
| 165 | + int defaultBarHeight = dpToPx(density, DEFAULT_BAR_HEIGHT_DP); | |
| 166 | + int defaultTouchTargetHeight = dpToPx(density, DEFAULT_TOUCH_TARGET_HEIGHT_DP); | |
| 167 | + int defaultAdMarkerWidth = dpToPx(density, DEFAULT_AD_MARKER_WIDTH_DP); | |
| 168 | + int defaultScrubberEnabledSize = dpToPx(density, DEFAULT_SCRUBBER_ENABLED_SIZE_DP); | |
| 169 | + int defaultScrubberDisabledSize = dpToPx(density, DEFAULT_SCRUBBER_DISABLED_SIZE_DP); | |
| 170 | + int defaultScrubberDraggedSize = dpToPx(density, DEFAULT_SCRUBBER_DRAGGED_SIZE_DP); | |
| 171 | + if (timebarAttrs != null) { | |
| 172 | + TypedArray a = | |
| 173 | + context.getTheme().obtainStyledAttributes(timebarAttrs, R.styleable.DefaultTimeBar, 0, 0); | |
| 174 | + try { | |
| 175 | + scrubberDrawable = a.getDrawable(R.styleable.DefaultTimeBar_scrubber_drawable); | |
| 176 | + if (scrubberDrawable != null) { | |
| 177 | + setDrawableLayoutDirection(scrubberDrawable); | |
| 178 | + defaultTouchTargetHeight = | |
| 179 | + Math.max(scrubberDrawable.getMinimumHeight(), defaultTouchTargetHeight); | |
| 180 | + } | |
| 181 | + barHeight = a.getDimensionPixelSize(R.styleable.DefaultTimeBar_bar_height, | |
| 182 | + defaultBarHeight); | |
| 183 | + touchTargetHeight = a.getDimensionPixelSize(R.styleable.DefaultTimeBar_touch_target_height, | |
| 184 | + defaultTouchTargetHeight); | |
| 185 | + adMarkerWidth = a.getDimensionPixelSize(R.styleable.DefaultTimeBar_ad_marker_width, | |
| 186 | + defaultAdMarkerWidth); | |
| 187 | + scrubberEnabledSize = a.getDimensionPixelSize( | |
| 188 | + R.styleable.DefaultTimeBar_scrubber_enabled_size, defaultScrubberEnabledSize); | |
| 189 | + scrubberDisabledSize = a.getDimensionPixelSize( | |
| 190 | + R.styleable.DefaultTimeBar_scrubber_disabled_size, defaultScrubberDisabledSize); | |
| 191 | + scrubberDraggedSize = a.getDimensionPixelSize( | |
| 192 | + R.styleable.DefaultTimeBar_scrubber_dragged_size, defaultScrubberDraggedSize); | |
| 193 | + int playedColor = a.getInt(R.styleable.DefaultTimeBar_played_color, DEFAULT_PLAYED_COLOR); | |
| 194 | + int scrubberColor = | |
| 195 | + a.getInt(R.styleable.DefaultTimeBar_scrubber_color, DEFAULT_SCRUBBER_COLOR); | |
| 196 | + int bufferedColor = | |
| 197 | + a.getInt(R.styleable.DefaultTimeBar_buffered_color, DEFAULT_BUFFERED_COLOR); | |
| 198 | + int unplayedColor = | |
| 199 | + a.getInt(R.styleable.DefaultTimeBar_unplayed_color, DEFAULT_UNPLAYED_COLOR); | |
| 200 | + int adMarkerColor = a.getInt(R.styleable.DefaultTimeBar_ad_marker_color, | |
| 201 | + DEFAULT_AD_MARKER_COLOR); | |
| 202 | + int playedAdMarkerColor = | |
| 203 | + a.getInt( | |
| 204 | + R.styleable.DefaultTimeBar_played_ad_marker_color, DEFAULT_PLAYED_AD_MARKER_COLOR); | |
| 205 | + playedPaint.setColor(playedColor); | |
| 206 | + scrubberPaint.setColor(scrubberColor); | |
| 207 | + bufferedPaint.setColor(bufferedColor); | |
| 208 | + unplayedPaint.setColor(unplayedColor); | |
| 209 | + adMarkerPaint.setColor(adMarkerColor); | |
| 210 | + playedAdMarkerPaint.setColor(playedAdMarkerColor); | |
| 211 | + } finally { | |
| 212 | + a.recycle(); | |
| 213 | + } | |
| 214 | + } else { | |
| 215 | + barHeight = defaultBarHeight; | |
| 216 | + touchTargetHeight = defaultTouchTargetHeight; | |
| 217 | + adMarkerWidth = defaultAdMarkerWidth; | |
| 218 | + scrubberEnabledSize = defaultScrubberEnabledSize; | |
| 219 | + scrubberDisabledSize = defaultScrubberDisabledSize; | |
| 220 | + scrubberDraggedSize = defaultScrubberDraggedSize; | |
| 221 | + playedPaint.setColor(DEFAULT_PLAYED_COLOR); | |
| 222 | + scrubberPaint.setColor(DEFAULT_SCRUBBER_COLOR); | |
| 223 | + bufferedPaint.setColor(DEFAULT_BUFFERED_COLOR); | |
| 224 | + unplayedPaint.setColor(DEFAULT_UNPLAYED_COLOR); | |
| 225 | + adMarkerPaint.setColor(DEFAULT_AD_MARKER_COLOR); | |
| 226 | + playedAdMarkerPaint.setColor(DEFAULT_PLAYED_AD_MARKER_COLOR); | |
| 227 | + scrubberDrawable = null; | |
| 228 | + } | |
| 229 | + formatBuilder = new StringBuilder(); | |
| 230 | + formatter = new Formatter(formatBuilder, Locale.getDefault()); | |
| 231 | + stopScrubbingRunnable = () -> stopScrubbing(/* canceled= */ false); | |
| 232 | + if (scrubberDrawable != null) { | |
| 233 | + scrubberPadding = (scrubberDrawable.getMinimumWidth() + 1) / 2; | |
| 234 | + } else { | |
| 235 | + scrubberPadding = | |
| 236 | + (Math.max(scrubberDisabledSize, Math.max(scrubberEnabledSize, scrubberDraggedSize)) + 1) | |
| 237 | + / 2; | |
| 238 | + } | |
| 239 | + duration = C.TIME_UNSET; | |
| 240 | + keyTimeIncrement = C.TIME_UNSET; | |
| 241 | + keyCountIncrement = DEFAULT_INCREMENT_COUNT; | |
| 242 | + setFocusable(true); | |
| 243 | + if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { | |
| 244 | + setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); | |
| 245 | + } | |
| 246 | + } | |
| 247 | + | |
| 248 | + /** | |
| 249 | + * Sets the color for the portion of the time bar representing media before the playback position. | |
| 250 | + * | |
| 251 | + * @param playedColor The color for the portion of the time bar representing media before the | |
| 252 | + * playback position. | |
| 253 | + */ | |
| 254 | + public void setPlayedColor(@ColorInt int playedColor) { | |
| 255 | + playedPaint.setColor(playedColor); | |
| 256 | + invalidate(seekBounds); | |
| 257 | + } | |
| 258 | + | |
| 259 | + /** | |
| 260 | + * Sets the color for the scrubber handle. | |
| 261 | + * | |
| 262 | + * @param scrubberColor The color for the scrubber handle. | |
| 263 | + */ | |
| 264 | + public void setScrubberColor(@ColorInt int scrubberColor) { | |
| 265 | + scrubberPaint.setColor(scrubberColor); | |
| 266 | + invalidate(seekBounds); | |
| 267 | + } | |
| 268 | + | |
| 269 | + /** | |
| 270 | + * Sets the color for the portion of the time bar after the current played position up to the | |
| 271 | + * current buffered position. | |
| 272 | + * | |
| 273 | + * @param bufferedColor The color for the portion of the time bar after the current played | |
| 274 | + * position up to the current buffered position. | |
| 275 | + */ | |
| 276 | + public void setBufferedColor(@ColorInt int bufferedColor) { | |
| 277 | + bufferedPaint.setColor(bufferedColor); | |
| 278 | + invalidate(seekBounds); | |
| 279 | + } | |
| 280 | + | |
| 281 | + /** | |
| 282 | + * Sets the color for the portion of the time bar after the current played position. | |
| 283 | + * | |
| 284 | + * @param unplayedColor The color for the portion of the time bar after the current played | |
| 285 | + * position. | |
| 286 | + */ | |
| 287 | + public void setUnplayedColor(@ColorInt int unplayedColor) { | |
| 288 | + unplayedPaint.setColor(unplayedColor); | |
| 289 | + invalidate(seekBounds); | |
| 290 | + } | |
| 291 | + | |
| 292 | + /** | |
| 293 | + * Sets the color for unplayed ad markers. | |
| 294 | + * | |
| 295 | + * @param adMarkerColor The color for unplayed ad markers. | |
| 296 | + */ | |
| 297 | + public void setAdMarkerColor(@ColorInt int adMarkerColor) { | |
| 298 | + adMarkerPaint.setColor(adMarkerColor); | |
| 299 | + invalidate(seekBounds); | |
| 300 | + } | |
| 301 | + | |
| 302 | + /** | |
| 303 | + * Sets the color for played ad markers. | |
| 304 | + * | |
| 305 | + * @param playedAdMarkerColor The color for played ad markers. | |
| 306 | + */ | |
| 307 | + public void setPlayedAdMarkerColor(@ColorInt int playedAdMarkerColor) { | |
| 308 | + playedAdMarkerPaint.setColor(playedAdMarkerColor); | |
| 309 | + invalidate(seekBounds); | |
| 310 | + } | |
| 311 | + | |
| 312 | + // TimeBar implementation. | |
| 313 | + | |
| 314 | + @Override | |
| 315 | + public void addListener(TimeBar.OnScrubListener listener) { | |
| 316 | + listeners.add(listener); | |
| 317 | + } | |
| 318 | + | |
| 319 | + @Override | |
| 320 | + public void removeListener(TimeBar.OnScrubListener listener) { | |
| 321 | + listeners.remove(listener); | |
| 322 | + } | |
| 323 | + | |
| 324 | + @Override | |
| 325 | + public void setKeyTimeIncrement(long time) { | |
| 326 | + Assertions.checkArgument(time > 0); | |
| 327 | + keyCountIncrement = C.INDEX_UNSET; | |
| 328 | + keyTimeIncrement = time; | |
| 329 | + } | |
| 330 | + | |
| 331 | + @Override | |
| 332 | + public void setKeyCountIncrement(int count) { | |
| 333 | + Assertions.checkArgument(count > 0); | |
| 334 | + keyCountIncrement = count; | |
| 335 | + keyTimeIncrement = C.TIME_UNSET; | |
| 336 | + } | |
| 337 | + | |
| 338 | + @Override | |
| 339 | + public void setPosition(long position) { | |
| 340 | + this.position = position; | |
| 341 | + setContentDescription(getProgressText()); | |
| 342 | + update(); | |
| 343 | + } | |
| 344 | + | |
| 345 | + @Override | |
| 346 | + public void setBufferedPosition(long bufferedPosition) { | |
| 347 | + this.bufferedPosition = bufferedPosition; | |
| 348 | + update(); | |
| 349 | + } | |
| 350 | + | |
| 351 | + @Override | |
| 352 | + public void setDuration(long duration) { | |
| 353 | + this.duration = duration; | |
| 354 | + if (scrubbing && duration == C.TIME_UNSET) { | |
| 355 | + stopScrubbing(/* canceled= */ true); | |
| 356 | + } | |
| 357 | + update(); | |
| 358 | + } | |
| 359 | + | |
| 360 | + @Override | |
| 361 | + public long getPreferredUpdateDelay() { | |
| 362 | + int timeBarWidthDp = pxToDp(density, progressBar.width()); | |
| 363 | + return timeBarWidthDp == 0 || duration == 0 || duration == C.TIME_UNSET | |
| 364 | + ? Long.MAX_VALUE | |
| 365 | + : duration / timeBarWidthDp; | |
| 366 | + } | |
| 367 | + | |
| 368 | + @Override | |
| 369 | + public void setAdGroupTimesMs(@Nullable long[] adGroupTimesMs, @Nullable boolean[] playedAdGroups, | |
| 370 | + int adGroupCount) { | |
| 371 | + Assertions.checkArgument(adGroupCount == 0 | |
| 372 | + || (adGroupTimesMs != null && playedAdGroups != null)); | |
| 373 | + this.adGroupCount = adGroupCount; | |
| 374 | + this.adGroupTimesMs = adGroupTimesMs; | |
| 375 | + this.playedAdGroups = playedAdGroups; | |
| 376 | + update(); | |
| 377 | + } | |
| 378 | + | |
| 379 | + // View methods. | |
| 380 | + | |
| 381 | + @Override | |
| 382 | + public void setEnabled(boolean enabled) { | |
| 383 | + super.setEnabled(enabled); | |
| 384 | + if (scrubbing && !enabled) { | |
| 385 | + stopScrubbing(/* canceled= */ true); | |
| 386 | + } | |
| 387 | + } | |
| 388 | + | |
| 389 | + @Override | |
| 390 | + public void onDraw(Canvas canvas) { | |
| 391 | + canvas.save(); | |
| 392 | + drawTimeBar(canvas); | |
| 393 | + drawPlayhead(canvas); | |
| 394 | + canvas.restore(); | |
| 395 | + } | |
| 396 | + | |
| 397 | + @Override | |
| 398 | + public boolean onTouchEvent(MotionEvent event) { | |
| 399 | + if (!isEnabled() || duration <= 0) { | |
| 400 | + return false; | |
| 401 | + } | |
| 402 | + Point touchPosition = resolveRelativeTouchPosition(event); | |
| 403 | + int x = touchPosition.x; | |
| 404 | + int y = touchPosition.y; | |
| 405 | + switch (event.getAction()) { | |
| 406 | + case MotionEvent.ACTION_DOWN: | |
| 407 | + if (isInSeekBar(x, y)) { | |
| 408 | + positionScrubber(x); | |
| 409 | + startScrubbing(getScrubberPosition()); | |
| 410 | + update(); | |
| 411 | + invalidate(); | |
| 412 | + return true; | |
| 413 | + } | |
| 414 | + break; | |
| 415 | + case MotionEvent.ACTION_MOVE: | |
| 416 | + if (scrubbing) { | |
| 417 | + if (y < fineScrubYThreshold) { | |
| 418 | + int relativeX = x - lastCoarseScrubXPosition; | |
| 419 | + positionScrubber(lastCoarseScrubXPosition + relativeX / FINE_SCRUB_RATIO); | |
| 420 | + } else { | |
| 421 | + lastCoarseScrubXPosition = x; | |
| 422 | + positionScrubber(x); | |
| 423 | + } | |
| 424 | + updateScrubbing(getScrubberPosition()); | |
| 425 | + update(); | |
| 426 | + invalidate(); | |
| 427 | + return true; | |
| 428 | + } | |
| 429 | + break; | |
| 430 | + case MotionEvent.ACTION_UP: | |
| 431 | + case MotionEvent.ACTION_CANCEL: | |
| 432 | + if (scrubbing) { | |
| 433 | + stopScrubbing(/* canceled= */ event.getAction() == MotionEvent.ACTION_CANCEL); | |
| 434 | + return true; | |
| 435 | + } | |
| 436 | + break; | |
| 437 | + default: | |
| 438 | + // Do nothing. | |
| 439 | + } | |
| 440 | + return false; | |
| 441 | + } | |
| 442 | + | |
| 443 | + @Override | |
| 444 | + public boolean onKeyDown(int keyCode, KeyEvent event) { | |
| 445 | + if (isEnabled()) { | |
| 446 | + long positionIncrement = getPositionIncrement(); | |
| 447 | + switch (keyCode) { | |
| 448 | + case KeyEvent.KEYCODE_DPAD_LEFT: | |
| 449 | + positionIncrement = -positionIncrement; | |
| 450 | + // Fall through. | |
| 451 | + case KeyEvent.KEYCODE_DPAD_RIGHT: | |
| 452 | + if (scrubIncrementally(positionIncrement)) { | |
| 453 | + removeCallbacks(stopScrubbingRunnable); | |
| 454 | + postDelayed(stopScrubbingRunnable, STOP_SCRUBBING_TIMEOUT_MS); | |
| 455 | + return true; | |
| 456 | + } | |
| 457 | + break; | |
| 458 | + case KeyEvent.KEYCODE_DPAD_CENTER: | |
| 459 | + case KeyEvent.KEYCODE_ENTER: | |
| 460 | + if (scrubbing) { | |
| 461 | + stopScrubbing(/* canceled= */ false); | |
| 462 | + return true; | |
| 463 | + } | |
| 464 | + break; | |
| 465 | + default: | |
| 466 | + // Do nothing. | |
| 467 | + } | |
| 468 | + } | |
| 469 | + return super.onKeyDown(keyCode, event); | |
| 470 | + } | |
| 471 | + | |
| 472 | + @Override | |
| 473 | + protected void onFocusChanged( | |
| 474 | + boolean gainFocus, int direction, @Nullable Rect previouslyFocusedRect) { | |
| 475 | + super.onFocusChanged(gainFocus, direction, previouslyFocusedRect); | |
| 476 | + if (scrubbing && !gainFocus) { | |
| 477 | + stopScrubbing(/* canceled= */ false); | |
| 478 | + } | |
| 479 | + } | |
| 480 | + | |
| 481 | + @Override | |
| 482 | + protected void drawableStateChanged() { | |
| 483 | + super.drawableStateChanged(); | |
| 484 | + updateDrawableState(); | |
| 485 | + } | |
| 486 | + | |
| 487 | + @Override | |
| 488 | + public void jumpDrawablesToCurrentState() { | |
| 489 | + super.jumpDrawablesToCurrentState(); | |
| 490 | + if (scrubberDrawable != null) { | |
| 491 | + scrubberDrawable.jumpToCurrentState(); | |
| 492 | + } | |
| 493 | + } | |
| 494 | + | |
| 495 | + @Override | |
| 496 | + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
| 497 | + int heightMode = View.MeasureSpec.getMode(heightMeasureSpec); | |
| 498 | + int heightSize = View.MeasureSpec.getSize(heightMeasureSpec); | |
| 499 | + int height = heightMode == View.MeasureSpec.UNSPECIFIED ? touchTargetHeight | |
| 500 | + : heightMode == View.MeasureSpec.EXACTLY ? heightSize : Math.min(touchTargetHeight, heightSize); | |
| 501 | + setMeasuredDimension(View.MeasureSpec.getSize(widthMeasureSpec), height); | |
| 502 | + updateDrawableState(); | |
| 503 | + } | |
| 504 | + | |
| 505 | + @Override | |
| 506 | + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { | |
| 507 | + int width = right - left; | |
| 508 | + int height = bottom - top; | |
| 509 | + int barY = (height - touchTargetHeight) / 2; | |
| 510 | + int seekLeft = getPaddingLeft(); | |
| 511 | + int seekRight = width - getPaddingRight(); | |
| 512 | + int progressY = barY + (touchTargetHeight - barHeight) / 2; | |
| 513 | + seekBounds.set(seekLeft, barY, seekRight, barY + touchTargetHeight); | |
| 514 | + progressBar.set(seekBounds.left + scrubberPadding, progressY, | |
| 515 | + seekBounds.right - scrubberPadding, progressY + barHeight); | |
| 516 | + update(); | |
| 517 | + } | |
| 518 | + | |
| 519 | + @Override | |
| 520 | + public void onRtlPropertiesChanged(int layoutDirection) { | |
| 521 | + if (scrubberDrawable != null && setDrawableLayoutDirection(scrubberDrawable, layoutDirection)) { | |
| 522 | + invalidate(); | |
| 523 | + } | |
| 524 | + } | |
| 525 | + | |
| 526 | + @Override | |
| 527 | + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { | |
| 528 | + super.onInitializeAccessibilityEvent(event); | |
| 529 | + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SELECTED) { | |
| 530 | + event.getText().add(getProgressText()); | |
| 531 | + } | |
| 532 | + event.setClassName(ACCESSIBILITY_CLASS_NAME); | |
| 533 | + } | |
| 534 | + | |
| 535 | + @TargetApi(21) | |
| 536 | + @Override | |
| 537 | + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { | |
| 538 | + super.onInitializeAccessibilityNodeInfo(info); | |
| 539 | + info.setClassName(ACCESSIBILITY_CLASS_NAME); | |
| 540 | + info.setContentDescription(getProgressText()); | |
| 541 | + if (duration <= 0) { | |
| 542 | + return; | |
| 543 | + } | |
| 544 | + if (Util.SDK_INT >= 21) { | |
| 545 | + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD); | |
| 546 | + info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_BACKWARD); | |
| 547 | + } else { | |
| 548 | + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); | |
| 549 | + info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); | |
| 550 | + } | |
| 551 | + } | |
| 552 | + | |
| 553 | + @Override | |
| 554 | + public boolean performAccessibilityAction(int action, @Nullable Bundle args) { | |
| 555 | + if (super.performAccessibilityAction(action, args)) { | |
| 556 | + return true; | |
| 557 | + } | |
| 558 | + if (duration <= 0) { | |
| 559 | + return false; | |
| 560 | + } | |
| 561 | + if (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD) { | |
| 562 | + if (scrubIncrementally(-getPositionIncrement())) { | |
| 563 | + stopScrubbing(/* canceled= */ false); | |
| 564 | + } | |
| 565 | + } else if (action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD) { | |
| 566 | + if (scrubIncrementally(getPositionIncrement())) { | |
| 567 | + stopScrubbing(/* canceled= */ false); | |
| 568 | + } | |
| 569 | + } else { | |
| 570 | + return false; | |
| 571 | + } | |
| 572 | + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_SELECTED); | |
| 573 | + return true; | |
| 574 | + } | |
| 575 | + | |
| 576 | + // Internal methods. | |
| 577 | + | |
| 578 | + private void startScrubbing(long scrubPosition) { | |
| 579 | + this.scrubPosition = scrubPosition; | |
| 580 | + scrubbing = true; | |
| 581 | + setPressed(true); | |
| 582 | + ViewParent parent = getParent(); | |
| 583 | + if (parent != null) { | |
| 584 | + parent.requestDisallowInterceptTouchEvent(true); | |
| 585 | + } | |
| 586 | + for (TimeBar.OnScrubListener listener : listeners) { | |
| 587 | + listener.onScrubStart(this, scrubPosition); | |
| 588 | + } | |
| 589 | + } | |
| 590 | + | |
| 591 | + private void updateScrubbing(long scrubPosition) { | |
| 592 | + if (this.scrubPosition == scrubPosition) { | |
| 593 | + return; | |
| 594 | + } | |
| 595 | + this.scrubPosition = scrubPosition; | |
| 596 | + for (TimeBar.OnScrubListener listener : listeners) { | |
| 597 | + listener.onScrubMove(this, scrubPosition); | |
| 598 | + } | |
| 599 | + } | |
| 600 | + | |
| 601 | + private void stopScrubbing(boolean canceled) { | |
| 602 | + removeCallbacks(stopScrubbingRunnable); | |
| 603 | + scrubbing = false; | |
| 604 | + setPressed(false); | |
| 605 | + ViewParent parent = getParent(); | |
| 606 | + if (parent != null) { | |
| 607 | + parent.requestDisallowInterceptTouchEvent(false); | |
| 608 | + } | |
| 609 | + invalidate(); | |
| 610 | + for (TimeBar.OnScrubListener listener : listeners) { | |
| 611 | + listener.onScrubStop(this, scrubPosition, canceled); | |
| 612 | + } | |
| 613 | + } | |
| 614 | + | |
| 615 | + /** | |
| 616 | + * Incrementally scrubs the position by {@code positionChange}. | |
| 617 | + * | |
| 618 | + * @param positionChange The change in the scrubber position, in milliseconds. May be negative. | |
| 619 | + * @return Returns whether the scrubber position changed. | |
| 620 | + */ | |
| 621 | + private boolean scrubIncrementally(long positionChange) { | |
| 622 | + if (duration <= 0) { | |
| 623 | + return false; | |
| 624 | + } | |
| 625 | + long previousPosition = scrubbing ? scrubPosition : position; | |
| 626 | + long scrubPosition = Util.constrainValue(previousPosition + positionChange, 0, duration); | |
| 627 | + if (scrubPosition == previousPosition) { | |
| 628 | + return false; | |
| 629 | + } | |
| 630 | + if (!scrubbing) { | |
| 631 | + startScrubbing(scrubPosition); | |
| 632 | + } else { | |
| 633 | + updateScrubbing(scrubPosition); | |
| 634 | + } | |
| 635 | + update(); | |
| 636 | + return true; | |
| 637 | + } | |
| 638 | + | |
| 639 | + private void update() { | |
| 640 | + bufferedBar.set(progressBar); | |
| 641 | + scrubberBar.set(progressBar); | |
| 642 | + long newScrubberTime = scrubbing ? scrubPosition : position; | |
| 643 | + if (duration > 0) { | |
| 644 | + int bufferedPixelWidth = (int) ((progressBar.width() * bufferedPosition) / duration); | |
| 645 | + bufferedBar.right = Math.min(progressBar.left + bufferedPixelWidth, progressBar.right); | |
| 646 | + int scrubberPixelPosition = (int) ((progressBar.width() * newScrubberTime) / duration); | |
| 647 | + scrubberBar.right = Math.min(progressBar.left + scrubberPixelPosition, progressBar.right); | |
| 648 | + } else { | |
| 649 | + bufferedBar.right = progressBar.left; | |
| 650 | + scrubberBar.right = progressBar.left; | |
| 651 | + } | |
| 652 | + invalidate(seekBounds); | |
| 653 | + } | |
| 654 | + | |
| 655 | + private void positionScrubber(float xPosition) { | |
| 656 | + scrubberBar.right = Util.constrainValue((int) xPosition, progressBar.left, progressBar.right); | |
| 657 | + } | |
| 658 | + | |
| 659 | + private Point resolveRelativeTouchPosition(MotionEvent motionEvent) { | |
| 660 | + getLocationOnScreen(locationOnScreen); | |
| 661 | + touchPosition.set( | |
| 662 | + ((int) motionEvent.getRawX()) - locationOnScreen[0], | |
| 663 | + ((int) motionEvent.getRawY()) - locationOnScreen[1]); | |
| 664 | + return touchPosition; | |
| 665 | + } | |
| 666 | + | |
| 667 | + private long getScrubberPosition() { | |
| 668 | + if (progressBar.width() <= 0 || duration == C.TIME_UNSET) { | |
| 669 | + return 0; | |
| 670 | + } | |
| 671 | + return (scrubberBar.width() * duration) / progressBar.width(); | |
| 672 | + } | |
| 673 | + | |
| 674 | + private boolean isInSeekBar(float x, float y) { | |
| 675 | + return seekBounds.contains((int) x, (int) y); | |
| 676 | + } | |
| 677 | + | |
| 678 | + private void drawTimeBar(Canvas canvas) { | |
| 679 | + int progressBarHeight = progressBar.height(); | |
| 680 | + int barTop = progressBar.centerY() - progressBarHeight / 2; | |
| 681 | + int barBottom = barTop + progressBarHeight; | |
| 682 | + if (duration <= 0) { | |
| 683 | + canvas.drawRect(progressBar.left, barTop, progressBar.right, barBottom, unplayedPaint); | |
| 684 | + return; | |
| 685 | + } | |
| 686 | + int bufferedLeft = bufferedBar.left; | |
| 687 | + int bufferedRight = bufferedBar.right; | |
| 688 | + int progressLeft = Math.max(Math.max(progressBar.left, bufferedRight), scrubberBar.right); | |
| 689 | + if (progressLeft < progressBar.right) { | |
| 690 | + canvas.drawRect(progressLeft, barTop, progressBar.right, barBottom, unplayedPaint); | |
| 691 | + } | |
| 692 | + bufferedLeft = Math.max(bufferedLeft, scrubberBar.right); | |
| 693 | + if (bufferedRight > bufferedLeft) { | |
| 694 | + canvas.drawRect(bufferedLeft, barTop, bufferedRight, barBottom, bufferedPaint); | |
| 695 | + } | |
| 696 | + if (scrubberBar.width() > 0) { | |
| 697 | + canvas.drawRect(scrubberBar.left, barTop, scrubberBar.right, barBottom, playedPaint); | |
| 698 | + } | |
| 699 | + if (adGroupCount == 0) { | |
| 700 | + return; | |
| 701 | + } | |
| 702 | + long[] adGroupTimesMs = Assertions.checkNotNull(this.adGroupTimesMs); | |
| 703 | + boolean[] playedAdGroups = Assertions.checkNotNull(this.playedAdGroups); | |
| 704 | + int adMarkerOffset = adMarkerWidth / 2; | |
| 705 | + for (int i = 0; i < adGroupCount; i++) { | |
| 706 | + long adGroupTimeMs = Util.constrainValue(adGroupTimesMs[i], 0, duration); | |
| 707 | + int markerPositionOffset = | |
| 708 | + (int) (progressBar.width() * adGroupTimeMs / duration) - adMarkerOffset; | |
| 709 | + int markerLeft = progressBar.left + Math.min(progressBar.width() - adMarkerWidth, | |
| 710 | + Math.max(0, markerPositionOffset)); | |
| 711 | + Paint paint = playedAdGroups[i] ? playedAdMarkerPaint : adMarkerPaint; | |
| 712 | + canvas.drawRect(markerLeft, barTop, markerLeft + adMarkerWidth, barBottom, paint); | |
| 713 | + } | |
| 714 | + } | |
| 715 | + | |
| 716 | + private void drawPlayhead(Canvas canvas) { | |
| 717 | + if (duration <= 0) { | |
| 718 | + return; | |
| 719 | + } | |
| 720 | + int playheadX = Util.constrainValue(scrubberBar.right, scrubberBar.left, progressBar.right); | |
| 721 | + int playheadY = scrubberBar.centerY(); | |
| 722 | + if (scrubberDrawable == null) { | |
| 723 | + int scrubberSize = (scrubbing || isFocused()) ? scrubberDraggedSize | |
| 724 | + : (isEnabled() ? scrubberEnabledSize : scrubberDisabledSize); | |
| 725 | + int playheadRadius = scrubberSize / 2; | |
| 726 | + canvas.drawCircle(playheadX, playheadY, playheadRadius, scrubberPaint); | |
| 727 | + } else { | |
| 728 | + int scrubberDrawableWidth = scrubberDrawable.getIntrinsicWidth(); | |
| 729 | + int scrubberDrawableHeight = scrubberDrawable.getIntrinsicHeight(); | |
| 730 | + scrubberDrawable.setBounds( | |
| 731 | + playheadX - scrubberDrawableWidth / 2, | |
| 732 | + playheadY - scrubberDrawableHeight / 2, | |
| 733 | + playheadX + scrubberDrawableWidth / 2, | |
| 734 | + playheadY + scrubberDrawableHeight / 2); | |
| 735 | + scrubberDrawable.draw(canvas); | |
| 736 | + } | |
| 737 | + } | |
| 738 | + | |
| 739 | + private void updateDrawableState() { | |
| 740 | + if (scrubberDrawable != null && scrubberDrawable.isStateful() | |
| 741 | + && scrubberDrawable.setState(getDrawableState())) { | |
| 742 | + invalidate(); | |
| 743 | + } | |
| 744 | + } | |
| 745 | + | |
| 746 | + private String getProgressText() { | |
| 747 | + return Util.getStringForTime(formatBuilder, formatter, position); | |
| 748 | + } | |
| 749 | + | |
| 750 | + private long getPositionIncrement() { | |
| 751 | + return keyTimeIncrement == C.TIME_UNSET | |
| 752 | + ? (duration == C.TIME_UNSET ? 0 : (duration / keyCountIncrement)) : keyTimeIncrement; | |
| 753 | + } | |
| 754 | + | |
| 755 | + private boolean setDrawableLayoutDirection(Drawable drawable) { | |
| 756 | + return Util.SDK_INT >= 23 && setDrawableLayoutDirection(drawable, getLayoutDirection()); | |
| 757 | + } | |
| 758 | + | |
| 759 | + private static boolean setDrawableLayoutDirection(Drawable drawable, int layoutDirection) { | |
| 760 | + return Util.SDK_INT >= 23 && drawable.setLayoutDirection(layoutDirection); | |
| 761 | + } | |
| 762 | + | |
| 763 | + private static int dpToPx(float density, int dps) { | |
| 764 | + return (int) (dps * density + 0.5f); | |
| 765 | + } | |
| 766 | + | |
| 767 | + private static int pxToDp(float density, int px) { | |
| 768 | + return (int) (px / density); | |
| 769 | + } | |
| 770 | +} | |
| 0 | 771 | \ No newline at end of file | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/CNLiveBaseVideoControlView.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer; | |
| 2 | + | |
| 3 | +import android.annotation.SuppressLint; | |
| 4 | +import android.app.Activity; | |
| 5 | +import android.content.Context; | |
| 6 | +import android.content.pm.ActivityInfo; | |
| 7 | +import android.content.res.Configuration; | |
| 8 | +import android.content.res.Resources; | |
| 9 | +import android.content.res.TypedArray; | |
| 10 | +import android.graphics.drawable.Drawable; | |
| 11 | +import android.os.Looper; | |
| 12 | +import android.os.SystemClock; | |
| 13 | +import android.util.AttributeSet; | |
| 14 | +import android.view.KeyEvent; | |
| 15 | +import android.view.LayoutInflater; | |
| 16 | +import android.view.MotionEvent; | |
| 17 | +import android.view.View; | |
| 18 | +import android.view.ViewGroup; | |
| 19 | +import android.view.WindowManager; | |
| 20 | +import android.widget.FrameLayout; | |
| 21 | +import android.widget.ImageView; | |
| 22 | +import android.widget.TextView; | |
| 23 | + | |
| 24 | +import androidx.annotation.ColorInt; | |
| 25 | +import androidx.annotation.Nullable; | |
| 26 | + | |
| 27 | +import com.cnlive.cloud.video.R; | |
| 28 | +import com.google.android.exoplayer2.C; | |
| 29 | +import com.google.android.exoplayer2.ExoPlayerLibraryInfo; | |
| 30 | +import com.google.android.exoplayer2.PlaybackPreparer; | |
| 31 | +import com.google.android.exoplayer2.Player; | |
| 32 | +import com.google.android.exoplayer2.Timeline; | |
| 33 | +import com.google.android.exoplayer2.ui.TimeBar; | |
| 34 | +import com.google.android.exoplayer2.util.Assertions; | |
| 35 | +import com.google.android.exoplayer2.util.RepeatModeUtil; | |
| 36 | +import com.google.android.exoplayer2.util.Util; | |
| 37 | + | |
| 38 | +import java.util.Arrays; | |
| 39 | +import java.util.Formatter; | |
| 40 | +import java.util.Locale; | |
| 41 | +import java.util.concurrent.CopyOnWriteArrayList; | |
| 42 | + | |
| 43 | +public abstract class CNLiveBaseVideoControlView extends FrameLayout { | |
| 44 | + | |
| 45 | + static { | |
| 46 | + ExoPlayerLibraryInfo.registerModule("goog.exo.ui"); | |
| 47 | + } | |
| 48 | + | |
| 49 | + /** | |
| 50 | + * Listener to be notified about changes of the visibility of the UI control. | |
| 51 | + */ | |
| 52 | + public interface VisibilityListener { | |
| 53 | + | |
| 54 | + /** | |
| 55 | + * Called when the visibility changes. | |
| 56 | + * | |
| 57 | + * @param visibility The new visibility. Either {@link View#VISIBLE} or {@link View#GONE}. | |
| 58 | + */ | |
| 59 | + void onVisibilityChange(int visibility); | |
| 60 | + } | |
| 61 | + | |
| 62 | + /** | |
| 63 | + * Listener to be notified when progress has been updated. | |
| 64 | + */ | |
| 65 | + public interface ProgressUpdateListener { | |
| 66 | + | |
| 67 | + /** | |
| 68 | + * Called when progress needs to be updated. | |
| 69 | + * | |
| 70 | + * @param position The current position. | |
| 71 | + * @param bufferedPosition The current buffered position. | |
| 72 | + */ | |
| 73 | + void onProgressUpdate(long position, long bufferedPosition); | |
| 74 | + } | |
| 75 | + | |
| 76 | + /** | |
| 77 | + * The default fast forward increment, in milliseconds. | |
| 78 | + */ | |
| 79 | + public static final int DEFAULT_FAST_FORWARD_MS = 15000; | |
| 80 | + /** | |
| 81 | + * The default rewind increment, in milliseconds. | |
| 82 | + */ | |
| 83 | + public static final int DEFAULT_REWIND_MS = 5000; | |
| 84 | + /** | |
| 85 | + * The default show timeout, in milliseconds. | |
| 86 | + */ | |
| 87 | + public static final int DEFAULT_SHOW_TIMEOUT_MS = 5000; | |
| 88 | + /** | |
| 89 | + * The default repeat toggle modes. | |
| 90 | + */ | |
| 91 | + public static final @RepeatModeUtil.RepeatToggleModes | |
| 92 | + int DEFAULT_REPEAT_TOGGLE_MODES = | |
| 93 | + RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE; | |
| 94 | + /** | |
| 95 | + * The default minimum interval between time bar position updates. | |
| 96 | + */ | |
| 97 | + public static final int DEFAULT_TIME_BAR_MIN_UPDATE_INTERVAL_MS = 200; | |
| 98 | + /** | |
| 99 | + * The maximum number of windows that can be shown in a multi-window time bar. | |
| 100 | + */ | |
| 101 | + public static final int MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR = 100; | |
| 102 | + | |
| 103 | + private static final long MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000; | |
| 104 | + /** | |
| 105 | + * The maximum interval between time bar position updates. | |
| 106 | + */ | |
| 107 | + private static final int MAX_UPDATE_INTERVAL_MS = 1000; | |
| 108 | + | |
| 109 | + private CNLiveBaseVideoControlView.ComponentListener componentListener; | |
| 110 | + private CopyOnWriteArrayList<CNLiveBaseVideoControlView.VisibilityListener> visibilityListeners; | |
| 111 | + @Nullable | |
| 112 | + private View previousButton; | |
| 113 | + @Nullable | |
| 114 | + private View nextButton; | |
| 115 | + @Nullable | |
| 116 | + private View playButton; | |
| 117 | + @Nullable | |
| 118 | + private View pauseButton; | |
| 119 | + @Nullable | |
| 120 | + private View fastForwardButton; | |
| 121 | + @Nullable | |
| 122 | + private View rewindButton; | |
| 123 | + @Nullable | |
| 124 | + private ImageView repeatToggleButton; | |
| 125 | + @Nullable | |
| 126 | + private ImageView shuffleButton; | |
| 127 | + @Nullable | |
| 128 | + private View vrButton; | |
| 129 | + @Nullable | |
| 130 | + private TextView durationView; | |
| 131 | + @Nullable | |
| 132 | + private TextView positionView; | |
| 133 | + @Nullable | |
| 134 | + private TimeBar timeBar; | |
| 135 | + private StringBuilder formatBuilder; | |
| 136 | + private Formatter formatter; | |
| 137 | + private Timeline.Period period; | |
| 138 | + private Timeline.Window window; | |
| 139 | + private Runnable updateProgressAction; | |
| 140 | + private Runnable hideAction; | |
| 141 | + private int controllerLayoutId; | |
| 142 | + private Drawable repeatOffButtonDrawable; | |
| 143 | + private Drawable repeatOneButtonDrawable; | |
| 144 | + private Drawable repeatAllButtonDrawable; | |
| 145 | + private String repeatOffButtonContentDescription; | |
| 146 | + private String repeatOneButtonContentDescription; | |
| 147 | + private String repeatAllButtonContentDescription; | |
| 148 | + private Drawable shuffleOnButtonDrawable; | |
| 149 | + private Drawable shuffleOffButtonDrawable; | |
| 150 | + private float buttonAlphaEnabled; | |
| 151 | + private float buttonAlphaDisabled; | |
| 152 | + private String shuffleOnContentDescription; | |
| 153 | + private String shuffleOffContentDescription; | |
| 154 | + //当前屏幕朝向 | |
| 155 | + private final int PORTRAIT_SCREEN = 0;//竖屏 | |
| 156 | + @Nullable | |
| 157 | + private VideoPlayer player; | |
| 158 | + // private CNLiveControlDispatcher controlDispatcher; | |
| 159 | +// @Nullable | |
| 160 | +// private CNLiveBaseVideoControlView.ProgressUpdateListener progressUpdateListener; | |
| 161 | +// @Nullable | |
| 162 | + private PlaybackPreparer playbackPreparer; | |
| 163 | + | |
| 164 | + private boolean isAttachedToWindow; | |
| 165 | + private boolean showMultiWindowTimeBar; | |
| 166 | + private boolean multiWindowTimeBar; | |
| 167 | + private boolean scrubbing; | |
| 168 | + private int rewindMs; | |
| 169 | + private int fastForwardMs; | |
| 170 | + private int showTimeoutMs; | |
| 171 | + private int timeBarMinUpdateIntervalMs; | |
| 172 | + private @RepeatModeUtil.RepeatToggleModes | |
| 173 | + int repeatToggleModes; | |
| 174 | + private boolean showShuffleButton; | |
| 175 | + private long hideAtMs; | |
| 176 | + private long[] adGroupTimesMs; | |
| 177 | + private boolean[] playedAdGroups; | |
| 178 | + private long[] extraAdGroupTimesMs; | |
| 179 | + private boolean[] extraPlayedAdGroups; | |
| 180 | + private long currentWindowOffset; | |
| 181 | + private Activity activity; | |
| 182 | + private TextView tvTopViewTitle;//顶部标题视图 | |
| 183 | + //横竖屏 | |
| 184 | + @Nullable | |
| 185 | + private View switchScreenButton; | |
| 186 | + public View basePortraitView; | |
| 187 | + public View baseLandscapeView; | |
| 188 | + | |
| 189 | + public Context context; | |
| 190 | + | |
| 191 | + public CNLiveBaseVideoControlView(Context context) { | |
| 192 | + this(context, /* attrs= */ null, 0, null); | |
| 193 | + this.context = context; | |
| 194 | + } | |
| 195 | + | |
| 196 | + protected int getControllViewId() { | |
| 197 | + return -1; | |
| 198 | + } | |
| 199 | + | |
| 200 | + protected abstract void onViewClick(View view); | |
| 201 | + | |
| 202 | + @SuppressWarnings({ | |
| 203 | + "nullness:argument.type.incompatible", | |
| 204 | + "nullness:method.invocation.invalid", | |
| 205 | + "nullness:methodref.receiver.bound.invalid" | |
| 206 | + }) | |
| 207 | + public CNLiveBaseVideoControlView( | |
| 208 | + Context context, | |
| 209 | + @Nullable AttributeSet attrs, | |
| 210 | + int defStyleAttr, | |
| 211 | + @Nullable AttributeSet playbackAttrs) { | |
| 212 | + super(context, attrs, defStyleAttr); | |
| 213 | + this.context = context; | |
| 214 | + if (context instanceof Activity) { | |
| 215 | + this.activity = (Activity) context; | |
| 216 | + } | |
| 217 | + componentListener = new CNLiveBaseVideoControlView.ComponentListener(); | |
| 218 | +// controlDispatcher = new CNLiveDefaultControlDispatcher(); | |
| 219 | + | |
| 220 | + controllerLayoutId = R.layout.layout_cnlive_control_view; | |
| 221 | + if (null != playbackAttrs) { | |
| 222 | + rewindMs = DEFAULT_REWIND_MS; | |
| 223 | + fastForwardMs = DEFAULT_FAST_FORWARD_MS; | |
| 224 | + showTimeoutMs = DEFAULT_SHOW_TIMEOUT_MS; | |
| 225 | + repeatToggleModes = DEFAULT_REPEAT_TOGGLE_MODES; | |
| 226 | + timeBarMinUpdateIntervalMs = DEFAULT_TIME_BAR_MIN_UPDATE_INTERVAL_MS; | |
| 227 | + hideAtMs = C.TIME_UNSET; | |
| 228 | + showShuffleButton = false; | |
| 229 | + if (playbackAttrs != null) { | |
| 230 | + TypedArray a = | |
| 231 | + context | |
| 232 | + .getTheme() | |
| 233 | + .obtainStyledAttributes(playbackAttrs, R.styleable.PlayerControlView, 0, 0); | |
| 234 | + try { | |
| 235 | + rewindMs = a.getInt(R.styleable.PlayerControlView_rewind_increment, rewindMs); | |
| 236 | + fastForwardMs = | |
| 237 | + a.getInt(R.styleable.PlayerControlView_fastforward_increment, fastForwardMs); | |
| 238 | + showTimeoutMs = a.getInt(R.styleable.PlayerControlView_show_timeout, showTimeoutMs); | |
| 239 | + controllerLayoutId = | |
| 240 | + a.getResourceId(R.styleable.PlayerControlView_controller_layout_id, controllerLayoutId); | |
| 241 | + repeatToggleModes = getRepeatToggleModes(a, repeatToggleModes); | |
| 242 | + showShuffleButton = | |
| 243 | + a.getBoolean(R.styleable.PlayerControlView_show_shuffle_button, showShuffleButton); | |
| 244 | + setTimeBarMinUpdateInterval( | |
| 245 | + a.getInt( | |
| 246 | + R.styleable.PlayerControlView_time_bar_min_update_interval, | |
| 247 | + timeBarMinUpdateIntervalMs)); | |
| 248 | + } finally { | |
| 249 | + a.recycle(); | |
| 250 | + } | |
| 251 | + } | |
| 252 | + visibilityListeners = new CopyOnWriteArrayList<>(); | |
| 253 | + period = new Timeline.Period(); | |
| 254 | + window = new Timeline.Window(); | |
| 255 | + formatBuilder = new StringBuilder(); | |
| 256 | + formatter = new Formatter(formatBuilder, Locale.getDefault()); | |
| 257 | + adGroupTimesMs = new long[0]; | |
| 258 | + playedAdGroups = new boolean[0]; | |
| 259 | + extraAdGroupTimesMs = new long[0]; | |
| 260 | + extraPlayedAdGroups = new boolean[0]; | |
| 261 | + updateProgressAction = this::updateProgress; | |
| 262 | + hideAction = this::hide; | |
| 263 | + | |
| 264 | + basePortraitView = LayoutInflater.from(context).inflate(controllerLayoutId, /* root= */ this); | |
| 265 | + setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); | |
| 266 | + | |
| 267 | + TimeBar customTimeBar = findViewById(R.id.cnlive_progress); | |
| 268 | + View timeBarPlaceholder = findViewById(R.id.cnlive_progress_placeholder); | |
| 269 | + if (customTimeBar != null) { | |
| 270 | + timeBar = customTimeBar; | |
| 271 | + } else if (timeBarPlaceholder != null) { | |
| 272 | + // Propagate attrs as timebarAttrs so that DefaultTimeBar's custom attributes are transferred, | |
| 273 | + // but standard attributes (e.g. background) are not. | |
| 274 | + BaseTimerBar defaultTimeBar = new BaseTimerBar(context, null, 0, playbackAttrs); | |
| 275 | + //todo 设置播放的颜色 | |
| 276 | +// defaultTimeBar.setPlayedColor(ContextCompat.getColor(context,R.color.qmui_config_color_red)); | |
| 277 | + //todo 设置缓冲颜色 | |
| 278 | +// defaultTimeBar.setBufferedColor(ContextCompat.getColor(context,R.color.qmui_config_color_red)); | |
| 279 | + defaultTimeBar.setId(R.id.cnlive_progress); | |
| 280 | + defaultTimeBar.setLayoutParams(timeBarPlaceholder.getLayoutParams()); | |
| 281 | + ViewGroup parent = ((ViewGroup) timeBarPlaceholder.getParent()); | |
| 282 | + int timeBarIndex = parent.indexOfChild(timeBarPlaceholder); | |
| 283 | + parent.removeView(timeBarPlaceholder); | |
| 284 | + parent.addView(defaultTimeBar, timeBarIndex); | |
| 285 | + timeBar = defaultTimeBar; | |
| 286 | + } else { | |
| 287 | + timeBar = null; | |
| 288 | + } | |
| 289 | + durationView = findViewById(R.id.cnlive_duration); | |
| 290 | + positionView = findViewById(R.id.cnlive_position); | |
| 291 | + //初始化顶部视图 | |
| 292 | + //顶部标题 | |
| 293 | + tvTopViewTitle = findViewById(R.id.cnlive_top_title); | |
| 294 | + //设置标题名称 | |
| 295 | + if (null != tvTopViewTitle) { | |
| 296 | + tvTopViewTitle.setText("测试"); | |
| 297 | + } | |
| 298 | + if (timeBar != null) { | |
| 299 | + timeBar.addListener(componentListener); | |
| 300 | + } | |
| 301 | + playButton = findViewById(R.id.cnlive_play); | |
| 302 | + if (playButton != null) { | |
| 303 | + playButton.setOnClickListener(componentListener); | |
| 304 | + } | |
| 305 | + pauseButton = findViewById(R.id.cnlive_pause); | |
| 306 | + if (pauseButton != null) { | |
| 307 | + pauseButton.setOnClickListener(componentListener); | |
| 308 | + } | |
| 309 | + previousButton = findViewById(R.id.cnlive_prev); | |
| 310 | + if (previousButton != null) { | |
| 311 | + previousButton.setOnClickListener(componentListener); | |
| 312 | + } | |
| 313 | + nextButton = findViewById(R.id.cnlive_next); | |
| 314 | + if (nextButton != null) { | |
| 315 | + nextButton.setOnClickListener(componentListener); | |
| 316 | + } | |
| 317 | + rewindButton = findViewById(R.id.cnlive_rew); | |
| 318 | + if (rewindButton != null) { | |
| 319 | + rewindButton.setOnClickListener(componentListener); | |
| 320 | + } | |
| 321 | + fastForwardButton = findViewById(R.id.cnlive_ffwd); | |
| 322 | + if (fastForwardButton != null) { | |
| 323 | + fastForwardButton.setOnClickListener(componentListener); | |
| 324 | + } | |
| 325 | + repeatToggleButton = findViewById(R.id.cnlive_repeat_toggle); | |
| 326 | + if (repeatToggleButton != null) { | |
| 327 | + repeatToggleButton.setOnClickListener(componentListener); | |
| 328 | + } | |
| 329 | + repeatToggleButton.setVisibility(GONE); | |
| 330 | + shuffleButton = findViewById(R.id.cnlive_shuffle); | |
| 331 | + if (shuffleButton != null) { | |
| 332 | + shuffleButton.setOnClickListener(componentListener); | |
| 333 | + } | |
| 334 | + //初始化横竖屏切换 | |
| 335 | + switchScreenButton = findViewById(R.id.cnlive_switch_screen); | |
| 336 | + if (switchScreenButton != null) { | |
| 337 | + switchScreenButton.setOnClickListener(componentListener); | |
| 338 | + } | |
| 339 | + vrButton = findViewById(R.id.cnlive_vr); | |
| 340 | + setShowVrButton(false); | |
| 341 | + | |
| 342 | + Resources resources = context.getResources(); | |
| 343 | + | |
| 344 | + buttonAlphaEnabled = | |
| 345 | + (float) resources.getInteger(R.integer.exo_media_button_opacity_percentage_enabled) / 100; | |
| 346 | + buttonAlphaDisabled = | |
| 347 | + (float) resources.getInteger(R.integer.exo_media_button_opacity_percentage_disabled) / 100; | |
| 348 | + | |
| 349 | + repeatOffButtonDrawable = resources.getDrawable(R.drawable.exo_controls_repeat_off); | |
| 350 | + repeatOneButtonDrawable = resources.getDrawable(R.drawable.exo_controls_repeat_one); | |
| 351 | + repeatAllButtonDrawable = resources.getDrawable(R.drawable.exo_controls_repeat_all); | |
| 352 | + shuffleOnButtonDrawable = resources.getDrawable(R.drawable.exo_controls_shuffle_on); | |
| 353 | + shuffleOffButtonDrawable = resources.getDrawable(R.drawable.exo_controls_shuffle_off); | |
| 354 | + repeatOffButtonContentDescription = | |
| 355 | + resources.getString(R.string.exo_controls_repeat_off_description); | |
| 356 | + repeatOneButtonContentDescription = | |
| 357 | + resources.getString(R.string.exo_controls_repeat_one_description); | |
| 358 | + repeatAllButtonContentDescription = | |
| 359 | + resources.getString(R.string.exo_controls_repeat_all_description); | |
| 360 | + shuffleOnContentDescription = resources.getString(R.string.exo_controls_shuffle_on_description); | |
| 361 | + shuffleOffContentDescription = | |
| 362 | + resources.getString(R.string.exo_controls_shuffle_off_description); | |
| 363 | + } else { | |
| 364 | + basePortraitView = LayoutInflater.from(context).inflate(getControllViewId(), /* root= */ this); | |
| 365 | + } | |
| 366 | + | |
| 367 | + if (null != componentListener && null != basePortraitView) { | |
| 368 | + initPortraitView(basePortraitView, componentListener); | |
| 369 | + } | |
| 370 | + } | |
| 371 | + | |
| 372 | + | |
| 373 | + protected abstract void initPortraitView(View basePortraitView, CNLiveBaseVideoControlView.ComponentListener onClickListener); | |
| 374 | + | |
| 375 | + @SuppressWarnings("ResourceType") | |
| 376 | + private static @RepeatModeUtil.RepeatToggleModes | |
| 377 | + int getRepeatToggleModes( | |
| 378 | + TypedArray a, @RepeatModeUtil.RepeatToggleModes int repeatToggleModes) { | |
| 379 | + return a.getInt(R.styleable.PlayerControlView_repeat_toggle_modes, repeatToggleModes); | |
| 380 | + } | |
| 381 | + | |
| 382 | + /** | |
| 383 | + * Returns the {@link Player} currently being controlled by this view, or null if no player is | |
| 384 | + * set. | |
| 385 | + */ | |
| 386 | + @Nullable | |
| 387 | + public VideoPlayer getPlayer() { | |
| 388 | + return player; | |
| 389 | + } | |
| 390 | + | |
| 391 | + /** | |
| 392 | + * Sets the {@link Player} to control. | |
| 393 | + * | |
| 394 | + * @param player The {@link Player} to control, or {@code null} to detach the current player. Only | |
| 395 | + * players which are accessed on the main thread are supported ({@code | |
| 396 | + * player.getApplicationLooper() == Looper.getMainLooper()}). | |
| 397 | + */ | |
| 398 | + public void setPlayer(@Nullable VideoPlayer player) { | |
| 399 | + Assertions.checkState(Looper.myLooper() == Looper.getMainLooper()); | |
| 400 | + Assertions.checkArgument( | |
| 401 | + player == null || player.getApplicationLooper() == Looper.getMainLooper()); | |
| 402 | + if (this.player == player) { | |
| 403 | + return; | |
| 404 | + } | |
| 405 | + if (this.player != null && null != componentListener) { | |
| 406 | + this.player.removeListener(componentListener); | |
| 407 | + } | |
| 408 | + this.player = player; | |
| 409 | + if (player != null && null != componentListener) { | |
| 410 | + player.addListener(componentListener); | |
| 411 | + } | |
| 412 | + updateAll(); | |
| 413 | + } | |
| 414 | + | |
| 415 | + /** | |
| 416 | + * Sets whether the time bar should show all windows, as opposed to just the current one. If the | |
| 417 | + * timeline has a period with unknown duration or more than {@link | |
| 418 | + * #MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR} windows the time bar will fall back to showing a single | |
| 419 | + * window. | |
| 420 | + * | |
| 421 | + * @param showMultiWindowTimeBar Whether the time bar should show all windows. | |
| 422 | + */ | |
| 423 | + public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) { | |
| 424 | + this.showMultiWindowTimeBar = showMultiWindowTimeBar; | |
| 425 | + updateTimeline(); | |
| 426 | + } | |
| 427 | + | |
| 428 | + /** | |
| 429 | + * Sets the millisecond positions of extra ad markers relative to the start of the window (or | |
| 430 | + * timeline, if in multi-window mode) and whether each extra ad has been played or not. The | |
| 431 | + * markers are shown in addition to any ad markers for ads in the player's timeline. | |
| 432 | + * | |
| 433 | + * @param extraAdGroupTimesMs The millisecond timestamps of the extra ad markers to show, or | |
| 434 | + * {@code null} to show no extra ad markers. | |
| 435 | + * @param extraPlayedAdGroups Whether each ad has been played. Must be the same length as {@code | |
| 436 | + * extraAdGroupTimesMs}, or {@code null} if {@code extraAdGroupTimesMs} is {@code null}. | |
| 437 | + */ | |
| 438 | + public void setExtraAdGroupMarkers( | |
| 439 | + @Nullable long[] extraAdGroupTimesMs, @Nullable boolean[] extraPlayedAdGroups) { | |
| 440 | + if (extraAdGroupTimesMs == null) { | |
| 441 | + this.extraAdGroupTimesMs = new long[0]; | |
| 442 | + this.extraPlayedAdGroups = new boolean[0]; | |
| 443 | + } else { | |
| 444 | + extraPlayedAdGroups = Assertions.checkNotNull(extraPlayedAdGroups); | |
| 445 | + Assertions.checkArgument(extraAdGroupTimesMs.length == extraPlayedAdGroups.length); | |
| 446 | + this.extraAdGroupTimesMs = extraAdGroupTimesMs; | |
| 447 | + this.extraPlayedAdGroups = extraPlayedAdGroups; | |
| 448 | + } | |
| 449 | + updateTimeline(); | |
| 450 | + } | |
| 451 | + | |
| 452 | + /** | |
| 453 | + * Adds a {@link CNLiveBaseVideoControlView.VisibilityListener}. | |
| 454 | + * | |
| 455 | + * @param listener The listener to be notified about visibility changes. | |
| 456 | + */ | |
| 457 | + public void addVisibilityListener(CNLiveBaseVideoControlView.VisibilityListener listener) { | |
| 458 | + visibilityListeners.add(listener); | |
| 459 | + } | |
| 460 | + | |
| 461 | + /** | |
| 462 | + * Removes a {@link CNLiveBaseVideoControlView.VisibilityListener}. | |
| 463 | + * | |
| 464 | + * @param listener The listener to be removed. | |
| 465 | + */ | |
| 466 | + public void removeVisibilityListener(CNLiveBaseVideoControlView.VisibilityListener listener) { | |
| 467 | + visibilityListeners.remove(listener); | |
| 468 | + } | |
| 469 | + | |
| 470 | + /** | |
| 471 | + * Sets the {@link CNLiveBaseVideoControlView.ProgressUpdateListener}. | |
| 472 | + * | |
| 473 | + * @param listener The listener to be notified about when progress is updated. | |
| 474 | + */ | |
| 475 | + public void setProgressUpdateListener(@Nullable CNLiveBaseVideoControlView.ProgressUpdateListener listener) { | |
| 476 | +// this.progressUpdateListener = listener; | |
| 477 | + | |
| 478 | + } | |
| 479 | + | |
| 480 | + /** | |
| 481 | + * Sets the {@link PlaybackPreparer}. | |
| 482 | + * | |
| 483 | + * @param playbackPreparer The {@link PlaybackPreparer}, or null to remove the current playback | |
| 484 | + * preparer. | |
| 485 | + */ | |
| 486 | + public void setPlaybackPreparer(@Nullable PlaybackPreparer playbackPreparer) { | |
| 487 | + this.playbackPreparer = playbackPreparer; | |
| 488 | + } | |
| 489 | + | |
| 490 | +// /** | |
| 491 | +// * Sets the {@link com.google.android.exoplayer2.ControlDispatcher}. | |
| 492 | +// * | |
| 493 | +// * @param controlDispatcher The {@link com.google.android.exoplayer2.ControlDispatcher}, or null | |
| 494 | +// * to use {@link com.google.android.exoplayer2.DefaultControlDispatcher}. | |
| 495 | +// */ | |
| 496 | +// public void setControlDispatcher( | |
| 497 | +// @Nullable CNLiveControlDispatcher controlDispatcher) { | |
| 498 | +// this.controlDispatcher = | |
| 499 | +// controlDispatcher == null | |
| 500 | +// ? new CNLiveDefaultControlDispatcher() | |
| 501 | +// : controlDispatcher; | |
| 502 | +// } | |
| 503 | + | |
| 504 | + /** | |
| 505 | + * Sets the rewind increment in milliseconds. | |
| 506 | + * | |
| 507 | + * @param rewindMs The rewind increment in milliseconds. A non-positive value will cause the | |
| 508 | + * rewind button to be disabled. | |
| 509 | + */ | |
| 510 | + public void setRewindIncrementMs(int rewindMs) { | |
| 511 | + this.rewindMs = rewindMs; | |
| 512 | + updateNavigation(); | |
| 513 | + } | |
| 514 | + | |
| 515 | + /** | |
| 516 | + * Sets the fast forward increment in milliseconds. | |
| 517 | + * | |
| 518 | + * @param fastForwardMs The fast forward increment in milliseconds. A non-positive value will | |
| 519 | + * cause the fast forward button to be disabled. | |
| 520 | + */ | |
| 521 | + public void setFastForwardIncrementMs(int fastForwardMs) { | |
| 522 | + this.fastForwardMs = fastForwardMs; | |
| 523 | + updateNavigation(); | |
| 524 | + } | |
| 525 | + | |
| 526 | + /** | |
| 527 | + * Returns the playback controls timeout. The playback controls are automatically hidden after | |
| 528 | + * this duration of time has elapsed without user input. | |
| 529 | + * | |
| 530 | + * @return The duration in milliseconds. A non-positive value indicates that the controls will | |
| 531 | + * remain visible indefinitely. | |
| 532 | + */ | |
| 533 | + public int getShowTimeoutMs() { | |
| 534 | + return showTimeoutMs; | |
| 535 | + } | |
| 536 | + | |
| 537 | + /** | |
| 538 | + * Sets the playback controls timeout. The playback controls are automatically hidden after this | |
| 539 | + * duration of time has elapsed without user input. | |
| 540 | + * | |
| 541 | + * @param showTimeoutMs The duration in milliseconds. A non-positive value will cause the controls | |
| 542 | + * to remain visible indefinitely. | |
| 543 | + */ | |
| 544 | + public void setShowTimeoutMs(int showTimeoutMs) { | |
| 545 | + this.showTimeoutMs = showTimeoutMs; | |
| 546 | + if (isVisible()) { | |
| 547 | + // Reset the timeout. | |
| 548 | + hideAfterTimeout(); | |
| 549 | + } | |
| 550 | + } | |
| 551 | + | |
| 552 | + /** | |
| 553 | + * Returns which repeat toggle modes are enabled. | |
| 554 | + * | |
| 555 | + * @return The currently enabled {@link RepeatModeUtil.RepeatToggleModes}. | |
| 556 | + */ | |
| 557 | + public @RepeatModeUtil.RepeatToggleModes | |
| 558 | + int getRepeatToggleModes() { | |
| 559 | + return repeatToggleModes; | |
| 560 | + } | |
| 561 | + | |
| 562 | + /** | |
| 563 | + * Sets which repeat toggle modes are enabled. | |
| 564 | + * | |
| 565 | + * @param repeatToggleModes A set of {@link RepeatModeUtil.RepeatToggleModes}. | |
| 566 | + */ | |
| 567 | + public void setRepeatToggleModes(@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) { | |
| 568 | + this.repeatToggleModes = repeatToggleModes; | |
| 569 | + if (player != null) { | |
| 570 | + @Player.RepeatMode int currentMode = player.getRepeatMode(); | |
| 571 | + if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE | |
| 572 | + && currentMode != Player.REPEAT_MODE_OFF) { | |
| 573 | + setPlayerRepeatMode(Player.REPEAT_MODE_OFF); | |
| 574 | + } else if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE | |
| 575 | + && currentMode == Player.REPEAT_MODE_ALL) { | |
| 576 | + setPlayerRepeatMode(Player.REPEAT_MODE_ONE); | |
| 577 | + } else if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL | |
| 578 | + && currentMode == Player.REPEAT_MODE_ONE) { | |
| 579 | + setPlayerRepeatMode(Player.REPEAT_MODE_ALL); | |
| 580 | + } | |
| 581 | + } | |
| 582 | +// updateRepeatModeButton(); | |
| 583 | + } | |
| 584 | + | |
| 585 | + /** | |
| 586 | + * Returns whether the shuffle button is shown. | |
| 587 | + */ | |
| 588 | + public boolean getShowShuffleButton() { | |
| 589 | + return showShuffleButton; | |
| 590 | + } | |
| 591 | + | |
| 592 | + /** | |
| 593 | + * Sets whether the shuffle button is shown. | |
| 594 | + * | |
| 595 | + * @param showShuffleButton Whether the shuffle button is shown. | |
| 596 | + */ | |
| 597 | + public void setShowShuffleButton(boolean showShuffleButton) { | |
| 598 | + this.showShuffleButton = showShuffleButton; | |
| 599 | + updateShuffleButton(); | |
| 600 | + } | |
| 601 | + | |
| 602 | + /** | |
| 603 | + * Returns whether the VR button is shown. | |
| 604 | + */ | |
| 605 | + public boolean getShowVrButton() { | |
| 606 | + return vrButton != null && vrButton.getVisibility() == VISIBLE; | |
| 607 | + } | |
| 608 | + | |
| 609 | + /** | |
| 610 | + * Sets whether the VR button is shown. | |
| 611 | + * | |
| 612 | + * @param showVrButton Whether the VR button is shown. | |
| 613 | + */ | |
| 614 | + public void setShowVrButton(boolean showVrButton) { | |
| 615 | + if (vrButton != null) { | |
| 616 | + vrButton.setVisibility(showVrButton ? VISIBLE : GONE); | |
| 617 | + } | |
| 618 | + } | |
| 619 | + | |
| 620 | + /** | |
| 621 | + * Sets listener for the VR button. | |
| 622 | + * | |
| 623 | + * @param onClickListener Listener for the VR button, or null to clear the listener. | |
| 624 | + */ | |
| 625 | + public void setVrButtonListener(@Nullable OnClickListener onClickListener) { | |
| 626 | + if (vrButton != null) { | |
| 627 | + vrButton.setOnClickListener(onClickListener); | |
| 628 | + } | |
| 629 | + } | |
| 630 | + | |
| 631 | + /** | |
| 632 | + * Sets the minimum interval between time bar position updates. | |
| 633 | + * | |
| 634 | + * <p>Note that smaller intervals, e.g. 33ms, will result in a smooth movement but will use more | |
| 635 | + * CPU resources while the time bar is visible, whereas larger intervals, e.g. 200ms, will result | |
| 636 | + * in a step-wise update with less CPU usage. | |
| 637 | + * | |
| 638 | + * @param minUpdateIntervalMs The minimum interval between time bar position updates, in | |
| 639 | + * milliseconds. | |
| 640 | + */ | |
| 641 | + public void setTimeBarMinUpdateInterval(int minUpdateIntervalMs) { | |
| 642 | + // Do not accept values below 16ms (60fps) and larger than the maximum update interval. | |
| 643 | + timeBarMinUpdateIntervalMs = | |
| 644 | + Util.constrainValue(minUpdateIntervalMs, 16, MAX_UPDATE_INTERVAL_MS); | |
| 645 | + } | |
| 646 | + | |
| 647 | + /** | |
| 648 | + * Shows the playback controls. If {@link #getShowTimeoutMs()} is positive then the controls will | |
| 649 | + * be automatically hidden after this duration of time has elapsed without user input. | |
| 650 | + */ | |
| 651 | + public void show() { | |
| 652 | + if (!isVisible()) { | |
| 653 | +// setVisibility(VISIBLE); | |
| 654 | + if (null != basePortraitView) { | |
| 655 | + basePortraitView.setVisibility(VISIBLE); | |
| 656 | + } | |
| 657 | + if (null != baseLandscapeView) { | |
| 658 | + baseLandscapeView.setVisibility(GONE); | |
| 659 | + } | |
| 660 | + if (null != visibilityListeners && visibilityListeners.size() > 0) { | |
| 661 | + for (CNLiveBaseVideoControlView.VisibilityListener visibilityListener : visibilityListeners) { | |
| 662 | + visibilityListener.onVisibilityChange(getVisibility()); | |
| 663 | + } | |
| 664 | + } | |
| 665 | + updateAll(); | |
| 666 | + requestPlayPauseFocus(); | |
| 667 | + } | |
| 668 | + // Call hideAfterTimeout even if already visible to reset the timeout. | |
| 669 | + hideAfterTimeout(); | |
| 670 | + } | |
| 671 | + | |
| 672 | + /** | |
| 673 | + * Hides the controller. | |
| 674 | + */ | |
| 675 | + public void hide() { | |
| 676 | + if (isVisible()) { | |
| 677 | +// setVisibility(GONE); | |
| 678 | + if (null != basePortraitView) { | |
| 679 | + basePortraitView.setVisibility(GONE); | |
| 680 | + } | |
| 681 | + if (null != visibilityListeners && visibilityListeners.size() > 0) { | |
| 682 | + for (CNLiveBaseVideoControlView.VisibilityListener visibilityListener : visibilityListeners) { | |
| 683 | + visibilityListener.onVisibilityChange(getVisibility()); | |
| 684 | + } | |
| 685 | + } | |
| 686 | + removeCallbacks(updateProgressAction); | |
| 687 | + removeCallbacks(hideAction); | |
| 688 | + hideAtMs = C.TIME_UNSET; | |
| 689 | + } | |
| 690 | + } | |
| 691 | + | |
| 692 | + /** | |
| 693 | + * Returns whether the controller is currently visible. | |
| 694 | + */ | |
| 695 | + public boolean isVisible() { | |
| 696 | + return getVisibility() == VISIBLE; | |
| 697 | + } | |
| 698 | + | |
| 699 | + private void hideAfterTimeout() { | |
| 700 | + removeCallbacks(hideAction); | |
| 701 | + if (showTimeoutMs > 0) { | |
| 702 | + hideAtMs = SystemClock.uptimeMillis() + showTimeoutMs; | |
| 703 | + if (isAttachedToWindow) { | |
| 704 | + postDelayed(hideAction, showTimeoutMs); | |
| 705 | + } | |
| 706 | + } else { | |
| 707 | + hideAtMs = C.TIME_UNSET; | |
| 708 | + } | |
| 709 | + } | |
| 710 | + | |
| 711 | + private void updateAll() { | |
| 712 | + updatePlayPauseButton(); | |
| 713 | + updateNavigation(); | |
| 714 | +// updateRepeatModeButton(); | |
| 715 | + updateShuffleButton(); | |
| 716 | + updateTimeline(); | |
| 717 | + } | |
| 718 | + | |
| 719 | + private void updatePlayPauseButton() { | |
| 720 | + if (!isVisible() || !isAttachedToWindow) { | |
| 721 | + return; | |
| 722 | + } | |
| 723 | + boolean requestPlayPauseFocus = false; | |
| 724 | + boolean shouldShowPauseButton = shouldShowPauseButton(); | |
| 725 | + if (playButton != null) { | |
| 726 | + requestPlayPauseFocus |= shouldShowPauseButton && playButton.isFocused(); | |
| 727 | + playButton.setVisibility(shouldShowPauseButton ? GONE : VISIBLE); | |
| 728 | + } | |
| 729 | + if (pauseButton != null) { | |
| 730 | + requestPlayPauseFocus |= !shouldShowPauseButton && pauseButton.isFocused(); | |
| 731 | + pauseButton.setVisibility(shouldShowPauseButton ? VISIBLE : GONE); | |
| 732 | + } | |
| 733 | + if (requestPlayPauseFocus) { | |
| 734 | + requestPlayPauseFocus(); | |
| 735 | + } | |
| 736 | + } | |
| 737 | + | |
| 738 | + private void updateNavigation() { | |
| 739 | + if (!isVisible() || !isAttachedToWindow) { | |
| 740 | + return; | |
| 741 | + } | |
| 742 | + | |
| 743 | + @Nullable VideoPlayer player = this.player; | |
| 744 | + boolean enableSeeking = false; | |
| 745 | + boolean enablePrevious = false; | |
| 746 | + boolean enableRewind = false; | |
| 747 | + boolean enableFastForward = false; | |
| 748 | + boolean enableNext = false; | |
| 749 | + if (player != null) { | |
| 750 | + Timeline timeline = player.getCurrentTimeline(); | |
| 751 | + if (!timeline.isEmpty() && !player.isPlayingAd()) { | |
| 752 | + timeline.getWindow(player.getCurrentWindowIndex(), window); | |
| 753 | + boolean isSeekable = window.isSeekable; | |
| 754 | + enableSeeking = isSeekable; | |
| 755 | + enablePrevious = isSeekable || !window.isDynamic || player.hasPrevious(); | |
| 756 | + enableRewind = isSeekable && rewindMs > 0; | |
| 757 | + enableFastForward = isSeekable && fastForwardMs > 0; | |
| 758 | + enableNext = window.isDynamic || player.hasNext(); | |
| 759 | + } | |
| 760 | + } | |
| 761 | + | |
| 762 | + setButtonEnabled(enablePrevious, previousButton); | |
| 763 | + setButtonEnabled(enableRewind, rewindButton); | |
| 764 | + setButtonEnabled(enableFastForward, fastForwardButton); | |
| 765 | + setButtonEnabled(enableNext, nextButton); | |
| 766 | + if (timeBar != null) { | |
| 767 | + timeBar.setEnabled(enableSeeking); | |
| 768 | + } | |
| 769 | + } | |
| 770 | +// | |
| 771 | +// private void updateRepeatModeButton() { | |
| 772 | +// if (!isVisible() || !isAttachedToWindow || repeatToggleButton == null) { | |
| 773 | +// return; | |
| 774 | +// } | |
| 775 | +// | |
| 776 | +// if (repeatToggleModes == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE) { | |
| 777 | +// repeatToggleButton.setVisibility(VISIBLE); | |
| 778 | +// return; | |
| 779 | +// } | |
| 780 | +// | |
| 781 | +// @Nullable Player player = this.player; | |
| 782 | +// if (player == null) { | |
| 783 | +// setButtonEnabled(false, repeatToggleButton); | |
| 784 | +// repeatToggleButton.setImageDrawable(repeatOffButtonDrawable); | |
| 785 | +// repeatToggleButton.setContentDescription(repeatOffButtonContentDescription); | |
| 786 | +// return; | |
| 787 | +// } | |
| 788 | +// | |
| 789 | +// setButtonEnabled(true, repeatToggleButton); | |
| 790 | +// switch (player.getRepeatMode()) { | |
| 791 | +// case Player.REPEAT_MODE_OFF: | |
| 792 | +// repeatToggleButton.setImageDrawable(repeatOffButtonDrawable); | |
| 793 | +// repeatToggleButton.setContentDescription(repeatOffButtonContentDescription); | |
| 794 | +// break; | |
| 795 | +// case Player.REPEAT_MODE_ONE: | |
| 796 | +// repeatToggleButton.setImageDrawable(repeatOneButtonDrawable); | |
| 797 | +// repeatToggleButton.setContentDescription(repeatOneButtonContentDescription); | |
| 798 | +// break; | |
| 799 | +// case Player.REPEAT_MODE_ALL: | |
| 800 | +// repeatToggleButton.setImageDrawable(repeatAllButtonDrawable); | |
| 801 | +// repeatToggleButton.setContentDescription(repeatAllButtonContentDescription); | |
| 802 | +// break; | |
| 803 | +// default: | |
| 804 | +// // Never happens. | |
| 805 | +// } | |
| 806 | +// repeatToggleButton.setVisibility(VISIBLE); | |
| 807 | +// } | |
| 808 | + | |
| 809 | + private void updateShuffleButton() { | |
| 810 | + if (!isVisible() || !isAttachedToWindow || shuffleButton == null) { | |
| 811 | + return; | |
| 812 | + } | |
| 813 | + | |
| 814 | + @Nullable VideoPlayer player = this.player; | |
| 815 | + if (!showShuffleButton) { | |
| 816 | + shuffleButton.setVisibility(GONE); | |
| 817 | + } else if (player == null) { | |
| 818 | + setButtonEnabled(false, shuffleButton); | |
| 819 | + shuffleButton.setImageDrawable(shuffleOffButtonDrawable); | |
| 820 | + shuffleButton.setContentDescription(shuffleOffContentDescription); | |
| 821 | + } else { | |
| 822 | + setButtonEnabled(true, shuffleButton); | |
| 823 | + shuffleButton.setImageDrawable( | |
| 824 | + player.getShuffleModeEnabled() ? shuffleOnButtonDrawable : shuffleOffButtonDrawable); | |
| 825 | + shuffleButton.setContentDescription( | |
| 826 | + player.getShuffleModeEnabled() | |
| 827 | + ? shuffleOnContentDescription | |
| 828 | + : shuffleOffContentDescription); | |
| 829 | + } | |
| 830 | + } | |
| 831 | + | |
| 832 | + private void updateTimeline() { | |
| 833 | + @Nullable VideoPlayer player = this.player; | |
| 834 | + if (player == null) { | |
| 835 | + return; | |
| 836 | + } | |
| 837 | + if (null == adGroupTimesMs || null == playedAdGroups) { | |
| 838 | + return; | |
| 839 | + } | |
| 840 | + multiWindowTimeBar = | |
| 841 | + showMultiWindowTimeBar && canShowMultiWindowTimeBar(player.getCurrentTimeline(), window); | |
| 842 | + currentWindowOffset = 0; | |
| 843 | + long durationUs = 0; | |
| 844 | + int adGroupCount = 0; | |
| 845 | + Timeline timeline = player.getCurrentTimeline(); | |
| 846 | + if (!timeline.isEmpty()) { | |
| 847 | + int currentWindowIndex = player.getCurrentWindowIndex(); | |
| 848 | + int firstWindowIndex = multiWindowTimeBar ? 0 : currentWindowIndex; | |
| 849 | + int lastWindowIndex = multiWindowTimeBar ? timeline.getWindowCount() - 1 : currentWindowIndex; | |
| 850 | + for (int i = firstWindowIndex; i <= lastWindowIndex; i++) { | |
| 851 | + if (i == currentWindowIndex) { | |
| 852 | + currentWindowOffset = C.usToMs(durationUs); | |
| 853 | + } | |
| 854 | + timeline.getWindow(i, window); | |
| 855 | + if (window.durationUs == C.TIME_UNSET) { | |
| 856 | + Assertions.checkState(!multiWindowTimeBar); | |
| 857 | + break; | |
| 858 | + } | |
| 859 | + for (int j = window.firstPeriodIndex; j <= window.lastPeriodIndex; j++) { | |
| 860 | + timeline.getPeriod(j, period); | |
| 861 | + int periodAdGroupCount = period.getAdGroupCount(); | |
| 862 | + for (int adGroupIndex = 0; adGroupIndex < periodAdGroupCount; adGroupIndex++) { | |
| 863 | + long adGroupTimeInPeriodUs = period.getAdGroupTimeUs(adGroupIndex); | |
| 864 | + if (adGroupTimeInPeriodUs == C.TIME_END_OF_SOURCE) { | |
| 865 | + if (period.durationUs == C.TIME_UNSET) { | |
| 866 | + // Don't show ad markers for postrolls in periods with unknown duration. | |
| 867 | + continue; | |
| 868 | + } | |
| 869 | + adGroupTimeInPeriodUs = period.durationUs; | |
| 870 | + } | |
| 871 | + long adGroupTimeInWindowUs = adGroupTimeInPeriodUs + period.getPositionInWindowUs(); | |
| 872 | + if (adGroupTimeInWindowUs >= 0 && adGroupTimeInWindowUs <= window.durationUs) { | |
| 873 | + if (adGroupCount == adGroupTimesMs.length) { | |
| 874 | + int newLength = adGroupTimesMs.length == 0 ? 1 : adGroupTimesMs.length * 2; | |
| 875 | + adGroupTimesMs = Arrays.copyOf(adGroupTimesMs, newLength); | |
| 876 | + playedAdGroups = Arrays.copyOf(playedAdGroups, newLength); | |
| 877 | + } | |
| 878 | + adGroupTimesMs[adGroupCount] = C.usToMs(durationUs + adGroupTimeInWindowUs); | |
| 879 | + playedAdGroups[adGroupCount] = period.hasPlayedAdGroup(adGroupIndex); | |
| 880 | + adGroupCount++; | |
| 881 | + } | |
| 882 | + } | |
| 883 | + } | |
| 884 | + durationUs += window.durationUs; | |
| 885 | + } | |
| 886 | + } | |
| 887 | + long durationMs = C.usToMs(durationUs); | |
| 888 | + if (durationView != null) { | |
| 889 | + durationView.setText(Util.getStringForTime(formatBuilder, formatter, durationMs)); | |
| 890 | + } | |
| 891 | + if (timeBar != null) { | |
| 892 | + timeBar.setDuration(durationMs); | |
| 893 | + int extraAdGroupCount = extraAdGroupTimesMs.length; | |
| 894 | + int totalAdGroupCount = adGroupCount + extraAdGroupCount; | |
| 895 | + if (totalAdGroupCount > adGroupTimesMs.length) { | |
| 896 | + adGroupTimesMs = Arrays.copyOf(adGroupTimesMs, totalAdGroupCount); | |
| 897 | + playedAdGroups = Arrays.copyOf(playedAdGroups, totalAdGroupCount); | |
| 898 | + } | |
| 899 | + System.arraycopy(extraAdGroupTimesMs, 0, adGroupTimesMs, adGroupCount, extraAdGroupCount); | |
| 900 | + System.arraycopy(extraPlayedAdGroups, 0, playedAdGroups, adGroupCount, extraAdGroupCount); | |
| 901 | + timeBar.setAdGroupTimesMs(adGroupTimesMs, playedAdGroups, totalAdGroupCount); | |
| 902 | + } | |
| 903 | + updateProgress(); | |
| 904 | + } | |
| 905 | + | |
| 906 | + private void updateProgress() { | |
| 907 | + if (!isVisible() || !isAttachedToWindow) { | |
| 908 | + return; | |
| 909 | + } | |
| 910 | + | |
| 911 | + @Nullable VideoPlayer player = this.player; | |
| 912 | + long position = 0; | |
| 913 | + long bufferedPosition = 0; | |
| 914 | + if (player != null) { | |
| 915 | + position = currentWindowOffset + player.getContentPosition(); | |
| 916 | + bufferedPosition = currentWindowOffset + player.getContentBufferedPosition(); | |
| 917 | + } | |
| 918 | + if (positionView != null && !scrubbing) { | |
| 919 | + positionView.setText(Util.getStringForTime(formatBuilder, formatter, position)); | |
| 920 | + } | |
| 921 | + if (timeBar != null) { | |
| 922 | + timeBar.setPosition(position); | |
| 923 | + timeBar.setBufferedPosition(bufferedPosition); | |
| 924 | + } | |
| 925 | +// if (progressUpdateListener != null) { | |
| 926 | +// progressUpdateListener.onProgressUpdate(position, bufferedPosition); | |
| 927 | +// } | |
| 928 | + | |
| 929 | + // Cancel any pending updates and schedule a new one if necessary. | |
| 930 | + removeCallbacks(updateProgressAction); | |
| 931 | + int playbackState = player == null ? Player.STATE_IDLE : player.getPlaybackState(); | |
| 932 | + if (player != null && player.isPlaying()) { | |
| 933 | + long mediaTimeDelayMs = | |
| 934 | + timeBar != null ? timeBar.getPreferredUpdateDelay() : MAX_UPDATE_INTERVAL_MS; | |
| 935 | + | |
| 936 | + // Limit delay to the start of the next full second to ensure position display is smooth. | |
| 937 | + long mediaTimeUntilNextFullSecondMs = 1000 - position % 1000; | |
| 938 | + mediaTimeDelayMs = Math.min(mediaTimeDelayMs, mediaTimeUntilNextFullSecondMs); | |
| 939 | + | |
| 940 | + // Calculate the delay until the next update in real time, taking playbackSpeed into account. | |
| 941 | + float playbackSpeed = player.getPlaybackParameters().speed; | |
| 942 | + long delayMs = | |
| 943 | + playbackSpeed > 0 ? (long) (mediaTimeDelayMs / playbackSpeed) : MAX_UPDATE_INTERVAL_MS; | |
| 944 | + | |
| 945 | + // Constrain the delay to avoid too frequent / infrequent updates. | |
| 946 | + delayMs = Util.constrainValue(delayMs, timeBarMinUpdateIntervalMs, MAX_UPDATE_INTERVAL_MS); | |
| 947 | + postDelayed(updateProgressAction, delayMs); | |
| 948 | + } else if (playbackState != Player.STATE_ENDED && playbackState != Player.STATE_IDLE) { | |
| 949 | + postDelayed(updateProgressAction, MAX_UPDATE_INTERVAL_MS); | |
| 950 | + } | |
| 951 | + } | |
| 952 | + | |
| 953 | + private void requestPlayPauseFocus() { | |
| 954 | + boolean shouldShowPauseButton = shouldShowPauseButton(); | |
| 955 | + if (!shouldShowPauseButton && playButton != null) { | |
| 956 | + playButton.requestFocus(); | |
| 957 | + } else if (shouldShowPauseButton && pauseButton != null) { | |
| 958 | + pauseButton.requestFocus(); | |
| 959 | + } | |
| 960 | + } | |
| 961 | + | |
| 962 | + private void setButtonEnabled(boolean enabled, @Nullable View view) { | |
| 963 | + if (view == null) { | |
| 964 | + return; | |
| 965 | + } | |
| 966 | + view.setEnabled(enabled); | |
| 967 | + view.setAlpha(enabled ? buttonAlphaEnabled : buttonAlphaDisabled); | |
| 968 | + view.setVisibility(VISIBLE); | |
| 969 | + } | |
| 970 | + | |
| 971 | + private void previous(VideoPlayer player) { | |
| 972 | + Timeline timeline = player.getCurrentTimeline(); | |
| 973 | + if (timeline.isEmpty() || player.isPlayingAd()) { | |
| 974 | + return; | |
| 975 | + } | |
| 976 | + int windowIndex = player.getCurrentWindowIndex(); | |
| 977 | + timeline.getWindow(windowIndex, window); | |
| 978 | + int previousWindowIndex = player.getPreviousWindowIndex(); | |
| 979 | + if (previousWindowIndex != C.INDEX_UNSET | |
| 980 | + && (player.getCurrentPosition() <= MAX_POSITION_FOR_SEEK_TO_PREVIOUS | |
| 981 | + || (window.isDynamic && !window.isSeekable))) { | |
| 982 | + seekTo(player, previousWindowIndex, C.TIME_UNSET); | |
| 983 | + } else { | |
| 984 | + seekTo(player, windowIndex, /* positionMs= */ 0); | |
| 985 | + } | |
| 986 | + } | |
| 987 | + | |
| 988 | + /** | |
| 989 | + * 播放下一个视频 | |
| 990 | + * | |
| 991 | + * @param player | |
| 992 | + */ | |
| 993 | + public void next(VideoPlayer player) { | |
| 994 | + Timeline timeline = player.getCurrentTimeline(); | |
| 995 | + if (timeline.isEmpty() || player.isPlayingAd()) { | |
| 996 | + return; | |
| 997 | + } | |
| 998 | + int windowIndex = player.getCurrentWindowIndex(); | |
| 999 | + int nextWindowIndex = player.getNextWindowIndex(); | |
| 1000 | + if (nextWindowIndex != C.INDEX_UNSET) { | |
| 1001 | + seekTo(player, nextWindowIndex, C.TIME_UNSET); | |
| 1002 | + } else if (timeline.getWindow(windowIndex, window).isDynamic) { | |
| 1003 | + seekTo(player, windowIndex, C.TIME_UNSET); | |
| 1004 | + } | |
| 1005 | + } | |
| 1006 | + | |
| 1007 | + public void rewind(VideoPlayer player) { | |
| 1008 | + if (player.isCurrentWindowSeekable() && rewindMs > 0) { | |
| 1009 | + seekToOffset(player, -rewindMs); | |
| 1010 | + } | |
| 1011 | + } | |
| 1012 | + | |
| 1013 | + | |
| 1014 | + private void seekToOffset(VideoPlayer player, long offsetMs) { | |
| 1015 | + long positionMs = player.getCurrentPosition() + offsetMs; | |
| 1016 | + long durationMs = player.getDuration(); | |
| 1017 | + if (durationMs != C.TIME_UNSET) { | |
| 1018 | + positionMs = Math.min(positionMs, durationMs); | |
| 1019 | + } | |
| 1020 | + positionMs = Math.max(positionMs, 0); | |
| 1021 | + seekTo(player, player.getCurrentWindowIndex(), positionMs); | |
| 1022 | + } | |
| 1023 | + | |
| 1024 | + /** | |
| 1025 | + * 移动进度条,移动画面 | |
| 1026 | + * | |
| 1027 | + * @param player | |
| 1028 | + * @param positionMs | |
| 1029 | + */ | |
| 1030 | + public void seekToTimeBarPosition(VideoPlayer player, long positionMs) { | |
| 1031 | + int windowIndex; | |
| 1032 | + Timeline timeline = player.getCurrentTimeline(); | |
| 1033 | + if (multiWindowTimeBar && !timeline.isEmpty()) { | |
| 1034 | + int windowCount = timeline.getWindowCount(); | |
| 1035 | + windowIndex = 0; | |
| 1036 | + while (true) { | |
| 1037 | + long windowDurationMs = timeline.getWindow(windowIndex, window).getDurationMs(); | |
| 1038 | + if (positionMs < windowDurationMs) { | |
| 1039 | + break; | |
| 1040 | + } else if (windowIndex == windowCount - 1) { | |
| 1041 | + // Seeking past the end of the last window should seek to the end of the timeline. | |
| 1042 | + positionMs = windowDurationMs; | |
| 1043 | + break; | |
| 1044 | + } | |
| 1045 | + positionMs -= windowDurationMs; | |
| 1046 | + windowIndex++; | |
| 1047 | + } | |
| 1048 | + } else { | |
| 1049 | + windowIndex = player.getCurrentWindowIndex(); | |
| 1050 | + } | |
| 1051 | + boolean dispatched = seekTo(player, windowIndex, positionMs); | |
| 1052 | + if (!dispatched) { | |
| 1053 | + // The seek wasn't dispatched then the progress bar scrubber will be in the wrong position. | |
| 1054 | + // Trigger a progress update to snap it back. | |
| 1055 | + updateProgress(); | |
| 1056 | + } | |
| 1057 | + } | |
| 1058 | + | |
| 1059 | + private boolean seekTo(VideoPlayer player, int windowIndex, long positionMs) { | |
| 1060 | + player.seekTo(windowIndex, positionMs); | |
| 1061 | + return true; | |
| 1062 | +// return controlDispatcher.dispatchSeekTo(player, windowIndex, positionMs); | |
| 1063 | + } | |
| 1064 | + | |
| 1065 | + @Override | |
| 1066 | + public void onAttachedToWindow() { | |
| 1067 | + super.onAttachedToWindow(); | |
| 1068 | +// isAttachedToWindow = true; | |
| 1069 | +// if (hideAtMs != C.TIME_UNSET) { | |
| 1070 | +// long delayMs = hideAtMs - SystemClock.uptimeMillis(); | |
| 1071 | +// if (delayMs <= 0) { | |
| 1072 | +// hide(); | |
| 1073 | +// } else { | |
| 1074 | +// postDelayed(hideAction, delayMs); | |
| 1075 | +// } | |
| 1076 | +// } else if (isVisible()) { | |
| 1077 | +// hideAfterTimeout(); | |
| 1078 | +// } | |
| 1079 | +// updateAll(); | |
| 1080 | + } | |
| 1081 | + | |
| 1082 | + @Override | |
| 1083 | + public void onDetachedFromWindow() { | |
| 1084 | + super.onDetachedFromWindow(); | |
| 1085 | + isAttachedToWindow = false; | |
| 1086 | + removeCallbacks(updateProgressAction); | |
| 1087 | + removeCallbacks(hideAction); | |
| 1088 | + } | |
| 1089 | + | |
| 1090 | + @Override | |
| 1091 | + public final boolean dispatchTouchEvent(MotionEvent ev) { | |
| 1092 | + if (ev.getAction() == MotionEvent.ACTION_DOWN) { | |
| 1093 | + removeCallbacks(hideAction); | |
| 1094 | + } else if (ev.getAction() == MotionEvent.ACTION_UP) { | |
| 1095 | + hideAfterTimeout(); | |
| 1096 | + } | |
| 1097 | + return super.dispatchTouchEvent(ev); | |
| 1098 | + } | |
| 1099 | + | |
| 1100 | + @Override | |
| 1101 | + public boolean dispatchKeyEvent(KeyEvent event) { | |
| 1102 | + return dispatchMediaKeyEvent(event) || super.dispatchKeyEvent(event); | |
| 1103 | + } | |
| 1104 | + | |
| 1105 | + /** | |
| 1106 | + * Called to process media key events. Any {@link KeyEvent} can be passed but only media key | |
| 1107 | + * events will be handled. | |
| 1108 | + * | |
| 1109 | + * @param event A key event. | |
| 1110 | + * @return Whether the key event was handled. | |
| 1111 | + */ | |
| 1112 | + public boolean dispatchMediaKeyEvent(KeyEvent event) { | |
| 1113 | + int keyCode = event.getKeyCode(); | |
| 1114 | + @Nullable VideoPlayer player = this.player; | |
| 1115 | + if (player == null || !isHandledMediaKey(keyCode)) { | |
| 1116 | + return false; | |
| 1117 | + } | |
| 1118 | + if (event.getAction() == KeyEvent.ACTION_DOWN) { | |
| 1119 | + if (keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) { | |
| 1120 | + fastForward(player); | |
| 1121 | + } else if (keyCode == KeyEvent.KEYCODE_MEDIA_REWIND) { | |
| 1122 | + rewind(player); | |
| 1123 | + } else if (event.getRepeatCount() == 0) { | |
| 1124 | + switch (keyCode) { | |
| 1125 | + case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: | |
| 1126 | + pauseVideo(); | |
| 1127 | +// controlDispatcher.dispatchSetPlayWhenReady(player, !player.getPlayWhenReady()); | |
| 1128 | + break; | |
| 1129 | + case KeyEvent.KEYCODE_MEDIA_PLAY: | |
| 1130 | + //播放视频 | |
| 1131 | + playVideo(); | |
| 1132 | + break; | |
| 1133 | + case KeyEvent.KEYCODE_MEDIA_PAUSE: | |
| 1134 | + //暂停视频 | |
| 1135 | + pauseVideo(); | |
| 1136 | + break; | |
| 1137 | + case KeyEvent.KEYCODE_MEDIA_NEXT: | |
| 1138 | + next(player); | |
| 1139 | + break; | |
| 1140 | + case KeyEvent.KEYCODE_MEDIA_PREVIOUS: | |
| 1141 | + previous(player); | |
| 1142 | + break; | |
| 1143 | + default: | |
| 1144 | + break; | |
| 1145 | + } | |
| 1146 | + } | |
| 1147 | + } | |
| 1148 | + return true; | |
| 1149 | + } | |
| 1150 | + | |
| 1151 | + private boolean shouldShowPauseButton() { | |
| 1152 | + return player != null | |
| 1153 | + && player.getPlaybackState() != Player.STATE_ENDED | |
| 1154 | + && player.getPlaybackState() != Player.STATE_IDLE | |
| 1155 | + && player.getPlayWhenReady(); | |
| 1156 | + } | |
| 1157 | + | |
| 1158 | + @SuppressLint("InlinedApi") | |
| 1159 | + private static boolean isHandledMediaKey(int keyCode) { | |
| 1160 | + return keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD | |
| 1161 | + || keyCode == KeyEvent.KEYCODE_MEDIA_REWIND | |
| 1162 | + || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE | |
| 1163 | + || keyCode == KeyEvent.KEYCODE_MEDIA_PLAY | |
| 1164 | + || keyCode == KeyEvent.KEYCODE_MEDIA_PAUSE | |
| 1165 | + || keyCode == KeyEvent.KEYCODE_MEDIA_NEXT | |
| 1166 | + || keyCode == KeyEvent.KEYCODE_MEDIA_PREVIOUS; | |
| 1167 | + } | |
| 1168 | + | |
| 1169 | + /** | |
| 1170 | + * Returns whether the specified {@code timeline} can be shown on a multi-window time bar. | |
| 1171 | + * | |
| 1172 | + * @param timeline The {@link Timeline} to check. | |
| 1173 | + * @param window A scratch {@link Timeline.Window} instance. | |
| 1174 | + * @return Whether the specified timeline can be shown on a multi-window time bar. | |
| 1175 | + */ | |
| 1176 | + private static boolean canShowMultiWindowTimeBar(Timeline timeline, Timeline.Window window) { | |
| 1177 | + if (timeline.getWindowCount() > MAX_WINDOWS_FOR_MULTI_WINDOW_TIME_BAR) { | |
| 1178 | + return false; | |
| 1179 | + } | |
| 1180 | + int windowCount = timeline.getWindowCount(); | |
| 1181 | + for (int i = 0; i < windowCount; i++) { | |
| 1182 | + if (timeline.getWindow(i, window).durationUs == C.TIME_UNSET) { | |
| 1183 | + return false; | |
| 1184 | + } | |
| 1185 | + } | |
| 1186 | + return true; | |
| 1187 | + } | |
| 1188 | + | |
| 1189 | + public final class ComponentListener | |
| 1190 | + implements Player.EventListener, TimeBar.OnScrubListener, OnClickListener { | |
| 1191 | + | |
| 1192 | + @Override | |
| 1193 | + public void onScrubStart(TimeBar timeBar, long position) { | |
| 1194 | + scrubbing = true; | |
| 1195 | + if (positionView != null) { | |
| 1196 | + positionView.setText(Util.getStringForTime(formatBuilder, formatter, position)); | |
| 1197 | + } | |
| 1198 | + } | |
| 1199 | + | |
| 1200 | + @Override | |
| 1201 | + public void onScrubMove(TimeBar timeBar, long position) { | |
| 1202 | + if (positionView != null) { | |
| 1203 | + positionView.setText(Util.getStringForTime(formatBuilder, formatter, position)); | |
| 1204 | + } | |
| 1205 | + } | |
| 1206 | + | |
| 1207 | + @Override | |
| 1208 | + public void onScrubStop(TimeBar timeBar, long position, boolean canceled) { | |
| 1209 | + scrubbing = false; | |
| 1210 | + if (!canceled && player != null) { | |
| 1211 | + seekToTimeBarPosition(player, position); | |
| 1212 | + } | |
| 1213 | + } | |
| 1214 | + | |
| 1215 | + @Override | |
| 1216 | + public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) { | |
| 1217 | + updatePlayPauseButton(); | |
| 1218 | + updateProgress(); | |
| 1219 | + } | |
| 1220 | + | |
| 1221 | + @Override | |
| 1222 | + public void onIsPlayingChanged(boolean isPlaying) { | |
| 1223 | + updateProgress(); | |
| 1224 | + } | |
| 1225 | + | |
| 1226 | + @Override | |
| 1227 | + public void onRepeatModeChanged(int repeatMode) { | |
| 1228 | +// updateRepeatModeButton(); | |
| 1229 | + updateNavigation(); | |
| 1230 | + } | |
| 1231 | + | |
| 1232 | + @Override | |
| 1233 | + public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { | |
| 1234 | + updateShuffleButton(); | |
| 1235 | + updateNavigation(); | |
| 1236 | + } | |
| 1237 | + | |
| 1238 | + @Override | |
| 1239 | + public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) { | |
| 1240 | + updateNavigation(); | |
| 1241 | + updateTimeline(); | |
| 1242 | + } | |
| 1243 | + | |
| 1244 | + @Override | |
| 1245 | + public void onTimelineChanged(Timeline timeline, @Player.TimelineChangeReason int reason) { | |
| 1246 | + updateNavigation(); | |
| 1247 | + updateTimeline(); | |
| 1248 | + } | |
| 1249 | + | |
| 1250 | + @Override | |
| 1251 | + public void onClick(View view) { | |
| 1252 | + onViewClick(view); | |
| 1253 | + VideoPlayer player = CNLiveBaseVideoControlView.this.player; | |
| 1254 | + if (player == null) { | |
| 1255 | + return; | |
| 1256 | + } | |
| 1257 | + if (nextButton == view) { | |
| 1258 | + next(player); | |
| 1259 | + } else if (previousButton == view) { | |
| 1260 | + previous(player); | |
| 1261 | + } else if (fastForwardButton == view) { | |
| 1262 | + fastForward(player); | |
| 1263 | + } else if (rewindButton == view) { | |
| 1264 | + rewind(player); | |
| 1265 | + } else if (playButton == view) { | |
| 1266 | + if (player.getPlaybackState() == Player.STATE_IDLE) { | |
| 1267 | + if (playbackPreparer != null) { | |
| 1268 | + playbackPreparer.preparePlayback(); | |
| 1269 | + } | |
| 1270 | + } else if (player.getPlaybackState() == Player.STATE_ENDED) { | |
| 1271 | + seekTo(player, player.getCurrentWindowIndex(), C.TIME_UNSET); | |
| 1272 | + } | |
| 1273 | + playVideo(); | |
| 1274 | + } else if (pauseButton == view) { | |
| 1275 | + pauseVideo(); | |
| 1276 | + } else if (repeatToggleButton == view) { | |
| 1277 | + setPlayerRepeatMode(Player.REPEAT_MODE_ONE); | |
| 1278 | + | |
| 1279 | +// setRepeatToggleModes(RepeatModeUtil.REPEAT_TOGGLE_MODE_ONE); | |
| 1280 | + } else if (shuffleButton == view) { | |
| 1281 | + setShuffleModeEnabled(!player.getShuffleModeEnabled()); | |
| 1282 | + } | |
| 1283 | + //横竖屏切换 | |
| 1284 | + else if (switchScreenButton == view) { | |
| 1285 | + switchScreenDirection(); | |
| 1286 | + } | |
| 1287 | + } | |
| 1288 | + } | |
| 1289 | + | |
| 1290 | + public void setShuffleModeEnabled(boolean isShuffleModeEnabled) { | |
| 1291 | + if (null == player) { | |
| 1292 | + return; | |
| 1293 | + } | |
| 1294 | + player.setShuffleModeEnabled(isShuffleModeEnabled); | |
| 1295 | +// controlDispatcher.dispatchSetShuffleModeEnabled(player, !player.getShuffleModeEnabled()); | |
| 1296 | + } | |
| 1297 | + | |
| 1298 | + /** | |
| 1299 | + * 设置播放模式 | |
| 1300 | + * | |
| 1301 | + * @param playerRepeatMode Player.REPEAT_MODE_ALL | |
| 1302 | + * Player.REPEAT_MODE_OFF | |
| 1303 | + * Player.REPEAT_MODE_ONE | |
| 1304 | + */ | |
| 1305 | + public void setPlayerRepeatMode(int playerRepeatMode) { | |
| 1306 | +// controlDispatcher.dispatchSetRepeatMode( | |
| 1307 | +// player, playerRepeatMode); | |
| 1308 | + player.setRepeatMode(playerRepeatMode); | |
| 1309 | + } | |
| 1310 | + | |
| 1311 | + | |
| 1312 | + /** | |
| 1313 | + * 切换屏幕方向 | |
| 1314 | + */ | |
| 1315 | + public void switchScreenDirection() { | |
| 1316 | + if (null != activity && !activity.isFinishing()) { | |
| 1317 | + //获取当前屏幕朝向 | |
| 1318 | + Configuration mConfiguration = activity.getResources().getConfiguration(); //获取设置的配置信息 | |
| 1319 | + int ori = mConfiguration.orientation; //获取屏幕方向 | |
| 1320 | + if (ori == mConfiguration.ORIENTATION_LANDSCAPE) { | |
| 1321 | + if (null != baseLandscapeView) { | |
| 1322 | + baseLandscapeView.setVisibility(GONE); | |
| 1323 | + } | |
| 1324 | + if (null != basePortraitView) { | |
| 1325 | + basePortraitView.setVisibility(VISIBLE); | |
| 1326 | + } | |
| 1327 | + //强制为竖屏 | |
| 1328 | + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); | |
| 1329 | + //显示状态栏 | |
| 1330 | + WindowManager.LayoutParams attr = activity.getWindow().getAttributes(); | |
| 1331 | + attr.flags &= (~WindowManager.LayoutParams.FLAG_FULLSCREEN); | |
| 1332 | + activity.getWindow().setAttributes(attr); | |
| 1333 | + activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); | |
| 1334 | + | |
| 1335 | + } else if (ori == mConfiguration.ORIENTATION_PORTRAIT) { | |
| 1336 | + if (null == baseLandscapeView) { | |
| 1337 | + //切换布局 | |
| 1338 | + baseLandscapeView = LayoutInflater.from(context).inflate(controllerLayoutId, /* root= */ null); | |
| 1339 | + } | |
| 1340 | + if (null != basePortraitView) { | |
| 1341 | + basePortraitView.setVisibility(GONE); | |
| 1342 | + } | |
| 1343 | + if (null != baseLandscapeView) { | |
| 1344 | + baseLandscapeView.setVisibility(VISIBLE); | |
| 1345 | + } | |
| 1346 | + //竖屏 | |
| 1347 | + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);//强制为横屏 | |
| 1348 | + //隐藏状态栏 | |
| 1349 | + WindowManager.LayoutParams lp = activity.getWindow().getAttributes(); | |
| 1350 | + lp.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; | |
| 1351 | + activity.getWindow().setAttributes(lp); | |
| 1352 | + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); | |
| 1353 | + } | |
| 1354 | + } | |
| 1355 | + } | |
| 1356 | + | |
| 1357 | + /** | |
| 1358 | + * 设置播放进度条的颜色 | |
| 1359 | + * | |
| 1360 | + * @param color | |
| 1361 | + */ | |
| 1362 | + public void setPlayedColor(@ColorInt int color) { | |
| 1363 | + if (null != timeBar && timeBar instanceof BaseTimerBar) { | |
| 1364 | + BaseTimerBar baseTimerBar = (BaseTimerBar) timeBar; | |
| 1365 | + baseTimerBar.setPlayedColor(color); | |
| 1366 | + } | |
| 1367 | + } | |
| 1368 | + | |
| 1369 | + /** | |
| 1370 | + * 快进视频 | |
| 1371 | + * | |
| 1372 | + * @param player | |
| 1373 | + */ | |
| 1374 | + public void fastForward(VideoPlayer player) { | |
| 1375 | + if (player.isCurrentWindowSeekable() && fastForwardMs > 0) { | |
| 1376 | + seekToOffset(player, fastForwardMs); | |
| 1377 | + } | |
| 1378 | + } | |
| 1379 | + | |
| 1380 | + /** | |
| 1381 | + * 播放视频 | |
| 1382 | + */ | |
| 1383 | + public void playVideo() { | |
| 1384 | + if (null == player) { | |
| 1385 | + return; | |
| 1386 | + } | |
| 1387 | + player.play(); | |
| 1388 | +// if (null == controlDispatcher || null == player) { | |
| 1389 | +// return; | |
| 1390 | +// } | |
| 1391 | +// controlDispatcher.dispatchSetPlayWhenReady(player, true); | |
| 1392 | + } | |
| 1393 | + | |
| 1394 | + /** | |
| 1395 | + * 暂停视频 | |
| 1396 | + */ | |
| 1397 | + public void pauseVideo() { | |
| 1398 | + if (null == player) { | |
| 1399 | + return; | |
| 1400 | + } | |
| 1401 | +// if (null == controlDispatcher || null == player) { | |
| 1402 | +// return; | |
| 1403 | +// } | |
| 1404 | + player.pause(); | |
| 1405 | +// controlDispatcher.dispatchSetPlayWhenReady(player, false); | |
| 1406 | + } | |
| 1407 | + | |
| 1408 | + public boolean isPlaying() { | |
| 1409 | + return player == null ? false : player.isPlaying(); | |
| 1410 | + } | |
| 1411 | +} | |
| 0 | 1412 | \ No newline at end of file | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/CNLiveControlDispatcher.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer; | |
| 2 | + | |
| 3 | + | |
| 4 | +import com.google.android.exoplayer2.Player; | |
| 5 | + | |
| 6 | +public interface CNLiveControlDispatcher { | |
| 7 | + /** | |
| 8 | + * Dispatches a {@link Player#setPlayWhenReady(boolean)} operation. | |
| 9 | + * | |
| 10 | + * @param player The {@link Player} to which the operation should be dispatched. | |
| 11 | + * @param playWhenReady Whether playback should proceed when ready. | |
| 12 | + * @return True if the operation was dispatched. False if suppressed. | |
| 13 | + */ | |
| 14 | + boolean dispatchSetPlayWhenReady(VideoPlayer player, boolean playWhenReady); | |
| 15 | + | |
| 16 | + /** | |
| 17 | + * Dispatches a {@link Player#seekTo(int, long)} operation. | |
| 18 | + * | |
| 19 | + * @param player The {@link Player} to which the operation should be dispatched. | |
| 20 | + * @param windowIndex The index of the window. | |
| 21 | + * @param positionMs The seek position in the specified window, or {@link C#TIME_UNSET} to seek to | |
| 22 | + * the window's default position. | |
| 23 | + * @return True if the operation was dispatched. False if suppressed. | |
| 24 | + */ | |
| 25 | + boolean dispatchSeekTo(VideoPlayer player, int windowIndex, long positionMs); | |
| 26 | + | |
| 27 | + /** | |
| 28 | + * Dispatches a {@link Player#setRepeatMode(int)} operation. | |
| 29 | + * | |
| 30 | + * @param player The {@link Player} to which the operation should be dispatched. | |
| 31 | + * @param repeatMode The repeat mode. | |
| 32 | + * @return True if the operation was dispatched. False if suppressed. | |
| 33 | + */ | |
| 34 | + boolean dispatchSetRepeatMode(VideoPlayer player, @Player.RepeatMode int repeatMode); | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * Dispatches a {@link Player#setShuffleModeEnabled(boolean)} operation. | |
| 38 | + * | |
| 39 | + * @param player The {@link Player} to which the operation should be dispatched. | |
| 40 | + * @param shuffleModeEnabled Whether shuffling is enabled. | |
| 41 | + * @return True if the operation was dispatched. False if suppressed. | |
| 42 | + */ | |
| 43 | + boolean dispatchSetShuffleModeEnabled(VideoPlayer player, boolean shuffleModeEnabled); | |
| 44 | + | |
| 45 | + /** | |
| 46 | + * Dispatches a {@link Player#stop()} operation. | |
| 47 | + * | |
| 48 | + * @param player The {@link Player} to which the operation should be dispatched. | |
| 49 | + * @param reset Whether the player should be reset. | |
| 50 | + * @return True if the operation was dispatched. False if suppressed. | |
| 51 | + */ | |
| 52 | + boolean dispatchStop(VideoPlayer player, boolean reset); | |
| 53 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/CNLiveDefaultControlDispatcher.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer; | |
| 2 | + | |
| 3 | +import com.google.android.exoplayer2.ControlDispatcher; | |
| 4 | +import com.google.android.exoplayer2.Player; | |
| 5 | + | |
| 6 | +public class CNLiveDefaultControlDispatcher implements CNLiveControlDispatcher { | |
| 7 | + | |
| 8 | + @Override | |
| 9 | + public boolean dispatchSetPlayWhenReady(VideoPlayer player, boolean playWhenReady) { | |
| 10 | +// player.setPlayWhenReady(playWhenReady); | |
| 11 | + return true; | |
| 12 | + } | |
| 13 | + | |
| 14 | + @Override | |
| 15 | + public boolean dispatchSeekTo(VideoPlayer player, int windowIndex, long positionMs) { | |
| 16 | + player.seekTo(windowIndex, positionMs); | |
| 17 | + return true; | |
| 18 | + } | |
| 19 | + | |
| 20 | + @Override | |
| 21 | + public boolean dispatchSetRepeatMode(VideoPlayer player, @Player.RepeatMode int repeatMode) { | |
| 22 | +// player.setRepeatMode(repeatMode); | |
| 23 | + return true; | |
| 24 | + } | |
| 25 | + | |
| 26 | + @Override | |
| 27 | + public boolean dispatchSetShuffleModeEnabled(VideoPlayer player, boolean shuffleModeEnabled) { | |
| 28 | +// player.setShuffleModeEnabled(shuffleModeEnabled); | |
| 29 | + return true; | |
| 30 | + } | |
| 31 | + | |
| 32 | + @Override | |
| 33 | + public boolean dispatchStop(VideoPlayer player, boolean reset) { | |
| 34 | +// player.stop(reset); | |
| 35 | + return true; | |
| 36 | + } | |
| 37 | +} | |
| 38 | + | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/CNLiveVideoView.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer; | |
| 2 | + | |
| 3 | +import android.annotation.SuppressLint; | |
| 4 | +import android.annotation.TargetApi; | |
| 5 | +import android.content.Context; | |
| 6 | +import android.content.res.Resources; | |
| 7 | +import android.content.res.TypedArray; | |
| 8 | +import android.graphics.Bitmap; | |
| 9 | +import android.graphics.BitmapFactory; | |
| 10 | +import android.graphics.Matrix; | |
| 11 | +import android.graphics.RectF; | |
| 12 | +import android.graphics.drawable.BitmapDrawable; | |
| 13 | +import android.graphics.drawable.Drawable; | |
| 14 | +import android.net.Uri; | |
| 15 | +import android.os.Looper; | |
| 16 | +import android.util.AttributeSet; | |
| 17 | +import android.view.KeyEvent; | |
| 18 | +import android.view.LayoutInflater; | |
| 19 | +import android.view.MotionEvent; | |
| 20 | +import android.view.SurfaceView; | |
| 21 | +import android.view.TextureView; | |
| 22 | +import android.view.View; | |
| 23 | +import android.view.ViewGroup; | |
| 24 | +import android.widget.FrameLayout; | |
| 25 | +import android.widget.ImageView; | |
| 26 | +import android.widget.TextView; | |
| 27 | + | |
| 28 | +import androidx.annotation.ColorInt; | |
| 29 | +import androidx.annotation.IntDef; | |
| 30 | +import androidx.annotation.Nullable; | |
| 31 | +import androidx.core.content.ContextCompat; | |
| 32 | + | |
| 33 | +import com.cnlive.cloud.video.R; | |
| 34 | +import com.google.android.exoplayer2.C; | |
| 35 | +import com.google.android.exoplayer2.ControlDispatcher; | |
| 36 | +import com.google.android.exoplayer2.DefaultControlDispatcher; | |
| 37 | +import com.google.android.exoplayer2.ExoPlaybackException; | |
| 38 | +import com.google.android.exoplayer2.PlaybackPreparer; | |
| 39 | +import com.google.android.exoplayer2.Player; | |
| 40 | +import com.google.android.exoplayer2.metadata.Metadata; | |
| 41 | +import com.google.android.exoplayer2.metadata.flac.PictureFrame; | |
| 42 | +import com.google.android.exoplayer2.metadata.id3.ApicFrame; | |
| 43 | +import com.google.android.exoplayer2.source.TrackGroupArray; | |
| 44 | +import com.google.android.exoplayer2.source.ads.AdsLoader; | |
| 45 | +import com.google.android.exoplayer2.text.Cue; | |
| 46 | +import com.google.android.exoplayer2.text.TextOutput; | |
| 47 | +import com.google.android.exoplayer2.trackselection.TrackSelection; | |
| 48 | +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; | |
| 49 | +import com.google.android.exoplayer2.ui.AspectRatioFrameLayout; | |
| 50 | +import com.google.android.exoplayer2.ui.PlayerView; | |
| 51 | +import com.google.android.exoplayer2.ui.SubtitleView; | |
| 52 | +import com.google.android.exoplayer2.ui.spherical.SingleTapListener; | |
| 53 | +import com.google.android.exoplayer2.ui.spherical.SphericalGLSurfaceView; | |
| 54 | +import com.google.android.exoplayer2.util.Assertions; | |
| 55 | +import com.google.android.exoplayer2.util.ErrorMessageProvider; | |
| 56 | +import com.google.android.exoplayer2.util.RepeatModeUtil; | |
| 57 | +import com.google.android.exoplayer2.util.Util; | |
| 58 | +import com.google.android.exoplayer2.video.VideoDecoderGLSurfaceView; | |
| 59 | +import com.google.android.exoplayer2.video.VideoListener; | |
| 60 | + | |
| 61 | +import java.lang.annotation.Documented; | |
| 62 | +import java.lang.annotation.Retention; | |
| 63 | +import java.lang.annotation.RetentionPolicy; | |
| 64 | +import java.util.ArrayList; | |
| 65 | +import java.util.List; | |
| 66 | + | |
| 67 | +public class CNLiveVideoView extends FrameLayout implements AdsLoader.AdViewProvider { | |
| 68 | + /** | |
| 69 | + * Determines when the buffering view is shown. One of {@link #SHOW_BUFFERING_NEVER}, {@link | |
| 70 | + * #SHOW_BUFFERING_WHEN_PLAYING} or {@link #SHOW_BUFFERING_ALWAYS}. | |
| 71 | + */ | |
| 72 | + @Documented | |
| 73 | + @Retention(RetentionPolicy.SOURCE) | |
| 74 | + @IntDef({SHOW_BUFFERING_NEVER, SHOW_BUFFERING_WHEN_PLAYING, SHOW_BUFFERING_ALWAYS}) | |
| 75 | + public @interface ShowBuffering { | |
| 76 | + } | |
| 77 | + | |
| 78 | + /** | |
| 79 | + * The buffering view is never shown. | |
| 80 | + */ | |
| 81 | + public static final int SHOW_BUFFERING_NEVER = 0; | |
| 82 | + /** | |
| 83 | + * The buffering view is shown when the player is in the {@link Player#STATE_BUFFERING buffering} | |
| 84 | + * state and {@link Player#getPlayWhenReady() playWhenReady} is {@code true}. | |
| 85 | + */ | |
| 86 | + public static final int SHOW_BUFFERING_WHEN_PLAYING = 1; | |
| 87 | + /** | |
| 88 | + * The buffering view is always shown when the player is in the {@link Player#STATE_BUFFERING | |
| 89 | + * buffering} state. | |
| 90 | + */ | |
| 91 | + public static final int SHOW_BUFFERING_ALWAYS = 2; | |
| 92 | + // LINT.ThenChange(../../../../../../res/values/attrs.xml) | |
| 93 | + | |
| 94 | + // LINT.IfChange | |
| 95 | + private static final int SURFACE_TYPE_NONE = 0; | |
| 96 | + private static final int SURFACE_TYPE_SURFACE_VIEW = 1; | |
| 97 | + private static final int SURFACE_TYPE_TEXTURE_VIEW = 2; | |
| 98 | + private static final int SURFACE_TYPE_SPHERICAL_GL_SURFACE_VIEW = 3; | |
| 99 | + private static final int SURFACE_TYPE_VIDEO_DECODER_GL_SURFACE_VIEW = 4; | |
| 100 | + // LINT.ThenChange(../../../../../../res/values/attrs.xml) | |
| 101 | + | |
| 102 | + private final CNLiveVideoView.ComponentListener componentListener; | |
| 103 | + @Nullable | |
| 104 | + private final AspectRatioFrameLayout contentFrame; | |
| 105 | + @Nullable | |
| 106 | + private final View shutterView; | |
| 107 | + @Nullable | |
| 108 | + private final View surfaceView; | |
| 109 | + @Nullable | |
| 110 | + private final ImageView artworkView; | |
| 111 | + @Nullable | |
| 112 | + private final SubtitleView subtitleView; | |
| 113 | + @Nullable | |
| 114 | + private final View bufferingView; | |
| 115 | + @Nullable | |
| 116 | + private final TextView errorMessageView; | |
| 117 | + @Nullable | |
| 118 | + private CNLiveBaseVideoControlView controller; | |
| 119 | + @Nullable | |
| 120 | + private final FrameLayout adOverlayFrameLayout; | |
| 121 | + @Nullable | |
| 122 | + private final FrameLayout overlayFrameLayout; | |
| 123 | + private boolean useController; | |
| 124 | + @Nullable | |
| 125 | + private CNLiveBaseVideoControlView.VisibilityListener controllerVisibilityListener; | |
| 126 | + private boolean useArtwork; | |
| 127 | + @Nullable | |
| 128 | + private Drawable defaultArtwork; | |
| 129 | + private @PlayerView.ShowBuffering | |
| 130 | + int showBuffering; | |
| 131 | + private boolean keepContentOnPlayerReset; | |
| 132 | + @Nullable | |
| 133 | + private ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider; | |
| 134 | + @Nullable | |
| 135 | + private CharSequence customErrorMessage; | |
| 136 | + private int controllerShowTimeoutMs; | |
| 137 | + private boolean controllerAutoShow; | |
| 138 | + private boolean controllerHideDuringAds; | |
| 139 | + private boolean controllerHideOnTouch; | |
| 140 | + private int textureViewRotation; | |
| 141 | + private boolean isTouching; | |
| 142 | + private static final int PICTURE_TYPE_FRONT_COVER = 3; | |
| 143 | + private static final int PICTURE_TYPE_NOT_SET = -1; | |
| 144 | + private View controllerPlaceholder; | |
| 145 | + private VideoPlayer player; | |
| 146 | + | |
| 147 | + public CNLiveVideoView(Context context) { | |
| 148 | + this(context, /* attrs= */ null); | |
| 149 | + } | |
| 150 | + | |
| 151 | + public CNLiveVideoView(Context context, @Nullable AttributeSet attrs) { | |
| 152 | + this(context, attrs, /* defStyleAttr= */ 0); | |
| 153 | + } | |
| 154 | + | |
| 155 | + @SuppressWarnings({"nullness:argument.type.incompatible", "nullness:method.invocation.invalid"}) | |
| 156 | + public CNLiveVideoView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { | |
| 157 | + super(context, attrs, defStyleAttr); | |
| 158 | + componentListener = new CNLiveVideoView.ComponentListener(); | |
| 159 | + //初始化videoplayer | |
| 160 | + player = new VideoPlayer(context); | |
| 161 | + if (isInEditMode()) { | |
| 162 | + contentFrame = null; | |
| 163 | + shutterView = null; | |
| 164 | + surfaceView = null; | |
| 165 | + artworkView = null; | |
| 166 | + subtitleView = null; | |
| 167 | + bufferingView = null; | |
| 168 | + errorMessageView = null; | |
| 169 | + controller = null; | |
| 170 | + adOverlayFrameLayout = null; | |
| 171 | + overlayFrameLayout = null; | |
| 172 | + ImageView logo = new ImageView(context); | |
| 173 | + if (Util.SDK_INT >= 23) { | |
| 174 | + configureEditModeLogoV23(getResources(), logo); | |
| 175 | + } else { | |
| 176 | + configureEditModeLogo(getResources(), logo); | |
| 177 | + } | |
| 178 | + addView(logo); | |
| 179 | + return; | |
| 180 | + } | |
| 181 | + | |
| 182 | + boolean shutterColorSet = false; | |
| 183 | + int shutterColor = 0; | |
| 184 | + int playerLayoutId = R.layout.layout_base_viedo_view; | |
| 185 | + boolean useArtwork = true; | |
| 186 | + int defaultArtworkId = 0; | |
| 187 | + boolean useController = true; | |
| 188 | + int surfaceType = SURFACE_TYPE_SURFACE_VIEW; | |
| 189 | + int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT; | |
| 190 | + int controllerShowTimeoutMs = CNLiveBaseVideoControlView.DEFAULT_SHOW_TIMEOUT_MS; | |
| 191 | + boolean controllerHideOnTouch = true; | |
| 192 | + boolean controllerAutoShow = true; | |
| 193 | + boolean controllerHideDuringAds = true; | |
| 194 | + int showBuffering = SHOW_BUFFERING_NEVER; | |
| 195 | + if (attrs != null) { | |
| 196 | + TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.PlayerView, 0, 0); | |
| 197 | + try { | |
| 198 | + shutterColorSet = typedArray.hasValue(R.styleable.PlayerView_shutter_background_color); | |
| 199 | + shutterColor = typedArray.getColor(R.styleable.PlayerView_shutter_background_color, shutterColor); | |
| 200 | + playerLayoutId = typedArray.getResourceId(R.styleable.PlayerView_player_layout_id, playerLayoutId); | |
| 201 | + useArtwork = typedArray.getBoolean(R.styleable.PlayerView_use_artwork, useArtwork); | |
| 202 | + defaultArtworkId = | |
| 203 | + typedArray.getResourceId(R.styleable.PlayerView_default_artwork, defaultArtworkId); | |
| 204 | + useController = typedArray.getBoolean(R.styleable.PlayerView_use_controller, useController); | |
| 205 | + surfaceType = typedArray.getInt(R.styleable.PlayerView_surface_type, surfaceType); | |
| 206 | + resizeMode = typedArray.getInt(R.styleable.PlayerView_resize_mode, resizeMode); | |
| 207 | + controllerShowTimeoutMs = | |
| 208 | + typedArray.getInt(R.styleable.PlayerView_show_timeout, controllerShowTimeoutMs); | |
| 209 | + controllerHideOnTouch = | |
| 210 | + typedArray.getBoolean(R.styleable.PlayerView_hide_on_touch, controllerHideOnTouch); | |
| 211 | + controllerAutoShow = typedArray.getBoolean(R.styleable.PlayerView_auto_show, controllerAutoShow); | |
| 212 | + showBuffering = typedArray.getInteger(R.styleable.PlayerView_show_buffering, showBuffering); | |
| 213 | + keepContentOnPlayerReset = | |
| 214 | + typedArray.getBoolean( | |
| 215 | + R.styleable.PlayerView_keep_content_on_player_reset, keepContentOnPlayerReset); | |
| 216 | + controllerHideDuringAds = | |
| 217 | + typedArray.getBoolean(R.styleable.PlayerView_hide_during_ads, controllerHideDuringAds); | |
| 218 | + } finally { | |
| 219 | + typedArray.recycle(); | |
| 220 | + } | |
| 221 | + } | |
| 222 | + | |
| 223 | + LayoutInflater.from(context).inflate(playerLayoutId, this); | |
| 224 | + setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); | |
| 225 | + | |
| 226 | + // Content frame. | |
| 227 | + contentFrame = findViewById(R.id.cnlive_content_frame); | |
| 228 | + if (contentFrame != null) { | |
| 229 | + setResizeModeRaw(contentFrame, resizeMode); | |
| 230 | + } | |
| 231 | + | |
| 232 | + // Shutter view. | |
| 233 | + shutterView = findViewById(R.id.cnlive_shutter); | |
| 234 | + if (shutterView != null && shutterColorSet) { | |
| 235 | + shutterView.setBackgroundColor(shutterColor); | |
| 236 | + } | |
| 237 | + | |
| 238 | + // Create a surface view and insert it into the content frame, if there is one. | |
| 239 | + if (contentFrame != null && surfaceType != SURFACE_TYPE_NONE) { | |
| 240 | + ViewGroup.LayoutParams params = | |
| 241 | + new ViewGroup.LayoutParams( | |
| 242 | + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); | |
| 243 | + switch (surfaceType) { | |
| 244 | + case SURFACE_TYPE_TEXTURE_VIEW: | |
| 245 | + surfaceView = new TextureView(context); | |
| 246 | + break; | |
| 247 | + case SURFACE_TYPE_SPHERICAL_GL_SURFACE_VIEW: | |
| 248 | + SphericalGLSurfaceView sphericalGLSurfaceView = new SphericalGLSurfaceView(context); | |
| 249 | + sphericalGLSurfaceView.setSingleTapListener(componentListener); | |
| 250 | + surfaceView = sphericalGLSurfaceView; | |
| 251 | + break; | |
| 252 | + case SURFACE_TYPE_VIDEO_DECODER_GL_SURFACE_VIEW: | |
| 253 | + surfaceView = new VideoDecoderGLSurfaceView(context); | |
| 254 | + break; | |
| 255 | + default: | |
| 256 | + surfaceView = new SurfaceView(context); | |
| 257 | + break; | |
| 258 | + } | |
| 259 | + surfaceView.setLayoutParams(params); | |
| 260 | + contentFrame.addView(surfaceView, 0); | |
| 261 | + } else { | |
| 262 | + surfaceView = null; | |
| 263 | + } | |
| 264 | + | |
| 265 | + // Ad overlay frame layout. | |
| 266 | + adOverlayFrameLayout = findViewById(R.id.cnlive_ad_overlay); | |
| 267 | + | |
| 268 | + // Overlay frame layout. | |
| 269 | + overlayFrameLayout = findViewById(R.id.cnlive_overlay); | |
| 270 | + | |
| 271 | + // Artwork view. | |
| 272 | + artworkView = findViewById(R.id.cnlive_artwork); | |
| 273 | + this.useArtwork = useArtwork && artworkView != null; | |
| 274 | + if (defaultArtworkId != 0) { | |
| 275 | + defaultArtwork = ContextCompat.getDrawable(getContext(), defaultArtworkId); | |
| 276 | + } | |
| 277 | + | |
| 278 | + // Subtitle view. | |
| 279 | + subtitleView = findViewById(R.id.cnlive_subtitles); | |
| 280 | + if (subtitleView != null) { | |
| 281 | + subtitleView.setUserDefaultStyle(); | |
| 282 | + subtitleView.setUserDefaultTextSize(); | |
| 283 | + } | |
| 284 | + | |
| 285 | + // Buffering view. | |
| 286 | + bufferingView = findViewById(R.id.cnlive_buffering); | |
| 287 | + if (bufferingView != null) { | |
| 288 | + bufferingView.setVisibility(View.GONE); | |
| 289 | + } | |
| 290 | + this.showBuffering = showBuffering; | |
| 291 | + | |
| 292 | + // Error message view. | |
| 293 | + errorMessageView = findViewById(R.id.cnlive_error_message); | |
| 294 | + if (errorMessageView != null) { | |
| 295 | + errorMessageView.setVisibility(View.GONE); | |
| 296 | + } | |
| 297 | + | |
| 298 | + // Playback control view. | |
| 299 | + CNLiveBaseVideoControlView customController = findViewById(R.id.cnlive_controller); | |
| 300 | + controllerPlaceholder = findViewById(R.id.cnlive_controller_placeholder); | |
| 301 | + if (customController != null) { | |
| 302 | + this.controller = customController; | |
| 303 | + } else if (controllerPlaceholder != null) { | |
| 304 | + // Propagate attrs as playbackAttrs so that PlayerControlView's custom attributes are | |
| 305 | + // transferred, but standard attributes (e.g. background) are not. | |
| 306 | + if (null == controller) { | |
| 307 | + this.controller = new CNLiveBaseVideoControlView(context, null, 0, attrs) { | |
| 308 | + @Override | |
| 309 | + protected void onViewClick(View view) { | |
| 310 | + | |
| 311 | + } | |
| 312 | + | |
| 313 | + @Override | |
| 314 | + protected void initPortraitView(View basePortraitView, ComponentListener onClickListener) { | |
| 315 | + | |
| 316 | + } | |
| 317 | + | |
| 318 | + }; | |
| 319 | + controller.setId(R.id.cnlive_controller); | |
| 320 | + controller.setLayoutParams(controllerPlaceholder.getLayoutParams()); | |
| 321 | + ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent()); | |
| 322 | + int controllerIndex = parent.indexOfChild(controllerPlaceholder); | |
| 323 | +// parent.removeView(controllerPlaceholder); | |
| 324 | + parent.addView(controller, controllerIndex); | |
| 325 | + } | |
| 326 | + } else { | |
| 327 | + this.controller = null; | |
| 328 | + } | |
| 329 | + this.controllerShowTimeoutMs = controller != null ? controllerShowTimeoutMs : 0; | |
| 330 | + this.controllerHideOnTouch = controllerHideOnTouch; | |
| 331 | + this.controllerAutoShow = controllerAutoShow; | |
| 332 | + this.controllerHideDuringAds = controllerHideDuringAds; | |
| 333 | + this.useController = useController && controller != null; | |
| 334 | + hideController(); | |
| 335 | + updateContentDescription(); | |
| 336 | + if (controller != null) { | |
| 337 | + controller.addVisibilityListener(/* listener= */ componentListener); | |
| 338 | + } | |
| 339 | + Uri mp4VideoUri = Uri.parse("http://ffyl.vods2.cnlive.com/223/vod/2018/1120/223_88c98ab1395f4762b703fbdd9d59d2e5/ff80808166c2a45201672f14a9933131_800.m3u8"); | |
| 340 | + player.setDataSource(mp4VideoUri); | |
| 341 | + player.prepare(true); | |
| 342 | + setPlayer(player); | |
| 343 | + } | |
| 344 | + | |
| 345 | + public void setCustomControllView(CNLiveBaseVideoControlView controllerView) { | |
| 346 | + if (null != controllerPlaceholder && null != controllerView) { | |
| 347 | + controllerView.setPlayer(player); | |
| 348 | + controller.setId(R.id.cnlive_controller); | |
| 349 | + controller.setLayoutParams(controllerPlaceholder.getLayoutParams()); | |
| 350 | + ViewGroup parent = ((ViewGroup) controllerPlaceholder.getParent()); | |
| 351 | + if (null != parent) { | |
| 352 | + if (null!=controller){ | |
| 353 | + parent.removeView(controller); | |
| 354 | + } | |
| 355 | + int controllerIndex = parent.indexOfChild(controllerPlaceholder); | |
| 356 | + this.controller = controllerView; | |
| 357 | + parent.addView(controller, controllerIndex); | |
| 358 | + parent.removeView(controllerPlaceholder); | |
| 359 | + } | |
| 360 | + | |
| 361 | + } | |
| 362 | + } | |
| 363 | + | |
| 364 | + /** | |
| 365 | + * Switches the view targeted by a given {@link Player}. | |
| 366 | + * | |
| 367 | + * @param player The player whose target view is being switched. | |
| 368 | + * @param oldPlayerView The old view to detach from the player. | |
| 369 | + * @param newPlayerView The new view to attach to the player. | |
| 370 | + */ | |
| 371 | + public static void switchTargetView( | |
| 372 | + Player player, @Nullable PlayerView oldPlayerView, @Nullable PlayerView newPlayerView) { | |
| 373 | + if (oldPlayerView == newPlayerView) { | |
| 374 | + return; | |
| 375 | + } | |
| 376 | + // We attach the new view before detaching the old one because this ordering allows the player | |
| 377 | + // to swap directly from one surface to another, without transitioning through a state where no | |
| 378 | + // surface is attached. This is significantly more efficient and achieves a more seamless | |
| 379 | + // transition when using platform provided video decoders. | |
| 380 | + if (newPlayerView != null) { | |
| 381 | + newPlayerView.setPlayer(player); | |
| 382 | + } | |
| 383 | + if (oldPlayerView != null) { | |
| 384 | + oldPlayerView.setPlayer(null); | |
| 385 | + } | |
| 386 | + } | |
| 387 | + | |
| 388 | + /** | |
| 389 | + * Returns the player currently set on this view, or null if no player is set. | |
| 390 | + */ | |
| 391 | + @Nullable | |
| 392 | + public VideoPlayer getPlayer() { | |
| 393 | + return player; | |
| 394 | + } | |
| 395 | + | |
| 396 | + /** | |
| 397 | + * Set the {@link Player} to use. | |
| 398 | + * | |
| 399 | + * <p>To transition a {@link Player} from targeting one view to another, it's recommended to use | |
| 400 | + * {@link #switchTargetView(Player, PlayerView, PlayerView)} rather than this method. If you do | |
| 401 | + * wish to use this method directly, be sure to attach the player to the new view <em>before</em> | |
| 402 | + * calling {@code setPlayer(null)} to detach it from the old one. This ordering is significantly | |
| 403 | + * more efficient and may allow for more seamless transitions. | |
| 404 | + * | |
| 405 | + * @param player The {@link Player} to use, or {@code null} to detach the current player. Only | |
| 406 | + * players which are accessed on the main thread are supported ({@code | |
| 407 | + * player.getApplicationLooper() == Looper.getMainLooper()}). | |
| 408 | + */ | |
| 409 | + private void setPlayer(@Nullable VideoPlayer player) { | |
| 410 | + Assertions.checkState(Looper.myLooper() == Looper.getMainLooper()); | |
| 411 | + Assertions.checkArgument( | |
| 412 | + player == null || player.getApplicationLooper() == Looper.getMainLooper()); | |
| 413 | +// if (this.player == player) { | |
| 414 | +// return; | |
| 415 | +// } | |
| 416 | + @Nullable VideoPlayer oldPlayer = this.player; | |
| 417 | + | |
| 418 | + if (oldPlayer != null) { | |
| 419 | + oldPlayer.removeListener(componentListener); | |
| 420 | + @Nullable Player.VideoComponent oldVideoComponent = oldPlayer.getVideoComponent(); | |
| 421 | + if (oldVideoComponent != null) { | |
| 422 | + oldVideoComponent.removeVideoListener(componentListener); | |
| 423 | + if (surfaceView instanceof TextureView) { | |
| 424 | + oldVideoComponent.clearVideoTextureView((TextureView) surfaceView); | |
| 425 | + } else if (surfaceView instanceof SphericalGLSurfaceView) { | |
| 426 | + ((SphericalGLSurfaceView) surfaceView).setVideoComponent(null); | |
| 427 | + } else if (surfaceView instanceof VideoDecoderGLSurfaceView) { | |
| 428 | + oldVideoComponent.setVideoDecoderOutputBufferRenderer(null); | |
| 429 | + } else if (surfaceView instanceof SurfaceView) { | |
| 430 | + oldVideoComponent.clearVideoSurfaceView((SurfaceView) surfaceView); | |
| 431 | + } | |
| 432 | + } | |
| 433 | + @Nullable Player.TextComponent oldTextComponent = oldPlayer.getTextComponent(); | |
| 434 | + if (oldTextComponent != null) { | |
| 435 | + oldTextComponent.removeTextOutput(componentListener); | |
| 436 | + } | |
| 437 | + } | |
| 438 | + this.player = player; | |
| 439 | + if (useController()) { | |
| 440 | + controller.setPlayer(player); | |
| 441 | + } | |
| 442 | + if (subtitleView != null) { | |
| 443 | + subtitleView.setCues(null); | |
| 444 | + } | |
| 445 | + updateBuffering(); | |
| 446 | + updateErrorMessage(); | |
| 447 | + updateForCurrentTrackSelections(/* isNewPlayer= */ true); | |
| 448 | + if (player != null) { | |
| 449 | + @Nullable Player.VideoComponent newVideoComponent = player.getVideoComponent(); | |
| 450 | + if (newVideoComponent != null) { | |
| 451 | + if (surfaceView instanceof TextureView) { | |
| 452 | + newVideoComponent.setVideoTextureView((TextureView) surfaceView); | |
| 453 | + } else if (surfaceView instanceof SphericalGLSurfaceView) { | |
| 454 | + ((SphericalGLSurfaceView) surfaceView).setVideoComponent(newVideoComponent); | |
| 455 | + } else if (surfaceView instanceof VideoDecoderGLSurfaceView) { | |
| 456 | + newVideoComponent.setVideoDecoderOutputBufferRenderer( | |
| 457 | + ((VideoDecoderGLSurfaceView) surfaceView).getVideoDecoderOutputBufferRenderer()); | |
| 458 | + } else if (surfaceView instanceof SurfaceView) { | |
| 459 | + newVideoComponent.setVideoSurfaceView((SurfaceView) surfaceView); | |
| 460 | + } | |
| 461 | + newVideoComponent.addVideoListener(componentListener); | |
| 462 | + } | |
| 463 | + @Nullable Player.TextComponent newTextComponent = player.getTextComponent(); | |
| 464 | + if (newTextComponent != null) { | |
| 465 | + newTextComponent.addTextOutput(componentListener); | |
| 466 | + } | |
| 467 | + player.addListener(componentListener); | |
| 468 | + maybeShowController(false); | |
| 469 | + } else { | |
| 470 | + hideController(); | |
| 471 | + } | |
| 472 | + } | |
| 473 | + | |
| 474 | + @Override | |
| 475 | + public void setVisibility(int visibility) { | |
| 476 | + super.setVisibility(visibility); | |
| 477 | + if (surfaceView instanceof SurfaceView) { | |
| 478 | + // Work around https://github.com/google/ExoPlayer/issues/3160. | |
| 479 | + surfaceView.setVisibility(visibility); | |
| 480 | + } | |
| 481 | + } | |
| 482 | + | |
| 483 | + /** | |
| 484 | + * Sets the {@link AspectRatioFrameLayout.ResizeMode}. | |
| 485 | + * | |
| 486 | + * @param resizeMode The {@link AspectRatioFrameLayout.ResizeMode}. | |
| 487 | + */ | |
| 488 | + public void setResizeMode(@AspectRatioFrameLayout.ResizeMode int resizeMode) { | |
| 489 | + Assertions.checkStateNotNull(contentFrame); | |
| 490 | + contentFrame.setResizeMode(resizeMode); | |
| 491 | + } | |
| 492 | + | |
| 493 | + /** | |
| 494 | + * Returns the {@link AspectRatioFrameLayout.ResizeMode}. | |
| 495 | + */ | |
| 496 | + public @AspectRatioFrameLayout.ResizeMode | |
| 497 | + int getResizeMode() { | |
| 498 | + Assertions.checkStateNotNull(contentFrame); | |
| 499 | + return contentFrame.getResizeMode(); | |
| 500 | + } | |
| 501 | + | |
| 502 | + /** | |
| 503 | + * Returns whether artwork is displayed if present in the media. | |
| 504 | + */ | |
| 505 | + public boolean getUseArtwork() { | |
| 506 | + return useArtwork; | |
| 507 | + } | |
| 508 | + | |
| 509 | + /** | |
| 510 | + * Sets whether artwork is displayed if present in the media. | |
| 511 | + * | |
| 512 | + * @param useArtwork Whether artwork is displayed. | |
| 513 | + */ | |
| 514 | + public void setUseArtwork(boolean useArtwork) { | |
| 515 | + Assertions.checkState(!useArtwork || artworkView != null); | |
| 516 | + if (this.useArtwork != useArtwork) { | |
| 517 | + this.useArtwork = useArtwork; | |
| 518 | + updateForCurrentTrackSelections(/* isNewPlayer= */ false); | |
| 519 | + } | |
| 520 | + } | |
| 521 | + | |
| 522 | + /** | |
| 523 | + * Returns the default artwork to display. | |
| 524 | + */ | |
| 525 | + @Nullable | |
| 526 | + public Drawable getDefaultArtwork() { | |
| 527 | + return defaultArtwork; | |
| 528 | + } | |
| 529 | + | |
| 530 | + /** | |
| 531 | + * Sets the default artwork to display if {@code useArtwork} is {@code true} and no artwork is | |
| 532 | + * present in the media. | |
| 533 | + * | |
| 534 | + * @param defaultArtwork the default artwork to display. | |
| 535 | + * @deprecated use (@link {@link #setDefaultArtwork(Drawable)} instead. | |
| 536 | + */ | |
| 537 | + @Deprecated | |
| 538 | + public void setDefaultArtwork(@Nullable Bitmap defaultArtwork) { | |
| 539 | + setDefaultArtwork( | |
| 540 | + defaultArtwork == null ? null : new BitmapDrawable(getResources(), defaultArtwork)); | |
| 541 | + } | |
| 542 | + | |
| 543 | + /** | |
| 544 | + * Sets the default artwork to display if {@code useArtwork} is {@code true} and no artwork is | |
| 545 | + * present in the media. | |
| 546 | + * | |
| 547 | + * @param defaultArtwork the default artwork to display | |
| 548 | + */ | |
| 549 | + public void setDefaultArtwork(@Nullable Drawable defaultArtwork) { | |
| 550 | + if (this.defaultArtwork != defaultArtwork) { | |
| 551 | + this.defaultArtwork = defaultArtwork; | |
| 552 | + updateForCurrentTrackSelections(/* isNewPlayer= */ false); | |
| 553 | + } | |
| 554 | + } | |
| 555 | + | |
| 556 | + /** | |
| 557 | + * Returns whether the playback controls can be shown. | |
| 558 | + */ | |
| 559 | + public boolean getUseController() { | |
| 560 | + return useController; | |
| 561 | + } | |
| 562 | + | |
| 563 | + /** | |
| 564 | + * Sets whether the playback controls can be shown. If set to {@code false} the playback controls | |
| 565 | + * are never visible and are disconnected from the player. | |
| 566 | + * | |
| 567 | + * @param useController Whether the playback controls can be shown. | |
| 568 | + */ | |
| 569 | + public void setUseController(boolean useController) { | |
| 570 | + Assertions.checkState(!useController || controller != null); | |
| 571 | + if (this.useController == useController) { | |
| 572 | + return; | |
| 573 | + } | |
| 574 | + this.useController = useController; | |
| 575 | + if (useController()) { | |
| 576 | + controller.setPlayer(player); | |
| 577 | + } else if (controller != null) { | |
| 578 | + controller.hide(); | |
| 579 | + controller.setPlayer(/* player= */ null); | |
| 580 | + } | |
| 581 | + updateContentDescription(); | |
| 582 | + } | |
| 583 | + | |
| 584 | + /** | |
| 585 | + * Sets the background color of the {@code exo_shutter} view. | |
| 586 | + * | |
| 587 | + * @param color The background color. | |
| 588 | + */ | |
| 589 | + public void setShutterBackgroundColor(int color) { | |
| 590 | + if (shutterView != null) { | |
| 591 | + shutterView.setBackgroundColor(color); | |
| 592 | + } | |
| 593 | + } | |
| 594 | + | |
| 595 | + /** | |
| 596 | + * Sets whether the currently displayed video frame or media artwork is kept visible when the | |
| 597 | + * player is reset. A player reset is defined to mean the player being re-prepared with different | |
| 598 | + * media, the player transitioning to unprepared media, {@link Player#stop(boolean)} being called | |
| 599 | + * with {@code reset=true}, or the player being replaced or cleared by calling {@link | |
| 600 | + * #setPlayer(Player)}. | |
| 601 | + * | |
| 602 | + * <p>If enabled, the currently displayed video frame or media artwork will be kept visible until | |
| 603 | + * the player set on the view has been successfully prepared with new media and loaded enough of | |
| 604 | + * it to have determined the available tracks. Hence enabling this option allows transitioning | |
| 605 | + * from playing one piece of media to another, or from using one player instance to another, | |
| 606 | + * without clearing the view's content. | |
| 607 | + * | |
| 608 | + * <p>If disabled, the currently displayed video frame or media artwork will be hidden as soon as | |
| 609 | + * the player is reset. Note that the video frame is hidden by making {@code exo_shutter} visible. | |
| 610 | + * Hence the video frame will not be hidden if using a custom layout that omits this view. | |
| 611 | + * | |
| 612 | + * @param keepContentOnPlayerReset Whether the currently displayed video frame or media artwork is | |
| 613 | + * kept visible when the player is reset. | |
| 614 | + */ | |
| 615 | + public void setKeepContentOnPlayerReset(boolean keepContentOnPlayerReset) { | |
| 616 | + if (this.keepContentOnPlayerReset != keepContentOnPlayerReset) { | |
| 617 | + this.keepContentOnPlayerReset = keepContentOnPlayerReset; | |
| 618 | + updateForCurrentTrackSelections(/* isNewPlayer= */ false); | |
| 619 | + } | |
| 620 | + } | |
| 621 | + | |
| 622 | + /** | |
| 623 | + * Sets whether a buffering spinner is displayed when the player is in the buffering state. The | |
| 624 | + * buffering spinner is not displayed by default. | |
| 625 | + * | |
| 626 | + * @param showBuffering Whether the buffering icon is displayed | |
| 627 | + * @deprecated Use {@link #setShowBuffering(int)} | |
| 628 | + */ | |
| 629 | + @SuppressLint("WrongConstant") | |
| 630 | + @Deprecated | |
| 631 | + public void setShowBuffering(boolean showBuffering) { | |
| 632 | + setShowBuffering(showBuffering ? SHOW_BUFFERING_WHEN_PLAYING : SHOW_BUFFERING_NEVER); | |
| 633 | + } | |
| 634 | + | |
| 635 | + /** | |
| 636 | + * Sets whether a buffering spinner is displayed when the player is in the buffering state. The | |
| 637 | + * buffering spinner is not displayed by default. | |
| 638 | + * | |
| 639 | + * @param showBuffering The mode that defines when the buffering spinner is displayed. One of | |
| 640 | + * {@link #SHOW_BUFFERING_NEVER}, {@link #SHOW_BUFFERING_WHEN_PLAYING} and {@link | |
| 641 | + * #SHOW_BUFFERING_ALWAYS}. | |
| 642 | + */ | |
| 643 | + public void setShowBuffering(@PlayerView.ShowBuffering int showBuffering) { | |
| 644 | + if (this.showBuffering != showBuffering) { | |
| 645 | + this.showBuffering = showBuffering; | |
| 646 | + updateBuffering(); | |
| 647 | + } | |
| 648 | + } | |
| 649 | + | |
| 650 | + /** | |
| 651 | + * Sets the optional {@link ErrorMessageProvider}. | |
| 652 | + * | |
| 653 | + * @param errorMessageProvider The error message provider. | |
| 654 | + */ | |
| 655 | + public void setErrorMessageProvider( | |
| 656 | + @Nullable ErrorMessageProvider<? super ExoPlaybackException> errorMessageProvider) { | |
| 657 | + if (this.errorMessageProvider != errorMessageProvider) { | |
| 658 | + this.errorMessageProvider = errorMessageProvider; | |
| 659 | + updateErrorMessage(); | |
| 660 | + } | |
| 661 | + } | |
| 662 | + | |
| 663 | + /** | |
| 664 | + * Sets a custom error message to be displayed by the view. The error message will be displayed | |
| 665 | + * permanently, unless it is cleared by passing {@code null} to this method. | |
| 666 | + * | |
| 667 | + * @param message The message to display, or {@code null} to clear a previously set message. | |
| 668 | + */ | |
| 669 | + public void setCustomErrorMessage(@Nullable CharSequence message) { | |
| 670 | + Assertions.checkState(errorMessageView != null); | |
| 671 | + customErrorMessage = message; | |
| 672 | + updateErrorMessage(); | |
| 673 | + } | |
| 674 | + | |
| 675 | + @Override | |
| 676 | + public boolean dispatchKeyEvent(KeyEvent event) { | |
| 677 | + if (player != null && player.isPlayingAd()) { | |
| 678 | + return super.dispatchKeyEvent(event); | |
| 679 | + } | |
| 680 | + | |
| 681 | + boolean isDpadKey = isDpadKey(event.getKeyCode()); | |
| 682 | + boolean handled = false; | |
| 683 | + if (isDpadKey && useController() && !controller.isVisible()) { | |
| 684 | + // Handle the key event by showing the controller. | |
| 685 | + maybeShowController(true); | |
| 686 | + handled = true; | |
| 687 | + } else if (dispatchMediaKeyEvent(event) || super.dispatchKeyEvent(event)) { | |
| 688 | + // The key event was handled as a media key or by the super class. We should also show the | |
| 689 | + // controller, or extend its show timeout if already visible. | |
| 690 | + maybeShowController(true); | |
| 691 | + handled = true; | |
| 692 | + } else if (isDpadKey && useController()) { | |
| 693 | + // The key event wasn't handled, but we should extend the controller's show timeout. | |
| 694 | + maybeShowController(true); | |
| 695 | + } | |
| 696 | + return handled; | |
| 697 | + } | |
| 698 | + | |
| 699 | + /** | |
| 700 | + * Called to process media key events. Any {@link KeyEvent} can be passed but only media key | |
| 701 | + * events will be handled. Does nothing if playback controls are disabled. | |
| 702 | + * | |
| 703 | + * @param event A key event. | |
| 704 | + * @return Whether the key event was handled. | |
| 705 | + */ | |
| 706 | + public boolean dispatchMediaKeyEvent(KeyEvent event) { | |
| 707 | + return useController() && controller.dispatchMediaKeyEvent(event); | |
| 708 | + } | |
| 709 | + | |
| 710 | + /** | |
| 711 | + * Returns whether the controller is currently visible. | |
| 712 | + */ | |
| 713 | + public boolean isControllerVisible() { | |
| 714 | + return controller != null && controller.isVisible(); | |
| 715 | + } | |
| 716 | + | |
| 717 | + /** | |
| 718 | + * Shows the playback controls. Does nothing if playback controls are disabled. | |
| 719 | + * | |
| 720 | + * <p>The playback controls are automatically hidden during playback after {{@link | |
| 721 | + * #getControllerShowTimeoutMs()}}. They are shown indefinitely when playback has not started yet, | |
| 722 | + * is paused, has ended or failed. | |
| 723 | + */ | |
| 724 | + public void showController() { | |
| 725 | + showController(shouldShowControllerIndefinitely()); | |
| 726 | + } | |
| 727 | + | |
| 728 | + /** | |
| 729 | + * Hides the playback controls. Does nothing if playback controls are disabled. | |
| 730 | + */ | |
| 731 | + public void hideController() { | |
| 732 | + if (controller != null) { | |
| 733 | + controller.hide(); | |
| 734 | + } | |
| 735 | + } | |
| 736 | + | |
| 737 | + /** | |
| 738 | + * Returns the playback controls timeout. The playback controls are automatically hidden after | |
| 739 | + * this duration of time has elapsed without user input and with playback or buffering in | |
| 740 | + * progress. | |
| 741 | + * | |
| 742 | + * @return The timeout in milliseconds. A non-positive value will cause the controller to remain | |
| 743 | + * visible indefinitely. | |
| 744 | + */ | |
| 745 | + public int getControllerShowTimeoutMs() { | |
| 746 | + return controllerShowTimeoutMs; | |
| 747 | + } | |
| 748 | + | |
| 749 | + /** | |
| 750 | + * Sets the playback controls timeout. The playback controls are automatically hidden after this | |
| 751 | + * duration of time has elapsed without user input and with playback or buffering in progress. | |
| 752 | + * | |
| 753 | + * @param controllerShowTimeoutMs The timeout in milliseconds. A non-positive value will cause the | |
| 754 | + * controller to remain visible indefinitely. | |
| 755 | + */ | |
| 756 | + public void setControllerShowTimeoutMs(int controllerShowTimeoutMs) { | |
| 757 | + Assertions.checkStateNotNull(controller); | |
| 758 | + this.controllerShowTimeoutMs = controllerShowTimeoutMs; | |
| 759 | + if (controller.isVisible()) { | |
| 760 | + // Update the controller's timeout if necessary. | |
| 761 | + showController(); | |
| 762 | + } | |
| 763 | + } | |
| 764 | + | |
| 765 | + /** | |
| 766 | + * Returns whether the playback controls are hidden by touch events. | |
| 767 | + */ | |
| 768 | + public boolean getControllerHideOnTouch() { | |
| 769 | + return controllerHideOnTouch; | |
| 770 | + } | |
| 771 | + | |
| 772 | + /** | |
| 773 | + * Sets whether the playback controls are hidden by touch events. | |
| 774 | + * | |
| 775 | + * @param controllerHideOnTouch Whether the playback controls are hidden by touch events. | |
| 776 | + */ | |
| 777 | + public void setControllerHideOnTouch(boolean controllerHideOnTouch) { | |
| 778 | + Assertions.checkStateNotNull(controller); | |
| 779 | + this.controllerHideOnTouch = controllerHideOnTouch; | |
| 780 | + updateContentDescription(); | |
| 781 | + } | |
| 782 | + | |
| 783 | + /** | |
| 784 | + * Returns whether the playback controls are automatically shown when playback starts, pauses, | |
| 785 | + * ends, or fails. If set to false, the playback controls can be manually operated with {@link | |
| 786 | + * #showController()} and {@link #hideController()}. | |
| 787 | + */ | |
| 788 | + public boolean getControllerAutoShow() { | |
| 789 | + return controllerAutoShow; | |
| 790 | + } | |
| 791 | + | |
| 792 | + /** | |
| 793 | + * Sets whether the playback controls are automatically shown when playback starts, pauses, ends, | |
| 794 | + * or fails. If set to false, the playback controls can be manually operated with {@link | |
| 795 | + * #showController()} and {@link #hideController()}. | |
| 796 | + * | |
| 797 | + * @param controllerAutoShow Whether the playback controls are allowed to show automatically. | |
| 798 | + */ | |
| 799 | + public void setControllerAutoShow(boolean controllerAutoShow) { | |
| 800 | + this.controllerAutoShow = controllerAutoShow; | |
| 801 | + } | |
| 802 | + | |
| 803 | + /** | |
| 804 | + * Sets whether the playback controls are hidden when ads are playing. Controls are always shown | |
| 805 | + * during ads if they are enabled and the player is paused. | |
| 806 | + * | |
| 807 | + * @param controllerHideDuringAds Whether the playback controls are hidden when ads are playing. | |
| 808 | + */ | |
| 809 | + public void setControllerHideDuringAds(boolean controllerHideDuringAds) { | |
| 810 | + this.controllerHideDuringAds = controllerHideDuringAds; | |
| 811 | + } | |
| 812 | + | |
| 813 | + /** | |
| 814 | + * Set the {@link CNLiveBaseVideoControlView.VisibilityListener}. | |
| 815 | + * | |
| 816 | + * @param listener The listener to be notified about visibility changes, or null to remove the | |
| 817 | + * current listener. | |
| 818 | + */ | |
| 819 | + public void setControllerVisibilityListener( | |
| 820 | + @Nullable CNLiveBaseVideoControlView.VisibilityListener listener) { | |
| 821 | + Assertions.checkStateNotNull(controller); | |
| 822 | + if (this.controllerVisibilityListener == listener) { | |
| 823 | + return; | |
| 824 | + } | |
| 825 | + if (this.controllerVisibilityListener != null) { | |
| 826 | + controller.removeVisibilityListener(this.controllerVisibilityListener); | |
| 827 | + } | |
| 828 | + this.controllerVisibilityListener = listener; | |
| 829 | + if (listener != null) { | |
| 830 | + controller.addVisibilityListener(listener); | |
| 831 | + } | |
| 832 | + } | |
| 833 | + | |
| 834 | + /** | |
| 835 | + * Sets the {@link PlaybackPreparer}. | |
| 836 | + * | |
| 837 | + * @param playbackPreparer The {@link PlaybackPreparer}, or null to remove the current playback | |
| 838 | + * preparer. | |
| 839 | + */ | |
| 840 | + public void setPlaybackPreparer(@Nullable PlaybackPreparer playbackPreparer) { | |
| 841 | + Assertions.checkStateNotNull(controller); | |
| 842 | + controller.setPlaybackPreparer(playbackPreparer); | |
| 843 | + } | |
| 844 | + | |
| 845 | + /** | |
| 846 | + * Sets the {@link ControlDispatcher}. | |
| 847 | + * | |
| 848 | + * @param controlDispatcher The {@link ControlDispatcher}, or null to use {@link | |
| 849 | + * DefaultControlDispatcher}. | |
| 850 | + */ | |
| 851 | + public void setControlDispatcher(@Nullable CNLiveControlDispatcher controlDispatcher) { | |
| 852 | + Assertions.checkStateNotNull(controller); | |
| 853 | +// controller.setControlDispatcher(controlDispatcher); | |
| 854 | + } | |
| 855 | + | |
| 856 | + /** | |
| 857 | + * Sets the rewind increment in milliseconds. | |
| 858 | + * | |
| 859 | + * @param rewindMs The rewind increment in milliseconds. A non-positive value will cause the | |
| 860 | + * rewind button to be disabled. | |
| 861 | + */ | |
| 862 | + public void setRewindIncrementMs(int rewindMs) { | |
| 863 | + Assertions.checkStateNotNull(controller); | |
| 864 | + controller.setRewindIncrementMs(rewindMs); | |
| 865 | + } | |
| 866 | + | |
| 867 | + /** | |
| 868 | + * Sets the fast forward increment in milliseconds. | |
| 869 | + * | |
| 870 | + * @param fastForwardMs The fast forward increment in milliseconds. A non-positive value will | |
| 871 | + * cause the fast forward button to be disabled. | |
| 872 | + */ | |
| 873 | + public void setFastForwardIncrementMs(int fastForwardMs) { | |
| 874 | + Assertions.checkStateNotNull(controller); | |
| 875 | + controller.setFastForwardIncrementMs(fastForwardMs); | |
| 876 | + } | |
| 877 | + | |
| 878 | + /** | |
| 879 | + * Sets which repeat toggle modes are enabled. | |
| 880 | + * | |
| 881 | + * @param repeatToggleModes A set of {@link RepeatModeUtil.RepeatToggleModes}. | |
| 882 | + */ | |
| 883 | + public void setRepeatToggleModes(@RepeatModeUtil.RepeatToggleModes int repeatToggleModes) { | |
| 884 | + Assertions.checkStateNotNull(controller); | |
| 885 | + controller.setRepeatToggleModes(repeatToggleModes); | |
| 886 | + } | |
| 887 | + | |
| 888 | + /** | |
| 889 | + * Sets whether the shuffle button is shown. | |
| 890 | + * | |
| 891 | + * @param showShuffleButton Whether the shuffle button is shown. | |
| 892 | + */ | |
| 893 | + public void setShowShuffleButton(boolean showShuffleButton) { | |
| 894 | + Assertions.checkStateNotNull(controller); | |
| 895 | + controller.setShowShuffleButton(showShuffleButton); | |
| 896 | + } | |
| 897 | + | |
| 898 | + /** | |
| 899 | + * Sets whether the time bar should show all windows, as opposed to just the current one. | |
| 900 | + * | |
| 901 | + * @param showMultiWindowTimeBar Whether to show all windows. | |
| 902 | + */ | |
| 903 | + public void setShowMultiWindowTimeBar(boolean showMultiWindowTimeBar) { | |
| 904 | + Assertions.checkStateNotNull(controller); | |
| 905 | + controller.setShowMultiWindowTimeBar(showMultiWindowTimeBar); | |
| 906 | + } | |
| 907 | + | |
| 908 | + /** | |
| 909 | + * Sets the millisecond positions of extra ad markers relative to the start of the window (or | |
| 910 | + * timeline, if in multi-window mode) and whether each extra ad has been played or not. The | |
| 911 | + * markers are shown in addition to any ad markers for ads in the player's timeline. | |
| 912 | + * | |
| 913 | + * @param extraAdGroupTimesMs The millisecond timestamps of the extra ad markers to show, or | |
| 914 | + * {@code null} to show no extra ad markers. | |
| 915 | + * @param extraPlayedAdGroups Whether each ad has been played, or {@code null} to show no extra ad | |
| 916 | + * markers. | |
| 917 | + */ | |
| 918 | + public void setExtraAdGroupMarkers( | |
| 919 | + @Nullable long[] extraAdGroupTimesMs, @Nullable boolean[] extraPlayedAdGroups) { | |
| 920 | + Assertions.checkStateNotNull(controller); | |
| 921 | + controller.setExtraAdGroupMarkers(extraAdGroupTimesMs, extraPlayedAdGroups); | |
| 922 | + } | |
| 923 | + | |
| 924 | + /** | |
| 925 | + * Set the {@link AspectRatioFrameLayout.AspectRatioListener}. | |
| 926 | + * | |
| 927 | + * @param listener The listener to be notified about aspect ratios changes of the video content or | |
| 928 | + * the content frame. | |
| 929 | + */ | |
| 930 | + public void setAspectRatioListener( | |
| 931 | + @Nullable AspectRatioFrameLayout.AspectRatioListener listener) { | |
| 932 | + Assertions.checkStateNotNull(contentFrame); | |
| 933 | + contentFrame.setAspectRatioListener(listener); | |
| 934 | + } | |
| 935 | + | |
| 936 | + /** | |
| 937 | + * Gets the view onto which video is rendered. This is a: | |
| 938 | + * | |
| 939 | + * <ul> | |
| 940 | + * <li>{@link SurfaceView} by default, or if the {@code surface_type} attribute is set to {@code | |
| 941 | + * surface_view}. | |
| 942 | + * <li>{@link TextureView} if {@code surface_type} is {@code texture_view}. | |
| 943 | + * <li>{@link SphericalGLSurfaceView} if {@code surface_type} is {@code | |
| 944 | + * spherical_gl_surface_view}. | |
| 945 | + * <li>{@link VideoDecoderGLSurfaceView} if {@code surface_type} is {@code | |
| 946 | + * video_decoder_gl_surface_view}. | |
| 947 | + * <li>{@code null} if {@code surface_type} is {@code none}. | |
| 948 | + * </ul> | |
| 949 | + * | |
| 950 | + * @return The {@link SurfaceView}, {@link TextureView}, {@link SphericalGLSurfaceView}, {@link | |
| 951 | + * VideoDecoderGLSurfaceView} or {@code null}. | |
| 952 | + */ | |
| 953 | + @Nullable | |
| 954 | + public View getVideoSurfaceView() { | |
| 955 | + return surfaceView; | |
| 956 | + } | |
| 957 | + | |
| 958 | + /** | |
| 959 | + * Gets the overlay {@link FrameLayout}, which can be populated with UI elements to show on top of | |
| 960 | + * the player. | |
| 961 | + * | |
| 962 | + * @return The overlay {@link FrameLayout}, or {@code null} if the layout has been customized and | |
| 963 | + * the overlay is not present. | |
| 964 | + */ | |
| 965 | + @Nullable | |
| 966 | + public FrameLayout getOverlayFrameLayout() { | |
| 967 | + return overlayFrameLayout; | |
| 968 | + } | |
| 969 | + | |
| 970 | + /** | |
| 971 | + * Gets the {@link SubtitleView}. | |
| 972 | + * | |
| 973 | + * @return The {@link SubtitleView}, or {@code null} if the layout has been customized and the | |
| 974 | + * subtitle view is not present. | |
| 975 | + */ | |
| 976 | + @Nullable | |
| 977 | + public SubtitleView getSubtitleView() { | |
| 978 | + return subtitleView; | |
| 979 | + } | |
| 980 | + | |
| 981 | + @Override | |
| 982 | + public boolean onTouchEvent(MotionEvent event) { | |
| 983 | + if (!useController() || player == null) { | |
| 984 | + return false; | |
| 985 | + } | |
| 986 | + switch (event.getAction()) { | |
| 987 | + case MotionEvent.ACTION_DOWN: | |
| 988 | + isTouching = true; | |
| 989 | + return true; | |
| 990 | + case MotionEvent.ACTION_UP: | |
| 991 | + if (isTouching) { | |
| 992 | + isTouching = false; | |
| 993 | + performClick(); | |
| 994 | + return true; | |
| 995 | + } | |
| 996 | + return false; | |
| 997 | + default: | |
| 998 | + return false; | |
| 999 | + } | |
| 1000 | + } | |
| 1001 | + | |
| 1002 | + @Override | |
| 1003 | + public boolean performClick() { | |
| 1004 | + super.performClick(); | |
| 1005 | + return toggleControllerVisibility(); | |
| 1006 | + } | |
| 1007 | + | |
| 1008 | + @Override | |
| 1009 | + public boolean onTrackballEvent(MotionEvent ev) { | |
| 1010 | + if (!useController() || player == null) { | |
| 1011 | + return false; | |
| 1012 | + } | |
| 1013 | + maybeShowController(true); | |
| 1014 | + return true; | |
| 1015 | + } | |
| 1016 | + | |
| 1017 | + /** | |
| 1018 | + * Should be called when the player is visible to the user and if {@code surface_type} is {@code | |
| 1019 | + * spherical_gl_surface_view}. It is the counterpart to {@link #onPause()}. | |
| 1020 | + * | |
| 1021 | + * <p>This method should typically be called in {@code Activity.onStart()}, or {@code | |
| 1022 | + * Activity.onResume()} for API versions <= 23. | |
| 1023 | + */ | |
| 1024 | + public void onResume() { | |
| 1025 | + if (surfaceView instanceof SphericalGLSurfaceView) { | |
| 1026 | + ((SphericalGLSurfaceView) surfaceView).onResume(); | |
| 1027 | + } | |
| 1028 | + } | |
| 1029 | + | |
| 1030 | + /** | |
| 1031 | + * Should be called when the player is no longer visible to the user and if {@code surface_type} | |
| 1032 | + * is {@code spherical_gl_surface_view}. It is the counterpart to {@link #onResume()}. | |
| 1033 | + * | |
| 1034 | + * <p>This method should typically be called in {@code Activity.onStop()}, or {@code | |
| 1035 | + * Activity.onPause()} for API versions <= 23. | |
| 1036 | + */ | |
| 1037 | + public void onPause() { | |
| 1038 | + if (surfaceView instanceof SphericalGLSurfaceView) { | |
| 1039 | + ((SphericalGLSurfaceView) surfaceView).onPause(); | |
| 1040 | + } | |
| 1041 | + } | |
| 1042 | + | |
| 1043 | + /** | |
| 1044 | + * Called when there's a change in the aspect ratio of the content being displayed. The default | |
| 1045 | + * implementation sets the aspect ratio of the content frame to that of the content, unless the | |
| 1046 | + * content view is a {@link SphericalGLSurfaceView} in which case the frame's aspect ratio is | |
| 1047 | + * cleared. | |
| 1048 | + * | |
| 1049 | + * @param contentAspectRatio The aspect ratio of the content. | |
| 1050 | + * @param contentFrame The content frame, or {@code null}. | |
| 1051 | + * @param contentView The view that holds the content being displayed, or {@code null}. | |
| 1052 | + */ | |
| 1053 | + protected void onContentAspectRatioChanged( | |
| 1054 | + float contentAspectRatio, | |
| 1055 | + @Nullable AspectRatioFrameLayout contentFrame, | |
| 1056 | + @Nullable View contentView) { | |
| 1057 | + if (contentFrame != null) { | |
| 1058 | + contentFrame.setAspectRatio( | |
| 1059 | + contentView instanceof SphericalGLSurfaceView ? 0 : contentAspectRatio); | |
| 1060 | + } | |
| 1061 | + } | |
| 1062 | + | |
| 1063 | + // AdsLoader.AdViewProvider implementation. | |
| 1064 | + | |
| 1065 | + @Override | |
| 1066 | + public ViewGroup getAdViewGroup() { | |
| 1067 | + return Assertions.checkStateNotNull( | |
| 1068 | + adOverlayFrameLayout, "exo_ad_overlay must be present for ad playback"); | |
| 1069 | + } | |
| 1070 | + | |
| 1071 | + @Override | |
| 1072 | + public View[] getAdOverlayViews() { | |
| 1073 | + ArrayList<View> overlayViews = new ArrayList<>(); | |
| 1074 | + if (overlayFrameLayout != null) { | |
| 1075 | + overlayViews.add(overlayFrameLayout); | |
| 1076 | + } | |
| 1077 | + if (controller != null) { | |
| 1078 | + overlayViews.add(controller); | |
| 1079 | + } | |
| 1080 | + return overlayViews.toArray(new View[0]); | |
| 1081 | + } | |
| 1082 | + | |
| 1083 | + // Internal methods. | |
| 1084 | + | |
| 1085 | + private boolean useController() { | |
| 1086 | + if (useController) { | |
| 1087 | + Assertions.checkStateNotNull(controller); | |
| 1088 | + return true; | |
| 1089 | + } | |
| 1090 | + return false; | |
| 1091 | + } | |
| 1092 | + | |
| 1093 | + private boolean useArtwork() { | |
| 1094 | + if (useArtwork) { | |
| 1095 | + Assertions.checkStateNotNull(artworkView); | |
| 1096 | + return true; | |
| 1097 | + } | |
| 1098 | + return false; | |
| 1099 | + } | |
| 1100 | + | |
| 1101 | + private boolean toggleControllerVisibility() { | |
| 1102 | + if (!useController() || player == null) { | |
| 1103 | + return false; | |
| 1104 | + } | |
| 1105 | + if (!controller.isVisible()) { | |
| 1106 | + maybeShowController(true); | |
| 1107 | + } else if (controllerHideOnTouch) { | |
| 1108 | + controller.hide(); | |
| 1109 | + } | |
| 1110 | + return true; | |
| 1111 | + } | |
| 1112 | + | |
| 1113 | + /** | |
| 1114 | + * Shows the playback controls, but only if forced or shown indefinitely. | |
| 1115 | + */ | |
| 1116 | + private void maybeShowController(boolean isForced) { | |
| 1117 | + if (isPlayingAd() && controllerHideDuringAds) { | |
| 1118 | + return; | |
| 1119 | + } | |
| 1120 | + if (useController()) { | |
| 1121 | + boolean wasShowingIndefinitely = controller.isVisible() && controller.getShowTimeoutMs() <= 0; | |
| 1122 | + boolean shouldShowIndefinitely = shouldShowControllerIndefinitely(); | |
| 1123 | + if (isForced || wasShowingIndefinitely || shouldShowIndefinitely) { | |
| 1124 | + showController(shouldShowIndefinitely); | |
| 1125 | + } | |
| 1126 | + } | |
| 1127 | + } | |
| 1128 | + | |
| 1129 | + private boolean shouldShowControllerIndefinitely() { | |
| 1130 | + if (player == null) { | |
| 1131 | + return true; | |
| 1132 | + } | |
| 1133 | + int playbackState = player.getPlaybackState(); | |
| 1134 | + return controllerAutoShow | |
| 1135 | + && (playbackState == Player.STATE_IDLE | |
| 1136 | + || playbackState == Player.STATE_ENDED | |
| 1137 | + || !player.getPlayWhenReady()); | |
| 1138 | + } | |
| 1139 | + | |
| 1140 | + private void showController(boolean showIndefinitely) { | |
| 1141 | + if (!useController()) { | |
| 1142 | + return; | |
| 1143 | + } | |
| 1144 | + controller.setShowTimeoutMs(showIndefinitely ? 0 : controllerShowTimeoutMs); | |
| 1145 | + controller.show(); | |
| 1146 | + } | |
| 1147 | + | |
| 1148 | + private boolean isPlayingAd() { | |
| 1149 | + return player != null && player.isPlayingAd() && player.getPlayWhenReady(); | |
| 1150 | + } | |
| 1151 | + | |
| 1152 | + private void updateForCurrentTrackSelections(boolean isNewPlayer) { | |
| 1153 | + @Nullable VideoPlayer player = this.player; | |
| 1154 | + if (player == null || player.getCurrentTrackGroups().isEmpty()) { | |
| 1155 | + if (!keepContentOnPlayerReset) { | |
| 1156 | + hideArtwork(); | |
| 1157 | + closeShutter(); | |
| 1158 | + } | |
| 1159 | + return; | |
| 1160 | + } | |
| 1161 | + | |
| 1162 | + if (isNewPlayer && !keepContentOnPlayerReset) { | |
| 1163 | + // Hide any video from the previous player. | |
| 1164 | + closeShutter(); | |
| 1165 | + } | |
| 1166 | + | |
| 1167 | + TrackSelectionArray selections = player.getCurrentTrackSelections(); | |
| 1168 | + for (int i = 0; i < selections.length; i++) { | |
| 1169 | + if (player.getRendererType(i) == C.TRACK_TYPE_VIDEO && selections.get(i) != null) { | |
| 1170 | + // Video enabled so artwork must be hidden. If the shutter is closed, it will be opened in | |
| 1171 | + // onRenderedFirstFrame(). | |
| 1172 | + hideArtwork(); | |
| 1173 | + return; | |
| 1174 | + } | |
| 1175 | + } | |
| 1176 | + | |
| 1177 | + // Video disabled so the shutter must be closed. | |
| 1178 | + closeShutter(); | |
| 1179 | + // Display artwork if enabled and available, else hide it. | |
| 1180 | + if (useArtwork()) { | |
| 1181 | + for (int i = 0; i < selections.length; i++) { | |
| 1182 | + @Nullable TrackSelection selection = selections.get(i); | |
| 1183 | + if (selection != null) { | |
| 1184 | + for (int j = 0; j < selection.length(); j++) { | |
| 1185 | + @Nullable Metadata metadata = selection.getFormat(j).metadata; | |
| 1186 | + if (metadata != null && setArtworkFromMetadata(metadata)) { | |
| 1187 | + return; | |
| 1188 | + } | |
| 1189 | + } | |
| 1190 | + } | |
| 1191 | + } | |
| 1192 | + if (setDrawableArtwork(defaultArtwork)) { | |
| 1193 | + return; | |
| 1194 | + } | |
| 1195 | + } | |
| 1196 | + // Artwork disabled or unavailable. | |
| 1197 | + hideArtwork(); | |
| 1198 | + } | |
| 1199 | + | |
| 1200 | + private boolean setArtworkFromMetadata(Metadata metadata) { | |
| 1201 | + boolean isArtworkSet = false; | |
| 1202 | + int currentPictureType = PICTURE_TYPE_NOT_SET; | |
| 1203 | + for (int i = 0; i < metadata.length(); i++) { | |
| 1204 | + Metadata.Entry metadataEntry = metadata.get(i); | |
| 1205 | + int pictureType; | |
| 1206 | + byte[] bitmapData; | |
| 1207 | + if (metadataEntry instanceof ApicFrame) { | |
| 1208 | + bitmapData = ((ApicFrame) metadataEntry).pictureData; | |
| 1209 | + pictureType = ((ApicFrame) metadataEntry).pictureType; | |
| 1210 | + } else if (metadataEntry instanceof PictureFrame) { | |
| 1211 | + bitmapData = ((PictureFrame) metadataEntry).pictureData; | |
| 1212 | + pictureType = ((PictureFrame) metadataEntry).pictureType; | |
| 1213 | + } else { | |
| 1214 | + continue; | |
| 1215 | + } | |
| 1216 | + // Prefer the first front cover picture. If there aren't any, prefer the first picture. | |
| 1217 | + if (currentPictureType == PICTURE_TYPE_NOT_SET || pictureType == PICTURE_TYPE_FRONT_COVER) { | |
| 1218 | + Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length); | |
| 1219 | + isArtworkSet = setDrawableArtwork(new BitmapDrawable(getResources(), bitmap)); | |
| 1220 | + currentPictureType = pictureType; | |
| 1221 | + if (currentPictureType == PICTURE_TYPE_FRONT_COVER) { | |
| 1222 | + break; | |
| 1223 | + } | |
| 1224 | + } | |
| 1225 | + } | |
| 1226 | + return isArtworkSet; | |
| 1227 | + } | |
| 1228 | + | |
| 1229 | + private boolean setDrawableArtwork(@Nullable Drawable drawable) { | |
| 1230 | + if (drawable != null) { | |
| 1231 | + int drawableWidth = drawable.getIntrinsicWidth(); | |
| 1232 | + int drawableHeight = drawable.getIntrinsicHeight(); | |
| 1233 | + if (drawableWidth > 0 && drawableHeight > 0) { | |
| 1234 | + float artworkAspectRatio = (float) drawableWidth / drawableHeight; | |
| 1235 | + onContentAspectRatioChanged(artworkAspectRatio, contentFrame, artworkView); | |
| 1236 | + artworkView.setImageDrawable(drawable); | |
| 1237 | + artworkView.setVisibility(VISIBLE); | |
| 1238 | + return true; | |
| 1239 | + } | |
| 1240 | + } | |
| 1241 | + return false; | |
| 1242 | + } | |
| 1243 | + | |
| 1244 | + private void hideArtwork() { | |
| 1245 | + if (artworkView != null) { | |
| 1246 | + artworkView.setImageResource(android.R.color.transparent); // Clears any bitmap reference. | |
| 1247 | + artworkView.setVisibility(INVISIBLE); | |
| 1248 | + } | |
| 1249 | + } | |
| 1250 | + | |
| 1251 | + private void closeShutter() { | |
| 1252 | + if (shutterView != null) { | |
| 1253 | + shutterView.setVisibility(View.VISIBLE); | |
| 1254 | + } | |
| 1255 | + } | |
| 1256 | + | |
| 1257 | + private void updateBuffering() { | |
| 1258 | + if (bufferingView != null) { | |
| 1259 | + boolean showBufferingSpinner = | |
| 1260 | + player != null | |
| 1261 | + && player.getPlaybackState() == Player.STATE_BUFFERING | |
| 1262 | + && (showBuffering == SHOW_BUFFERING_ALWAYS | |
| 1263 | + || (showBuffering == SHOW_BUFFERING_WHEN_PLAYING && player.getPlayWhenReady())); | |
| 1264 | + bufferingView.setVisibility(showBufferingSpinner ? View.VISIBLE : View.GONE); | |
| 1265 | + } | |
| 1266 | + } | |
| 1267 | + | |
| 1268 | + private void updateErrorMessage() { | |
| 1269 | + if (errorMessageView != null) { | |
| 1270 | + if (customErrorMessage != null) { | |
| 1271 | + errorMessageView.setText(customErrorMessage); | |
| 1272 | + errorMessageView.setVisibility(View.VISIBLE); | |
| 1273 | + return; | |
| 1274 | + } | |
| 1275 | + @Nullable ExoPlaybackException error = player != null ? player.getPlaybackError() : null; | |
| 1276 | + if (error != null && errorMessageProvider != null) { | |
| 1277 | + CharSequence errorMessage = errorMessageProvider.getErrorMessage(error).second; | |
| 1278 | + errorMessageView.setText(errorMessage); | |
| 1279 | + errorMessageView.setVisibility(View.VISIBLE); | |
| 1280 | + } else { | |
| 1281 | + errorMessageView.setVisibility(View.GONE); | |
| 1282 | + } | |
| 1283 | + } | |
| 1284 | + } | |
| 1285 | + | |
| 1286 | + private void updateContentDescription() { | |
| 1287 | + if (controller == null || !useController) { | |
| 1288 | + setContentDescription(/* contentDescription= */ null); | |
| 1289 | + } else if (controller.getVisibility() == View.VISIBLE) { | |
| 1290 | + setContentDescription( | |
| 1291 | + /* contentDescription= */ controllerHideOnTouch | |
| 1292 | + ? getResources().getString(R.string.exo_controls_hide) | |
| 1293 | + : null); | |
| 1294 | + } else { | |
| 1295 | + setContentDescription( | |
| 1296 | + /* contentDescription= */ getResources().getString(R.string.exo_controls_show)); | |
| 1297 | + } | |
| 1298 | + } | |
| 1299 | + | |
| 1300 | + @TargetApi(23) | |
| 1301 | + private static void configureEditModeLogoV23(Resources resources, ImageView logo) { | |
| 1302 | + logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo, null)); | |
| 1303 | + logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color, null)); | |
| 1304 | + } | |
| 1305 | + | |
| 1306 | + private static void configureEditModeLogo(Resources resources, ImageView logo) { | |
| 1307 | + logo.setImageDrawable(resources.getDrawable(R.drawable.exo_edit_mode_logo)); | |
| 1308 | + logo.setBackgroundColor(resources.getColor(R.color.exo_edit_mode_background_color)); | |
| 1309 | + } | |
| 1310 | + | |
| 1311 | + @SuppressWarnings("ResourceType") | |
| 1312 | + private static void setResizeModeRaw(AspectRatioFrameLayout aspectRatioFrame, int resizeMode) { | |
| 1313 | + aspectRatioFrame.setResizeMode(resizeMode); | |
| 1314 | + } | |
| 1315 | + | |
| 1316 | + /** | |
| 1317 | + * Applies a texture rotation to a {@link TextureView}. | |
| 1318 | + */ | |
| 1319 | + private static void applyTextureViewRotation(TextureView textureView, int textureViewRotation) { | |
| 1320 | + Matrix transformMatrix = new Matrix(); | |
| 1321 | + float textureViewWidth = textureView.getWidth(); | |
| 1322 | + float textureViewHeight = textureView.getHeight(); | |
| 1323 | + if (textureViewWidth != 0 && textureViewHeight != 0 && textureViewRotation != 0) { | |
| 1324 | + float pivotX = textureViewWidth / 2; | |
| 1325 | + float pivotY = textureViewHeight / 2; | |
| 1326 | + transformMatrix.postRotate(textureViewRotation, pivotX, pivotY); | |
| 1327 | + | |
| 1328 | + // After rotation, scale the rotated texture to fit the TextureView size. | |
| 1329 | + RectF originalTextureRect = new RectF(0, 0, textureViewWidth, textureViewHeight); | |
| 1330 | + RectF rotatedTextureRect = new RectF(); | |
| 1331 | + transformMatrix.mapRect(rotatedTextureRect, originalTextureRect); | |
| 1332 | + transformMatrix.postScale( | |
| 1333 | + textureViewWidth / rotatedTextureRect.width(), | |
| 1334 | + textureViewHeight / rotatedTextureRect.height(), | |
| 1335 | + pivotX, | |
| 1336 | + pivotY); | |
| 1337 | + } | |
| 1338 | + textureView.setTransform(transformMatrix); | |
| 1339 | + } | |
| 1340 | + | |
| 1341 | + @SuppressLint("InlinedApi") | |
| 1342 | + private boolean isDpadKey(int keyCode) { | |
| 1343 | + return keyCode == KeyEvent.KEYCODE_DPAD_UP | |
| 1344 | + || keyCode == KeyEvent.KEYCODE_DPAD_UP_RIGHT | |
| 1345 | + || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT | |
| 1346 | + || keyCode == KeyEvent.KEYCODE_DPAD_DOWN_RIGHT | |
| 1347 | + || keyCode == KeyEvent.KEYCODE_DPAD_DOWN | |
| 1348 | + || keyCode == KeyEvent.KEYCODE_DPAD_DOWN_LEFT | |
| 1349 | + || keyCode == KeyEvent.KEYCODE_DPAD_LEFT | |
| 1350 | + || keyCode == KeyEvent.KEYCODE_DPAD_UP_LEFT | |
| 1351 | + || keyCode == KeyEvent.KEYCODE_DPAD_CENTER; | |
| 1352 | + } | |
| 1353 | + | |
| 1354 | + private final class ComponentListener | |
| 1355 | + implements Player.EventListener, | |
| 1356 | + TextOutput, | |
| 1357 | + VideoListener, | |
| 1358 | + OnLayoutChangeListener, | |
| 1359 | + SingleTapListener, | |
| 1360 | + CNLiveBaseVideoControlView.VisibilityListener { | |
| 1361 | + | |
| 1362 | + // TextOutput implementation | |
| 1363 | + | |
| 1364 | + @Override | |
| 1365 | + public void onCues(List<Cue> cues) { | |
| 1366 | + if (subtitleView != null) { | |
| 1367 | + subtitleView.onCues(cues); | |
| 1368 | + } | |
| 1369 | + } | |
| 1370 | + | |
| 1371 | + // VideoListener implementation | |
| 1372 | + | |
| 1373 | + @Override | |
| 1374 | + public void onVideoSizeChanged( | |
| 1375 | + int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) { | |
| 1376 | + float videoAspectRatio = | |
| 1377 | + (height == 0 || width == 0) ? 1 : (width * pixelWidthHeightRatio) / height; | |
| 1378 | + | |
| 1379 | + if (surfaceView instanceof TextureView) { | |
| 1380 | + // Try to apply rotation transformation when our surface is a TextureView. | |
| 1381 | + if (unappliedRotationDegrees == 90 || unappliedRotationDegrees == 270) { | |
| 1382 | + // We will apply a rotation 90/270 degree to the output texture of the TextureView. | |
| 1383 | + // In this case, the output video's width and height will be swapped. | |
| 1384 | + videoAspectRatio = 1 / videoAspectRatio; | |
| 1385 | + } | |
| 1386 | + if (textureViewRotation != 0) { | |
| 1387 | + surfaceView.removeOnLayoutChangeListener(this); | |
| 1388 | + } | |
| 1389 | + textureViewRotation = unappliedRotationDegrees; | |
| 1390 | + if (textureViewRotation != 0) { | |
| 1391 | + // The texture view's dimensions might be changed after layout step. | |
| 1392 | + // So add an OnLayoutChangeListener to apply rotation after layout step. | |
| 1393 | + surfaceView.addOnLayoutChangeListener(this); | |
| 1394 | + } | |
| 1395 | + applyTextureViewRotation((TextureView) surfaceView, textureViewRotation); | |
| 1396 | + } | |
| 1397 | + | |
| 1398 | + onContentAspectRatioChanged(videoAspectRatio, contentFrame, surfaceView); | |
| 1399 | + } | |
| 1400 | + | |
| 1401 | + @Override | |
| 1402 | + public void onRenderedFirstFrame() { | |
| 1403 | + if (shutterView != null) { | |
| 1404 | + shutterView.setVisibility(INVISIBLE); | |
| 1405 | + } | |
| 1406 | + } | |
| 1407 | + | |
| 1408 | + @Override | |
| 1409 | + public void onTracksChanged(TrackGroupArray tracks, TrackSelectionArray selections) { | |
| 1410 | + updateForCurrentTrackSelections(/* isNewPlayer= */ false); | |
| 1411 | + } | |
| 1412 | + | |
| 1413 | + // Player.EventListener implementation | |
| 1414 | + | |
| 1415 | + @Override | |
| 1416 | + public void onPlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) { | |
| 1417 | + updateBuffering(); | |
| 1418 | + updateErrorMessage(); | |
| 1419 | + if (isPlayingAd() && controllerHideDuringAds) { | |
| 1420 | + hideController(); | |
| 1421 | + } else { | |
| 1422 | + maybeShowController(false); | |
| 1423 | + } | |
| 1424 | + } | |
| 1425 | + | |
| 1426 | + @Override | |
| 1427 | + public void onPositionDiscontinuity(@Player.DiscontinuityReason int reason) { | |
| 1428 | + if (isPlayingAd() && controllerHideDuringAds) { | |
| 1429 | + hideController(); | |
| 1430 | + } | |
| 1431 | + } | |
| 1432 | + | |
| 1433 | + // OnLayoutChangeListener implementation | |
| 1434 | + | |
| 1435 | + @Override | |
| 1436 | + public void onLayoutChange( | |
| 1437 | + View view, | |
| 1438 | + int left, | |
| 1439 | + int top, | |
| 1440 | + int right, | |
| 1441 | + int bottom, | |
| 1442 | + int oldLeft, | |
| 1443 | + int oldTop, | |
| 1444 | + int oldRight, | |
| 1445 | + int oldBottom) { | |
| 1446 | + applyTextureViewRotation((TextureView) view, textureViewRotation); | |
| 1447 | + } | |
| 1448 | + | |
| 1449 | + // SingleTapListener implementation | |
| 1450 | + | |
| 1451 | + @Override | |
| 1452 | + public boolean onSingleTapUp(MotionEvent e) { | |
| 1453 | + return toggleControllerVisibility(); | |
| 1454 | + } | |
| 1455 | + | |
| 1456 | + // PlayerControlView.VisibilityListener implementation | |
| 1457 | + | |
| 1458 | + @Override | |
| 1459 | + public void onVisibilityChange(int visibility) { | |
| 1460 | + updateContentDescription(); | |
| 1461 | + } | |
| 1462 | + } | |
| 1463 | + | |
| 1464 | + | |
| 1465 | + /** | |
| 1466 | + * 设置播放进度条的颜色 | |
| 1467 | + * | |
| 1468 | + * @param color | |
| 1469 | + */ | |
| 1470 | + public void setPlayedProgressColor(@ColorInt int color) { | |
| 1471 | + if (null != controller && controller instanceof CNLiveBaseVideoControlView) { | |
| 1472 | + CNLiveBaseVideoControlView controlView = controller; | |
| 1473 | + controller.setPlayedColor(color); | |
| 1474 | + } | |
| 1475 | + } | |
| 1476 | + | |
| 1477 | +} | |
| 1478 | + | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/CacheUtil.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer; | |
| 2 | + | |
| 3 | +import android.content.ContentValues; | |
| 4 | +import android.content.Context; | |
| 5 | +import android.database.Cursor; | |
| 6 | +import android.database.SQLException; | |
| 7 | +import android.database.sqlite.SQLiteDatabase; | |
| 8 | +import android.database.sqlite.SQLiteOpenHelper; | |
| 9 | +import android.text.TextUtils; | |
| 10 | +import android.util.Log; | |
| 11 | + | |
| 12 | +import com.libs.video.cache.utils.DesUtil; | |
| 13 | +import com.libs.video.util.Config; | |
| 14 | + | |
| 15 | +import java.security.InvalidKeyException; | |
| 16 | +import java.security.spec.InvalidKeySpecException; | |
| 17 | +import java.util.UUID; | |
| 18 | + | |
| 19 | +import javax.crypto.BadPaddingException; | |
| 20 | +import javax.crypto.IllegalBlockSizeException; | |
| 21 | +import javax.crypto.NoSuchPaddingException; | |
| 22 | + | |
| 23 | +public class CacheUtil { | |
| 24 | + private static final String TAG = "CacheUtil"; | |
| 25 | + private final String encodeText=".cnlive.video."; | |
| 26 | + /** | |
| 27 | + * Database Name | |
| 28 | + */ | |
| 29 | + private static final String DATABASE_NAME = "cnlive_video_data"; | |
| 30 | + | |
| 31 | + /** | |
| 32 | + * Database Version | |
| 33 | + */ | |
| 34 | + private static final int DATABASE_VERSION = 1; | |
| 35 | + | |
| 36 | + /** | |
| 37 | + * Table Name | |
| 38 | + */ | |
| 39 | + private static final String DATABASE_TABLE = "tb_video_data"; | |
| 40 | + | |
| 41 | + /** | |
| 42 | + * Table columns | |
| 43 | + */ | |
| 44 | + public static final String KEY_NAME = "video_id"; | |
| 45 | + public static final String KEY_GRADE = "video_url"; | |
| 46 | + public static final String KEY_ROWID = "id"; | |
| 47 | + | |
| 48 | + /** | |
| 49 | + * Database creation sql statement | |
| 50 | + */ | |
| 51 | + private static final String CREATE_TABLE = | |
| 52 | + "create table " + DATABASE_TABLE + " (" + KEY_ROWID + " text primary key," | |
| 53 | + + KEY_NAME + " text not null, " + KEY_GRADE + " text not null);"; | |
| 54 | + | |
| 55 | + /** | |
| 56 | + * Context | |
| 57 | + */ | |
| 58 | + private final Context mCtx; | |
| 59 | + private DatabaseHelper mDbHelper; | |
| 60 | + private SQLiteDatabase mDb; | |
| 61 | + | |
| 62 | + /** | |
| 63 | + * Inner private class. Database Helper class for creating and updating database. | |
| 64 | + */ | |
| 65 | + private static class DatabaseHelper extends SQLiteOpenHelper { | |
| 66 | + DatabaseHelper(Context context) { | |
| 67 | + super(context, DATABASE_NAME, null, DATABASE_VERSION); | |
| 68 | + } | |
| 69 | + | |
| 70 | + /** | |
| 71 | + * onCreate method is called for the 1st time when database doesn't exists. | |
| 72 | + */ | |
| 73 | + @Override | |
| 74 | + public void onCreate(SQLiteDatabase db) { | |
| 75 | + Log.i(TAG, "Creating DataBase: " + CREATE_TABLE); | |
| 76 | + db.execSQL(CREATE_TABLE); | |
| 77 | + } | |
| 78 | + | |
| 79 | + /** | |
| 80 | + * onUpgrade method is called when database version changes. | |
| 81 | + */ | |
| 82 | + @Override | |
| 83 | + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { | |
| 84 | + Log.w(TAG, "Upgrading database from version " + oldVersion + " to " | |
| 85 | + + newVersion); | |
| 86 | + } | |
| 87 | + } | |
| 88 | + | |
| 89 | + /** | |
| 90 | + * Constructor - takes the context to allow the database to be | |
| 91 | + * opened/created | |
| 92 | + * | |
| 93 | + * @param ctx the Context within which to work | |
| 94 | + */ | |
| 95 | + public CacheUtil(Context ctx) { | |
| 96 | + this.mCtx = ctx; | |
| 97 | + open(); | |
| 98 | + } | |
| 99 | + | |
| 100 | + /** | |
| 101 | + * This method is used for creating/opening connection | |
| 102 | + * | |
| 103 | + * @return instance of CacheUtil | |
| 104 | + * @throws SQLException | |
| 105 | + */ | |
| 106 | + private CacheUtil open() throws SQLException { | |
| 107 | + if (null == mDbHelper) { | |
| 108 | + mDbHelper = new DatabaseHelper(mCtx); | |
| 109 | + mDb = mDbHelper.getWritableDatabase(); | |
| 110 | + } | |
| 111 | + return this; | |
| 112 | + } | |
| 113 | + | |
| 114 | + /** | |
| 115 | + * This method is used for closing the connection. | |
| 116 | + */ | |
| 117 | + public void close() { | |
| 118 | + mDbHelper.close(); | |
| 119 | + } | |
| 120 | + | |
| 121 | + /** | |
| 122 | + * This method is used to create/insert new record record. | |
| 123 | + * | |
| 124 | + * @param videoId | |
| 125 | + * @param videoUrl | |
| 126 | + * @return long | |
| 127 | + */ | |
| 128 | + public long add(String videoId, String videoUrl) throws IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException, InvalidKeySpecException, InvalidKeyException { | |
| 129 | + ContentValues initialValues = new ContentValues(); | |
| 130 | + initialValues.put(KEY_ROWID, UUID.randomUUID().toString().replace("-", "")); | |
| 131 | + initialValues.put(KEY_NAME, videoId); | |
| 132 | + String enVideoUrl= DesUtil.enCrypto(videoUrl, encodeText); | |
| 133 | + initialValues.put(KEY_GRADE, enVideoUrl); | |
| 134 | + return mDb.insert(DATABASE_TABLE, null, initialValues); | |
| 135 | + } | |
| 136 | + | |
| 137 | + /** | |
| 138 | + * This method will delete record. | |
| 139 | + * | |
| 140 | + * @param rowId | |
| 141 | + * @return boolean | |
| 142 | + */ | |
| 143 | + public boolean delete(long rowId) { | |
| 144 | + return mDb.delete(DATABASE_TABLE, KEY_ROWID + "=" + rowId, null) > 0; | |
| 145 | + } | |
| 146 | + | |
| 147 | + /** | |
| 148 | + * This method will deleteAll record. | |
| 149 | + * | |
| 150 | + * @return | |
| 151 | + */ | |
| 152 | + public boolean deleteAll() { | |
| 153 | + return mDb.delete(DATABASE_TABLE, " 1 ", null) > 0; | |
| 154 | + } | |
| 155 | + | |
| 156 | + /** | |
| 157 | + * This method will return Cursor holding all the records. | |
| 158 | + * | |
| 159 | + * @return Cursor | |
| 160 | + */ | |
| 161 | + public Cursor fetchAll() { | |
| 162 | + return mDb.query(DATABASE_TABLE, new String[]{KEY_ROWID, KEY_NAME, | |
| 163 | + KEY_GRADE}, null, null, null, null, null); | |
| 164 | + } | |
| 165 | + | |
| 166 | +// /** | |
| 167 | +// * This method will return Cursor holding the specific record. | |
| 168 | +// * | |
| 169 | +// * @param id | |
| 170 | +// * @return Cursor | |
| 171 | +// * @throws SQLException | |
| 172 | +// */ | |
| 173 | +// public Cursor fetch(long id) throws SQLException { | |
| 174 | +// Cursor mCursor = | |
| 175 | +// mDb.query(true, DATABASE_TABLE, new String[]{KEY_ROWID, | |
| 176 | +// KEY_NAME, KEY_GRADE}, KEY_ROWID + "=" + id, null, | |
| 177 | +// null, null, null, null); | |
| 178 | +// if (mCursor != null) { | |
| 179 | +// mCursor.moveToFirst(); | |
| 180 | +// } | |
| 181 | +// return mCursor; | |
| 182 | +// } | |
| 183 | + | |
| 184 | + public String getVideoUri(String videoUid) throws SQLException, IllegalBlockSizeException, NoSuchPaddingException, BadPaddingException, InvalidKeySpecException, InvalidKeyException { | |
| 185 | + String result = ""; | |
| 186 | + Cursor mCursor = | |
| 187 | + mDb.query(true, DATABASE_TABLE, new String[]{KEY_ROWID, | |
| 188 | + KEY_NAME, KEY_GRADE}, KEY_NAME + "=" + videoUid, null, | |
| 189 | + null, null, null, null); | |
| 190 | + if (mCursor != null && mCursor.getCount() > 0) { | |
| 191 | + while (mCursor.moveToNext()) { | |
| 192 | + if (mCursor.getColumnIndex(KEY_GRADE) > 0) { | |
| 193 | + result = mCursor.getString(mCursor.getColumnIndex(KEY_GRADE)); | |
| 194 | + if (!TextUtils.isEmpty(result)) { | |
| 195 | + mCursor.close(); | |
| 196 | + result=DesUtil.deCrypto(result, encodeText); | |
| 197 | + break; | |
| 198 | + } | |
| 199 | + } | |
| 200 | + } | |
| 201 | + } | |
| 202 | + return result; | |
| 203 | + } | |
| 204 | + | |
| 205 | + /** | |
| 206 | + * This method will update record. | |
| 207 | + * | |
| 208 | + * @param id | |
| 209 | + * @param name | |
| 210 | + * @param standard | |
| 211 | + * @return boolean | |
| 212 | + */ | |
| 213 | + public boolean update(int id, String name, String standard) { | |
| 214 | + ContentValues args = new ContentValues(); | |
| 215 | + args.put(KEY_NAME, name); | |
| 216 | + args.put(KEY_GRADE, standard); | |
| 217 | + return mDb.update(DATABASE_TABLE, args, KEY_ROWID + "=" + id, null) > 0; | |
| 218 | + } | |
| 219 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/IVideoDataSource.java
0 → 100644
cloud/video/src/main/java/com/libs/video/video/exoplayer/IVideoPlayer.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer; | |
| 2 | + | |
| 3 | +import android.net.Uri; | |
| 4 | +import android.os.Looper; | |
| 5 | + | |
| 6 | +import androidx.annotation.Nullable; | |
| 7 | + | |
| 8 | +import com.google.android.exoplayer2.C; | |
| 9 | +import com.google.android.exoplayer2.ExoPlaybackException; | |
| 10 | +import com.google.android.exoplayer2.PlaybackParameters; | |
| 11 | +import com.google.android.exoplayer2.Player; | |
| 12 | +import com.google.android.exoplayer2.Renderer; | |
| 13 | +import com.google.android.exoplayer2.Timeline; | |
| 14 | +import com.google.android.exoplayer2.source.MediaSource; | |
| 15 | +import com.google.android.exoplayer2.source.TrackGroupArray; | |
| 16 | +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; | |
| 17 | +import com.google.android.exoplayer2.ui.PlayerView; | |
| 18 | +import com.libs.video.video.base.IDataSource; | |
| 19 | +import com.libs.video.video.base.IMediaPlayer; | |
| 20 | + | |
| 21 | +public interface IVideoPlayer { | |
| 22 | + // /** | |
| 23 | +// * 设置播放资源 | |
| 24 | +// * | |
| 25 | +// * @param data | |
| 26 | +// */ | |
| 27 | + void setDataSource(IVideoDataSource data); | |
| 28 | + | |
| 29 | + void setDataSource(Uri data); | |
| 30 | + | |
| 31 | + void setPlayView(CNLiveVideoView playView); | |
| 32 | + | |
| 33 | + void play() throws IllegalStateException; | |
| 34 | + | |
| 35 | + void stop() throws IllegalStateException; | |
| 36 | + | |
| 37 | + void pause(); | |
| 38 | + | |
| 39 | + void prepare(); | |
| 40 | + | |
| 41 | + void prepare(boolean isPlay); | |
| 42 | + | |
| 43 | + void setForegroundMode(boolean foregroundMode); | |
| 44 | + | |
| 45 | + //默认的 | |
| 46 | + Looper getApplicationLooper(); | |
| 47 | + | |
| 48 | + void removeListener(Player.EventListener listener); | |
| 49 | + | |
| 50 | + @Nullable | |
| 51 | + Player.VideoComponent getVideoComponent(); | |
| 52 | + | |
| 53 | + @Nullable | |
| 54 | + Player.TextComponent getTextComponent(); | |
| 55 | + | |
| 56 | + void addListener(Player.EventListener listener); | |
| 57 | + | |
| 58 | + @Player.RepeatMode | |
| 59 | + int getRepeatMode(); | |
| 60 | + | |
| 61 | + Timeline getCurrentTimeline(); | |
| 62 | + /** | |
| 63 | + * Returns whether the player is currently playing an ad. | |
| 64 | + */ | |
| 65 | + boolean isPlayingAd(); | |
| 66 | + /** | |
| 67 | + * Returns whether a previous window exists, which may depend on the current repeat mode and | |
| 68 | + * whether shuffle mode is enabled. | |
| 69 | + */ | |
| 70 | + boolean hasPrevious(); | |
| 71 | + /** | |
| 72 | + * Returns whether a next window exists, which may depend on the current repeat mode and whether | |
| 73 | + * shuffle mode is enabled. | |
| 74 | + */ | |
| 75 | + boolean hasNext(); | |
| 76 | + /** | |
| 77 | + * If {@link #isPlayingAd()} returns {@code true}, returns the content position that will be | |
| 78 | + * played once all ads in the ad group have finished playing, in milliseconds. If there is no ad | |
| 79 | + * playing, the returned position is the same as that returned by {@link #getCurrentPosition()}. | |
| 80 | + */ | |
| 81 | + long getContentPosition(); | |
| 82 | + | |
| 83 | + /** | |
| 84 | + * If {@link #isPlayingAd()} returns {@code true}, returns an estimate of the content position in | |
| 85 | + * the current content window up to which data is buffered, in milliseconds. If there is no ad | |
| 86 | + * playing, the returned position is the same as that returned by {@link #getBufferedPosition()}. | |
| 87 | + */ | |
| 88 | + long getContentBufferedPosition(); | |
| 89 | + int getPlaybackState(); | |
| 90 | + /** | |
| 91 | + * Returns the currently active playback parameters. | |
| 92 | + * | |
| 93 | + * @see Player.EventListener#onPlaybackParametersChanged(PlaybackParameters) | |
| 94 | + */ | |
| 95 | + PlaybackParameters getPlaybackParameters(); | |
| 96 | + | |
| 97 | + /** | |
| 98 | + * Returns whether shuffling of windows is enabled. | |
| 99 | + */ | |
| 100 | + boolean getShuffleModeEnabled(); | |
| 101 | + /** | |
| 102 | + * Whether playback will proceed when {@link #getPlaybackState()} == {@link #STATE_READY}. | |
| 103 | + * | |
| 104 | + * @return Whether playback will proceed when ready. | |
| 105 | + */ | |
| 106 | + boolean getPlayWhenReady(); | |
| 107 | + /** | |
| 108 | + * Returns whether the current window is seekable, or {@code false} if the {@link Timeline} is | |
| 109 | + * empty. | |
| 110 | + * | |
| 111 | + * @see Timeline.Window#isSeekable | |
| 112 | + */ | |
| 113 | + boolean isCurrentWindowSeekable(); | |
| 114 | + int getPreviousWindowIndex(); | |
| 115 | + /** Returns the playback position in the current content window or ad, in milliseconds. */ | |
| 116 | + long getCurrentPosition(); | |
| 117 | + /** | |
| 118 | + * Returns the index of the next timeline window to be played, which may depend on the current | |
| 119 | + * repeat mode and whether shuffle mode is enabled. Returns {@link C#INDEX_UNSET} if the window | |
| 120 | + * currently being played is the last window. | |
| 121 | + */ | |
| 122 | + int getNextWindowIndex(); | |
| 123 | + /** | |
| 124 | + * 释放资源 | |
| 125 | + */ | |
| 126 | + void release(); | |
| 127 | + | |
| 128 | + /** | |
| 129 | + * 移动资源 | |
| 130 | + * | |
| 131 | + * @param windowIndex 当前位置 | |
| 132 | + * @param positionMs 毫秒 | |
| 133 | + */ | |
| 134 | + void seekTo(int windowIndex, long positionMs); | |
| 135 | + | |
| 136 | + /** | |
| 137 | + * 获取当前进度 | |
| 138 | + * | |
| 139 | + * @return | |
| 140 | + */ | |
| 141 | + int getCurrentWindowIndex(); | |
| 142 | + | |
| 143 | + /** | |
| 144 | + * 获取总时长 | |
| 145 | + * | |
| 146 | + * @return | |
| 147 | + */ | |
| 148 | + long getDuration(); | |
| 149 | + | |
| 150 | + /** | |
| 151 | + * Returns the available track groups. | |
| 152 | + */ | |
| 153 | + TrackGroupArray getCurrentTrackGroups(); | |
| 154 | + | |
| 155 | + /** | |
| 156 | + * Returns the current track selections for each renderer. | |
| 157 | + */ | |
| 158 | + TrackSelectionArray getCurrentTrackSelections(); | |
| 159 | + | |
| 160 | + /** | |
| 161 | + * Returns the track type that the renderer at a given index handles. | |
| 162 | + * | |
| 163 | + * @see Renderer#getTrackType() | |
| 164 | + * @param index The index of the renderer. | |
| 165 | + * @return One of the {@code TRACK_TYPE_*} constants defined in {@link C}. | |
| 166 | + */ | |
| 167 | + int getRendererType(int index); | |
| 168 | + /** | |
| 169 | + * Returns whether the player is playing, i.e. {@link #getContentPosition()} is advancing. | |
| 170 | + * | |
| 171 | + * <p>If {@code false}, then at least one of the following is true: | |
| 172 | + * | |
| 173 | + * <ul> | |
| 174 | + * <li>The {@link #getPlaybackState() playback state} is not {@link #STATE_READY ready}. | |
| 175 | + * <li>There is no {@link #getPlayWhenReady() intention to play}. | |
| 176 | + * <li>Playback is {@link #getPlaybackSuppressionReason() suppressed for other reasons}. | |
| 177 | + * </ul> | |
| 178 | + * | |
| 179 | + * @return Whether the player is playing. | |
| 180 | + */ | |
| 181 | + boolean isPlaying(); | |
| 182 | + /** | |
| 183 | + * Sets the {@link Player.RepeatMode} to be used for playback. | |
| 184 | + * | |
| 185 | + * @param repeatMode The repeat mode. | |
| 186 | + */ | |
| 187 | + void setRepeatMode(@Player.RepeatMode int repeatMode); | |
| 188 | + /** | |
| 189 | + * Sets whether shuffling of windows is enabled. | |
| 190 | + * | |
| 191 | + * @param shuffleModeEnabled Whether shuffling is enabled. | |
| 192 | + */ | |
| 193 | + void setShuffleModeEnabled(boolean shuffleModeEnabled); | |
| 194 | + @Nullable | |
| 195 | + ExoPlaybackException getPlaybackError(); | |
| 196 | + interface OnPreparedListener { | |
| 197 | + void onPrepared(IVideoPlayer videoPlayer); | |
| 198 | + } | |
| 199 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/MyTestControlView.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.util.AttributeSet; | |
| 5 | +import android.view.View; | |
| 6 | + | |
| 7 | +import androidx.annotation.Nullable; | |
| 8 | + | |
| 9 | +import com.cnlive.cloud.video.R; | |
| 10 | + | |
| 11 | +public class MyTestControlView extends CNLiveBaseVideoControlView { | |
| 12 | + private View playButton; | |
| 13 | + | |
| 14 | + public MyTestControlView(Context context) { | |
| 15 | + super(context); | |
| 16 | + } | |
| 17 | + | |
| 18 | + public MyTestControlView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, @Nullable AttributeSet playbackAttrs) { | |
| 19 | + super(context, attrs, defStyleAttr, playbackAttrs); | |
| 20 | + } | |
| 21 | + | |
| 22 | + @Override | |
| 23 | + protected void initPortraitView(View basePortraitView, ComponentListener onClickListener) { | |
| 24 | + playButton = basePortraitView.findViewById(R.id.btn_start); | |
| 25 | + playButton.setOnClickListener(onClickListener); | |
| 26 | + } | |
| 27 | + | |
| 28 | + | |
| 29 | + @Override | |
| 30 | + protected int getControllViewId() { | |
| 31 | + return R.layout.test_view; | |
| 32 | + } | |
| 33 | + | |
| 34 | + @Override | |
| 35 | + protected void onViewClick(View view) { | |
| 36 | + if (view.getId()==R.id.btn_start){ | |
| 37 | +// if (isPlaying()){ | |
| 38 | +// pauseVideo(); | |
| 39 | +// }else { | |
| 40 | +// playVideo(); | |
| 41 | +// } | |
| 42 | + switchScreenDirection(); | |
| 43 | + | |
| 44 | + } | |
| 45 | + } | |
| 46 | +} | ... | ... |
cloud/video/src/main/java/com/libs/video/video/exoplayer/VideoPlayer.java
0 → 100644
| 1 | +package com.libs.video.video.exoplayer; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | +import android.net.Uri; | |
| 5 | +import android.os.Looper; | |
| 6 | +import android.text.TextUtils; | |
| 7 | +import android.util.Log; | |
| 8 | + | |
| 9 | +import androidx.annotation.Nullable; | |
| 10 | + | |
| 11 | +import com.google.android.exoplayer2.ExoPlaybackException; | |
| 12 | +import com.google.android.exoplayer2.PlaybackParameters; | |
| 13 | +import com.google.android.exoplayer2.Player; | |
| 14 | +import com.google.android.exoplayer2.SimpleExoPlayer; | |
| 15 | +import com.google.android.exoplayer2.Timeline; | |
| 16 | +import com.google.android.exoplayer2.ext.rtmp.RtmpDataSourceFactory; | |
| 17 | +import com.google.android.exoplayer2.source.MediaSource; | |
| 18 | +import com.google.android.exoplayer2.source.ProgressiveMediaSource; | |
| 19 | +import com.google.android.exoplayer2.source.TrackGroupArray; | |
| 20 | +import com.google.android.exoplayer2.source.dash.DashMediaSource; | |
| 21 | +import com.google.android.exoplayer2.source.hls.HlsMediaSource; | |
| 22 | +import com.google.android.exoplayer2.source.smoothstreaming.SsMediaSource; | |
| 23 | +import com.google.android.exoplayer2.trackselection.TrackSelectionArray; | |
| 24 | +import com.google.android.exoplayer2.ui.PlayerView; | |
| 25 | +import com.google.android.exoplayer2.upstream.DataSource; | |
| 26 | +import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; | |
| 27 | +import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; | |
| 28 | +import com.google.android.exoplayer2.util.Util; | |
| 29 | +import com.libs.video.data.network.Callback; | |
| 30 | +import com.libs.video.data.network.DataLoader; | |
| 31 | +import com.libs.video.util.AuthUtil; | |
| 32 | +import com.libs.video.util.Config; | |
| 33 | +import com.libs.video.video.base.IActivityDataSource; | |
| 34 | +import com.libs.video.video.base.IChannelDataSource; | |
| 35 | +import com.libs.video.video.base.IDataSource; | |
| 36 | +import com.umeng.analytics.MobclickAgent; | |
| 37 | + | |
| 38 | +import java.security.InvalidKeyException; | |
| 39 | +import java.security.spec.InvalidKeySpecException; | |
| 40 | +import java.util.HashMap; | |
| 41 | +import java.util.Map; | |
| 42 | + | |
| 43 | +import javax.crypto.BadPaddingException; | |
| 44 | +import javax.crypto.IllegalBlockSizeException; | |
| 45 | +import javax.crypto.NoSuchPaddingException; | |
| 46 | + | |
| 47 | +public class VideoPlayer implements IVideoPlayer { | |
| 48 | + private static final String TAG = VideoPlayer.class.getName(); | |
| 49 | + public SimpleExoPlayer player;//exo播放器 | |
| 50 | + // private Player.EventListener eventListener;//播放器监听器 | |
| 51 | + //init统计使用,onPrepare后当前的对比值和之前的对比值进行对比,不一样就进行统计 | |
| 52 | + //对比值对于使用视讯云播放的就是播放id,对于使用金山播放器的就是dataSource | |
| 53 | + private String probePreCompareStr; | |
| 54 | + private String probeCurCompareStr; | |
| 55 | + //记录当前是否是镜像播放 | |
| 56 | + private boolean mMirror = false; | |
| 57 | + private Context mContext; | |
| 58 | + //测量播放过程中的带宽。 如果不需要,可以为null。 | |
| 59 | + private DefaultBandwidthMeter bandwidthMeter; | |
| 60 | + private IVideoDataSource mDataSource, mProbeSource;//播放资源 | |
| 61 | + //资源类型 | |
| 62 | + private MediaSource currentMediaSource;//当前正在播放的资源类型 | |
| 63 | + private HlsMediaSource hlsMediaSource;//HLS媒体资源 | |
| 64 | + private SsMediaSource ssMediaSource;//SmoothStreming媒体资源 | |
| 65 | + private DashMediaSource dashMediaSource;//dash媒体资源 | |
| 66 | + private ProgressiveMediaSource normalSource;//常规媒体文件 | |
| 67 | + //资源工厂类型 | |
| 68 | + private RtmpDataSourceFactory rtmpDataSourceFactory;//rtmp直播 | |
| 69 | + // Create a data source factory. | |
| 70 | + private DataSource.Factory httpDataSourceFactory; | |
| 71 | + private final String VIDEO_TYPE_RTMP = "rtmp://";//是直播 | |
| 72 | + private final String VIDEO_TYPE_MU3U8 = ".m3u8";//是点播 | |
| 73 | + public static final int SCALE_FULL = 0;//铺满屏幕 | |
| 74 | + public static final int SCALE_FIT = 1;//按比例 | |
| 75 | + private DataLoader mDataLoader;//数据加载 | |
| 76 | + private CacheUtil cacheUtil;//缓存工具 | |
| 77 | + private OnPreparedListener onPreparedListener; | |
| 78 | + | |
| 79 | + public VideoPlayer(Context context) { | |
| 80 | + cacheUtil = new CacheUtil(context); | |
| 81 | + //初始化exoplayer | |
| 82 | + //step2. 创建播放器 | |
| 83 | + player = new SimpleExoPlayer.Builder(context).build(); | |
| 84 | + mContext = context; | |
| 85 | + bandwidthMeter = DefaultBandwidthMeter.getSingletonInstance(context); | |
| 86 | + //创建资源工厂 | |
| 87 | + rtmpDataSourceFactory = new RtmpDataSourceFactory(); | |
| 88 | + httpDataSourceFactory = new DefaultHttpDataSourceFactory(Util.getUserAgent(context, "app-name")); | |
| 89 | + //数据加载 | |
| 90 | + mDataLoader = new DataLoader(); | |
| 91 | + //添加行为监听器 | |
| 92 | + player.addListener(new Player.EventListener() { | |
| 93 | + @Override | |
| 94 | + public void onTimelineChanged(Timeline timeline, int reason) { | |
| 95 | + | |
| 96 | + } | |
| 97 | + | |
| 98 | + @Override | |
| 99 | + public void onTimelineChanged(Timeline timeline, @Nullable Object manifest, int reason) { | |
| 100 | + | |
| 101 | + } | |
| 102 | + | |
| 103 | + @Override | |
| 104 | + public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { | |
| 105 | + | |
| 106 | + } | |
| 107 | + | |
| 108 | + @Override | |
| 109 | + public void onLoadingChanged(boolean isLoading) { | |
| 110 | + | |
| 111 | + } | |
| 112 | + | |
| 113 | + @Override | |
| 114 | + public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { | |
| 115 | +//状态改变监听 | |
| 116 | + switch (playbackState) { | |
| 117 | + //这是初始状态,播放机停止时的状态,以及播放失败时的状态。 | |
| 118 | + case Player.STATE_IDLE: | |
| 119 | + Log.d(TAG, "onPlayerStateChanged: STATE_IDLE"); | |
| 120 | + break; | |
| 121 | + // 播放器无法立即从其当前位置进行播放。这主要是因为需要加载更多的数据。 | |
| 122 | + case Player.STATE_BUFFERING: | |
| 123 | + Log.d(TAG, "onPlayerStateChanged: STATE_BUFFERING"); | |
| 124 | + break; | |
| 125 | + //播放器可以立即从其当前位置进行播放。 | |
| 126 | + case Player.STATE_READY: | |
| 127 | + Log.d(TAG, "onPlayerStateChanged: STATE_READY"); | |
| 128 | + break; | |
| 129 | + //播放器完成了所有媒体的播放。 | |
| 130 | + case Player.STATE_ENDED: | |
| 131 | + Log.d(TAG, "onPlayerStateChanged: STATE_ENDED"); | |
| 132 | + break; | |
| 133 | + } | |
| 134 | + } | |
| 135 | + | |
| 136 | + @Override | |
| 137 | + public void onPlaybackSuppressionReasonChanged(int playbackSuppressionReason) { | |
| 138 | + | |
| 139 | + } | |
| 140 | + | |
| 141 | + @Override | |
| 142 | + public void onIsPlayingChanged(boolean isPlaying) { | |
| 143 | + | |
| 144 | + } | |
| 145 | + | |
| 146 | + @Override | |
| 147 | + public void onRepeatModeChanged(int repeatMode) { | |
| 148 | + | |
| 149 | + } | |
| 150 | + | |
| 151 | + @Override | |
| 152 | + public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { | |
| 153 | + | |
| 154 | + } | |
| 155 | + | |
| 156 | + @Override | |
| 157 | + public void onPlayerError(ExoPlaybackException error) { | |
| 158 | + | |
| 159 | + } | |
| 160 | + | |
| 161 | + @Override | |
| 162 | + public void onPositionDiscontinuity(int reason) { | |
| 163 | + | |
| 164 | + } | |
| 165 | + | |
| 166 | + @Override | |
| 167 | + public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) { | |
| 168 | + | |
| 169 | + } | |
| 170 | + | |
| 171 | + @Override | |
| 172 | + public void onSeekProcessed() { | |
| 173 | + | |
| 174 | + } | |
| 175 | + }); | |
| 176 | + } | |
| 177 | + | |
| 178 | + /** | |
| 179 | + * 获取当前版本 | |
| 180 | + * | |
| 181 | + * @return | |
| 182 | + */ | |
| 183 | + public static String getVersion() { | |
| 184 | + return Config.SDK_PLAYER_VERSION; | |
| 185 | + } | |
| 186 | + | |
| 187 | + /** | |
| 188 | + * 获取版本号 | |
| 189 | + * | |
| 190 | + * @return | |
| 191 | + */ | |
| 192 | + public static String getVersionCode() { | |
| 193 | + StringBuilder buffer = new StringBuilder(); | |
| 194 | + String[] strArray = getVersion().split("\\."); | |
| 195 | + | |
| 196 | + for (String aStrArray : strArray) { | |
| 197 | + buffer.append(aStrArray); | |
| 198 | + } | |
| 199 | + | |
| 200 | + return buffer.toString(); | |
| 201 | + } | |
| 202 | + | |
| 203 | + @Override | |
| 204 | + public void setDataSource(IVideoDataSource data) { | |
| 205 | + mDataSource = null; | |
| 206 | + mDataSource = data; | |
| 207 | + Map<String, String> probeData = new HashMap<>(); | |
| 208 | + probeData.put("mediaId", mDataSource == null ? "" : mDataSource.getDataSourceId()); | |
| 209 | +// probeData.put("mediaRate", mDataSource == null ? "" : mDataSource.getDataSourceRate()); | |
| 210 | + probeData.put("mediaType", mDataSource == null ? "" : mDataSource.getDataSourceType()); | |
| 211 | + probeData.put("mediaUrl", ""); | |
| 212 | + MobclickAgent.onEvent(mContext, "media_init", probeData); | |
| 213 | + //判断该条数据是否在缓存中 | |
| 214 | + if (null != mDataSource && !TextUtils.isEmpty(mDataSource.getDataSourceId())) { | |
| 215 | + //获取缓存的url | |
| 216 | + String cachVideoUrl = null; | |
| 217 | + try { | |
| 218 | + cachVideoUrl = cacheUtil.getVideoUri(mDataSource.getDataSourceId()); | |
| 219 | + } catch (IllegalBlockSizeException e) { | |
| 220 | + e.printStackTrace(); | |
| 221 | + } catch (NoSuchPaddingException e) { | |
| 222 | + e.printStackTrace(); | |
| 223 | + } catch (BadPaddingException e) { | |
| 224 | + e.printStackTrace(); | |
| 225 | + } catch (InvalidKeySpecException e) { | |
| 226 | + e.printStackTrace(); | |
| 227 | + } catch (InvalidKeyException e) { | |
| 228 | + e.printStackTrace(); | |
| 229 | + } | |
| 230 | + //判断是否有缓存数据 | |
| 231 | + if (!TextUtils.isEmpty(cachVideoUrl)) { | |
| 232 | + currentMediaSource = getPlayerMediaSource(cachVideoUrl); | |
| 233 | + } | |
| 234 | + } | |
| 235 | + if (!AuthUtil.isValid()) { | |
| 236 | + AuthUtil.auth(new Callback() { | |
| 237 | + @Override | |
| 238 | + public void onState(int what, String extra, Object obj) { | |
| 239 | + if (what > 0) { | |
| 240 | +// if (mCNOnInfoListener != null) | |
| 241 | +// mCNOnInfoListener.onInfo(MediaPlayer.this, what, 0); | |
| 242 | +// loadDataSource(data, curSource.equals(preSource)); | |
| 243 | + } else if (what < 0) { | |
| 244 | +// if (mCNOnErrorListener != null) | |
| 245 | +// mCNOnErrorListener.onError(MediaPlayer.this, what, 0); | |
| 246 | + } | |
| 247 | + } | |
| 248 | + | |
| 249 | + @Override | |
| 250 | + public void onError(String errCode, Exception e) { | |
| 251 | + | |
| 252 | + } | |
| 253 | + }); | |
| 254 | + } else { | |
| 255 | +// loadDataSource(data, curSource.equals(preSource)); | |
| 256 | + } | |
| 257 | + } | |
| 258 | + | |
| 259 | + @Override | |
| 260 | + public void setDataSource(Uri data) { | |
| 261 | + currentMediaSource = getPlayerMediaSource(data.toString()); | |
| 262 | + } | |
| 263 | + | |
| 264 | + @Override | |
| 265 | + public void setPlayView(CNLiveVideoView playView) { | |
| 266 | + } | |
| 267 | + | |
| 268 | + private void loadDataSource(final IDataSource data, boolean retry) { | |
| 269 | + String activityId = data instanceof IActivityDataSource ? ((IActivityDataSource) data).getDataActivityId() : ""; | |
| 270 | + String tag = data instanceof IChannelDataSource ? ((IChannelDataSource) data).getDataTag() : ""; | |
| 271 | + String channelName = data instanceof IChannelDataSource ? ((IChannelDataSource) data).getChannelName() : ""; | |
| 272 | + mDataLoader.loadDataSource(data.getDataSourceId(), data.getDataSourceRate(), data.getDataSourceType(), activityId, tag, channelName, retry, new Callback() { | |
| 273 | + @Override | |
| 274 | + public void onState(int what, String extra, Object obj) { | |
| 275 | + if (what > 0) { | |
| 276 | + String playerUrl; | |
| 277 | + if (data.getDataSourceType() == Config.TYPE_VOD) { | |
| 278 | + //缓存数据 | |
| 279 | + try { | |
| 280 | + //暂时只有点播才能缓存 | |
| 281 | + cacheUtil.add(data.getDataSourceId(), extra); | |
| 282 | + } catch (IllegalBlockSizeException e) { | |
| 283 | + e.printStackTrace(); | |
| 284 | + } catch (NoSuchPaddingException e) { | |
| 285 | + e.printStackTrace(); | |
| 286 | + } catch (BadPaddingException e) { | |
| 287 | + e.printStackTrace(); | |
| 288 | + } catch (InvalidKeySpecException e) { | |
| 289 | + e.printStackTrace(); | |
| 290 | + } catch (InvalidKeyException e) { | |
| 291 | + e.printStackTrace(); | |
| 292 | + } | |
| 293 | + } | |
| 294 | + playerUrl = extra; | |
| 295 | + currentMediaSource = getPlayerMediaSource(playerUrl); | |
| 296 | +// if (mCNOnInfoListener != null) | |
| 297 | +// mCNOnInfoListener.onInfo(MediaPlayer.this, MEDIA_INFO_GET_PLAYURL_SUCCESS, 0); | |
| 298 | + } else if (what < 0) { | |
| 299 | +// if (mCNOnErrorListener != null) | |
| 300 | +// mCNOnErrorListener.onError(MediaPlayer.this, what, 0); | |
| 301 | + } | |
| 302 | +// | |
| 303 | + } | |
| 304 | + | |
| 305 | + @Override | |
| 306 | + public void onError(String errCode, Exception e) { | |
| 307 | + | |
| 308 | + } | |
| 309 | + }); | |
| 310 | + } | |
| 311 | + | |
| 312 | + @Override | |
| 313 | + public void play() throws IllegalStateException { | |
| 314 | + if (null != player) { | |
| 315 | + //准备好就开始播放 | |
| 316 | + player.setPlayWhenReady(true); | |
| 317 | + } | |
| 318 | + } | |
| 319 | + | |
| 320 | + @Override | |
| 321 | + public void stop() throws IllegalStateException { | |
| 322 | + if (null != player) { | |
| 323 | + player.stop(); | |
| 324 | + } | |
| 325 | + } | |
| 326 | + | |
| 327 | + @Override | |
| 328 | + public void pause() { | |
| 329 | + if (null != player) { | |
| 330 | + player.setPlayWhenReady(false); | |
| 331 | + } | |
| 332 | + } | |
| 333 | + | |
| 334 | + /** | |
| 335 | + * 加载视频 | |
| 336 | + */ | |
| 337 | + @Override | |
| 338 | + public void prepare() { | |
| 339 | + if (null != currentMediaSource && null != player) { | |
| 340 | + player.prepare(currentMediaSource); | |
| 341 | + } | |
| 342 | + } | |
| 343 | + | |
| 344 | + @Override | |
| 345 | + public void prepare(boolean isPlay) { | |
| 346 | + if (null != currentMediaSource && null != player) { | |
| 347 | + player.prepare(currentMediaSource); | |
| 348 | + player.setPlayWhenReady(isPlay); | |
| 349 | + } | |
| 350 | + } | |
| 351 | + | |
| 352 | + private MediaSource getPlayerMediaSource(String playerUrl) { | |
| 353 | + if (TextUtils.isEmpty(playerUrl)) { | |
| 354 | + return null; | |
| 355 | + } | |
| 356 | + | |
| 357 | + Uri uri = Uri.parse(playerUrl); | |
| 358 | + int type = Util.inferContentType(!TextUtils.isEmpty(playerUrl) ? | |
| 359 | + "." + playerUrl | |
| 360 | + : uri.getLastPathSegment()); | |
| 361 | +// case C.TYPE_DASH: | |
| 362 | + if (playerUrl.indexOf(VIDEO_TYPE_MU3U8) != -1) { | |
| 363 | + //点播 | |
| 364 | + hlsMediaSource = new HlsMediaSource.Factory(httpDataSourceFactory).createMediaSource(uri); | |
| 365 | + return hlsMediaSource; | |
| 366 | + } else if (playerUrl.indexOf(VIDEO_TYPE_RTMP) != -1) { | |
| 367 | + normalSource = new ProgressiveMediaSource.Factory(rtmpDataSourceFactory) | |
| 368 | + .createMediaSource(uri); | |
| 369 | + return normalSource; | |
| 370 | + } else { | |
| 371 | + normalSource = new ProgressiveMediaSource.Factory(httpDataSourceFactory) | |
| 372 | + .createMediaSource(uri); | |
| 373 | + return normalSource; | |
| 374 | + } | |
| 375 | + } | |
| 376 | + | |
| 377 | + | |
| 378 | + @Override | |
| 379 | + public void setForegroundMode(boolean foregroundMode) { | |
| 380 | + if (null != player) { | |
| 381 | + player.setForegroundMode(foregroundMode); | |
| 382 | + } | |
| 383 | + } | |
| 384 | + | |
| 385 | + @Override | |
| 386 | + public Looper getApplicationLooper() { | |
| 387 | + return player.getApplicationLooper(); | |
| 388 | + } | |
| 389 | + | |
| 390 | + @Override | |
| 391 | + public void removeListener(Player.EventListener listener) { | |
| 392 | + player.removeListener(listener); | |
| 393 | + } | |
| 394 | + | |
| 395 | + @Nullable | |
| 396 | + @Override | |
| 397 | + public Player.VideoComponent getVideoComponent() { | |
| 398 | + return player.getVideoComponent(); | |
| 399 | + } | |
| 400 | + | |
| 401 | + @Nullable | |
| 402 | + @Override | |
| 403 | + public Player.TextComponent getTextComponent() { | |
| 404 | + return player.getTextComponent(); | |
| 405 | + } | |
| 406 | + | |
| 407 | + @Override | |
| 408 | + public void addListener(Player.EventListener listener) { | |
| 409 | + player.addListener(listener); | |
| 410 | + } | |
| 411 | + | |
| 412 | + @Override | |
| 413 | + public int getRepeatMode() { | |
| 414 | + return player.getRepeatMode(); | |
| 415 | + } | |
| 416 | + | |
| 417 | + @Override | |
| 418 | + public Timeline getCurrentTimeline() { | |
| 419 | + return player.getCurrentTimeline(); | |
| 420 | + } | |
| 421 | + | |
| 422 | + @Override | |
| 423 | + public boolean isPlayingAd() { | |
| 424 | + return player.isPlayingAd(); | |
| 425 | + } | |
| 426 | + | |
| 427 | + @Override | |
| 428 | + public boolean hasPrevious() { | |
| 429 | + return player.hasPrevious(); | |
| 430 | + } | |
| 431 | + | |
| 432 | + @Override | |
| 433 | + public boolean hasNext() { | |
| 434 | + return player.hasNext(); | |
| 435 | + } | |
| 436 | + | |
| 437 | + @Override | |
| 438 | + public long getContentPosition() { | |
| 439 | + return player.getContentPosition(); | |
| 440 | + } | |
| 441 | + | |
| 442 | + @Override | |
| 443 | + public long getContentBufferedPosition() { | |
| 444 | + return player.getContentBufferedPosition(); | |
| 445 | + } | |
| 446 | + | |
| 447 | + @Override | |
| 448 | + public int getPlaybackState() { | |
| 449 | + return player.getPlaybackState(); | |
| 450 | + } | |
| 451 | + | |
| 452 | + @Override | |
| 453 | + public PlaybackParameters getPlaybackParameters() { | |
| 454 | + return player.getPlaybackParameters(); | |
| 455 | + } | |
| 456 | + | |
| 457 | + @Override | |
| 458 | + public boolean getShuffleModeEnabled() { | |
| 459 | + return player.getShuffleModeEnabled(); | |
| 460 | + } | |
| 461 | + | |
| 462 | + @Override | |
| 463 | + public boolean getPlayWhenReady() { | |
| 464 | + return player.getPlayWhenReady(); | |
| 465 | + } | |
| 466 | + | |
| 467 | + @Override | |
| 468 | + public boolean isCurrentWindowSeekable() { | |
| 469 | + return player.isCurrentWindowSeekable(); | |
| 470 | + } | |
| 471 | + | |
| 472 | + @Override | |
| 473 | + public int getPreviousWindowIndex() { | |
| 474 | + return player.getPreviousWindowIndex(); | |
| 475 | + } | |
| 476 | + | |
| 477 | + @Override | |
| 478 | + public long getCurrentPosition() { | |
| 479 | + return player.getCurrentPosition(); | |
| 480 | + } | |
| 481 | + | |
| 482 | + @Override | |
| 483 | + public int getNextWindowIndex() { | |
| 484 | + return player.getNextWindowIndex(); | |
| 485 | + } | |
| 486 | + | |
| 487 | + @Override | |
| 488 | + public void release() { | |
| 489 | + //释放资源 | |
| 490 | + if (null != player) { | |
| 491 | + player.release(); | |
| 492 | + } | |
| 493 | + } | |
| 494 | + | |
| 495 | + /** | |
| 496 | + * 移动位置 | |
| 497 | + * | |
| 498 | + * @param windowIndex 当前位置 | |
| 499 | + * @param positionMs 毫秒 | |
| 500 | + */ | |
| 501 | + @Override | |
| 502 | + public void seekTo(int windowIndex, long positionMs) { | |
| 503 | + if (null != player) { | |
| 504 | + player.seekTo(windowIndex, positionMs); | |
| 505 | + } | |
| 506 | + } | |
| 507 | + | |
| 508 | + /** | |
| 509 | + * 获取当前位置 | |
| 510 | + * | |
| 511 | + * @return | |
| 512 | + */ | |
| 513 | + @Override | |
| 514 | + public int getCurrentWindowIndex() { | |
| 515 | + if (null != player) { | |
| 516 | + player.getCurrentWindowIndex(); | |
| 517 | + } | |
| 518 | + return 0; | |
| 519 | + } | |
| 520 | + | |
| 521 | + @Override | |
| 522 | + public long getDuration() { | |
| 523 | + if (null != player) { | |
| 524 | + player.getDuration(); | |
| 525 | + } | |
| 526 | + return 0; | |
| 527 | + } | |
| 528 | + | |
| 529 | + @Override | |
| 530 | + public TrackGroupArray getCurrentTrackGroups() { | |
| 531 | + return player.getCurrentTrackGroups(); | |
| 532 | + } | |
| 533 | + | |
| 534 | + @Override | |
| 535 | + public TrackSelectionArray getCurrentTrackSelections() { | |
| 536 | + return player.getCurrentTrackSelections(); | |
| 537 | + } | |
| 538 | + | |
| 539 | + @Override | |
| 540 | + public int getRendererType(int index) { | |
| 541 | + return player.getRendererType(index); | |
| 542 | + } | |
| 543 | + | |
| 544 | + @Override | |
| 545 | + public boolean isPlaying() { | |
| 546 | + return player.isPlaying(); | |
| 547 | + } | |
| 548 | + | |
| 549 | + @Override | |
| 550 | + public void setRepeatMode(int repeatMode) { | |
| 551 | + player.setRepeatMode(repeatMode); | |
| 552 | + } | |
| 553 | + | |
| 554 | + @Override | |
| 555 | + public void setShuffleModeEnabled(boolean shuffleModeEnabled) { | |
| 556 | + player.setShuffleModeEnabled(shuffleModeEnabled); | |
| 557 | + } | |
| 558 | + | |
| 559 | + @Nullable | |
| 560 | + @Override | |
| 561 | + public ExoPlaybackException getPlaybackError() { | |
| 562 | + return player.getPlaybackError(); | |
| 563 | + } | |
| 564 | + | |
| 565 | +} | ... | ... |
cloud/video/src/main/res/drawable/shape_video_top_bg.xml
0 → 100644
| 1 | +<shape xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 2 | + <!-- | |
| 3 | + android:startColor="#aa000000" 渐变起始色值 | |
| 4 | + android:centerColor="" 渐变中间色值 | |
| 5 | + android:endColor="#ffffffff" 渐变结束颜色 | |
| 6 | + android:angle="45" 渐变的方向 默认为0 从做向右 ,90时从下向上 必须为45的整数倍 | |
| 7 | + android:type="radial" 渐变类型 有三种 线性linear 放射渐变radial 扫描线性渐变sweep | |
| 8 | + android:centerX="0.5" 渐变中心相对X坐标只有渐变类型为放射渐变时有效 | |
| 9 | + android:centerY="0.5" 渐变中心相对Y坐标只有渐变类型为放射渐变时有效 | |
| 10 | + android:gradientRadius="100" 渐变半径 非线性放射有效 | |
| 11 | + --> | |
| 12 | + <gradient | |
| 13 | + android:angle="90" | |
| 14 | + android:endColor="#1A000000" | |
| 15 | + android:startColor="#80000000" | |
| 16 | + android:type="linear" /> | |
| 17 | + | |
| 18 | +</shape> | |
| 0 | 19 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/layout/layout_base_viedo_view.xml
0 → 100644
| 1 | +<merge xmlns:android="http://schemas.android.com/apk/res/android"> | |
| 2 | + | |
| 3 | + <com.google.android.exoplayer2.ui.AspectRatioFrameLayout | |
| 4 | + android:id="@id/cnlive_content_frame" | |
| 5 | + android:layout_width="match_parent" | |
| 6 | + android:layout_height="match_parent" | |
| 7 | + android:layout_gravity="center"> | |
| 8 | + | |
| 9 | + <!-- Video surface will be inserted as the first child of the content frame. --> | |
| 10 | + | |
| 11 | + <View | |
| 12 | + android:id="@id/cnlive_shutter" | |
| 13 | + android:layout_width="match_parent" | |
| 14 | + android:layout_height="match_parent" | |
| 15 | + android:background="#80000000" /> | |
| 16 | + | |
| 17 | + <ImageView | |
| 18 | + android:id="@id/cnlive_artwork" | |
| 19 | + android:layout_width="match_parent" | |
| 20 | + android:layout_height="match_parent" | |
| 21 | + android:scaleType="fitXY" /> | |
| 22 | + | |
| 23 | + <com.google.android.exoplayer2.ui.SubtitleView | |
| 24 | + android:id="@id/cnlive_subtitles" | |
| 25 | + android:layout_width="match_parent" | |
| 26 | + android:layout_height="match_parent" /> | |
| 27 | + | |
| 28 | + <ProgressBar | |
| 29 | + android:id="@id/cnlive_buffering" | |
| 30 | + android:layout_width="wrap_content" | |
| 31 | + android:layout_height="wrap_content" | |
| 32 | + android:layout_gravity="center" | |
| 33 | + android:indeterminate="true" /> | |
| 34 | + | |
| 35 | + <TextView | |
| 36 | + android:id="@id/cnlive_error_message" | |
| 37 | + android:layout_width="match_parent" | |
| 38 | + android:layout_height="match_parent" | |
| 39 | + android:layout_gravity="center" | |
| 40 | + android:background="@color/exo_error_message_background_color" | |
| 41 | + android:gravity="center" | |
| 42 | + android:padding="16dp" /> | |
| 43 | + | |
| 44 | + </com.google.android.exoplayer2.ui.AspectRatioFrameLayout> | |
| 45 | + | |
| 46 | + <FrameLayout | |
| 47 | + android:id="@id/cnlive_ad_overlay" | |
| 48 | + android:layout_width="match_parent" | |
| 49 | + android:layout_height="match_parent" /> | |
| 50 | + | |
| 51 | + <FrameLayout | |
| 52 | + android:id="@id/cnlive_overlay" | |
| 53 | + android:layout_width="match_parent" | |
| 54 | + android:layout_height="match_parent" /> | |
| 55 | + | |
| 56 | + <View | |
| 57 | + android:id="@id/cnlive_controller_placeholder" | |
| 58 | + android:layout_width="match_parent" | |
| 59 | + android:layout_height="match_parent" /> | |
| 60 | + | |
| 61 | +</merge> | |
| 0 | 62 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/layout/layout_cnlive_control_view.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2016 The Android Open Source Project | |
| 2 | + | |
| 3 | + Licensed under the Apache License, Version 2.0 (the "License"); | |
| 4 | + you may not use this file except in compliance with the License. | |
| 5 | + You may obtain a copy of the License at | |
| 6 | + | |
| 7 | + http://www.apache.org/licenses/LICENSE-2.0 | |
| 8 | + | |
| 9 | + Unless required by applicable law or agreed to in writing, software | |
| 10 | + distributed under the License is distributed on an "AS IS" BASIS, | |
| 11 | + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 12 | + See the License for the specific language governing permissions and | |
| 13 | + limitations under the License. | |
| 14 | +--> | |
| 15 | +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 16 | + xmlns:tools="http://schemas.android.com/tools" | |
| 17 | + android:layout_width="match_parent" | |
| 18 | + android:layout_height="match_parent" | |
| 19 | + android:orientation="vertical" | |
| 20 | + tools:targetApi="28"> | |
| 21 | + | |
| 22 | + | |
| 23 | + <!-- 控制视图--> | |
| 24 | + <LinearLayout | |
| 25 | + android:layout_width="match_parent" | |
| 26 | + android:layout_height="wrap_content" | |
| 27 | + android:layout_gravity="bottom" | |
| 28 | + android:background="#80000000" | |
| 29 | + android:orientation="vertical"> | |
| 30 | + <!-- 进度条--> | |
| 31 | + <LinearLayout | |
| 32 | + android:layout_width="match_parent" | |
| 33 | + android:layout_height="wrap_content" | |
| 34 | + android:layout_marginTop="4dp" | |
| 35 | + android:gravity="center_vertical" | |
| 36 | + android:orientation="horizontal"> | |
| 37 | + | |
| 38 | + <TextView | |
| 39 | + android:id="@id/cnlive_position" | |
| 40 | + android:layout_width="wrap_content" | |
| 41 | + android:layout_height="wrap_content" | |
| 42 | + android:includeFontPadding="false" | |
| 43 | + android:paddingLeft="4dp" | |
| 44 | + android:paddingRight="4dp" | |
| 45 | + android:textColor="#FFBEBEBE" | |
| 46 | + android:textSize="14sp" | |
| 47 | + android:textStyle="bold" /> | |
| 48 | + | |
| 49 | + <View | |
| 50 | + android:id="@id/cnlive_progress_placeholder" | |
| 51 | + android:layout_width="0dp" | |
| 52 | + android:layout_height="26dp" | |
| 53 | + android:layout_weight="1" /> | |
| 54 | + | |
| 55 | + <TextView | |
| 56 | + android:id="@id/cnlive_duration" | |
| 57 | + android:layout_width="wrap_content" | |
| 58 | + android:layout_height="wrap_content" | |
| 59 | + android:includeFontPadding="false" | |
| 60 | + android:paddingLeft="4dp" | |
| 61 | + android:paddingRight="4dp" | |
| 62 | + android:textColor="#FFBEBEBE" | |
| 63 | + android:textSize="14sp" | |
| 64 | + android:textStyle="bold" /> | |
| 65 | + | |
| 66 | + </LinearLayout> | |
| 67 | + <!----> | |
| 68 | + <LinearLayout | |
| 69 | + android:layout_width="match_parent" | |
| 70 | + android:layout_height="wrap_content" | |
| 71 | + android:layout_gravity="bottom" | |
| 72 | + android:gravity="center" | |
| 73 | + android:layoutDirection="ltr" | |
| 74 | + android:orientation="horizontal" | |
| 75 | + android:paddingTop="4dp"> | |
| 76 | + | |
| 77 | + <ImageButton | |
| 78 | + android:id="@id/cnlive_prev" | |
| 79 | + style="@style/ExoMediaButton.Previous" /> | |
| 80 | + | |
| 81 | + <ImageButton | |
| 82 | + android:id="@id/cnlive_rew" | |
| 83 | + style="@style/ExoMediaButton.Rewind" /> | |
| 84 | + | |
| 85 | + <ImageButton | |
| 86 | + android:id="@id/cnlive_shuffle" | |
| 87 | + style="@style/ExoMediaButton" /> | |
| 88 | + | |
| 89 | + <ImageButton | |
| 90 | + android:id="@id/cnlive_repeat_toggle" | |
| 91 | + android:layout_width="30dp" | |
| 92 | + android:layout_height="30dp" | |
| 93 | + android:src="@drawable/wufalianjie" /> | |
| 94 | + | |
| 95 | + <ImageButton | |
| 96 | + android:id="@id/cnlive_play" | |
| 97 | + style="@style/ExoMediaButton.Play" /> | |
| 98 | + | |
| 99 | + <ImageButton | |
| 100 | + android:id="@id/cnlive_pause" | |
| 101 | + style="@style/ExoMediaButton.Pause" /> | |
| 102 | + | |
| 103 | + <ImageButton | |
| 104 | + android:id="@id/cnlive_switch_screen" | |
| 105 | + style="@style/ExoMediaButton.FastForward" /> | |
| 106 | + | |
| 107 | + <ImageButton | |
| 108 | + android:id="@id/cnlive_next" | |
| 109 | + style="@style/ExoMediaButton.Next" /> | |
| 110 | + | |
| 111 | + <ImageButton | |
| 112 | + android:id="@id/cnlive_vr" | |
| 113 | + style="@style/ExoMediaButton.VR" /> | |
| 114 | + | |
| 115 | + </LinearLayout> | |
| 116 | + | |
| 117 | + | |
| 118 | + </LinearLayout> | |
| 119 | + <!--顶部视图--> | |
| 120 | + <RelativeLayout | |
| 121 | + android:layout_width="match_parent" | |
| 122 | + android:layout_height="40dp" | |
| 123 | + android:background="#80000000"> | |
| 124 | + | |
| 125 | + <TextView | |
| 126 | + android:id="@id/cnlive_top_title" | |
| 127 | + android:layout_width="wrap_content" | |
| 128 | + android:layout_height="wrap_content" | |
| 129 | + android:layout_centerVertical="true" | |
| 130 | + android:layout_marginLeft="10dp" | |
| 131 | + android:textColor="@color/white" | |
| 132 | + android:textSize="15sp" | |
| 133 | + android:textStyle="bold" /> | |
| 134 | + | |
| 135 | + <ImageButton | |
| 136 | + android:id="@id/cnlive_play" | |
| 137 | + style="@style/ExoMediaButton.VR" | |
| 138 | + android:layout_alignParentRight="true" | |
| 139 | + android:layout_centerVertical="true" /> | |
| 140 | + </RelativeLayout> | |
| 141 | + | |
| 142 | +</FrameLayout> | ... | ... |
cloud/video/src/main/res/layout/layout_test.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="match_parent" | |
| 5 | + android:orientation="vertical"> | |
| 6 | + | |
| 7 | + <SurfaceView | |
| 8 | + android:id="@+id/sv" | |
| 9 | + android:layout_width="match_parent" | |
| 10 | + android:layout_height="match_parent"></SurfaceView> | |
| 11 | +</LinearLayout> | |
| 0 | 12 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/layout/test_view.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | |
| 3 | + android:layout_width="match_parent" | |
| 4 | + android:layout_height="wrap_content" | |
| 5 | + android:layout_gravity="bottom" | |
| 6 | + android:background="@color/color_282828" | |
| 7 | + android:orientation="vertical"> | |
| 8 | + | |
| 9 | + <Button | |
| 10 | + android:id="@+id/btn_start" | |
| 11 | + android:layout_width="50dp" | |
| 12 | + android:layout_height="50dp" /> | |
| 13 | +</LinearLayout> | |
| 0 | 14 | \ No newline at end of file | ... | ... |
cloud/video/src/main/res/values/values.xml
0 → 100644
| 1 | +<?xml version="1.0" encoding="utf-8"?> | |
| 2 | +<resources> | |
| 3 | + <item name="cnlive_ad_overlay" type="id" /> | |
| 4 | + <item name="cnlive_artwork" type="id" /> | |
| 5 | + <item name="cnlive_buffering" type="id" /> | |
| 6 | + <item name="cnlive_content_frame" type="id" /> | |
| 7 | + <item name="cnlive_controller" type="id" /> | |
| 8 | + <item name="cnlive_controller_placeholder" type="id" /> | |
| 9 | + <item name="cnlive_duration" type="id" /> | |
| 10 | + <item name="cnlive_error_message" type="id" /> | |
| 11 | + <item name="cnlive_ffwd" type="id" /> | |
| 12 | + <item name="cnlive_next" type="id" /> | |
| 13 | + <item name="cnlive_overlay" type="id" /> | |
| 14 | + <item name="cnlive_pause" type="id" /> | |
| 15 | + <item name="cnlive_play" type="id" /> | |
| 16 | + <item name="cnlive_position" type="id" /> | |
| 17 | + <item name="cnlive_prev" type="id" /> | |
| 18 | + <item name="cnlive_progress" type="id" /> | |
| 19 | + <item name="cnlive_progress_placeholder" type="id" /> | |
| 20 | + <item name="cnlive_repeat_toggle" type="id" /> | |
| 21 | + <item name="cnlive_rew" type="id" /> | |
| 22 | + <item name="cnlive_shuffle" type="id" /> | |
| 23 | + <item name="cnlive_shutter" type="id" /> | |
| 24 | + <item name="cnlive_subtitles" type="id" /> | |
| 25 | + <item name="cnlive_vr" type="id" /> | |
| 26 | + <!-- 横竖屏切换--> | |
| 27 | + <item name="cnlive_switch_screen" type="id" /> | |
| 28 | + | |
| 29 | + | |
| 30 | + <!--杨帅自定义视图--> | |
| 31 | + <!--顶部视图根布局--> | |
| 32 | + <item name="cnlive_top_root_view" type="id" /> | |
| 33 | + <!--顶部视图标题--> | |
| 34 | + <item name="cnlive_top_title" type="id" /> | |
| 35 | + | |
| 36 | + <!--右视图--> | |
| 37 | + | |
| 38 | +</resources> | |
| 0 | 39 | \ No newline at end of file | ... | ... |
core/base/src/main/AndroidManifest.xml
| ... | ... | @@ -28,6 +28,9 @@ |
| 28 | 28 | <!-- android:name="android.support.FILE_PROVIDER_PATHS" --> |
| 29 | 29 | <!-- android:resource="@xml/file_paths" /> --> |
| 30 | 30 | <!-- </provider> --> |
| 31 | - | |
| 31 | + <activity | |
| 32 | + android:name="com.cnlive.core.libs.base.frame.activity.BaseActivity" | |
| 33 | + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" | |
| 34 | + android:screenOrientation="portrait" /> | |
| 32 | 35 | </application> |
| 33 | 36 | </manifest> | ... | ... |
core/base/src/main/java/com/cnlive/core/libs/base/application/MyAppGlideModule.java
| 1 | 1 | package com.cnlive.core.libs.base.application; |
| 2 | 2 | |
| 3 | +import android.content.Context; | |
| 4 | + | |
| 5 | +import androidx.annotation.NonNull; | |
| 6 | + | |
| 7 | +import com.bumptech.glide.GlideBuilder; | |
| 3 | 8 | import com.bumptech.glide.annotation.GlideModule; |
| 4 | 9 | import com.bumptech.glide.module.AppGlideModule; |
| 5 | 10 | |
| ... | ... | @@ -9,5 +14,8 @@ import com.bumptech.glide.module.AppGlideModule; |
| 9 | 14 | |
| 10 | 15 | @GlideModule |
| 11 | 16 | public final class MyAppGlideModule extends AppGlideModule { |
| 12 | - | |
| 17 | + @Override | |
| 18 | + public void applyOptions(@NonNull Context context, @NonNull GlideBuilder builder) { | |
| 19 | + super.applyOptions(context, builder); | |
| 20 | + } | |
| 13 | 21 | } | ... | ... |
core/base/src/main/java/com/cnlive/core/libs/base/arouter/RouterConst.java
core/base/src/main/java/com/cnlive/core/libs/base/arouter/user/CNLiveUserService.java
| ... | ... | @@ -8,9 +8,6 @@ import com.alibaba.android.arouter.facade.template.IProvider; |
| 8 | 8 | * 给上海电商 用户库 |
| 9 | 9 | */ |
| 10 | 10 | public interface CNLiveUserService extends IProvider { |
| 11 | - public static final String PATH = "/openService/CNLiveUserService"; | |
| 12 | - public static final String NAME = "开放用户信息的服务接口"; | |
| 13 | - | |
| 14 | 11 | /** |
| 15 | 12 | * 跳转到登录页 |
| 16 | 13 | * | ... | ... |
core/base/src/main/java/com/cnlive/core/libs/base/frame/activity/BaseActivity.java
| ... | ... | @@ -85,8 +85,6 @@ public abstract class BaseActivity<V extends BaseView, P extends BasePresenter<V |
| 85 | 85 | private boolean isShowTitle = true; |
| 86 | 86 | //是否显示状态栏 |
| 87 | 87 | private boolean isShowStatusBar = true; |
| 88 | - //是否允许旋转屏幕 | |
| 89 | - private boolean isAllowScreenRoate = false; | |
| 90 | 88 | public Context context; |
| 91 | 89 | /*******************************************/ |
| 92 | 90 | private int stateBarColor = -1;//状态栏颜色 |
| ... | ... | @@ -155,10 +153,6 @@ public abstract class BaseActivity<V extends BaseView, P extends BasePresenter<V |
| 155 | 153 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN |
| 156 | 154 | , WindowManager.LayoutParams.FLAG_FULLSCREEN); |
| 157 | 155 | } |
| 158 | - //设置屏幕是否可旋转 | |
| 159 | - if (!isAllowScreenRoate) { | |
| 160 | - setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); | |
| 161 | - } | |
| 162 | 156 | //设置binding数据 |
| 163 | 157 | if (null != baseBinding) { |
| 164 | 158 | initBindingLayoutData(baseBinding); |
| ... | ... | @@ -320,14 +314,6 @@ public abstract class BaseActivity<V extends BaseView, P extends BasePresenter<V |
| 320 | 314 | isShowStatusBar = showStatusBar; |
| 321 | 315 | } |
| 322 | 316 | |
| 323 | - /** | |
| 324 | - * 是否允许屏幕旋转 | |
| 325 | - * | |
| 326 | - * @param allowScreenRoate true or false | |
| 327 | - */ | |
| 328 | - public final void enableAllowScreenRoate(boolean allowScreenRoate) { | |
| 329 | - isAllowScreenRoate = allowScreenRoate; | |
| 330 | - } | |
| 331 | 317 | |
| 332 | 318 | |
| 333 | 319 | /** | ... | ... |
core/base/src/main/java/com/cnlive/core/libs/base/util/OpenServiceHelpUtil.java
0 → 100644
| 1 | +package com.cnlive.core.libs.base.util; | |
| 2 | + | |
| 3 | +import android.content.Context; | |
| 4 | + | |
| 5 | +import com.alibaba.android.arouter.launcher.ARouter; | |
| 6 | +import com.cnlive.core.libs.base.arouter.RouterConst; | |
| 7 | +import com.cnlive.core.libs.base.arouter.user.CNLiveUserService; | |
| 8 | + | |
| 9 | +public class OpenServiceHelpUtil { | |
| 10 | + /** | |
| 11 | + * 获取用户库 | |
| 12 | + * | |
| 13 | + * @param context | |
| 14 | + * @return | |
| 15 | + */ | |
| 16 | + public static CNLiveUserService getUserService(Context context) { | |
| 17 | + CNLiveUserService cnLiveUserService = null; | |
| 18 | + cnLiveUserService = (CNLiveUserService) ARouter.getInstance().build(RouterConst.OPEN_USER_SERVICE_PATH).navigation(context); | |
| 19 | + return cnLiveUserService; | |
| 20 | + } | |
| 21 | + | |
| 22 | +// /** | |
| 23 | +// * 获取支付库 | |
| 24 | +// * | |
| 25 | +// * @param context | |
| 26 | +// * @return | |
| 27 | +// */ | |
| 28 | +// public static CNLivePayService getPayService(Context context) { | |
| 29 | +// CNLivePayService cnLivePayService = null; | |
| 30 | +// cnLivePayService = (CNLivePayService) ARouter.getInstance().build(CNLivePayService.PATH).navigation(context); | |
| 31 | +// return cnLivePayService; | |
| 32 | +// } | |
| 33 | +// | |
| 34 | +// /** | |
| 35 | +// * 获取分享库 | |
| 36 | +// * | |
| 37 | +// * @param context | |
| 38 | +// * @return | |
| 39 | +// */ | |
| 40 | +// public static CNLiveShareService getShareService(Context context) { | |
| 41 | +// CNLiveShareService cnLiveShareService = null; | |
| 42 | +// cnLiveShareService = (CNLiveShareService) ARouter.getInstance().build(CNLiveShareService.PATH).navigation(context); | |
| 43 | +// return cnLiveShareService; | |
| 44 | +// } | |
| 45 | +} | ... | ... |
core/imageload/src/main/java/com/cnlive/core/imageload/util/MultiView.java
| 1 | 1 | package com.cnlive.core.imageload.util; |
| 2 | 2 | |
| 3 | -import android.annotation.SuppressLint; | |
| 4 | 3 | import android.content.Context; |
| 5 | 4 | import android.content.res.TypedArray; |
| 6 | 5 | import android.graphics.Bitmap; |
| ... | ... | @@ -158,7 +157,7 @@ public class MultiView extends ImageView { |
| 158 | 157 | .applyDimension(TypedValue.COMPLEX_UNIT_DIP, |
| 159 | 158 | BODER_RADIUS_DEFAULT, getResources() |
| 160 | 159 | .getDisplayMetrics()));// 默认为10dp |
| 161 | - type = typedArray.getInt(R.styleable.RoundImageView_type, TYPE_CIRCLE);// 默认为Circle | |
| 160 | + type = typedArray.getInt(R.styleable.RoundImageView_shapeType, TYPE_CIRCLE);// 默认为Circle | |
| 162 | 161 | angleCount = typedArray.getInt(R.styleable.RoundImageView_angleCount, ANGLECOUNT); |
| 163 | 162 | currentAngle = typedArray.getInt(R.styleable.RoundImageView_currentAngle, currentAngle); |
| 164 | 163 | ... | ... |
core/imageload/src/main/res/values/style.xml
| ... | ... | @@ -2,10 +2,10 @@ |
| 2 | 2 | <resources> |
| 3 | 3 | <declare-styleable name="RoundImageView"> |
| 4 | 4 | <attr name="borderRadius" format="dimension" /> |
| 5 | - <attr name="type"> | |
| 5 | + <attr name="shapeType"> | |
| 6 | 6 | <enum name="circle" value="0" /> |
| 7 | 7 | <enum name="round" value="1" /> |
| 8 | - <enum name="multi" value="3" /> | |
| 8 | + <enum name="polygon" value="3" /> | |
| 9 | 9 | </attr> |
| 10 | 10 | <attr name="angleCount" format="integer" /> |
| 11 | 11 | <attr name="currentAngle" format="integer" /> | ... | ... |
module/shanghaishop/build.gradle
| ... | ... | @@ -17,6 +17,14 @@ dependencies { |
| 17 | 17 | testImplementation 'junit:junit:4.12' |
| 18 | 18 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' |
| 19 | 19 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' |
| 20 | + api('cn.cnlive.b2c:cnliveb2c:1.0.0-20191217.063321-17') { | |
| 21 | + exclude group: 'com.github.bumptech.glide' | |
| 22 | + } | |
| 23 | +// api 'cn.cnlive.b2c:cnliveb2c:1.0.0-20191217.063321-17' | |
| 24 | + | |
| 25 | + api 'cn.cnlive.b2c:nicevideoplayer:1.0.0-20191206.090854-2' | |
| 26 | + api 'cn.cnlive.b2c:udesk-sdk-ui:1.0.0' | |
| 27 | + | |
| 20 | 28 | |
| 21 | 29 | /** 圆形图片处理 androidx **/ |
| 22 | 30 | implementation 'de.hdodenhof:circleimageview:3.0.1' |
| ... | ... | @@ -31,7 +39,8 @@ dependencies { |
| 31 | 39 | implementation 'com.alibaba:fastjson:1.2.61' |
| 32 | 40 | |
| 33 | 41 | /*轮播图*/ |
| 34 | - implementation 'com.bigkoo:convenientbanner:2.1.5' | |
| 42 | +// implementation 'com.bigkoo:convenientbanner:2.1.5' | |
| 43 | + implementation 'com.bigkoo:convenientbanner:2.0.5' | |
| 35 | 44 | |
| 36 | 45 | /*滚动播放的公告控件*/ |
| 37 | 46 | implementation 'com.github.czy1121:noticeview:1.1.0' |
| ... | ... | @@ -49,7 +58,7 @@ dependencies { |
| 49 | 58 | implementation 'com.songhang:smart-headerfooter-recyclerview:1.0.1' |
| 50 | 59 | |
| 51 | 60 | /*二维码*/ |
| 52 | - implementation 'com.google.zxing:core:3.3.0' | |
| 61 | +// implementation 'com.google.zxing:core:3.3.0' | |
| 53 | 62 | |
| 54 | 63 | // implementation ('cn.yipianfengye.android:zxing-library:2.2'){ |
| 55 | 64 | // // 如果已经引入 support:appcompat-v7 可以加上这句话 |
| ... | ... | @@ -69,12 +78,12 @@ dependencies { |
| 69 | 78 | /*一个Android TabLayout库*/ |
| 70 | 79 | implementation 'com.flyco.tablayout:FlycoTabLayout_Lib:2.1.2@aar' |
| 71 | 80 | |
| 72 | - /*应用卡顿检测*/ | |
| 73 | - debugImplementation 'com.github.markzhai:blockcanary-android:1.5.0' | |
| 74 | - releaseImplementation 'com.github.markzhai:blockcanary-no-op:1.5.0' | |
| 81 | +// /*应用卡顿检测*/ | |
| 82 | +// debugImplementation 'com.github.markzhai:blockcanary-android:1.5.0' | |
| 83 | +// releaseImplementation 'com.github.markzhai:blockcanary-no-op:1.5.0' | |
| 75 | 84 | |
| 76 | - /*友盟统计*/ | |
| 77 | - implementation 'com.umeng.analytics:analytics:latest.integration' | |
| 85 | +// /*友盟统计*/ | |
| 86 | +// implementation 'com.umeng.analytics:analytics:latest.integration' | |
| 78 | 87 | |
| 79 | 88 | /*Float Labeled EditText*/ |
| 80 | 89 | implementation 'com.wrapp.floatlabelededittext:library:0.0.6' |
| ... | ... | @@ -87,30 +96,8 @@ dependencies { |
| 87 | 96 | |
| 88 | 97 | /*自定义各种形状的imageview */ |
| 89 | 98 | implementation 'com.github.siyamed:android-shape-imageview:0.9.+@aar' |
| 90 | - | |
| 91 | - // compile 'com.android.support:appcompat-v7:23.4.0' | |
| 92 | - | |
| 93 | - // compile 'com.android.support:recyclerview-v7:23.4.0' | |
| 94 | - | |
| 95 | - // compile 'com.android.support:design:23.4.0' | |
| 96 | - implementation files('libs/alipaySingle-20161222.jar') | |
| 97 | -// implementation files('libs/BaiduLBS_Android.jar') | |
| 98 | - implementation files('libs/bankabccaller.jar') | |
| 99 | - implementation files('libs/EhkingSDK-v2.0.jar') | |
| 100 | - implementation files('libs/jackson-all-1.9.11.jar') | |
| 101 | - implementation files('libs/MagicWindowSDK.jar') | |
| 102 | - implementation files('libs/mqqopenpay.jar') | |
| 103 | - implementation files('libs/SecurityEnvSDK-release-1.1.0.jar') | |
| 104 | - implementation files('libs/umeng-common-1.5.3.jar') | |
| 105 | - implementation files('libs/umeng-share-core-6.9.2.jar') | |
| 106 | -// implementation files('libs/umeng-share-QQ-simplify-6.9.2.jar') | |
| 107 | -// implementation files('libs/umeng-share-sina-simplify-6.9.2.jar') | |
| 108 | -// implementation files('libs/umeng-share-wechat-simplify-6.9.2.jar') | |
| 109 | -// implementation files('libs/umeng-share-sina-simplify-6.9.2.jar') | |
| 110 | - implementation files('libs/umeng-shareboard-widget-6.9.2.jar') | |
| 111 | - implementation files('libs/umeng-sharetool-6.9.2.jar') | |
| 112 | - implementation files('libs/UPPayAssistEx.jar') | |
| 113 | - implementation files('libs/UPPayPluginExPro.jar') | |
| 114 | - | |
| 99 | + implementation 'com.cnlive.libs:base:1.7.6.18' | |
| 100 | + implementation project(path: ':library_baidumap') | |
| 101 | + implementation project(path: ':core:base') | |
| 115 | 102 | |
| 116 | 103 | } | ... | ... |
module/shanghaishop/libs/EhkingSDK-v2.0.jar deleted
100644 → 0
No preview for this file type
module/shanghaishop/libs/MagicWindowSDK.jar deleted
100644 → 0
No preview for this file type
module/shanghaishop/libs/SecurityEnvSDK-release-1.1.0.jar deleted
100644 → 0
No preview for this file type
module/shanghaishop/libs/UPPayAssistEx.jar deleted
100644 → 0
No preview for this file type
module/shanghaishop/libs/UPPayPluginExPro.jar deleted
100644 → 0
No preview for this file type
module/shanghaishop/libs/alipaySingle-20161222.jar deleted
100644 → 0
No preview for this file type
module/shanghaishop/libs/bankabccaller.jar deleted
100644 → 0
No preview for this file type
module/shanghaishop/libs/jackson-all-1.9.11.jar deleted
100644 → 0
No preview for this file type
module/shanghaishop/libs/mqqopenpay.jar deleted
100644 → 0
No preview for this file type
module/shanghaishop/libs/umeng-common-1.5.3.jar deleted
100644 → 0
No preview for this file type
module/shanghaishop/libs/umeng-share-core-6.9.2.jar deleted
100644 → 0
No preview for this file type
module/shanghaishop/libs/umeng-shareboard-widget-6.9.2.jar deleted
100644 → 0
No preview for this file type
module/shanghaishop/libs/umeng-sharetool-6.9.2.jar deleted
100644 → 0
No preview for this file type
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/DisplayUtil.java deleted
100644 → 0
| 1 | -package com.uuzuche.lib_zxing; | |
| 2 | - | |
| 3 | -import android.content.Context; | |
| 4 | - | |
| 5 | -/** | |
| 6 | - * Created by aaron on 16/8/3. | |
| 7 | - */ | |
| 8 | -public class DisplayUtil | |
| 9 | -{ | |
| 10 | - | |
| 11 | - public static int screenWidthPx; //屏幕宽 px | |
| 12 | - public static int screenhightPx; //屏幕高 px | |
| 13 | - public static float density;//屏幕密度 | |
| 14 | - public static int densityDPI;//屏幕密度 | |
| 15 | - public static float screenWidthDip;// dp单位 | |
| 16 | - public static float screenHightDip;// dp单位 | |
| 17 | - | |
| 18 | - | |
| 19 | - | |
| 20 | - /** | |
| 21 | - * 根据手机的分辨率从 dp 的单位 转成为 px(像素) | |
| 22 | - */ | |
| 23 | - public static int dip2px(Context context, float dpValue) { | |
| 24 | - final float scale = context.getResources().getDisplayMetrics().density; | |
| 25 | - return (int) (dpValue * scale + 0.5f); | |
| 26 | - } | |
| 27 | - | |
| 28 | - /** | |
| 29 | - * 根据手机的分辨率从 px(像素) 的单位 转成为 dp | |
| 30 | - */ | |
| 31 | - public static int px2dip(Context context, float pxValue) { | |
| 32 | - final float scale = context.getResources().getDisplayMetrics().density; | |
| 33 | - return (int) (pxValue / scale + 0.5f); | |
| 34 | - } | |
| 35 | - | |
| 36 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/ZApplication.java deleted
100644 → 0
| 1 | -package com.uuzuche.lib_zxing; | |
| 2 | - | |
| 3 | -import android.app.Application; | |
| 4 | -import android.util.DisplayMetrics; | |
| 5 | - | |
| 6 | -/** | |
| 7 | - * Created by aaron on 16/8/9. | |
| 8 | - */ | |
| 9 | - | |
| 10 | -public class ZApplication extends Application { | |
| 11 | - | |
| 12 | - @Override | |
| 13 | - public void onCreate() { | |
| 14 | - super.onCreate(); | |
| 15 | - /** | |
| 16 | - * 初始化尺寸工具类 | |
| 17 | - */ | |
| 18 | - initDisplayOpinion(); | |
| 19 | - } | |
| 20 | - | |
| 21 | - private void initDisplayOpinion() { | |
| 22 | - DisplayMetrics dm = getResources().getDisplayMetrics(); | |
| 23 | - DisplayUtil.density = dm.density; | |
| 24 | - DisplayUtil.densityDPI = dm.densityDpi; | |
| 25 | - DisplayUtil.screenWidthPx = dm.widthPixels; | |
| 26 | - DisplayUtil.screenhightPx = dm.heightPixels; | |
| 27 | - DisplayUtil.screenWidthDip = DisplayUtil.px2dip(getApplicationContext(), dm.widthPixels); | |
| 28 | - DisplayUtil.screenHightDip = DisplayUtil.px2dip(getApplicationContext(), dm.heightPixels); | |
| 29 | - } | |
| 30 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/activity/CaptureActivity.java deleted
100644 → 0
| 1 | -package com.uuzuche.lib_zxing.activity; | |
| 2 | - | |
| 3 | -import android.content.Intent; | |
| 4 | -import android.graphics.Bitmap; | |
| 5 | -import android.os.Bundle; | |
| 6 | -import android.util.Log; | |
| 7 | - | |
| 8 | -import androidx.appcompat.app.AppCompatActivity; | |
| 9 | - | |
| 10 | -import com.cnlive.cloud.shanghaishop.R; | |
| 11 | - | |
| 12 | - | |
| 13 | -/** | |
| 14 | - * Initial the camera | |
| 15 | - * <p> | |
| 16 | - * 默认的二维码扫描Activity | |
| 17 | - */ | |
| 18 | -public class CaptureActivity extends AppCompatActivity { | |
| 19 | - | |
| 20 | - | |
| 21 | - @Override | |
| 22 | - protected void onCreate(Bundle savedInstanceState) { | |
| 23 | - | |
| 24 | - super.onCreate(savedInstanceState); | |
| 25 | - setContentView(R.layout.camera); | |
| 26 | - CaptureFragment captureFragment = new CaptureFragment(); | |
| 27 | - captureFragment.setAnalyzeCallback(analyzeCallback); | |
| 28 | - getSupportFragmentManager().beginTransaction().replace(R.id.fl_zxing_container, captureFragment).commit(); | |
| 29 | - captureFragment.setCameraInitCallBack(new CaptureFragment.CameraInitCallBack() { | |
| 30 | - @Override | |
| 31 | - public void callBack(Exception e) { | |
| 32 | - if (e == null) { | |
| 33 | - | |
| 34 | - } else { | |
| 35 | - Log.e("TAG", "callBack: ", e); | |
| 36 | - } | |
| 37 | - } | |
| 38 | - }); | |
| 39 | - | |
| 40 | - } | |
| 41 | - | |
| 42 | - /** | |
| 43 | - * 二维码解析回调函数 | |
| 44 | - */ | |
| 45 | - CodeUtils.AnalyzeCallback analyzeCallback = new CodeUtils.AnalyzeCallback() { | |
| 46 | - @Override | |
| 47 | - public void onAnalyzeSuccess(Bitmap mBitmap, String result) { | |
| 48 | - Intent resultIntent = new Intent(); | |
| 49 | - Bundle bundle = new Bundle(); | |
| 50 | - bundle.putInt(CodeUtils.RESULT_TYPE, CodeUtils.RESULT_SUCCESS); | |
| 51 | - bundle.putString(CodeUtils.RESULT_STRING, result); | |
| 52 | - resultIntent.putExtras(bundle); | |
| 53 | - CaptureActivity.this.setResult(RESULT_OK, resultIntent); | |
| 54 | - CaptureActivity.this.finish(); | |
| 55 | - } | |
| 56 | - | |
| 57 | - @Override | |
| 58 | - public void onAnalyzeFailed() { | |
| 59 | - Intent resultIntent = new Intent(); | |
| 60 | - Bundle bundle = new Bundle(); | |
| 61 | - bundle.putInt(CodeUtils.RESULT_TYPE, CodeUtils.RESULT_FAILED); | |
| 62 | - bundle.putString(CodeUtils.RESULT_STRING, ""); | |
| 63 | - resultIntent.putExtras(bundle); | |
| 64 | - CaptureActivity.this.setResult(RESULT_OK, resultIntent); | |
| 65 | - CaptureActivity.this.finish(); | |
| 66 | - } | |
| 67 | - }; | |
| 68 | -} | |
| 69 | 0 | \ No newline at end of file |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/activity/CaptureFragment.java deleted
100644 → 0
| 1 | -package com.uuzuche.lib_zxing.activity; | |
| 2 | - | |
| 3 | -import android.content.res.AssetFileDescriptor; | |
| 4 | -import android.graphics.Bitmap; | |
| 5 | -import android.hardware.Camera; | |
| 6 | -import android.media.AudioManager; | |
| 7 | -import android.media.MediaPlayer; | |
| 8 | -import android.os.Bundle; | |
| 9 | -import android.os.Handler; | |
| 10 | -import android.os.Vibrator; | |
| 11 | -import android.text.TextUtils; | |
| 12 | -import android.view.LayoutInflater; | |
| 13 | -import android.view.SurfaceHolder; | |
| 14 | -import android.view.SurfaceView; | |
| 15 | -import android.view.View; | |
| 16 | -import android.view.ViewGroup; | |
| 17 | - | |
| 18 | -import androidx.annotation.Nullable; | |
| 19 | -import androidx.fragment.app.Fragment; | |
| 20 | - | |
| 21 | -import com.cnlive.cloud.shanghaishop.R; | |
| 22 | -import com.google.zxing.BarcodeFormat; | |
| 23 | -import com.google.zxing.Result; | |
| 24 | -import com.uuzuche.lib_zxing.camera.CameraManager; | |
| 25 | -import com.uuzuche.lib_zxing.decoding.CaptureActivityHandler; | |
| 26 | -import com.uuzuche.lib_zxing.decoding.InactivityTimer; | |
| 27 | -import com.uuzuche.lib_zxing.view.ViewfinderView; | |
| 28 | - | |
| 29 | -import java.io.IOException; | |
| 30 | -import java.util.Vector; | |
| 31 | - | |
| 32 | -/** | |
| 33 | - * 自定义实现的扫描Fragment | |
| 34 | - */ | |
| 35 | -public class CaptureFragment extends Fragment implements SurfaceHolder.Callback { | |
| 36 | - | |
| 37 | - private CaptureActivityHandler handler; | |
| 38 | - private ViewfinderView viewfinderView; | |
| 39 | - private boolean hasSurface; | |
| 40 | - private Vector<BarcodeFormat> decodeFormats; | |
| 41 | - private String characterSet; | |
| 42 | - private InactivityTimer inactivityTimer; | |
| 43 | - private MediaPlayer mediaPlayer; | |
| 44 | - private boolean playBeep; | |
| 45 | - private static final float BEEP_VOLUME = 0.10f; | |
| 46 | - private boolean vibrate; | |
| 47 | - private SurfaceView surfaceView; | |
| 48 | - private SurfaceHolder surfaceHolder; | |
| 49 | - private CodeUtils.AnalyzeCallback analyzeCallback; | |
| 50 | - private Camera camera; | |
| 51 | - | |
| 52 | - @Override | |
| 53 | - public void onCreate(@Nullable Bundle savedInstanceState) { | |
| 54 | - super.onCreate(savedInstanceState); | |
| 55 | - | |
| 56 | - | |
| 57 | - CameraManager.init(getActivity().getApplication()); | |
| 58 | - | |
| 59 | - hasSurface = false; | |
| 60 | - inactivityTimer = new InactivityTimer(this.getActivity()); | |
| 61 | - } | |
| 62 | - | |
| 63 | - @Nullable | |
| 64 | - @Override | |
| 65 | - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { | |
| 66 | - | |
| 67 | - Bundle bundle = getArguments(); | |
| 68 | - View view = null; | |
| 69 | - if (bundle != null) { | |
| 70 | - int layoutId = bundle.getInt(CodeUtils.LAYOUT_ID); | |
| 71 | - if (layoutId != -1) { | |
| 72 | - view = inflater.inflate(layoutId, null); | |
| 73 | - } | |
| 74 | - } | |
| 75 | - | |
| 76 | - if (view == null) { | |
| 77 | - view = inflater.inflate(R.layout.fragment_capture, null); | |
| 78 | - } | |
| 79 | - | |
| 80 | - viewfinderView = (ViewfinderView) view.findViewById(R.id.viewfinder_view); | |
| 81 | - surfaceView = (SurfaceView) view.findViewById(R.id.preview_view); | |
| 82 | - surfaceHolder = surfaceView.getHolder(); | |
| 83 | - | |
| 84 | - return view; | |
| 85 | - } | |
| 86 | - | |
| 87 | - @Override | |
| 88 | - public void onResume() { | |
| 89 | - super.onResume(); | |
| 90 | - if (hasSurface) { | |
| 91 | - initCamera(surfaceHolder); | |
| 92 | - } else { | |
| 93 | - surfaceHolder.addCallback(this); | |
| 94 | - surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); | |
| 95 | - } | |
| 96 | - decodeFormats = null; | |
| 97 | - characterSet = null; | |
| 98 | - | |
| 99 | - playBeep = true; | |
| 100 | - AudioManager audioService = (AudioManager) getActivity().getSystemService(getActivity().AUDIO_SERVICE); | |
| 101 | - if (audioService.getRingerMode() != AudioManager.RINGER_MODE_NORMAL) { | |
| 102 | - playBeep = false; | |
| 103 | - } | |
| 104 | - initBeepSound(); | |
| 105 | - vibrate = true; | |
| 106 | - } | |
| 107 | - | |
| 108 | - @Override | |
| 109 | - public void onPause() { | |
| 110 | - super.onPause(); | |
| 111 | - if (handler != null) { | |
| 112 | - handler.quitSynchronously(); | |
| 113 | - handler = null; | |
| 114 | - } | |
| 115 | - CameraManager.get().closeDriver(); | |
| 116 | - } | |
| 117 | - | |
| 118 | - @Override | |
| 119 | - public void onDestroy() { | |
| 120 | - super.onDestroy(); | |
| 121 | - inactivityTimer.shutdown(); | |
| 122 | - } | |
| 123 | - | |
| 124 | - | |
| 125 | - /** | |
| 126 | - * Handler scan result | |
| 127 | - * | |
| 128 | - * @param result | |
| 129 | - * @param barcode | |
| 130 | - */ | |
| 131 | - public void handleDecode(Result result, Bitmap barcode) { | |
| 132 | - inactivityTimer.onActivity(); | |
| 133 | - playBeepSoundAndVibrate(); | |
| 134 | - | |
| 135 | - if (result == null || TextUtils.isEmpty(result.getText())) { | |
| 136 | - if (analyzeCallback != null) { | |
| 137 | - analyzeCallback.onAnalyzeFailed(); | |
| 138 | - } | |
| 139 | - } else { | |
| 140 | - if (analyzeCallback != null) { | |
| 141 | - analyzeCallback.onAnalyzeSuccess(barcode, result.getText()); | |
| 142 | - } | |
| 143 | - } | |
| 144 | - } | |
| 145 | - | |
| 146 | - private void initCamera(SurfaceHolder surfaceHolder) { | |
| 147 | - try { | |
| 148 | - CameraManager.get().openDriver(surfaceHolder); | |
| 149 | - camera = CameraManager.get().getCamera(); | |
| 150 | - } catch (Exception e) { | |
| 151 | - if (callBack != null) { | |
| 152 | - callBack.callBack(e); | |
| 153 | - } | |
| 154 | - return; | |
| 155 | - } | |
| 156 | - if (callBack != null) { | |
| 157 | - callBack.callBack(null); | |
| 158 | - } | |
| 159 | - if (handler == null) { | |
| 160 | - handler = new CaptureActivityHandler(this, decodeFormats, characterSet, viewfinderView); | |
| 161 | - } | |
| 162 | - } | |
| 163 | - | |
| 164 | - @Override | |
| 165 | - public void surfaceChanged(SurfaceHolder holder, int format, int width, | |
| 166 | - int height) { | |
| 167 | - | |
| 168 | - } | |
| 169 | - | |
| 170 | - @Override | |
| 171 | - public void surfaceCreated(SurfaceHolder holder) { | |
| 172 | - if (!hasSurface) { | |
| 173 | - hasSurface = true; | |
| 174 | - initCamera(holder); | |
| 175 | - } | |
| 176 | - | |
| 177 | - } | |
| 178 | - | |
| 179 | - @Override | |
| 180 | - public void surfaceDestroyed(SurfaceHolder holder) { | |
| 181 | - hasSurface = false; | |
| 182 | - if (camera != null) { | |
| 183 | - if (camera != null && CameraManager.get().isPreviewing()) { | |
| 184 | - if (!CameraManager.get().isUseOneShotPreviewCallback()) { | |
| 185 | - camera.setPreviewCallback(null); | |
| 186 | - } | |
| 187 | - camera.stopPreview(); | |
| 188 | - CameraManager.get().getPreviewCallback().setHandler(null, 0); | |
| 189 | - CameraManager.get().getAutoFocusCallback().setHandler(null, 0); | |
| 190 | - CameraManager.get().setPreviewing(false); | |
| 191 | - } | |
| 192 | - } | |
| 193 | - } | |
| 194 | - | |
| 195 | - public Handler getHandler() { | |
| 196 | - return handler; | |
| 197 | - } | |
| 198 | - | |
| 199 | - public void drawViewfinder() { | |
| 200 | - viewfinderView.drawViewfinder(); | |
| 201 | - | |
| 202 | - } | |
| 203 | - | |
| 204 | - private void initBeepSound() { | |
| 205 | - if (playBeep && mediaPlayer == null) { | |
| 206 | - // The volume on STREAM_SYSTEM is not adjustable, and users found it | |
| 207 | - // too loud, | |
| 208 | - // so we now play on the music stream. | |
| 209 | - getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC); | |
| 210 | - mediaPlayer = new MediaPlayer(); | |
| 211 | - mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC); | |
| 212 | - mediaPlayer.setOnCompletionListener(beepListener); | |
| 213 | - | |
| 214 | - AssetFileDescriptor file = getResources().openRawResourceFd( | |
| 215 | - R.raw.beep); | |
| 216 | - try { | |
| 217 | - mediaPlayer.setDataSource(file.getFileDescriptor(), | |
| 218 | - file.getStartOffset(), file.getLength()); | |
| 219 | - file.close(); | |
| 220 | - mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME); | |
| 221 | - mediaPlayer.prepare(); | |
| 222 | - } catch (IOException e) { | |
| 223 | - mediaPlayer = null; | |
| 224 | - } | |
| 225 | - } | |
| 226 | - } | |
| 227 | - | |
| 228 | - private static final long VIBRATE_DURATION = 200L; | |
| 229 | - | |
| 230 | - private void playBeepSoundAndVibrate() { | |
| 231 | - if (playBeep && mediaPlayer != null) { | |
| 232 | - mediaPlayer.start(); | |
| 233 | - } | |
| 234 | - if (vibrate) { | |
| 235 | - Vibrator vibrator = (Vibrator) getActivity().getSystemService(getActivity().VIBRATOR_SERVICE); | |
| 236 | - vibrator.vibrate(VIBRATE_DURATION); | |
| 237 | - } | |
| 238 | - } | |
| 239 | - | |
| 240 | - /** | |
| 241 | - * When the beep has finished playing, rewind to queue up another one. | |
| 242 | - */ | |
| 243 | - private final MediaPlayer.OnCompletionListener beepListener = new MediaPlayer.OnCompletionListener() { | |
| 244 | - public void onCompletion(MediaPlayer mediaPlayer) { | |
| 245 | - mediaPlayer.seekTo(0); | |
| 246 | - } | |
| 247 | - }; | |
| 248 | - | |
| 249 | - public CodeUtils.AnalyzeCallback getAnalyzeCallback() { | |
| 250 | - return analyzeCallback; | |
| 251 | - } | |
| 252 | - | |
| 253 | - public void setAnalyzeCallback(CodeUtils.AnalyzeCallback analyzeCallback) { | |
| 254 | - this.analyzeCallback = analyzeCallback; | |
| 255 | - } | |
| 256 | - | |
| 257 | - @Nullable | |
| 258 | - CameraInitCallBack callBack; | |
| 259 | - | |
| 260 | - /** | |
| 261 | - * Set callback for Camera check whether Camera init success or not. | |
| 262 | - */ | |
| 263 | - public void setCameraInitCallBack(CameraInitCallBack callBack) { | |
| 264 | - this.callBack = callBack; | |
| 265 | - } | |
| 266 | - | |
| 267 | - interface CameraInitCallBack { | |
| 268 | - /** | |
| 269 | - * Callback for Camera init result. | |
| 270 | - * @param e If is's null,means success.otherwise Camera init failed with the Exception. | |
| 271 | - */ | |
| 272 | - void callBack(Exception e); | |
| 273 | - } | |
| 274 | - | |
| 275 | - | |
| 276 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/activity/CodeUtils.java deleted
100644 → 0
| 1 | -package com.uuzuche.lib_zxing.activity; | |
| 2 | - | |
| 3 | -import android.graphics.Bitmap; | |
| 4 | -import android.graphics.BitmapFactory; | |
| 5 | -import android.graphics.Matrix; | |
| 6 | -import android.hardware.Camera; | |
| 7 | -import android.os.Bundle; | |
| 8 | -import android.text.TextUtils; | |
| 9 | - | |
| 10 | -import com.google.zxing.BarcodeFormat; | |
| 11 | -import com.google.zxing.BinaryBitmap; | |
| 12 | -import com.google.zxing.DecodeHintType; | |
| 13 | -import com.google.zxing.EncodeHintType; | |
| 14 | -import com.google.zxing.MultiFormatReader; | |
| 15 | -import com.google.zxing.Result; | |
| 16 | -import com.google.zxing.WriterException; | |
| 17 | -import com.google.zxing.common.BitMatrix; | |
| 18 | -import com.google.zxing.common.HybridBinarizer; | |
| 19 | -import com.google.zxing.qrcode.QRCodeWriter; | |
| 20 | -import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; | |
| 21 | -import com.uuzuche.lib_zxing.camera.BitmapLuminanceSource; | |
| 22 | -import com.uuzuche.lib_zxing.camera.CameraManager; | |
| 23 | -import com.uuzuche.lib_zxing.decoding.DecodeFormatManager; | |
| 24 | - | |
| 25 | -import java.util.Hashtable; | |
| 26 | -import java.util.Vector; | |
| 27 | - | |
| 28 | -/** | |
| 29 | - * Created by aaron on 16/7/27. | |
| 30 | - * 二维码扫描工具类 | |
| 31 | - */ | |
| 32 | -public class CodeUtils { | |
| 33 | - | |
| 34 | - public static final String RESULT_TYPE = "result_type"; | |
| 35 | - public static final String RESULT_STRING = "result_string"; | |
| 36 | - public static final int RESULT_SUCCESS = 1; | |
| 37 | - public static final int RESULT_FAILED = 2; | |
| 38 | - | |
| 39 | - public static final String LAYOUT_ID = "layout_id"; | |
| 40 | - | |
| 41 | - | |
| 42 | - | |
| 43 | - /** | |
| 44 | - * 解析二维码图片工具类 | |
| 45 | - * @param analyzeCallback | |
| 46 | - */ | |
| 47 | - public static void analyzeBitmap(String path, AnalyzeCallback analyzeCallback) { | |
| 48 | - | |
| 49 | - /** | |
| 50 | - * 首先判断图片的大小,若图片过大,则执行图片的裁剪操作,防止OOM | |
| 51 | - */ | |
| 52 | - BitmapFactory.Options options = new BitmapFactory.Options(); | |
| 53 | - options.inJustDecodeBounds = true; // 先获取原大小 | |
| 54 | - Bitmap mBitmap = BitmapFactory.decodeFile(path, options); | |
| 55 | - options.inJustDecodeBounds = false; // 获取新的大小 | |
| 56 | - | |
| 57 | - int sampleSize = (int) (options.outHeight / (float) 400); | |
| 58 | - | |
| 59 | - if (sampleSize <= 0) | |
| 60 | - sampleSize = 1; | |
| 61 | - options.inSampleSize = sampleSize; | |
| 62 | - mBitmap = BitmapFactory.decodeFile(path, options); | |
| 63 | - | |
| 64 | - MultiFormatReader multiFormatReader = new MultiFormatReader(); | |
| 65 | - | |
| 66 | - // 解码的参数 | |
| 67 | - Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(2); | |
| 68 | - // 可以解析的编码类型 | |
| 69 | - Vector<BarcodeFormat> decodeFormats = new Vector<BarcodeFormat>(); | |
| 70 | - if (decodeFormats == null || decodeFormats.isEmpty()) { | |
| 71 | - decodeFormats = new Vector<BarcodeFormat>(); | |
| 72 | - | |
| 73 | - // 这里设置可扫描的类型,我这里选择了都支持 | |
| 74 | - decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS); | |
| 75 | - decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS); | |
| 76 | - decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS); | |
| 77 | - } | |
| 78 | - hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats); | |
| 79 | - // 设置继续的字符编码格式为UTF8 | |
| 80 | - // hints.put(DecodeHintType.CHARACTER_SET, "UTF8"); | |
| 81 | - // 设置解析配置参数 | |
| 82 | - multiFormatReader.setHints(hints); | |
| 83 | - | |
| 84 | - // 开始对图像资源解码 | |
| 85 | - Result rawResult = null; | |
| 86 | - try { | |
| 87 | - rawResult = multiFormatReader.decodeWithState(new BinaryBitmap(new HybridBinarizer(new BitmapLuminanceSource(mBitmap)))); | |
| 88 | - } catch (Exception e) { | |
| 89 | - e.printStackTrace(); | |
| 90 | - } | |
| 91 | - | |
| 92 | - if (rawResult != null) { | |
| 93 | - if (analyzeCallback != null) { | |
| 94 | - analyzeCallback.onAnalyzeSuccess(mBitmap, rawResult.getText()); | |
| 95 | - } | |
| 96 | - } else { | |
| 97 | - if (analyzeCallback != null) { | |
| 98 | - analyzeCallback.onAnalyzeFailed(); | |
| 99 | - } | |
| 100 | - } | |
| 101 | - } | |
| 102 | - | |
| 103 | - /** | |
| 104 | - * 生成二维码图片 | |
| 105 | - * @param text | |
| 106 | - * @param w | |
| 107 | - * @param h | |
| 108 | - * @param logo | |
| 109 | - * @return | |
| 110 | - */ | |
| 111 | - public static Bitmap createImage(String text, int w, int h, Bitmap logo) { | |
| 112 | - if (TextUtils.isEmpty(text)) { | |
| 113 | - return null; | |
| 114 | - } | |
| 115 | - try { | |
| 116 | - Bitmap scaleLogo = getScaleLogo(logo,w,h); | |
| 117 | - | |
| 118 | - int offsetX = w / 2; | |
| 119 | - int offsetY = h / 2; | |
| 120 | - | |
| 121 | - int scaleWidth = 0; | |
| 122 | - int scaleHeight = 0; | |
| 123 | - if (scaleLogo != null) { | |
| 124 | - scaleWidth = scaleLogo.getWidth(); | |
| 125 | - scaleHeight = scaleLogo.getHeight(); | |
| 126 | - offsetX = (w - scaleWidth) / 2; | |
| 127 | - offsetY = (h - scaleHeight) / 2; | |
| 128 | - } | |
| 129 | - Hashtable<EncodeHintType, Object> hints = new Hashtable<EncodeHintType, Object>(); | |
| 130 | - hints.put(EncodeHintType.CHARACTER_SET, "utf-8"); | |
| 131 | - //容错级别 | |
| 132 | - hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H); | |
| 133 | - //设置空白边距的宽度 | |
| 134 | - hints.put(EncodeHintType.MARGIN, 0); | |
| 135 | - BitMatrix bitMatrix = new QRCodeWriter().encode(text, BarcodeFormat.QR_CODE, w, h, hints); | |
| 136 | - int[] pixels = new int[w * h]; | |
| 137 | - for (int y = 0; y < h; y++) { | |
| 138 | - for (int x = 0; x < w; x++) { | |
| 139 | - if(x >= offsetX && x < offsetX + scaleWidth && y>= offsetY && y < offsetY + scaleHeight){ | |
| 140 | - int pixel = scaleLogo.getPixel(x-offsetX,y-offsetY); | |
| 141 | - if(pixel == 0){ | |
| 142 | - if(bitMatrix.get(x, y)){ | |
| 143 | - pixel = 0xff000000; | |
| 144 | - }else{ | |
| 145 | - pixel = 0xffffffff; | |
| 146 | - } | |
| 147 | - } | |
| 148 | - pixels[y * w + x] = pixel; | |
| 149 | - }else{ | |
| 150 | - if (bitMatrix.get(x, y)) { | |
| 151 | - pixels[y * w + x] = 0xff000000; | |
| 152 | - } else { | |
| 153 | - pixels[y * w + x] = 0xffffffff; | |
| 154 | - } | |
| 155 | - } | |
| 156 | - } | |
| 157 | - } | |
| 158 | - Bitmap bitmap = Bitmap.createBitmap(w, h, | |
| 159 | - Bitmap.Config.ARGB_8888); | |
| 160 | - bitmap.setPixels(pixels, 0, w, 0, 0, w, h); | |
| 161 | - return bitmap; | |
| 162 | - } catch (WriterException e) { | |
| 163 | - e.printStackTrace(); | |
| 164 | - } | |
| 165 | - return null; | |
| 166 | - } | |
| 167 | - | |
| 168 | - private static Bitmap getScaleLogo(Bitmap logo, int w, int h){ | |
| 169 | - if(logo == null)return null; | |
| 170 | - Matrix matrix = new Matrix(); | |
| 171 | - float scaleFactor = Math.min(w * 1.0f / 5 / logo.getWidth(), h * 1.0f / 5 /logo.getHeight()); | |
| 172 | - matrix.postScale(scaleFactor,scaleFactor); | |
| 173 | - Bitmap result = Bitmap.createBitmap(logo, 0, 0, logo.getWidth(), logo.getHeight(), matrix, true); | |
| 174 | - return result; | |
| 175 | - } | |
| 176 | - | |
| 177 | - /** | |
| 178 | - * 解析二维码结果 | |
| 179 | - */ | |
| 180 | - public interface AnalyzeCallback{ | |
| 181 | - | |
| 182 | - public void onAnalyzeSuccess(Bitmap mBitmap, String result); | |
| 183 | - | |
| 184 | - public void onAnalyzeFailed(); | |
| 185 | - } | |
| 186 | - | |
| 187 | - | |
| 188 | - /** | |
| 189 | - * 为CaptureFragment设置layout参数 | |
| 190 | - * @param captureFragment | |
| 191 | - * @param layoutId | |
| 192 | - */ | |
| 193 | - public static void setFragmentArgs(CaptureFragment captureFragment, int layoutId) { | |
| 194 | - if (captureFragment == null || layoutId == -1) { | |
| 195 | - return; | |
| 196 | - } | |
| 197 | - | |
| 198 | - Bundle bundle = new Bundle(); | |
| 199 | - bundle.putInt(LAYOUT_ID, layoutId); | |
| 200 | - captureFragment.setArguments(bundle); | |
| 201 | - } | |
| 202 | - | |
| 203 | - public static void isLightEnable(boolean isEnable) { | |
| 204 | - if (isEnable) { | |
| 205 | - Camera camera = CameraManager.get().getCamera(); | |
| 206 | - if (camera != null) { | |
| 207 | - Camera.Parameters parameter = camera.getParameters(); | |
| 208 | - parameter.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH); | |
| 209 | - camera.setParameters(parameter); | |
| 210 | - } | |
| 211 | - } else { | |
| 212 | - Camera camera = CameraManager.get().getCamera(); | |
| 213 | - if (camera != null) { | |
| 214 | - Camera.Parameters parameter = camera.getParameters(); | |
| 215 | - parameter.setFlashMode(Camera.Parameters.FLASH_MODE_OFF); | |
| 216 | - camera.setParameters(parameter); | |
| 217 | - } | |
| 218 | - } | |
| 219 | - } | |
| 220 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/activity/ZXingLibrary.java deleted
100644 → 0
| 1 | -package com.uuzuche.lib_zxing.activity; | |
| 2 | - | |
| 3 | -import android.content.Context; | |
| 4 | -import android.util.DisplayMetrics; | |
| 5 | - | |
| 6 | -import com.uuzuche.lib_zxing.DisplayUtil; | |
| 7 | - | |
| 8 | -/** | |
| 9 | - * Created by aaron on 16/9/7. | |
| 10 | - */ | |
| 11 | - | |
| 12 | -public class ZXingLibrary { | |
| 13 | - | |
| 14 | - public static void initDisplayOpinion(Context context) { | |
| 15 | - if (context == null) { | |
| 16 | - return; | |
| 17 | - } | |
| 18 | - DisplayMetrics dm = context.getResources().getDisplayMetrics(); | |
| 19 | - DisplayUtil.density = dm.density; | |
| 20 | - DisplayUtil.densityDPI = dm.densityDpi; | |
| 21 | - DisplayUtil.screenWidthPx = dm.widthPixels; | |
| 22 | - DisplayUtil.screenhightPx = dm.heightPixels; | |
| 23 | - DisplayUtil.screenWidthDip = DisplayUtil.px2dip(context, dm.widthPixels); | |
| 24 | - DisplayUtil.screenHightDip = DisplayUtil.px2dip(context, dm.heightPixels); | |
| 25 | - } | |
| 26 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/camera/AutoFocusCallback.java deleted
100644 → 0
| 1 | -/* | |
| 2 | - * Copyright (C) 2010 ZXing authors | |
| 3 | - * | |
| 4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | - * you may not use this file except in compliance with the License. | |
| 6 | - * You may obtain a copy of the License at | |
| 7 | - * | |
| 8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | - * | |
| 10 | - * Unless required by applicable law or agreed to in writing, software | |
| 11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | - * See the License for the specific language governing permissions and | |
| 14 | - * limitations under the License. | |
| 15 | - */ | |
| 16 | - | |
| 17 | -package com.uuzuche.lib_zxing.camera; | |
| 18 | - | |
| 19 | -import android.hardware.Camera; | |
| 20 | -import android.os.Handler; | |
| 21 | -import android.os.Message; | |
| 22 | -import android.util.Log; | |
| 23 | - | |
| 24 | -public final class AutoFocusCallback implements Camera.AutoFocusCallback { | |
| 25 | - | |
| 26 | - private static final String TAG = AutoFocusCallback.class.getSimpleName(); | |
| 27 | - | |
| 28 | - private static final long AUTOFOCUS_INTERVAL_MS = 1500L; | |
| 29 | - | |
| 30 | - private Handler autoFocusHandler; | |
| 31 | - private int autoFocusMessage; | |
| 32 | - | |
| 33 | - public void setHandler(Handler autoFocusHandler, int autoFocusMessage) { | |
| 34 | - this.autoFocusHandler = autoFocusHandler; | |
| 35 | - this.autoFocusMessage = autoFocusMessage; | |
| 36 | - } | |
| 37 | - | |
| 38 | - public void onAutoFocus(boolean success, Camera camera) { | |
| 39 | - if (autoFocusHandler != null) { | |
| 40 | - Message message = autoFocusHandler.obtainMessage(autoFocusMessage, success); | |
| 41 | - autoFocusHandler.sendMessageDelayed(message, AUTOFOCUS_INTERVAL_MS); | |
| 42 | - autoFocusHandler = null; | |
| 43 | - } else { | |
| 44 | - Log.d(TAG, "Got auto-focus callback, but no handler for it"); | |
| 45 | - } | |
| 46 | - } | |
| 47 | - | |
| 48 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/camera/BitmapLuminanceSource.java deleted
100644 → 0
| 1 | -package com.uuzuche.lib_zxing.camera; | |
| 2 | - | |
| 3 | -import android.graphics.Bitmap; | |
| 4 | - | |
| 5 | -import com.google.zxing.LuminanceSource; | |
| 6 | - | |
| 7 | -/** | |
| 8 | - * Created by aaron on 16/7/27. | |
| 9 | - * 自定义解析Bitmap LuminanceSource | |
| 10 | - */ | |
| 11 | -public class BitmapLuminanceSource extends LuminanceSource { | |
| 12 | - | |
| 13 | - private byte bitmapPixels[]; | |
| 14 | - | |
| 15 | - public BitmapLuminanceSource(Bitmap bitmap) { | |
| 16 | - super(bitmap.getWidth(), bitmap.getHeight()); | |
| 17 | - | |
| 18 | - // 首先,要取得该图片的像素数组内容 | |
| 19 | - int[] data = new int[bitmap.getWidth() * bitmap.getHeight()]; | |
| 20 | - this.bitmapPixels = new byte[bitmap.getWidth() * bitmap.getHeight()]; | |
| 21 | - bitmap.getPixels(data, 0, getWidth(), 0, 0, getWidth(), getHeight()); | |
| 22 | - | |
| 23 | - // 将int数组转换为byte数组,也就是取像素值中蓝色值部分作为辨析内容 | |
| 24 | - for (int i = 0; i < data.length; i++) { | |
| 25 | - this.bitmapPixels[i] = (byte) data[i]; | |
| 26 | - } | |
| 27 | - } | |
| 28 | - | |
| 29 | - @Override | |
| 30 | - public byte[] getMatrix() { | |
| 31 | - // 返回我们生成好的像素数据 | |
| 32 | - return bitmapPixels; | |
| 33 | - } | |
| 34 | - | |
| 35 | - @Override | |
| 36 | - public byte[] getRow(int y, byte[] row) { | |
| 37 | - // 这里要得到指定行的像素数据 | |
| 38 | - System.arraycopy(bitmapPixels, y * getWidth(), row, 0, getWidth()); | |
| 39 | - return row; | |
| 40 | - } | |
| 41 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/camera/CameraConfigurationManager.java deleted
100644 → 0
| 1 | -/* | |
| 2 | - * Copyright (C) 2010 ZXing authors | |
| 3 | - * | |
| 4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | - * you may not use this file except in compliance with the License. | |
| 6 | - * You may obtain a copy of the License at | |
| 7 | - * | |
| 8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | - * | |
| 10 | - * Unless required by applicable law or agreed to in writing, software | |
| 11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | - * See the License for the specific language governing permissions and | |
| 14 | - * limitations under the License. | |
| 15 | - */ | |
| 16 | - | |
| 17 | -package com.uuzuche.lib_zxing.camera; | |
| 18 | - | |
| 19 | -import android.content.Context; | |
| 20 | -import android.graphics.Point; | |
| 21 | -import android.hardware.Camera; | |
| 22 | -import android.os.Build; | |
| 23 | -import android.util.Log; | |
| 24 | -import android.view.Display; | |
| 25 | -import android.view.WindowManager; | |
| 26 | - | |
| 27 | -import java.util.regex.Pattern; | |
| 28 | - | |
| 29 | -final class CameraConfigurationManager { | |
| 30 | - | |
| 31 | - private static final String TAG = CameraConfigurationManager.class.getSimpleName(); | |
| 32 | - | |
| 33 | - private static final int TEN_DESIRED_ZOOM = 27; | |
| 34 | - private static final int DESIRED_SHARPNESS = 30; | |
| 35 | - | |
| 36 | - private static final Pattern COMMA_PATTERN = Pattern.compile(","); | |
| 37 | - | |
| 38 | - private final Context context; | |
| 39 | - private Point screenResolution; | |
| 40 | - private Point cameraResolution; | |
| 41 | - private int previewFormat; | |
| 42 | - private String previewFormatString; | |
| 43 | - | |
| 44 | - CameraConfigurationManager(Context context) { | |
| 45 | - this.context = context; | |
| 46 | - } | |
| 47 | - | |
| 48 | - /** | |
| 49 | - * Reads, one time, values from the camera that are needed by the app. | |
| 50 | - */ | |
| 51 | - void initFromCameraParameters(Camera camera) { | |
| 52 | - Camera.Parameters parameters = camera.getParameters(); | |
| 53 | - previewFormat = parameters.getPreviewFormat(); | |
| 54 | - previewFormatString = parameters.get("preview-format"); | |
| 55 | - Log.d(TAG, "Default preview format: " + previewFormat + '/' + previewFormatString); | |
| 56 | - WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); | |
| 57 | - Display display = manager.getDefaultDisplay(); | |
| 58 | - screenResolution = new Point(display.getWidth(), display.getHeight()); | |
| 59 | - Log.d(TAG, "Screen resolution: " + screenResolution); | |
| 60 | - | |
| 61 | - Point screenResolutionForCamera = new Point(); | |
| 62 | - screenResolutionForCamera.x = screenResolution.x; | |
| 63 | - screenResolutionForCamera.y = screenResolution.y; | |
| 64 | - // preview size is always something like 480*320, other 320*480 | |
| 65 | - if (screenResolution.x < screenResolution.y) { | |
| 66 | - screenResolutionForCamera.x = screenResolution.y; | |
| 67 | - screenResolutionForCamera.y = screenResolution.x; | |
| 68 | - } | |
| 69 | - Log.i("#########", "screenX:" + screenResolutionForCamera.x + " screenY:" + screenResolutionForCamera.y); | |
| 70 | - cameraResolution = getCameraResolution(parameters, screenResolutionForCamera); | |
| 71 | - | |
| 72 | - // cameraResolution = getCameraResolution(parameters, screenResolution); | |
| 73 | - Log.d(TAG, "Camera resolution: " + screenResolution); | |
| 74 | - } | |
| 75 | - | |
| 76 | - /** | |
| 77 | - * Sets the camera up to take preview images which are used for both preview and decoding. | |
| 78 | - * We detect the preview format here so that buildLuminanceSource() can build an appropriate | |
| 79 | - * LuminanceSource subclass. In the future we may want to force YUV420SP as it's the smallest, | |
| 80 | - * and the planar Y can be used for barcode scanning without a copy in some cases. | |
| 81 | - */ | |
| 82 | - void setDesiredCameraParameters(Camera camera) { | |
| 83 | - Camera.Parameters parameters = camera.getParameters(); | |
| 84 | - Log.d(TAG, "Setting preview size: " + cameraResolution); | |
| 85 | - parameters.setPreviewSize(cameraResolution.x, cameraResolution.y); | |
| 86 | - setFlash(parameters); | |
| 87 | - setZoom(parameters); | |
| 88 | - //setSharpness(parameters); | |
| 89 | - //modify here | |
| 90 | - camera.setDisplayOrientation(90); | |
| 91 | - camera.setParameters(parameters); | |
| 92 | - } | |
| 93 | - | |
| 94 | - Point getCameraResolution() { | |
| 95 | - return cameraResolution; | |
| 96 | - } | |
| 97 | - | |
| 98 | - Point getScreenResolution() { | |
| 99 | - return screenResolution; | |
| 100 | - } | |
| 101 | - | |
| 102 | - int getPreviewFormat() { | |
| 103 | - return previewFormat; | |
| 104 | - } | |
| 105 | - | |
| 106 | - String getPreviewFormatString() { | |
| 107 | - return previewFormatString; | |
| 108 | - } | |
| 109 | - | |
| 110 | - private static Point getCameraResolution(Camera.Parameters parameters, Point screenResolution) { | |
| 111 | - | |
| 112 | - String previewSizeValueString = parameters.get("preview-size-values"); | |
| 113 | - // saw this on Xperia | |
| 114 | - if (previewSizeValueString == null) { | |
| 115 | - previewSizeValueString = parameters.get("preview-size-value"); | |
| 116 | - } | |
| 117 | - | |
| 118 | - Point cameraResolution = null; | |
| 119 | - | |
| 120 | - if (previewSizeValueString != null) { | |
| 121 | - Log.d(TAG, "preview-size-values parameter: " + previewSizeValueString); | |
| 122 | - cameraResolution = findBestPreviewSizeValue(previewSizeValueString, screenResolution); | |
| 123 | - } | |
| 124 | - | |
| 125 | - if (cameraResolution == null) { | |
| 126 | - // Ensure that the camera resolution is a multiple of 8, as the screen may not be. | |
| 127 | - cameraResolution = new Point( | |
| 128 | - (screenResolution.x >> 3) << 3, | |
| 129 | - (screenResolution.y >> 3) << 3); | |
| 130 | - } | |
| 131 | - | |
| 132 | - return cameraResolution; | |
| 133 | - } | |
| 134 | - | |
| 135 | - private static Point findBestPreviewSizeValue(CharSequence previewSizeValueString, Point screenResolution) { | |
| 136 | - int bestX = 0; | |
| 137 | - int bestY = 0; | |
| 138 | - int diff = Integer.MAX_VALUE; | |
| 139 | - for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) { | |
| 140 | - | |
| 141 | - previewSize = previewSize.trim(); | |
| 142 | - int dimPosition = previewSize.indexOf('x'); | |
| 143 | - if (dimPosition < 0) { | |
| 144 | - Log.w(TAG, "Bad preview-size: " + previewSize); | |
| 145 | - continue; | |
| 146 | - } | |
| 147 | - | |
| 148 | - int newX; | |
| 149 | - int newY; | |
| 150 | - try { | |
| 151 | - newX = Integer.parseInt(previewSize.substring(0, dimPosition)); | |
| 152 | - newY = Integer.parseInt(previewSize.substring(dimPosition + 1)); | |
| 153 | - } catch (NumberFormatException nfe) { | |
| 154 | - Log.w(TAG, "Bad preview-size: " + previewSize); | |
| 155 | - continue; | |
| 156 | - } | |
| 157 | - | |
| 158 | - int newDiff = Math.abs(newX - screenResolution.x) + Math.abs(newY - screenResolution.y); | |
| 159 | - if (newDiff == 0) { | |
| 160 | - bestX = newX; | |
| 161 | - bestY = newY; | |
| 162 | - break; | |
| 163 | - } else if (newDiff < diff) { | |
| 164 | - bestX = newX; | |
| 165 | - bestY = newY; | |
| 166 | - diff = newDiff; | |
| 167 | - } | |
| 168 | - | |
| 169 | - } | |
| 170 | - | |
| 171 | - if (bestX > 0 && bestY > 0) { | |
| 172 | - return new Point(bestX, bestY); | |
| 173 | - } | |
| 174 | - return null; | |
| 175 | - } | |
| 176 | - | |
| 177 | - private static int findBestMotZoomValue(CharSequence stringValues, int tenDesiredZoom) { | |
| 178 | - int tenBestValue = 0; | |
| 179 | - for (String stringValue : COMMA_PATTERN.split(stringValues)) { | |
| 180 | - stringValue = stringValue.trim(); | |
| 181 | - double value; | |
| 182 | - try { | |
| 183 | - value = Double.parseDouble(stringValue); | |
| 184 | - } catch (NumberFormatException nfe) { | |
| 185 | - return tenDesiredZoom; | |
| 186 | - } | |
| 187 | - int tenValue = (int) (10.0 * value); | |
| 188 | - if (Math.abs(tenDesiredZoom - value) < Math.abs(tenDesiredZoom - tenBestValue)) { | |
| 189 | - tenBestValue = tenValue; | |
| 190 | - } | |
| 191 | - } | |
| 192 | - return tenBestValue; | |
| 193 | - } | |
| 194 | - | |
| 195 | - private void setFlash(Camera.Parameters parameters) { | |
| 196 | - // FIXME: This is a hack to turn the flash off on the Samsung Galaxy. | |
| 197 | - // And this is a hack-hack to work around a different value on the Behold II | |
| 198 | - // Restrict Behold II check to Cupcake, per Samsung's advice | |
| 199 | - //if (Build.MODEL.contains("Behold II") && | |
| 200 | - // CameraManager.SDK_INT == Build.VERSION_CODES.CUPCAKE) { | |
| 201 | - if (Build.MODEL.contains("Behold II") && CameraManager.SDK_INT == 3) { // 3 = Cupcake | |
| 202 | - parameters.set("flash-value", 1); | |
| 203 | - } else { | |
| 204 | - parameters.set("flash-value", 2); | |
| 205 | - } | |
| 206 | - // This is the standard setting to turn the flash off that all devices should honor. | |
| 207 | - parameters.set("flash-mode", "off"); | |
| 208 | - } | |
| 209 | - | |
| 210 | - private void setZoom(Camera.Parameters parameters) { | |
| 211 | - | |
| 212 | - String zoomSupportedString = parameters.get("zoom-supported"); | |
| 213 | - if (zoomSupportedString != null && !Boolean.parseBoolean(zoomSupportedString)) { | |
| 214 | - return; | |
| 215 | - } | |
| 216 | - | |
| 217 | - int tenDesiredZoom = TEN_DESIRED_ZOOM; | |
| 218 | - | |
| 219 | - String maxZoomString = parameters.get("max-zoom"); | |
| 220 | - if (maxZoomString != null) { | |
| 221 | - try { | |
| 222 | - int tenMaxZoom = (int) (10.0 * Double.parseDouble(maxZoomString)); | |
| 223 | - if (tenDesiredZoom > tenMaxZoom) { | |
| 224 | - tenDesiredZoom = tenMaxZoom; | |
| 225 | - } | |
| 226 | - } catch (NumberFormatException nfe) { | |
| 227 | - Log.w(TAG, "Bad max-zoom: " + maxZoomString); | |
| 228 | - } | |
| 229 | - } | |
| 230 | - | |
| 231 | - String takingPictureZoomMaxString = parameters.get("taking-picture-zoom-max"); | |
| 232 | - if (takingPictureZoomMaxString != null) { | |
| 233 | - try { | |
| 234 | - int tenMaxZoom = Integer.parseInt(takingPictureZoomMaxString); | |
| 235 | - if (tenDesiredZoom > tenMaxZoom) { | |
| 236 | - tenDesiredZoom = tenMaxZoom; | |
| 237 | - } | |
| 238 | - } catch (NumberFormatException nfe) { | |
| 239 | - Log.w(TAG, "Bad taking-picture-zoom-max: " + takingPictureZoomMaxString); | |
| 240 | - } | |
| 241 | - } | |
| 242 | - | |
| 243 | - String motZoomValuesString = parameters.get("mot-zoom-values"); | |
| 244 | - if (motZoomValuesString != null) { | |
| 245 | - tenDesiredZoom = findBestMotZoomValue(motZoomValuesString, tenDesiredZoom); | |
| 246 | - } | |
| 247 | - | |
| 248 | - String motZoomStepString = parameters.get("mot-zoom-step"); | |
| 249 | - if (motZoomStepString != null) { | |
| 250 | - try { | |
| 251 | - double motZoomStep = Double.parseDouble(motZoomStepString.trim()); | |
| 252 | - int tenZoomStep = (int) (10.0 * motZoomStep); | |
| 253 | - if (tenZoomStep > 1) { | |
| 254 | - tenDesiredZoom -= tenDesiredZoom % tenZoomStep; | |
| 255 | - } | |
| 256 | - } catch (NumberFormatException nfe) { | |
| 257 | - // continue | |
| 258 | - } | |
| 259 | - } | |
| 260 | - | |
| 261 | - // Set zoom. This helps encourage the user to pull back. | |
| 262 | - // Some devices like the Behold have a zoom parameter | |
| 263 | - if (maxZoomString != null || motZoomValuesString != null) { | |
| 264 | - parameters.set("zoom", String.valueOf(tenDesiredZoom / 10.0)); | |
| 265 | - } | |
| 266 | - | |
| 267 | - // Most devices, like the Hero, appear to expose this zoom parameter. | |
| 268 | - // It takes on values like "27" which appears to mean 2.7x zoom | |
| 269 | - if (takingPictureZoomMaxString != null) { | |
| 270 | - parameters.set("taking-picture-zoom", tenDesiredZoom); | |
| 271 | - } | |
| 272 | - } | |
| 273 | - | |
| 274 | - public static int getDesiredSharpness() { | |
| 275 | - return DESIRED_SHARPNESS; | |
| 276 | - } | |
| 277 | - | |
| 278 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/camera/CameraManager.java deleted
100644 → 0
| 1 | -/* | |
| 2 | - * Copyright (C) 2008 ZXing authors | |
| 3 | - * | |
| 4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | - * you may not use this file except in compliance with the License. | |
| 6 | - * You may obtain a copy of the License at | |
| 7 | - * | |
| 8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | - * | |
| 10 | - * Unless required by applicable law or agreed to in writing, software | |
| 11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | - * See the License for the specific language governing permissions and | |
| 14 | - * limitations under the License. | |
| 15 | - */ | |
| 16 | - | |
| 17 | -package com.uuzuche.lib_zxing.camera; | |
| 18 | - | |
| 19 | -import android.content.Context; | |
| 20 | -import android.graphics.PixelFormat; | |
| 21 | -import android.graphics.Point; | |
| 22 | -import android.graphics.Rect; | |
| 23 | -import android.hardware.Camera; | |
| 24 | -import android.os.Build; | |
| 25 | -import android.os.Handler; | |
| 26 | -import android.view.SurfaceHolder; | |
| 27 | - | |
| 28 | -import java.io.IOException; | |
| 29 | - | |
| 30 | -/** | |
| 31 | - * This object wraps the Camera service object and expects to be the only one talking to it. The | |
| 32 | - * implementation encapsulates the steps needed to take preview-sized images, which are used for | |
| 33 | - * both preview and decoding. | |
| 34 | - */ | |
| 35 | -public final class CameraManager { | |
| 36 | - | |
| 37 | - private static final String TAG = CameraManager.class.getSimpleName(); | |
| 38 | - | |
| 39 | - public static int FRAME_WIDTH = -1; | |
| 40 | - public static int FRAME_HEIGHT = -1; | |
| 41 | - public static int FRAME_MARGINTOP = -1; | |
| 42 | - | |
| 43 | - private static CameraManager cameraManager; | |
| 44 | - | |
| 45 | - static final int SDK_INT; // Later we can use Build.VERSION.SDK_INT | |
| 46 | - | |
| 47 | - static { | |
| 48 | - int sdkInt; | |
| 49 | - try { | |
| 50 | - sdkInt = Integer.parseInt(Build.VERSION.SDK); | |
| 51 | - } catch (NumberFormatException nfe) { | |
| 52 | - // Just to be safe | |
| 53 | - sdkInt = 10000; | |
| 54 | - } | |
| 55 | - SDK_INT = sdkInt; | |
| 56 | - } | |
| 57 | - | |
| 58 | - private final Context context; | |
| 59 | - private final CameraConfigurationManager configManager; | |
| 60 | - private Camera camera; | |
| 61 | - private Rect framingRect; | |
| 62 | - private Rect framingRectInPreview; | |
| 63 | - private boolean initialized; | |
| 64 | - private boolean previewing; | |
| 65 | - private final boolean useOneShotPreviewCallback; | |
| 66 | - /** | |
| 67 | - * Preview frames are delivered here, which we pass on to the registered handler. Make sure to | |
| 68 | - * clear the handler so it will only receive one message. | |
| 69 | - */ | |
| 70 | - private final PreviewCallback previewCallback; | |
| 71 | - /** | |
| 72 | - * Autofocus callbacks arrive here, and are dispatched to the Handler which requested them. | |
| 73 | - */ | |
| 74 | - private final AutoFocusCallback autoFocusCallback; | |
| 75 | - | |
| 76 | - /** | |
| 77 | - * Initializes this static object with the Context of the calling Activity. | |
| 78 | - * | |
| 79 | - * @param context The Activity which wants to use the camera. | |
| 80 | - */ | |
| 81 | - public static void init(Context context) { | |
| 82 | - if (cameraManager == null) { | |
| 83 | - cameraManager = new CameraManager(context); | |
| 84 | - } | |
| 85 | - } | |
| 86 | - | |
| 87 | - /** | |
| 88 | - * Gets the CameraManager singleton instance. | |
| 89 | - * | |
| 90 | - * @return A reference to the CameraManager singleton. | |
| 91 | - */ | |
| 92 | - public static CameraManager get() { | |
| 93 | - return cameraManager; | |
| 94 | - } | |
| 95 | - | |
| 96 | - private CameraManager(Context context) { | |
| 97 | - | |
| 98 | - this.context = context; | |
| 99 | - this.configManager = new CameraConfigurationManager(context); | |
| 100 | - | |
| 101 | - // Camera.setOneShotPreviewCallback() has a race condition in Cupcake, so we use the older | |
| 102 | - // Camera.setPreviewCallback() on 1.5 and earlier. For Donut and later, we need to use | |
| 103 | - // the more efficient one shot callback, as the older one can swamp the system and cause it | |
| 104 | - // to run out of memory. We can't use SDK_INT because it was introduced in the Donut SDK. | |
| 105 | - //useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > Build.VERSION_CODES.CUPCAKE; | |
| 106 | - useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > 3; // 3 = Cupcake | |
| 107 | - | |
| 108 | - previewCallback = new PreviewCallback(configManager, useOneShotPreviewCallback); | |
| 109 | - autoFocusCallback = new AutoFocusCallback(); | |
| 110 | - } | |
| 111 | - | |
| 112 | - /** | |
| 113 | - * Opens the camera driver and initializes the hardware parameters. | |
| 114 | - * | |
| 115 | - * @param holder The surface object which the camera will draw preview frames into. | |
| 116 | - * @throws IOException Indicates the camera driver failed to open. | |
| 117 | - */ | |
| 118 | - public void openDriver(SurfaceHolder holder) throws IOException { | |
| 119 | - if (camera == null) { | |
| 120 | - camera = Camera.open(); | |
| 121 | - if (camera == null) { | |
| 122 | - throw new IOException(); | |
| 123 | - } | |
| 124 | - camera.setPreviewDisplay(holder); | |
| 125 | - | |
| 126 | - if (!initialized) { | |
| 127 | - initialized = true; | |
| 128 | - configManager.initFromCameraParameters(camera); | |
| 129 | - } | |
| 130 | - configManager.setDesiredCameraParameters(camera); | |
| 131 | - | |
| 132 | - //FIXME | |
| 133 | - // SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); | |
| 134 | - //�Ƿ�ʹ��ǰ�� | |
| 135 | -// if (prefs.getBoolean(PreferencesActivity.KEY_FRONT_LIGHT, false)) { | |
| 136 | -// FlashlightManager.enableFlashlight(); | |
| 137 | -// } | |
| 138 | - FlashlightManager.enableFlashlight(); | |
| 139 | - } | |
| 140 | - } | |
| 141 | - | |
| 142 | - /** | |
| 143 | - * Closes the camera driver if still in use. | |
| 144 | - */ | |
| 145 | - public void closeDriver() { | |
| 146 | - if (camera != null) { | |
| 147 | - FlashlightManager.disableFlashlight(); | |
| 148 | - camera.release(); | |
| 149 | - camera = null; | |
| 150 | - } | |
| 151 | - } | |
| 152 | - | |
| 153 | - /** | |
| 154 | - * Asks the camera hardware to begin drawing preview frames to the screen. | |
| 155 | - */ | |
| 156 | - public void startPreview() { | |
| 157 | - if (camera != null && !previewing) { | |
| 158 | - camera.startPreview(); | |
| 159 | - previewing = true; | |
| 160 | - } | |
| 161 | - } | |
| 162 | - | |
| 163 | - /** | |
| 164 | - * Tells the camera to stop drawing preview frames. | |
| 165 | - */ | |
| 166 | - public void stopPreview() { | |
| 167 | - if (camera != null && previewing) { | |
| 168 | - if (!useOneShotPreviewCallback) { | |
| 169 | - camera.setPreviewCallback(null); | |
| 170 | - } | |
| 171 | - camera.stopPreview(); | |
| 172 | - previewCallback.setHandler(null, 0); | |
| 173 | - autoFocusCallback.setHandler(null, 0); | |
| 174 | - previewing = false; | |
| 175 | - } | |
| 176 | - } | |
| 177 | - | |
| 178 | - /** | |
| 179 | - * A single preview frame will be returned to the handler supplied. The data will arrive as byte[] | |
| 180 | - * in the message.obj field, with width and height encoded as message.arg1 and message.arg2, | |
| 181 | - * respectively. | |
| 182 | - * | |
| 183 | - * @param handler The handler to send the message to. | |
| 184 | - * @param message The what field of the message to be sent. | |
| 185 | - */ | |
| 186 | - public void requestPreviewFrame(Handler handler, int message) { | |
| 187 | - if (camera != null && previewing) { | |
| 188 | - previewCallback.setHandler(handler, message); | |
| 189 | - if (useOneShotPreviewCallback) { | |
| 190 | - camera.setOneShotPreviewCallback(previewCallback); | |
| 191 | - } else { | |
| 192 | - camera.setPreviewCallback(previewCallback); | |
| 193 | - } | |
| 194 | - } | |
| 195 | - } | |
| 196 | - | |
| 197 | - /** | |
| 198 | - * Asks the camera hardware to perform an autofocus. | |
| 199 | - * | |
| 200 | - * @param handler The Handler to notify when the autofocus completes. | |
| 201 | - * @param message The message to deliver. | |
| 202 | - */ | |
| 203 | - public void requestAutoFocus(Handler handler, int message) { | |
| 204 | - if (camera != null && previewing) { | |
| 205 | - autoFocusCallback.setHandler(handler, message); | |
| 206 | - //Log.d(TAG, "Requesting auto-focus callback"); | |
| 207 | - camera.autoFocus(autoFocusCallback); | |
| 208 | - } | |
| 209 | - } | |
| 210 | - | |
| 211 | - /** | |
| 212 | - * Calculates the framing rect which the UI should draw to show the user where to place the | |
| 213 | - * barcode. This target helps with alignment as well as forces the user to hold the device | |
| 214 | - * far enough away to ensure the image will be in focus. | |
| 215 | - * | |
| 216 | - * @return The rectangle to draw on screen in window coordinates. | |
| 217 | - */ | |
| 218 | - public Rect getFramingRect() { | |
| 219 | - try { | |
| 220 | - Point screenResolution = configManager.getScreenResolution(); | |
| 221 | - // if (framingRect == null) { | |
| 222 | - if (camera == null) { | |
| 223 | - return null; | |
| 224 | - } | |
| 225 | - | |
| 226 | - int leftOffset = (screenResolution.x - FRAME_WIDTH) / 2; | |
| 227 | - | |
| 228 | - int topOffset; | |
| 229 | - if (FRAME_MARGINTOP != -1) { | |
| 230 | - topOffset = FRAME_MARGINTOP; | |
| 231 | - } else { | |
| 232 | - topOffset = (screenResolution.y - FRAME_HEIGHT) / 2; | |
| 233 | - } | |
| 234 | - framingRect = new Rect(leftOffset, topOffset, leftOffset + FRAME_WIDTH, topOffset + FRAME_HEIGHT); | |
| 235 | - // } | |
| 236 | - return framingRect; | |
| 237 | - } catch (Exception e) { | |
| 238 | - e.printStackTrace(); | |
| 239 | - return null; | |
| 240 | - } | |
| 241 | - } | |
| 242 | - | |
| 243 | - /** | |
| 244 | - * Like {@link #getFramingRect} but coordinates are in terms of the preview frame, | |
| 245 | - * not UI / screen. | |
| 246 | - */ | |
| 247 | - public Rect getFramingRectInPreview() { | |
| 248 | - if (framingRectInPreview == null) { | |
| 249 | - Rect rect = new Rect(getFramingRect()); | |
| 250 | - Point cameraResolution = configManager.getCameraResolution(); | |
| 251 | - Point screenResolution = configManager.getScreenResolution(); | |
| 252 | - //modify here | |
| 253 | -// rect.left = rect.left * cameraResolution.x / screenResolution.x; | |
| 254 | -// rect.right = rect.right * cameraResolution.x / screenResolution.x; | |
| 255 | -// rect.top = rect.top * cameraResolution.y / screenResolution.y; | |
| 256 | -// rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y; | |
| 257 | - rect.left = rect.left * cameraResolution.y / screenResolution.x; | |
| 258 | - rect.right = rect.right * cameraResolution.y / screenResolution.x; | |
| 259 | - rect.top = rect.top * cameraResolution.x / screenResolution.y; | |
| 260 | - rect.bottom = rect.bottom * cameraResolution.x / screenResolution.y; | |
| 261 | - framingRectInPreview = rect; | |
| 262 | - } | |
| 263 | - return framingRectInPreview; | |
| 264 | - } | |
| 265 | - | |
| 266 | - /** | |
| 267 | - * Converts the result points from still resolution coordinates to screen coordinates. | |
| 268 | - * | |
| 269 | - * @param points The points returned by the Reader subclass through Result.getResultPoints(). | |
| 270 | - * @return An array of Points scaled to the size of the framing rect and offset appropriately | |
| 271 | - * so they can be drawn in screen coordinates. | |
| 272 | - */ | |
| 273 | - /* | |
| 274 | - public Point[] convertResultPoints(ResultPoint[] points) { | |
| 275 | - Rect frame = getFramingRectInPreview(); | |
| 276 | - int count = points.length; | |
| 277 | - Point[] output = new Point[count]; | |
| 278 | - for (int x = 0; x < count; x++) { | |
| 279 | - output[x] = new Point(); | |
| 280 | - output[x].x = frame.left + (int) (points[x].getX() + 0.5f); | |
| 281 | - output[x].y = frame.top + (int) (points[x].getY() + 0.5f); | |
| 282 | - } | |
| 283 | - return output; | |
| 284 | - } | |
| 285 | - */ | |
| 286 | - | |
| 287 | - /** | |
| 288 | - * A factory method to build the appropriate LuminanceSource object based on the format | |
| 289 | - * of the preview buffers, as described by Camera.Parameters. | |
| 290 | - * | |
| 291 | - * @param data A preview frame. | |
| 292 | - * @param width The width of the image. | |
| 293 | - * @param height The height of the image. | |
| 294 | - * @return A PlanarYUVLuminanceSource instance. | |
| 295 | - */ | |
| 296 | - public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) { | |
| 297 | - Rect rect = getFramingRectInPreview(); | |
| 298 | - int previewFormat = configManager.getPreviewFormat(); | |
| 299 | - String previewFormatString = configManager.getPreviewFormatString(); | |
| 300 | - switch (previewFormat) { | |
| 301 | - // This is the standard Android format which all devices are REQUIRED to support. | |
| 302 | - // In theory, it's the only one we should ever care about. | |
| 303 | - case PixelFormat.YCbCr_420_SP: | |
| 304 | - // This format has never been seen in the wild, but is compatible as we only care | |
| 305 | - // about the Y channel, so allow it. | |
| 306 | - case PixelFormat.YCbCr_422_SP: | |
| 307 | - return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, | |
| 308 | - rect.width(), rect.height()); | |
| 309 | - default: | |
| 310 | - // The Samsung Moment incorrectly uses this variant instead of the 'sp' version. | |
| 311 | - // Fortunately, it too has all the Y data up front, so we can read it. | |
| 312 | - if ("yuv420p".equals(previewFormatString)) { | |
| 313 | - return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top, | |
| 314 | - rect.width(), rect.height()); | |
| 315 | - } | |
| 316 | - } | |
| 317 | - throw new IllegalArgumentException("Unsupported picture format: " + | |
| 318 | - previewFormat + '/' + previewFormatString); | |
| 319 | - } | |
| 320 | - | |
| 321 | - public Context getContext() { | |
| 322 | - return context; | |
| 323 | - } | |
| 324 | - | |
| 325 | - public Camera getCamera() { | |
| 326 | - return camera; | |
| 327 | - } | |
| 328 | - | |
| 329 | - public boolean isPreviewing() { | |
| 330 | - return previewing; | |
| 331 | - } | |
| 332 | - | |
| 333 | - public boolean isUseOneShotPreviewCallback() { | |
| 334 | - return useOneShotPreviewCallback; | |
| 335 | - } | |
| 336 | - | |
| 337 | - public PreviewCallback getPreviewCallback() { | |
| 338 | - return previewCallback; | |
| 339 | - } | |
| 340 | - | |
| 341 | - public AutoFocusCallback getAutoFocusCallback() { | |
| 342 | - return autoFocusCallback; | |
| 343 | - } | |
| 344 | - | |
| 345 | - public void setPreviewing(boolean previewing) { | |
| 346 | - this.previewing = previewing; | |
| 347 | - } | |
| 348 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/camera/FlashlightManager.java deleted
100644 → 0
| 1 | -/* | |
| 2 | - * Copyright (C) 2010 ZXing authors | |
| 3 | - * | |
| 4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | - * you may not use this file except in compliance with the License. | |
| 6 | - * You may obtain a copy of the License at | |
| 7 | - * | |
| 8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | - * | |
| 10 | - * Unless required by applicable law or agreed to in writing, software | |
| 11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | - * See the License for the specific language governing permissions and | |
| 14 | - * limitations under the License. | |
| 15 | - */ | |
| 16 | - | |
| 17 | -package com.uuzuche.lib_zxing.camera; | |
| 18 | - | |
| 19 | -import android.os.IBinder; | |
| 20 | -import android.util.Log; | |
| 21 | - | |
| 22 | -import java.lang.reflect.InvocationTargetException; | |
| 23 | -import java.lang.reflect.Method; | |
| 24 | - | |
| 25 | -/** | |
| 26 | - * This class is used to activate the weak light on some camera phones (not flash) | |
| 27 | - * in order to illuminate surfaces for scanning. There is no official way to do this, | |
| 28 | - * but, classes which allow access to this function still exist on some devices. | |
| 29 | - * This therefore proceeds through a great deal of reflection. | |
| 30 | - * <p> | |
| 31 | - * See <a href="http://almondmendoza.com/2009/01/05/changing-the-screen-brightness-programatically/"> | |
| 32 | - * http://almondmendoza.com/2009/01/05/changing-the-screen-brightness-programatically/</a> and | |
| 33 | - * <a href="http://code.google.com/p/droidled/source/browse/trunk/src/com/droidled/demo/DroidLED.java"> | |
| 34 | - * http://code.google.com/p/droidled/source/browse/trunk/src/com/droidled/demo/DroidLED.java</a>. | |
| 35 | - * Thanks to Ryan Alford for pointing out the availability of this class. | |
| 36 | - */ | |
| 37 | -final class FlashlightManager { | |
| 38 | - | |
| 39 | - private static final String TAG = FlashlightManager.class.getSimpleName(); | |
| 40 | - | |
| 41 | - private static final Object iHardwareService; | |
| 42 | - private static final Method setFlashEnabledMethod; | |
| 43 | - | |
| 44 | - static { | |
| 45 | - iHardwareService = getHardwareService(); | |
| 46 | - setFlashEnabledMethod = getSetFlashEnabledMethod(iHardwareService); | |
| 47 | - if (iHardwareService == null) { | |
| 48 | - Log.v(TAG, "This device does supports control of a flashlight"); | |
| 49 | - } else { | |
| 50 | - Log.v(TAG, "This device does not support control of a flashlight"); | |
| 51 | - } | |
| 52 | - } | |
| 53 | - | |
| 54 | - private FlashlightManager() { | |
| 55 | - } | |
| 56 | - | |
| 57 | - /** | |
| 58 | - * �����������ƿ��� | |
| 59 | - */ | |
| 60 | - //FIXME | |
| 61 | - static void enableFlashlight() { | |
| 62 | - setFlashlight(false); | |
| 63 | - } | |
| 64 | - | |
| 65 | - static void disableFlashlight() { | |
| 66 | - setFlashlight(false); | |
| 67 | - } | |
| 68 | - | |
| 69 | - private static Object getHardwareService() { | |
| 70 | - Class<?> serviceManagerClass = maybeForName("android.os.ServiceManager"); | |
| 71 | - if (serviceManagerClass == null) { | |
| 72 | - return null; | |
| 73 | - } | |
| 74 | - | |
| 75 | - Method getServiceMethod = maybeGetMethod(serviceManagerClass, "getService", String.class); | |
| 76 | - if (getServiceMethod == null) { | |
| 77 | - return null; | |
| 78 | - } | |
| 79 | - | |
| 80 | - Object hardwareService = invoke(getServiceMethod, null, "hardware"); | |
| 81 | - if (hardwareService == null) { | |
| 82 | - return null; | |
| 83 | - } | |
| 84 | - | |
| 85 | - Class<?> iHardwareServiceStubClass = maybeForName("android.os.IHardwareService$Stub"); | |
| 86 | - if (iHardwareServiceStubClass == null) { | |
| 87 | - return null; | |
| 88 | - } | |
| 89 | - | |
| 90 | - Method asInterfaceMethod = maybeGetMethod(iHardwareServiceStubClass, "asInterface", IBinder.class); | |
| 91 | - if (asInterfaceMethod == null) { | |
| 92 | - return null; | |
| 93 | - } | |
| 94 | - | |
| 95 | - return invoke(asInterfaceMethod, null, hardwareService); | |
| 96 | - } | |
| 97 | - | |
| 98 | - private static Method getSetFlashEnabledMethod(Object iHardwareService) { | |
| 99 | - if (iHardwareService == null) { | |
| 100 | - return null; | |
| 101 | - } | |
| 102 | - Class<?> proxyClass = iHardwareService.getClass(); | |
| 103 | - return maybeGetMethod(proxyClass, "setFlashlightEnabled", boolean.class); | |
| 104 | - } | |
| 105 | - | |
| 106 | - private static Class<?> maybeForName(String name) { | |
| 107 | - try { | |
| 108 | - return Class.forName(name); | |
| 109 | - } catch (ClassNotFoundException cnfe) { | |
| 110 | - // OK | |
| 111 | - return null; | |
| 112 | - } catch (RuntimeException re) { | |
| 113 | - Log.w(TAG, "Unexpected error while finding class " + name, re); | |
| 114 | - return null; | |
| 115 | - } | |
| 116 | - } | |
| 117 | - | |
| 118 | - private static Method maybeGetMethod(Class<?> clazz, String name, Class<?>... argClasses) { | |
| 119 | - try { | |
| 120 | - return clazz.getMethod(name, argClasses); | |
| 121 | - } catch (NoSuchMethodException nsme) { | |
| 122 | - // OK | |
| 123 | - return null; | |
| 124 | - } catch (RuntimeException re) { | |
| 125 | - Log.w(TAG, "Unexpected error while finding method " + name, re); | |
| 126 | - return null; | |
| 127 | - } | |
| 128 | - } | |
| 129 | - | |
| 130 | - private static Object invoke(Method method, Object instance, Object... args) { | |
| 131 | - try { | |
| 132 | - return method.invoke(instance, args); | |
| 133 | - } catch (IllegalAccessException e) { | |
| 134 | - Log.w(TAG, "Unexpected error while invoking " + method, e); | |
| 135 | - return null; | |
| 136 | - } catch (InvocationTargetException e) { | |
| 137 | - Log.w(TAG, "Unexpected error while invoking " + method, e.getCause()); | |
| 138 | - return null; | |
| 139 | - } catch (RuntimeException re) { | |
| 140 | - Log.w(TAG, "Unexpected error while invoking " + method, re); | |
| 141 | - return null; | |
| 142 | - } | |
| 143 | - } | |
| 144 | - | |
| 145 | - private static void setFlashlight(boolean active) { | |
| 146 | - if (iHardwareService != null) { | |
| 147 | - invoke(setFlashEnabledMethod, iHardwareService, active); | |
| 148 | - } | |
| 149 | - } | |
| 150 | - | |
| 151 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/camera/PlanarYUVLuminanceSource.java deleted
100644 → 0
| 1 | -/* | |
| 2 | - * Copyright 2009 ZXing authors | |
| 3 | - * | |
| 4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | - * you may not use this file except in compliance with the License. | |
| 6 | - * You may obtain a copy of the License at | |
| 7 | - * | |
| 8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | - * | |
| 10 | - * Unless required by applicable law or agreed to in writing, software | |
| 11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | - * See the License for the specific language governing permissions and | |
| 14 | - * limitations under the License. | |
| 15 | - */ | |
| 16 | - | |
| 17 | -package com.uuzuche.lib_zxing.camera; | |
| 18 | - | |
| 19 | -import android.graphics.Bitmap; | |
| 20 | - | |
| 21 | -import com.google.zxing.LuminanceSource; | |
| 22 | - | |
| 23 | -/** | |
| 24 | - * This object extends LuminanceSource around an array of YUV data returned from the camera driver, | |
| 25 | - * with the option to crop to a rectangle within the full data. This can be used to exclude | |
| 26 | - * superfluous pixels around the perimeter and speed up decoding. | |
| 27 | - * <p> | |
| 28 | - * It works for any pixel format where the Y channel is planar and appears first, including | |
| 29 | - * YCbCr_420_SP and YCbCr_422_SP. | |
| 30 | - * | |
| 31 | - * @author dswitkin@google.com (Daniel Switkin) | |
| 32 | - */ | |
| 33 | -public final class PlanarYUVLuminanceSource extends LuminanceSource { | |
| 34 | - private final byte[] yuvData; | |
| 35 | - private final int dataWidth; | |
| 36 | - private final int dataHeight; | |
| 37 | - private final int left; | |
| 38 | - private final int top; | |
| 39 | - | |
| 40 | - public PlanarYUVLuminanceSource(byte[] yuvData, int dataWidth, int dataHeight, int left, int top, | |
| 41 | - int width, int height) { | |
| 42 | - super(width, height); | |
| 43 | - | |
| 44 | - if (left + width > dataWidth || top + height > dataHeight) { | |
| 45 | - throw new IllegalArgumentException("Crop rectangle does not fit within image data."); | |
| 46 | - } | |
| 47 | - | |
| 48 | - this.yuvData = yuvData; | |
| 49 | - this.dataWidth = dataWidth; | |
| 50 | - this.dataHeight = dataHeight; | |
| 51 | - this.left = left; | |
| 52 | - this.top = top; | |
| 53 | - } | |
| 54 | - | |
| 55 | - @Override | |
| 56 | - public byte[] getRow(int y, byte[] row) { | |
| 57 | - if (y < 0 || y >= getHeight()) { | |
| 58 | - throw new IllegalArgumentException("Requested row is outside the image: " + y); | |
| 59 | - } | |
| 60 | - int width = getWidth(); | |
| 61 | - if (row == null || row.length < width) { | |
| 62 | - row = new byte[width]; | |
| 63 | - } | |
| 64 | - int offset = (y + top) * dataWidth + left; | |
| 65 | - System.arraycopy(yuvData, offset, row, 0, width); | |
| 66 | - return row; | |
| 67 | - } | |
| 68 | - | |
| 69 | - @Override | |
| 70 | - public byte[] getMatrix() { | |
| 71 | - int width = getWidth(); | |
| 72 | - int height = getHeight(); | |
| 73 | - | |
| 74 | - // If the caller asks for the entire underlying image, save the copy and give them the | |
| 75 | - // original data. The docs specifically warn that result.length must be ignored. | |
| 76 | - if (width == dataWidth && height == dataHeight) { | |
| 77 | - return yuvData; | |
| 78 | - } | |
| 79 | - | |
| 80 | - int area = width * height; | |
| 81 | - byte[] matrix = new byte[area]; | |
| 82 | - int inputOffset = top * dataWidth + left; | |
| 83 | - | |
| 84 | - // If the width matches the full width of the underlying data, perform a single copy. | |
| 85 | - if (width == dataWidth) { | |
| 86 | - System.arraycopy(yuvData, inputOffset, matrix, 0, area); | |
| 87 | - return matrix; | |
| 88 | - } | |
| 89 | - | |
| 90 | - // Otherwise copy one cropped row at a time. | |
| 91 | - byte[] yuv = yuvData; | |
| 92 | - for (int y = 0; y < height; y++) { | |
| 93 | - int outputOffset = y * width; | |
| 94 | - System.arraycopy(yuv, inputOffset, matrix, outputOffset, width); | |
| 95 | - inputOffset += dataWidth; | |
| 96 | - } | |
| 97 | - return matrix; | |
| 98 | - } | |
| 99 | - | |
| 100 | - @Override | |
| 101 | - public boolean isCropSupported() { | |
| 102 | - return true; | |
| 103 | - } | |
| 104 | - | |
| 105 | - public int getDataWidth() { | |
| 106 | - return dataWidth; | |
| 107 | - } | |
| 108 | - | |
| 109 | - public int getDataHeight() { | |
| 110 | - return dataHeight; | |
| 111 | - } | |
| 112 | - | |
| 113 | - public Bitmap renderCroppedGreyscaleBitmap() { | |
| 114 | - int width = getWidth(); | |
| 115 | - int height = getHeight(); | |
| 116 | - int[] pixels = new int[width * height]; | |
| 117 | - byte[] yuv = yuvData; | |
| 118 | - int inputOffset = top * dataWidth + left; | |
| 119 | - | |
| 120 | - for (int y = 0; y < height; y++) { | |
| 121 | - int outputOffset = y * width; | |
| 122 | - for (int x = 0; x < width; x++) { | |
| 123 | - int grey = yuv[inputOffset + x] & 0xff; | |
| 124 | - pixels[outputOffset + x] = 0xFF000000 | (grey * 0x00010101); | |
| 125 | - } | |
| 126 | - inputOffset += dataWidth; | |
| 127 | - } | |
| 128 | - | |
| 129 | - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); | |
| 130 | - bitmap.setPixels(pixels, 0, width, 0, 0, width, height); | |
| 131 | - return bitmap; | |
| 132 | - } | |
| 133 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/camera/PreviewCallback.java deleted
100644 → 0
| 1 | -/* | |
| 2 | - * Copyright (C) 2010 ZXing authors | |
| 3 | - * | |
| 4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | - * you may not use this file except in compliance with the License. | |
| 6 | - * You may obtain a copy of the License at | |
| 7 | - * | |
| 8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | - * | |
| 10 | - * Unless required by applicable law or agreed to in writing, software | |
| 11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | - * See the License for the specific language governing permissions and | |
| 14 | - * limitations under the License. | |
| 15 | - */ | |
| 16 | - | |
| 17 | -package com.uuzuche.lib_zxing.camera; | |
| 18 | - | |
| 19 | -import android.graphics.Point; | |
| 20 | -import android.hardware.Camera; | |
| 21 | -import android.os.Handler; | |
| 22 | -import android.os.Message; | |
| 23 | -import android.util.Log; | |
| 24 | - | |
| 25 | -public final class PreviewCallback implements Camera.PreviewCallback { | |
| 26 | - | |
| 27 | - private static final String TAG = PreviewCallback.class.getSimpleName(); | |
| 28 | - | |
| 29 | - private final CameraConfigurationManager configManager; | |
| 30 | - private final boolean useOneShotPreviewCallback; | |
| 31 | - private Handler previewHandler; | |
| 32 | - private int previewMessage; | |
| 33 | - | |
| 34 | - PreviewCallback(CameraConfigurationManager configManager, boolean useOneShotPreviewCallback) { | |
| 35 | - this.configManager = configManager; | |
| 36 | - this.useOneShotPreviewCallback = useOneShotPreviewCallback; | |
| 37 | - } | |
| 38 | - | |
| 39 | - public void setHandler(Handler previewHandler, int previewMessage) { | |
| 40 | - this.previewHandler = previewHandler; | |
| 41 | - this.previewMessage = previewMessage; | |
| 42 | - } | |
| 43 | - | |
| 44 | - public void onPreviewFrame(byte[] data, Camera camera) { | |
| 45 | - Point cameraResolution = configManager.getCameraResolution(); | |
| 46 | - if (!useOneShotPreviewCallback) { | |
| 47 | - camera.setPreviewCallback(null); | |
| 48 | - } | |
| 49 | - if (previewHandler != null) { | |
| 50 | - Message message = previewHandler.obtainMessage(previewMessage, cameraResolution.x, | |
| 51 | - cameraResolution.y, data); | |
| 52 | - message.sendToTarget(); | |
| 53 | - previewHandler = null; | |
| 54 | - } else { | |
| 55 | - Log.d(TAG, "Got preview callback, but no handler for it"); | |
| 56 | - } | |
| 57 | - } | |
| 58 | - | |
| 59 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/decoding/CaptureActivityHandler.java deleted
100644 → 0
| 1 | -/* | |
| 2 | - * Copyright (C) 2008 ZXing authors | |
| 3 | - * | |
| 4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | - * you may not use this file except in compliance with the License. | |
| 6 | - * You may obtain a copy of the License at | |
| 7 | - * | |
| 8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | - * | |
| 10 | - * Unless required by applicable law or agreed to in writing, software | |
| 11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | - * See the License for the specific language governing permissions and | |
| 14 | - * limitations under the License. | |
| 15 | - */ | |
| 16 | - | |
| 17 | -package com.uuzuche.lib_zxing.decoding; | |
| 18 | - | |
| 19 | -import android.app.Activity; | |
| 20 | -import android.content.Intent; | |
| 21 | -import android.graphics.Bitmap; | |
| 22 | -import android.net.Uri; | |
| 23 | -import android.os.Bundle; | |
| 24 | -import android.os.Handler; | |
| 25 | -import android.os.Message; | |
| 26 | -import android.util.Log; | |
| 27 | - | |
| 28 | -import com.cnlive.cloud.shanghaishop.R; | |
| 29 | -import com.google.zxing.BarcodeFormat; | |
| 30 | -import com.google.zxing.Result; | |
| 31 | -import com.uuzuche.lib_zxing.activity.CaptureFragment; | |
| 32 | -import com.uuzuche.lib_zxing.camera.CameraManager; | |
| 33 | -import com.uuzuche.lib_zxing.view.ViewfinderResultPointCallback; | |
| 34 | -import com.uuzuche.lib_zxing.view.ViewfinderView; | |
| 35 | - | |
| 36 | -import java.util.Vector; | |
| 37 | - | |
| 38 | -/** | |
| 39 | - * This class handles all the messaging which comprises the state machine for capture. | |
| 40 | - */ | |
| 41 | -public final class CaptureActivityHandler extends Handler { | |
| 42 | - | |
| 43 | - private static final String TAG = CaptureActivityHandler.class.getSimpleName(); | |
| 44 | - | |
| 45 | - private final CaptureFragment fragment; | |
| 46 | - private final DecodeThread decodeThread; | |
| 47 | - private State state; | |
| 48 | - | |
| 49 | - private enum State { | |
| 50 | - PREVIEW, | |
| 51 | - SUCCESS, | |
| 52 | - DONE | |
| 53 | - } | |
| 54 | - | |
| 55 | - public CaptureActivityHandler(CaptureFragment fragment, Vector<BarcodeFormat> decodeFormats, | |
| 56 | - String characterSet, ViewfinderView viewfinderView) { | |
| 57 | - this.fragment = fragment; | |
| 58 | - decodeThread = new DecodeThread(fragment, decodeFormats, characterSet, | |
| 59 | - new ViewfinderResultPointCallback(viewfinderView)); | |
| 60 | - decodeThread.start(); | |
| 61 | - state = State.SUCCESS; | |
| 62 | - // Start ourselves capturing previews and decoding. | |
| 63 | - CameraManager.get().startPreview(); | |
| 64 | - restartPreviewAndDecode(); | |
| 65 | - } | |
| 66 | - | |
| 67 | - @Override | |
| 68 | - public void handleMessage(Message message) { | |
| 69 | - if (message.what == R.id.auto_focus) { | |
| 70 | - //Log.d(TAG, "Got auto-focus message"); | |
| 71 | - // When one auto focus pass finishes, start another. This is the closest thing to | |
| 72 | - // continuous AF. It does seem to hunt a bit, but I'm not sure what else to do. | |
| 73 | - if (state == State.PREVIEW) { | |
| 74 | - CameraManager.get().requestAutoFocus(this, R.id.auto_focus); | |
| 75 | - } | |
| 76 | - } else if (message.what == R.id.restart_preview) { | |
| 77 | - Log.d(TAG, "Got restart preview message"); | |
| 78 | - restartPreviewAndDecode(); | |
| 79 | - } else if (message.what == R.id.decode_succeeded) { | |
| 80 | - Log.d(TAG, "Got decode succeeded message"); | |
| 81 | - state = State.SUCCESS; | |
| 82 | - Bundle bundle = message.getData(); | |
| 83 | - | |
| 84 | - /***********************************************************************/ | |
| 85 | - Bitmap barcode = bundle == null ? null : | |
| 86 | - (Bitmap) bundle.getParcelable(DecodeThread.BARCODE_BITMAP);//���ñ����߳� | |
| 87 | - | |
| 88 | - fragment.handleDecode((Result) message.obj, barcode);//���ؽ�� | |
| 89 | - /***********************************************************************/ | |
| 90 | - } else if (message.what == R.id.decode_failed) { | |
| 91 | - // We're decoding as fast as possible, so when one decode fails, start another. | |
| 92 | - state = State.PREVIEW; | |
| 93 | - CameraManager.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode); | |
| 94 | - } else if (message.what == R.id.return_scan_result) { | |
| 95 | - Log.d(TAG, "Got return scan result message"); | |
| 96 | - fragment.getActivity().setResult(Activity.RESULT_OK, (Intent) message.obj); | |
| 97 | - fragment.getActivity().finish(); | |
| 98 | - } else if (message.what == R.id.launch_product_query) { | |
| 99 | - Log.d(TAG, "Got product query message"); | |
| 100 | - String url = (String) message.obj; | |
| 101 | - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); | |
| 102 | - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); | |
| 103 | - fragment.getActivity().startActivity(intent); | |
| 104 | - } | |
| 105 | - } | |
| 106 | - | |
| 107 | - public void quitSynchronously() { | |
| 108 | - state = State.DONE; | |
| 109 | - CameraManager.get().stopPreview(); | |
| 110 | - Message quit = Message.obtain(decodeThread.getHandler(), R.id.quit); | |
| 111 | - quit.sendToTarget(); | |
| 112 | - try { | |
| 113 | - decodeThread.join(); | |
| 114 | - } catch (InterruptedException e) { | |
| 115 | - // continue | |
| 116 | - } | |
| 117 | - | |
| 118 | - // Be absolutely sure we don't send any queued up messages | |
| 119 | - removeMessages(R.id.decode_succeeded); | |
| 120 | - removeMessages(R.id.decode_failed); | |
| 121 | - } | |
| 122 | - | |
| 123 | - private void restartPreviewAndDecode() { | |
| 124 | - if (state == State.SUCCESS) { | |
| 125 | - state = State.PREVIEW; | |
| 126 | - CameraManager.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode); | |
| 127 | - CameraManager.get().requestAutoFocus(this, R.id.auto_focus); | |
| 128 | - fragment.drawViewfinder(); | |
| 129 | - } | |
| 130 | - } | |
| 131 | - | |
| 132 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/decoding/DecodeFormatManager.java deleted
100644 → 0
| 1 | -/* | |
| 2 | - * Copyright (C) 2010 ZXing authors | |
| 3 | - * | |
| 4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | - * you may not use this file except in compliance with the License. | |
| 6 | - * You may obtain a copy of the License at | |
| 7 | - * | |
| 8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | - * | |
| 10 | - * Unless required by applicable law or agreed to in writing, software | |
| 11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | - * See the License for the specific language governing permissions and | |
| 14 | - * limitations under the License. | |
| 15 | - */ | |
| 16 | - | |
| 17 | -package com.uuzuche.lib_zxing.decoding; | |
| 18 | - | |
| 19 | -import android.content.Intent; | |
| 20 | -import android.net.Uri; | |
| 21 | - | |
| 22 | -import com.google.zxing.BarcodeFormat; | |
| 23 | - | |
| 24 | -import java.util.Arrays; | |
| 25 | -import java.util.List; | |
| 26 | -import java.util.Vector; | |
| 27 | -import java.util.regex.Pattern; | |
| 28 | - | |
| 29 | -public class DecodeFormatManager { | |
| 30 | - | |
| 31 | - private static final Pattern COMMA_PATTERN = Pattern.compile(","); | |
| 32 | - | |
| 33 | - public static final Vector<BarcodeFormat> PRODUCT_FORMATS; | |
| 34 | - public static final Vector<BarcodeFormat> ONE_D_FORMATS; | |
| 35 | - public static final Vector<BarcodeFormat> QR_CODE_FORMATS; | |
| 36 | - public static final Vector<BarcodeFormat> DATA_MATRIX_FORMATS; | |
| 37 | - | |
| 38 | - static { | |
| 39 | - PRODUCT_FORMATS = new Vector<BarcodeFormat>(5); | |
| 40 | - PRODUCT_FORMATS.add(BarcodeFormat.UPC_A); | |
| 41 | - PRODUCT_FORMATS.add(BarcodeFormat.UPC_E); | |
| 42 | - PRODUCT_FORMATS.add(BarcodeFormat.EAN_13); | |
| 43 | - PRODUCT_FORMATS.add(BarcodeFormat.EAN_8); | |
| 44 | - // PRODUCT_FORMATS.add(BarcodeFormat.RSS14); | |
| 45 | - ONE_D_FORMATS = new Vector<BarcodeFormat>(PRODUCT_FORMATS.size() + 4); | |
| 46 | - ONE_D_FORMATS.addAll(PRODUCT_FORMATS); | |
| 47 | - ONE_D_FORMATS.add(BarcodeFormat.CODE_39); | |
| 48 | - ONE_D_FORMATS.add(BarcodeFormat.CODE_93); | |
| 49 | - ONE_D_FORMATS.add(BarcodeFormat.CODE_128); | |
| 50 | - ONE_D_FORMATS.add(BarcodeFormat.ITF); | |
| 51 | - QR_CODE_FORMATS = new Vector<BarcodeFormat>(1); | |
| 52 | - QR_CODE_FORMATS.add(BarcodeFormat.QR_CODE); | |
| 53 | - DATA_MATRIX_FORMATS = new Vector<BarcodeFormat>(1); | |
| 54 | - DATA_MATRIX_FORMATS.add(BarcodeFormat.DATA_MATRIX); | |
| 55 | - } | |
| 56 | - | |
| 57 | - private DecodeFormatManager() { | |
| 58 | - } | |
| 59 | - | |
| 60 | - static Vector<BarcodeFormat> parseDecodeFormats(Intent intent) { | |
| 61 | - List<String> scanFormats = null; | |
| 62 | - String scanFormatsString = intent.getStringExtra(Intents.Scan.SCAN_FORMATS); | |
| 63 | - if (scanFormatsString != null) { | |
| 64 | - scanFormats = Arrays.asList(COMMA_PATTERN.split(scanFormatsString)); | |
| 65 | - } | |
| 66 | - return parseDecodeFormats(scanFormats, intent.getStringExtra(Intents.Scan.MODE)); | |
| 67 | - } | |
| 68 | - | |
| 69 | - static Vector<BarcodeFormat> parseDecodeFormats(Uri inputUri) { | |
| 70 | - List<String> formats = inputUri.getQueryParameters(Intents.Scan.SCAN_FORMATS); | |
| 71 | - if (formats != null && formats.size() == 1 && formats.get(0) != null) { | |
| 72 | - formats = Arrays.asList(COMMA_PATTERN.split(formats.get(0))); | |
| 73 | - } | |
| 74 | - return parseDecodeFormats(formats, inputUri.getQueryParameter(Intents.Scan.MODE)); | |
| 75 | - } | |
| 76 | - | |
| 77 | - private static Vector<BarcodeFormat> parseDecodeFormats(Iterable<String> scanFormats, | |
| 78 | - String decodeMode) { | |
| 79 | - if (scanFormats != null) { | |
| 80 | - Vector<BarcodeFormat> formats = new Vector<BarcodeFormat>(); | |
| 81 | - try { | |
| 82 | - for (String format : scanFormats) { | |
| 83 | - formats.add(BarcodeFormat.valueOf(format)); | |
| 84 | - } | |
| 85 | - return formats; | |
| 86 | - } catch (IllegalArgumentException iae) { | |
| 87 | - // ignore it then | |
| 88 | - } | |
| 89 | - } | |
| 90 | - if (decodeMode != null) { | |
| 91 | - if (Intents.Scan.PRODUCT_MODE.equals(decodeMode)) { | |
| 92 | - return PRODUCT_FORMATS; | |
| 93 | - } | |
| 94 | - if (Intents.Scan.QR_CODE_MODE.equals(decodeMode)) { | |
| 95 | - return QR_CODE_FORMATS; | |
| 96 | - } | |
| 97 | - if (Intents.Scan.DATA_MATRIX_MODE.equals(decodeMode)) { | |
| 98 | - return DATA_MATRIX_FORMATS; | |
| 99 | - } | |
| 100 | - if (Intents.Scan.ONE_D_MODE.equals(decodeMode)) { | |
| 101 | - return ONE_D_FORMATS; | |
| 102 | - } | |
| 103 | - } | |
| 104 | - return null; | |
| 105 | - } | |
| 106 | - | |
| 107 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/decoding/DecodeHandler.java deleted
100644 → 0
| 1 | -/* | |
| 2 | - * Copyright (C) 2010 ZXing authors | |
| 3 | - * | |
| 4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | - * you may not use this file except in compliance with the License. | |
| 6 | - * You may obtain a copy of the License at | |
| 7 | - * | |
| 8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | - * | |
| 10 | - * Unless required by applicable law or agreed to in writing, software | |
| 11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | - * See the License for the specific language governing permissions and | |
| 14 | - * limitations under the License. | |
| 15 | - */ | |
| 16 | - | |
| 17 | -package com.uuzuche.lib_zxing.decoding; | |
| 18 | - | |
| 19 | -import android.os.Bundle; | |
| 20 | -import android.os.Handler; | |
| 21 | -import android.os.Looper; | |
| 22 | -import android.os.Message; | |
| 23 | -import android.util.Log; | |
| 24 | - | |
| 25 | -import com.cnlive.cloud.shanghaishop.R; | |
| 26 | -import com.google.zxing.BinaryBitmap; | |
| 27 | -import com.google.zxing.DecodeHintType; | |
| 28 | -import com.google.zxing.MultiFormatReader; | |
| 29 | -import com.google.zxing.ReaderException; | |
| 30 | -import com.google.zxing.Result; | |
| 31 | -import com.google.zxing.common.HybridBinarizer; | |
| 32 | -import com.uuzuche.lib_zxing.activity.CaptureFragment; | |
| 33 | -import com.uuzuche.lib_zxing.camera.CameraManager; | |
| 34 | -import com.uuzuche.lib_zxing.camera.PlanarYUVLuminanceSource; | |
| 35 | - | |
| 36 | -import java.util.Hashtable; | |
| 37 | - | |
| 38 | -final class DecodeHandler extends Handler { | |
| 39 | - | |
| 40 | - private static final String TAG = DecodeHandler.class.getSimpleName(); | |
| 41 | - | |
| 42 | - private final CaptureFragment fragment; | |
| 43 | - private final MultiFormatReader multiFormatReader; | |
| 44 | - | |
| 45 | - DecodeHandler(CaptureFragment fragment, Hashtable<DecodeHintType, Object> hints) { | |
| 46 | - multiFormatReader = new MultiFormatReader(); | |
| 47 | - multiFormatReader.setHints(hints); | |
| 48 | - this.fragment = fragment; | |
| 49 | - } | |
| 50 | - | |
| 51 | - @Override | |
| 52 | - public void handleMessage(Message message) { | |
| 53 | - if (message.what == R.id.decode) { | |
| 54 | - decode((byte[]) message.obj, message.arg1, message.arg2); | |
| 55 | - } else if (message.what == R.id.quit) { | |
| 56 | - Looper.myLooper().quit(); | |
| 57 | - } | |
| 58 | - } | |
| 59 | - | |
| 60 | - /** | |
| 61 | - * Decode the data within the viewfinder rectangle, and time how long it took. For efficiency, | |
| 62 | - * reuse the same reader objects from one decode to the next. | |
| 63 | - * | |
| 64 | - * @param data The YUV preview frame. | |
| 65 | - * @param width The width of the preview frame. | |
| 66 | - * @param height The height of the preview frame. | |
| 67 | - */ | |
| 68 | - private void decode(byte[] data, int width, int height) { | |
| 69 | - long start = System.currentTimeMillis(); | |
| 70 | - Result rawResult = null; | |
| 71 | - | |
| 72 | - //modify here | |
| 73 | - byte[] rotatedData = new byte[data.length]; | |
| 74 | - for (int y = 0; y < height; y++) { | |
| 75 | - for (int x = 0; x < width; x++) | |
| 76 | - rotatedData[x * height + height - y - 1] = data[x + y * width]; | |
| 77 | - } | |
| 78 | - int tmp = width; // Here we are swapping, that's the difference to #11 | |
| 79 | - width = height; | |
| 80 | - height = tmp; | |
| 81 | - | |
| 82 | - PlanarYUVLuminanceSource source = CameraManager.get().buildLuminanceSource(rotatedData, width, height); | |
| 83 | - BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); | |
| 84 | - try { | |
| 85 | - rawResult = multiFormatReader.decodeWithState(bitmap); | |
| 86 | - } catch (ReaderException re) { | |
| 87 | - // continue | |
| 88 | - } finally { | |
| 89 | - multiFormatReader.reset(); | |
| 90 | - } | |
| 91 | - | |
| 92 | - if (rawResult != null) { | |
| 93 | - long end = System.currentTimeMillis(); | |
| 94 | - Log.d(TAG, "Found barcode (" + (end - start) + " ms):\n" + rawResult.toString()); | |
| 95 | - Message message = Message.obtain(fragment.getHandler(), R.id.decode_succeeded, rawResult); | |
| 96 | - Bundle bundle = new Bundle(); | |
| 97 | - bundle.putParcelable(DecodeThread.BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap()); | |
| 98 | - message.setData(bundle); | |
| 99 | - //Log.d(TAG, "Sending decode succeeded message..."); | |
| 100 | - message.sendToTarget(); | |
| 101 | - } else { | |
| 102 | - Message message = Message.obtain(fragment.getHandler(), R.id.decode_failed); | |
| 103 | - message.sendToTarget(); | |
| 104 | - } | |
| 105 | - } | |
| 106 | - | |
| 107 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/decoding/DecodeThread.java deleted
100644 → 0
| 1 | -/* | |
| 2 | - * Copyright (C) 2008 ZXing authors | |
| 3 | - * | |
| 4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | - * you may not use this file except in compliance with the License. | |
| 6 | - * You may obtain a copy of the License at | |
| 7 | - * | |
| 8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | - * | |
| 10 | - * Unless required by applicable law or agreed to in writing, software | |
| 11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | - * See the License for the specific language governing permissions and | |
| 14 | - * limitations under the License. | |
| 15 | - */ | |
| 16 | - | |
| 17 | -package com.uuzuche.lib_zxing.decoding; | |
| 18 | - | |
| 19 | -import android.os.Handler; | |
| 20 | -import android.os.Looper; | |
| 21 | - | |
| 22 | -import com.google.zxing.BarcodeFormat; | |
| 23 | -import com.google.zxing.DecodeHintType; | |
| 24 | -import com.google.zxing.ResultPointCallback; | |
| 25 | -import com.uuzuche.lib_zxing.activity.CaptureFragment; | |
| 26 | - | |
| 27 | -import java.util.Hashtable; | |
| 28 | -import java.util.Vector; | |
| 29 | -import java.util.concurrent.CountDownLatch; | |
| 30 | - | |
| 31 | -/** | |
| 32 | - * This thread does all the heavy lifting of decoding the images. | |
| 33 | - * �����߳� | |
| 34 | - */ | |
| 35 | -final class DecodeThread extends Thread { | |
| 36 | - | |
| 37 | - public static final String BARCODE_BITMAP = "barcode_bitmap"; | |
| 38 | - private final CaptureFragment fragment; | |
| 39 | - private final Hashtable<DecodeHintType, Object> hints; | |
| 40 | - private Handler handler; | |
| 41 | - private final CountDownLatch handlerInitLatch; | |
| 42 | - | |
| 43 | - DecodeThread(CaptureFragment fragment, | |
| 44 | - Vector<BarcodeFormat> decodeFormats, | |
| 45 | - String characterSet, | |
| 46 | - ResultPointCallback resultPointCallback) { | |
| 47 | - | |
| 48 | - this.fragment = fragment; | |
| 49 | - handlerInitLatch = new CountDownLatch(1); | |
| 50 | - | |
| 51 | - hints = new Hashtable<DecodeHintType, Object>(3); | |
| 52 | - | |
| 53 | - if (decodeFormats == null || decodeFormats.isEmpty()) { | |
| 54 | - decodeFormats = new Vector<BarcodeFormat>(); | |
| 55 | - decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS); | |
| 56 | - decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS); | |
| 57 | - decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS); | |
| 58 | - } | |
| 59 | - | |
| 60 | - hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats); | |
| 61 | - | |
| 62 | - if (characterSet != null) { | |
| 63 | - hints.put(DecodeHintType.CHARACTER_SET, characterSet); | |
| 64 | - } | |
| 65 | - | |
| 66 | - hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback); | |
| 67 | - } | |
| 68 | - | |
| 69 | - Handler getHandler() { | |
| 70 | - try { | |
| 71 | - handlerInitLatch.await(); | |
| 72 | - } catch (InterruptedException ie) { | |
| 73 | - // continue? | |
| 74 | - } | |
| 75 | - return handler; | |
| 76 | - } | |
| 77 | - | |
| 78 | - @Override | |
| 79 | - public void run() { | |
| 80 | - Looper.prepare(); | |
| 81 | - handler = new DecodeHandler(fragment, hints); | |
| 82 | - handlerInitLatch.countDown(); | |
| 83 | - Looper.loop(); | |
| 84 | - } | |
| 85 | - | |
| 86 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/decoding/FinishListener.java deleted
100644 → 0
| 1 | -/* | |
| 2 | - * Copyright (C) 2010 ZXing authors | |
| 3 | - * | |
| 4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | - * you may not use this file except in compliance with the License. | |
| 6 | - * You may obtain a copy of the License at | |
| 7 | - * | |
| 8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | - * | |
| 10 | - * Unless required by applicable law or agreed to in writing, software | |
| 11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | - * See the License for the specific language governing permissions and | |
| 14 | - * limitations under the License. | |
| 15 | - */ | |
| 16 | - | |
| 17 | -package com.uuzuche.lib_zxing.decoding; | |
| 18 | - | |
| 19 | -import android.app.Activity; | |
| 20 | -import android.content.DialogInterface; | |
| 21 | - | |
| 22 | -/** | |
| 23 | - * Simple listener used to exit the app in a few cases. | |
| 24 | - */ | |
| 25 | -public final class FinishListener | |
| 26 | - implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener, Runnable { | |
| 27 | - | |
| 28 | - private final Activity activityToFinish; | |
| 29 | - | |
| 30 | - public FinishListener(Activity activityToFinish) { | |
| 31 | - this.activityToFinish = activityToFinish; | |
| 32 | - } | |
| 33 | - | |
| 34 | - public void onCancel(DialogInterface dialogInterface) { | |
| 35 | - run(); | |
| 36 | - } | |
| 37 | - | |
| 38 | - public void onClick(DialogInterface dialogInterface, int i) { | |
| 39 | - run(); | |
| 40 | - } | |
| 41 | - | |
| 42 | - public void run() { | |
| 43 | - activityToFinish.finish(); | |
| 44 | - } | |
| 45 | - | |
| 46 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/decoding/InactivityTimer.java deleted
100644 → 0
| 1 | -/* | |
| 2 | - * Copyright (C) 2010 ZXing authors | |
| 3 | - * | |
| 4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | - * you may not use this file except in compliance with the License. | |
| 6 | - * You may obtain a copy of the License at | |
| 7 | - * | |
| 8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | - * | |
| 10 | - * Unless required by applicable law or agreed to in writing, software | |
| 11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | - * See the License for the specific language governing permissions and | |
| 14 | - * limitations under the License. | |
| 15 | - */ | |
| 16 | - | |
| 17 | -package com.uuzuche.lib_zxing.decoding; | |
| 18 | - | |
| 19 | -import android.app.Activity; | |
| 20 | - | |
| 21 | -import java.util.concurrent.Executors; | |
| 22 | -import java.util.concurrent.ScheduledExecutorService; | |
| 23 | -import java.util.concurrent.ScheduledFuture; | |
| 24 | -import java.util.concurrent.ThreadFactory; | |
| 25 | -import java.util.concurrent.TimeUnit; | |
| 26 | - | |
| 27 | -/** | |
| 28 | - * Finishes an activity after a period of inactivity. | |
| 29 | - */ | |
| 30 | -public final class InactivityTimer { | |
| 31 | - | |
| 32 | - private static final int INACTIVITY_DELAY_SECONDS = 5 * 60; | |
| 33 | - | |
| 34 | - private final ScheduledExecutorService inactivityTimer = | |
| 35 | - Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory()); | |
| 36 | - private final Activity activity; | |
| 37 | - private ScheduledFuture<?> inactivityFuture = null; | |
| 38 | - | |
| 39 | - public InactivityTimer(Activity activity) { | |
| 40 | - this.activity = activity; | |
| 41 | - onActivity(); | |
| 42 | - } | |
| 43 | - | |
| 44 | - public void onActivity() { | |
| 45 | - cancel(); | |
| 46 | - inactivityFuture = inactivityTimer.schedule(new FinishListener(activity), | |
| 47 | - INACTIVITY_DELAY_SECONDS, | |
| 48 | - TimeUnit.SECONDS); | |
| 49 | - } | |
| 50 | - | |
| 51 | - private void cancel() { | |
| 52 | - if (inactivityFuture != null) { | |
| 53 | - inactivityFuture.cancel(true); | |
| 54 | - inactivityFuture = null; | |
| 55 | - } | |
| 56 | - } | |
| 57 | - | |
| 58 | - public void shutdown() { | |
| 59 | - cancel(); | |
| 60 | - inactivityTimer.shutdown(); | |
| 61 | - } | |
| 62 | - | |
| 63 | - private static final class DaemonThreadFactory implements ThreadFactory { | |
| 64 | - public Thread newThread(Runnable runnable) { | |
| 65 | - Thread thread = new Thread(runnable); | |
| 66 | - thread.setDaemon(true); | |
| 67 | - return thread; | |
| 68 | - } | |
| 69 | - } | |
| 70 | - | |
| 71 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/decoding/Intents.java deleted
100644 → 0
| 1 | -/* | |
| 2 | - * Copyright (C) 2008 ZXing authors | |
| 3 | - * | |
| 4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | - * you may not use this file except in compliance with the License. | |
| 6 | - * You may obtain a copy of the License at | |
| 7 | - * | |
| 8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | - * | |
| 10 | - * Unless required by applicable law or agreed to in writing, software | |
| 11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | - * See the License for the specific language governing permissions and | |
| 14 | - * limitations under the License. | |
| 15 | - */ | |
| 16 | - | |
| 17 | -package com.uuzuche.lib_zxing.decoding; | |
| 18 | - | |
| 19 | -/** | |
| 20 | - * This class provides the constants to use when sending an Intent to Barcode Scanner. | |
| 21 | - * These strings are effectively API and cannot be changed. | |
| 22 | - */ | |
| 23 | -public final class Intents { | |
| 24 | - private Intents() { | |
| 25 | - } | |
| 26 | - | |
| 27 | - public static final class Scan { | |
| 28 | - /** | |
| 29 | - * Send this intent to open the Barcodes app in scanning mode, find a barcode, and return | |
| 30 | - * the results. | |
| 31 | - */ | |
| 32 | - public static final String ACTION = "com.google.zxing.client.android.SCAN"; | |
| 33 | - | |
| 34 | - /** | |
| 35 | - * By default, sending Scan.ACTION will decode all barcodes that we understand. However it | |
| 36 | - * may be useful to limit scanning to certain formats. Use Intent.putExtra(MODE, value) with | |
| 37 | - * one of the values below ({@link #PRODUCT_MODE}, {@link #ONE_D_MODE}, {@link #QR_CODE_MODE}). | |
| 38 | - * Optional. | |
| 39 | - * <p> | |
| 40 | - * Setting this is effectively shorthnad for setting explicit formats with {@link #SCAN_FORMATS}. | |
| 41 | - * It is overridden by that setting. | |
| 42 | - */ | |
| 43 | - public static final String MODE = "SCAN_MODE"; | |
| 44 | - | |
| 45 | - /** | |
| 46 | - * Comma-separated list of formats to scan for. The values must match the names of | |
| 47 | - * {@link com.google.zxing.BarcodeFormat}s, such as {@link com.google.zxing.BarcodeFormat#EAN_13}. | |
| 48 | - * Example: "EAN_13,EAN_8,QR_CODE" | |
| 49 | - * <p> | |
| 50 | - * This overrides {@link #MODE}. | |
| 51 | - */ | |
| 52 | - public static final String SCAN_FORMATS = "SCAN_FORMATS"; | |
| 53 | - | |
| 54 | - /** | |
| 55 | - * @see com.google.zxing.DecodeHintType#CHARACTER_SET | |
| 56 | - */ | |
| 57 | - public static final String CHARACTER_SET = "CHARACTER_SET"; | |
| 58 | - | |
| 59 | - /** | |
| 60 | - * Decode only UPC and EAN barcodes. This is the right choice for shopping apps which get | |
| 61 | - * prices, reviews, etc. for products. | |
| 62 | - */ | |
| 63 | - public static final String PRODUCT_MODE = "PRODUCT_MODE"; | |
| 64 | - | |
| 65 | - /** | |
| 66 | - * Decode only 1D barcodes (currently UPC, EAN, Code 39, and Code 128). | |
| 67 | - */ | |
| 68 | - public static final String ONE_D_MODE = "ONE_D_MODE"; | |
| 69 | - | |
| 70 | - /** | |
| 71 | - * Decode only QR codes. | |
| 72 | - */ | |
| 73 | - public static final String QR_CODE_MODE = "QR_CODE_MODE"; | |
| 74 | - | |
| 75 | - /** | |
| 76 | - * Decode only Data Matrix codes. | |
| 77 | - */ | |
| 78 | - public static final String DATA_MATRIX_MODE = "DATA_MATRIX_MODE"; | |
| 79 | - | |
| 80 | - /** | |
| 81 | - * If a barcode is found, Barcodes returns RESULT_OK to onActivityResult() of the app which | |
| 82 | - * requested the scan via startSubActivity(). The barcodes contents can be retrieved with | |
| 83 | - * intent.getStringExtra(RESULT). If the user presses Back, the result code will be | |
| 84 | - * RESULT_CANCELED. | |
| 85 | - */ | |
| 86 | - public static final String RESULT = "SCAN_RESULT"; | |
| 87 | - | |
| 88 | - /** | |
| 89 | - * Call intent.getStringExtra(RESULT_FORMAT) to determine which barcode format was found. | |
| 90 | - * See Contents.Format for possible values. | |
| 91 | - */ | |
| 92 | - public static final String RESULT_FORMAT = "SCAN_RESULT_FORMAT"; | |
| 93 | - | |
| 94 | - /** | |
| 95 | - * Setting this to false will not save scanned codes in the history. | |
| 96 | - */ | |
| 97 | - public static final String SAVE_HISTORY = "SAVE_HISTORY"; | |
| 98 | - | |
| 99 | - private Scan() { | |
| 100 | - } | |
| 101 | - } | |
| 102 | - | |
| 103 | - public static final class Encode { | |
| 104 | - /** | |
| 105 | - * Send this intent to encode a piece of data as a QR code and display it full screen, so | |
| 106 | - * that another person can scan the barcode from your screen. | |
| 107 | - */ | |
| 108 | - public static final String ACTION = "com.google.zxing.client.android.ENCODE"; | |
| 109 | - | |
| 110 | - /** | |
| 111 | - * The data to encode. Use Intent.putExtra(DATA, data) where data is either a String or a | |
| 112 | - * Bundle, depending on the type and format specified. Non-QR Code formats should | |
| 113 | - * just use a String here. For QR Code, see Contents for details. | |
| 114 | - */ | |
| 115 | - public static final String DATA = "ENCODE_DATA"; | |
| 116 | - | |
| 117 | - /** | |
| 118 | - * The type of data being supplied if the format is QR Code. Use | |
| 119 | - * Intent.putExtra(TYPE, type) with one of Contents.Type. | |
| 120 | - */ | |
| 121 | - public static final String TYPE = "ENCODE_TYPE"; | |
| 122 | - | |
| 123 | - /** | |
| 124 | - * The barcode format to be displayed. If this isn't specified or is blank, | |
| 125 | - * it defaults to QR Code. Use Intent.putExtra(FORMAT, format), where | |
| 126 | - * format is one of Contents.Format. | |
| 127 | - */ | |
| 128 | - public static final String FORMAT = "ENCODE_FORMAT"; | |
| 129 | - | |
| 130 | - private Encode() { | |
| 131 | - } | |
| 132 | - } | |
| 133 | - | |
| 134 | - public static final class SearchBookContents { | |
| 135 | - /** | |
| 136 | - * Use Google Book Search to search the contents of the book provided. | |
| 137 | - */ | |
| 138 | - public static final String ACTION = "com.google.zxing.client.android.SEARCH_BOOK_CONTENTS"; | |
| 139 | - | |
| 140 | - /** | |
| 141 | - * The book to search, identified by ISBN number. | |
| 142 | - */ | |
| 143 | - public static final String ISBN = "ISBN"; | |
| 144 | - | |
| 145 | - /** | |
| 146 | - * An optional field which is the text to search for. | |
| 147 | - */ | |
| 148 | - public static final String QUERY = "QUERY"; | |
| 149 | - | |
| 150 | - private SearchBookContents() { | |
| 151 | - } | |
| 152 | - } | |
| 153 | - | |
| 154 | - public static final class WifiConnect { | |
| 155 | - /** | |
| 156 | - * Internal intent used to trigger connection to a wi-fi network. | |
| 157 | - */ | |
| 158 | - public static final String ACTION = "com.google.zxing.client.android.WIFI_CONNECT"; | |
| 159 | - | |
| 160 | - /** | |
| 161 | - * The network to connect to, all the configuration provided here. | |
| 162 | - */ | |
| 163 | - public static final String SSID = "SSID"; | |
| 164 | - | |
| 165 | - /** | |
| 166 | - * The network to connect to, all the configuration provided here. | |
| 167 | - */ | |
| 168 | - public static final String TYPE = "TYPE"; | |
| 169 | - | |
| 170 | - /** | |
| 171 | - * The network to connect to, all the configuration provided here. | |
| 172 | - */ | |
| 173 | - public static final String PASSWORD = "PASSWORD"; | |
| 174 | - | |
| 175 | - private WifiConnect() { | |
| 176 | - } | |
| 177 | - } | |
| 178 | - | |
| 179 | - | |
| 180 | - public static final class Share { | |
| 181 | - /** | |
| 182 | - * Give the user a choice of items to encode as a barcode, then render it as a QR Code and | |
| 183 | - * display onscreen for a friend to scan with their phone. | |
| 184 | - */ | |
| 185 | - public static final String ACTION = "com.google.zxing.client.android.SHARE"; | |
| 186 | - | |
| 187 | - private Share() { | |
| 188 | - } | |
| 189 | - } | |
| 190 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/encoding/EncodingHandler.java deleted
100644 → 0
| 1 | -package com.uuzuche.lib_zxing.encoding; | |
| 2 | - | |
| 3 | -import android.graphics.Bitmap; | |
| 4 | - | |
| 5 | -import com.google.zxing.BarcodeFormat; | |
| 6 | -import com.google.zxing.EncodeHintType; | |
| 7 | -import com.google.zxing.MultiFormatWriter; | |
| 8 | -import com.google.zxing.WriterException; | |
| 9 | -import com.google.zxing.common.BitMatrix; | |
| 10 | - | |
| 11 | -import java.util.Hashtable; | |
| 12 | - | |
| 13 | -/** | |
| 14 | - * @author Ryan Tang | |
| 15 | - */ | |
| 16 | -public final class EncodingHandler { | |
| 17 | - private static final int BLACK = 0xff000000; | |
| 18 | - | |
| 19 | - public static Bitmap createQRCode(String str, int widthAndHeight) throws WriterException { | |
| 20 | - Hashtable<EncodeHintType, String> hints = new Hashtable<EncodeHintType, String>(); | |
| 21 | - hints.put(EncodeHintType.CHARACTER_SET, "utf-8"); | |
| 22 | - BitMatrix matrix = new MultiFormatWriter().encode(str, | |
| 23 | - BarcodeFormat.QR_CODE, widthAndHeight, widthAndHeight); | |
| 24 | - int width = matrix.getWidth(); | |
| 25 | - int height = matrix.getHeight(); | |
| 26 | - int[] pixels = new int[width * height]; | |
| 27 | - | |
| 28 | - for (int y = 0; y < height; y++) { | |
| 29 | - for (int x = 0; x < width; x++) { | |
| 30 | - if (matrix.get(x, y)) { | |
| 31 | - pixels[y * width + x] = BLACK; | |
| 32 | - } | |
| 33 | - } | |
| 34 | - } | |
| 35 | - Bitmap bitmap = Bitmap.createBitmap(width, height, | |
| 36 | - Bitmap.Config.ARGB_8888); | |
| 37 | - bitmap.setPixels(pixels, 0, width, 0, 0, width, height); | |
| 38 | - return bitmap; | |
| 39 | - } | |
| 40 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/view/ViewfinderResultPointCallback.java deleted
100644 → 0
| 1 | -/* | |
| 2 | - * Copyright (C) 2009 ZXing authors | |
| 3 | - * | |
| 4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | - * you may not use this file except in compliance with the License. | |
| 6 | - * You may obtain a copy of the License at | |
| 7 | - * | |
| 8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | - * | |
| 10 | - * Unless required by applicable law or agreed to in writing, software | |
| 11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | - * See the License for the specific language governing permissions and | |
| 14 | - * limitations under the License. | |
| 15 | - */ | |
| 16 | - | |
| 17 | -package com.uuzuche.lib_zxing.view; | |
| 18 | - | |
| 19 | -import com.google.zxing.ResultPoint; | |
| 20 | -import com.google.zxing.ResultPointCallback; | |
| 21 | - | |
| 22 | -public final class ViewfinderResultPointCallback implements ResultPointCallback { | |
| 23 | - | |
| 24 | - private final ViewfinderView viewfinderView; | |
| 25 | - | |
| 26 | - public ViewfinderResultPointCallback(ViewfinderView viewfinderView) { | |
| 27 | - this.viewfinderView = viewfinderView; | |
| 28 | - } | |
| 29 | - | |
| 30 | - public void foundPossibleResultPoint(ResultPoint point) { | |
| 31 | - viewfinderView.addPossibleResultPoint(point); | |
| 32 | - } | |
| 33 | - | |
| 34 | -} |
module/shanghaishop/src/main/java/com/uuzuche/lib_zxing/view/ViewfinderView.java deleted
100644 → 0
| 1 | -/* | |
| 2 | - * Copyright (C) 2008 ZXing authors | |
| 3 | - * | |
| 4 | - * Licensed under the Apache License, Version 2.0 (the "License"); | |
| 5 | - * you may not use this file except in compliance with the License. | |
| 6 | - * You may obtain a copy of the License at | |
| 7 | - * | |
| 8 | - * http://www.apache.org/licenses/LICENSE-2.0 | |
| 9 | - * | |
| 10 | - * Unless required by applicable law or agreed to in writing, software | |
| 11 | - * distributed under the License is distributed on an "AS IS" BASIS, | |
| 12 | - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| 13 | - * See the License for the specific language governing permissions and | |
| 14 | - * limitations under the License. | |
| 15 | - */ | |
| 16 | - | |
| 17 | -package com.uuzuche.lib_zxing.view; | |
| 18 | - | |
| 19 | -import android.content.Context; | |
| 20 | -import android.content.res.Resources; | |
| 21 | -import android.content.res.TypedArray; | |
| 22 | -import android.graphics.Bitmap; | |
| 23 | -import android.graphics.BitmapFactory; | |
| 24 | -import android.graphics.Canvas; | |
| 25 | -import android.graphics.Color; | |
| 26 | -import android.graphics.Paint; | |
| 27 | -import android.graphics.Rect; | |
| 28 | -import android.graphics.drawable.Drawable; | |
| 29 | -import android.util.AttributeSet; | |
| 30 | -import android.view.View; | |
| 31 | - | |
| 32 | -import com.cnlive.cloud.shanghaishop.R; | |
| 33 | -import com.google.zxing.ResultPoint; | |
| 34 | -import com.uuzuche.lib_zxing.DisplayUtil; | |
| 35 | -import com.uuzuche.lib_zxing.camera.CameraManager; | |
| 36 | - | |
| 37 | -import java.util.Collection; | |
| 38 | -import java.util.HashSet; | |
| 39 | - | |
| 40 | -/** | |
| 41 | - * 自定义组件实现,扫描功能 | |
| 42 | - */ | |
| 43 | -public final class ViewfinderView extends View { | |
| 44 | - | |
| 45 | - private static final long ANIMATION_DELAY = 100L; | |
| 46 | - private static final int OPAQUE = 0xFF; | |
| 47 | - | |
| 48 | - private final Paint paint; | |
| 49 | - private Bitmap resultBitmap; | |
| 50 | - private final int maskColor; | |
| 51 | - private final int resultColor; | |
| 52 | - private final int resultPointColor; | |
| 53 | - private Collection<ResultPoint> possibleResultPoints; | |
| 54 | - private Collection<ResultPoint> lastPossibleResultPoints; | |
| 55 | - | |
| 56 | - public ViewfinderView(Context context) { | |
| 57 | - this(context, null); | |
| 58 | - } | |
| 59 | - | |
| 60 | - public ViewfinderView(Context context, AttributeSet attrs) { | |
| 61 | - this(context, attrs, -1); | |
| 62 | - | |
| 63 | - } | |
| 64 | - | |
| 65 | - public ViewfinderView(Context context, AttributeSet attrs, int defStyleAttr) { | |
| 66 | - super(context, attrs, defStyleAttr); | |
| 67 | - paint = new Paint(); | |
| 68 | - Resources resources = getResources(); | |
| 69 | - maskColor = resources.getColor(R.color.viewfinder_mask); | |
| 70 | - resultColor = resources.getColor(R.color.result_view); | |
| 71 | - resultPointColor = resources.getColor(R.color.possible_result_points); | |
| 72 | - possibleResultPoints = new HashSet<>(5); | |
| 73 | - | |
| 74 | - scanLight = BitmapFactory.decodeResource(resources, | |
| 75 | - R.drawable.scan_light); | |
| 76 | - | |
| 77 | - initInnerRect(context, attrs); | |
| 78 | - } | |
| 79 | - | |
| 80 | - /** | |
| 81 | - * 初始化内部框的大小 | |
| 82 | - * | |
| 83 | - * @param context | |
| 84 | - * @param attrs | |
| 85 | - */ | |
| 86 | - private void initInnerRect(Context context, AttributeSet attrs) { | |
| 87 | - TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ViewfinderView); | |
| 88 | - | |
| 89 | - // 扫描框距离顶部 | |
| 90 | - float innerMarginTop = ta.getDimension(R.styleable.ViewfinderView_inner_margintop, -1); | |
| 91 | - if (innerMarginTop != -1) { | |
| 92 | - CameraManager.FRAME_MARGINTOP = (int) innerMarginTop; | |
| 93 | - } | |
| 94 | - | |
| 95 | - // 扫描框的宽度 | |
| 96 | - CameraManager.FRAME_WIDTH = (int) ta.getDimension(R.styleable.ViewfinderView_inner_width, DisplayUtil.screenWidthPx / 2); | |
| 97 | - | |
| 98 | - // 扫描框的高度 | |
| 99 | - CameraManager.FRAME_HEIGHT = (int) ta.getDimension(R.styleable.ViewfinderView_inner_height, DisplayUtil.screenWidthPx / 2); | |
| 100 | - | |
| 101 | - // 扫描框边角颜色 | |
| 102 | - innercornercolor = ta.getColor(R.styleable.ViewfinderView_inner_corner_color, Color.parseColor("#45DDDD")); | |
| 103 | - // 扫描框边角长度 | |
| 104 | - innercornerlength = (int) ta.getDimension(R.styleable.ViewfinderView_inner_corner_length, 65); | |
| 105 | - // 扫描框边角宽度 | |
| 106 | - innercornerwidth = (int) ta.getDimension(R.styleable.ViewfinderView_inner_corner_width, 15); | |
| 107 | - | |
| 108 | - // 扫描bitmap | |
| 109 | - Drawable drawable = ta.getDrawable(R.styleable.ViewfinderView_inner_scan_bitmap); | |
| 110 | - if (drawable != null) { | |
| 111 | - } | |
| 112 | - | |
| 113 | - // 扫描控件 | |
| 114 | - scanLight = BitmapFactory.decodeResource(getResources(), ta.getResourceId(R.styleable.ViewfinderView_inner_scan_bitmap, R.drawable.scan_light)); | |
| 115 | - // 扫描速度 | |
| 116 | - SCAN_VELOCITY = ta.getInt(R.styleable.ViewfinderView_inner_scan_speed, 5); | |
| 117 | - | |
| 118 | - isCircle = ta.getBoolean(R.styleable.ViewfinderView_inner_scan_iscircle, true); | |
| 119 | - | |
| 120 | - ta.recycle(); | |
| 121 | - } | |
| 122 | - | |
| 123 | - @Override | |
| 124 | - public void onDraw(Canvas canvas) { | |
| 125 | - Rect frame = CameraManager.get().getFramingRect(); | |
| 126 | - if (frame == null) { | |
| 127 | - return; | |
| 128 | - } | |
| 129 | - int width = canvas.getWidth(); | |
| 130 | - int height = canvas.getHeight(); | |
| 131 | - | |
| 132 | - // Draw the exterior (i.e. outside the framing rect) darkened | |
| 133 | - paint.setColor(resultBitmap != null ? resultColor : maskColor); | |
| 134 | - canvas.drawRect(0, 0, width, frame.top, paint); | |
| 135 | - canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint); | |
| 136 | - canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint); | |
| 137 | - canvas.drawRect(0, frame.bottom + 1, width, height, paint); | |
| 138 | - | |
| 139 | - if (resultBitmap != null) { | |
| 140 | - // Draw the opaque result bitmap over the scanning rectangle | |
| 141 | - paint.setAlpha(OPAQUE); | |
| 142 | - canvas.drawBitmap(resultBitmap, frame.left, frame.top, paint); | |
| 143 | - } else { | |
| 144 | - | |
| 145 | - drawFrameBounds(canvas, frame); | |
| 146 | - | |
| 147 | - drawScanLight(canvas, frame); | |
| 148 | - | |
| 149 | - Collection<ResultPoint> currentPossible = possibleResultPoints; | |
| 150 | - Collection<ResultPoint> currentLast = lastPossibleResultPoints; | |
| 151 | - if (currentPossible.isEmpty()) { | |
| 152 | - lastPossibleResultPoints = null; | |
| 153 | - } else { | |
| 154 | - possibleResultPoints = new HashSet<ResultPoint>(5); | |
| 155 | - lastPossibleResultPoints = currentPossible; | |
| 156 | - paint.setAlpha(OPAQUE); | |
| 157 | - paint.setColor(resultPointColor); | |
| 158 | - | |
| 159 | - if (isCircle) { | |
| 160 | - for (ResultPoint point : currentPossible) { | |
| 161 | - canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 6.0f, paint); | |
| 162 | - } | |
| 163 | - } | |
| 164 | - } | |
| 165 | - if (currentLast != null) { | |
| 166 | - paint.setAlpha(OPAQUE / 2); | |
| 167 | - paint.setColor(resultPointColor); | |
| 168 | - | |
| 169 | - if (isCircle) { | |
| 170 | - for (ResultPoint point : currentLast) { | |
| 171 | - canvas.drawCircle(frame.left + point.getX(), frame.top + point.getY(), 3.0f, paint); | |
| 172 | - } | |
| 173 | - } | |
| 174 | - } | |
| 175 | - | |
| 176 | - postInvalidateDelayed(ANIMATION_DELAY, frame.left, frame.top, frame.right, frame.bottom); | |
| 177 | - } | |
| 178 | - } | |
| 179 | - | |
| 180 | - // 扫描线移动的y | |
| 181 | - private int scanLineTop; | |
| 182 | - // 扫描线移动速度 | |
| 183 | - private int SCAN_VELOCITY; | |
| 184 | - // 扫描线 | |
| 185 | - private Bitmap scanLight; | |
| 186 | - // 是否展示小圆点 | |
| 187 | - private boolean isCircle; | |
| 188 | - | |
| 189 | - /** | |
| 190 | - * 绘制移动扫描线 | |
| 191 | - * | |
| 192 | - * @param canvas | |
| 193 | - * @param frame | |
| 194 | - */ | |
| 195 | - private void drawScanLight(Canvas canvas, Rect frame) { | |
| 196 | - | |
| 197 | - if (scanLineTop == 0) { | |
| 198 | - scanLineTop = frame.top; | |
| 199 | - } | |
| 200 | - | |
| 201 | - if (scanLineTop >= frame.bottom - 30) { | |
| 202 | - scanLineTop = frame.top; | |
| 203 | - } else { | |
| 204 | - scanLineTop += SCAN_VELOCITY; | |
| 205 | - } | |
| 206 | - Rect scanRect = new Rect(frame.left, scanLineTop, frame.right, | |
| 207 | - scanLineTop + 30); | |
| 208 | - canvas.drawBitmap(scanLight, null, scanRect, paint); | |
| 209 | - } | |
| 210 | - | |
| 211 | - | |
| 212 | - // 扫描框边角颜色 | |
| 213 | - private int innercornercolor; | |
| 214 | - // 扫描框边角长度 | |
| 215 | - private int innercornerlength; | |
| 216 | - // 扫描框边角宽度 | |
| 217 | - private int innercornerwidth; | |
| 218 | - | |
| 219 | - /** | |
| 220 | - * 绘制取景框边框 | |
| 221 | - * | |
| 222 | - * @param canvas | |
| 223 | - * @param frame | |
| 224 | - */ | |
| 225 | - private void drawFrameBounds(Canvas canvas, Rect frame) { | |
| 226 | - | |
| 227 | - /*paint.setColor(Color.WHITE); | |
| 228 | - paint.setStrokeWidth(2); | |
| 229 | - paint.setStyle(Paint.Style.STROKE); | |
| 230 | - | |
| 231 | - canvas.drawRect(frame, paint);*/ | |
| 232 | - | |
| 233 | - paint.setColor(innercornercolor); | |
| 234 | - paint.setStyle(Paint.Style.FILL); | |
| 235 | - | |
| 236 | - int corWidth = innercornerwidth; | |
| 237 | - int corLength = innercornerlength; | |
| 238 | - | |
| 239 | - // 左上角 | |
| 240 | - canvas.drawRect(frame.left, frame.top, frame.left + corWidth, frame.top | |
| 241 | - + corLength, paint); | |
| 242 | - canvas.drawRect(frame.left, frame.top, frame.left | |
| 243 | - + corLength, frame.top + corWidth, paint); | |
| 244 | - // 右上角 | |
| 245 | - canvas.drawRect(frame.right - corWidth, frame.top, frame.right, | |
| 246 | - frame.top + corLength, paint); | |
| 247 | - canvas.drawRect(frame.right - corLength, frame.top, | |
| 248 | - frame.right, frame.top + corWidth, paint); | |
| 249 | - // 左下角 | |
| 250 | - canvas.drawRect(frame.left, frame.bottom - corLength, | |
| 251 | - frame.left + corWidth, frame.bottom, paint); | |
| 252 | - canvas.drawRect(frame.left, frame.bottom - corWidth, frame.left | |
| 253 | - + corLength, frame.bottom, paint); | |
| 254 | - // 右下角 | |
| 255 | - canvas.drawRect(frame.right - corWidth, frame.bottom - corLength, | |
| 256 | - frame.right, frame.bottom, paint); | |
| 257 | - canvas.drawRect(frame.right - corLength, frame.bottom - corWidth, | |
| 258 | - frame.right, frame.bottom, paint); | |
| 259 | - } | |
| 260 | - | |
| 261 | - | |
| 262 | - public void drawViewfinder() { | |
| 263 | - resultBitmap = null; | |
| 264 | - invalidate(); | |
| 265 | - } | |
| 266 | - | |
| 267 | - public void addPossibleResultPoint(ResultPoint point) { | |
| 268 | - possibleResultPoints.add(point); | |
| 269 | - } | |
| 270 | - | |
| 271 | - | |
| 272 | - /** | |
| 273 | - * 根据手机的分辨率从 dp 的单位 转成为 px(像素) | |
| 274 | - */ | |
| 275 | - public static int dip2px(Context context, float dpValue) { | |
| 276 | - final float scale = context.getResources().getDisplayMetrics().density; | |
| 277 | - return (int) (dpValue * scale + 0.5f); | |
| 278 | - } | |
| 279 | - | |
| 280 | - | |
| 281 | -} |