diff --git a/app/build.gradle b/app/build.gradle
index 772f76f68..d3668110b 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -114,8 +114,10 @@ dependencies {
implementation('org.simpleframework:simple-xml:2.7.1') { exclude group: 'stax', module: 'stax-api' exclude group: 'xpp3', module: 'xpp3' }
leanbackImplementation 'androidx.leanback:leanback:1.2.0-alpha02'
leanbackImplementation 'me.jessyan:autosize:1.2.1'
+ mobileImplementation 'androidx.mediarouter:mediarouter:1.3.0'
mobileImplementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
mobileImplementation 'com.github.devin1014.DLNA-Cast:dlna-dmc:V1.0.0'
+ mobileImplementation 'com.google.android.gms:play-services-cast-framework:21.3.0'
mobileImplementation('com.journeyapps:zxing-android-embedded:4.3.0') { transitive = false }
annotationProcessor 'androidx.room:room-compiler:2.5.1'
annotationProcessor 'com.github.bumptech.glide:compiler:4.14.2'
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
index a74243cb5..46f459cfe 100644
--- a/app/proguard-rules.pro
+++ b/app/proguard-rules.pro
@@ -35,6 +35,9 @@
-keep class com.github.catvod.crawler.** { *; }
-keep class * extends com.github.catvod.crawler.Spider
+# Cast
+-keep class * implements com.google.android.gms.cast.framework.OptionsProvider
+
# Cling
-keep class org.fourthline.cling.** { *; }
diff --git a/app/src/main/java/com/fongmi/android/tv/bean/Device.java b/app/src/main/java/com/fongmi/android/tv/bean/Device.java
index 3c9fb8a81..c60c6c347 100644
--- a/app/src/main/java/com/fongmi/android/tv/bean/Device.java
+++ b/app/src/main/java/com/fongmi/android/tv/bean/Device.java
@@ -88,6 +88,10 @@ public class Device {
this.type = type;
}
+ public boolean isLeanback() {
+ return getType() == 0;
+ }
+
public boolean isMobile() {
return getType() == 1;
}
@@ -96,8 +100,16 @@ public class Device {
return getType() == 2;
}
+ public boolean isCast() {
+ return getType() == 3;
+ }
+
+ public boolean isApp() {
+ return isLeanback() || isMobile();
+ }
+
public String getHost() {
- return isDLNA() ? getUuid() : Uri.parse(getIp()).getHost();
+ return isDLNA() || isCast() ? getUuid() : Uri.parse(getIp()).getHost();
}
public Device save() {
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 0a1f3776b..b47158eb9 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,7 +2,7 @@
TV
- C0868879
+ 08955620
Press back again to exit
diff --git a/app/src/mobile/AndroidManifest.xml b/app/src/mobile/AndroidManifest.xml
index d59b78ccb..7e9df0c00 100644
--- a/app/src/mobile/AndroidManifest.xml
+++ b/app/src/mobile/AndroidManifest.xml
@@ -6,6 +6,10 @@
+
+
> devices;
+ private final List routers;
private static class Loader {
static volatile CastDevice INSTANCE = new CastDevice();
@@ -19,6 +25,7 @@ public class CastDevice {
public CastDevice() {
this.devices = new ArrayList<>();
+ this.routers = new ArrayList<>();
}
public boolean isEmpty() {
@@ -33,16 +40,39 @@ public class CastDevice {
return device;
}
- public List getAll() {
+ private com.fongmi.android.tv.bean.Device create(MediaRouter.RouteInfo item) {
+ com.fongmi.android.tv.bean.Device device = new com.fongmi.android.tv.bean.Device();
+ device.setName(item.getName());
+ device.setUuid(item.getId());
+ device.setType(3);
+ return device;
+ }
+
+ public List getDLNA() {
List items = new ArrayList<>();
- for (Device, ?, ?> device : devices) items.add(create(device));
+ for (Device, ?, ?> item : devices) items.add(create(item));
return items;
}
- public List add(Device, ?, ?> device) {
- devices.remove(device);
- devices.add(device);
- return getAll();
+ public List getCast() {
+ List items = new ArrayList<>();
+ for (MediaRouter.RouteInfo item : routers) items.add(create(item));
+ return items;
+ }
+
+ public List add(Device, ?, ?> item) {
+ devices.remove(item);
+ devices.add(item);
+ return getDLNA();
+ }
+
+ @SuppressLint("RestrictedApi")
+ public List add(List items) {
+ ArrayList routes = new ArrayList<>(items);
+ onFilterRoutes(routes);
+ routers.clear();
+ routers.addAll(routes);
+ return getCast();
}
public com.fongmi.android.tv.bean.Device remove(Device, ?, ?> device) {
@@ -50,8 +80,22 @@ public class CastDevice {
return create(device);
}
- public Device, ?, ?> find(com.fongmi.android.tv.bean.Device item) {
+ public Device, ?, ?> findDLNA(com.fongmi.android.tv.bean.Device item) {
for (Device, ?, ?> device : devices) if (device.getIdentity().getUdn().getIdentifierString().equals(item.getUuid())) return device;
return null;
}
+
+ public MediaRouter.RouteInfo findCast(com.fongmi.android.tv.bean.Device item) {
+ for (MediaRouter.RouteInfo device : routers) if (device.getId().equals(item.getUuid())) return device;
+ return null;
+ }
+
+ private void onFilterRoutes(@NonNull List routes) {
+ for (int i = routes.size(); i-- > 0; ) if (!onFilterRoute(routes.get(i))) routes.remove(i);
+ }
+
+ @SuppressLint("RestrictedApi")
+ private boolean onFilterRoute(@NonNull MediaRouter.RouteInfo route) {
+ return !route.isDefaultOrBluetooth() && route.isEnabled();
+ }
}
diff --git a/app/src/mobile/java/com/fongmi/android/tv/cast/CastOptionsProvider.java b/app/src/mobile/java/com/fongmi/android/tv/cast/CastOptionsProvider.java
new file mode 100644
index 000000000..bda5960b9
--- /dev/null
+++ b/app/src/mobile/java/com/fongmi/android/tv/cast/CastOptionsProvider.java
@@ -0,0 +1,62 @@
+package com.fongmi.android.tv.cast;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.fongmi.android.tv.R;
+import com.fongmi.android.tv.ui.activity.DetailActivity;
+import com.google.android.gms.cast.LaunchOptions;
+import com.google.android.gms.cast.MediaMetadata;
+import com.google.android.gms.cast.framework.CastOptions;
+import com.google.android.gms.cast.framework.OptionsProvider;
+import com.google.android.gms.cast.framework.SessionProvider;
+import com.google.android.gms.cast.framework.media.CastMediaOptions;
+import com.google.android.gms.cast.framework.media.ImageHints;
+import com.google.android.gms.cast.framework.media.ImagePicker;
+import com.google.android.gms.cast.framework.media.MediaIntentReceiver;
+import com.google.android.gms.cast.framework.media.NotificationOptions;
+import com.google.android.gms.common.images.WebImage;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class CastOptionsProvider implements OptionsProvider {
+
+ @NonNull
+ @Override
+ public CastOptions getCastOptions(Context context) {
+ NotificationOptions notificationOptions = new NotificationOptions.Builder().setActions(Arrays.asList(MediaIntentReceiver.ACTION_SKIP_NEXT, MediaIntentReceiver.ACTION_TOGGLE_PLAYBACK, MediaIntentReceiver.ACTION_STOP_CASTING), new int[]{1, 2}).setTargetActivityClassName(DetailActivity.class.getName()).build();
+ CastMediaOptions mediaOptions = new CastMediaOptions.Builder().setImagePicker(new ImagePickerImpl()).setNotificationOptions(notificationOptions).setExpandedControllerActivityClassName(DetailActivity.class.getName()).build();
+ LaunchOptions launchOptions = new LaunchOptions.Builder().setAndroidReceiverCompatible(true).build();
+ return new CastOptions.Builder().setLaunchOptions(launchOptions).setReceiverApplicationId(context.getString(R.string.app_id)).setCastMediaOptions(mediaOptions).build();
+ }
+
+ @Nullable
+ @Override
+ public List getAdditionalSessionProviders(@NonNull Context context) {
+ return null;
+ }
+
+ private static class ImagePickerImpl extends ImagePicker {
+
+ @Override
+ public WebImage onPickImage(MediaMetadata mediaMetadata, ImageHints hints) {
+ int type = hints.getType();
+ if ((mediaMetadata == null) || !mediaMetadata.hasImages()) {
+ return null;
+ }
+ List images = mediaMetadata.getImages();
+ if (images.size() == 1) {
+ return images.get(0);
+ } else {
+ if (type == ImagePicker.IMAGE_TYPE_MEDIA_ROUTE_CONTROLLER_DIALOG_BACKGROUND) {
+ return images.get(0);
+ } else {
+ return images.get(1);
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/DetailActivity.java b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/DetailActivity.java
index 9e22765e1..eb91125fd 100644
--- a/app/src/mobile/java/com/fongmi/android/tv/ui/activity/DetailActivity.java
+++ b/app/src/mobile/java/com/fongmi/android/tv/ui/activity/DetailActivity.java
@@ -44,18 +44,17 @@ import com.fongmi.android.tv.event.ErrorEvent;
import com.fongmi.android.tv.event.PlayerEvent;
import com.fongmi.android.tv.event.RefreshEvent;
import com.fongmi.android.tv.model.SiteViewModel;
-import com.fongmi.android.tv.utils.PiP;
-import com.fongmi.android.tv.receiver.PiPReceiver;
import com.fongmi.android.tv.player.ExoUtil;
import com.fongmi.android.tv.player.Players;
+import com.fongmi.android.tv.receiver.PiPReceiver;
import com.fongmi.android.tv.ui.adapter.EpisodeAdapter;
import com.fongmi.android.tv.ui.adapter.FlagAdapter;
import com.fongmi.android.tv.ui.adapter.ParseAdapter;
import com.fongmi.android.tv.ui.adapter.SearchAdapter;
import com.fongmi.android.tv.ui.base.BaseActivity;
+import com.fongmi.android.tv.ui.base.ViewType;
import com.fongmi.android.tv.ui.custom.CustomKeyDownVod;
import com.fongmi.android.tv.ui.custom.SpaceItemDecoration;
-import com.fongmi.android.tv.ui.base.ViewType;
import com.fongmi.android.tv.ui.custom.dialog.CastDialog;
import com.fongmi.android.tv.ui.custom.dialog.ControlDialog;
import com.fongmi.android.tv.ui.custom.dialog.EpisodeGridDialog;
@@ -63,10 +62,12 @@ import com.fongmi.android.tv.ui.custom.dialog.EpisodeListDialog;
import com.fongmi.android.tv.ui.custom.dialog.TrackDialog;
import com.fongmi.android.tv.utils.Clock;
import com.fongmi.android.tv.utils.Notify;
+import com.fongmi.android.tv.utils.PiP;
import com.fongmi.android.tv.utils.Prefers;
import com.fongmi.android.tv.utils.ResUtil;
import com.fongmi.android.tv.utils.Traffic;
import com.fongmi.android.tv.utils.Utils;
+import com.google.android.gms.cast.framework.CastContext;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import com.permissionx.guolindev.PermissionX;
@@ -94,6 +95,7 @@ public class DetailActivity extends BaseActivity implements Clock.Callback, Cust
private ExecutorService mExecutor;
private SiteViewModel mViewModel;
private FlagAdapter mFlagAdapter;
+ private CastContext mCastContext;
private List