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 66a2855bf..be1960e0a 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 @@ -4,6 +4,7 @@ import android.util.Base64; import com.fongmi.android.tv.R; import com.fongmi.android.tv.bean.Channel; +import com.fongmi.android.tv.bean.Drm; import com.fongmi.android.tv.bean.Group; import com.fongmi.android.tv.bean.Live; import com.fongmi.android.tv.utils.Utils; @@ -55,13 +56,16 @@ public class LiveParser { } private static void m3u(Live live, String text) { + Setting setting = Setting.create(); Channel channel = Channel.create(""); for (String line : text.split("\n")) { + setting.check(line); if (Thread.interrupted()) break; if (line.startsWith("#EXTINF:")) { Group group = live.find(Group.create(extract(line, GROUP))); channel = group.find(Channel.create(extract(line, NAME))); channel.setLogo(extract(line, LOGO)); + setting.copy(channel).clear(); } else if (line.contains("://")) { channel.getUrls().add(line); } @@ -113,9 +117,11 @@ public class LiveParser { private static class Setting { - public String ua; - public String referer; - public Integer player; + private String ua; + private String key; + private String type; + private String referer; + private Integer player; public static Setting create() { return new Setting(); @@ -125,13 +131,17 @@ public class LiveParser { if (line.startsWith("ua")) ua(line); if (line.startsWith("player")) player(line); if (line.startsWith("referer")) referer(line); + if (line.startsWith("#KODIPROP:inputstream.adaptive.license_key")) key(line); + if (line.startsWith("#KODIPROP:inputstream.adaptive.license_type")) type(line); if (line.contains("#genre#")) clear(); } - public void copy(Channel channel) { + public Setting copy(Channel channel) { if (ua != null) channel.setUa(ua); if (referer != null) channel.setReferer(referer); if (player != null) channel.setPlayerType(player); + if (key != null && type != null) channel.setDrm(Drm.create(key, type)); + return this; } private void ua(String line) { @@ -158,6 +168,22 @@ public class LiveParser { } } + private void key(String line) { + try { + key = line.split("=")[1].trim(); + } catch (Exception e) { + key = null; + } + } + + private void type(String line) { + try { + type = line.split("=")[1].trim(); + } catch (Exception e) { + type = null; + } + } + private void clear() { player = null; referer = null; 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 7d742a26b..53a8af9b7 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 @@ -39,6 +39,8 @@ public class Channel { private JsonElement header; @SerializedName("playerType") private Integer playerType; + @SerializedName("drm") + private Drm drm; private boolean selected; private Group group; @@ -141,6 +143,14 @@ public class Channel { this.playerType = playerType; } + public Drm getDrm() { + return drm; + } + + public void setDrm(Drm drm) { + this.drm = drm; + } + public Group getGroup() { return group; } diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Drm.java b/app/src/main/java/com/fongmi/android/tv/bean/Drm.java new file mode 100644 index 000000000..93d2c631a --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/bean/Drm.java @@ -0,0 +1,52 @@ +package com.fongmi.android.tv.bean; + +import android.text.TextUtils; + +import androidx.media3.common.C; +import androidx.media3.common.MediaItem; + +import com.fongmi.android.tv.server.Server; +import com.github.catvod.utils.Util; +import com.google.gson.annotations.SerializedName; + +import java.util.UUID; + +public class Drm { + + @SerializedName("key") + private String key; + @SerializedName("type") + private String type; + + public static Drm create(String key, String type) { + return new Drm(key, type); + } + + private Drm(String key, String type) { + this.key = key; + this.type = type; + } + + private String getKey() { + return TextUtils.isEmpty(key) ? "" : key; + } + + private String getType() { + return TextUtils.isEmpty(type) ? "" : type; + } + + private UUID getUUID() { + if (getType().contains("widevine")) return C.WIDEVINE_UUID; + if (getType().contains("clearkey")) return C.CLEARKEY_UUID; + return C.UUID_NIL; + } + + private String getUri() { + if (getKey().startsWith("http")) return getKey(); + return Server.get().getAddress("license/") + Util.base64(getKey()); + } + + public MediaItem.DrmConfiguration get() { + return new MediaItem.DrmConfiguration.Builder(getUUID()).setLicenseUri(getUri()).build(); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/player/ExoUtil.java b/app/src/main/java/com/fongmi/android/tv/player/ExoUtil.java index b9e1b2ebb..9b249dd76 100644 --- a/app/src/main/java/com/fongmi/android/tv/player/ExoUtil.java +++ b/app/src/main/java/com/fongmi/android/tv/player/ExoUtil.java @@ -37,6 +37,7 @@ import androidx.media3.ui.CaptionStyleCompat; import com.fongmi.android.tv.App; import com.fongmi.android.tv.Setting; import com.fongmi.android.tv.bean.Channel; +import com.fongmi.android.tv.bean.Drm; import com.fongmi.android.tv.bean.Result; import com.fongmi.android.tv.bean.Sub; import com.fongmi.android.tv.utils.Sniffer; @@ -98,27 +99,28 @@ public class ExoUtil { } public static MediaSource getSource(Result result, int errorCode) { - return getSource(result.getHeaders(), result.getRealUrl(), result.getFormat(), result.getSubs(), errorCode); + return getSource(result.getHeaders(), result.getRealUrl(), result.getFormat(), result.getSubs(), null, errorCode); } public static MediaSource getSource(Channel channel, int errorCode) { - return getSource(channel.getHeaders(), channel.getUrl(), null, Collections.emptyList(), errorCode); + return getSource(channel.getHeaders(), channel.getUrl(), null, Collections.emptyList(), channel.getDrm(), errorCode); } public static MediaSource getSource(Map headers, String url, int errorCode) { - return getSource(headers, url, null, Collections.emptyList(), errorCode); + return getSource(headers, url, null, Collections.emptyList(), null, errorCode); } - private static MediaSource getSource(Map headers, String url, String format, List subs, int errorCode) { + private static MediaSource getSource(Map headers, String url, String format, List subs, Drm drm, int errorCode) { Uri uri = Uri.parse(url.trim().replace("\\", "")); String mimeType = getMimeType(format, errorCode); if (uri.getUserInfo() != null) headers.put(HttpHeaders.AUTHORIZATION, "Basic " + Util.base64(uri.getUserInfo())); - return new DefaultMediaSourceFactory(getDataSourceFactory(headers), getExtractorsFactory()).createMediaSource(getMediaItem(uri, mimeType, subs)); + return new DefaultMediaSourceFactory(getDataSourceFactory(headers), getExtractorsFactory()).createMediaSource(getMediaItem(uri, mimeType, subs, drm)); } - private static MediaItem getMediaItem(Uri uri, String mimeType, List subs) { + private static MediaItem getMediaItem(Uri uri, String mimeType, List subs, Drm drm) { MediaItem.Builder builder = new MediaItem.Builder().setUri(uri); if (subs.size() > 0) builder.setSubtitleConfigurations(getSubtitles(subs)); + if (drm != null) builder.setDrmConfiguration(drm.get()); builder.setAllowChunklessPreparation(Players.isHard()); if (mimeType != null) builder.setMimeType(mimeType); builder.setAds(Sniffer.getRegex(uri)); diff --git a/app/src/main/java/com/fongmi/android/tv/server/Nano.java b/app/src/main/java/com/fongmi/android/tv/server/Nano.java index dfd3b9d41..089587521 100644 --- a/app/src/main/java/com/fongmi/android/tv/server/Nano.java +++ b/app/src/main/java/com/fongmi/android/tv/server/Nano.java @@ -1,6 +1,7 @@ package com.fongmi.android.tv.server; import android.net.Uri; +import android.util.Base64; import com.fongmi.android.tv.R; import com.fongmi.android.tv.api.ApiConfig; @@ -94,6 +95,7 @@ public class Nano extends NanoHTTPD { else if (url.startsWith("/newFolder")) return doNewFolder(session.getParms()); else if (url.startsWith("/delFolder") || url.startsWith("/delFile")) return doDelFolder(session.getParms()); else if (url.startsWith("/tvbus")) return createSuccessResponse(LiveConfig.get().getHome().getCore().getResp()); + else if (url.startsWith("/license/")) return createSuccessResponse(new String(Base64.decode(url.substring(9), Base64.DEFAULT))); break; } return createErrorResponse(NanoHTTPD.Response.Status.NOT_FOUND, "Not Found");