From 3a85186046059fbb5df24aaf8c223637bc25ae2a Mon Sep 17 00:00:00 2001 From: jhengazuki Date: Sun, 14 Sep 2025 01:53:13 +0800 Subject: [PATCH] Support video wallpaper --- .../android/tv/ui/activity/HomeActivity.java | 1 - .../android/tv/ui/base/BaseActivity.java | 19 +--- .../android/tv/api/config/WallConfig.java | 55 +++++----- .../android/tv/ui/custom/CustomWallView.java | 100 ++++++++++++++++++ .../com/fongmi/android/tv/utils/ImgUtil.java | 8 +- .../android/tv/ui/activity/HomeActivity.java | 3 +- .../android/tv/ui/base/BaseActivity.java | 19 +--- 7 files changed, 147 insertions(+), 58 deletions(-) create mode 100644 app/src/main/java/com/fongmi/android/tv/ui/custom/CustomWallView.java diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/HomeActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/HomeActivity.java index 0b5e07284..2ab848d81 100644 --- a/app/src/leanback/java/com/fongmi/android/tv/ui/activity/HomeActivity.java +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/activity/HomeActivity.java @@ -297,7 +297,6 @@ public class HomeActivity extends BaseActivity implements CustomTitleView.Listen @Subscribe(threadMode = ThreadMode.MAIN) public void onRefreshEvent(RefreshEvent event) { - super.onRefreshEvent(event); switch (event.getType()) { case CONFIG: setLogo(); diff --git a/app/src/leanback/java/com/fongmi/android/tv/ui/base/BaseActivity.java b/app/src/leanback/java/com/fongmi/android/tv/ui/base/BaseActivity.java index 044ac07cb..688e8d156 100644 --- a/app/src/leanback/java/com/fongmi/android/tv/ui/base/BaseActivity.java +++ b/app/src/leanback/java/com/fongmi/android/tv/ui/base/BaseActivity.java @@ -1,7 +1,6 @@ package com.fongmi.android.tv.ui.base; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.widget.ImageView.ScaleType.CENTER_CROP; import android.app.Activity; import android.content.res.Configuration; @@ -10,7 +9,6 @@ import android.os.Build; import android.os.Bundle; import android.view.View; import android.view.ViewGroup; -import android.widget.ImageView; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; @@ -21,8 +19,7 @@ import androidx.leanback.widget.ArrayObjectAdapter; import androidx.recyclerview.widget.RecyclerView; import androidx.viewbinding.ViewBinding; -import com.fongmi.android.tv.api.config.WallConfig; -import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.ui.custom.CustomWallView; import com.fongmi.android.tv.utils.Util; import org.greenrobot.eventbus.EventBus; @@ -34,7 +31,6 @@ import me.jessyan.autosize.AutoSizeCompat; public abstract class BaseActivity extends AppCompatActivity { private OnBackInvokedCallback callback; - private ImageView wall; protected abstract ViewBinding getBinding(); @@ -52,7 +48,8 @@ public abstract class BaseActivity extends AppCompatActivity { @Override public void setContentView(View view) { super.setContentView(view); - if (customWall()) addWallView(); + if (!customWall()) return; + ((ViewGroup) findViewById(android.R.id.content)).addView(new CustomWallView(this), 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } protected Activity getActivity() { @@ -94,13 +91,6 @@ public abstract class BaseActivity extends AppCompatActivity { } } - private void addWallView() { - wall = new ImageView(this); - wall.setScaleType(CENTER_CROP); - ((ViewGroup) findViewById(android.R.id.content)).addView(wall, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); - WallConfig.refresh(wall); - } - private Resources hackResources(Resources resources) { try { AutoSizeCompat.autoConvertDensityOfGlobal(resources); @@ -111,8 +101,7 @@ public abstract class BaseActivity extends AppCompatActivity { } @Subscribe(threadMode = ThreadMode.MAIN) - public void onRefreshEvent(RefreshEvent event) { - if (event.getType() == RefreshEvent.Type.WALL && customWall()) WallConfig.refresh(wall); + public void onSubscribe(Object o) { } @Override diff --git a/app/src/main/java/com/fongmi/android/tv/api/config/WallConfig.java b/app/src/main/java/com/fongmi/android/tv/api/config/WallConfig.java index 445a3a056..3e3a53fcb 100644 --- a/app/src/main/java/com/fongmi/android/tv/api/config/WallConfig.java +++ b/app/src/main/java/com/fongmi/android/tv/api/config/WallConfig.java @@ -1,7 +1,10 @@ package com.fongmi.android.tv.api.config; +import android.net.Uri; import android.text.TextUtils; -import android.widget.ImageView; + +import androidx.media3.common.MediaItem; +import androidx.media3.exoplayer.ExoPlayer; import com.fongmi.android.tv.App; import com.fongmi.android.tv.R; @@ -10,9 +13,7 @@ import com.fongmi.android.tv.bean.Config; import com.fongmi.android.tv.event.RefreshEvent; import com.fongmi.android.tv.impl.Callback; import com.fongmi.android.tv.utils.FileUtil; -import com.fongmi.android.tv.utils.ImgUtil; import com.fongmi.android.tv.utils.Notify; -import com.fongmi.android.tv.utils.ResUtil; import com.fongmi.android.tv.utils.UrlUtil; import com.github.catvod.net.OkHttp; import com.github.catvod.utils.Path; @@ -24,6 +25,7 @@ import java.util.concurrent.Executors; public class WallConfig { private Config config; + private ExoPlayer player; private ExecutorService executor; private boolean sync; @@ -49,6 +51,7 @@ public class WallConfig { } public WallConfig init() { + createPlayer(); return config(Config.wall()); } @@ -60,6 +63,7 @@ public class WallConfig { } public WallConfig clear() { + getPlayer().clearMediaItems(); this.config = null; return this; } @@ -68,6 +72,10 @@ public class WallConfig { return config == null ? Config.wall() : config; } + public ExoPlayer getPlayer() { + return player == null ? createPlayer() : player; + } + public void load(Callback callback) { if (executor != null) executor.shutdownNow(); executor = Executors.newSingleThreadExecutor(); @@ -76,40 +84,39 @@ public class WallConfig { private void loadConfig(Callback callback) { try { - File file = write(FileUtil.getWall(0)); - if (Path.exists(file)) refresh(0); - else config(Config.find(VodConfig.get().getWall(), 2)); + byte[] data = OkHttp.bytes(UrlUtil.convert(getUrl())); + if (data.length == 0) throw new RuntimeException(); + Path.write(FileUtil.getWall(0), data); App.post(callback::success); config.update(); + refresh(0); } catch (Throwable e) { - App.post(() -> callback.error(Notify.getError(R.string.error_config_parse, e))); - config(Config.find(VodConfig.get().getWall(), 2)); + if (TextUtils.isEmpty(config.getUrl())) App.post(() -> callback.error("")); + else App.post(() -> callback.error(Notify.getError(R.string.error_config_get, e))); e.printStackTrace(); } } - private File write(File file) { - if (TextUtils.isEmpty(getUrl())) return file; - Path.write(file, OkHttp.bytes(UrlUtil.convert(getUrl()))); - return file; - } - public boolean needSync(String url) { return sync || TextUtils.isEmpty(config.getUrl()) || url.equals(config.getUrl()); } + private ExoPlayer createPlayer() { + player = new ExoPlayer.Builder(App.get()).build(); + player.setRepeatMode(ExoPlayer.REPEAT_MODE_ALL); + player.setPlayWhenReady(true); + player.setVolume(0); + return player; + } + + public static void load(File file) { + if (get().getPlayer().getMediaItemCount() > 0) return; + get().getPlayer().setMediaItem(MediaItem.fromUri(Uri.fromFile(file))); + get().getPlayer().prepare(); + } + public static void refresh(int index) { Setting.putWall(index); RefreshEvent.wall(); } - - public static void refresh(ImageView view) { - try { - File file = FileUtil.getWall(Setting.getWall()); - if (Path.exists(file)) ImgUtil.load(file, view); - else view.setImageResource(ResUtil.getDrawable(file.getName())); - } catch (Exception e) { - view.setImageResource(R.drawable.wallpaper_1); - } - } } diff --git a/app/src/main/java/com/fongmi/android/tv/ui/custom/CustomWallView.java b/app/src/main/java/com/fongmi/android/tv/ui/custom/CustomWallView.java new file mode 100644 index 000000000..a5a64db3c --- /dev/null +++ b/app/src/main/java/com/fongmi/android/tv/ui/custom/CustomWallView.java @@ -0,0 +1,100 @@ +package com.fongmi.android.tv.ui.custom; + +import static android.widget.ImageView.ScaleType.CENTER_CROP; + +import android.content.Context; +import android.media.MediaMetadataRetriever; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.media3.ui.AspectRatioFrameLayout; +import androidx.media3.ui.PlayerView; + +import com.fongmi.android.tv.Setting; +import com.fongmi.android.tv.api.config.WallConfig; +import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.utils.FileUtil; +import com.fongmi.android.tv.utils.ImgUtil; +import com.fongmi.android.tv.utils.ResUtil; + +import org.greenrobot.eventbus.EventBus; +import org.greenrobot.eventbus.Subscribe; +import org.greenrobot.eventbus.ThreadMode; + +import java.io.File; + +public class CustomWallView extends FrameLayout { + + private ImageView image; + private PlayerView video; + + public CustomWallView(@NonNull Context context) { + super(context); + init(context); + } + + private void init(Context context) { + image = new ImageView(context); + image.setScaleType(CENTER_CROP); + video = new PlayerView(context); + video.setUseController(false); + video.setKeepContentOnPlayerReset(true); + video.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_ZOOM); + addView(image, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + addView(video, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + refresh(); + } + + private boolean isVideo(File file) { + try (MediaMetadataRetriever retriever = new MediaMetadataRetriever()) { + retriever.setDataSource(file.getAbsolutePath()); + return "yes".equalsIgnoreCase(retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO)); + } catch (Exception e) { + return false; + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + public void onRefreshEvent(RefreshEvent event) { + if (event.getType() == RefreshEvent.Type.WALL) refresh(); + } + + private void refresh() { + if (Setting.getWall() == 0) load(FileUtil.getWall(Setting.getWall())); + else image.setImageResource(ResUtil.getDrawable("wallpaper_" + Setting.getWall())); + } + + private void load(File file) { + if (isVideo(file)) loadVideo(file); + else loadImage(file); + } + + private void loadImage(File file) { + video.setPlayer(null); + ImgUtil.load(file, image); + video.setVisibility(GONE); + image.setVisibility(VISIBLE); + WallConfig.get().getPlayer().clearMediaItems(); + } + + private void loadVideo(File file) { + WallConfig.load(file); + image.setVisibility(GONE); + video.setVisibility(VISIBLE); + image.setImageDrawable(null); + video.setPlayer(WallConfig.get().getPlayer()); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + EventBus.getDefault().register(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + EventBus.getDefault().unregister(this); + } +} diff --git a/app/src/main/java/com/fongmi/android/tv/utils/ImgUtil.java b/app/src/main/java/com/fongmi/android/tv/utils/ImgUtil.java index c6422ba14..c80ea7370 100644 --- a/app/src/main/java/com/fongmi/android/tv/utils/ImgUtil.java +++ b/app/src/main/java/com/fongmi/android/tv/utils/ImgUtil.java @@ -23,6 +23,7 @@ import com.bumptech.glide.request.target.Target; import com.bumptech.glide.request.transition.Transition; import com.bumptech.glide.signature.ObjectKey; import com.fongmi.android.tv.App; +import com.fongmi.android.tv.R; import com.fongmi.android.tv.impl.CustomTarget; import com.github.catvod.utils.Json; import com.google.common.net.HttpHeaders; @@ -53,12 +54,17 @@ public class ImgUtil { } public static void load(File file, ImageView view) { - Glide.with(view).load(file).diskCacheStrategy(DiskCacheStrategy.NONE).signature(new ObjectKey(file.lastModified())).into(new CustomTarget() { + Glide.with(view).load(file).diskCacheStrategy(DiskCacheStrategy.NONE).error(R.drawable.wallpaper_1).signature(new ObjectKey(file.lastModified())).into(new CustomTarget() { @Override public void onResourceReady(@NonNull Drawable resource, @Nullable Transition transition) { if (resource instanceof GifDrawable) ((GifDrawable) resource).start(); view.setImageDrawable(resource); } + + @Override + public void onLoadFailed(@Nullable Drawable errorDrawable) { + view.setImageDrawable(errorDrawable); + } }); } diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HomeActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HomeActivity.java index 44b18fbe8..926d7d688 100644 --- a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HomeActivity.java +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/HomeActivity.java @@ -171,9 +171,8 @@ public class HomeActivity extends BaseActivity implements NavigationBarView.OnIt mManager.change(position); } - @Override + @Subscribe(threadMode = ThreadMode.MAIN) public void onRefreshEvent(RefreshEvent event) { - super.onRefreshEvent(event); if (event.getType().equals(RefreshEvent.Type.CONFIG)) setNavigation(); } diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/base/BaseActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/base/BaseActivity.java index 466b8c8e7..9a5bd6b51 100644 --- a/app/src/mobile/java/com/fongmi/android/tv/ui/base/BaseActivity.java +++ b/app/src/mobile/java/com/fongmi/android/tv/ui/base/BaseActivity.java @@ -1,7 +1,6 @@ package com.fongmi.android.tv.ui.base; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.widget.ImageView.ScaleType.CENTER_CROP; import android.app.Activity; import android.graphics.Color; @@ -11,7 +10,6 @@ import android.view.DisplayCutout; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; -import android.widget.ImageView; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; @@ -19,8 +17,7 @@ import androidx.activity.OnBackPressedCallback; import androidx.appcompat.app.AppCompatActivity; import androidx.viewbinding.ViewBinding; -import com.fongmi.android.tv.api.config.WallConfig; -import com.fongmi.android.tv.event.RefreshEvent; +import com.fongmi.android.tv.ui.custom.CustomWallView; import com.fongmi.android.tv.utils.ResUtil; import org.greenrobot.eventbus.EventBus; @@ -30,7 +27,6 @@ import org.greenrobot.eventbus.ThreadMode; public abstract class BaseActivity extends AppCompatActivity { private OnBackInvokedCallback callback; - private ImageView wall; protected abstract ViewBinding getBinding(); @@ -48,7 +44,8 @@ public abstract class BaseActivity extends AppCompatActivity { @Override public void setContentView(View view) { super.setContentView(view); - if (customWall()) addWallView(); + if (!customWall()) return; + ((ViewGroup) findViewById(android.R.id.content)).addView(new CustomWallView(this), 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } protected Activity getActivity() { @@ -117,16 +114,8 @@ public abstract class BaseActivity extends AppCompatActivity { activity.getWindow().setStatusBarColor(Color.TRANSPARENT); } - private void addWallView() { - wall = new ImageView(this); - wall.setScaleType(CENTER_CROP); - ((ViewGroup) findViewById(android.R.id.content)).addView(wall, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); - WallConfig.refresh(wall); - } - @Subscribe(threadMode = ThreadMode.MAIN) - public void onRefreshEvent(RefreshEvent event) { - if (event.getType() == RefreshEvent.Type.WALL && customWall()) WallConfig.refresh(wall); + public void onSubscribe(Object o) { } protected void onBackInvoked() {