diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index b8941670f..9977ef56c 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -36,7 +36,6 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.VideoStream; -import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; @@ -81,10 +80,13 @@ public class RouterActivity extends AppCompatActivity { protected int selectedPreviously = -1; protected String currentUrl; + protected boolean internalRoute = false; protected final CompositeDisposable disposables = new CompositeDisposable(); private boolean selectionIsDownload = false; + public static final String internalRouteKey = "internalRoute"; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -99,6 +101,8 @@ public class RouterActivity extends AppCompatActivity { } } + internalRoute = getIntent().getBooleanExtra(internalRouteKey, false); + setTheme(ThemeHelper.isLightThemeSelected(this) ? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark); } @@ -383,8 +387,10 @@ public class RouterActivity extends AppCompatActivity { .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(intent -> { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + if(!internalRoute){ + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + } startActivity(intent); finish(); diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java index 0e6321f35..9be272198 100644 --- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java +++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java @@ -15,6 +15,9 @@ import org.schabi.newpipe.util.CommentTextOnTouchListener; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.NavigationHelper; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import de.hdodenhof.circleimageview.CircleImageView; public class CommentsMiniInfoItemHolder extends InfoItemHolder { @@ -28,7 +31,23 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { private static final int commentExpandedLines = 1000; private String commentText; - private boolean containsLinks = false; + private String streamUrl; + + private static final Pattern pattern = Pattern.compile("(\\d+:)?(\\d+)?:(\\d+)"); + + private final Linkify.TransformFilter timestampLink = new Linkify.TransformFilter() { + @Override + public String transformUrl(Matcher match, String url) { + int timestamp = 0; + String hours = match.group(1); + String minutes = match.group(2); + String seconds = match.group(3); + if(hours != null) timestamp += (Integer.parseInt(hours.replace(":", ""))*3600); + if(minutes != null) timestamp += (Integer.parseInt(minutes.replace(":", ""))*60); + if(seconds != null) timestamp += (Integer.parseInt(seconds)); + return streamUrl + url.replace(match.group(0), "&t=" + String.valueOf(timestamp)); + } + }; CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { super(infoItemBuilder, layoutId, parent); @@ -70,10 +89,12 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { } }); + streamUrl = item.getUrl(); + itemContentView.setMaxLines(commentDefaultLines); commentText = item.getCommentText(); itemContentView.setText(commentText); - containsLinks = linkify(); + linkify(); itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE); if(itemContentView.getLineCount() == 0){ @@ -100,7 +121,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { int endOfLastLine = itemContentView.getLayout().getLineEnd(commentDefaultLines - 1); String newVal = itemContentView.getText().subSequence(0, endOfLastLine - 3) + "..."; itemContentView.setText(newVal); - if(containsLinks) linkify(); + linkify(); } } @@ -115,12 +136,12 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { private void expand() { itemContentView.setMaxLines(commentExpandedLines); itemContentView.setText(commentText); - if(containsLinks) linkify(); + linkify(); } - private boolean linkify(){ - boolean res = Linkify.addLinks(itemContentView, Linkify.WEB_URLS); + private void linkify(){ + Linkify.addLinks(itemContentView, Linkify.WEB_URLS); + Linkify.addLinks(itemContentView, pattern, null, null, timestampLink); itemContentView.setMovementMethod(null); - return res; } } diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java index 2934145ff..570b5f8b2 100644 --- a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java +++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java @@ -1,18 +1,38 @@ package org.schabi.newpipe.util; +import android.content.Context; import android.text.Layout; import android.text.Selection; import android.text.Spannable; import android.text.Spanned; import android.text.style.ClickableSpan; +import android.text.style.URLSpan; import android.view.MotionEvent; import android.view.View; import android.widget.TextView; +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.SinglePlayQueue; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.reactivex.Single; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + public class CommentTextOnTouchListener implements View.OnTouchListener { public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener(); + private static final Pattern timestampPattern = Pattern.compile(".*&t=(\\d+)"); + @Override public boolean onTouch(View v, MotionEvent event) { if(!(v instanceof TextView)){ @@ -45,7 +65,11 @@ public class CommentTextOnTouchListener implements View.OnTouchListener { if (link.length != 0) { if (action == MotionEvent.ACTION_UP) { - link[0].onClick(widget); + boolean handled = false; + if(link[0] instanceof URLSpan){ + handled = handleUrl(v.getContext(), (URLSpan) link[0]); + } + if(!handled) link[0].onClick(widget); } else if (action == MotionEvent.ACTION_DOWN) { Selection.setSelection(buffer, buffer.getSpanStart(link[0]), @@ -59,4 +83,46 @@ public class CommentTextOnTouchListener implements View.OnTouchListener { return false; } + + private boolean handleUrl(Context context, URLSpan urlSpan) { + String url = urlSpan.getURL(); + StreamingService service; + StreamingService.LinkType linkType; + try { + service = NewPipe.getServiceByUrl(url); + linkType = service.getLinkTypeByUrl(url); + } catch (ExtractionException e) { + return false; + } + if(linkType == StreamingService.LinkType.NONE){ + return false; + } + Matcher matcher = timestampPattern.matcher(url); + if(linkType == StreamingService.LinkType.STREAM && matcher.matches()){ + int seconds = Integer.parseInt(matcher.group(1)); + return playOnPopup(context, url, service, seconds); + }else{ + NavigationHelper.openRouterActivity(context, url); + return true; + } + } + + private boolean playOnPopup(Context context, String url, StreamingService service, int seconds) { + LinkHandlerFactory factory = service.getStreamLHFactory(); + String cleanUrl = null; + try { + cleanUrl = factory.getUrl(factory.getId(url)); + } catch (ParsingException e) { + return false; + } + Single single = ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false); + single.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(info -> { + PlayQueue playQueue = new SinglePlayQueue((StreamInfo) info); + ((StreamInfo) info).setStartPosition(seconds); + NavigationHelper.enqueueOnPopupPlayer(context, playQueue, true); + }); + return true; + } } diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java index 4b93600ce..98ae3a88a 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -21,6 +21,7 @@ import com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; +import org.schabi.newpipe.RouterActivity; import org.schabi.newpipe.about.AboutActivity; import org.schabi.newpipe.download.DownloadActivity; import org.schabi.newpipe.extractor.NewPipe; @@ -34,11 +35,11 @@ import org.schabi.newpipe.fragments.MainFragment; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.fragments.list.channel.ChannelFragment; import org.schabi.newpipe.fragments.list.comments.CommentsFragment; -import org.schabi.newpipe.local.bookmark.BookmarkFragment; -import org.schabi.newpipe.local.feed.FeedFragment; import org.schabi.newpipe.fragments.list.kiosk.KioskFragment; import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment; import org.schabi.newpipe.fragments.list.search.SearchFragment; +import org.schabi.newpipe.local.bookmark.BookmarkFragment; +import org.schabi.newpipe.local.feed.FeedFragment; import org.schabi.newpipe.local.history.StatisticsPlaylistFragment; import org.schabi.newpipe.local.playlist.LocalPlaylistFragment; import org.schabi.newpipe.local.subscription.SubscriptionFragment; @@ -422,6 +423,13 @@ public class NavigationHelper { context.startActivity(mIntent); } + public static void openRouterActivity(Context context, String url) { + Intent mIntent = new Intent(context, RouterActivity.class); + mIntent.setData(Uri.parse(url)); + mIntent.putExtra(RouterActivity.internalRouteKey, true); + context.startActivity(mIntent); + } + public static void openAbout(Context context) { Intent intent = new Intent(context, AboutActivity.class); context.startActivity(intent);