Commit 80ac6704e965612b438c06e4d5601d629228c503

Authored by YangShuai
1 parent 988f945b

1 新增exoplayer

Showing 30 changed files with 4459 additions and 29 deletions

Too many changes to show.

To preserve performance only 30 of 48 files are displayed.

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
... ...