diff --git a/.agents/workflows/compiler-apk.md b/.agents/workflows/compiler-apk.md
index 7009147..0fc3c42 100644
--- a/.agents/workflows/compiler-apk.md
+++ b/.agents/workflows/compiler-apk.md
@@ -12,13 +12,14 @@ description: 自動編譯 Android APK、輸出至專屬目錄並安裝至實體
```bash
// turbo
# 終止殘留的 Gradle/Java 程序,避免多個 Daemon 互搶資源
-pkill -9 -f "gradle" 2>/dev/null; pkill -9 -f "GradleDaemon" 2>/dev/null; sleep 1
+pkill -9 -f "gradle" 2>/dev/null; pkill -9 -f "GradleDaemon" 2>/dev/null; sleep 3
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
cd /home/mama/projects/TaiwanStar_general_size_s_Chengwai
# 使用 clean 確保完整重新編譯,避免舊快取遮蔽 import 缺失等錯誤
-./gradlew clean :app:assembleS_12_XY_U_Standard_Debug
+# --no-daemon:強制使用單次 JVM 進程,避免 Daemon 初始化期間被殘留訊號中斷
+./gradlew clean :app:assembleS_12_XY_U_Standard_Debug --no-daemon
```
## 步驟二:導出 APK 到 outputs 資料夾
diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100644
index 0000000..d58d49b
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/caches/deviceStreaming.xml b/.idea/caches/deviceStreaming.xml
index 9a395db..0354225 100644
--- a/.idea/caches/deviceStreaming.xml
+++ b/.idea/caches/deviceStreaming.xml
@@ -1235,6 +1235,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
@@ -1537,6 +1549,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..1042843
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
index c08a2df..a94cba3 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,6 @@
+
+
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..5bd6771
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..9661ac7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/unibuy/smartdevice/HomeActivity.java b/app/src/main/java/com/unibuy/smartdevice/HomeActivity.java
index 54cd485..4bbad9d 100644
--- a/app/src/main/java/com/unibuy/smartdevice/HomeActivity.java
+++ b/app/src/main/java/com/unibuy/smartdevice/HomeActivity.java
@@ -2,11 +2,14 @@ package com.unibuy.smartdevice;
import static android.view.View.VISIBLE;
+import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.graphics.Color;
+import android.os.Build;
import android.os.Bundle;
import android.os.Looper;
import android.view.View;
@@ -76,6 +79,25 @@ public class HomeActivity extends AppCompatActivityAbstract {
private ActivityHomeBinding binding;
private static VideoEoxPlayer videoEoxPlayer;
+ // ✅ 廣告更新監聽器:MQTT 廣告同步完成後自動刷新待機廣告播放器
+ private final BroadcastReceiver adUpdateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ getLogs().info("🔔 [HomeActivity] 收到 ACTION_AD_UPDATED 廣播,準備刷新待機頁廣告播放器...");
+ // 🔹 清除 Glide 記憶體快取,避免拿到舊的圖片 Bitmap
+ Glide.get(context).clearMemory();
+ // 使用 postDelayed 確保 DB 寫入完成後再刷新
+ new android.os.Handler(Looper.getMainLooper()).postDelayed(() -> {
+ if (!isFinishing() && !isDestroyed()) {
+ startOnSwitchFragment();
+ getLogs().info("✅ [HomeActivity] 待機頁廣告播放器已重建");
+ } else {
+ getLogs().info("⚠️ [HomeActivity] Activity 已銷毀,取消刷新廣告播放器");
+ }
+ }, 500);
+ }
+ };
+
private long lastReportTime = 0;
private static ReportShotTimeByScheduler reportOnScheduler;
@@ -227,6 +249,14 @@ public class HomeActivity extends AppCompatActivityAbstract {
reportOnScheduler.start(1L, 10L, TimeUnit.SECONDS);
startOnSwitchFragment();
+
+ // ✅ 註冊廣告更新監聽
+ IntentFilter adFilter = new IntentFilter("com.unibuy.smartdevice.ACTION_AD_UPDATED");
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ registerReceiver(adUpdateReceiver, adFilter, Context.RECEIVER_NOT_EXPORTED);
+ } else {
+ registerReceiver(adUpdateReceiver, adFilter);
+ }
}
public void startOnSwitchFragment() {
@@ -236,6 +266,7 @@ public class HomeActivity extends AppCompatActivityAbstract {
videoEoxPlayer = new VideoEoxPlayer(getCtx());
getHandlerMain().getHandler().postDelayed(() -> {
+ if (isFinishing() || isDestroyed()) return;
try {
// videoEoxPlayer.playMedia(binding.videoView, binding.imageAd); // ✅ 同時支援影片與圖片輪播
videoEoxPlayer.playMediaByFlag("3", binding.videoView, binding.imageAd); // ✅ 同時支援影片與圖片輪播
@@ -579,5 +610,11 @@ public class HomeActivity extends AppCompatActivityAbstract {
protected void onDestroy() {
super.onDestroy();
stopOnSwitchFragment();
+ // ✅ 取消註冊廣告更新監聽
+ try {
+ unregisterReceiver(adUpdateReceiver);
+ } catch (Exception e) {
+ getLogs().info("unregisterReceiver adUpdateReceiver: " + e.getMessage());
+ }
}
}
diff --git a/app/src/main/java/com/unibuy/smartdevice/MyApp.java b/app/src/main/java/com/unibuy/smartdevice/MyApp.java
index 9861d71..6009a86 100644
--- a/app/src/main/java/com/unibuy/smartdevice/MyApp.java
+++ b/app/src/main/java/com/unibuy/smartdevice/MyApp.java
@@ -417,17 +417,16 @@ public class MyApp extends Application {
serviceIntent.putExtra("machineId", machineId);
serviceIntent.putExtra("apiToken", apiKey);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- try {
+ try {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent);
- } catch (Exception e) {
- // Android 12+ 在 AlarmManager 喚醒時不允許直接啟動 ForegroundService
- // 改用普通 startService 讓 Service 先啟動,等 Activity 進前台後再提升
- logs.info("ForegroundService start blocked (background state), falling back to startService");
+ } else {
startService(serviceIntent);
}
- } else {
- startService(serviceIntent);
+ } catch (Exception e) {
+ // Android 12+ 在背景狀態下(如安裝後自動重啟)不允許啟動前台/背景服務
+ // 這裡只記錄日誌,不再嘗試 fallback,等使用者開啟 App 進前台後再由 Activity 觸發
+ logs.info("MQTT Service start blocked (app in background): " + e.getMessage());
}
}
}
diff --git a/app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java b/app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java
index c064e3b..80310bd 100644
--- a/app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java
+++ b/app/src/main/java/com/unibuy/smartdevice/external/StarCloudAPI.java
@@ -20,10 +20,14 @@ import com.unibuy.smartdevice.structure.ReportSlotListStructure;
import com.unibuy.smartdevice.structure.SlotStructure;
import com.unibuy.smartdevice.devices.SlotField;
import com.unibuy.smartdevice.structure.SettingStructure;
+import com.unibuy.smartdevice.ui.SettingActivity;
+import com.unibuy.smartdevice.ui.tools.VideoEoxPlayer;
+import com.unibuy.smartdevice.ui.tools.VideoImageMixedPlayer;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Collections;
import java.util.List;
+import java.util.ArrayList;
/**
* 專門對接 Star-Cloud 後台的 API 類別
@@ -671,11 +675,140 @@ public class StarCloudAPI {
}
Log.i(TAG, "Updated product references in all slots.");
- // 🔹 發送廣播通知所有監聽者(如銷售平台)商品與貨道資料已更新
+ // 🔹 發送廣播通知所有監聯者(如銷售平台)商品與貨道資料已更新
android.content.Intent intent = new android.content.Intent("com.unibuy.smartdevice.ACTION_SLOT_UPDATED");
+ intent.setPackage(handlerMain.getContext().getPackageName());
handlerMain.getContext().sendBroadcast(intent);
}
+ /**
+ * B005: 下載廣告清單並同步媒體檔案
+ */
+ public void downloadAdvertisement() {
+ downloadAdvertisement(null);
+ }
+
+ public void downloadAdvertisement(final Runnable onComplete) {
+ final String url = BASE_URL + "machine/ad/B005";
+ Log.i(TAG, "Starting B005 request to: " + url);
+
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ int responseCode = -1;
+ String receive = "";
+ try {
+ java.net.URL reqUrl = new java.net.URL(url);
+ java.net.HttpURLConnection conn = (java.net.HttpURLConnection) reqUrl.openConnection();
+ conn.setRequestMethod("GET");
+ conn.setRequestProperty("Accept", "application/json");
+
+ String machineToken = MyApp.getInstance().getMachine().getApiKey();
+ if (machineToken != null && !machineToken.isEmpty()) {
+ conn.setRequestProperty("Authorization", "Bearer " + machineToken);
+ }
+
+ conn.setConnectTimeout(15000);
+ conn.setReadTimeout(30000); // 廣告清單可能較大
+
+ responseCode = conn.getResponseCode();
+ java.io.InputStream is = (responseCode >= 200 && responseCode < 300)
+ ? conn.getInputStream() : conn.getErrorStream();
+ if (is != null) {
+ java.io.BufferedReader br = new java.io.BufferedReader(new java.io.InputStreamReader(is, "UTF-8"));
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = br.readLine()) != null) sb.append(line);
+ receive = sb.toString();
+ }
+ conn.disconnect();
+ } catch (Exception e) {
+ Log.e(TAG, "B005 Network Error", e);
+ handlerMain.start("StarCloudAPI", 500, "廣告同步連線失敗");
+ if (onComplete != null) onComplete.run();
+ return;
+ }
+
+ final int finalCode = responseCode;
+ final String finalReceive = receive;
+ if (finalCode >= 200 && finalCode < 300) {
+ try {
+ JSONObject res = new JSONObject(finalReceive);
+ if (res.optBoolean("success", false)) {
+ JSONArray ads = res.getJSONArray("data");
+ saveAds(ads);
+ handlerMain.start("StarCloudAPI", 200, "廣告同步完成");
+ } else {
+ handlerMain.start("StarCloudAPI", 401, "廣告同步失敗: " + res.optString("message"));
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "B005 Parse Error", e);
+ handlerMain.start("StarCloudAPI", 500, "廣告解析錯誤");
+ }
+ } else {
+ handlerMain.start("StarCloudAPI", finalCode, "廣告同步網路錯誤: " + finalCode);
+ }
+ if (onComplete != null) onComplete.run();
+ }
+ }).start();
+ }
+
+ private void saveAds(JSONArray ads) throws JSONException {
+ SettingsDao settingsDao = new SettingsDao(handlerMain.getContext());
+ // 清除舊廣告設定
+ settingsDao.deleteWhere(VideoEoxPlayer.class.getSimpleName());
+
+ // 🔹 清除 MediaCacheHelper 本地快取,避免 URL 相同但內容變更時播放器讀到過期快取
+ VideoImageMixedPlayer.clearAdsCache(handlerMain.getContext());
+ Log.i(TAG, "Cleared local ads media cache before sync.");
+
+ MyApp.getInstance().getFontendAdUrlString().clear();
+
+ for (int i = 0; i < ads.length(); i++) {
+ JSONObject ad = ads.getJSONObject(i);
+ String url = ad.optString("t070v04", "");
+ String flag = String.valueOf(ad.optInt("t070v03", 0));
+ String duration = String.valueOf(ad.optInt("t070v02", 10));
+
+ if (url.isEmpty()) continue;
+
+ MyApp.getInstance().getFontendAdUrlString().add(url);
+
+ // 下載檔案
+ try {
+ Log.i(TAG, "Downloading Ad (" + (i+1) + "/" + ads.length() + "): " + url);
+ HttpConnect httpConnect = new HttpConnect();
+ httpConnect.download(url, true);
+ } catch (Exception e) {
+ Log.e(TAG, "Download Ad Failed: " + url, e);
+ }
+
+ // 儲存至資料庫 [URL, Flag, Duration]
+ settingsDao.insertOne(new SettingStructure(
+ VideoEoxPlayer.class.getSimpleName(),
+ new String[]{url, flag, duration}
+ ));
+
+ // 更新 UI 進度
+ final int percent = (i + 1) * 100 / ads.length();
+ new android.os.Handler(android.os.Looper.getMainLooper()).post(new Runnable() {
+ @Override
+ public void run() {
+ SettingActivity.updateProgressUI(percent, "廣告下載中...");
+ }
+ });
+ }
+ settingsDao.close();
+ Log.i(TAG, "Synced " + ads.length() + " ads from star-cloud.");
+
+ // ✅ 發送廣播通知前台 Activity(HomeActivity / FontendActivity)刷新廣告播放器
+ // 🔹 必須設定 setPackage 才能被 RECEIVER_NOT_EXPORTED 的接收器收到(Android 13+)
+ android.content.Intent adIntent = new android.content.Intent("com.unibuy.smartdevice.ACTION_AD_UPDATED");
+ adIntent.setPackage(handlerMain.getContext().getPackageName());
+ handlerMain.getContext().sendBroadcast(adIntent);
+ Log.i(TAG, "ACTION_AD_UPDATED broadcast sent.");
+ }
+
/**
* 取得設備真實 IP 地址
*/
diff --git a/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java b/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java
index 1edb2d7..a4db1cc 100644
--- a/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java
+++ b/app/src/main/java/com/unibuy/smartdevice/service/MqttService.java
@@ -240,6 +240,40 @@ public class MqttService extends Service {
logs.info("Failed to parse or start change command: " + e.getMessage());
mqttManager.publishCommandAck(cmdId, "failed", "Error: " + e.getMessage());
}
+ } else if ("update_ads".equals(cmd)) {
+ logs.info("Executing remote advertisement sync command...");
+
+ // 📢 在主執行緒顯示 Toast 提示使用者
+ new android.os.Handler(android.os.Looper.getMainLooper()).post(() ->
+ android.widget.Toast.makeText(getApplicationContext(),
+ "📢 廣告更新中...", android.widget.Toast.LENGTH_SHORT).show());
+
+ com.unibuy.smartdevice.tools.HandlerMain adHandler = new com.unibuy.smartdevice.tools.HandlerMain(
+ getApplicationContext(), logs) {
+ @Override
+ protected void execute(Context context, int code, String message) {
+ logs.info("Ad sync event: [" + code + "] " + message);
+ if (code == 200 && "廣告同步完成".equals(message)) {
+ mqttManager.publishCommandAck(cmdId, "success",
+ "Advertisement sync completed successfully");
+
+ // ✅ Toast 提示完成
+ new android.os.Handler(android.os.Looper.getMainLooper()).post(() ->
+ android.widget.Toast.makeText(context,
+ "✅ 廣告更新完成", android.widget.Toast.LENGTH_SHORT).show());
+ // 注意:廣播 ACTION_AD_UPDATED 已在 StarCloudAPI.saveAds() 中統一發送
+ } else if (code >= 400) {
+ mqttManager.publishCommandAck(cmdId, "failed",
+ "Advertisement sync failed: " + message);
+
+ new android.os.Handler(android.os.Looper.getMainLooper()).post(() ->
+ android.widget.Toast.makeText(context,
+ "❌ 廣告更新失敗: " + message, android.widget.Toast.LENGTH_LONG).show());
+ }
+ }
+ };
+ // 使用新架構 StarCloudAPI 執行同步
+ new com.unibuy.smartdevice.external.StarCloudAPI(adHandler, adHandler).downloadAdvertisement();
} else if ("update_inventory".equals(cmd)) {
logs.info("Executing update_inventory command...");
try {
diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java b/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java
index 27eacd8..4b8427c 100644
--- a/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java
+++ b/app/src/main/java/com/unibuy/smartdevice/ui/FontendActivity.java
@@ -141,6 +141,22 @@ public class FontendActivity extends AppCompatActivityAbstract {
}
};
+ // ✅ 廣告更新監聽器:MQTT 廣告同步完成後自動刷新販賣頁廣告播放器
+ private final BroadcastReceiver adUpdateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ getLogs().info("🔔 [FontendActivity] 收到 ACTION_AD_UPDATED 廣播,準備刷新販賣頁廣告播放器...");
+ // 🔹 清除 Glide 記憶體快取,避免拿到舊的圖片 Bitmap
+ Glide.get(context).clearMemory();
+ new Handler(Looper.getMainLooper()).postDelayed(() -> {
+ if (!isFinishing() && !isDestroyed()) {
+ startOnSwitchFragment();
+ getLogs().info("✅ [FontendActivity] 販賣頁廣告播放器已重建");
+ }
+ }, 500);
+ }
+ };
+
private static VideoEoxPlayer videoEoxPlayer;
@Override
@@ -423,7 +439,9 @@ public class FontendActivity extends AppCompatActivityAbstract {
// 確保前一個 ExoPlayer 完整釋放後再初始化新的
new Handler(Looper.getMainLooper()).postDelayed(() -> {
- initVideoPlayer();
+ if (!isFinishing() && !isDestroyed()) {
+ initVideoPlayer();
+ }
}, 100);
} catch (Exception e) {
getLogs().warning(e);
@@ -434,6 +452,7 @@ public class FontendActivity extends AppCompatActivityAbstract {
videoEoxPlayer = new VideoEoxPlayer(getCtx()); // ✅ 用新版建構子
getHandlerMain().getHandler().postDelayed(() -> {
+ if (isFinishing() || isDestroyed()) return;
if (videoEoxPlayer != null) {
// videoEoxPlayer.playMedia(binding.videoView, binding.imageAd); // ✅ 傳入 view
// 開始播放
@@ -1100,6 +1119,14 @@ public class FontendActivity extends AppCompatActivityAbstract {
registerReceiver(slotUpdateReceiver, filter);
}
+ // ✅ 註冊廣告更新監聽
+ IntentFilter adFilter = new IntentFilter("com.unibuy.smartdevice.ACTION_AD_UPDATED");
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ registerReceiver(adUpdateReceiver, adFilter, Context.RECEIVER_NOT_EXPORTED);
+ } else {
+ registerReceiver(adUpdateReceiver, adFilter);
+ }
+
// 🔹 從設定頁面或其他頁面回到銷售平台時,強制刷新商品列表,確保新圖片能顯示
// 先清除 Glide 記憶體快取,避免拿到舊的 Bitmap
Glide.get(this).clearMemory();
@@ -1120,6 +1147,12 @@ public class FontendActivity extends AppCompatActivityAbstract {
} catch (Exception e) {
getLogs().info("unregisterReceiver slotUpdateReceiver: " + e.getMessage());
}
+ // ✅ 取消註冊廣告更新監聽
+ try {
+ unregisterReceiver(adUpdateReceiver);
+ } catch (Exception e) {
+ getLogs().info("unregisterReceiver adUpdateReceiver: " + e.getMessage());
+ }
}
@Override
diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/RestartHostActivity.java b/app/src/main/java/com/unibuy/smartdevice/ui/RestartHostActivity.java
index dab8306..d7c6d8d 100644
--- a/app/src/main/java/com/unibuy/smartdevice/ui/RestartHostActivity.java
+++ b/app/src/main/java/com/unibuy/smartdevice/ui/RestartHostActivity.java
@@ -58,6 +58,7 @@ public class RestartHostActivity extends AppCompatActivityAbstract {
private RecyclerSettingListAdpter recyclerSettingListAdpter;
private HttpAPI httpAPI;
+ private com.unibuy.smartdevice.external.StarCloudAPI starCloudAPI;
@Override
protected Context setCtx() {
@@ -88,11 +89,7 @@ public class RestartHostActivity extends AppCompatActivityAbstract {
new ImageReload(this).start();
break;
case AD_RELOAD:
- try {
- httpAPI.downloadAdvertisement();
- } catch (LogsParseException | LogsSettingEmptyException e) {
- getLogs().warning(e);
- }
+ starCloudAPI.downloadAdvertisement();
break;
}
}
@@ -107,6 +104,7 @@ public class RestartHostActivity extends AppCompatActivityAbstract {
setContentView(binding.getRoot());
httpAPI = new HttpAPI(getHandlerMain());
+ starCloudAPI = new com.unibuy.smartdevice.external.StarCloudAPI(getHandlerMain(), getHandlerMain());
recyclerSettingListAdpter = new RecyclerSettingListAdpter(statusList);
binding.recyclerSettingList.setAdapter(recyclerSettingListAdpter);
diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/SettingActivity.java b/app/src/main/java/com/unibuy/smartdevice/ui/SettingActivity.java
index 2b9d702..563b37d 100644
--- a/app/src/main/java/com/unibuy/smartdevice/ui/SettingActivity.java
+++ b/app/src/main/java/com/unibuy/smartdevice/ui/SettingActivity.java
@@ -114,6 +114,9 @@ public class SettingActivity extends AppCompatActivityAbstract {
if ("綁定成功".equals(message)) {
binding.progressLayout.setVisibility(View.VISIBLE); // 顯示進度條,因為要同步商品
starCloudAPI.downloadProductInfo();
+ } else if ("商品下載成功".equals(message)) {
+ binding.progressLayout.setVisibility(View.VISIBLE); // 顯示進度條,同步廣告
+ starCloudAPI.downloadAdvertisement();
}
checkSetting();
@@ -207,6 +210,7 @@ public class SettingActivity extends AppCompatActivityAbstract {
}
public static void updateProgressUI(int percent, String message) {
+ if (instance == null) return;
SettingActivity activity = instance.get();
if (activity == null)
return;
diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/tools/VideoEoxPlayer.java b/app/src/main/java/com/unibuy/smartdevice/ui/tools/VideoEoxPlayer.java
index 56fe1a3..38662e1 100644
--- a/app/src/main/java/com/unibuy/smartdevice/ui/tools/VideoEoxPlayer.java
+++ b/app/src/main/java/com/unibuy/smartdevice/ui/tools/VideoEoxPlayer.java
@@ -75,18 +75,26 @@ public class VideoEoxPlayer {
SettingsDao settingsDao = new SettingsDao(context);
List dataList = settingsDao.getWhere(VideoEoxPlayer.class.getSimpleName());
- List targetUrls = new ArrayList<>();
+ List targetAds = new ArrayList<>();
for (SettingStructure data : dataList) {
String url = data.getData(0);
String flag = data.getData(1);
+ int duration = 10;
+ try {
+ if (data.getDataCount() > 2) {
+ duration = Integer.parseInt(data.getData(2));
+ }
+ } catch (Exception ignored) {}
+
if (flag != null && flag.equals(targetFlag)) {
- targetUrls.add(url);
+ targetAds.add(new VideoImageMixedPlayer.AdItem(url, duration));
}
}
settingsDao.close();
+ logs.info("Ads [Flag: " + targetFlag + "] 過濾後數量: " + targetAds.size());
- mixedPlayer = new VideoImageMixedPlayer(context, targetUrls);
+ mixedPlayer = new VideoImageMixedPlayer(context, targetAds);
mixedPlayer.start(videoView, imageView);
}
diff --git a/app/src/main/java/com/unibuy/smartdevice/ui/tools/VideoImageMixedPlayer.java b/app/src/main/java/com/unibuy/smartdevice/ui/tools/VideoImageMixedPlayer.java
index d68607b..58b3396 100644
--- a/app/src/main/java/com/unibuy/smartdevice/ui/tools/VideoImageMixedPlayer.java
+++ b/app/src/main/java/com/unibuy/smartdevice/ui/tools/VideoImageMixedPlayer.java
@@ -29,9 +29,19 @@ import java.util.concurrent.Executors;
public class VideoImageMixedPlayer {
+ public static class AdItem {
+ public String url;
+ public int duration; // 秒
+
+ public AdItem(String url, int duration) {
+ this.url = url;
+ this.duration = duration;
+ }
+ }
+
private final Logs logs;
private final Context context;
- private final List mediaUrls;
+ private final List adItems;
private ExoPlayer exoPlayer;
private PlayerView playerView;
@@ -39,9 +49,9 @@ public class VideoImageMixedPlayer {
private Handler handler;
private int currentIndex = 0;
- public VideoImageMixedPlayer(Context context, List mediaUrls) {
+ public VideoImageMixedPlayer(Context context, List adItems) {
this.context = context;
- this.mediaUrls = mediaUrls;
+ this.adItems = adItems;
this.logs = new Logs(getClass());
this.handler = new Handler(Looper.getMainLooper());
}
@@ -54,16 +64,28 @@ public class VideoImageMixedPlayer {
}
private void playNext() {
- if (mediaUrls == null || mediaUrls.isEmpty()) {
- logs.info("Ads沒有任何媒體可以播放!");
+ if (adItems == null || adItems.isEmpty()) {
+ logs.info("Ads沒有任何媒體可以播放!清空視圖 (flag-based)...");
+ if (playerView != null) {
+ playerView.setVisibility(android.view.View.GONE);
+ playerView.setPlayer(null);
+ }
+ if (imageView != null) {
+ imageView.setVisibility(android.view.View.GONE);
+ imageView.setImageDrawable(null); // 清除舊圖片
+ }
+ releaseExoPlayer();
return;
}
- if (currentIndex >= mediaUrls.size()) {
+ logs.info("Ads 目前播放索引: " + currentIndex + " / 總數: " + adItems.size());
+
+ if (currentIndex >= adItems.size()) {
currentIndex = 0;
}
- String url = mediaUrls.get(currentIndex);
+ AdItem item = adItems.get(currentIndex);
+ String url = item.url;
// 先檢查快取
String cachedPath = MediaCacheHelper.getCachedPath(context, url);
@@ -132,10 +154,23 @@ public class VideoImageMixedPlayer {
exoPlayer.prepare();
exoPlayer.setPlayWhenReady(true);
+ // ✅ 使用設定的秒數控制影片播放時間,到時間就強制切下一個
+ int duration = adItems.get(currentIndex).duration;
+ if (duration <= 0) duration = 10;
+ final Runnable forceNextRunnable = () -> {
+ logs.info("影片播放到設定秒數 (" + adItems.get(currentIndex).duration + "s),強制切下一個廣告");
+ releaseExoPlayer();
+ currentIndex = (currentIndex + 1) % adItems.size();
+ playNext();
+ };
+ handler.postDelayed(forceNextRunnable, duration * 1000L);
+
exoPlayer.addListener(new Player.Listener() {
@Override
public void onPlaybackStateChanged(int state) {
if (state == Player.STATE_ENDED) {
+ // 影片自然播放結束,取消強制計時器,直接切下一個
+ handler.removeCallbacks(forceNextRunnable);
nextSchedule(1000);
}
}
@@ -143,6 +178,7 @@ public class VideoImageMixedPlayer {
@Override
public void onPlayerError(PlaybackException error) {
logs.info("影片播放錯誤: " + error.getMessage());
+ handler.removeCallbacks(forceNextRunnable);
nextSchedule(1000);
}
});
@@ -155,17 +191,29 @@ public class VideoImageMixedPlayer {
Object source = path.startsWith("/") ? new File(path) : path;
+ // ✅ 增加生命週期檢查,避免 Glide 在 Activity 銷毀後嘗試載入導致崩潰
+ if (context instanceof android.app.Activity) {
+ android.app.Activity activity = (android.app.Activity) context;
+ if (activity.isFinishing() || (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1 && activity.isDestroyed())) {
+ logs.info("⚠️ Activity 已銷毀,跳過圖片載入: " + path);
+ return;
+ }
+ }
+
Glide.with(context)
.load(source)
.fitCenter()
.into(imageView);
- nextSchedule(10000); // 圖片輪播 10 秒
+ // 使用動態秒數,若為 0 則預設 10 秒
+ int duration = adItems.get(currentIndex).duration;
+ if (duration <= 0) duration = 10;
+ nextSchedule(duration * 1000L);
}
private void nextSchedule(long delayMs) {
handler.postDelayed(() -> {
- currentIndex = (currentIndex + 1) % mediaUrls.size();
+ currentIndex = (currentIndex + 1) % adItems.size();
playNext();
}, delayMs);
}
@@ -180,7 +228,16 @@ public class VideoImageMixedPlayer {
}
public void stop() {
+ logs.info("Ads 播放器停止,清空視圖");
releaseExoPlayer();
+ if (playerView != null) {
+ playerView.setVisibility(android.view.View.GONE);
+ playerView.setPlayer(null);
+ }
+ if (imageView != null) {
+ imageView.setVisibility(android.view.View.GONE);
+ imageView.setImageDrawable(null);
+ }
if (handler != null) {
handler.removeCallbacksAndMessages(null);
}
diff --git a/build_log.txt b/build_log.txt
new file mode 100644
index 0000000..46c550a
--- /dev/null
+++ b/build_log.txt
@@ -0,0 +1,57 @@
+Starting a Gradle Daemon, 1 busy and 3 incompatible and 1 stopped Daemons could not be reused, use --status for details
+> Task :app:preBuild UP-TO-DATE
+> Task :app:preS_12_XY_U_Standard_DebugBuild UP-TO-DATE
+> Task :app:mergeS_12_XY_U_Standard_DebugNativeDebugMetadata NO-SOURCE
+> Task :app:generateS_12_XY_U_Standard_DebugResValues
+> Task :app:dataBindingMergeDependencyArtifactsS_12_XY_U_Standard_Debug
+> Task :app:generateS_12_XY_U_Standard_DebugResources
+> Task :app:packageS_12_XY_U_Standard_DebugResources
+> Task :app:javaPreCompileS_12_XY_U_Standard_Debug
+> Task :app:mergeS_12_XY_U_Standard_DebugResources
+> Task :app:checkS_12_XY_U_Standard_DebugAarMetadata
+> Task :app:mapS_12_XY_U_Standard_DebugSourceSetPaths
+> Task :app:createS_12_XY_U_Standard_DebugCompatibleScreenManifests
+> Task :app:extractDeepLinksS_12_XY_U_Standard_Debug
+> Task :app:parseS_12_XY_U_Standard_DebugLocalResources
+> Task :app:processS_12_XY_U_Standard_DebugMainManifest
+> Task :app:dataBindingGenBaseClassesS_12_XY_U_Standard_Debug
+> Task :app:processS_12_XY_U_Standard_DebugManifest
+> Task :app:mergeS_12_XY_U_Standard_DebugShaders
+> Task :app:compileS_12_XY_U_Standard_DebugShaders NO-SOURCE
+> Task :app:generateS_12_XY_U_Standard_DebugAssets UP-TO-DATE
+> Task :app:mergeS_12_XY_U_Standard_DebugAssets
+> Task :app:compressS_12_XY_U_Standard_DebugAssets
+> Task :app:desugarS_12_XY_U_Standard_DebugFileDependencies
+> Task :app:mergeS_12_XY_U_Standard_DebugJniLibFolders
+> Task :app:checkS_12_XY_U_Standard_DebugDuplicateClasses
+> Task :app:mergeS_12_XY_U_Standard_DebugNativeLibs
+> Task :app:processS_12_XY_U_Standard_DebugManifestForPackage
+> Task :app:mergeLibDexS_12_XY_U_Standard_Debug
+
+> Task :app:stripS_12_XY_U_Standard_DebugDebugSymbols
+Unable to strip the following libraries, packaging them as they are: libserial_port.so.
+
+> Task :app:validateSigningS_12_XY_U_Standard_Debug
+> Task :app:writeS_12_XY_U_Standard_DebugAppMetadata
+> Task :app:writeS_12_XY_U_Standard_DebugSigningConfigVersions
+> Task :app:mergeExtDexS_12_XY_U_Standard_Debug
+warn: removing resource com.unibuy.smartdevice:string/reload_product without required default value.
+warn: removing resource com.unibuy.smartdevice:string/reload_product_warning without required default value.
+
+> Task :app:processS_12_XY_U_Standard_DebugResources
+
+> Task :app:compileS_12_XY_U_Standard_DebugJavaWithJavac
+Note: Some input files use or override a deprecated API.
+Note: Recompile with -Xlint:deprecation for details.
+
+> Task :app:dexBuilderS_12_XY_U_Standard_Debug
+> Task :app:processS_12_XY_U_Standard_DebugJavaRes
+> Task :app:mergeProjectDexS_12_XY_U_Standard_Debug
+> Task :app:mergeS_12_XY_U_Standard_DebugJavaResource
+> Task :app:packageS_12_XY_U_Standard_Debug
+> Task :app:createS_12_XY_U_Standard_DebugApkListingFileRedirect
+> Task :app:assembleS_12_XY_U_Standard_Debug
+[Incubating] Problems report is available at: file:///home/mama/projects/TaiwanStar_general_size_s_Chengwai/build/reports/problems/problems-report.html
+
+BUILD SUCCESSFUL in 56s
+36 actionable tasks: 36 executed