diff --git a/app/build.gradle b/app/build.gradle index ebbb4197..22c0eb85 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -99,4 +99,5 @@ dependencies { implementation 'org.jsoup:jsoup:1.14.1' implementation 'com.github.hedzr:android-file-chooser:v1.2.0-final' implementation 'commons-io:commons-io:2.11.0' + implementation 'com.googlecode.juniversalchardet:juniversalchardet:1.0.3' } \ No newline at end of file diff --git a/app/src/main/java/com/github/tvbox/osc/subtitle/DefaultSubtitleEngine.java b/app/src/main/java/com/github/tvbox/osc/subtitle/DefaultSubtitleEngine.java index 79450642..189ec21d 100644 --- a/app/src/main/java/com/github/tvbox/osc/subtitle/DefaultSubtitleEngine.java +++ b/app/src/main/java/com/github/tvbox/osc/subtitle/DefaultSubtitleEngine.java @@ -32,12 +32,16 @@ import androidx.annotation.Nullable; import android.text.TextUtils; import android.util.Log; +import com.github.tvbox.osc.base.App; +import com.github.tvbox.osc.cache.CacheManager; import com.github.tvbox.osc.subtitle.cache.SubtitleCache; import com.github.tvbox.osc.subtitle.model.Subtitle; import com.github.tvbox.osc.subtitle.model.Time; -import com.github.tvbox.osc.subtitle.model.TimedTextObject; +import com.github.tvbox.osc.util.FileUtils; +import com.github.tvbox.osc.util.MD5; import com.github.tvbox.osc.util.SubtitleHelper; +import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.TreeMap; @@ -67,7 +71,6 @@ public class DefaultSubtitleEngine implements SubtitleEngine { public DefaultSubtitleEngine() { mCache = new SubtitleCache(); - } @Override @@ -92,12 +95,12 @@ public class DefaultSubtitleEngine implements SubtitleEngine { } SubtitleLoader.loadSubtitle(path, new SubtitleLoader.Callback() { @Override - public void onSuccess(final TimedTextObject timedTextObject) { - if (timedTextObject == null) { + public void onSuccess(final SubtitleLoadSuccessResult subtitleLoadSuccessResult) { + if (subtitleLoadSuccessResult.timedTextObject == null) { Log.d(TAG, "onSuccess: timedTextObject is null."); return; } - final TreeMap captions = timedTextObject.captions; + final TreeMap captions = subtitleLoadSuccessResult.timedTextObject.captions; if (captions == null) { Log.d(TAG, "onSuccess: captions is null."); return; @@ -106,6 +109,23 @@ public class DefaultSubtitleEngine implements SubtitleEngine { setSubtitleDelay(SubtitleHelper.getTimeDelay()); notifyPrepared(); mCache.put(path, new ArrayList<>(captions.values())); + + String subtitlePath = subtitleLoadSuccessResult.subtitlePath; + if (subtitlePath.startsWith("http://") || subtitlePath.startsWith("https://")) { + String subtitleFileCacheDir = App.getInstance().getCacheDir().getAbsolutePath() + "/zimu/"; + File cacheDir = new File(subtitleFileCacheDir); + if (!cacheDir.exists()) { + cacheDir.mkdirs(); + } + String subtitleFile = subtitleFileCacheDir + subtitleLoadSuccessResult.fileName; + File cacheSubtitleFile = new File(subtitleFile); + boolean writeResult = FileUtils.writeSimple(subtitleLoadSuccessResult.content.getBytes(), cacheSubtitleFile); + if (writeResult) { + CacheManager.save(MD5.string2MD5(getPlaySubtitleCacheKey()), subtitleFile); + } + } else { + CacheManager.save(MD5.string2MD5(getPlaySubtitleCacheKey()), path); + } } @Override @@ -143,6 +163,15 @@ public class DefaultSubtitleEngine implements SubtitleEngine { mSubtitles = thisSubtitles; } + private static String playSubtitleCacheKey; + public void setPlaySubtitleCacheKey(String cacheKey) { + playSubtitleCacheKey = cacheKey; + } + + public String getPlaySubtitleCacheKey() { + return playSubtitleCacheKey; + } + @Override public void reset() { stop(); diff --git a/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleEngine.java b/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleEngine.java index 09de9351..3ad3a2d3 100644 --- a/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleEngine.java +++ b/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleEngine.java @@ -52,6 +52,10 @@ public interface SubtitleEngine { */ void setSubtitleDelay(Integer milliseconds); + void setPlaySubtitleCacheKey(String cacheKey); + + String getPlaySubtitleCacheKey(); + /** * 开启字幕刷新任务 */ diff --git a/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleLoadSuccessResult.java b/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleLoadSuccessResult.java new file mode 100644 index 00000000..9d40ad8b --- /dev/null +++ b/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleLoadSuccessResult.java @@ -0,0 +1,10 @@ +package com.github.tvbox.osc.subtitle; + +import com.github.tvbox.osc.subtitle.model.TimedTextObject; + +public class SubtitleLoadSuccessResult { + public String fileName; + public String content; + public TimedTextObject timedTextObject; + public String subtitlePath; +} diff --git a/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleLoader.java b/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleLoader.java index 89d0bc5e..33b3e774 100644 --- a/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleLoader.java +++ b/app/src/main/java/com/github/tvbox/osc/subtitle/SubtitleLoader.java @@ -25,7 +25,6 @@ package com.github.tvbox.osc.subtitle; -import android.net.Uri; import android.text.TextUtils; import android.util.Log; @@ -35,16 +34,19 @@ import com.github.tvbox.osc.subtitle.format.FormatSRT; import com.github.tvbox.osc.subtitle.format.FormatSTL; import com.github.tvbox.osc.subtitle.model.TimedTextObject; import com.github.tvbox.osc.subtitle.runtime.AppTaskExecutor; +import com.github.tvbox.osc.util.FileUtils; import com.github.tvbox.osc.util.UnicodeReader; import com.lzy.okgo.OkGo; import org.apache.commons.io.input.ReaderInputStream; +import org.mozilla.universalchardet.UniversalDetector; + import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import java.nio.charset.Charset; import okhttp3.Response; @@ -77,12 +79,12 @@ public class SubtitleLoader { @Override public void run() { try { - final TimedTextObject timedTextObject = loadFromRemote(remoteSubtitlePath); + final SubtitleLoadSuccessResult subtitleLoadSuccessResult = loadFromRemote(remoteSubtitlePath); if (callback != null) { AppTaskExecutor.mainThread().execute(new Runnable() { @Override public void run() { - callback.onSuccess(timedTextObject); + callback.onSuccess(subtitleLoadSuccessResult); } }); } @@ -109,12 +111,12 @@ public class SubtitleLoader { @Override public void run() { try { - final TimedTextObject timedTextObject = loadFromLocal(localSubtitlePath); + final SubtitleLoadSuccessResult subtitleLoadSuccessResult = loadFromLocal(localSubtitlePath); if (callback != null) { AppTaskExecutor.mainThread().execute(new Runnable() { @Override public void run() { - callback.onSuccess(timedTextObject); + callback.onSuccess(subtitleLoadSuccessResult); } }); } @@ -135,7 +137,7 @@ public class SubtitleLoader { }); } - public TimedTextObject loadSubtitle(String path) { + public SubtitleLoadSuccessResult loadSubtitle(String path) { if (TextUtils.isEmpty(path)) { return null; } @@ -152,47 +154,75 @@ public class SubtitleLoader { return null; } - private static TimedTextObject loadFromRemote(final String remoteSubtitlePath) + private static SubtitleLoadSuccessResult loadFromRemote(final String remoteSubtitlePath) throws IOException, FatalParsingException { Log.d(TAG, "parseRemote: remoteSubtitlePath = " + remoteSubtitlePath); String referer = ""; - String from = ""; if (remoteSubtitlePath.contains("alicloud")) { referer = "https://www.aliyundrive.com/"; - from = "aliyundrive"; } else if (remoteSubtitlePath.contains("assrt.net")) { referer = "https://secure.assrt.net/"; - from = "assrt"; } String ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.54 Safari/537.36"; Response response = OkGo.get(remoteSubtitlePath) .headers("Referer", referer) .headers("User-Agent", ua) .execute(); - String content = response.body().string(); + byte[] bytes = response.body().bytes(); try { - Uri uri = Uri.parse(remoteSubtitlePath); - InputStream subtitle = new ByteArrayInputStream(content.getBytes()); + UniversalDetector detector = new UniversalDetector(null); + detector.handleData(bytes, 0, bytes.length); + detector.dataEnd(); + String encoding = detector.getDetectedCharset(); + String content = new String(bytes, encoding); + InputStream is = new ByteArrayInputStream(content.getBytes()); String filename = ""; - if (from == "aliyundrive") { - filename = uri.getQueryParameter("response-content-disposition"); - filename = "zimu." + filename.substring(filename.lastIndexOf(".")+1); - } else { - filename = uri.getPath(); - filename = "zimu." + filename.substring(filename.lastIndexOf(".")+1); + String contentDispostion = response.header("content-disposition", ""); + String[] cd = contentDispostion.split(";"); + if (cd.length > 1) { + String filenameInfo = cd[1]; + filenameInfo = filenameInfo.trim(); + if (filenameInfo.startsWith("filename=")) { + filename = filenameInfo.replace("filename=", ""); + filename = filename.replace("\"", ""); + } else if (filenameInfo.startsWith("filename*=")) { + filename = filenameInfo.substring(filenameInfo.lastIndexOf("''")+2); + } + filename = filename.trim(); } - return loadAndParse(subtitle, filename); + SubtitleLoadSuccessResult subtitleLoadSuccessResult = new SubtitleLoadSuccessResult(); + subtitleLoadSuccessResult.timedTextObject = loadAndParse(is, filename); + subtitleLoadSuccessResult.fileName = filename; + subtitleLoadSuccessResult.content = content; + subtitleLoadSuccessResult.subtitlePath = remoteSubtitlePath; + return subtitleLoadSuccessResult; } catch (Exception e) { e.printStackTrace(); } return null; } - private static TimedTextObject loadFromLocal(final String localSubtitlePath) + private static SubtitleLoadSuccessResult loadFromLocal(final String localSubtitlePath) throws IOException, FatalParsingException { Log.d(TAG, "parseLocal: localSubtitlePath = " + localSubtitlePath); File file = new File(localSubtitlePath); - return loadAndParse(new FileInputStream(file), file.getPath()); + if (!file.exists()) { + return null; + } + byte[] bytes = FileUtils.readSimple(file); + UniversalDetector detector = new UniversalDetector(null); + detector.handleData(bytes, 0, bytes.length); + detector.dataEnd(); + String encoding = detector.getDetectedCharset(); + String content = new String(bytes, encoding); + InputStream is = new ByteArrayInputStream(content.getBytes()); + String filePath = file.getPath(); + SubtitleLoadSuccessResult subtitleLoadSuccessResult = new SubtitleLoadSuccessResult(); + subtitleLoadSuccessResult.timedTextObject = loadAndParse(is, filePath); + String fileName = filePath.substring(filePath.lastIndexOf("/") + 1); + subtitleLoadSuccessResult.fileName = fileName; + subtitleLoadSuccessResult.subtitlePath = localSubtitlePath; + return subtitleLoadSuccessResult; } private static TimedTextObject loadAndParse(final InputStream is, final String filePath) @@ -201,7 +231,7 @@ public class SubtitleLoader { String ext = fileName.substring(fileName.lastIndexOf(".")); Log.d(TAG, "parse: name = " + fileName + ", ext = " + ext); Reader reader = new UnicodeReader(is); //处理有BOM头的utf8 - InputStream newInputStream = new ReaderInputStream(reader, "UTF-8"); + InputStream newInputStream = new ReaderInputStream(reader, Charset.defaultCharset()); if (".srt".equalsIgnoreCase(ext)) { return new FormatSRT().parseFile(fileName, newInputStream); } else if (".ass".equalsIgnoreCase(ext)) { @@ -215,7 +245,7 @@ public class SubtitleLoader { } public interface Callback { - void onSuccess(TimedTextObject timedTextObject); + void onSuccess(SubtitleLoadSuccessResult SubtitleLoadSuccessResult); void onError(Exception exception); } diff --git a/app/src/main/java/com/github/tvbox/osc/subtitle/widget/SimpleSubtitleView.java b/app/src/main/java/com/github/tvbox/osc/subtitle/widget/SimpleSubtitleView.java index 0452233d..c3ef87e5 100644 --- a/app/src/main/java/com/github/tvbox/osc/subtitle/widget/SimpleSubtitleView.java +++ b/app/src/main/java/com/github/tvbox/osc/subtitle/widget/SimpleSubtitleView.java @@ -99,6 +99,14 @@ public class SimpleSubtitleView extends TextView mSubtitleEngine.setSubtitleDelay(mseconds); } + public void setPlaySubtitleCacheKey(String cacheKey) { + mSubtitleEngine.setPlaySubtitleCacheKey(cacheKey); + } + + public String getPlaySubtitleCacheKey() { + return mSubtitleEngine.getPlaySubtitleCacheKey(); + } + @Override public void reset() { mSubtitleEngine.reset(); diff --git a/app/src/main/java/com/github/tvbox/osc/ui/activity/PlayActivity.java b/app/src/main/java/com/github/tvbox/osc/ui/activity/PlayActivity.java index ca4efd3e..26723238 100644 --- a/app/src/main/java/com/github/tvbox/osc/ui/activity/PlayActivity.java +++ b/app/src/main/java/com/github/tvbox/osc/ui/activity/PlayActivity.java @@ -330,10 +330,17 @@ public class PlayActivity extends BaseActivity { // 绑定MediaPlayer mController.mSubtitleView.bindToMediaPlayer(mVideoView.getMediaPlayer()); mController.mSubtitleView.setVisibility(View.INVISIBLE); + mController.mSubtitleView.setPlaySubtitleCacheKey(subtitleCacheKey); if (playSubtitle != null && playSubtitle .length() > 0) { // 设置字幕 mController.mSubtitleView.setSubtitlePath(playSubtitle); mController.mSubtitleView.setVisibility(View.VISIBLE); + } else { + String subtitlePathCache = (String)CacheManager.getCache(MD5.string2MD5(subtitleCacheKey)); + if (subtitlePathCache != null && !subtitlePathCache.isEmpty()) { + mController.mSubtitleView.setSubtitlePath(subtitlePathCache); + mController.mSubtitleView.setVisibility(View.VISIBLE); + } } //加载字幕结束 } @@ -353,6 +360,7 @@ public class PlayActivity extends BaseActivity { boolean parse = info.optString("parse", "1").equals("1"); boolean jx = info.optString("jx", "0").equals("1"); playSubtitle = info.optString("subt", /*"https://dash.akamaized.net/akamai/test/caption_test/ElephantsDream/ElephantsDream_en.vtt"*/""); + subtitleCacheKey = info.optString("subtKey", null); String playUrl = info.optString("playUrl", ""); String flag = info.optString("flag"); String url = info.getString("url"); @@ -546,9 +554,8 @@ public class PlayActivity extends BaseActivity { stopParse(); if(mVideoView!=null) mVideoView.release(); + String subtitleCacheKey = mVodInfo.sourceKey + "-" + mVodInfo.id + "-" + mVodInfo.playFlag + "-" + mVodInfo.playIndex + "-subt"; String progressKey = mVodInfo.sourceKey + mVodInfo.id + mVodInfo.playFlag + mVodInfo.playIndex; - //存储播放进度 - Object bodyKey=CacheManager.getCache(MD5.string2MD5(progressKey)); //重新播放清除现有进度 if (reset) {CacheManager.delete(MD5.string2MD5(progressKey), 0);} if (Thunder.play(vs.url, new Thunder.ThunderCallback() { @@ -573,12 +580,11 @@ public class PlayActivity extends BaseActivity { mController.showParse(false); return; } - sourceViewModel.getPlay(sourceKey, mVodInfo.playFlag, progressKey, vs.url); - //执行重新播放后还原之前的进度 -// if (reset) CacheManager.save(MD5.string2MD5(progressKey),bodyKey); + sourceViewModel.getPlay(sourceKey, mVodInfo.playFlag, progressKey, vs.url, subtitleCacheKey); } private String playSubtitle; + private String subtitleCacheKey; private String progressKey; private String parseFlag; private String webUrl; diff --git a/app/src/main/java/com/github/tvbox/osc/ui/fragment/PlayFragment.java b/app/src/main/java/com/github/tvbox/osc/ui/fragment/PlayFragment.java index 10855411..30bd2fb1 100644 --- a/app/src/main/java/com/github/tvbox/osc/ui/fragment/PlayFragment.java +++ b/app/src/main/java/com/github/tvbox/osc/ui/fragment/PlayFragment.java @@ -328,10 +328,17 @@ public class PlayFragment extends BaseLazyFragment { // 绑定MediaPlayer mController.mSubtitleView.bindToMediaPlayer(mVideoView.getMediaPlayer()); mController.mSubtitleView.setVisibility(View.INVISIBLE); + mController.mSubtitleView.setPlaySubtitleCacheKey(subtitleCacheKey); if (playSubtitle != null && playSubtitle .length() > 0) { // 设置字幕 mController.mSubtitleView.setSubtitlePath(playSubtitle); mController.mSubtitleView.setVisibility(View.VISIBLE); + } else { + String subtitlePathCache = (String)CacheManager.getCache(MD5.string2MD5(subtitleCacheKey)); + if (subtitlePathCache != null && !subtitlePathCache.isEmpty()) { + mController.mSubtitleView.setSubtitlePath(subtitlePathCache); + mController.mSubtitleView.setVisibility(View.VISIBLE); + } } //加载字幕结束 } @@ -351,6 +358,7 @@ public class PlayFragment extends BaseLazyFragment { boolean parse = info.optString("parse", "1").equals("1"); boolean jx = info.optString("jx", "0").equals("1"); playSubtitle = info.optString("subt", /*"https://dash.akamaized.net/akamai/test/caption_test/ElephantsDream/ElephantsDream_en.vtt"*/""); + subtitleCacheKey = info.optString("subtKey", null); String playUrl = info.optString("playUrl", ""); String flag = info.optString("flag"); String url = info.getString("url"); @@ -558,9 +566,8 @@ public class PlayFragment extends BaseLazyFragment { stopParse(); if(mVideoView!=null) mVideoView.release(); + String subtitleCacheKey = mVodInfo.sourceKey + "-" + mVodInfo.id + "-" + mVodInfo.playFlag + "-" + mVodInfo.playIndex + "-subt"; String progressKey = mVodInfo.sourceKey + mVodInfo.id + mVodInfo.playFlag + mVodInfo.playIndex; - //存储播放进度 - Object bodyKey=CacheManager.getCache(MD5.string2MD5(progressKey)); //重新播放清除现有进度 if (reset) CacheManager.delete(MD5.string2MD5(progressKey), 0); if (Thunder.play(vs.url, new Thunder.ThunderCallback() { @@ -585,12 +592,11 @@ public class PlayFragment extends BaseLazyFragment { mController.showParse(false); return; } - sourceViewModel.getPlay(sourceKey, mVodInfo.playFlag, progressKey, vs.url); - //执行重新播放后还原之前的进度 -// if (reset) CacheManager.save(MD5.string2MD5(progressKey),bodyKey); + sourceViewModel.getPlay(sourceKey, mVodInfo.playFlag, progressKey, vs.url, subtitleCacheKey); } private String playSubtitle; + private String subtitleCacheKey; private String progressKey; private String parseFlag; private String webUrl; diff --git a/app/src/main/java/com/github/tvbox/osc/viewmodel/SourceViewModel.java b/app/src/main/java/com/github/tvbox/osc/viewmodel/SourceViewModel.java index 907c910b..5eb14faa 100644 --- a/app/src/main/java/com/github/tvbox/osc/viewmodel/SourceViewModel.java +++ b/app/src/main/java/com/github/tvbox/osc/viewmodel/SourceViewModel.java @@ -630,7 +630,7 @@ public class SourceViewModel extends ViewModel { } } // playerContent - public void getPlay(String sourceKey, String playFlag, String progressKey, String url) { + public void getPlay(String sourceKey, String playFlag, String progressKey, String url, String subtitleKey) { SourceBean sourceBean = ApiConfig.get().getSource(sourceKey); int type = sourceBean.getType(); if (type == 3) { @@ -643,6 +643,7 @@ public class SourceViewModel extends ViewModel { JSONObject result = new JSONObject(json); result.put("key", url); result.put("proKey", progressKey); + result.put("subtKey", subtitleKey); if (!result.has("flag")) result.put("flag", playFlag); playResult.postValue(result);