diff --git a/app/build.gradle b/app/build.gradle index 58b5a84..d22555b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -126,7 +126,7 @@ dependencies { implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.browser:browser:1.2.0' implementation 'androidx.documentfile:documentfile:1.0.1' - testImplementation 'junit:junit:4.13' + testImplementation 'junit:junit:4.13.1' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' @@ -155,5 +155,7 @@ dependencies { implementation "androidx.work:work-runtime:2.4.0" implementation "androidx.work:work-runtime-ktx:2.4.0" implementation 'jp.wasabeef:glide-transformations:4.0.0' + implementation 'su.litvak.chromecast:api-v2:0.11.3' + implementation 'com.fasterxml.jackson.core:jackson-core:2.12.0' } \ No newline at end of file diff --git a/app/src/acad/res/values/strings.xml b/app/src/acad/res/values/strings.xml index 671d716..5ac6096 100644 --- a/app/src/acad/res/values/strings.xml +++ b/app/src/acad/res/values/strings.xml @@ -17,7 +17,8 @@ set_video_sensitive_choice Vidéos dans une liste Change la mise en page pour afficher les vidéos dans une liste - + ChromeCast + Choix de la ChromeCast Réessayer Échec de rafraîchissement du jeton d\'accès Vous pouvez réessayer de le rafraîchir ou simplement déconnecter le compte diff --git a/app/src/bittube/res/values/strings.xml b/app/src/bittube/res/values/strings.xml index 35c10c7..55439ff 100644 --- a/app/src/bittube/res/values/strings.xml +++ b/app/src/bittube/res/values/strings.xml @@ -19,7 +19,8 @@ No instances ! Show more Show less - + ChromeCast + ChromeCast choice Screen lock Keep playing videos when the screen is locked diff --git a/app/src/full/res/values/strings.xml b/app/src/full/res/values/strings.xml index 88bd203..72d782a 100644 --- a/app/src/full/res/values/strings.xml +++ b/app/src/full/res/values/strings.xml @@ -19,6 +19,8 @@ No instances ! Show more Show less + ChromeCast + ChromeCast choice Screen lock Keep playing videos when the screen is locked diff --git a/app/src/main/java/app/fedilab/fedilabtube/MainActivity.java b/app/src/main/java/app/fedilab/fedilabtube/MainActivity.java index 1346364..8eccdfb 100644 --- a/app/src/main/java/app/fedilab/fedilabtube/MainActivity.java +++ b/app/src/main/java/app/fedilab/fedilabtube/MainActivity.java @@ -24,6 +24,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.Looper; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -47,6 +48,11 @@ import com.kobakei.ratethisapp.RateThisApp; import org.jetbrains.annotations.NotNull; +import java.io.IOException; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.Collections; +import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.List; import java.util.Set; @@ -77,6 +83,7 @@ import app.fedilab.fedilabtube.sqlite.Sqlite; import app.fedilab.fedilabtube.sqlite.StoredInstanceDAO; import app.fedilab.fedilabtube.viewmodel.TimelineVM; import es.dmoral.toasty.Toasty; +import su.litvak.chromecast.api.v2.ChromeCasts; import static app.fedilab.fedilabtube.MainActivity.TypeOfConnection.NORMAL; import static app.fedilab.fedilabtube.MainActivity.TypeOfConnection.SURFING; @@ -198,6 +205,36 @@ public class MainActivity extends AppCompatActivity { View view = binding.getRoot(); setContentView(view); + new Thread(() -> { + try { + List interfaces; + interfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); + for (NetworkInterface ni : interfaces) { + if ((!ni.isLoopback()) && ni.isUp() && (ni.getName().equals("wlan0"))) { + Enumeration inetAddressEnumeration = ni.getInetAddresses(); + while (inetAddressEnumeration.hasMoreElements()) { + InetAddress inetAddress = inetAddressEnumeration.nextElement(); + ChromeCasts.restartDiscovery(inetAddress); + PeertubeActivity.chromeCasts = ChromeCasts.get(); + int tryFind = 0; + while (PeertubeActivity.chromeCasts.isEmpty() && tryFind < 5) { + try { + Thread.sleep(1000); + PeertubeActivity.chromeCasts = ChromeCasts.get(); + tryFind++; + } catch (InterruptedException ignored) { + } + } + } + } + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = this::invalidateOptionsMenu; + mainHandler.post(myRunnable); + } catch (IOException e) { + e.printStackTrace(); + } + }).start(); Toolbar toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); @@ -722,7 +759,7 @@ public class MainActivity extends AppCompatActivity { return locaFragment; } } - return null; + return overviewFragment; } @Override diff --git a/app/src/main/java/app/fedilab/fedilabtube/PeertubeActivity.java b/app/src/main/java/app/fedilab/fedilabtube/PeertubeActivity.java index f3006c1..530bee0 100644 --- a/app/src/main/java/app/fedilab/fedilabtube/PeertubeActivity.java +++ b/app/src/main/java/app/fedilab/fedilabtube/PeertubeActivity.java @@ -44,6 +44,7 @@ import android.text.method.LinkMovementMethod; import android.text.style.ClickableSpan; import android.util.DisplayMetrics; import android.view.LayoutInflater; +import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; @@ -103,6 +104,8 @@ import com.google.android.exoplayer2.video.VideoListener; import org.jetbrains.annotations.NotNull; +import java.io.IOException; +import java.security.GeneralSecurityException; import java.text.DateFormat; import java.util.ArrayList; import java.util.HashMap; @@ -133,6 +136,7 @@ import app.fedilab.fedilabtube.drawer.CommentListAdapter; import app.fedilab.fedilabtube.drawer.MenuAdapter; import app.fedilab.fedilabtube.drawer.MenuItemAdapter; import app.fedilab.fedilabtube.helper.CacheDataSourceFactory; +import app.fedilab.fedilabtube.helper.DashCastRequest; import app.fedilab.fedilabtube.helper.Helper; import app.fedilab.fedilabtube.helper.HelperInstance; import app.fedilab.fedilabtube.sqlite.AccountDAO; @@ -147,6 +151,11 @@ import app.fedilab.fedilabtube.webview.CustomWebview; import app.fedilab.fedilabtube.webview.MastalabWebChromeClient; import app.fedilab.fedilabtube.webview.MastalabWebViewClient; import es.dmoral.toasty.Toasty; +import su.litvak.chromecast.api.v2.Application; +import su.litvak.chromecast.api.v2.ChromeCast; +import su.litvak.chromecast.api.v2.ChromeCasts; +import su.litvak.chromecast.api.v2.ChromeCastsListener; +import su.litvak.chromecast.api.v2.Status; import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.ADD_COMMENT; import static app.fedilab.fedilabtube.client.RetrofitPeertubeAPI.ActionType.RATEVIDEO; @@ -160,7 +169,7 @@ import static app.fedilab.fedilabtube.helper.Helper.peertubeInformation; import static com.google.android.exoplayer2.Player.MEDIA_ITEM_TRANSITION_REASON_AUTO; -public class PeertubeActivity extends AppCompatActivity implements CommentListAdapter.AllCommentRemoved, Player.EventListener, VideoListener, TorrentListener, MenuAdapter.ItemClicked, MenuItemAdapter.ItemAction { +public class PeertubeActivity extends AppCompatActivity implements CommentListAdapter.AllCommentRemoved, Player.EventListener, VideoListener, TorrentListener, MenuAdapter.ItemClicked, MenuItemAdapter.ItemAction, ChromeCastsListener { public static String video_id; public static List playedVideos = new ArrayList<>(); @@ -194,6 +203,9 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd private String currentCaption; private boolean isRemote; private boolean willPlayFromIntent; + public static List chromeCasts; + private ChromeCast chromeCast; + private String chromeCastVideoURL; public static void hideKeyboard(Activity activity) { if (activity != null && activity.getWindow() != null) { @@ -240,6 +252,14 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd public void onStreamStopped() { } + @Override + public void newChromeCastDiscovered(ChromeCast chromeCast) { + } + + @Override + public void chromeCastRemoved(ChromeCast chromeCast) { + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -263,6 +283,9 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd .removeFilesAfterStop(true) .build(); + + ChromeCastsListener chromeCastsListener = this; + ChromeCasts.registerListener(chromeCastsListener); fullScreenMode = false; torrentStream = TorrentStream.init(torrentOptions); torrentStream.addListener(PeertubeActivity.this); @@ -661,6 +684,20 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd }).start(); } + @Override + public boolean onCreateOptionsMenu(@NotNull Menu menu) { + getMenuInflater().inflate(R.menu.video_menu, menu); + MenuItem castItem = menu.findItem(R.id.action_cast); + if (chromeCasts != null && chromeCasts.size() > 0) { + castItem.setVisible(true); + if (chromeCast != null && chromeCast.isConnected()) { + castItem.setIcon(R.drawable.ic_baseline_cast_connected_24); + } else { + castItem.setIcon(R.drawable.ic_baseline_cast_24); + } + } + return true; + } @Override public boolean onOptionsItemSelected(MenuItem item) { @@ -670,6 +707,69 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd } finish(); return true; + } else if (item.getItemId() == R.id.action_cast) { + if (chromeCasts != null && chromeCasts.size() > 0) { + String[] chromecast_choice = new String[chromeCasts.size()]; + AlertDialog.Builder alt_bld = new AlertDialog.Builder(this); + alt_bld.setTitle(R.string.chromecast_choice); + int i = 0; + for (ChromeCast cc : chromeCasts) { + chromecast_choice[i] = cc.getTitle(); + i++; + } + alt_bld.setSingleChoiceItems(chromecast_choice, i, (dialog, position) -> { + chromeCast = chromeCasts.get(position); + new Thread(() -> { + if (chromeCast != null) { + if (chromeCast.isConnected()) { + try { + chromeCast.disconnect(); + chromeCast = null; + } catch (IOException e) { + e.printStackTrace(); + } + } else { + try { + chromeCast.connect(); + + + Status status = chromeCast.getStatus(); + + String app_id = status.getRunningApp().id; + if (chromeCast.isAppAvailable(app_id) && !status.isAppRunning(app_id)) { + Application app = chromeCast.launchApp(app_id); + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> { + invalidateOptionsMenu(); + dialog.dismiss(); + }; + mainHandler.post(myRunnable); + if (chromeCastVideoURL != null) { + chromeCast.load(peertube.getTitle(), "https://" + HelperInstance.getLiveInstance(PeertubeActivity.this) + peertube.getThumbnailPath(), chromeCastVideoURL, null); + // chromeCast.send("urn:x-cast:es.offd.dashcast", new DashCastRequest(chromeCastVideoURL, true, false, 0)); + } + + } catch (IOException | GeneralSecurityException e) { + e.printStackTrace(); + } + } + + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> { + invalidateOptionsMenu(); + dialog.dismiss(); + }; + mainHandler.post(myRunnable); + + } + }).start(); + + }); + alt_bld.setPositiveButton(R.string.close, (dialog, id) -> dialog.dismiss()); + AlertDialog alert = alt_bld.create(); + alert.show(); + } } return super.onOptionsItemSelected(item); } @@ -1182,6 +1282,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd private void startStream(String videoURL, String streamingPlaylistsURLS, boolean autoPlay, long position, Uri subtitles, String lang, boolean promptNSFW) { + chromeCastVideoURL = videoURL; SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); String nsfwAction = sharedpreferences.getString(getString(R.string.set_video_sensitive_choice), Helper.BLUR); if (promptNSFW && peertube != null && peertube.isNsfw() && (nsfwAction.compareTo(Helper.BLUR) == 0 || nsfwAction.compareTo(Helper.DO_NOT_LIST) == 0)) { @@ -1205,6 +1306,7 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd stream(videoURL, streamingPlaylistsURLS, autoPlay, position, subtitles, lang); } + } @Override @@ -1247,6 +1349,14 @@ public class PeertubeActivity extends AppCompatActivity implements CommentListAd if (torrentStream != null && torrentStream.isStreaming()) { torrentStream.stopStream(); } + ChromeCasts.unregisterListener(this); + if (chromeCast != null) { + try { + chromeCast.disconnect(); + } catch (IOException e) { + e.printStackTrace(); + } + } unregisterReceiver(); } diff --git a/app/src/main/java/app/fedilab/fedilabtube/helper/DashCastRequest.java b/app/src/main/java/app/fedilab/fedilabtube/helper/DashCastRequest.java new file mode 100644 index 0000000..9a325ca --- /dev/null +++ b/app/src/main/java/app/fedilab/fedilabtube/helper/DashCastRequest.java @@ -0,0 +1,38 @@ +package app.fedilab.fedilabtube.helper; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import su.litvak.chromecast.api.v2.Request; + +public class DashCastRequest implements Request { + @JsonProperty + final String url; + @JsonProperty + final boolean force; + @JsonProperty + final boolean reload; + @JsonProperty("reload_time") + final int reloadTime; + + private Long requestId; + + public DashCastRequest(String url, + boolean force, + boolean reload, + int reloadTime) { + this.url = url; + this.force = force; + this.reload = reload; + this.reloadTime = reloadTime; + } + + @Override + public Long getRequestId() { + return requestId; + } + + @Override + public void setRequestId(Long requestId) { + this.requestId = requestId; + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_cast_24.xml b/app/src/main/res/drawable/ic_baseline_cast_24.xml new file mode 100644 index 0000000..beb2f68 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_cast_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_baseline_cast_connected_24.xml b/app/src/main/res/drawable/ic_baseline_cast_connected_24.xml new file mode 100644 index 0000000..7f2eab7 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_cast_connected_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/menu/video_menu.xml b/app/src/main/res/menu/video_menu.xml new file mode 100644 index 0000000..88d4e36 --- /dev/null +++ b/app/src/main/res/menu/video_menu.xml @@ -0,0 +1,10 @@ + + + +