直播配置独立设置(兼容直接传入.txt,.m3u的直播地址);优化分类筛选即时生效

pull/142/head
于俊 1 year ago
parent 1b116f1b04
commit b23e08de5b
  1. 272
      app/src/main/java/com/github/tvbox/osc/api/ApiConfig.java
  2. 13
      app/src/main/java/com/github/tvbox/osc/ui/activity/HomeActivity.java
  3. 5
      app/src/main/java/com/github/tvbox/osc/ui/activity/SettingActivity.java
  4. 47
      app/src/main/java/com/github/tvbox/osc/ui/dialog/ApiDialog.java
  5. 2
      app/src/main/java/com/github/tvbox/osc/ui/dialog/GridFilterDialog.java
  6. 101
      app/src/main/java/com/github/tvbox/osc/ui/fragment/GridFragment.java
  7. 42
      app/src/main/java/com/github/tvbox/osc/util/FileUtils.java
  8. 1
      app/src/main/java/com/github/tvbox/osc/util/HawkConfig.java
  9. 1
      app/src/main/java/com/github/tvbox/osc/util/VideoParseRuler.java
  10. 155
      app/src/main/res/layout/dialog_api.xml
  11. 1
      app/src/main/res/values/dimens.xml
  12. 2
      gradle.properties

