diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/LiveActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/LiveActivity.java index f27ad87bf..de4381d95 100644 --- a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/LiveActivity.java +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/LiveActivity.java @@ -577,7 +577,7 @@ public class LiveActivity extends BaseActivity implements GroupPresenter.OnClick public void onItemClick(EpgData item) { if (item.isSelected()) { fetch(item); - } else if (mChannel.hasCatchup()) { + } else if (mChannel.hasCatchup() || mChannel.isRtsp()) { mBinding.widget.title.setText(getString(R.string.detail_title, mChannel.getShow(), item.getTitle())); Notify.show(getString(R.string.play_ready, item.getTitle())); setActivated(item); diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/VodActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/VodActivity.java index ff3fd52a4..92e50e8e9 100644 --- a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/VodActivity.java +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/VodActivity.java @@ -87,6 +87,7 @@ public class VodActivity extends BaseActivity implements TypePresenter.OnClickLi @Override public void onPageSelected(int position) { mBinding.recycler.setSelectedPosition(position); + mBinding.recycler.requestFocus(); } }); mBinding.recycler.addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() { diff --git a/app/src/main/java/com/fongmi/android/tv/App.java b/app/src/main/java/com/fongmi/android/tv/App.java index 97186d60e..40f94bf92 100644 --- a/app/src/main/java/com/fongmi/android/tv/App.java +++ b/app/src/main/java/com/fongmi/android/tv/App.java @@ -27,10 +27,11 @@ public class App extends Application implements Application.ActivityLifecycleCal private final ExecutorService searchExecutor; private final ExecutorService executor; private final Handler handler; - private static App instance; - private Activity activity; private final Gson gson; private final long time; + + private static volatile App instance; + private Activity activity; private Hook hook; public App() { diff --git a/app/src/main/java/com/fongmi/android/tv/api/config/BaseConfig.java b/app/src/main/java/com/fongmi/android/tv/api/config/BaseConfig.java index 6986ec41b..97d68060c 100644 --- a/app/src/main/java/com/fongmi/android/tv/api/config/BaseConfig.java +++ b/app/src/main/java/com/fongmi/android/tv/api/config/BaseConfig.java @@ -32,7 +32,7 @@ abstract class BaseConfig { private final AtomicInteger taskId = new AtomicInteger(0); protected boolean sync; - protected Config config; + protected volatile Config config; private volatile Future future; protected abstract String getTag(); diff --git a/app/src/main/java/com/fongmi/android/tv/api/config/LiveConfig.java b/app/src/main/java/com/fongmi/android/tv/api/config/LiveConfig.java index a074f0057..578eb6e65 100644 --- a/app/src/main/java/com/fongmi/android/tv/api/config/LiveConfig.java +++ b/app/src/main/java/com/fongmi/android/tv/api/config/LiveConfig.java @@ -30,6 +30,8 @@ import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import java.time.ZoneId; + public class LiveConfig extends BaseConfig { private static final String TAG = LiveConfig.class.getSimpleName(); @@ -235,6 +237,12 @@ public class LiveConfig extends BaseConfig { this.ads = ads; } + public ZoneId getZoneId() { + String tz = getHome().getTimeZone(); + if (tz.isEmpty()) return ZoneId.systemDefault(); + try { return ZoneId.of(tz); } catch (Exception ignored) { return ZoneId.systemDefault(); } + } + public Live getHome() { return home == null ? new Live() : home; } diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Catchup.java b/app/src/main/java/com/fongmi/android/tv/bean/Catchup.java index 9b300810f..d6b689a96 100644 --- a/app/src/main/java/com/fongmi/android/tv/bean/Catchup.java +++ b/app/src/main/java/com/fongmi/android/tv/bean/Catchup.java @@ -5,7 +5,9 @@ import android.text.TextUtils; import com.google.gson.annotations.SerializedName; import java.net.URI; -import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.Locale; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -114,7 +116,7 @@ public class Catchup { private String formatTime(long millis, String fmt) { if (fmt.equals("timestamp")) return String.valueOf(millis / 1000); - return new SimpleDateFormat(fmt, Locale.getDefault()).format(millis); + return DateTimeFormatter.ofPattern(fmt, Locale.getDefault()).format(Instant.ofEpochMilli(millis).atZone(ZoneId.systemDefault())); } private String format(String group, long start, long end) { diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Channel.java b/app/src/main/java/com/fongmi/android/tv/bean/Channel.java index ef7e33962..544efabcf 100644 --- a/app/src/main/java/com/fongmi/android/tv/bean/Channel.java +++ b/app/src/main/java/com/fongmi/android/tv/bean/Channel.java @@ -17,7 +17,11 @@ import com.google.gson.JsonElement; import com.google.gson.annotations.JsonAdapter; import com.google.gson.annotations.SerializedName; +import com.fongmi.android.tv.utils.Formatters; + +import java.time.LocalDate; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -64,7 +68,7 @@ public class Channel { private Group group; private String show; private int index; - private Epg data; + private List dataList; public static Channel objectFrom(JsonElement element) { return App.gson().fromJson(element, Channel.class); @@ -234,11 +238,23 @@ public class Channel { } public Epg getData() { - return data == null ? new Epg() : data; + String today = LocalDate.now().format(Formatters.DATE); + if (dataList == null) return new Epg(); + return dataList.stream().filter(e -> e.equal(today)).findFirst().orElse(new Epg()); + } + + public List getDataList() { + return dataList == null ? Collections.emptyList() : dataList; } public void setData(Epg data) { - this.data = data; + if (dataList == null) dataList = new ArrayList<>(); + dataList.removeIf(e -> e.equal(data.getDate())); + dataList.add(data); + } + + public void setDataList(List list) { + this.dataList = new ArrayList<>(list); } public int getIndex() { @@ -291,6 +307,10 @@ public class Channel { return getUrls().isEmpty() || getIndex() == getUrls().size() - 1; } + public boolean isRtsp() { + return getCurrent().startsWith("rtsp"); + } + public boolean hasCatchup() { if (getCatchup().isEmpty() && getCurrent().contains("/PLTV/")) setCatchup(Catchup.PLTV()); if (!getCatchup().getRegex().isEmpty()) return getCatchup().match(getCurrent()); @@ -358,7 +378,7 @@ public class Channel { setName(item.getName()); setShow(item.getShow()); setUrls(item.getUrls()); - setData(item.getData()); + setDataList(item.getDataList()); setDrm(item.getDrm()); setEpg(item.getEpg()); setUa(item.getUa()); diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Epg.java b/app/src/main/java/com/fongmi/android/tv/bean/Epg.java index c0bd6eb7d..7eb78f0ce 100644 --- a/app/src/main/java/com/fongmi/android/tv/bean/Epg.java +++ b/app/src/main/java/com/fongmi/android/tv/bean/Epg.java @@ -4,11 +4,12 @@ import android.text.TextUtils; import com.fongmi.android.tv.App; import com.fongmi.android.tv.api.EpgParser; -import com.fongmi.android.tv.utils.Util; +import com.fongmi.android.tv.utils.Formatters; import com.github.catvod.utils.Json; import com.google.gson.annotations.SerializedName; -import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.ZoneId; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; @@ -25,10 +26,10 @@ public class Epg { private int width; - public static Epg objectFrom(String str, String key, List formats) throws Exception { - if (!Json.isObj(str)) return EpgParser.getEpg(str, key); + public static Epg objectFrom(String str, String key, ZoneId zoneId) throws Exception { + if (!Json.isObj(str)) return EpgParser.getEpg(str, key, zoneId); Epg item = App.gson().fromJson(str, Epg.class); - item.setTime(formats); + item.setTime(zoneId); item.setKey(key); return item; } @@ -77,11 +78,11 @@ public class Epg { return getDate().equals(date); } - private void setTime(List formats) { + private void setTime(ZoneId zoneId) { setList(new ArrayList<>(new LinkedHashSet<>(getList()))); for (EpgData item : getList()) { - item.setStartTime(Util.parse(formats, getDate().concat(item.getStart()))); - item.setEndTime(Util.parse(formats, getDate().concat(item.getEnd()))); + item.setStartTime(parseEpgTime(getDate().concat(item.getStart()), zoneId)); + item.setEndTime(parseEpgTime(getDate().concat(item.getEnd()), zoneId)); if (item.getEndTime() < item.getStartTime()) item.checkDay(); item.trans(); } @@ -106,4 +107,13 @@ public class Epg { for (int i = 0; i < getList().size(); i++) if (getList().get(i).isInRange()) return i; return -1; } + + private long parseEpgTime(String source, ZoneId zoneId) { + try { + var fmt = source.length() > 16 ? Formatters.EPG_DT_LONG : Formatters.EPG_DT_SHORT; + return LocalDateTime.parse(source, fmt).atZone(zoneId).toInstant().toEpochMilli(); + } catch (Exception ignored) { + return 0L; + } + } } diff --git a/app/src/main/java/com/fongmi/android/tv/bean/EpgData.java b/app/src/main/java/com/fongmi/android/tv/bean/EpgData.java index 0c1f1bab2..a8feeb870 100644 --- a/app/src/main/java/com/fongmi/android/tv/bean/EpgData.java +++ b/app/src/main/java/com/fongmi/android/tv/bean/EpgData.java @@ -5,15 +5,14 @@ import android.text.TextUtils; import androidx.annotation.Nullable; import com.fongmi.android.tv.R; +import com.fongmi.android.tv.utils.Formatters; import com.fongmi.android.tv.utils.ResUtil; import com.github.catvod.utils.Trans; import com.google.gson.annotations.SerializedName; -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Locale; +import java.time.Instant; +import java.time.ZoneId; import java.util.Objects; -import java.util.TimeZone; public class EpgData { @@ -99,17 +98,12 @@ public class EpgData { return getStart() + " ~ " + getEnd(); } - public void checkDay() { - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(getEndTime()); - cal.add(Calendar.DAY_OF_MONTH, 1); - setEndTime(cal.getTimeInMillis()); + public String getRange() { + return "clock=" + Formatters.EPG_RANGE.format(Instant.ofEpochMilli(getStartTime())) + "-" + Formatters.EPG_RANGE.format(Instant.ofEpochMilli(getEndTime())); } - public String getRange() { - SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd'T'HHmmss'Z'", Locale.US); - sdf.setTimeZone(TimeZone.getTimeZone("UTC")); - return "clock=" + sdf.format(getStartTime()) + "-" + sdf.format(getEndTime()); + public void checkDay() { + setEndTime(Instant.ofEpochMilli(getEndTime()).atZone(ZoneId.systemDefault()).plusDays(1).toInstant().toEpochMilli()); } public void trans() { diff --git a/app/src/main/java/com/fongmi/android/tv/db/AppDatabase.java b/app/src/main/java/com/fongmi/android/tv/db/AppDatabase.java index bf849cc30..4f02eab7d 100644 --- a/app/src/main/java/com/fongmi/android/tv/db/AppDatabase.java +++ b/app/src/main/java/com/fongmi/android/tv/db/AppDatabase.java @@ -23,14 +23,13 @@ import com.fongmi.android.tv.db.dao.LiveDao; import com.fongmi.android.tv.db.dao.SiteDao; import com.fongmi.android.tv.db.dao.TrackDao; import com.fongmi.android.tv.utils.FileUtil; +import com.fongmi.android.tv.utils.Formatters; import com.github.catvod.utils.Path; import java.io.File; -import java.text.SimpleDateFormat; +import java.time.LocalDate; import java.util.ArrayList; -import java.util.Date; import java.util.List; -import java.util.Locale; @Database(entities = {Keep.class, Site.class, Live.class, Track.class, Config.class, Device.class, History.class}, version = AppDatabase.VERSION) public abstract class AppDatabase extends RoomDatabase { @@ -52,7 +51,7 @@ public abstract class AppDatabase extends RoomDatabase { public static void backup(com.fongmi.android.tv.impl.Callback callback) { App.execute(() -> { - File file = new File(Path.tv(), "tv-" + new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(new Date()) + ".bk"); + File file = new File(Path.tv(), "tv-" + LocalDate.now().format(Formatters.DATE) + ".bk"); Backup backup = Backup.create(); if (backup.getConfig().isEmpty()) { App.post(callback::error); diff --git a/app/src/main/java/com/fongmi/android/tv/server/Server.java b/app/src/main/java/com/fongmi/android/tv/server/Server.java index 8eb938488..ceacf7456 100644 --- a/app/src/main/java/com/fongmi/android/tv/server/Server.java +++ b/app/src/main/java/com/fongmi/android/tv/server/Server.java @@ -7,8 +7,8 @@ import com.github.catvod.utils.Util; public class Server { - private Players player; - private Nano nano; + private volatile Players player; + private volatile Nano nano; private static class Loader { static volatile Server INSTANCE = new Server(); diff --git a/app/src/main/java/com/fongmi/android/tv/server/process/Local.java b/app/src/main/java/com/fongmi/android/tv/server/process/Local.java index 941dcb735..1fcc3b4cf 100644 --- a/app/src/main/java/com/fongmi/android/tv/server/process/Local.java +++ b/app/src/main/java/com/fongmi/android/tv/server/process/Local.java @@ -7,6 +7,7 @@ import static fi.iki.elonen.NanoHTTPD.newFixedLengthResponse; import com.fongmi.android.tv.server.Nano; import com.fongmi.android.tv.server.impl.Process; import com.fongmi.android.tv.utils.FileUtil; +import com.fongmi.android.tv.utils.Formatters; import com.github.catvod.utils.Path; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -16,9 +17,8 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; +import java.time.Instant; +import java.time.ZoneId; import java.util.Map; import java.util.zip.CRC32; @@ -28,12 +28,6 @@ import fi.iki.elonen.NanoHTTPD.Response.Status; public class Local implements Process { - private final SimpleDateFormat format; - - public Local() { - this.format = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()); - } - @Override public boolean isRequest(IHTTPSession session, String url) { return url.startsWith("/file") || url.startsWith("/upload") || url.startsWith("/newFolder") || url.startsWith("/delFolder") || url.startsWith("/delFile"); @@ -91,7 +85,7 @@ public class Local implements Process { JsonObject obj = new JsonObject(); obj.addProperty("name", file.getName()); obj.addProperty("path", relativeTo(file, rootPath)); - obj.addProperty("time", format.format(new Date(file.lastModified()))); + obj.addProperty("time", Formatters.LOCAL_DATETIME.format(Instant.ofEpochMilli(file.lastModified()).atZone(ZoneId.systemDefault()))); obj.addProperty("dir", file.isDirectory() ? 1 : 0); files.add(obj); } diff --git a/app/src/main/java/com/fongmi/android/tv/utils/Clock.java b/app/src/main/java/com/fongmi/android/tv/utils/Clock.java index fe381437b..b3d283e75 100644 --- a/app/src/main/java/com/fongmi/android/tv/utils/Clock.java +++ b/app/src/main/java/com/fongmi/android/tv/utils/Clock.java @@ -4,17 +4,16 @@ import android.widget.TextView; import com.fongmi.android.tv.App; -import java.text.SimpleDateFormat; -import java.util.Date; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.Locale; import java.util.Timer; import java.util.TimerTask; public class Clock { - private SimpleDateFormat format; + private DateTimeFormatter format; private Callback callback; - private final Date date; private TextView view; private Timer timer; @@ -27,7 +26,6 @@ public class Clock { } public Clock() { - this.date = new Date(); } public Clock view(TextView view) { @@ -36,7 +34,7 @@ public class Clock { } public Clock format(String format) { - this.format = new SimpleDateFormat(format, Locale.getDefault()); + this.format = DateTimeFormatter.ofPattern(format, Locale.getDefault()); return this; } @@ -56,10 +54,9 @@ public class Clock { private void doJob() { try { - long time; - date.setTime(time = System.currentTimeMillis()); + long time = System.currentTimeMillis(); if (callback != null) callback.onTimeChanged(time); - if (view != null) view.setText(format.format(date)); + if (view != null) view.setText(format.format(LocalDateTime.now())); } catch (Exception ignored) { } } diff --git a/app/src/main/java/com/fongmi/android/tv/utils/Formatters.java b/app/src/main/java/com/fongmi/android/tv/utils/Formatters.java new file mode 100644 index 000000000..a234b08fb --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/utils/Formatters.java @@ -0,0 +1,21 @@ +package com.fongmi.android.tv.utils; + +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +public final class Formatters { + + public static final DateTimeFormatter DATE = DateTimeFormatter.ofPattern("yyyy-MM-dd", Locale.ROOT); + public static final DateTimeFormatter TIME = DateTimeFormatter.ofPattern("HH:mm", Locale.ROOT); + public static final DateTimeFormatter TIME_SEC = DateTimeFormatter.ofPattern("HH:mm:ss", Locale.ROOT).withZone(ZoneId.systemDefault()); + public static final DateTimeFormatter LOCAL_DATETIME = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss", Locale.ROOT); + public static final DateTimeFormatter EPG_DT_SHORT = DateTimeFormatter.ofPattern("yyyy-MM-ddHH:mm", Locale.ROOT); + public static final DateTimeFormatter EPG_DT_LONG = DateTimeFormatter.ofPattern("yyyy-MM-ddHH:mm:ss", Locale.ROOT); + public static final DateTimeFormatter EPG_RANGE = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'", Locale.ROOT).withZone(ZoneOffset.UTC); + public static final DateTimeFormatter EPG_FULL = DateTimeFormatter.ofPattern("yyyyMMddHHmmss Z", Locale.ROOT); + public static final DateTimeFormatter EPG_FULL_NO_TZ = DateTimeFormatter.ofPattern("yyyyMMddHHmmss", Locale.ROOT); + public static final DateTimeFormatter EPG_FULL_COLON = DateTimeFormatter.ofPattern("yyyyMMddHHmmss ZZZ", Locale.ROOT); + +} diff --git a/app/src/main/java/com/fongmi/android/tv/utils/ImgUtil.java b/app/src/main/java/com/fongmi/android/tv/utils/ImgUtil.java index bceda35d4..37a1f43d5 100644 --- a/app/src/main/java/com/fongmi/android/tv/utils/ImgUtil.java +++ b/app/src/main/java/com/fongmi/android/tv/utils/ImgUtil.java @@ -28,6 +28,7 @@ import com.fongmi.android.tv.impl.CustomTarget; import com.github.catvod.utils.Json; import com.google.common.net.HttpHeaders; +import java.util.Collections; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -36,7 +37,7 @@ import jahirfiquitiva.libs.textdrawable.TextDrawable; public class ImgUtil { - private static final Set failed = new HashSet<>(); + private static final Set failed = Collections.synchronizedSet(new HashSet<>()); public static void logo(ImageView view) { try { diff --git a/app/src/main/java/com/fongmi/android/tv/utils/Util.java b/app/src/main/java/com/fongmi/android/tv/utils/Util.java index ba570275e..09423d1cd 100644 --- a/app/src/main/java/com/fongmi/android/tv/utils/Util.java +++ b/app/src/main/java/com/fongmi/android/tv/utils/Util.java @@ -28,9 +28,7 @@ import com.fongmi.android.tv.R; import com.github.catvod.utils.Shell; import java.net.NetworkInterface; -import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Date; import java.util.Formatter; import java.util.List; import java.util.regex.Matcher; @@ -168,18 +166,6 @@ public class Util { return text; } - public static Date parse(SimpleDateFormat format, String source) { - try { - return format.parse(source); - } catch (Exception e) { - return new Date(0); - } - } - - public static long parse(List formats, String source) { - return formats.stream().map(format -> parse(format, source)).map(Date::getTime).filter(time -> time > 0).findFirst().orElse(0L); - } - public static boolean isLeanback() { return "leanback".equals(BuildConfig.FLAVOR_mode); } diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/LiveActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/LiveActivity.java index 20403b14f..4c3323ab9 100644 --- a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/LiveActivity.java +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/LiveActivity.java @@ -609,7 +609,7 @@ public class LiveActivity extends BaseActivity implements CustomKeyDown.Listener public void onItemClick(EpgData item) { if (item.isSelected()) { fetch(item); - } else if (mChannel.hasCatchup()) { + } else if (mChannel.hasCatchup() || mChannel.isRtsp()) { mBinding.control.title.setText(getString(R.string.detail_title, mChannel.getShow(), item.getTitle())); Notify.show(getString(R.string.play_ready, item.getTitle())); mEpgDataAdapter.setSelected(item); diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/RestoreAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/RestoreAdapter.java index 95b539967..26ff947c9 100644 --- a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/RestoreAdapter.java +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/RestoreAdapter.java @@ -7,22 +7,20 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.fongmi.android.tv.databinding.AdapterRestoreBinding; +import com.fongmi.android.tv.utils.Formatters; import com.github.catvod.utils.Path; import java.io.File; -import java.text.SimpleDateFormat; +import java.time.Instant; import java.util.ArrayList; import java.util.List; -import java.util.Locale; public class RestoreAdapter extends RecyclerView.Adapter { private final OnClickListener listener; - private final SimpleDateFormat format; private final List mItems; public RestoreAdapter(OnClickListener listener) { - this.format = new SimpleDateFormat("HH:mm:ss", Locale.getDefault()); this.mItems = new ArrayList<>(); this.listener = listener; this.addAll(); @@ -67,7 +65,7 @@ public class RestoreAdapter extends RecyclerView.Adapter listener.onDeleteClick(item)); holder.binding.getRoot().setOnClickListener(v -> listener.onItemClick(item)); }