();
158 |
159 | String sql = "select * from Channel where name like ?";
160 | Cursor cursor = db.rawQuery(sql, new String[]{"%" + value + "%"});
161 | while (cursor.moveToNext()) {
162 | channelList.add(new Channel(cursor.getInt(cursor.getColumnIndex("id")),
163 | cursor.getString(cursor.getColumnIndex("name")),
164 | cursor.getString(cursor.getColumnIndex("url")),
165 | cursor.getInt(cursor.getColumnIndex("isLike"))
166 | )
167 | );
168 | }
169 | return channelList;
170 | }
171 | }
172 |
--------------------------------------------------------------------------------
/app/src/main/java/com/z/exoplayertest/database/SqliteHelper.java:
--------------------------------------------------------------------------------
1 | package com.z.exoplayertest.database;
2 |
3 | import android.content.Context;
4 | import android.database.sqlite.SQLiteDatabase;
5 | import android.database.sqlite.SQLiteOpenHelper;
6 |
7 | public class SqliteHelper extends SQLiteOpenHelper {
8 |
9 | /**
10 | * 数据库版本
11 | */
12 | private static final int DB_VERSION = 1;
13 | /**
14 | * 数据库名称
15 | */
16 | private static final String DB_NAME = "TvShow.db";
17 | /**
18 | * 表的名称
19 | */
20 | public static final String TABLE_NAME = "Channel";
21 |
22 | public SqliteHelper(Context context) {
23 | super(context, DB_NAME, null, DB_VERSION);
24 | }
25 |
26 | /**
27 | * 这个方法
28 | * 1、在第一次打开数据库的时候才会走
29 | * 2、在清除数据之后再次运行-->打开数据库,这个方法会走
30 | * 3、没有清除数据,不会走这个方法
31 | * 4、数据库升级的时候这个方法不会走
32 | */
33 | @Override
34 | public void onCreate(SQLiteDatabase db) {
35 | //初始化数据表,可以再这里面对多个表进行处理
36 | String sql = "create table if not exists " + TABLE_NAME + " (id integer primary key AUTOINCREMENT, name text, url text, isLike integer)";
37 | db.execSQL(sql);
38 | }
39 |
40 | /**
41 | * 数据库升级
42 | * 1、第一次创建数据库的时候,这个方法不会走
43 | * 2、清除数据后再次运行(相当于第一次创建)这个方法不会走
44 | * 3、数据库已经存在,而且版本升高的时候,这个方法才会调用
45 | *
46 | * @param db
47 | * @param oldVersion
48 | * @param newVersion
49 | */
50 | @Override
51 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
52 | String sql = "DROP TABLE IF EXISTS " + TABLE_NAME;
53 | db.execSQL(sql);
54 | onCreate(db);
55 | }
56 |
57 | /**
58 | * 执行数据库的降级操作
59 | * 1、只有新版本比旧版本低的时候才会执行
60 | * 2、如果不执行降级操作,会抛出异常
61 | */
62 | @Override
63 | public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
64 | super.onDowngrade(db, oldVersion, newVersion);
65 | }
66 |
67 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/z/exoplayertest/utils/AppUtils.java:
--------------------------------------------------------------------------------
1 | package com.z.exoplayertest.utils;
2 |
3 | import android.content.Context;
4 | import android.util.DisplayMetrics;
5 |
6 | /**
7 | * Created by Raul_lsj on 2018/3/5.
8 | */
9 |
10 | public class AppUtils {
11 |
12 | public final static String WIDTH = "width";
13 |
14 | public final static String HEIGHT = "height";
15 |
16 | /**
17 | * px转dp
18 | *
19 | * @param context The context
20 | * @param px the pixel value
21 | * @return value in dp
22 | */
23 | public static int pxToDp(Context context, float px) {
24 | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
25 | return (int) ((px / displayMetrics.density) + 0.5f);
26 | }
27 |
28 | /**
29 | * dp转px
30 | *
31 | * @param context
32 | * @param dp
33 | * @return
34 | */
35 | public static int dpToPx(Context context, float dp) {
36 | DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
37 | return (int) ((dp * displayMetrics.density) + 0.5f);
38 | }
39 |
40 | /**
41 | * 获取状态栏高度
42 | *
43 | * @param context
44 | * @return
45 | */
46 | public static int getStatusBarHeight(Context context) {
47 | int result = 0;
48 | int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen",
49 | "android");
50 | if (resourceId > 0) {
51 | result = context.getResources().getDimensionPixelSize(resourceId);
52 | }
53 | return result;
54 | }
55 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/z/exoplayertest/utils/Density.java:
--------------------------------------------------------------------------------
1 | package com.z.exoplayertest.utils;
2 |
3 | import android.app.Activity;
4 | import android.app.Application;
5 | import android.content.ComponentCallbacks;
6 | import android.content.res.Configuration;
7 | import android.util.DisplayMetrics;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.annotation.Nullable;
11 |
12 | /**
13 | * 通过修改系统参数来适配android设备
14 | * https://www.jianshu.com/p/4254ea9d1b27
15 | *
16 | * Created by Raul_lsj on 2018/6/6.
17 | */
18 |
19 | public class Density {
20 |
21 | private static float appDensity;
22 | private static float appScaledDensity;
23 | private static DisplayMetrics appDisplayMetrics;
24 | private static int barHeight;
25 |
26 | public static void setDensity(@NonNull final Application application) {
27 | //获取application的DisplayMetrics
28 | appDisplayMetrics = application.getResources().getDisplayMetrics();
29 | //获取状态栏高度
30 | barHeight = AppUtils.getStatusBarHeight(application);
31 |
32 | if (appDensity == 0) {
33 | //初始化的时候赋值
34 | appDensity = appDisplayMetrics.density;
35 | appScaledDensity = appDisplayMetrics.scaledDensity;
36 |
37 | //添加字体变化的监听
38 | application.registerComponentCallbacks(new ComponentCallbacks() {
39 | @Override
40 | public void onConfigurationChanged(Configuration newConfig) {
41 | //字体改变后,将appScaledDensity重新赋值
42 | if (newConfig != null && newConfig.fontScale > 0) {
43 | appScaledDensity = application.getResources().getDisplayMetrics().scaledDensity;
44 | }
45 | }
46 |
47 | @Override
48 | public void onLowMemory() {
49 | }
50 | });
51 | }
52 | }
53 |
54 | //此方法在BaseActivity中做初始化(如果不封装BaseActivity的话,直接用下面那个方法就好)
55 | public static void setDefault(Activity activity) {
56 | setAppOrientation(activity, AppUtils.WIDTH);
57 | }
58 |
59 | //此方法用于在某一个Activity里面更改适配的方向
60 | public static void setOrientation(Activity activity, String orientation) {
61 | setAppOrientation(activity, orientation);
62 | }
63 |
64 | /**
65 | * targetDensity
66 | * targetScaledDensity
67 | * targetDensityDpi
68 | * 这三个参数是统一修改过后的值
69 | *
70 | * orientation:方向值,传入width或height
71 | */
72 | private static void setAppOrientation(@Nullable Activity activity, String orientation) {
73 |
74 | float targetDensity;
75 |
76 | if (orientation.equals("height")) {
77 | targetDensity = (appDisplayMetrics.heightPixels - barHeight) / 667f;
78 | } else {
79 | targetDensity = appDisplayMetrics.widthPixels / 360f;
80 | }
81 |
82 | float targetScaledDensity = targetDensity * (appScaledDensity / appDensity);
83 | int targetDensityDpi = (int) (160 * targetDensity);
84 |
85 | /**
86 | *
87 | * 最后在这里将修改过后的值赋给系统参数
88 | *
89 | * 只修改Activity的density值
90 | */
91 | DisplayMetrics activityDisplayMetrics = activity.getResources().getDisplayMetrics();
92 | activityDisplayMetrics.density = targetDensity;
93 | activityDisplayMetrics.scaledDensity = targetScaledDensity;
94 | activityDisplayMetrics.densityDpi = targetDensityDpi;
95 | }
96 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/z/exoplayertest/utils/FileUtil.java:
--------------------------------------------------------------------------------
1 | package com.z.exoplayertest.utils;
2 |
3 | import android.content.Context;
4 | import android.content.res.AssetManager;
5 | import android.os.Environment;
6 |
7 | import com.z.exoplayertest.database.Channel;
8 |
9 | import java.io.BufferedReader;
10 | import java.io.BufferedWriter;
11 | import java.io.File;
12 | import java.io.FileInputStream;
13 | import java.io.FileOutputStream;
14 | import java.io.IOException;
15 | import java.io.InputStream;
16 | import java.io.InputStreamReader;
17 | import java.io.OutputStreamWriter;
18 | import java.io.RandomAccessFile;
19 | import java.util.ArrayList;
20 | import java.util.List;
21 |
22 | public class FileUtil {
23 | /**
24 | * 用户手动添加频道文件地址
25 | */
26 | public static String USER_CHANNEL_FILE_PATH = Environment.getExternalStorageDirectory() + File.separator + "channel.txt";
27 | /**
28 | * 获取文件中的数据
29 | *
30 | * @param context
31 | * @return
32 | */
33 | static public List getChannelFromTxt(Context context) {
34 | List list = new ArrayList<>();
35 | AssetManager am = context.getAssets();
36 | try {
37 | //读取assets内的文件
38 | InputStream is = am.open("channel");
39 | BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is, "utf-8"));
40 |
41 | String lineTxt = null;
42 | while ((lineTxt = bufferedReader.readLine()) != null) {
43 | if (lineTxt.contains("&&")) {
44 | String[] str = lineTxt.split("&&");
45 | if (str.length > 1) {
46 | list.add(new Channel(str[0], str[1], 0));
47 | }
48 | }
49 | }
50 | bufferedReader.close();
51 | is.close();
52 |
53 | File file = new File(USER_CHANNEL_FILE_PATH);
54 | if (file.exists()) {
55 | //读取用户手动添加的文件
56 | InputStreamReader reader = new InputStreamReader(new FileInputStream(file), "utf-8");
57 | BufferedReader br = new BufferedReader(reader);
58 | String s = null;
59 | while ((s = br.readLine()) != null) {
60 | if (s.contains("&&")) {
61 | String[] str = s.split("&&");
62 | if (str.length > 1) {
63 | list.add(new Channel(str[0], str[1], 0));
64 | }
65 | }
66 | }
67 | }
68 | } catch (IOException e) {
69 | e.printStackTrace();
70 | }
71 | return list;
72 | }
73 |
74 | // 将字符串写入到文本文件中
75 | public static void writeFile(String strFilePath, String strcontent) {
76 | // 每次写入时,都换行写
77 | String strContent = strcontent + "\r\n";
78 | try {
79 | File file = new File(strFilePath);
80 | if (!file.exists()) {
81 | file.getParentFile().mkdirs();
82 | file.createNewFile();
83 | }
84 | RandomAccessFile raf = new RandomAccessFile(file, "rwd");
85 | raf.seek(file.length());
86 | raf.write(strContent.getBytes());
87 | raf.close();
88 | } catch (Exception e) {
89 |
90 | }
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/app/src/main/java/com/z/exoplayertest/view/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.z.exoplayertest.view;
2 |
3 | import android.os.Bundle;
4 | import android.view.Window;
5 | import android.view.WindowManager;
6 | import android.widget.Toast;
7 |
8 | import androidx.annotation.Nullable;
9 | import androidx.appcompat.app.AppCompatActivity;
10 |
11 | public abstract class BaseActivity extends AppCompatActivity {
12 | /***是否显示标题栏*/
13 | private boolean isshowtitle = false;
14 | /***是否显示标题栏*/
15 | private boolean isshowstate = true;
16 | /***封装toast对象**/
17 | private static Toast toast;
18 | /***获取TAG的activity名称**/
19 | protected final String TAG = this.getClass().getSimpleName();
20 | @Override
21 | protected void onCreate(@Nullable Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | if(!isshowtitle){
24 | requestWindowFeature(Window.FEATURE_NO_TITLE);
25 | }
26 |
27 | if(isshowstate){
28 | getWindow().setFlags(WindowManager.LayoutParams. FLAG_FULLSCREEN ,
29 | WindowManager.LayoutParams. FLAG_FULLSCREEN);
30 | }
31 | //设置布局
32 | setContentView(intiLayout());
33 | //初始化控件
34 | initView();
35 | //设置数据
36 | initData();
37 | }
38 |
39 | /**
40 | * 设置布局
41 | *
42 | * @return
43 | */
44 | public abstract int intiLayout();
45 |
46 | /**
47 | * 初始化布局
48 | */
49 | public abstract void initView();
50 |
51 | /**
52 | * 设置数据
53 | */
54 | public abstract void initData();
55 |
56 | /**
57 | * 是否设置标题栏
58 | *
59 | * @return
60 | */
61 | public void setTitle(boolean ishow) {
62 | isshowtitle=ishow;
63 | }
64 |
65 | /**
66 | * 设置是否显示状态栏
67 | * @param ishow
68 | */
69 | public void setState(boolean ishow) {
70 | isshowstate=ishow;
71 | }
72 |
73 | /**
74 | * 显示长toast
75 | * @param msg
76 | */
77 | public void toastLong(String msg){
78 | if (null == toast) {
79 | toast = new Toast(this);
80 | toast.setDuration(Toast.LENGTH_LONG);
81 | toast.setText(msg);
82 | toast.show();
83 | } else {
84 | toast.setText(msg);
85 | toast.show();
86 | }
87 | }
88 |
89 | /**
90 | * 显示短toast
91 | * @param msg
92 | */
93 | public void toastShort(String msg){
94 | if (null == toast) {
95 | toast = new Toast(this);
96 | toast.setDuration(Toast.LENGTH_SHORT);
97 | toast.setText(msg);
98 | toast.show();
99 | } else {
100 | toast.setText(msg);
101 | toast.show();
102 | }
103 | }
104 |
105 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/z/exoplayertest/view/BaseApplication.java:
--------------------------------------------------------------------------------
1 | package com.z.exoplayertest.view;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 |
6 | import com.tencent.bugly.Bugly;
7 | import com.tencent.stat.StatConfig;
8 | import com.tencent.stat.StatService;
9 | import com.tencent.tinker.entry.ApplicationLike;
10 | import com.tinkerpatch.sdk.TinkerPatch;
11 | import com.tinkerpatch.sdk.loader.TinkerPatchApplicationLike;
12 | import com.z.exoplayertest.BuildConfig;
13 | import com.z.exoplayertest.database.Channel;
14 | import com.z.exoplayertest.database.ChannelDao;
15 | import com.z.exoplayertest.utils.Density;
16 | import com.z.exoplayertest.utils.FileUtil;
17 |
18 | import java.util.List;
19 |
20 | public class BaseApplication extends Application {
21 | private ApplicationLike tinkerApplicationLike;
22 | @Override
23 | public void onCreate() {
24 | super.onCreate();
25 | Bugly.init(getApplicationContext(), "ff995c6793", false);
26 | Density.setDensity(this);
27 | ChannelDao channelDao = ChannelDao.getInstance(this);
28 | List list = channelDao.getAllData();
29 | if (list == null || list.size() < 1) {
30 | channelDao.addList(FileUtil.getChannelFromTxt(this));
31 | }
32 | // [可选]设置是否打开debug输出,上线时请关闭,Logcat标签为"MtaSDK"
33 | StatConfig.setDebugEnable(false);
34 | // 基础统计API
35 | StatService.registerActivityLifecycleCallbacks(this);
36 |
37 | if (BuildConfig.TINKER_ENABLE) {
38 | // 我们可以从这里获得Tinker加载过程的信息
39 | tinkerApplicationLike = TinkerPatchApplicationLike.getTinkerPatchApplicationLike();
40 |
41 | // 初始化TinkerPatch SDK, 更多配置可参照API章节中的,初始化SDK
42 | TinkerPatch.init(tinkerApplicationLike)
43 | .reflectPatchLibrary()
44 | .setPatchRollbackOnScreenOff(true)
45 | .setPatchRestartOnSrceenOff(true)
46 | .setFetchPatchIntervalByHours(3);
47 |
48 | // 每隔3个小时(通过setFetchPatchIntervalByHours设置)去访问后台时候有更新,通过handler实现轮训的效果
49 | TinkerPatch.with().fetchPatchUpdateAndPollWithInterval();
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/z/exoplayertest/view/BaseFragment.java:
--------------------------------------------------------------------------------
1 | package com.z.exoplayertest.view;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.os.Bundle;
6 | import android.view.Gravity;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.Toast;
11 |
12 | import androidx.annotation.Nullable;
13 | import androidx.appcompat.app.AppCompatActivity;
14 | import androidx.fragment.app.Fragment;
15 |
16 | public abstract class BaseFragment extends Fragment {
17 |
18 |
19 | protected Activity mActivity;
20 |
21 | @Override
22 | public void onAttach(Context context) {
23 | super.onAttach(context);
24 | mActivity = (AppCompatActivity) context;
25 | }
26 |
27 | @Override
28 | public void onCreate(@Nullable Bundle savedInstanceState) {
29 | super.onCreate(savedInstanceState);
30 | }
31 |
32 | @Nullable
33 | @Override
34 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
35 | return setLayoutView(inflater, container, savedInstanceState);
36 | }
37 |
38 | @Override
39 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
40 | findViewById(view);
41 | setViewData(view);
42 | setClickEvent(view);
43 | }
44 |
45 | public void showToast(String value){
46 | Toast toast = Toast.makeText(getContext(),value,Toast.LENGTH_SHORT);
47 | toast.setGravity(Gravity.CENTER,0,0);
48 | toast.show();
49 | }
50 | /**
51 | * 设置布局
52 | * @param inflater
53 | * @param container
54 | * @param savedInstanceState
55 | * @return
56 | */
57 | public abstract View setLayoutView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState);
58 |
59 | /**
60 | * findViewById
61 | */
62 | public void findViewById(View view) {
63 | }
64 |
65 | /**
66 | * setViewData
67 | */
68 | public void setViewData(View view) {
69 | }
70 |
71 | /**
72 | * setClickEvent
73 | */
74 | public void setClickEvent(View view) {
75 | }
76 |
77 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/z/exoplayertest/view/ChannelFragment.java:
--------------------------------------------------------------------------------
1 | package com.z.exoplayertest.view;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.text.Editable;
6 | import android.text.TextUtils;
7 | import android.text.TextWatcher;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.EditText;
12 | import android.widget.ImageView;
13 |
14 | import com.tencent.stat.StatService;
15 | import com.z.exoplayertest.R;
16 | import com.z.exoplayertest.adpter.ChannelAdaptr;
17 | import com.z.exoplayertest.database.Channel;
18 | import com.z.exoplayertest.database.ChannelDao;
19 |
20 | import java.util.List;
21 | import java.util.Properties;
22 |
23 | import androidx.recyclerview.widget.DefaultItemAnimator;
24 | import androidx.recyclerview.widget.DividerItemDecoration;
25 | import androidx.recyclerview.widget.LinearLayoutManager;
26 | import androidx.recyclerview.widget.RecyclerView;
27 |
28 | /**
29 | * http://www.hdpfans.com/thread-834810-1-2.html
30 | * http://www.hdpfans.com/thread-820088-1-1.html
31 | * https://exoplayer.dev/hls.html
32 | */
33 | public class ChannelFragment extends BaseFragment {
34 |
35 | private View view = null;
36 | private RecyclerView mRecyclerView;
37 | private RecyclerView.LayoutManager mLayoutManager;
38 | private EditText etQuery;
39 | private ImageView ivCancel;
40 | private ChannelAdaptr mAdapter;
41 | private List channelList;
42 | private ChannelDao channelDao;
43 | private Properties prop;
44 | private OnRefreshListener onRefreshListener;
45 | public static ChannelFragment instance = null;
46 |
47 | public ChannelFragment() {
48 | }
49 |
50 | public static ChannelFragment newInstance() {
51 | if (instance == null) {
52 | instance = new ChannelFragment();
53 | }
54 | Bundle args = new Bundle();
55 | instance.setArguments(args);
56 | return instance;
57 | }
58 |
59 | @Override
60 | public void onCreate(Bundle savedInstanceState) {
61 | super.onCreate(savedInstanceState);
62 | if (getArguments() != null) {
63 |
64 | }
65 | }
66 |
67 | @Override
68 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
69 | if (null != view) {
70 | ViewGroup parent = (ViewGroup) view.getParent();
71 | if (null != parent) {
72 | parent.removeView(view);
73 | }
74 | } else {
75 | view = setLayoutView(inflater, container, savedInstanceState);
76 | initData();
77 | initView(view);
78 | }
79 | return view;
80 | }
81 |
82 | private void initView(View view) {
83 | etQuery = view.findViewById(R.id.query);
84 | ivCancel = view.findViewById(R.id.cancel);
85 | ivCancel.setOnClickListener(new View.OnClickListener() {
86 | @Override
87 | public void onClick(View v) {
88 | etQuery.setText("");
89 | }
90 | });
91 | etQuery.addTextChangedListener(new TextWatcher() {
92 | @Override
93 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
94 |
95 | }
96 |
97 | @Override
98 | public void onTextChanged(CharSequence s, int start, int before, int count) {
99 | if (TextUtils.isEmpty(s.toString())){
100 | ivCancel.setVisibility(View.INVISIBLE);
101 | ivCancel.setEnabled(false);
102 | refresh();
103 | }else {
104 | ivCancel.setVisibility(View.VISIBLE);
105 | ivCancel.setEnabled(true);
106 | List list = channelDao.queryByName(s.toString());
107 | channelList.clear();
108 | channelList.addAll(list);
109 | if (mAdapter != null) {
110 | mAdapter.notifyDataSetChanged();
111 | }
112 | }
113 | }
114 |
115 | @Override
116 | public void afterTextChanged(Editable s) {
117 |
118 | }
119 | });
120 | mRecyclerView = view.findViewById(R.id.rv_main);
121 | mAdapter = new ChannelAdaptr(getContext(), channelList);
122 | mAdapter.setOnItemClickListener(new ChannelAdaptr.OnItemClickListener() {
123 | @Override
124 | public void onClickItem(int position, Channel channel, boolean isClickLike) {
125 | if (isClickLike) {
126 | int like;
127 | if (channel.getIsLike() == 1) {
128 | like = 0;
129 | } else {
130 | like = 1;
131 | statisticalLike(channelList.get(position).getName());
132 | }
133 | channelList.get(position).setIsLike(like);
134 | if (channelDao != null) {
135 | channelDao.update(new String[]{String.valueOf(like), String.valueOf(channel.getId())});
136 | }
137 | mAdapter.notifyDataSetChanged();
138 | onRefreshListener.onRefresh();
139 | } else {
140 | Intent intent = new Intent(getActivity(), MainActivity.class);
141 | intent.putExtra("name", channel.getName());
142 | intent.putExtra("url", channel.getUrl());
143 | intent.putExtra("id", channel.getId());
144 | intent.putExtra("isLike", channel.getIsLike());
145 | startActivityForResult(intent, 1000);
146 | statistical(channel.getName());
147 | }
148 | }
149 | });
150 | mLayoutManager = new LinearLayoutManager(getContext());
151 | //设置布局管理器
152 | mRecyclerView.setLayoutManager(mLayoutManager);
153 | //设置adapter
154 | mRecyclerView.setAdapter(mAdapter);
155 | //设置Item增加、移除动画
156 | mRecyclerView.setItemAnimator(new DefaultItemAnimator());
157 | //添加分割线
158 | // mRecyclerView.addItemDecoration(new DividerItemDecoration(
159 | // getActivity(), DividerItemDecoration.VERTICAL));
160 |
161 | }
162 |
163 | /**
164 | * 统计用户播放
165 | */
166 | private void statistical(String name) {
167 | // 统计按钮状态
168 | prop = new Properties();
169 | prop.setProperty("电视台", name);
170 | StatService.trackCustomKVEvent(getContext(), "播放情况", prop);
171 | }
172 |
173 | /**
174 | * 统计用户收藏
175 | */
176 | private void statisticalLike(String name) {
177 | // 统计按钮状态
178 | prop = new Properties();
179 | prop.setProperty("电视台", name);
180 | StatService.trackCustomKVEvent(getContext(), "收藏情况", prop);
181 | }
182 |
183 | private void initData() {
184 | channelDao = ChannelDao.getInstance(getContext());
185 | channelList = channelDao.getAllData();
186 | }
187 |
188 | @Override
189 | public View setLayoutView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
190 | return inflater.inflate(R.layout.fragment_channel, container, false);
191 | }
192 |
193 | /**
194 | * 刷新
195 | */
196 | public void refresh() {
197 | List list = channelDao.getAllData();
198 | channelList.clear();
199 | channelList.addAll(list);
200 | if (mAdapter != null) {
201 | mAdapter.notifyDataSetChanged();
202 | }
203 | }
204 |
205 | interface OnRefreshListener {
206 | void onRefresh();
207 | }
208 |
209 | public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
210 | this.onRefreshListener = onRefreshListener;
211 | }
212 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/z/exoplayertest/view/FetchPatchHandler.java:
--------------------------------------------------------------------------------
1 | package com.z.exoplayertest.view;
2 |
3 | import android.os.Handler;
4 | import android.os.Message;
5 |
6 | import com.tinkerpatch.sdk.TinkerPatch;
7 |
8 | /**
9 | * Created by Administrator on 2017/10/18 0018.
10 | */
11 |
12 | public class FetchPatchHandler extends Handler {
13 |
14 | public static final long HOUR_INTERVAL = 3600 * 1000;
15 | private long checkInterval;
16 |
17 |
18 | /**
19 | * 通过handler, 达到按照时间间隔轮训的效果
20 | */
21 | public void fetchPatchWithInterval(int hour) {
22 | //设置TinkerPatch的时间间隔
23 | TinkerPatch.with().setFetchPatchIntervalByHours(hour);
24 | checkInterval = hour * HOUR_INTERVAL;
25 | //立刻尝试去访问,检查是否有更新
26 | sendEmptyMessage(0);
27 | }
28 |
29 |
30 | @Override
31 | public void handleMessage(Message msg) {
32 | super.handleMessage(msg);
33 | //这里使用false即可
34 | TinkerPatch.with().fetchPatchUpdate(false);
35 | //每隔一段时间都去访问后台, 增加10分钟的buffer时间
36 | sendEmptyMessageDelayed(0, checkInterval + 10 * 60 * 1000);
37 | }
38 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/z/exoplayertest/view/LikeFragment.java:
--------------------------------------------------------------------------------
1 | package com.z.exoplayertest.view;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.text.Editable;
6 | import android.text.TextUtils;
7 | import android.text.TextWatcher;
8 | import android.view.LayoutInflater;
9 | import android.view.View;
10 | import android.view.ViewGroup;
11 | import android.widget.EditText;
12 | import android.widget.ImageView;
13 |
14 | import com.z.exoplayertest.R;
15 | import com.z.exoplayertest.adpter.ChannelAdaptr;
16 | import com.z.exoplayertest.adpter.LikeAdaptr;
17 | import com.z.exoplayertest.database.Channel;
18 | import com.z.exoplayertest.database.ChannelDao;
19 |
20 | import java.util.List;
21 |
22 | import androidx.recyclerview.widget.DefaultItemAnimator;
23 | import androidx.recyclerview.widget.LinearLayoutManager;
24 | import androidx.recyclerview.widget.RecyclerView;
25 |
26 | public class LikeFragment extends BaseFragment {
27 |
28 | private View view = null;
29 | private RecyclerView mRecyclerView;
30 | private RecyclerView.LayoutManager mLayoutManager;
31 | private EditText etQuery;
32 | private ImageView ivCancel;
33 | private LikeAdaptr mAdapter;
34 | private List channelList;
35 | private ChannelDao channelDao;
36 | OnRefreshListener onRefreshListener;
37 | public static LikeFragment instance = null;
38 |
39 | public LikeFragment() {
40 | }
41 |
42 | public static LikeFragment newInstance() {
43 | if (instance==null){
44 | instance = new LikeFragment();
45 | }
46 | Bundle args = new Bundle();
47 | instance.setArguments(args);
48 | return instance;
49 | }
50 |
51 | @Override
52 | public void onCreate(Bundle savedInstanceState) {
53 | super.onCreate(savedInstanceState);
54 | if (getArguments() != null) {
55 |
56 | }
57 | }
58 |
59 | @Override
60 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
61 | if (null != view) {
62 | ViewGroup parent = (ViewGroup) view.getParent();
63 | if (null != parent) {
64 | parent.removeView(view);
65 | }
66 | } else {
67 | view = setLayoutView(inflater, container, savedInstanceState);
68 | initData();
69 | initView(view);
70 | }
71 | return view;
72 | }
73 |
74 | private void initView(View view) {
75 | etQuery = view.findViewById(R.id.query);
76 | ivCancel = view.findViewById(R.id.cancel);
77 | ivCancel.setOnClickListener(new View.OnClickListener() {
78 | @Override
79 | public void onClick(View v) {
80 | etQuery.setText("");
81 | }
82 | });
83 | etQuery.addTextChangedListener(new TextWatcher() {
84 | @Override
85 | public void beforeTextChanged(CharSequence s, int start, int count, int after) {
86 |
87 | }
88 |
89 | @Override
90 | public void onTextChanged(CharSequence s, int start, int before, int count) {
91 | if (TextUtils.isEmpty(s.toString())){
92 | ivCancel.setVisibility(View.INVISIBLE);
93 | ivCancel.setEnabled(false);
94 | refresh();
95 | }else {
96 | ivCancel.setVisibility(View.VISIBLE);
97 | ivCancel.setEnabled(true);
98 | List list = channelDao.queryByNameAndLike(s.toString());
99 | channelList.clear();
100 | channelList.addAll(list);
101 | if (mAdapter != null) {
102 | mAdapter.notifyDataSetChanged();
103 | }
104 | }
105 | }
106 |
107 | @Override
108 | public void afterTextChanged(Editable s) {
109 |
110 | }
111 | });
112 | mRecyclerView = view.findViewById(R.id.rv_main);
113 | mAdapter = new LikeAdaptr(getContext(), channelList);
114 | mAdapter.setOnItemClickListener(new LikeAdaptr.OnItemClickListener() {
115 | @Override
116 | public void onClickItem(int position, Channel channel, boolean isClickLike) {
117 | if (isClickLike) {
118 | if (channelDao!=null){
119 | channelDao.update(new String[]{"0",String.valueOf(channel.getId())});
120 | }
121 | channelList.remove(position);
122 | mAdapter.notifyDataSetChanged();
123 | onRefreshListener.onRefresh();
124 | } else {
125 | Intent intent = new Intent(getActivity(), MainActivity.class);
126 | intent.putExtra("name", channel.getName());
127 | intent.putExtra("url", channel.getUrl());
128 | intent.putExtra("id", channel.getId());
129 | intent.putExtra("isLike", channel.getIsLike());
130 | startActivityForResult(intent,1000);
131 | }
132 | }
133 | });
134 | mLayoutManager = new LinearLayoutManager(getContext());
135 | //设置布局管理器
136 | mRecyclerView.setLayoutManager(mLayoutManager);
137 | //设置adapter
138 | mRecyclerView.setAdapter(mAdapter);
139 | //设置Item增加、移除动画
140 | mRecyclerView.setItemAnimator(new DefaultItemAnimator());
141 | //添加分割线
142 | // mRecyclerView.addItemDecoration(new DividerItemDecoration(
143 | // getActivity(), DividerItemDecoration.VERTICAL));
144 |
145 | }
146 |
147 | private void initData() {
148 | channelDao = ChannelDao.getInstance(getContext());
149 | channelList = channelDao.queryUserLike();
150 | }
151 |
152 | @Override
153 | public View setLayoutView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
154 | return inflater.inflate(R.layout.fragment_channel, container, false);
155 | }
156 |
157 | public void refresh(){
158 | List list = channelDao.queryUserLike();
159 | channelList.clear();
160 | channelList.addAll(list);
161 | if (mAdapter!=null){
162 | mAdapter.notifyDataSetChanged();
163 | }
164 | }
165 |
166 | interface OnRefreshListener {
167 | void onRefresh();
168 | }
169 |
170 | public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
171 | this.onRefreshListener = onRefreshListener;
172 | }
173 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/z/exoplayertest/view/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.z.exoplayertest.view;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Activity;
5 | import android.content.Intent;
6 | import android.content.pm.ActivityInfo;
7 | import android.content.res.Configuration;
8 | import android.net.Uri;
9 | import android.os.Build;
10 | import android.os.Bundle;
11 | import android.os.PowerManager;
12 | import android.view.Display;
13 | import android.view.MenuItem;
14 | import android.view.View;
15 | import android.view.ViewGroup;
16 | import android.view.WindowManager;
17 | import android.widget.ImageView;
18 | import android.widget.TextView;
19 | import android.widget.Toast;
20 |
21 | import com.google.android.exoplayer2.DefaultRenderersFactory;
22 | import com.google.android.exoplayer2.ExoPlaybackException;
23 | import com.google.android.exoplayer2.ExoPlayer;
24 | import com.google.android.exoplayer2.ExoPlayerFactory;
25 | import com.google.android.exoplayer2.PlaybackParameters;
26 | import com.google.android.exoplayer2.PlaybackPreparer;
27 | import com.google.android.exoplayer2.RenderersFactory;
28 | import com.google.android.exoplayer2.SimpleExoPlayer;
29 | import com.google.android.exoplayer2.ext.rtmp.RtmpDataSourceFactory;
30 | import com.google.android.exoplayer2.source.ExtractorMediaSource;
31 | import com.google.android.exoplayer2.source.MediaSource;
32 | import com.google.android.exoplayer2.source.TrackGroupArray;
33 | import com.google.android.exoplayer2.source.hls.DefaultHlsDataSourceFactory;
34 | import com.google.android.exoplayer2.source.hls.HlsMediaSource;
35 | import com.google.android.exoplayer2.trackselection.AdaptiveTrackSelection;
36 | import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
37 | import com.google.android.exoplayer2.trackselection.TrackSelection;
38 | import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
39 | import com.google.android.exoplayer2.ui.PlayerView;
40 | import com.google.android.exoplayer2.upstream.DataSource;
41 | import com.google.android.exoplayer2.upstream.DataSpec;
42 | import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
43 | import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
44 | import com.google.android.exoplayer2.upstream.HttpDataSource;
45 | import com.google.android.exoplayer2.util.EventLogger;
46 | import com.google.android.exoplayer2.util.Util;
47 | import com.tencent.stat.StatService;
48 | import com.z.exoplayertest.BuildConfig;
49 | import com.z.exoplayertest.R;
50 | import com.z.exoplayertest.database.ChannelDao;
51 |
52 | import java.io.IOException;
53 | import java.util.Properties;
54 |
55 | import androidx.appcompat.app.AppCompatActivity;
56 |
57 | import static com.google.android.exoplayer2.ui.PlayerView.SHOW_BUFFERING_ALWAYS;
58 |
59 | public class MainActivity extends AppCompatActivity implements View.OnClickListener {
60 |
61 | private DefaultTrackSelector trackSelector;
62 | private PlayerView playerView;
63 | private SimpleExoPlayer player;
64 | private Activity activity;
65 | private String url;
66 | private String tvName;
67 | private int isLike;
68 | private int id;
69 | /**
70 | * 保持屏幕常亮
71 | */
72 | private PowerManager.WakeLock mWakeLock;
73 | private ImageView ivFull;
74 | /**
75 | * 自适应高度
76 | */
77 | private boolean isWrapContent = false;
78 | private ImageView ivLike;
79 | private TextView tvError;
80 | private int like;
81 | private int width;
82 | private Properties prop;
83 |
84 | @SuppressLint("InvalidWakeLockTag")
85 | @Override
86 | protected void onCreate(Bundle savedInstanceState) {
87 | activity = this;
88 | super.onCreate(savedInstanceState);
89 | setContentView(R.layout.activity_main);
90 | setHalfTransparent();
91 | setFitSystemWindow(false);
92 | PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
93 | if (powerManager != null) {
94 | mWakeLock = powerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, "WakeLock");
95 | }
96 |
97 | Intent intent = getIntent();
98 | url = intent.getStringExtra("url");
99 | tvName = intent.getStringExtra("name");
100 | isLike = intent.getIntExtra("isLike", 0);
101 | id = intent.getIntExtra("id", 1);
102 | playerView = findViewById(R.id.player_view);
103 | tvError = findViewById(R.id.play_error);
104 | ivLike = findViewById(R.id.like);
105 | like = isLike;
106 | if (isLike == 1) {
107 | ivLike.setImageDrawable(getDrawable(R.mipmap.like));
108 | } else {
109 | ivLike.setImageDrawable(getDrawable(R.mipmap.unlike));
110 | }
111 | ivLike.setOnClickListener(this);
112 | Display display = getWindowManager().getDefaultDisplay();
113 | width = display.getWidth();
114 | playerView.getLayoutParams().height = (width / 16) * 9;
115 | TextView name = findViewById(R.id.tv_name);
116 | name.setText(tvName);
117 | playerView.setShowBuffering(SHOW_BUFFERING_ALWAYS);
118 | ivFull = findViewById(R.id.full);
119 | ivFull.setOnClickListener(this);
120 | findViewById(R.id.back).setOnClickListener(this);
121 | if (player != null && player.isPlaying()) {
122 | player.stop(true);
123 | player.release();
124 | player = null;
125 | }
126 | playerView.getVideoSurfaceView().setKeepScreenOn(true);
127 | try {
128 | TrackSelection.Factory trackSelectionFactory = new AdaptiveTrackSelection.Factory();
129 | RenderersFactory renderersFactory = buildRenderersFactory(false);
130 | trackSelector = new DefaultTrackSelector(trackSelectionFactory);
131 | trackSelector.setParameters(new DefaultTrackSelector.ParametersBuilder().build());
132 | Uri uri = Uri.parse(url);
133 | player = ExoPlayerFactory.newSimpleInstance(activity, renderersFactory, trackSelector);
134 | player.setPlayWhenReady(true);
135 | player.addAnalyticsListener(new EventLogger(trackSelector));
136 | playerView.setPlayer(player);
137 | playerView.setPlaybackPreparer(new PlaybackPreparer() {
138 | @Override
139 | public void preparePlayback() {
140 | if (tvError.getVisibility() == View.VISIBLE) {
141 | tvError.setVisibility(View.GONE);
142 | }
143 | player.retry();
144 | }
145 | });
146 | player.addListener(eventListener);
147 | if (url.contains("rtmp")) {
148 | //rtmp://58.200.131.2:1935/livetv/cctv1hd
149 | DataSource.Factory rtmpDataSourceFactory = new RtmpDataSourceFactory();
150 | MediaSource rtmpMediaSource = new ExtractorMediaSource.Factory(rtmpDataSourceFactory).createMediaSource(uri);
151 | player.prepare(rtmpMediaSource, true, false);
152 | }else if (url.contains("m3u8")){
153 | //hls
154 | // https://cdn.letv-cdn.com/2018/12/05/JOCeEEUuoteFrjCg/playlist.m3u8
155 | //http://ivi.bupt.edu.cn/hls/cctv1hd.m3u8
156 | DataSource.Factory hlsDataSourceFactory =
157 | new DefaultHttpDataSourceFactory(Util.getUserAgent(this, "MainActivity"));
158 | HlsMediaSource hlsMediaSource =
159 | new HlsMediaSource.Factory(hlsDataSourceFactory).createMediaSource(uri);
160 | player.prepare(hlsMediaSource, true, false);
161 | }else {
162 | //普通网络地址
163 | //https://media.w3.org/2010/05/sintel/trailer.mp4
164 | DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this, Util.getUserAgent(this, "MainActivity"));
165 | MediaSource mediaSource = new ExtractorMediaSource.Factory(dataSourceFactory).createMediaSource(uri);
166 | player.prepare(mediaSource, true, false);
167 | }
168 |
169 | } catch (Exception e) {
170 | e.printStackTrace();
171 | }
172 | }
173 |
174 | private ExoPlayer.EventListener eventListener = new ExoPlayer.EventListener() {
175 | @Override
176 | public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
177 | }
178 |
179 | @Override
180 | public void onLoadingChanged(boolean isLoading) {
181 | }
182 |
183 | @Override
184 | public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
185 | switch (playbackState) {
186 | case ExoPlayer.STATE_ENDED:
187 | //Stop playback and return to start position
188 | break;
189 | case ExoPlayer.STATE_READY:
190 | if (tvError.getVisibility() == View.VISIBLE) {
191 | tvError.setVisibility(View.GONE);
192 | }
193 | if (!isWrapContent) {
194 | //设置自适应高度
195 | playerView.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
196 | playerView.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
197 | isWrapContent = true;
198 | }
199 | break;
200 | case ExoPlayer.STATE_BUFFERING:
201 | break;
202 | case ExoPlayer.STATE_IDLE:
203 | break;
204 | default:
205 | }
206 | }
207 |
208 | @Override
209 | public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
210 | }
211 |
212 | @Override
213 | public void onPlayerError(ExoPlaybackException error) {
214 | if (error.type == ExoPlaybackException.TYPE_SOURCE) {
215 | tvError.setVisibility(View.VISIBLE);
216 | IOException cause = error.getSourceException();
217 | if (cause instanceof HttpDataSource.HttpDataSourceException) {
218 | // An HTTP error occurred.
219 | HttpDataSource.HttpDataSourceException httpError = (HttpDataSource.HttpDataSourceException) cause;
220 | // This is the request for which the error occurred.
221 | DataSpec requestDataSpec = httpError.dataSpec;
222 | // It's possible to find out more about the error both by casting and by
223 | // querying the cause.
224 | if (httpError instanceof HttpDataSource.InvalidResponseCodeException) {
225 | // Cast to InvalidResponseCodeException and retrieve the response code,
226 | // message and headers.
227 | } else {
228 | // Try calling httpError.getCause() to retrieve the underlying cause,
229 | // although note that it may be null.
230 | }
231 | }
232 | }
233 | }
234 | };
235 |
236 | @Override
237 | protected void onDestroy() {
238 | super.onDestroy();
239 | if (player != null) {
240 | player.stop(true);
241 | player.release();
242 | player = null;
243 | }
244 | }
245 |
246 | @Override
247 | public void onBackPressed() {
248 | setResult(like == isLike ? 100 : 200);
249 | super.onBackPressed();
250 | }
251 |
252 | @Override
253 | protected void onPause() {
254 | super.onPause();
255 | if (player != null) {
256 | player.stop(false);
257 | }
258 | if (mWakeLock != null) {
259 | mWakeLock.release();
260 | }
261 | }
262 |
263 | @Override
264 | protected void onResume() {
265 | super.onResume();
266 | if (mWakeLock != null) {
267 | mWakeLock.acquire();
268 | }
269 | }
270 |
271 | /**
272 | * Returns whether extension renderers should be used.
273 | */
274 | public boolean useExtensionRenderers() {
275 | return "withExtensions".equals(BuildConfig.FLAVOR);
276 | }
277 |
278 | public RenderersFactory buildRenderersFactory(boolean preferExtensionRenderer) {
279 | @DefaultRenderersFactory.ExtensionRendererMode
280 | int extensionRendererMode =
281 | useExtensionRenderers()
282 | ? (preferExtensionRenderer
283 | ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
284 | : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
285 | : DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF;
286 | return new DefaultRenderersFactory(this)
287 | .setExtensionRendererMode(extensionRendererMode);
288 | }
289 |
290 | /**
291 | * 半透明状态栏
292 | */
293 | protected void setHalfTransparent() {
294 |
295 | if (Build.VERSION.SDK_INT >= 21) {
296 | //21表示5.0
297 | View decorView = getWindow().getDecorView();
298 | int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
299 | decorView.setSystemUiVisibility(option);
300 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
301 |
302 | } else if (Build.VERSION.SDK_INT >= 19) {
303 | //19表示4.4
304 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
305 | //虚拟键盘也透明
306 | // getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
307 | }
308 | }
309 |
310 | /**
311 | * 如果需要内容紧贴着StatusBar
312 | * 应该在对应的xml布局文件中,设置根布局fitsSystemWindows=true。
313 | */
314 | private View contentViewGroup;
315 |
316 | protected void setFitSystemWindow(boolean fitSystemWindow) {
317 | if (contentViewGroup == null) {
318 | contentViewGroup = ((ViewGroup) findViewById(android.R.id.content)).getChildAt(0);
319 | }
320 | contentViewGroup.setFitsSystemWindows(fitSystemWindow);
321 | }
322 |
323 | @Override
324 | public void onWindowFocusChanged(boolean hasFocus) {
325 | super.onWindowFocusChanged(hasFocus);
326 | if (hasFocus && Build.VERSION.SDK_INT >= 19) {
327 | View decorView = getWindow().getDecorView();
328 | decorView.setSystemUiVisibility(
329 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
330 | | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
331 | | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
332 | | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
333 | | View.SYSTEM_UI_FLAG_FULLSCREEN
334 | | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
335 | }
336 | }
337 |
338 | @Override
339 | public void onClick(View v) {
340 | int id = v.getId();
341 | if (id == R.id.like) {
342 | if (like == 1) {
343 | like = 0;
344 | ivLike.setImageDrawable(getDrawable(R.mipmap.unlike));
345 | } else {
346 | like = 1;
347 | ivLike.setImageDrawable(getDrawable(R.mipmap.like));
348 | statisticalLike(tvName);
349 | }
350 | ChannelDao.getInstance(this).update(new String[]{String.valueOf(like), String.valueOf(this.id)});
351 | } else if (id == R.id.full) {
352 | //获取设置的配置信息
353 | Configuration mConfiguration = getResources().getConfiguration();
354 | //获取屏幕方向
355 | int ori = mConfiguration.orientation;
356 | if (ori == Configuration.ORIENTATION_PORTRAIT) {
357 | //竖屏 强制为横屏
358 | if (!isWrapContent) {
359 | playerView.getLayoutParams().width = ViewGroup.LayoutParams.MATCH_PARENT;
360 | playerView.getLayoutParams().height = ViewGroup.LayoutParams.WRAP_CONTENT;
361 | isWrapContent = true;
362 | }
363 | ivFull.setVisibility(View.GONE);
364 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
365 | }
366 | } else if (id == R.id.back) {
367 | //获取设置的配置信息
368 | Configuration mConfiguration = getResources().getConfiguration();
369 | //获取屏幕方向
370 | int ori = mConfiguration.orientation;
371 | if (ori == Configuration.ORIENTATION_LANDSCAPE) {
372 | //横屏 强制为竖屏
373 | ivFull.setVisibility(View.VISIBLE);
374 | setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
375 | if (!player.isPlaying()){
376 | playerView.getLayoutParams().height = (width / 16) * 9;
377 | isWrapContent = false;
378 | }
379 | } else if (ori == Configuration.ORIENTATION_PORTRAIT) {
380 | //竖屏 退出
381 | if (player != null) {
382 | player.stop();
383 | player.release();
384 | }
385 | setResult(like == isLike ? 100 : 200);
386 | finish();
387 | }
388 | }
389 | }
390 | /**
391 | * 统计用户收藏
392 | */
393 | private void statisticalLike(String name) {
394 | // 统计按钮状态
395 | prop = new Properties();
396 | prop.setProperty("电视台", name);
397 | StatService.trackCustomKVEvent(this, "收藏情况", prop);
398 | }
399 | }
400 |
--------------------------------------------------------------------------------
/app/src/main/java/com/z/exoplayertest/view/SelectTvActivity.java:
--------------------------------------------------------------------------------
1 | package com.z.exoplayertest.view;
2 |
3 | import android.Manifest;
4 | import android.app.Activity;
5 | import android.content.Intent;
6 | import android.content.pm.PackageManager;
7 | import android.graphics.Color;
8 | import android.graphics.Typeface;
9 | import android.os.Build;
10 | import android.os.Bundle;
11 | import android.text.TextPaint;
12 | import android.view.Display;
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 | import android.view.Window;
16 | import android.view.WindowManager;
17 | import android.widget.TextView;
18 |
19 | import com.google.android.material.tabs.TabLayout;
20 | import com.z.exoplayertest.adpter.PagerAdapter;
21 | import com.z.exoplayertest.R;
22 |
23 | import androidx.annotation.Nullable;
24 | import androidx.appcompat.app.AppCompatActivity;
25 | import androidx.appcompat.widget.AppCompatTextView;
26 | import androidx.core.app.ActivityCompat;
27 | import androidx.viewpager.widget.ViewPager;
28 |
29 | public class SelectTvActivity extends AppCompatActivity{
30 | private TabLayout tablayout;
31 | private ViewPager viewpager;
32 | private String[] titles = {"频道", "收藏", "设置"};
33 | private int textMinWidth = 0;
34 | private int textMaxWidth = 0;
35 | private boolean isClickTab;
36 | private float mLastPositionOffsetSum;
37 |
38 | @Override
39 | protected void onCreate(@Nullable Bundle savedInstanceState) {
40 | super.onCreate(savedInstanceState);
41 | setContentView(R.layout.activity_select_tv);
42 | setStatusBarFullTransparent();
43 | setAndroidNativeLightStatusBar(this,true);
44 | setFitSystemWindow(false);
45 | tablayout = findViewById(R.id.tablayout);
46 | viewpager = findViewById(R.id.viewpager);
47 | viewpager.setAdapter(new PagerAdapter(this, getSupportFragmentManager()));
48 | tablayout.setupWithViewPager(viewpager);
49 | checkPermission();
50 | initSize();
51 | for (int i = 0; i < titles.length; i++) {
52 | TabLayout.Tab tab = tablayout.getTabAt(i);
53 | assert tab != null;
54 | //给tab自定义样式
55 | tab.setCustomView(R.layout.tab_item);
56 | assert tab.getCustomView() != null;
57 | AppCompatTextView textView = tab.getCustomView().findViewById(R.id.tab_text);
58 | textView.setText(titles[i]);
59 | AppCompatTextView compatTextView = ((AppCompatTextView) tab.getCustomView().findViewById(R.id.tab_text));
60 | if (i == 0) {
61 | //第一个tab被选中
62 | compatTextView.setSelected(true);
63 | compatTextView.setWidth(textMaxWidth);
64 | compatTextView.setTypeface(Typeface.DEFAULT_BOLD);
65 | // ((WaveView) tab.getCustomView().findViewById(R.id.wave)).setWaveWidth(textMaxWidth, true);
66 | } else {
67 | compatTextView.setWidth(textMinWidth);
68 | // ((WaveView) tab.getCustomView().findViewById(R.id.wave)).setWaveWidth(textMinWidth, false);
69 | }
70 | }
71 |
72 | viewpager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
73 | @Override
74 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
75 | // 当前总的偏移量
76 | float currentPositionOffsetSum = position + positionOffset;
77 | // 上次滑动的总偏移量大于此次滑动的总偏移量,页面从右向左进入(手指从右向左滑动)
78 | boolean rightToLeft = mLastPositionOffsetSum <= currentPositionOffsetSum;
79 | if (currentPositionOffsetSum == mLastPositionOffsetSum) {
80 | return;
81 | }
82 | int enterPosition;
83 | int leavePosition;
84 | float percent;
85 | if (rightToLeft) {
86 | // 从右向左滑
87 | enterPosition = (positionOffset == 0.0f) ? position : position + 1;
88 | leavePosition = enterPosition - 1;
89 | percent = (positionOffset == 0.0f) ? 1.0f : positionOffset;
90 | } else {
91 | // 从左向右滑
92 | enterPosition = position;
93 | leavePosition = position + 1;
94 | percent = 1 - positionOffset;
95 | }
96 | if (!isClickTab) {
97 | int width = (int) (textMinWidth + (textMaxWidth - textMinWidth) * (1 - percent));
98 | ((AppCompatTextView) (tablayout.getTabAt(leavePosition).getCustomView().findViewById(R.id.tab_text)))
99 | .setWidth(width);
100 | ((AppCompatTextView) (tablayout.getTabAt(enterPosition).getCustomView().findViewById(R.id.tab_text)))
101 | .setWidth((int) (textMinWidth + (textMaxWidth - textMinWidth) * percent));
102 | }
103 |
104 | mLastPositionOffsetSum = currentPositionOffsetSum;
105 | }
106 |
107 | @Override
108 | public void onPageSelected(int position) {
109 | for (int i = 0; i < 3; i++) {
110 | TabLayout.Tab tab = tablayout.getTabAt(i);
111 | assert tab != null;
112 | if (i == position) {
113 | ((AppCompatTextView) (tab.getCustomView().findViewById(R.id.tab_text))).setTypeface(Typeface.DEFAULT_BOLD);
114 | // ((WaveView) tab.getCustomView().findViewById(R.id.wave)).setWaveWidth(textMaxWidth, true);
115 | } else {
116 | ((AppCompatTextView) (tab.getCustomView().findViewById(R.id.tab_text))).setTypeface(Typeface.DEFAULT);
117 | // ((WaveView) tab.getCustomView().findViewById(R.id.wave)).setWaveWidth(textMinWidth, false);
118 | }
119 | }
120 |
121 | }
122 |
123 | @Override
124 | public void onPageScrollStateChanged(int state) {
125 | if (state == 0) {
126 | isClickTab = false;
127 | }
128 | }
129 | });
130 |
131 | tablayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
132 | @Override
133 | public void onTabSelected(TabLayout.Tab tab) {
134 | isClickTab = true;
135 | tab.getCustomView().findViewById(R.id.tab_text).setSelected(true);
136 | viewpager.setCurrentItem(tab.getPosition());
137 | ((AppCompatTextView) (tab.getCustomView().findViewById(R.id.tab_text))).setWidth(textMaxWidth);
138 | // ((WaveView) tab.getCustomView().findViewById(R.id.wave)).setWaveWidth(textMaxWidth, true);
139 | }
140 |
141 | @Override
142 | public void onTabUnselected(TabLayout.Tab tab) {
143 | tab.getCustomView().findViewById(R.id.tab_text).setSelected(false);
144 | ((AppCompatTextView) (tab.getCustomView().findViewById(R.id.tab_text))).setWidth(textMinWidth);
145 | // ((WaveView) tab.getCustomView().findViewById(R.id.wave)).setWaveWidth(textMinWidth, false);
146 | }
147 |
148 | @Override
149 | public void onTabReselected(TabLayout.Tab tab) {
150 |
151 |
152 | }
153 | });
154 | viewpager.setCurrentItem(0);
155 | viewpager.setOffscreenPageLimit(2);
156 | ChannelFragment.newInstance().setOnRefreshListener(onRefreshLikeListener);
157 | LikeFragment.newInstance().setOnRefreshListener(onRefreshChannelListener);
158 | SettingFragment.newInstance().setOnRefreshListener(onRefreshListener);
159 | }
160 |
161 | private void initSize() {
162 | TextView tv = new TextView(this);
163 | tv.setTextSize(getResources().getDimension(R.dimen.title_no_selected));
164 | TextPaint textPaint = tv.getPaint();
165 | textMinWidth = (int) textPaint.measureText("频道");
166 | tv = new TextView(this);
167 | tv.setTextSize(getResources().getDimension(R.dimen.title_selected));
168 | textPaint = tv.getPaint();
169 | textMaxWidth = (int) textPaint.measureText("频道");
170 | }
171 |
172 | public void checkPermission() {
173 | boolean isGranted = true;
174 | if (android.os.Build.VERSION.SDK_INT >= 23) {
175 | if (this.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
176 | //如果没有写sd卡权限
177 | isGranted = false;
178 | }
179 | if (this.checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
180 | isGranted = false;
181 | }
182 | if (!isGranted) {
183 | ActivityCompat.requestPermissions(this,
184 | new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission
185 | .ACCESS_FINE_LOCATION,
186 | Manifest.permission.READ_EXTERNAL_STORAGE,
187 | Manifest.permission.WRITE_EXTERNAL_STORAGE},
188 | 102);
189 | }
190 | }
191 | }
192 |
193 | /**
194 | * 全透状态栏
195 | */
196 | protected void setStatusBarFullTransparent() {
197 | if (Build.VERSION.SDK_INT >= 21) {
198 | //21表示5.0
199 | Window window = getWindow();
200 | window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
201 | window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
202 | | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
203 | window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
204 | window.setStatusBarColor(Color.TRANSPARENT);
205 | } else if (Build.VERSION.SDK_INT >= 19) {
206 | //19表示4.4
207 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
208 | //虚拟键盘也透明
209 | //getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
210 | }
211 | }
212 |
213 | private static void setAndroidNativeLightStatusBar(Activity activity, boolean dark) {
214 | View decor = activity.getWindow().getDecorView();
215 | if (dark) {
216 | decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
217 | } else {
218 | decor.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
219 | }
220 | }
221 |
222 | /**
223 | * 如果需要内容紧贴着StatusBar
224 | * 应该在对应的xml布局文件中,设置根布局fitsSystemWindows=true。
225 | */
226 | private View contentViewGroup;
227 |
228 | protected void setFitSystemWindow(boolean fitSystemWindow) {
229 | if (contentViewGroup == null) {
230 | contentViewGroup = ((ViewGroup) findViewById(android.R.id.content)).getChildAt(0);
231 | }
232 | contentViewGroup.setFitsSystemWindows(fitSystemWindow);
233 | }
234 |
235 | /**
236 | * 刷新收藏页面
237 | */
238 | ChannelFragment.OnRefreshListener onRefreshLikeListener = new ChannelFragment.OnRefreshListener() {
239 | @Override
240 | public void onRefresh() {
241 | LikeFragment.newInstance().refresh();
242 | }
243 | };
244 |
245 | LikeFragment.OnRefreshListener onRefreshChannelListener = new LikeFragment.OnRefreshListener() {
246 | @Override
247 | public void onRefresh() {
248 | ChannelFragment.newInstance().refresh();
249 | }
250 | };
251 |
252 | SettingFragment.OnRefreshListener onRefreshListener = new SettingFragment.OnRefreshListener() {
253 | @Override
254 | public void onRefresh() {
255 | LikeFragment.newInstance().refresh();
256 | ChannelFragment.newInstance().refresh();
257 | }
258 | };
259 |
260 | @Override
261 | protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
262 | super.onActivityResult(requestCode, resultCode, data);
263 | if (resultCode==200){
264 | ChannelFragment.newInstance().refresh();
265 | LikeFragment.newInstance().refresh();
266 | }
267 | }
268 | }
269 |
--------------------------------------------------------------------------------
/app/src/main/java/com/z/exoplayertest/view/SettingFragment.java:
--------------------------------------------------------------------------------
1 | package com.z.exoplayertest.view;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.Context;
5 | import android.content.DialogInterface;
6 | import android.content.pm.PackageInfo;
7 | import android.content.pm.PackageManager;
8 | import android.os.Bundle;
9 | import android.os.Environment;
10 | import android.text.TextUtils;
11 | import android.view.LayoutInflater;
12 | import android.view.View;
13 | import android.view.ViewGroup;
14 | import android.view.inputmethod.InputMethodManager;
15 | import android.widget.EditText;
16 | import android.widget.LinearLayout;
17 | import android.widget.TextView;
18 |
19 | import com.tencent.bugly.Bugly;
20 | import com.tencent.bugly.beta.Beta;
21 | import com.tencent.bugly.crashreport.CrashReport;
22 | import com.tencent.stat.StatService;
23 | import com.z.exoplayertest.R;
24 | import com.z.exoplayertest.database.Channel;
25 | import com.z.exoplayertest.database.ChannelDao;
26 | import com.z.exoplayertest.utils.FileUtil;
27 |
28 | import java.io.File;
29 | import java.util.Properties;
30 |
31 | import androidx.appcompat.app.AlertDialog;
32 |
33 | public class SettingFragment extends BaseFragment implements View.OnClickListener {
34 |
35 | private View view = null;
36 | private LinearLayout llCreateChannel;
37 | private LinearLayout llReset;
38 | private LinearLayout llCheckUpdate;
39 | private LinearLayout llFeedback;
40 | private AlertDialog.Builder builder;
41 | private ChannelDao channelDao;
42 | private Properties prop;
43 | private OnRefreshListener onRefreshListener;
44 | public static SettingFragment instance = null;
45 |
46 | public SettingFragment() {
47 | }
48 |
49 | public static SettingFragment newInstance() {
50 | if (instance == null) {
51 | instance = new SettingFragment();
52 | }
53 | Bundle args = new Bundle();
54 | instance.setArguments(args);
55 | return instance;
56 | }
57 |
58 | @Override
59 | public void onCreate(Bundle savedInstanceState) {
60 | super.onCreate(savedInstanceState);
61 | if (getArguments() != null) {
62 |
63 | }
64 | }
65 |
66 | @Override
67 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
68 | if (null != view) {
69 | ViewGroup parent = (ViewGroup) view.getParent();
70 | if (null != parent) {
71 | parent.removeView(view);
72 | }
73 | } else {
74 | view = setLayoutView(inflater, container, savedInstanceState);
75 | initView(view);
76 | }
77 | return view;
78 | }
79 |
80 | private void initView(View view) {
81 | channelDao = ChannelDao.getInstance(getContext());
82 | llCreateChannel = view.findViewById(R.id.create_channel);
83 | llCreateChannel.setOnClickListener(this);
84 | llReset = view.findViewById(R.id.reset_channel);
85 | llReset.setOnClickListener(this);
86 | llCheckUpdate = view.findViewById(R.id.check_update);
87 | llCheckUpdate.setOnClickListener(this);
88 | llFeedback = view.findViewById(R.id.feedback);
89 | llFeedback.setOnClickListener(this);
90 | TextView tvAppName = view.findViewById(R.id.app_name);
91 | tvAppName.setText(getString(R.string.app_name) + " " + versionName(getContext()));
92 | }
93 |
94 | @Override
95 | public View setLayoutView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
96 | return inflater.inflate(R.layout.fragment_setting, container, false);
97 | }
98 |
99 | @Override
100 | public void onClick(View v) {
101 | int id = v.getId();
102 | if (id == R.id.create_channel) {
103 | showAddChannelDialog();
104 | } else if (id == R.id.reset_channel) {
105 | //重新读取文件中的数据
106 | showResetDialog();
107 | } else if (id == R.id.check_update) {
108 | Beta.checkUpgrade();
109 | } else if (id == R.id.feedback) {
110 | // CrashReport.testJavaCrash();
111 | showFeedbackDialog();
112 | }
113 | }
114 |
115 | public String versionName(Context context) {
116 | PackageManager manager = context.getPackageManager();
117 | String name = null;
118 | try {
119 | PackageInfo info = manager.getPackageInfo(context.getPackageName(), 0);
120 | name = info.versionName;
121 | } catch (PackageManager.NameNotFoundException e) {
122 | e.printStackTrace();
123 | }
124 | return name;
125 | }
126 |
127 | /**
128 | * 添加频道弹窗
129 | */
130 | private void showAddChannelDialog() {
131 | View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_edit, null);
132 | final EditText etName = view.findViewById(R.id.name);
133 | final EditText etUrl = view.findViewById(R.id.url);
134 |
135 | final AlertDialog alertDialog = new AlertDialog.Builder(getActivity()).setPositiveButton("添加", null).create();
136 | alertDialog.setTitle("添加频道");
137 | alertDialog.setIcon(R.mipmap.ic_launcher);
138 | alertDialog.setView(view);
139 | alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
140 | @Override
141 | public void onShow(DialogInterface dialog) {
142 | //为了避免点击 positive 按钮后直接关闭 dialog,把点击事件拿出来设置
143 | alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
144 | .setOnClickListener(new View.OnClickListener() {
145 | @Override
146 | public void onClick(View v) {
147 | String name = etName.getText().toString();
148 | String url = etUrl.getText().toString();
149 | if (TextUtils.isEmpty(name) || TextUtils.isEmpty(url)) {
150 | showToast("名称和链接不能为空");
151 | return;
152 | }
153 | Channel channel = new Channel(name, url, 1);
154 | channelDao.add(channel);
155 | FileUtil.writeFile(FileUtil.USER_CHANNEL_FILE_PATH, name + "&&" + url);
156 | onRefreshListener.onRefresh();
157 | showToast("频道添加成功");
158 | alertDialog.dismiss();
159 | InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
160 | inputMethodManager.hideSoftInputFromWindow(etUrl.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
161 | statisticalAdd(name + "&&" + url);
162 | }
163 | });
164 | }
165 | });
166 | alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "取消", new DialogInterface.OnClickListener() {
167 | @Override
168 | public void onClick(DialogInterface dialog, int which) {
169 | InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
170 | inputMethodManager.hideSoftInputFromWindow(etUrl.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
171 | dialog.dismiss();
172 | }
173 | });
174 | alertDialog.show();
175 | alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(getContext().getResources().getColor(R.color.black));
176 | alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setTextColor(getContext().getResources().getColor(R.color.black));
177 | alertDialog.setCancelable(false);
178 | }
179 |
180 | /**
181 | * 反馈弹窗
182 | */
183 | private void showFeedbackDialog() {
184 | View view = LayoutInflater.from(getContext()).inflate(R.layout.dialog_feedback, null);
185 | final EditText editText = view.findViewById(R.id.dialog_feedback);
186 | final AlertDialog alertDialog = new AlertDialog.Builder(getActivity()).setPositiveButton("提交", null).create();
187 | alertDialog.setTitle("反馈与建议");
188 | alertDialog.setIcon(R.mipmap.ic_launcher);
189 | alertDialog.setView(view);
190 | alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
191 | @Override
192 | public void onShow(final DialogInterface dialog) {
193 | //为了避免点击 positive 按钮后直接关闭 dialog,把点击事件拿出来设置
194 | alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
195 | .setOnClickListener(new View.OnClickListener() {
196 | @Override
197 | public void onClick(View v) {
198 | String str = editText.getText().toString();
199 | if (str.length() < 5) {
200 | showToast("请输入五个字以上");
201 | return;
202 | }
203 | InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
204 | inputMethodManager.hideSoftInputFromWindow(editText.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
205 | statisticalFeedBack(str);
206 | alertDialog.dismiss();
207 | showToast("感谢您的宝贵意见");
208 | }
209 | });
210 | }
211 | });
212 | alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "取消", new DialogInterface.OnClickListener() {
213 | @Override
214 | public void onClick(DialogInterface dialog, int which) {
215 | InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
216 | inputMethodManager.hideSoftInputFromWindow(editText.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
217 | dialog.dismiss();
218 | }
219 | });
220 | alertDialog.show();
221 | alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(getContext().getResources().getColor(R.color.black));
222 | alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setTextColor(getContext().getResources().getColor(R.color.black));
223 | alertDialog.setCancelable(false);
224 | }
225 |
226 | /**
227 | * 重置
228 | */
229 | private void showResetDialog() {
230 | final AlertDialog alertDialog = new AlertDialog.Builder(getActivity()).setPositiveButton("继续", null).create();
231 | alertDialog.setTitle("更新");
232 | alertDialog.setMessage("是否更新频道列表?");
233 | alertDialog.setIcon(R.mipmap.ic_launcher);
234 | alertDialog.setOnShowListener(new DialogInterface.OnShowListener() {
235 | @Override
236 | public void onShow(final DialogInterface dialog) {
237 | //为了避免点击 positive 按钮后直接关闭 dialog,把点击事件拿出来设置
238 | alertDialog.getButton(AlertDialog.BUTTON_POSITIVE)
239 | .setOnClickListener(new View.OnClickListener() {
240 | @Override
241 | public void onClick(View v) {
242 | channelDao.deleteAll();
243 | channelDao.addList(FileUtil.getChannelFromTxt(getContext()));
244 | onRefreshListener.onRefresh();
245 | showToast("更新成功");
246 | dialog.dismiss();
247 | }
248 | });
249 | }
250 | });
251 | alertDialog.setButton(AlertDialog.BUTTON_NEGATIVE, "取消", new DialogInterface.OnClickListener() {
252 | @Override
253 | public void onClick(DialogInterface dialog, int which) {
254 | dialog.dismiss();
255 | }
256 | });
257 | alertDialog.show();
258 | alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(getContext().getResources().getColor(R.color.black));
259 | alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setTextColor(getContext().getResources().getColor(R.color.black));
260 | alertDialog.setCancelable(false);
261 | }
262 |
263 | /**
264 | * 统计用户新增电视台
265 | */
266 | private void statisticalAdd(String name) {
267 | // 统计按钮状态
268 | prop = new Properties();
269 | prop.setProperty("电视台", name);
270 | StatService.trackCustomKVEvent(getContext(), "新增电视台", prop);
271 | }
272 |
273 | /**
274 | * 统计用户反馈
275 | */
276 | private void statisticalFeedBack(String value) {
277 | // 统计按钮状态
278 | prop = new Properties();
279 | prop.setProperty("内容", value);
280 | StatService.trackCustomKVEvent(getContext(), "反馈", prop);
281 | }
282 |
283 | interface OnRefreshListener {
284 | void onRefresh();
285 | }
286 |
287 | public void setOnRefreshListener(OnRefreshListener onRefreshListener) {
288 | this.onRefreshListener = onRefreshListener;
289 | }
290 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/z/exoplayertest/view/WaveView.java:
--------------------------------------------------------------------------------
1 | package com.z.exoplayertest.view;
2 |
3 | import android.content.Context;
4 | import android.graphics.Canvas;
5 | import android.graphics.Color;
6 | import android.graphics.CornerPathEffect;
7 | import android.graphics.Paint;
8 | import android.graphics.Path;
9 | import android.util.AttributeSet;
10 | import android.util.DisplayMetrics;
11 | import android.util.TypedValue;
12 | import android.view.View;
13 |
14 | import com.z.exoplayertest.R;
15 |
16 | import java.util.Random;
17 |
18 | import androidx.annotation.Nullable;
19 |
20 | public class WaveView extends View {
21 | private Paint mPaint;
22 |
23 | private Path mPath;
24 |
25 | private float mDrawHeight;
26 |
27 | private float mDrawWidth;
28 |
29 | private float amplitude[];
30 | private float waveWidth;
31 | private float waveStart, waveEnd;
32 | private boolean isMax = true;
33 | private Context context;
34 |
35 | public WaveView(Context context, @Nullable AttributeSet attrs) {
36 | super(context, attrs);
37 | this.context = context;
38 | init();
39 | }
40 |
41 | private void init() {
42 | mPath = new Path();
43 | mPaint = new Paint();
44 | mPaint.setAntiAlias(true);
45 | mPaint.setDither(true);
46 | mPaint.setStrokeWidth(1.5f);
47 | mPaint.setStyle(Paint.Style.STROKE);
48 | mPaint.setColor(context.getColor(R.color.colorTitle));
49 | CornerPathEffect cornerPathEffect = new CornerPathEffect(300);
50 | mPaint.setPathEffect(cornerPathEffect);
51 | }
52 |
53 | @Override
54 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
55 | widthMeasureSpec = measureWidth(widthMeasureSpec);
56 | heightMeasureSpec = measureHeight(heightMeasureSpec);
57 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
58 | int paddingLeft = getPaddingLeft();
59 | int paddingRight = getPaddingRight();
60 | int paddingTop = getPaddingTop();
61 | int paddingBottom = getPaddingBottom();
62 | mDrawWidth = getMeasuredWidth() - paddingLeft - paddingRight;
63 | mDrawHeight = getMeasuredHeight() - paddingTop - paddingBottom;
64 | initOthers();
65 | }
66 |
67 | public void setWaveWidth(float waveWidth, boolean isMax) {
68 | this.waveWidth = waveWidth;
69 | this.isMax = isMax;
70 | invalidate();
71 | }
72 |
73 | private void initOthers() {
74 | waveStart = (mDrawWidth - waveWidth) / 2;
75 | waveEnd = waveStart + waveWidth;
76 | float mAmplitude = isMax ? mDrawHeight / 2 : mDrawHeight / 4;
77 | amplitude = new float[20];
78 | Random random = new Random();
79 | for (int i = 0; i < 20; i++) {
80 | if (i % 2 == 0) {
81 | amplitude[i] = mDrawHeight / 2 + (random.nextFloat() + 0.3f) * mAmplitude;
82 | } else {
83 | amplitude[i] = mDrawHeight / 2 - (random.nextFloat() + 0.3f) * mAmplitude;
84 | }
85 | }
86 | }
87 |
88 | private int measureWidth(int spec) {
89 | int mode = MeasureSpec.getMode(spec);
90 | if (mode == MeasureSpec.UNSPECIFIED) {
91 | DisplayMetrics dm = getResources().getDisplayMetrics();
92 | int width = dm.widthPixels;
93 | spec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
94 | } else if (mode == MeasureSpec.AT_MOST) {
95 | int value = MeasureSpec.getSize(spec);
96 | spec = MeasureSpec.makeMeasureSpec(value, MeasureSpec.EXACTLY);
97 | }
98 | return spec;
99 | }
100 |
101 | private int measureHeight(int spec) {
102 | int mode = MeasureSpec.getMode(spec);
103 | if (mode == MeasureSpec.EXACTLY) {
104 | return spec;
105 | }
106 |
107 | // 其他模式下的最大高度
108 | int height = (int) dip2px(50);
109 |
110 | if (mode == MeasureSpec.AT_MOST) {
111 | int preValue = MeasureSpec.getSize(spec);
112 | if (preValue < height) {
113 | height = preValue;
114 | }
115 | }
116 | spec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
117 | return spec;
118 | }
119 |
120 | private float dip2px(float dp) {
121 | DisplayMetrics dm = getResources().getDisplayMetrics();
122 | return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, dm);
123 | }
124 |
125 | @Override
126 | protected void onDraw(Canvas canvas) {
127 | mPath.reset();
128 | mPath.moveTo(0, mDrawHeight / 2);
129 | mPath.lineTo(waveStart, mDrawHeight / 2);
130 | //使前端直线慢慢过渡,不要太平滑
131 | for (int i = 0; i < 6; i++) {
132 | if (amplitude[0] > 0) {
133 | mPath.lineTo(waveStart, mDrawHeight / 2 + i * 2);
134 | } else {
135 | mPath.lineTo(waveStart, mDrawHeight / 2 - i * 2);
136 | }
137 | }
138 |
139 | for (int i = 0; i < amplitude.length; i++) {
140 | mPath.lineTo(waveStart + i * waveWidth / 20, amplitude[i]);
141 | }
142 | mPath.lineTo(waveEnd, mDrawHeight / 2);
143 | //使尾端直线慢慢过渡,不要太平滑
144 | for (int i = 0; i < 6; i++) {
145 | mPath.lineTo(waveEnd + i * 2, mDrawHeight / 2);
146 | }
147 | mPath.lineTo(mDrawWidth + 40, mDrawHeight / 2);
148 | canvas.drawPath(mPath, mPaint);
149 | }
150 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/edit_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/player_bottom_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ripple.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/search_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
21 |
22 |
31 |
32 |
33 |
36 |
37 |
48 |
49 |
56 |
57 |
58 |
64 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_select_tv.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
22 |
23 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/channel_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
19 |
26 |
27 |
28 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_edit.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
14 |
23 |
24 |
33 |
34 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_feedback.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_channel.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
18 |
19 |
30 |
31 |
41 |
42 |
43 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
17 |
18 |
24 |
25 |
31 |
32 |
41 |
42 |
43 |
51 |
52 |
59 |
60 |
68 |
69 |
76 |
77 |
78 |
84 |
85 |
93 |
94 |
99 |
100 |
108 |
109 |
116 |
117 |
118 |
124 |
125 |
134 |
135 |
140 |
141 |
149 |
150 |
157 |
158 |
159 |
165 |
166 |
175 |
176 |
181 |
182 |
189 |
190 |
191 |
198 |
199 |
208 |
209 |
214 |
215 |
223 |
224 |
231 |
232 |
233 |
234 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/playback_control_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
24 |
25 |
26 |
32 |
33 |
37 |
38 |
47 |
48 |
57 |
58 |
59 |
62 |
63 |
73 |
74 |
83 |
84 |
91 |
92 |
102 |
103 |
113 |
114 |
115 |
116 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/tab_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/add.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xhdpi/add.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/back.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xhdpi/back.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/cancel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xhdpi/cancel.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/delete.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xhdpi/delete.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/donation.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xhdpi/donation.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/feedback.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xhdpi/feedback.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/full.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xhdpi/full.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_black.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xhdpi/ic_launcher_black.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/like.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xhdpi/like.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xhdpi/pause.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xhdpi/play.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/reset.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xhdpi/reset.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xhdpi/right.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/unlike.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xhdpi/unlike.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/update.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xhdpi/update.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-zh/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 轻TV
4 | 设置
5 | 请输入地址
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #C5C4C4
4 | #3D3D3D
5 | #F3F2F2
6 | #000000
7 | #C9C8C8
8 | #818080
9 | #FFFFFF
10 | #00FFFFFF
11 | #292929
12 | #F3F1F1
13 |
14 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 | 14sp
4 | 6sp
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Light TV
3 | Settings
4 | Please enter url
5 | 通过百度搜索关键字:电视台名称+RTMP直播地址,然后把搜索到的以RTMP开头的链接拷贝到下方。
6 | 注:如果需要批量增加,则在手机根目录下找到channel.txt文件,如果没有则新建该文件,在文件内一个频道占一行,格式为:频道名称+&&+链接
7 |
8 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/test/java/com/z/exoplayertest/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.z.exoplayertest;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/app/tinkerpatch.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'tinkerpatch-support'
2 |
3 | /**
4 | * TODO: 请按自己的需求修改为适应自己工程的参数
5 | */
6 |
7 | //基包路径
8 | def bakPath = file("${buildDir}/bakApk/")
9 | //基包文件夹名(打补丁包的时候,需要修改)
10 | def baseInfo = "app-1.0.0-0331-09-22-36"
11 | //版本名称
12 | def variantName = "debug"
13 |
14 | /**
15 | * 对于插件各参数的详细解析请参考
16 | *
17 | */
18 | tinkerpatchSupport {
19 | //可以在debug的时候关闭 tinkerPatch
20 | tinkerEnable = false
21 | //是否使用一键接入功能 默认为false 是否反射 Application 实现一键接入;
22 | // 一般来说,接入 Tinker 我们需要改造我们的 Application, 若这里为 true, 即我们无需对应用做任何改造即可接入。
23 | reflectApplication = true
24 | //将每次编译产生的 apk/mapping.txt/R.txt 归档存储的位置
25 | autoBackupApkPath = "${bakPath}"
26 | appKey = "90c78bae1d82ba20"// 注意!!! 需要修改成你的appkey
27 |
28 | /** 注意: 若发布新的全量包, appVersion一定要更新 **/
29 | appVersion = "1.0.0"
30 |
31 | def pathPrefix = "${bakPath}/${baseInfo}/${variantName}/"
32 | def name = "${project.name}-${variantName}"
33 | /**
34 | * 基准包的文件路径, 对应 tinker 插件中的 oldApk 参数;编译补丁包时,
35 | * 必需指定基准版本的 apk,默认值为空,则表示不是进行补丁包的编译
36 | */
37 | baseApkFile = "${pathPrefix}/${name}.apk"
38 |
39 | /**
40 | * 基准包的 Proguard mapping.txt 文件路径, 对应 tinker 插件 applyMapping 参数;在编译新的 apk 时候,
41 | * 我们希望通过保持基准 apk 的 proguard 混淆方式,
42 | * 从而减少补丁包的大小。这是强烈推荐的,编译补丁包时,我们推荐输入基准 apk 生成的 mapping.txt 文件。
43 | */
44 | baseProguardMappingFile = "${pathPrefix}/${name}-mapping.txt"
45 | /**
46 | * 基准包的资源 R.txt 文件路径, 对应 tinker 插件 applyResourceMapping 参数;在编译新的apk时候,
47 | * 我们希望通基准 apk 的 R.txt 文件来保持 Resource Id 的分配,这样不仅可以减少补丁包的大小,
48 | * 同时也避免由于 Resource Id 改变导致 remote view 异常
49 | */
50 | baseResourceRFile = "${pathPrefix}/${name}-R.txt"
51 | /**
52 | * 若有编译多flavors需求, 可以参照: https://github.com/TinkerPatch/tinkerpatch-flavors-sample
53 | * 注意: 除非你不同的flavor代码是不一样的,不然建议采用zip comment或者文件方式生成渠道信息(相关工具:walle 或者 packer-ng)
54 | **/
55 | }
56 |
57 | /**
58 | * 用于用户在代码中判断tinkerPatch是否被使能
59 | */
60 | android {
61 | defaultConfig {
62 | buildConfigField "boolean", "TINKER_ENABLE", "${tinkerpatchSupport.tinkerEnable}"
63 | }
64 | }
65 | /**
66 | * 一般来说,我们无需对下面的参数做任何的修改
67 | * 对于各参数的详细介绍请参考:
68 | * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
69 | */
70 | tinkerPatch {
71 | ignoreWarning = false
72 | useSign = true //是否需要签名,打正式包如果这里是true,则要配置签名,否则会编译不过去
73 | dex {
74 | dexMode = "jar"
75 | pattern = ["classes*.dex"]
76 | loader = []
77 | }
78 | lib {
79 | pattern = ["lib/*/*.so"]
80 | }
81 |
82 | res {
83 | pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
84 | ignoreChange = []
85 | largeModSize = 100
86 | }
87 | packageConfig {
88 | }
89 | sevenZip {
90 | zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
91 | // path = "/usr/local/bin/7za"
92 | }
93 | buildConfig {
94 | keepDexApply = false
95 | }
96 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | google()
6 | jcenter()
7 |
8 | }
9 | dependencies {
10 | classpath 'com.android.tools.build:gradle:3.5.2'
11 | // TinkerPatch 热修复插件
12 | classpath "com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:1.2.14.6"
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | allprojects {
19 | repositories {
20 | google()
21 | jcenter()
22 |
23 | }
24 | }
25 |
26 | task clean(type: Delete) {
27 | delete rootProject.buildDir
28 | }
29 |
30 | ext {
31 | exoplayer_version = '2.10.8'
32 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 |
21 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Mar 30 11:28:30 CST 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/image/test.txt:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/image/微信图片_20200529163913.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/image/微信图片_20200529163913.jpg
--------------------------------------------------------------------------------
/image/微信图片_20200529163927.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/image/微信图片_20200529163927.jpg
--------------------------------------------------------------------------------
/image/微信图片_20200529163933.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/image/微信图片_20200529163933.jpg
--------------------------------------------------------------------------------
/image/微信图片_20200529164235.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/image/微信图片_20200529164235.jpg
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 | rootProject.name='ExoPlayerTest'
3 |
--------------------------------------------------------------------------------
/轻TV.1.0.0.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/virtualC9/ExoPlayerTest/414dafe604763b78a1567d7829567af379685296/轻TV.1.0.0.apk
--------------------------------------------------------------------------------