Commit 80ac6704e965612b438c06e4d5601d629228c503

Authored by YangShuai
1 parent 988f945b

1 新增exoplayer

Showing 48 changed files with 8775 additions and 49 deletions
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"
... ... @@ -29,6 +29,12 @@
29 29 tools:replace="android:resource" />
30 30 </provider>
31 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" />
32 38 <activity android:name="com.cnlive.strike.demo.activity.CNplayerActivity" />
33 39 <activity android:name="com.cnlive.strike.demo.activity.DemoAnimActivity" />
34 40 <activity
... ... @@ -49,8 +55,8 @@
49 55  
50 56 <meta-data
51 57 android:name="com.baidu.lbsapi.API_KEY"
52   - tools:replace="android:value"
53   - android:value="${BAIDU_MAP_APP_KEY}" />
  58 + android:value="${BAIDU_MAP_APP_KEY}"
  59 + tools:replace="android:value" />
54 60 <meta-data
55 61 android:name="com.baidu.speech.APP_ID"
56 62 android:value="${BAIDU_APP_ID}" />
... ...
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,10 +65,10 @@ 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 74 binding.videoview.setDataSource(new DataSource("604_maishicaijing", Config.TYPE_LIVE, Config.TYPE_PLAY_RATE_2));
... ... @@ -62,12 +76,6 @@ public class CNplayerActivity extends BaseActivity implements IMediaPlayer.OnPre
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
... ... @@ -8,8 +8,10 @@ import android.text.TextUtils;
8 8 import android.view.View;
9 9  
10 10 import androidx.annotation.NonNull;
  11 +import androidx.annotation.Nullable;
11 12 import androidx.databinding.ViewDataBinding;
12 13  
  14 +import com.alibaba.android.arouter.launcher.ARouter;
13 15 import com.cnlive.cloud.R;
14 16 import com.cnlive.cloud.databinding.ActivityDemoMvpMainBinding;
15 17 import com.cnlive.cloud.user.auth.UserService;
... ... @@ -23,6 +25,7 @@ import com.cnlive.cloud.xiaojiaspeaker.ui.activity.AllDeviceActivity;
23 25 import com.cnlive.core.libs.base.arouter.user.CNLiveUserService;
24 26 import com.cnlive.core.libs.base.frame.activity.BaseActivity;
25 27 import com.cnlive.core.libs.base.interfaces.OnPermissionResponseListener;
  28 +import com.cnlive.core.libs.base.util.AlertUtil;
