Support tvg-url parse

pull/497/head
FongMi 2 years ago
parent df27337880
commit 086e40f245
  1. 4
      app/proguard-rules.pro
  2. 12
      app/src/leanback/java/com/fongmi/android/tv/ui/activity/LiveActivity.java
  3. 2
      app/src/main/java/com/fongmi/android/tv/Constant.java
  4. 83
      app/src/main/java/com/fongmi/android/tv/api/EpgParser.java
  5. 2
      app/src/main/java/com/fongmi/android/tv/api/LiveParser.java
  6. 4
      app/src/main/java/com/fongmi/android/tv/bean/Channel.java
  7. 14
      app/src/main/java/com/fongmi/android/tv/bean/Epg.java
  8. 8
      app/src/main/java/com/fongmi/android/tv/bean/EpgData.java
  9. 4
      app/src/main/java/com/fongmi/android/tv/bean/Live.java
  10. 79
      app/src/main/java/com/fongmi/android/tv/bean/Tv.java
  11. 4
      app/src/main/java/com/fongmi/android/tv/model/LiveViewModel.java
  12. 21
      app/src/mobile/java/com/fongmi/android/tv/ui/activity/LiveActivity.java
  13. 5
      app/src/mobile/java/com/fongmi/android/tv/ui/adapter/ChannelAdapter.java
  14. 10
      app/src/mobile/res/drawable/ic_action_arrow.xml
  15. 8
      app/src/mobile/res/layout/adapter_channel.xml

@ -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 <fields>; }
-keepclassmembers,allowobfuscation class * { @org.simpleframework.xml.Root <fields>; }
-keepclassmembers,allowobfuscation class * { @org.simpleframework.xml.Text <fields>; }
-keepclassmembers,allowobfuscation class * { @org.simpleframework.xml.Element <fields>; }
-keepclassmembers,allowobfuscation class * { @org.simpleframework.xml.Attribute <fields>; }
-keepclassmembers,allowobfuscation class * { @org.simpleframework.xml.ElementList <fields>; }

@ -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() {

@ -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;
//解析預設時間

@ -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<String> exist = new HashSet<>();
Map<String, Epg> epgMap = new HashMap<>();
Map<String, String> 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()));
}
}
}
}

@ -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:")) {

@ -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) {

@ -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<EpgData> getList() {
return list == null ? Collections.emptyList() : list;
}

@ -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;
}

@ -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;
}

@ -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> channel;
@ElementList(entry = "programme", inline = true)
private List<Programme> programme;
public List<Channel> getChannel() {
return channel == null ? Collections.emptyList() : channel;
}
public List<Programme> 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;
}
}
}

@ -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();
});
}

@ -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() {

@ -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<ChannelAdapter.ViewHold
public interface OnClickListener {
void showEpg(Channel item);
void onItemClick(Channel item);
boolean onLongClick(Channel item);
@ -86,10 +83,8 @@ public class ChannelAdapter extends RecyclerView.Adapter<ChannelAdapter.ViewHold
holder.binding.name.setText(item.getName());
holder.binding.number.setText(item.getNumber());
holder.binding.getRoot().setSelected(item.isSelected());
holder.binding.epg.setOnClickListener(view -> 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 {

@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#FFFFFF"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M496.35,480L312.35,296L376,232.35L623.65,480L376,727.65L312.35,664L496.35,480Z" />
</vector>

@ -43,12 +43,4 @@
android:textSize="14sp"
tools:text="CNN" />
<ImageView
android:id="@+id/epg"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="4dp"
android:background="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_action_arrow" />
</LinearLayout>
Loading…
Cancel
Save