Add cast support #123

This commit is contained in:
Thomas 2020-12-02 18:05:35 +01:00
parent 680bdf7bd2
commit 5757a8b1d8
10 changed files with 226 additions and 5 deletions

View File

@ -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'
}

View File

@ -17,7 +17,8 @@
<string name="set_video_sensitive_choice" translatable="false">set_video_sensitive_choice</string>
<string name="set_video_in_list">Vidéos dans une liste</string>
<string name="set_video_in_list_description">Change la mise en page pour afficher les vidéos dans une liste</string>
<string name="cast">ChromeCast</string>
<string name="chromecast_choice">Choix de la ChromeCast</string>
<string name="_retry">Réessayer</string>
<string name="refresh_token_failed">Échec de rafraîchissement du jeton d\'accès</string>
<string name="refresh_token_failed_message">Vous pouvez réessayer de le rafraîchir ou simplement déconnecter le compte</string>

View File

@ -19,7 +19,8 @@
<string name="no_instances">No instances !</string>
<string name="show_more">Show more</string>
<string name="show_less">Show less</string>
<string name="cast">ChromeCast</string>
<string name="chromecast_choice">ChromeCast choice</string>
<string name="set_play_screen_lock">Screen lock</string>
<string name="set_play_screen_lock_description">Keep playing videos when the screen is locked</string>

View File

@ -19,6 +19,8 @@
<string name="no_instances">No instances !</string>
<string name="show_more">Show more</string>
<string name="show_less">Show less</string>
<string name="cast">ChromeCast</string>
<string name="chromecast_choice">ChromeCast choice</string>
<string name="set_play_screen_lock">Screen lock</string>
<string name="set_play_screen_lock_description">Keep playing videos when the screen is locked</string>

View File

@ -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<NetworkInterface> interfaces;
interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface ni : interfaces) {
if ((!ni.isLoopback()) && ni.isUp() && (ni.getName().equals("wlan0"))) {
Enumeration<InetAddress> 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

View File

@ -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<String> 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<ChromeCast> 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();
}

View File

@ -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;
}
}

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM1,18v3h3c0,-1.66 -1.34,-3 -3,-3zM1,14v2c2.76,0 5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zM1,10v2c4.97,0 9,4.03 9,9h2c0,-6.08 -4.93,-11 -11,-11z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M1,18v3h3c0,-1.66 -1.34,-3 -3,-3zM1,14v2c2.76,0 5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zM19,7L5,7v1.63c3.96,1.28 7.09,4.41 8.37,8.37L19,17L19,7zM1,10v2c4.97,0 9,4.03 9,9h2c0,-6.08 -4.93,-11 -11,-11zM21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2z" />
</vector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_cast"
android:icon="@drawable/ic_baseline_cast_24"
android:title="@string/cast"
android:visible="false"
app:showAsAction="always" />
</menu>