From a48f997ae3e8f9ea74c54e7b682a4065297c0233 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 28 Jun 2022 16:28:11 +0200 Subject: [PATCH] Manage Peertube videos --- .../android/activities/MediaActivity.java | 2 +- .../activities/SearchResultTabActivity.java | 1 + .../endpoints/MastodonTimelinesService.java | 4 ++ .../client/entities/api/Attachment.java | 2 + .../entities/peertube/PeertubeVideo.java | 28 +++++++- .../app/fedilab/android/helper/Helper.java | 1 + .../android/ui/drawer/StatusAdapter.java | 18 +++++ .../ui/fragment/media/FragmentMedia.java | 65 ++++++++++++------- .../viewmodel/mastodon/TimelinesVM.java | 29 +++++++++ 9 files changed, 123 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/activities/MediaActivity.java b/app/src/main/java/app/fedilab/android/activities/MediaActivity.java index 4af12d8d..baeb5a82 100644 --- a/app/src/main/java/app/fedilab/android/activities/MediaActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/MediaActivity.java @@ -125,7 +125,7 @@ public class MediaActivity extends BaseActivity implements OnDownloadInterface { ScreenSlidePagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(MediaActivity.this); binding.mediaViewpager.setAdapter(mPagerAdapter); - + binding.mediaViewpager.setSaveEnabled(false); binding.mediaViewpager.setCurrentItem(mediaPosition - 1); binding.haulerView.setOnDragDismissedListener(dragDirection -> ActivityCompat.finishAfterTransition(MediaActivity.this)); registerReceiver(onDownloadComplete, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); diff --git a/app/src/main/java/app/fedilab/android/activities/SearchResultTabActivity.java b/app/src/main/java/app/fedilab/android/activities/SearchResultTabActivity.java index b7814e67..04195b35 100644 --- a/app/src/main/java/app/fedilab/android/activities/SearchResultTabActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/SearchResultTabActivity.java @@ -83,6 +83,7 @@ public class SearchResultTabActivity extends BaseActivity { binding.searchTabLayout.setTabIconTint(ThemeHelper.getColorStateList(SearchResultTabActivity.this)); ScreenSlidePagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(SearchResultTabActivity.this); binding.searchViewpager.setAdapter(mPagerAdapter); + binding.searchViewpager.setSaveEnabled(false); binding.searchViewpager.setOffscreenPageLimit(3); new TabLayoutMediator(binding.searchTabLayout, binding.searchViewpager, (tab, position) -> { diff --git a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonTimelinesService.java b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonTimelinesService.java index 69850f5b..5602c02a 100644 --- a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonTimelinesService.java +++ b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonTimelinesService.java @@ -224,4 +224,8 @@ public interface MastodonTimelinesService { @Query("count") int count ); + @GET("api/v1/videos/{id}") + Call getPeertubeVideo( + @Path("id") String id + ); } diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Attachment.java b/app/src/main/java/app/fedilab/android/client/entities/api/Attachment.java index 5f11e2e1..2d06c314 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/Attachment.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Attachment.java @@ -44,4 +44,6 @@ public class Attachment implements Serializable { @SerializedName("local_path") public String local_path; + public String peertubeHost = null; + public String peertubeId = null; } diff --git a/app/src/main/java/app/fedilab/android/client/entities/peertube/PeertubeVideo.java b/app/src/main/java/app/fedilab/android/client/entities/peertube/PeertubeVideo.java index 90f32381..7f48a99a 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/peertube/PeertubeVideo.java +++ b/app/src/main/java/app/fedilab/android/client/entities/peertube/PeertubeVideo.java @@ -56,9 +56,11 @@ public class PeertubeVideo implements Serializable { status.account = account; List attachmentList = new ArrayList<>(); Attachment attachment = new Attachment(); - attachment.type = "video/mp4"; + attachment.type = "video"; attachment.url = "https://" + peertubeVideo.account.host + peertubeVideo.embedPath; attachment.preview_url = "https://" + peertubeVideo.account.host + peertubeVideo.thumbnailPath; + attachment.peertubeId = peertubeVideo.uuid; + attachment.peertubeHost = peertubeVideo.account.host; attachmentList.add(attachment); status.media_attachments = attachmentList; return status; @@ -111,11 +113,33 @@ public class PeertubeVideo implements Serializable { public Date updatedAt; @SerializedName("uuid") public String uuid; - + @SerializedName("files") + public List files; @SerializedName("views") public int views; } + public class File implements Serializable { + @SerializedName("fileDownloadUrl") + public String fileDownloadUrl; + @SerializedName("fileUrl") + public String fileUrl; + @SerializedName("fps") + public int fps; + @SerializedName("magnetUri") + public String magnetUri; + @SerializedName("metadataUrl") + public String metadataUrl; + @SerializedName("resolution") + public Item resolutions; + @SerializedName("size") + public long size; + @SerializedName("torrentDownloadUrl") + public String torrentDownloadUrl; + @SerializedName("torrentUrl") + public String torrentUrl; + } + public static class PeertubeAccount implements Serializable { @SerializedName("avatar") public Avatar avatar; diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index 284e1ae3..361ef13e 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -280,6 +280,7 @@ public class Helper { public static final Pattern libredditPattern = Pattern.compile("(www\\.|m\\.)?(reddit\\.com|preview\\.redd\\.it|i\\.redd\\.it|redd\\.it)/(((?!([\"'<])).)*)"); public static final Pattern ouichesPattern = Pattern.compile("https?://ouich\\.es/tag/(\\w+)"); public static final Pattern xmppPattern = Pattern.compile("xmpp:[-a-zA-Z0-9+$&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"); + public static final Pattern peertubePattern = Pattern.compile("(https?://([\\da-z.-]+\\.[a-z.]{2,10}))/videos/watch/(\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12})$"); public static final Pattern mediumPattern = Pattern.compile("([\\w@-]*)?\\.?medium.com/@?([/\\w-]+)"); public static final Pattern wikipediaPattern = Pattern.compile("([\\w_-]+)\\.wikipedia.org/(((?!([\"'<])).)*)"); public static final Pattern codePattern = Pattern.compile("code=([\\w-]+)"); diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java index 79804933..a8e0c650 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java @@ -433,6 +433,24 @@ public class StatusAdapter extends RecyclerView.Adapter holder.binding.statusContent.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); holder.binding.spoiler.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14); } + + //If the message contain a link to peertube and no media was added, we add it + if (statusToDeal.card != null && statusToDeal.card.url != null && (statusToDeal.media_attachments == null || statusToDeal.media_attachments.size() == 0)) { + Matcher matcherLink = Helper.peertubePattern.matcher(statusToDeal.card.url); + if (matcherLink.find()) { //Peertubee video + List attachmentList = new ArrayList<>(); + Attachment attachment = new Attachment(); + attachment.type = "video"; + attachment.url = matcherLink.group(0); + attachment.preview_url = statusToDeal.card.image; + attachment.peertubeHost = matcherLink.group(2); + attachment.peertubeId = matcherLink.group(3); + attachmentList.add(attachment); + statusToDeal.media_attachments = attachmentList; + adapter.notifyItemChanged(getPositionAsync(notificationList, statusList, statusToDeal)); + } + } + if (status.card != null && (display_card || status.isFocused)) { if (status.card.width > status.card.height) { holder.binding.cardImageHorizontal.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/media/FragmentMedia.java b/app/src/main/java/app/fedilab/android/ui/fragment/media/FragmentMedia.java index 3ef9f557..08fee270 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/media/FragmentMedia.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/media/FragmentMedia.java @@ -31,6 +31,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; import androidx.preference.PreferenceManager; import com.bumptech.glide.Glide; @@ -51,6 +52,7 @@ import app.fedilab.android.client.entities.api.Attachment; import app.fedilab.android.databinding.FragmentSlideMediaBinding; import app.fedilab.android.helper.CacheDataSourceFactory; import app.fedilab.android.helper.Helper; +import app.fedilab.android.viewmodel.mastodon.TimelinesVM; import app.fedilab.android.webview.CustomWebview; import app.fedilab.android.webview.FedilabWebChromeClient; import app.fedilab.android.webview.FedilabWebViewClient; @@ -198,32 +200,18 @@ public class FragmentMedia extends Fragment { case "video": case "audio": case "gifv": - binding.pbarInf.setIndeterminate(false); - binding.pbarInf.setScaleY(3f); - binding.mediaVideo.setVisibility(View.VISIBLE); - Uri uri = Uri.parse(url); - - String userAgent = sharedpreferences.getString(getString(R.string.SET_CUSTOM_USER_AGENT), Helper.USER_AGENT); - int video_cache = sharedpreferences.getInt(getString(R.string.SET_VIDEO_CACHE), Helper.DEFAULT_VIDEO_CACHE_MB); - ProgressiveMediaSource videoSource; - if (video_cache == 0) { - DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(requireActivity(), - Util.getUserAgent(requireActivity(), userAgent), null); - videoSource = new ProgressiveMediaSource.Factory(dataSourceFactory) - .createMediaSource(uri); + if (attachment.peertubeId != null) { + //It's a peertube video, we are fetching data + TimelinesVM timelinesVM = new ViewModelProvider(requireActivity()).get(TimelinesVM.class); + String finalType = type; + timelinesVM.getPeertubeVideo(attachment.peertubeHost, attachment.peertubeId).observe(requireActivity(), video -> { + if (video != null && video.files != null && video.files.size() > 0) { + loadVideo(video.files.get(0).fileUrl, finalType); + } + }); } else { - CacheDataSourceFactory cacheDataSourceFactory = new CacheDataSourceFactory(requireActivity()); - videoSource = new ProgressiveMediaSource.Factory(cacheDataSourceFactory) - .createMediaSource(uri); + loadVideo(url, type); } - player = new SimpleExoPlayer.Builder(requireActivity()).build(); - if (type.equalsIgnoreCase("gifv")) - player.setRepeatMode(Player.REPEAT_MODE_ONE); - binding.mediaVideo.setPlayer(player); - binding.loader.setVisibility(View.GONE); - binding.mediaPicture.setVisibility(View.GONE); - player.prepare(videoSource); - player.setPlayWhenReady(true); break; case "web": binding.loader.setVisibility(View.GONE); @@ -261,6 +249,35 @@ public class FragmentMedia extends Fragment { } } + private void loadVideo(String url, String type) { + binding.pbarInf.setIndeterminate(false); + binding.pbarInf.setScaleY(3f); + binding.mediaVideo.setVisibility(View.VISIBLE); + Uri uri = Uri.parse(url); + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); + String userAgent = sharedpreferences.getString(getString(R.string.SET_CUSTOM_USER_AGENT), Helper.USER_AGENT); + int video_cache = sharedpreferences.getInt(getString(R.string.SET_VIDEO_CACHE), Helper.DEFAULT_VIDEO_CACHE_MB); + ProgressiveMediaSource videoSource; + if (video_cache == 0) { + DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(requireActivity(), + Util.getUserAgent(requireActivity(), userAgent), null); + videoSource = new ProgressiveMediaSource.Factory(dataSourceFactory) + .createMediaSource(uri); + } else { + CacheDataSourceFactory cacheDataSourceFactory = new CacheDataSourceFactory(requireActivity()); + videoSource = new ProgressiveMediaSource.Factory(cacheDataSourceFactory) + .createMediaSource(uri); + } + player = new SimpleExoPlayer.Builder(requireActivity()).build(); + if (type.equalsIgnoreCase("gifv")) + player.setRepeatMode(Player.REPEAT_MODE_ONE); + binding.mediaVideo.setPlayer(player); + binding.loader.setVisibility(View.GONE); + binding.mediaPicture.setVisibility(View.GONE); + player.prepare(videoSource); + player.setPlayWhenReady(true); + } + @Override public void onCreate(Bundle saveInstance) { super.onCreate(saveInstance); diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java index f0243dff..368467da 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java @@ -71,6 +71,7 @@ public class TimelinesVM extends AndroidViewModel { private MutableLiveData> statusDraftListMutableLiveData; private MutableLiveData statusMutableLiveData; private MutableLiveData statusesMutableLiveData; + private MutableLiveData peertubeVideoMutableLiveData; private MutableLiveData conversationListMutableLiveData; private MutableLiveData mastodonListMutableLiveData; private MutableLiveData> mastodonListListMutableLiveData; @@ -246,6 +247,34 @@ public class TimelinesVM extends AndroidViewModel { } + /** + * Returns details for a peertube video + * + * @return {@link LiveData} containing a {@link PeertubeVideo.Video} + */ + public LiveData getPeertubeVideo(@NonNull String instance, String id) { + MastodonTimelinesService mastodonTimelinesService = initInstanceOnly(instance); + peertubeVideoMutableLiveData = new MutableLiveData<>(); + new Thread(() -> { + Call publicTlCall = mastodonTimelinesService.getPeertubeVideo(id); + PeertubeVideo.Video peertubeVideo = null; + try { + Response videoResponse = publicTlCall.execute(); + if (videoResponse.isSuccessful()) { + peertubeVideo = videoResponse.body(); + } + } catch (Exception e) { + e.printStackTrace(); + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + PeertubeVideo.Video finalPeertubeVideo = peertubeVideo; + Runnable myRunnable = () -> peertubeVideoMutableLiveData.setValue(finalPeertubeVideo); + mainHandler.post(myRunnable); + }).start(); + return peertubeVideoMutableLiveData; + } + + /** * View public statuses containing the given hashtag. *