diff --git a/app/src/main/java/com/github/tvbox/osc/ui/activity/LivePlayActivity.java b/app/src/main/java/com/github/tvbox/osc/ui/activity/LivePlayActivity.java index 263efab8..c45c4f7d 100644 --- a/app/src/main/java/com/github/tvbox/osc/ui/activity/LivePlayActivity.java +++ b/app/src/main/java/com/github/tvbox/osc/ui/activity/LivePlayActivity.java @@ -1,5 +1,6 @@ package com.github.tvbox.osc.ui.activity; +import android.Manifest; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.IntEvaluator; @@ -1457,25 +1458,33 @@ public class LivePlayActivity extends BaseActivity { @Override public void playStateChanged(int playState) { + mHandler.removeCallbacks(mConnectTimeoutChangeSourceRun); switch (playState) { case VideoView.STATE_IDLE: + // 空闲状态:播放器处于空闲,尚未开始播放。一般不需要自动换源。 case VideoView.STATE_PAUSED: + // 暂停状态:播放被暂停,通常是用户操作,不触发自动换源 break; case VideoView.STATE_PREPARED: + // 准备就绪:播放器已经加载好媒体数据,但尚未开始播放。 case VideoView.STATE_BUFFERED: case VideoView.STATE_PLAYING: + // 播放状态:当播放器缓冲完成或正在正常播放时,表明当前源是可用的, currentLiveChangeSourceTimes = 0; - mHandler.removeCallbacks(mConnectTimeoutChangeSourceRun); break; case VideoView.STATE_ERROR: case VideoView.STATE_PLAYBACK_COMPLETED: - mHandler.removeCallbacks(mConnectTimeoutChangeSourceRun); - mHandler.postDelayed(mConnectTimeoutChangeSourceRun, 3000); + // 错误或播放结束状态:播放器遇到错误或播放完毕时, + // 启动自动换源任务,等待3秒后尝试切换至备选源 + mHandler.postDelayed(mConnectTimeoutChangeSourceRun, 3500); break; case VideoView.STATE_PREPARING: case VideoView.STATE_BUFFERING: - mHandler.removeCallbacks(mConnectTimeoutChangeSourceRun); - mHandler.postDelayed(mConnectTimeoutChangeSourceRun, (Hawk.get(HawkConfig.LIVE_CONNECT_TIMEOUT, 1) + 1) * 5000); + // 正在准备或缓冲状态:表示当前源正在加载中 + mHandler.postDelayed(mConnectTimeoutChangeSourceRun, (Hawk.get(HawkConfig.LIVE_CONNECT_TIMEOUT, 1) + 1) * 5000L); + break; + default: + LOG.i("echo-Unexpected live_play state: " + playState); break; } } @@ -1798,6 +1807,10 @@ public class LivePlayActivity extends BaseActivity { break; case 5://多源切换 //TODO + if (mVideoView != null) { + mVideoView.release(); + mVideoView=null; + } if(position==Hawk.get(HawkConfig.LIVE_GROUP_INDEX, 0))break; JsonArray live_groups=Hawk.get(HawkConfig.LIVE_GROUP_LIST,new JsonArray()); JsonObject livesOBJ = live_groups.get(position).getAsJsonObject(); @@ -1816,10 +1829,6 @@ public class LivePlayActivity extends BaseActivity { liveSettingItemAdapter.selectItem(position, true, true); Hawk.put(HawkConfig.LIVE_GROUP_INDEX, position); ApiConfig.get().loadLiveApi(livesOBJ); - if (mVideoView != null) { - mVideoView.release(); - mVideoView=null; - } recreate(); return; } @@ -1875,6 +1884,11 @@ public class LivePlayActivity extends BaseActivity { LOG.i("echo-live-url:"+url); if(url.contains(".py")){ + if (!hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + Toast.makeText(App.getInstance(), "该源需要存储权限", Toast.LENGTH_SHORT).show(); + finish(); + return; + } String finalUrl = url; Runnable waitResponse = new Runnable() { @Override @@ -1892,7 +1906,7 @@ public class LivePlayActivity extends BaseActivity { }); String sortJson = null; try { - sortJson = future.get(6, TimeUnit.SECONDS); + sortJson = future.get(10, TimeUnit.SECONDS); } catch (TimeoutException e) { e.printStackTrace(); future.cancel(true); @@ -1928,8 +1942,13 @@ public class LivePlayActivity extends BaseActivity { ApiConfig.get().loadLives(livesArray); List list = ApiConfig.get().getChannelGroupList(); if (list.isEmpty()) { - Toast.makeText(App.getInstance(), "频道列表为空", Toast.LENGTH_SHORT).show(); - finish(); + mHandler.post(new Runnable() { + @Override + public void run() { + Toast.makeText(App.getInstance(), "频道列表为空", Toast.LENGTH_SHORT).show(); + finish(); + } + }); return; } liveChannelGroupList.clear(); @@ -2005,6 +2024,39 @@ public class LivePlayActivity extends BaseActivity { } } + private void autoSwitchNextLive(){ + Toast.makeText(App.getInstance(), "加载错误,自动切换下一个源", Toast.LENGTH_SHORT).show(); + JsonArray live_groups=Hawk.get(HawkConfig.LIVE_GROUP_LIST,new JsonArray()); + int position=Hawk.get(HawkConfig.LIVE_GROUP_INDEX,0)+1; + if(position>live_groups.size()-1){ + Hawk.put(HawkConfig.LIVE_GROUP_INDEX,0); + Toast.makeText(App.getInstance(), "当前无有效直播,自动退出到主页", Toast.LENGTH_SHORT).show(); + jumpActivity(HomeActivity.class); + } + + JsonObject livesOBJ = live_groups.get(position).getAsJsonObject(); + if(livesOBJ.has("type")){ + String type= livesOBJ.get("type").getAsString(); + if(!type.equals("0") && !type.equals("3")){ + Toast.makeText(App.getInstance(), "暂不支持该直播类型", Toast.LENGTH_SHORT).show(); + autoSwitchNextLive(); + } + }else { + if(!livesOBJ.has("channels")){ + Toast.makeText(App.getInstance(), "暂不支持该直播类型", Toast.LENGTH_SHORT).show(); + autoSwitchNextLive(); + } + } + liveSettingItemAdapter.selectItem(position, true, true); + Hawk.put(HawkConfig.LIVE_GROUP_INDEX, position); + ApiConfig.get().loadLiveApi(livesOBJ); + if (mVideoView != null) { + mVideoView.release(); + mVideoView=null; + } + recreate(); + } + private void initLiveState() { String lastChannelName = Hawk.get(HawkConfig.LIVE_CHANNEL, ""); diff --git a/app/src/python/java/com/undcover/freedom/pyramid/PythonLoader.java b/app/src/python/java/com/undcover/freedom/pyramid/PythonLoader.java index e9257769..2e5a37f0 100644 --- a/app/src/python/java/com/undcover/freedom/pyramid/PythonLoader.java +++ b/app/src/python/java/com/undcover/freedom/pyramid/PythonLoader.java @@ -25,6 +25,12 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import okhttp3.Call; import okhttp3.Response; @@ -120,26 +126,39 @@ public class PythonLoader { PyLog.d(key + " :缓存加载成功!"); return spiders.get(key); } + + // 使用ExecutorService来管理线程 + ExecutorService executor = Executors.newSingleThreadExecutor(); + Future future = null; try { PythonSpider sp = new PythonSpider(key, cache); - Thread initThread = new Thread(() -> { + + // 提交初始化任务 + future = executor.submit(() -> { try { sp.init(app, url); } catch (Exception e) { e.printStackTrace(); } }); - initThread.start(); - initThread.join(12_000); - if (initThread.isAlive()) { - PyLog.e("echo-init方法执行超时超时"); - initThread.interrupt(); - return new SpiderNull(); - } + + // 等待线程完成,最多10秒 + future.get(10, TimeUnit.SECONDS); + + // 任务成功,缓存并返回 spiders.put(key, sp); return sp; - } catch (Throwable th) { - PyLog.e(th.toString()); + } catch (TimeoutException e) { + PyLog.e("echo-init方法执行超时"); + // 超时了,不做中断,返回空的Spider + } catch (ExecutionException | InterruptedException e) { + PyLog.e("echo-init:ExecutionException|InterruptedException"); + } finally { + // 关闭线程池 + if (future != null && !future.isDone()) { + future.cancel(true); // 取消任务 + } + executor.shutdown(); // 关闭线程池 } return new SpiderNull(); }