自动去bom头 直播增加壳子代理/proxy?go=live;修正模拟器播放中断的问题

pull/138/head
于俊 1 year ago
parent 399d7b3813
commit 971b86ffb7
  1. 93
      app/src/main/java/com/github/tvbox/osc/player/IjkMediaPlayer.java
  2. 26
      app/src/main/java/com/github/tvbox/osc/server/RemoteServer.java
  3. 22
      app/src/main/java/com/github/tvbox/osc/ui/activity/PlayActivity.java
  4. 4
      app/src/main/java/com/github/tvbox/osc/util/OkGoHelper.java
  5. 198
      app/src/main/java/com/github/tvbox/osc/util/Proxy.java

@ -5,6 +5,7 @@ import android.text.TextUtils;
import com.github.tvbox.osc.api.ApiConfig;
import com.github.tvbox.osc.bean.IJKCode;
import com.github.tvbox.osc.server.ControlManager;
import com.github.tvbox.osc.util.FileUtils;
import com.github.tvbox.osc.util.HawkConfig;
import com.github.tvbox.osc.util.LOG;
@ -12,6 +13,8 @@ import com.github.tvbox.osc.util.MD5;
import com.orhanobut.hawk.Hawk;
import java.io.File;
import java.net.URI;
import java.net.URLEncoder;
import java.util.LinkedHashMap;
import java.util.Map;
@ -74,33 +77,48 @@ public class IjkMediaPlayer extends IjkPlayer {
}
}
private static final String ITV_TARGET_DOMAIN = "gslbserv.itv.cmvideo.cn";
@Override
public void setDataSource(String path, Map<String, String> headers) {
try {
if (path.contains("rtsp") || path.contains("udp") || path.contains("rtp")) {
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "infbuf", 1);
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_transport", "tcp");
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_flags", "prefer_tcp");
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 512 * 1000);
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", 2 * 1000 * 1000);
} else if (!TextUtils.isEmpty(path)
&& !path.contains(".m3u8")
&& (path.contains(".mp4") || path.contains(".mkv") || path.contains(".avi"))) {
if (Hawk.get(HawkConfig.IJK_CACHE_PLAY, false)) {
String cachePath = FileUtils.getCachePath() + "/ijkcaches/";
String cacheMapPath = cachePath;
File cacheFile = new File(cachePath);
if (!cacheFile.exists()) cacheFile.mkdirs();
String tmpMd5 = MD5.string2MD5(path);
cachePath += tmpMd5 + ".file";
cacheMapPath += tmpMd5 + ".map";
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "cache_file_path", cachePath);
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "cache_map_path", cacheMapPath);
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "parse_cache_map", 1);
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "auto_save_map", 1);
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "cache_max_capacity", 60 * 1024 * 1024);
path = "ijkio:cache:ffio:" + path;
}
switch (getStreamType(path)) {
case RTSP_UDP_RTP:
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "infbuf", 1);
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_transport", "tcp");
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "rtsp_flags", "prefer_tcp");
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "probesize", 512 * 1000);
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "analyzeduration", 2 * 1000 * 1000);
break;
case CACHE_VIDEO:
if (Hawk.get(HawkConfig.IJK_CACHE_PLAY, false)) {
String cachePath = FileUtils.getCachePath() + "/ijkcaches/";
File cacheFile = new File(cachePath);
if (!cacheFile.exists()) cacheFile.mkdirs();
String tmpMd5 = MD5.string2MD5(path);
String cacheFilePath = cachePath + tmpMd5 + ".file";
String cacheMapPath = cachePath + tmpMd5 + ".map";
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "cache_file_path", cacheFilePath);
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "cache_map_path", cacheMapPath);
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "parse_cache_map", 1);
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "auto_save_map", 1);
mMediaPlayer.setOption(tv.danmaku.ijk.media.player.IjkMediaPlayer.OPT_CATEGORY_FORMAT, "cache_max_capacity", 60 * 1024 * 1024);
path = "ijkio:cache:ffio:" + path;
}
break;
case M3U8:
// 直播且是ijk的时候自动自动走代理解决DNS
if (Hawk.get(HawkConfig.PLAYER_IS_LIVE, false) ) {
URI uri = new URI(path);
String host = uri.getHost();
if(ITV_TARGET_DOMAIN.equalsIgnoreCase(host))path = ControlManager.get().getAddress(true) + "proxy?go=live&type=m3u8&url="+ URLEncoder.encode(path,"UTF-8");
}
break;
default:
break;
}
} catch (Exception e) {
e.printStackTrace();
@ -110,6 +128,33 @@ public class IjkMediaPlayer extends IjkPlayer {
super.setDataSource(path, null);
}
/**
* 解析 URL
*/
private static final int RTSP_UDP_RTP = 1;
private static final int CACHE_VIDEO = 2;
private static final int M3U8 = 3;
private static final int OTHER = 0;
private int getStreamType(String path) {
if (TextUtils.isEmpty(path)) {
return OTHER;
}
// 低成本检查 RTSP/UDP/RTP 类型
String lowerPath = path.toLowerCase();
if (lowerPath.startsWith("rtsp://") || lowerPath.startsWith("udp://") || lowerPath.startsWith("rtp://")) {
return RTSP_UDP_RTP;
}
String cleanUrl = path.split("\\?")[0];
if (cleanUrl.endsWith(".m3u8")) {
return M3U8;
}
if (cleanUrl.endsWith(".mp4") || cleanUrl.endsWith(".mkv") || cleanUrl.endsWith(".avi")) {
return CACHE_VIDEO;
}
return OTHER;
}
private void setDataSourceHeader(Map<String, String> headers) {
if (headers != null && !headers.isEmpty()) {
String userAgent = headers.get("User-Agent");

@ -13,6 +13,7 @@ import com.github.tvbox.osc.event.RefreshEvent;
import com.github.tvbox.osc.event.ServerEvent;
import com.github.tvbox.osc.util.FileUtils;
import com.github.tvbox.osc.util.OkGoHelper;
import com.github.tvbox.osc.util.Proxy;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
@ -135,6 +136,31 @@ public class RemoteServer extends NanoHTTPD {
return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "500");
}
}
if (params.containsKey("go")) {
Object[] rs = Proxy.proxy(params);
try {
assert rs != null;
int code = (int) rs[0];
String mime = (String) rs[1];
InputStream stream = rs[2] != null ? (InputStream) rs[2] : null;
Response response = NanoHTTPD.newChunkedResponse(
NanoHTTPD.Response.Status.lookup(code),
mime,
stream
);
if(rs.length>=4){
HashMap<String, String> mapHeader = (HashMap<String, String>) rs[3];
if(!mapHeader.isEmpty()){
for (String key : mapHeader.keySet()) {
response.addHeader(key, mapHeader.get(key));
}
}
}
return response;
} catch (Throwable th) {
return NanoHTTPD.newFixedLengthResponse(NanoHTTPD.Response.Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "500");
}
}
} else if (fileName.startsWith("/file/")) {
try {
String f = fileName.substring(6);

@ -93,6 +93,7 @@ import org.xwalk.core.XWalkWebResourceResponse;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
@ -500,7 +501,13 @@ public class PlayActivity extends BaseActivity {
void playUrl(String url, HashMap<String, String> headers) {
LOG.i("playUrl:" + url);
if(autoRetryCount>1 && url.contains(".m3u8")){
//todo
try {
String url_encode;
url_encode=URLEncoder.encode(url,"UTF-8");
url = ControlManager.get().getAddress(true) + "proxy?go=bom&url="+ url_encode;
}catch (UnsupportedEncodingException e) {
}
}
final String finalUrl = url;
runOnUiThread(new Runnable() {
@ -826,12 +833,21 @@ public class PlayActivity extends BaseActivity {
private int autoRetryCount = 0;
private long lastRetryTime = 0; // 记录上次调用时间(毫秒)
boolean autoRetry() {
if (loadFoundVideoUrls != null && loadFoundVideoUrls.size() > 0) {
long currentTime = System.currentTimeMillis();
// 如果距离上次重试超过 10 秒(10000 毫秒),重置重试次数
if (currentTime - lastRetryTime > 10_000) {
autoRetryCount = 0;
}
lastRetryTime = currentTime; // 更新上次调用时间
if (loadFoundVideoUrls != null && !loadFoundVideoUrls.isEmpty()) {
autoRetryFromLoadFoundVideoUrls();
return true;
}
if (autoRetryCount < 1) {
if (autoRetryCount < 2) {
autoRetryCount++;
play(false);
return true;

@ -46,6 +46,7 @@ import xyz.doikki.videoplayer.exo.ExoMediaSourceHelper;
public class OkGoHelper {
public static final long DEFAULT_MILLISECONDS = 8000; //默认的超时时间
static OkHttpClient ItvClient = null;
static void initExoOkHttpClient() {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor("OkExoPlayer");
@ -72,8 +73,9 @@ public class OkGoHelper {
// builder.dns(dnsOverHttps);
builder.dns(new CustomDns());
ItvClient=builder.build();
ExoMediaSourceHelper.getInstance(App.getInstance()).setOkClient(builder.build());
ExoMediaSourceHelper.getInstance(App.getInstance()).setOkClient(ItvClient);
}
public static DnsOverHttps dnsOverHttps = null;

@ -0,0 +1,198 @@
package com.github.tvbox.osc.util;
import com.github.catvod.crawler.SpiderDebug;
import com.github.tvbox.osc.server.ControlManager;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Map;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
public class Proxy {
public static Object[] proxy(Map<String, String> params) {
try {
String what = params.get("go");
assert what != null;
if (what.equals("live")) {
return itv(params);
}
else if (what.equals("bom")) {
return removeBOMFromM3U8(params);
}
else if (what.equals("ad")) {
//TODO
return null;
}
} catch (Throwable ignored) {
}
return null;
}
public static Object[] itv(Map<String, String> params) throws Exception {
try {
Object[] result = new Object[3];
String url = params.get("url");
String type = params.get("type");
url = URLDecoder.decode(url,"UTF-8");
OkHttpClient client = OkGoHelper.ItvClient;
assert type != null;
if (type.equals("m3u8")) {
String redirectUrl = getRedirectedUrl(url);
// LOG.i("echo-url"+redirectUrl);
Request request = new Request.Builder().url(redirectUrl).build();
try (Response response = executeRequest(client, request)) {
if (response.isSuccessful()) {
assert response.body() != null;
String respContent = response.body().string();
String m3u8Content = processM3u8Content(respContent, redirectUrl);
result[0] = 200;
result[1] = "application/vnd.apple.mpegurl";
result[2] = new ByteArrayInputStream(m3u8Content.getBytes());
} else {
throw new IOException("M3U8 Request failed with code: " + response.code());
}
}
} else if (type.equals("ts")) {
Request request = new Request.Builder().url(url).build();
try (Response response = executeRequest(client, request)) {
if (response.isSuccessful()) {
result[0] = 200;
result[1] = "video/mp2t";
assert response.body() != null;
result[2] = new ByteArrayInputStream(response.body().bytes());
} else {
throw new IOException("TS Request failed with code: " + response.code());
}
}
} else {
throw new IllegalArgumentException("Invalid type: " + type);
}
return result;
} catch (Exception e) {
SpiderDebug.log(e);
return null;
}
}
public static Object[] removeBOMFromM3U8(Map<String, String> params) throws Exception {
try {
Object[] result = new Object[3];
String url = params.get("url");
url = URLDecoder.decode(url,"UTF-8");
OkHttpClient client = OkGoHelper.ItvClient;
String redirectUrl = getRedirectedUrl(url);
// LOG.i("echo-url"+redirectUrl);
Request request = new Request.Builder().url(redirectUrl).build();
try (Response response = executeRequest(client, request)) {
if (response.isSuccessful()) {
assert response.body() != null;
String m3u8Content = response.body().string();
// 检查并去除 UTF-8 BOM 头(BOM 为 \uFEFF)
if (m3u8Content.startsWith("\ufeff")) {
m3u8Content = m3u8Content.substring(1);
}
result[0] = 200;
result[1] = "application/vnd.apple.mpegurl";
result[2] = new ByteArrayInputStream(m3u8Content.getBytes());
} else {
throw new IOException("M3U8 Request failed with code: " + response.code());
}
}
return result;
} catch (Exception e) {
SpiderDebug.log(e);
return null;
}
}
private static Response executeRequest(OkHttpClient client, Request request) throws IOException {
try {
return client.newCall(request).execute();
} catch (IOException e) {
System.err.println("网络请求异常:" + e.getMessage());
throw e; // 重新抛出异常,让外层处理
}
}
private static String processM3u8Content(String m3u8Content, String m3u8Url) {
String[] m3u8Lines = m3u8Content.trim().split("\n");
StringBuilder processedM3u8 = new StringBuilder();
for (String line : m3u8Lines) {
if (line.startsWith("#")) {
processedM3u8.append(line).append("\n");
} else {
processedM3u8.append(joinUrl(m3u8Url, line)).append("\n");
}
}
return processedM3u8.toString().replace("\\n\\n", "\n");
}
private static String joinUrl(String base, String url) {
if (base == null) base = "";
if (url == null) url = "";
try {
URI baseUri = new URI(base.trim());
url = url.trim();
URI urlUri = new URI(url);
String proxyUrl = ControlManager.get().getAddress(true) + "proxy?go=live&type=ts&url=";
if (url.startsWith("http://") || url.startsWith("https://")) {
return proxyUrl + URLEncoder.encode(urlUri.toString(),"UTF-8");
} else if (url.startsWith("://")) {
return proxyUrl + URLEncoder.encode(new URI(baseUri.getScheme() + url).toString(),"UTF-8");
} else if (url.startsWith("//")) {
return proxyUrl + URLEncoder.encode(new URI(baseUri.getScheme() + ":" + url).toString(),"UTF-8");
} else {
URI resolvedUri = baseUri.resolve(url);
return proxyUrl + URLEncoder.encode(resolvedUri.toString(),"UTF-8");
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static String getRedirectedUrl(String url) throws IOException {
OkHttpClient client = new OkHttpClient.Builder()
.followRedirects(false) // 不自动跟随重定向
.build();
Request request = new Request.Builder()
.url(url)
.build();
try (Response response = client.newCall(request).execute()) {
if (response.isRedirect()) { // 判断是否为重定向
return response.header("Location"); // 获取重定向后的地址
}
return url; // 如果没有重定向,返回原 URL
}
}
public static String getM3U8Content(String url) throws IOException {
Request request = new Request.Builder()
.url(url)
.build();
OkHttpClient client = OkGoHelper.ItvClient;
try (Response response = client.newCall(request).execute()) {
if (response.isSuccessful()) {
return response.body().string(); // 获取 m3u8 文件内容
} else {
throw new IOException("请求失败,HTTP 状态码: " + response.code());
}
}
}
}
Loading…
Cancel
Save