@ -20,6 +20,7 @@ import com.github.tvbox.osc.server.ControlManager;
import com.github.tvbox.osc.util.AES;
import com.github.tvbox.osc.util.AdBlocker;
import com.github.tvbox.osc.util.DefaultConfig;
import com.github.tvbox.osc.util.FileUtils;
import com.github.tvbox.osc.util.HawkConfig;
import com.github.tvbox.osc.util.LOG;
import com.github.tvbox.osc.util.M3u8;
@ -73,15 +74,18 @@ public class ApiConfig {
private JarLoader jarLoader = new JarLoader();
private JsLoader jsLoader = new JsLoader();
private Gson gson;
private String userAgent = "okhttp/3.15";
private String requestAccept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9";
private String defaultLiveObjString="{\"lives\":[{\"name\":\"txt_m3u\",\"type\":0,\"url\":\"txt_m3u_url\"}]}";
private ApiConfig() {
sourceBeanList = new LinkedHashMap<>();
liveChannelGroupList = new ArrayList<>();
parseBeanList = new ArrayList<>();
gson = new Gson();
}
public static ApiConfig get() {
@ -133,23 +137,8 @@ public class ApiConfig {
return "".getBytes();
}
public void loadConfig(boolean useCache, LoadConfigCallback callback, Activity activity) {
String apiUrl = Hawk.get(HawkConfig.API_URL, "");
if (apiUrl.isEmpty()) {
callback.error("-1");
return;
}
File cache = new File(App.getInstance().getFilesDir().getAbsolutePath() + "/" + MD5.encode(apiUrl));
if (useCache && cache.exists()) {
try {
parseJson(apiUrl, cache);
callback.success();
return;
} catch (Throwable th) {
th.printStackTrace();
}
}
String TempKey = null, configUrl = "", pk = ";pk;";
private String configUrl(String apiUrl){
String configUrl = "", pk = ";pk;";
apiUrl=apiUrl.replace("file://", "clan://localhost/");
if (apiUrl.contains(pk)) {
String[] a = apiUrl.split(pk);
@ -168,9 +157,98 @@ public class ApiConfig {
} else {
configUrl = apiUrl;
}
return configUrl;
}
private String TempKey = null;
public void loadConfig(boolean useCache, LoadConfigCallback callback, Activity activity) {
String apiUrl = Hawk.get(HawkConfig.API_URL, "");
if (apiUrl.isEmpty()) {
callback.error("-1");
return;
}
//独立加载直播配置
String liveApiUrl = Hawk.get(HawkConfig.LIVE_API_URL, "");
String liveApiConfigUrl=configUrl(liveApiUrl);
if(!liveApiUrl.isEmpty() && !liveApiUrl.equals(apiUrl)){
if(liveApiUrl.contains(".txt") || liveApiUrl.contains(".m3u")){
initLiveSettings();
defaultLiveObjString = defaultLiveObjString.replace("txt_m3u_url",liveApiConfigUrl);
parseLiveJson(liveApiUrl,defaultLiveObjString);
}else {
File live_cache = new File(App.getInstance().getFilesDir().getAbsolutePath() + "/" + MD5.encode(liveApiUrl));
if (useCache && live_cache.exists()) {
try {
parseLiveJson(liveApiUrl, live_cache);
} catch (Throwable th) {
th.printStackTrace();
}
}else {
OkGo.<String>get(liveApiConfigUrl)
.headers("User-Agent", userAgent)
.headers("Accept", requestAccept)
.execute(new AbsCallback<String>() {
@Override
public void onSuccess(Response<String> response) {
try {
String json = response.body();
parseLiveJson(liveApiUrl, json);
FileUtils.saveCache(live_cache,json);
} catch (Throwable th) {
th.printStackTrace();
callback.notice("解析直播配置失败");
}
}
@Override
public void onError(Response<String> response) {
super.onError(response);
if (live_cache.exists()) {
try {
parseLiveJson(liveApiUrl, live_cache);
callback.success();
return;
} catch (Throwable th) {
th.printStackTrace();
}
}
callback.notice("直播配置拉取失败");
}
public String convertResponse(okhttp3.Response response) throws Throwable {
String result = "";
if (response.body() == null) {
result = "";
}else {
result=response.body().string();
if (liveApiUrl.startsWith("clan")) {
result = clanContentFix(clanToAddress(liveApiUrl), result);
}
//假相對路徑
result = fixContentPath(liveApiUrl,result);
}
return result;
}
});
}
}
}
File cache = new File(App.getInstance().getFilesDir().getAbsolutePath() + "/" + MD5.encode(apiUrl));
if (useCache && cache.exists()) {
try {
parseJson(apiUrl, cache);
callback.success();
return;
} catch (Throwable th) {
th.printStackTrace();
}
}
// String finalApiUrl = apiUrl;
String configUrl=configUrl(apiUrl);
String configKey = TempKey;
String finalApiUrl = apiUrl;
OkGo.<String>get(configUrl)
.headers("User-Agent", userAgent)
.headers("Accept", requestAccept)
@ -179,20 +257,8 @@ public class ApiConfig {
public void onSuccess(Response<String> response) {
try {
String json = response.body();
parseJson(finalApiUrl, json);
try {
File cacheDir = cache.getParentFile();
if (!cacheDir.exists())
cacheDir.mkdirs();
if (cache.exists())
cache.delete();
FileOutputStream fos = new FileOutputStream(cache);
fos.write(json.getBytes("UTF-8"));
fos.flush();
fos.close();
} catch (Throwable th) {
th.printStackTrace();
}
parseJson(apiUrl, json);
FileUtils.saveCache(cache,json);
callback.success();
} catch (Throwable th) {
th.printStackTrace();
@ -205,7 +271,7 @@ public class ApiConfig {
super.onError(response);
if (cache.exists()) {
try {
parseJson(finalApiUrl, cache);
parseJson(apiUrl, cache);
callback.success();
return;
} catch (Throwable th) {
@ -223,17 +289,16 @@ public class ApiConfig {
result = FindResult(response.body().string(), configKey);
}
if (finalApiUrl.startsWith("clan")) {
result = clanContentFix(clanToAddress(finalApiUrl), result);
if (apiUrl.startsWith("clan")) {
result = clanContentFix(clanToAddress(apiUrl), result);
}
//假相對路徑
result = fixContentPath(finalApiUrl,result);
result = fixContentPath(apiUrl,result);
return result;
}
});
}
public void loadJar(boolean useCache, String spider, LoadConfigCallback callback) {
String[] urls = spider.split(";md5;");
String jarUrl = urls[0];
@ -312,7 +377,7 @@ public class ApiConfig {
}
private void parseJson(String apiUrl, String jsonStr) {
JsonObject infoJson = new Gson().fromJson(jsonStr, JsonObject.class);
JsonObject infoJson = gson.fromJson(jsonStr, JsonObject.class);
// spider
spider = DefaultConfig.safeJsonString(infoJson, "spider", "");
// wallpaper
@ -382,31 +447,45 @@ public class ApiConfig {
}
// 直播源
initLiveSettings();
if(infoJson.has("lives")){
JsonArray lives_groups=infoJson.get("lives").getAsJsonArray();
int live_group_index=Hawk.get(HawkConfig.LIVE_GROUP_INDEX,0);
if(live_group_index>lives_groups.size()-1)Hawk.put(HawkConfig.LIVE_GROUP_INDEX,0);
Hawk.put(HawkConfig.LIVE_GROUP_LIST,lives_groups);
//加载多源配置
try {
ArrayList<LiveSettingItem> liveSettingItemList = new ArrayList<>();
for (int i=0; i< lives_groups.size();i++) {
JsonObject jsonObject = lives_groups.get(i).getAsJsonObject();
String name = jsonObject.has("name")?jsonObject.get("name").getAsString():"线路"+(i+1);
LiveSettingItem liveSettingItem = new LiveSettingItem();
liveSettingItem.setItemIndex(i);
liveSettingItem.setItemName(name);
liveSettingItemList.add(liveSettingItem);
if(apiUrl.equals(Hawk.get(HawkConfig.LIVE_API_URL,""))){
LOG.i("echo-load-config_live");
initLiveSettings();
if(infoJson.has("lives")){
JsonArray lives_groups=infoJson.get("lives").getAsJsonArray();
int live_group_index=Hawk.get(HawkConfig.LIVE_GROUP_INDEX,0);
if(live_group_index>lives_groups.size()-1)Hawk.put(HawkConfig.LIVE_GROUP_INDEX,0);
Hawk.put(HawkConfig.LIVE_GROUP_LIST,lives_groups);
//加载多源配置
try {
ArrayList<LiveSettingItem> liveSettingItemList = new ArrayList<>();
for (int i=0; i< lives_groups.size();i++) {
JsonObject jsonObject = lives_groups.get(i).getAsJsonObject();
String name = jsonObject.has("name")?jsonObject.get("name").getAsString():"线路"+(i+1);
LiveSettingItem liveSettingItem = new LiveSettingItem();
liveSettingItem.setItemIndex(i);
liveSettingItem.setItemName(name);
liveSettingItemList.add(liveSettingItem);
}
liveSettingGroupList.get(5).setLiveSettingItems(liveSettingItemList);
} catch (Exception e) {
// 捕获任何可能发生的异常
e.printStackTrace();
}
liveSettingGroupList.get(5).setLiveSettingItems(liveSettingItemList);
} catch (Exception e) {
// 捕获任何可能发生的异常
e.printStackTrace();
}
JsonObject livesOBJ = lives_groups.get(live_group_index).getAsJsonObject();
loadLiveApi(livesOBJ);
JsonObject livesOBJ = lives_groups.get(live_group_index).getAsJsonObject();
loadLiveApi(livesOBJ);
}
myHosts = new HashMap<>();
if (infoJson.has("hosts")) {
JsonArray hostsArray = infoJson.getAsJsonArray("hosts");
for (int i = 0; i < hostsArray.size(); i++) {
String entry = hostsArray.get(i).getAsString();
String[] parts = entry.split("=", 2); // 只分割一次,防止 value 里有 =
if (parts.length == 2) {
myHosts.put(parts[0], parts[1]);
}
}
}
}
//video parse rule for host
@ -457,17 +536,7 @@ public class ApiConfig {
}
}
}
myHosts = new HashMap<>();
if (infoJson.has("hosts")) {
JsonArray hostsArray = infoJson.getAsJsonArray("hosts");
for (int i = 0; i < hostsArray.size(); i++) {
String entry = hostsArray.get(i).getAsString();
String[] parts = entry.split("=", 2); // 只分割一次,防止 value 里有 =
if (parts.length == 2) {
myHosts.put(parts[0], parts[1]);
}
}
}
if (infoJson.has("doh")) {
String doh_json = infoJson.getAsJsonArray("doh").toString();
Hawk.put(HawkConfig.DOH_JSON,doh_json);
@ -478,7 +547,7 @@ public class ApiConfig {
LOG.i("echo-api-config-----------load");
String defaultIJKADS="{\"ijk\":[{\"options\":[{\"name\":\"opensles\",\"category\":4,\"value\":\"0\"},{\"name\":\"framedrop\",\"category\":4,\"value\":\"1\"},{\"name\":\"soundtouch\",\"category\":4,\"value\":\"1\"},{\"name\":\"start-on-prepared\",\"category\":4,\"value\":\"1\"},{\"name\":\"http-detect-rangeupport\",\"category\":1,\"value\":\"0\"},{\"name\":\"fflags\",\"category\":1,\"value\":\"fastseek\"},{\"name\":\"skip_loop_filter\",\"category\":2,\"value\":\"48\"},{\"name\":\"reconnect\",\"category\":4,\"value\":\"1\"},{\"name\":\"enable-accurate-seek\",\"category\":4,\"value\":\"0\"},{\"name\":\"mediacodec\",\"category\":4,\"value\":\"0\"},{\"name\":\"mediacodec-all-videos\",\"category\":4,\"value\":\"0\"},{\"name\":\"mediacodec-auto-rotate\",\"category\":4,\"value\":\"0\"},{\"name\":\"mediacodec-handle-resolution-change\",\"category\":4,\"value\":\"0\"},{\"name\":\"mediacodec-hevc\",\"category\":4,\"value\":\"0\"},{\"name\":\"max-buffer-size\",\"category\":4,\"value\":\"15728640\"}],\"group\":\"软解码\"},{\"options\":[{\"name\":\"opensles\",\"category\":4,\"value\":\"0\"},{\"name\":\"framedrop\",\"category\":4,\"value\":\"1\"},{\"name\":\"soundtouch\",\"category\":4,\"value\":\"1\"},{\"name\":\"start-on-prepared\",\"category\":4,\"value\":\"1\"},{\"name\":\"http-detect-rangeupport\",\"category\":1,\"value\":\"0\"},{\"name\":\"fflags\",\"category\":1,\"value\":\"fastseek\"},{\"name\":\"skip_loop_filter\",\"category\":2,\"value\":\"48\"},{\"name\":\"reconnect\",\"category\":4,\"value\":\"1\"},{\"name\":\"enable-accurate-seek\",\"category\":4,\"value\":\"0\"},{\"name\":\"mediacodec\",\"category\":4,\"value\":\"1\"},{\"name\":\"mediacodec-all-videos\",\"category\":4,\"value\":\"1\"},{\"name\":\"mediacodec-auto-rotate\",\"category\":4,\"value\":\"1\"},{\"name\":\"mediacodec-handle-resolution-change\",\"category\":4,\"value\":\"1\"},{\"name\":\"mediacodec-hevc\",\"category\":4,\"value\":\"1\"},{\"name\":\"max-buffer-size\",\"category\":4,\"value\":\"15728640\"}],\"group\":\"硬解码\"}],\"ads\":[\"mimg.0c1q0l.cn\",\"www.googletagmanager.com\",\"www.google-analytics.com\",\"mc.usihnbcq.cn\",\"mg.g1mm3d.cn\",\"mscs.svaeuzh.cn\",\"cnzz.hhttm.top\",\"tp.vinuxhome.com\",\"cnzz.mmstat.com\",\"www.baihuillq.com\",\"s23.cnzz.com\",\"z3.cnzz.com\",\"c.cnzz.com\",\"stj.v1vo.top\",\"z12.cnzz.com\",\"img.mosflower.cn\",\"tips.gamevvip.com\",\"ehwe.yhdtns.com\",\"xdn.cqqc3.com\",\"www.jixunkyy.cn\",\"sp.chemacid.cn\",\"hm.baidu.com\",\"s9.cnzz.com\",\"z6.cnzz.com\",\"um.cavuc.com\",\"mav.mavuz.com\",\"wofwk.aoidf3.com\",\"z5.cnzz.com\",\"xc.hubeijieshikj.cn\",\"tj.tianwenhu.com\",\"xg.gars57.cn\",\"k.jinxiuzhilv.com\",\"cdn.bootcss.com\",\"ppl.xunzhuo123.com\",\"xomk.jiangjunmh.top\",\"img.xunzhuo123.com\",\"z1.cnzz.com\",\"s13.cnzz.com\",\"xg.huataisangao.cn\",\"z7.cnzz.com\",\"xg.huataisangao.cn\",\"z2.cnzz.com\",\"s96.cnzz.com\",\"q11.cnzz.com\",\"thy.dacedsfa.cn\",\"xg.whsbpw.cn\",\"s19.cnzz.com\",\"z8.cnzz.com\",\"s4.cnzz.com\",\"f5w.as12df.top\",\"ae01.alicdn.com\",\"www.92424.cn\",\"k.wudejia.com\",\"vivovip.mmszxc.top\",\"qiu.xixiqiu.com\",\"cdnjs.hnfenxun.com\",\"cms.qdwght.com\"]}";
JsonObject defaultJson=new Gson().fromJson(defaultIJKADS, JsonObject.class);
JsonObject defaultJson=gson.fromJson(defaultIJKADS, JsonObject.class);
// 广告地址
if(AdBlocker.isEmpty()){
//默认广告拦截
@ -528,6 +597,60 @@ public class ApiConfig {
}
}
private void parseLiveJson(String apiUrl, File f) throws Throwable {
BufferedReader bReader = new BufferedReader(new InputStreamReader(new FileInputStream(f), "UTF-8"));
StringBuilder sb = new StringBuilder();
String s = "";
while ((s = bReader.readLine()) != null) {
sb.append(s + "\n");
}
bReader.close();
parseLiveJson(apiUrl, sb.toString());
}
private void parseLiveJson(String apiUrl, String jsonStr) {
JsonObject infoJson = gson.fromJson(jsonStr, JsonObject.class);
// 直播源
initLiveSettings();
if(infoJson.has("lives")){
JsonArray lives_groups=infoJson.get("lives").getAsJsonArray();
int live_group_index=Hawk.get(HawkConfig.LIVE_GROUP_INDEX,0);
if(live_group_index>lives_groups.size()-1)Hawk.put(HawkConfig.LIVE_GROUP_INDEX,0);
Hawk.put(HawkConfig.LIVE_GROUP_LIST,lives_groups);
//加载多源配置
try {
ArrayList<LiveSettingItem> liveSettingItemList = new ArrayList<>();
for (int i=0; i< lives_groups.size();i++) {
JsonObject jsonObject = lives_groups.get(i).getAsJsonObject();
String name = jsonObject.has("name")?jsonObject.get("name").getAsString():"线路"+(i+1);
LiveSettingItem liveSettingItem = new LiveSettingItem();
liveSettingItem.setItemIndex(i);
liveSettingItem.setItemName(name);
liveSettingItemList.add(liveSettingItem);
}
liveSettingGroupList.get(5).setLiveSettingItems(liveSettingItemList);
} catch (Exception e) {
// 捕获任何可能发生的异常
e.printStackTrace();
}
JsonObject livesOBJ = lives_groups.get(live_group_index).getAsJsonObject();
loadLiveApi(livesOBJ);
}
myHosts = new HashMap<>();
if (infoJson.has("hosts")) {
JsonArray hostsArray = infoJson.getAsJsonArray("hosts");
for (int i = 0; i < hostsArray.size(); i++) {
String entry = hostsArray.get(i).getAsString();
String[] parts = entry.split("=", 2); // 只分割一次,防止 value 里有 =
if (parts.length == 2) {
myHosts.put(parts[0], parts[1]);
}
}
}
LOG.i("echo-api-live-config-----------load");
}
private final List<LiveSettingGroup> liveSettingGroupList = new ArrayList<>();
private void initLiveSettings() {
ArrayList<String> groupNames = new ArrayList<>(Arrays.asList("线路选择", "画面比例", "播放解码", "超时换源", "偏好设置", "多源切换"));
@ -687,9 +810,8 @@ public class ApiConfig {
public interface LoadConfigCallback {
void success();
void retry();
void error(String msg);
void notice(String msg);
}
public interface FastParseCallback {

@ -292,8 +292,13 @@ public class HomeActivity extends BaseActivity {
}
@Override
public void retry() {
public void notice(String msg) {
mHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(HomeActivity.this, msg, Toast.LENGTH_SHORT).show();
}
});
}
@Override
@ -315,11 +320,11 @@ public class HomeActivity extends BaseActivity {
TipDialog dialog = null;
@Override
public void retry() {
public void notice(String msg) {
mHandler.post(new Runnable() {
@Override
public void run() {
initData();
Toast.makeText(HomeActivity.this, msg, Toast.LENGTH_SHORT).show();
}
});
}

@ -46,6 +46,7 @@ public class SettingActivity extends BaseActivity {
private String currentApi;
private int homeRec;
private int dnsOpt;
private String currentLiveApi;
@Override
protected int getLayoutResID() {
@ -110,6 +111,7 @@ public class SettingActivity extends BaseActivity {
homeSourceKey = ApiConfig.get().getHomeSourceBean().getKey();
homeRec = Hawk.get(HawkConfig.HOME_REC, 0);
dnsOpt = Hawk.get(HawkConfig.DOH_URL, 0);
currentLiveApi = Hawk.get(HawkConfig.LIVE_API_URL, "");
List<String> sortList = new ArrayList<>();
sortList.add("设置其他");
sortAdapter.setNewData(sortList);
@ -180,7 +182,8 @@ public class SettingActivity extends BaseActivity {
if ((homeSourceKey != null && !homeSourceKey.equals(Hawk.get(HawkConfig.HOME_API, ""))) ||
!currentApi.equals(Hawk.get(HawkConfig.API_URL, "")) ||
homeRec != Hawk.get(HawkConfig.HOME_REC, 0) ||
dnsOpt != Hawk.get(HawkConfig.DOH_URL, 0)) {
dnsOpt != Hawk.get(HawkConfig.DOH_URL, 0) ||
!currentLiveApi.equals(Hawk.get(HawkConfig.LIVE_API_URL, ""))) {
AppManager.getInstance().finishAllActivity();
if (currentApi.equals(Hawk.get(HawkConfig.API_URL, ""))) {
Bundle bundle = new Bundle();

@ -42,6 +42,7 @@ public class ApiDialog extends BaseDialog {
private ImageView ivQRCode;
private TextView tvAddress;
private EditText inputApi;
private EditText inputApiLive;
@Subscribe(threadMode = ThreadMode.MAIN)
public void refresh(RefreshEvent event) {
@ -57,8 +58,10 @@ public class ApiDialog extends BaseDialog {
ivQRCode = findViewById(R.id.ivQRCode);
tvAddress = findViewById(R.id.tvAddress);
inputApi = findViewById(R.id.input);
inputApiLive = findViewById(R.id.inputLive);
//内置网络接口在此处添加
inputApi.setText(Hawk.get(HawkConfig.API_URL, ""));
inputApiLive.setText(Hawk.get(HawkConfig.LIVE_API_URL, Hawk.get(HawkConfig.API_URL)));
findViewById(R.id.inputSubmit).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@ -70,34 +73,59 @@ public class ApiDialog extends BaseDialog {
if (history.size() > 30)
history.remove(30);
Hawk.put(HawkConfig.API_HISTORY, history);
// String newLiveApi = inputApi.getText().toString().trim();
if(!newApi.equals(Hawk.get(HawkConfig.API_URL, newApi))){
inputApiLive.setText(newApi);
Hawk.put(HawkConfig.LIVE_API_URL, newApi);
}
listener.onchange(newApi);
dismiss();
}
}
});
findViewById(R.id.inputSubmitLive).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String newApi = inputApiLive.getText().toString().trim();
if (!newApi.isEmpty()) {
ArrayList<String> history = Hawk.get(HawkConfig.LIVE_API_HISTORY, new ArrayList<String>());
if (!history.contains(newApi))
history.add(0, newApi);
if (history.size() > 30)
history.remove(30);
Hawk.put(HawkConfig.LIVE_API_HISTORY, history);
Hawk.put(HawkConfig.LIVE_API_URL, newApi);
inputApiLive.setText(newApi);
dismiss();
}
}
});
findViewById(R.id.apiHistory).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ArrayList<String> history = Hawk.get(HawkConfig.API_HISTORY, new ArrayList<String>());
if (history.isEmpty())
ArrayList<String> history = Hawk.get(HawkConfig.LIVE_API_HISTORY, new ArrayList<String>());
if (history.isEmpty()){
Toast.makeText(getContext(), "直播历史为空", Toast.LENGTH_SHORT).show();
return;
String current = Hawk.get(HawkConfig.API_URL, "");
}
String current = Hawk.get(HawkConfig.LIVE_API_URL, "");
int idx = 0;
if (history.contains(current))
idx = history.indexOf(current);
ApiHistoryDialog dialog = new ApiHistoryDialog(getContext());
dialog.setTip("历史配置列表");
dialog.setTip("直播历史配置");
dialog.setAdapter(new ApiHistoryDialogAdapter.SelectDialogInterface() {
@Override
public void click(String value) {
inputApi.setText(value);
listener.onchange(value);
inputApiLive.setText(value);
Hawk.put(HawkConfig.LIVE_API_URL, value);
dialog.dismiss();
}
@Override
public void del(String value, ArrayList<String> data) {
Hawk.put(HawkConfig.API_HISTORY, data);
Hawk.put(HawkConfig.LIVE_API_HISTORY, data);
}
}, history, idx);
dialog.show();
@ -144,6 +172,11 @@ public class ApiDialog extends BaseDialog {
if (history.size() > 30)
history.remove(30);
Hawk.put(HawkConfig.API_HISTORY, history);
if(!newApi.equals(Hawk.get(HawkConfig.API_URL, newApi))){
inputApiLive.setText(newApi);
Hawk.put(HawkConfig.LIVE_API_URL, newApi);
}
listener.onchange(newApi);
dismiss();
}

@ -24,7 +24,7 @@ import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
public class GridFilterDialog extends BaseDialog {
private LinearLayout filterRoot;
public LinearLayout filterRoot;
public GridFilterDialog(@NonNull @NotNull Context context) {
super(context);

@ -1,11 +1,15 @@
package com.github.tvbox.osc.ui.fragment;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.animation.BounceInterpolator;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.chad.library.adapter.base.BaseQuickAdapter;
import com.github.tvbox.osc.R;
@ -20,17 +24,23 @@ import com.github.tvbox.osc.ui.activity.DetailActivity;
import com.github.tvbox.osc.ui.activity.FastSearchActivity;
import com.github.tvbox.osc.ui.activity.SearchActivity;
import com.github.tvbox.osc.ui.adapter.GridAdapter;
import com.github.tvbox.osc.ui.adapter.GridFilterKVAdapter;
import com.github.tvbox.osc.ui.dialog.GridFilterDialog;
import com.github.tvbox.osc.ui.tv.widget.LoadMoreView;
import com.github.tvbox.osc.util.FastClickCheckUtil;
import com.github.tvbox.osc.util.HawkConfig;
import com.github.tvbox.osc.util.LOG;
import com.github.tvbox.osc.viewmodel.SourceViewModel;
import com.orhanobut.hawk.Hawk;
import com.owen.tvrecyclerview.widget.TvRecyclerView;
import com.owen.tvrecyclerview.widget.V7GridLayoutManager;
import com.owen.tvrecyclerview.widget.V7LinearLayoutManager;
import java.util.ArrayList;
import java.util.Stack;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import org.greenrobot.eventbus.EventBus;
@ -249,7 +259,6 @@ public class GridFragment extends BaseLazyFragment {
sourceViewModel.listResult.observe(this, new Observer<AbsXml>() {
@Override
public void onChanged(AbsXml absXml) {
// if(mGridView != null) mGridView.requestFocus();
if (absXml != null && absXml.movie != null && absXml.movie.videoList != null && absXml.movie.videoList.size() > 0) {
if (page == 1) {
showSuccess();
@ -259,23 +268,15 @@ public class GridFragment extends BaseLazyFragment {
gridAdapter.addData(absXml.movie.videoList);
}
page++;
maxPage = absXml.movie.pagecount;
if (maxPage>0 && page > maxPage) {
gridAdapter.loadMoreEnd();
gridAdapter.setEnableLoadMore(false);
if(page>2)Toast.makeText(getContext(), "最后一页啦", Toast.LENGTH_SHORT).show();
} else {
gridAdapter.loadMoreComplete();
gridAdapter.setEnableLoadMore(true);
}
gridAdapter.loadMoreComplete();
gridAdapter.setEnableLoadMore(true);
} else {
if(page == 1){
if (page == 1) {
showEmpty();
}else{
Toast.makeText(getContext(), "最后一页啦", Toast.LENGTH_SHORT).show();
gridAdapter.loadMoreEnd();
} else if(page > 2){// 只有一页数据时不提示
Toast.makeText(getContext(), "没有更多了", Toast.LENGTH_SHORT).show();
}
gridAdapter.loadMoreEnd();
gridAdapter.setEnableLoadMore(false);
}
}
@ -313,17 +314,73 @@ public class GridFragment extends BaseLazyFragment {
public void showFilter() {
if (!sortData.filters.isEmpty() && gridFilterDialog == null) {
gridFilterDialog = new GridFilterDialog(mContext);
gridFilterDialog.setData(sortData);
gridFilterDialog.setOnDismiss(new GridFilterDialog.Callback() {
// gridFilterDialog.setData(sortData);
// gridFilterDialog.setOnDismiss(new GridFilterDialog.Callback() {
// @Override
// public void change() {
// page = 1;
// initData();
// }
// });
setFilterDialogData();
}
if (gridFilterDialog != null)
gridFilterDialog.show();
}
public void setFilterDialogData() {
Context context = getContext();
LayoutInflater inflater = LayoutInflater.from(context);
assert context != null;
final int defaultColor = ContextCompat.getColor(context, R.color.color_FFFFFF);
final int selectedColor = ContextCompat.getColor(context, R.color.color_02F8E1);
// 遍历过滤条件数据
for (MovieSort.SortFilter filter : sortData.filters) {
View line = inflater.inflate(R.layout.item_grid_filter, gridFilterDialog.filterRoot, false);
TextView filterNameTv = line.findViewById(R.id.filterName);
filterNameTv.setText(filter.name);
TvRecyclerView gridView = line.findViewById(R.id.mFilterKv);
gridView.setHasFixedSize(true);
gridView.setLayoutManager(new V7LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));
GridFilterKVAdapter adapter = new GridFilterKVAdapter();
gridView.setAdapter(adapter);
final String key = filter.key;
final ArrayList<String> values = new ArrayList<>(filter.values.keySet());
final ArrayList<String> keys = new ArrayList<>(filter.values.values());
adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
// 用于记录上一次选中的 view
View previousSelectedView = null;
@Override
public void change() {
page = 1;
initData();
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
String currentSelection = sortData.filterSelect.get(key);
String newSelection = keys.get(position);
if (currentSelection == null || !currentSelection.equals(newSelection)) {
// 更新选中状态
sortData.filterSelect.put(key, newSelection);
updateViewStyle(view, selectedColor, true);
if (previousSelectedView != null) {
updateViewStyle(previousSelectedView, defaultColor, false);
}
previousSelectedView = view;
} else {
// 取消选中
sortData.filterSelect.remove(key);
if (previousSelectedView != null) {
updateViewStyle(previousSelectedView, defaultColor, false);
}
previousSelectedView = null;
}
forceRefresh();
}
private void updateViewStyle(View view, int color, boolean isBold) {
TextView valueTv = view.findViewById(R.id.filterValue);
valueTv.getPaint().setFakeBoldText(isBold);
valueTv.setTextColor(color);
}
});
adapter.setNewData(values);
gridFilterDialog.filterRoot.addView(line);
}
if (gridFilterDialog != null)
gridFilterDialog.show();
}
public void forceRefresh() {

@ -100,24 +100,6 @@ public class FileUtils {
return jsonString;
}
public static String getAssetFile(String assetName) throws IOException {
InputStream is = App.getInstance().getAssets().open(assetName);
byte[] data = new byte[is.available()];
is.read(data);
return new String(data, "UTF-8");
}
public static boolean isAssetFile(String name, String path) {
try {
for(String one : App.getInstance().getAssets().list(path)) {
if (one.equals(name)) return true;
}
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
public static String getRootPath() {
return Environment.getExternalStorageDirectory().getAbsolutePath();
}
@ -220,14 +202,6 @@ public class FileUtils {
return fileName;
}
public static String getFileExt(String fileName){
if(TextUtils.isEmpty(fileName)) return "";
int p = fileName.lastIndexOf('.');
if(p != -1) {
return fileName.substring(p).toLowerCase();
}
return "";
}
public static boolean hasExtension(String path) {
int lastDotIndex = path.lastIndexOf(".");
@ -235,4 +209,20 @@ public class FileUtils {
// 如果路径中有点号,并且点号在最后一个斜杠之后,认为有后缀
return lastDotIndex > lastSlashIndex && lastDotIndex < path.length() - 1;
}
public static void saveCache(File cache,String json){
try {
File cacheDir = cache.getParentFile();
if (!cacheDir.exists())
cacheDir.mkdirs();
if (cache.exists())
cache.delete();
FileOutputStream fos = new FileOutputStream(cache);
fos.write(json.getBytes("UTF-8"));
fos.flush();
fos.close();
} catch (Throwable th) {
th.printStackTrace();
}
}
}

@ -10,6 +10,7 @@ public class HawkConfig {
public static final String EPG_URL = "epg_url";
public static final String SHOW_PREVIEW = "show_preview";
public static final String API_HISTORY = "api_history";
public static final String LIVE_API_HISTORY = "live_api_history";
public static final String EPG_HISTORY = "epg_history";
public static final String HOME_API = "home_api";
public static final String DEFAULT_PARSE = "parse_default";

@ -14,6 +14,7 @@ public class VideoParseRuler {
public static void clearRule() {
HOSTS_RULE.clear();
HOSTS_FILTER.clear();
HOSTS_REGEX.clear();
}
public static void addHostRule(String host, ArrayList<String> rule) {

@ -1,18 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<!-- 将外层容器高度改为 wrap_content,避免内部控件溢出 -->
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="@dimen/vs_360"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/shape_dialog_bg_main"
android:orientation="horizontal"
android:padding="@dimen/vs_30">
<ImageView
android:id="@+id/ivQRCode"
android:layout_width="@dimen/vs_300"
@ -21,82 +21,90 @@
android:focusable="false" />
<LinearLayout
android:layout_width="@dimen/vs_480"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/vs_20"
android:orientation="vertical">
android:layout_width="@dimen/vs_520"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/vs_20"
android:orientation="vertical"
android:paddingTop="@dimen/vs_20"
android:paddingBottom="@dimen/vs_20"
android:layout_marginLeft="@dimen/vs_20">
<TextView
android:id="@+id/tvAddress"
android:layout_width="@dimen/vs_480"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/vs_10"
android:lineSpacingMultiplier="1.5"
android:textColor="@color/color_CC000000"
android:textSize="@dimen/ts_24"
tools:text="1111111111111111111111111111111111111111111111111111" />
<Space
android:layout_width="match_parent"
android:layout_height="@dimen/vs_0"
android:layout_weight="1" />
<!-- 配置历史与存储权限 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/vs_10"
android:orientation="horizontal"
android:visibility="visible">
android:layout_marginBottom="@dimen/vs_10"
android:orientation="horizontal">
<TextView
android:id="@+id/apiHistory"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginRight="@dimen/vs_5"
android:layout_weight="1"
android:layout_marginEnd="@dimen/vs_5"
android:background="@drawable/button_dialog_main"
android:focusable="true"
android:gravity="center"
android:padding="@dimen/vs_10"
android:text="配置历史"
android:text="直播历史"
android:textColor="@color/color_FFFFFF"
android:textSize="@dimen/ts_22" />
android:textSize="@dimen/ts_22"
android:layout_marginRight="@dimen/vs_5" />
<TextView
android:id="@+id/storagePermission"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="@dimen/vs_5"
android:layout_weight="1"
android:background="@drawable/button_dialog_main"
android:focusable="true"
android:gravity="center"
android:padding="@dimen/vs_10"
android:text="存储权限"
android:textColor="@color/color_FFFFFF"
android:textSize="@dimen/ts_22" />
android:id="@+id/storagePermission"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginStart="@dimen/vs_5"
android:background="@drawable/button_dialog_main"
android:focusable="true"
android:gravity="center"
android:padding="@dimen/vs_10"
android:text="存储权限"
android:textColor="@color/color_FFFFFF"
android:textSize="@dimen/ts_22"
android:layout_marginLeft="@dimen/vs_5" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="@dimen/vs_0"
android:layout_weight="1" />
<!-- 点播输入区域 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
android:orientation="horizontal"
android:layout_marginBottom="@dimen/vs_10">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:focusable="true"
android:gravity="center"
android:paddingRight="@dimen/vs_5"
android:text="点播"
android:textSize="@dimen/ts_22"
android:paddingEnd="@dimen/vs_5"
android:cursorVisible="false"
tools:ignore="RtlSymmetry,TextViewEdits" />
<EditText
android:id="@+id/input"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/input_dialog_api_input"
android:imeOptions="actionDone"
android:hint="请输入配置地址"
android:imeOptions="actionDone"
android:inputType="text"
android:maxLines="1"
android:paddingLeft="@dimen/vs_2"
@ -112,16 +120,69 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginLeft="@dimen/vs_5"
android:layout_marginStart="@dimen/vs_5"
android:background="@drawable/button_dialog_main"
android:focusable="true"
android:gravity="center"
android:padding="@dimen/vs_10"
android:text="确定"
android:textColor="@color/color_FFFFFF"
android:textSize="@dimen/ts_22" />
android:textSize="@dimen/ts_22"
android:layout_marginLeft="@dimen/vs_5" />
</LinearLayout>
<!-- 直播输入区域 -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:focusable="true"
android:gravity="center"
android:paddingRight="@dimen/vs_5"
android:text="直播"
android:textSize="@dimen/ts_22"
android:paddingEnd="@dimen/vs_5"
android:cursorVisible="false"
tools:ignore="RtlSymmetry,TextViewEdits" />
<EditText
android:id="@+id/inputLive"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@drawable/input_dialog_api_input"
android:hint="请输入直播地址"
android:imeOptions="actionDone"
android:inputType="text"
android:maxLines="1"
android:paddingLeft="@dimen/vs_2"
android:paddingTop="@dimen/vs_10"
android:paddingRight="@dimen/vs_2"
android:paddingBottom="@dimen/vs_10"
android:textColor="@color/color_CC000000"
android:textColorHint="@color/color_6C3D3D3D"
android:textSize="@dimen/ts_26" />
<TextView
android:id="@+id/inputSubmitLive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@dimen/vs_5"
android:background="@drawable/button_dialog_main"
android:focusable="true"
android:gravity="center"
android:padding="@dimen/vs_10"
android:text="确定"
android:textColor="@color/color_FFFFFF"
android:textSize="@dimen/ts_22"
android:layout_marginLeft="@dimen/vs_5" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</FrameLayout>
</FrameLayout>

@ -81,6 +81,7 @@
<dimen name="vs_410">410mm</dimen>
<dimen name="vs_470">470mm</dimen>
<dimen name="vs_480">480mm</dimen>
<dimen name="vs_520">540mm</dimen>
<dimen name="vs_600">600mm</dimen>
<dimen name="vs_610">610mm</dimen>
<dimen name="vs_640">640mm</dimen>

@ -18,4 +18,4 @@ android.useAndroidX=true
# Automatically convert third-party libraries to use AndroidX
android.enableJetifier=true
IsDebug=true
org.gradle.jvmargs=-Xmx2048m --add-opens java.base/java.io=ALL-UNNAMED
#org.gradle.jvmargs=-Xmx2048m --add-opens java.base/java.io=ALL-UNNAMED

Loading…
Cancel
Save