26 29 import com.cnlive.core.libs.base.util.OpenServiceHelpUtil;
27 30 import com.cnlive.core.libs.base.util.QMUITipDialogUtil;
28 31 import com.cnlive.core.libs.base.util.StatusBarConfig;
... ... @@ -34,6 +37,8 @@ import com.cnlive.moudle.pedometer.commonInfo.CommonInfo;
34 37 import com.cnlive.moudle.pedometer.ui.activity.RunLineActivity;
35 38 import com.cnlive.strike.demo.activity.presenter.DemoActivityPresenter;
36 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;
37 42 import com.cnlive.strike.network.api.ApiService;
38 43 import com.cnlive.strike.network.api.ApiTimeService;
39 44 import com.cnlive.strike.network.api.TimeServiceBean;
... ... @@ -397,9 +402,29 @@ public class DemoMvpActivity extends BaseActivity&lt;DemoActivityView, DemoActivity
397 402 binding.btnLivePlayer.setOnClickListener(new View.OnClickListener() {
398 403 @Override
399 404 public void onClick(View v) {
400   - Intent intent=new Intent(me,CNplayerActivity.class);
401   - intent.putExtra("type","1");
402   - 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 + }
403 428 });
404 429 //获取uid
405 430 binding.btnUid.setOnClickListener(new View.OnClickListener() {
... ... @@ -415,8 +440,8 @@ public class DemoMvpActivity extends BaseActivity&lt;DemoActivityView, DemoActivity
415 440 binding.btnShanghai.setOnClickListener(new View.OnClickListener() {
416 441 @Override
417 442 public void onClick(View v) {
418   -// Intent intent = new Intent(me, cn.cnlive.b2c.MainActivity.class);
419   -// startActivity(intent);
  443 + Intent intent = new Intent(me, cn.cnlive.b2c.MainActivity.class);
  444 + startActivity(intent);
420 445 }
421 446 });
422 447  
... ... @@ -509,4 +534,5 @@ public class DemoMvpActivity extends BaseActivity&lt;DemoActivityView, DemoActivity
509 534 // /*添加View到当前显示的Activity*/
510 535 // MyApp.getInstance().showBottomView(view);
511 536 }
  537 +
512 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
  1 +package com.cnlive.strike.demo.activity;
  2 +
  3 +import com.google.android.exoplayer2.Timeline;
  4 +
  5 +public interface IVideoPlayer {
  6 + void onTimelineChanged(Timeline timeline, int reason);
  7 +}
... ...
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
... ... @@ -91,12 +91,14 @@
91 91 android:layout_height="wrap_content"
92 92 android:layout_margin="5dp"
93 93 android:text="动画特效" />
  94 +
94 95 <Button
95 96 android:id="@+id/btn_shanghai"
96 97 android:layout_width="match_parent"
97 98 android:layout_height="wrap_content"
98 99 android:layout_margin="5dp"
99 100 android:text="上海电商" />
  101 +
100 102 <Button
101 103 android:id="@+id/btn_uid"
102 104 android:layout_width="match_parent"
... ... @@ -119,6 +121,18 @@
119 121 android:text="直播" />
120 122  
121 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官方" />
  135 + <Button
122 136 android:id="@+id/btn_pay"
123 137 android:layout_width="match_parent"
124 138 android:layout_height="wrap_content"
... ...
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/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>
... ...
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
1 1 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2   - package="com.cnlive.cloud.video" />
  2 + package="com.cnlive.cloud.video">
  3 + <application>
  4 + </application>
  5 +
  6 +</manifest>
... ...
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
... ... @@ -8,6 +8,7 @@ import java.io.Serializable;
8 8  
9 9 public interface IDataSource extends Serializable {
10 10  
  11 +
11 12 String getDataSourceId();
12 13  
13 14 String getDataSourceType();
... ...
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 &lt;= 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 &lt;= 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
  1 +package com.libs.video.video.exoplayer;
  2 +
  3 +import android.net.Uri;
  4 +
  5 +public interface IVideoDataSource {
  6 + String getDataSourceId();
  7 +
  8 + String getDataSourceType();
  9 +
  10 + Uri getPlayUri();
  11 +}
... ...
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/frame/activity/BaseActivity.java
... ... @@ -85,8 +85,6 @@ public abstract class BaseActivity&lt;V extends BaseView, P extends BasePresenter&lt;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&lt;V extends BaseView, P extends BasePresenter&lt;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&lt;V extends BaseView, P extends BasePresenter&lt;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 /**
... ...
module/shanghaishop/build.gradle
... ... @@ -17,10 +17,11 @@ dependencies {
17 17 testImplementation 'junit:junit:4.12'
18 18 androidTestImplementation 'androidx.test.ext:junit:1.1.1'
19 19 androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
20   -
21   - api('cn.cnlive.b2c:cnliveb2c:1.0.0-20191212.082956-16') {
22   - exclude group: 'com.bumptech.glide'
  20 + api('cn.cnlive.b2c:cnliveb2c:1.0.0-20191217.063321-17') {
  21 + exclude group: 'com.github.bumptech.glide'
23 22 }
  23 +// api 'cn.cnlive.b2c:cnliveb2c:1.0.0-20191217.063321-17'
  24 +
24 25 api 'cn.cnlive.b2c:nicevideoplayer:1.0.0-20191206.090854-2'
25 26 api 'cn.cnlive.b2c:udesk-sdk-ui:1.0.0'
26 27  
... ... @@ -38,7 +39,8 @@ dependencies {
38 39 implementation 'com.alibaba:fastjson:1.2.61'
39 40  
40 41 /*轮播图*/
41   - implementation 'com.bigkoo:convenientbanner:2.1.5'
  42 +// implementation 'com.bigkoo:convenientbanner:2.1.5'
  43 + implementation 'com.bigkoo:convenientbanner:2.0.5'
42 44  
43 45 /*滚动播放的公告控件*/
44 46 implementation 'com.github.czy1121:noticeview:1.1.0'
... ... @@ -94,6 +96,8 @@ dependencies {
94 96  
95 97 /*自定义各种形状的imageview */
96 98 implementation 'com.github.siyamed:android-shape-imageview:0.9.+@aar'
97   -
  99 + implementation 'com.cnlive.libs:base:1.7.6.18'
  100 + implementation project(path: ':library_baidumap')
  101 + implementation project(path: ':core:base')
98 102  
99 103 }
... ...