From 086e40f24556e6437112056103cee970b9dc4e09 Mon Sep 17 00:00:00 2001 From: FongMi Date: Thu, 6 Jun 2024 19:44:21 +0800 Subject: [PATCH] Support tvg-url parse --- app/proguard-rules.pro | 4 + .../android/tv/ui/activity/LiveActivity.java | 12 ++- .../java/com/fongmi/android/tv/Constant.java | 2 + .../com/fongmi/android/tv/api/EpgParser.java | 83 +++++++++++++++++++ .../com/fongmi/android/tv/api/LiveParser.java | 2 + .../com/fongmi/android/tv/bean/Channel.java | 4 +- .../java/com/fongmi/android/tv/bean/Epg.java | 14 +++- .../com/fongmi/android/tv/bean/EpgData.java | 8 ++ .../java/com/fongmi/android/tv/bean/Live.java | 4 + .../java/com/fongmi/android/tv/bean/Tv.java | 79 ++++++++++++++++++ .../android/tv/model/LiveViewModel.java | 4 +- .../android/tv/ui/activity/LiveActivity.java | 21 +++-- .../android/tv/ui/adapter/ChannelAdapter.java | 5 -- .../mobile/res/drawable/ic_action_arrow.xml | 10 --- app/src/mobile/res/layout/adapter_channel.xml | 8 -- 15 files changed, 220 insertions(+), 40 deletions(-) create mode 100644 app/src/main/java/com/fongmi/android/tv/api/EpgParser.java create mode 100644 app/src/main/java/com/fongmi/android/tv/bean/Tv.java delete mode 100644 app/src/mobile/res/drawable/ic_action_arrow.xml diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 943e20e18..c5f09f8bc 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -1,3 +1,6 @@ +# TV +-keep class com.fongmi.android.tv.bean.** { *; } + # Gson -keep class com.google.gson.** { *; } @@ -11,6 +14,7 @@ -keepclassmembers,allowobfuscation class * { @org.simpleframework.xml.Path ; } -keepclassmembers,allowobfuscation class * { @org.simpleframework.xml.Root ; } -keepclassmembers,allowobfuscation class * { @org.simpleframework.xml.Text ; } +-keepclassmembers,allowobfuscation class * { @org.simpleframework.xml.Element ; } -keepclassmembers,allowobfuscation class * { @org.simpleframework.xml.Attribute ; } -keepclassmembers,allowobfuscation class * { @org.simpleframework.xml.ElementList ; } 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 934bd418b..8eee2fa04 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 @@ -527,9 +527,13 @@ public class LiveActivity extends BaseActivity implements GroupPresenter.OnClick @Override public void onItemClick(Channel item) { - mGroup.setPosition(mBinding.channel.getSelectedPosition()); - setChannel(item.group(mGroup)); - hideUI(); + if (item.getData().getList().size() > 0 && item.isSelected() && mChannel != null) { + showEpg(item); + } else { + mGroup.setPosition(mBinding.channel.getSelectedPosition()); + setChannel(item.group(mGroup)); + hideUI(); + } } @Override @@ -599,7 +603,7 @@ public class LiveActivity extends BaseActivity implements GroupPresenter.OnClick } private void setEpg(Epg epg) { - if (mChannel != null && mChannel.getName().equals(epg.getKey())) setEpg(); + if (mChannel != null && mChannel.getTvgName().equals(epg.getKey())) setEpg(); } private void fetch() { diff --git a/app/src/main/java/com/fongmi/android/tv/Constant.java b/app/src/main/java/com/fongmi/android/tv/Constant.java index 76e913d8b..5bf1ce193 100644 --- a/app/src/main/java/com/fongmi/android/tv/Constant.java +++ b/app/src/main/java/com/fongmi/android/tv/Constant.java @@ -13,6 +13,8 @@ public class Constant { public static final int TIMEOUT_LIVE = 30 * 1000; //節目爬蟲時間 public static final int TIMEOUT_EPG = 5 * 1000; + //節目爬蟲時間 + public static final int TIMEOUT_XML = 15 * 1000; //播放超時時間 public static final int TIMEOUT_PLAY = 15 * 1000; //解析預設時間 diff --git a/app/src/main/java/com/fongmi/android/tv/api/EpgParser.java b/app/src/main/java/com/fongmi/android/tv/api/EpgParser.java new file mode 100644 index 000000000..a161bf7e8 --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/api/EpgParser.java @@ -0,0 +1,83 @@ +package com.fongmi.android.tv.api; + +import android.net.Uri; + +import com.fongmi.android.tv.bean.Channel; +import com.fongmi.android.tv.bean.Epg; +import com.fongmi.android.tv.bean.EpgData; +import com.fongmi.android.tv.bean.Group; +import com.fongmi.android.tv.bean.Live; +import com.fongmi.android.tv.bean.Tv; +import com.fongmi.android.tv.utils.Download; +import com.github.catvod.utils.Path; + +import org.simpleframework.xml.core.Persister; + +import java.io.File; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class EpgParser { + + private static final SimpleDateFormat formatFull = new SimpleDateFormat("yyyyMMddHHmmss"); + private static final SimpleDateFormat formatDate = new SimpleDateFormat("yyyy-MM-dd"); + private static final SimpleDateFormat formatTime = new SimpleDateFormat("HH:mm"); + + public static void start(Live live) throws Exception { + if (!live.getEpg().contains(".xml") || live.getEpg().contains("{")) return; + File file = Path.cache(Uri.parse(live.getEpg()).getLastPathSegment()); + if (shouldDownload(file)) Download.create(live.getEpg(), file).start(); + readXml(live, Path.read(file)); + } + + private static boolean shouldDownload(File file) { + return !file.exists() || isOverOneDay(file); + } + + private static boolean isOverOneDay(File file) { + long lastModified = file.lastModified(); + long currentTimeMillis = System.currentTimeMillis(); + return currentTimeMillis - lastModified > 1000 * 60 * 60 * 24; + } + + private static Date parseDateTime(String text) throws ParseException { + return formatFull.parse(text.substring(0, 14)); + } + + private static void readXml(Live live, String xml) throws Exception { + Set exist = new HashSet<>(); + Map epgMap = new HashMap<>(); + Map mapping = new HashMap<>(); + Tv tv = new Persister().read(Tv.class, xml); + for (Group group : live.getGroups()) for (Channel channel : group.getChannel()) exist.add(channel.getTvgName()); + for (Tv.Channel channel : tv.getChannel()) mapping.put(channel.getId(), channel.getDisplayName()); + for (Tv.Programme programme : tv.getProgramme()) { + String key = mapping.get(programme.getChannel()); + if (!exist.contains(key)) continue; + String title = programme.getTitle(); + String start = programme.getStart(); + String stop = programme.getStop(); + Date startDate = parseDateTime(start); + Date endDate = parseDateTime(stop); + EpgData epgData = new EpgData(); + epgData.setStart(formatTime.format(startDate)); + epgData.setEnd(formatTime.format(endDate)); + epgData.setStartTime(startDate.getTime()); + epgData.setEndTime(endDate.getTime()); + epgData.setTitle(title); + Epg epg = epgMap.get(key); + if (epg == null) epgMap.put(key, epg = Epg.create(key, formatDate.format(startDate))); + epg.getList().add(epgData); + } + for (Group group : live.getGroups()) { + for (Channel channel : group.getChannel()) { + channel.setData(epgMap.get(channel.getTvgName())); + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fongmi/android/tv/api/LiveParser.java b/app/src/main/java/com/fongmi/android/tv/api/LiveParser.java index 087307f6e..c2dc4d2cf 100644 --- a/app/src/main/java/com/fongmi/android/tv/api/LiveParser.java +++ b/app/src/main/java/com/fongmi/android/tv/api/LiveParser.java @@ -27,6 +27,7 @@ public class LiveParser { private static final Pattern CATCHUP = Pattern.compile(".*catchup=\"(.?|.+?)\".*"); private static final Pattern TVG_NAME = Pattern.compile(".*tvg-name=\"(.?|.+?)\".*"); private static final Pattern TVG_LOGO = Pattern.compile(".*tvg-logo=\"(.?|.+?)\".*"); + private static final Pattern TVG_URL = Pattern.compile(".*x-tvg-url=\"(.?|.+?)\".*"); private static final Pattern GROUP = Pattern.compile(".*group-title=\"(.?|.+?)\".*"); private static final Pattern NAME = Pattern.compile(".*,(.+?)$"); @@ -74,6 +75,7 @@ public class LiveParser { if (setting.find(line)) { setting.check(line); } else if (line.startsWith("#EXTM3U")) { + live.setEpg(extract(line, TVG_URL)); catchup.setType(extract(line, CATCHUP)); catchup.setSource(extract(line, CATCHUP_SOURCE)); } else if (line.startsWith("#EXTINF:")) { 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 978eba059..156d038fa 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 @@ -325,8 +325,8 @@ public class Channel { if (live.getOrigin().length() > 0 && getOrigin().isEmpty()) setOrigin(live.getOrigin()); if (!live.getCatchup().isEmpty() && getCatchup().isEmpty()) setCatchup(live.getCatchup()); if (live.getReferer().length() > 0 && getReferer().isEmpty()) setReferer(live.getReferer()); - if (!getEpg().startsWith("http")) setEpg(live.getEpg().replace("{name}", getTvgName()).replace("{epg}", getEpg())); - if (!getLogo().startsWith("http")) setLogo(live.getLogo().replace("{name}", getTvgName()).replace("{logo}", getLogo())); + if (!getEpg().startsWith("http") && live.getEpg().contains("{")) setEpg(live.getEpg().replace("{name}", getTvgName()).replace("{epg}", getEpg())); + if (!getLogo().startsWith("http") && live.getLogo().contains("{")) setLogo(live.getLogo().replace("{name}", getTvgName()).replace("{logo}", getLogo())); } public void setLine(String line) { 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 4ccdadaaf..ec98d0323 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 @@ -35,8 +35,16 @@ public class Epg { } } + public static Epg create(String key, String date) { + Epg item = new Epg(); + item.setKey(key); + item.setDate(date); + item.setList(new ArrayList<>()); + return item; + } + public String getKey() { - return key; + return TextUtils.isEmpty(key) ? "" : key; } public void setKey(String key) { @@ -47,6 +55,10 @@ public class Epg { return TextUtils.isEmpty(date) ? "" : date; } + public void setDate(String date) { + this.date = date; + } + public List getList() { return list == null ? Collections.emptyList() : list; } 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 e3b29c602..5cbdd31b4 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 @@ -36,10 +36,18 @@ public class EpgData { return TextUtils.isEmpty(start) ? "" : start; } + public void setStart(String start) { + this.start = start; + } + public String getEnd() { return TextUtils.isEmpty(end) ? "" : end; } + public void setEnd(String end) { + this.end = end; + } + public boolean isSelected() { return selected; } diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Live.java b/app/src/main/java/com/fongmi/android/tv/bean/Live.java index e213524ee..9e4192c70 100644 --- a/app/src/main/java/com/fongmi/android/tv/bean/Live.java +++ b/app/src/main/java/com/fongmi/android/tv/bean/Live.java @@ -180,6 +180,10 @@ public class Live { return TextUtils.isEmpty(epg) ? "" : epg; } + public void setEpg(String epg) { + this.epg = epg; + } + public String getUa() { return TextUtils.isEmpty(ua) ? "" : ua; } diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Tv.java b/app/src/main/java/com/fongmi/android/tv/bean/Tv.java new file mode 100644 index 000000000..ee6e642af --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Tv.java @@ -0,0 +1,79 @@ +package com.fongmi.android.tv.bean; + +import android.text.TextUtils; + +import org.simpleframework.xml.Attribute; +import org.simpleframework.xml.Element; +import org.simpleframework.xml.ElementList; +import org.simpleframework.xml.Root; + +import java.util.Collections; +import java.util.List; + +@Root(name = "tv", strict = false) +public class Tv { + + @ElementList(entry = "channel", inline = true) + private List channel; + + @ElementList(entry = "programme", inline = true) + private List programme; + + public List getChannel() { + return channel == null ? Collections.emptyList() : channel; + } + + public List getProgramme() { + return programme == null ? Collections.emptyList() : programme; + } + + @Root(name = "channel") + public static class Channel { + + @Attribute(name = "id") + private String id; + + @Element(name = "display-name") + private String displayName; + + public String getId() { + return TextUtils.isEmpty(id) ? "" : id; + } + + public String getDisplayName() { + return TextUtils.isEmpty(displayName) ? "" : displayName; + } + } + + @Root(name = "programme") + public static class Programme { + + @Attribute(name = "start") + private String start; + + @Attribute(name = "stop") + private String stop; + + @Attribute(name = "channel") + private String channel; + + @Element(name = "title") + private String title; + + public String getStart() { + return start; + } + + public String getStop() { + return stop; + } + + public String getChannel() { + return channel; + } + + public String getTitle() { + return title; + } + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/model/LiveViewModel.java b/app/src/main/java/com/fongmi/android/tv/model/LiveViewModel.java index 20f3398c4..8a0910d81 100644 --- a/app/src/main/java/com/fongmi/android/tv/model/LiveViewModel.java +++ b/app/src/main/java/com/fongmi/android/tv/model/LiveViewModel.java @@ -4,6 +4,7 @@ import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; import com.fongmi.android.tv.Constant; +import com.fongmi.android.tv.api.EpgParser; import com.fongmi.android.tv.api.LiveParser; import com.fongmi.android.tv.api.config.VodConfig; import com.fongmi.android.tv.bean.Channel; @@ -53,6 +54,7 @@ public class LiveViewModel extends ViewModel { execute(LIVE, () -> { VodConfig.get().setRecent(item.getJar()); LiveParser.start(item); + EpgParser.start(item); verify(item); return item; }); @@ -62,7 +64,7 @@ public class LiveViewModel extends ViewModel { String date = formatDate.format(new Date()); String url = item.getEpg().replace("{date}", date); execute(EPG, () -> { - if (!item.getData().equal(date)) item.setData(Epg.objectFrom(OkHttp.string(url), item.getName(), formatTime)); + if (!item.getData().equal(date)) item.setData(Epg.objectFrom(OkHttp.string(url), item.getTvgName(), formatTime)); return item.getData().selected(); }); } 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 bd3a9071b..b5c91cdd4 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 @@ -442,8 +442,7 @@ public class LiveActivity extends BaseActivity implements CustomKeyDownLive.List hideEpg(); } - @Override - public void showEpg(Channel item) { + private void showEpg(Channel item) { if (mChannel == null || mChannel.getData().getList().isEmpty() || mEpgDataAdapter.getItemCount() == 0 || !mChannel.equals(item)) return; mBinding.widget.epgData.scrollToPosition(item.getData().getSelected()); mBinding.widget.epg.setVisibility(View.VISIBLE); @@ -570,12 +569,16 @@ public class LiveActivity extends BaseActivity implements CustomKeyDownLive.List @Override public void onItemClick(Channel item) { - mGroup.setPosition(mChannelAdapter.setSelected(item.group(mGroup))); - setArtwork(item.getLogo()); - mChannel = item; - showInfo(); - hideUI(); - fetch(); + if (item.getData().getList().size() > 0 && item.isSelected() && mChannel != null) { + showEpg(item); + } else { + mGroup.setPosition(mChannelAdapter.setSelected(item.group(mGroup))); + setArtwork(item.getLogo()); + mChannel = item; + showInfo(); + hideUI(); + fetch(); + } } @Override @@ -641,7 +644,7 @@ public class LiveActivity extends BaseActivity implements CustomKeyDownLive.List } private void setEpg(Epg epg) { - if (mChannel != null && mChannel.getName().equals(epg.getKey())) setEpg(); + if (mChannel != null && mChannel.getTvgName().equals(epg.getKey())) setEpg(); } private void fetch() { diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ChannelAdapter.java b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ChannelAdapter.java index f75dcbf8c..669fa230b 100644 --- a/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ChannelAdapter.java +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ChannelAdapter.java @@ -1,7 +1,6 @@ package com.fongmi.android.tv.ui.adapter; import android.view.LayoutInflater; -import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; @@ -25,8 +24,6 @@ public class ChannelAdapter extends RecyclerView.Adapter mListener.showEpg(item)); holder.binding.getRoot().setOnClickListener(view -> mListener.onItemClick(item)); holder.binding.getRoot().setOnLongClickListener(view -> mListener.onLongClick(item)); - holder.binding.epg.setVisibility(item.getData().getList().isEmpty() || !item.isSelected() ? View.GONE : View.VISIBLE); } static class ViewHolder extends RecyclerView.ViewHolder { diff --git a/app/src/mobile/res/drawable/ic_action_arrow.xml b/app/src/mobile/res/drawable/ic_action_arrow.xml deleted file mode 100644 index 3577518ab..000000000 --- a/app/src/mobile/res/drawable/ic_action_arrow.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - \ No newline at end of file diff --git a/app/src/mobile/res/layout/adapter_channel.xml b/app/src/mobile/res/layout/adapter_channel.xml index 405eb4c51..f093dc427 100644 --- a/app/src/mobile/res/layout/adapter_channel.xml +++ b/app/src/mobile/res/layout/adapter_channel.xml @@ -43,12 +43,4 @@ android:textSize="14sp" tools:text="CNN" /> - - \ No newline at end of file