1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2024-12-23 16:40:32 +00:00

Open recognized timestamps in the description of contents in the popup player

This commit adds support of opening recognized timestamps in the popup
player instead of starting an intent which opens the YouTube website with
the video timestamp.
This commit is contained in:
TiA4f8R 2021-02-01 16:40:54 +01:00
parent 06d10cf9aa
commit 4031777606
No known key found for this signature in database
GPG Key ID: E6D3E7F5949450DD
3 changed files with 118 additions and 69 deletions

View File

@ -1,6 +1,5 @@
package org.schabi.newpipe.util; package org.schabi.newpipe.util;
import android.content.Context;
import android.text.Layout; import android.text.Layout;
import android.text.Selection; import android.text.Selection;
import android.text.Spannable; import android.text.Spannable;
@ -11,27 +10,9 @@ import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.widget.TextView; 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.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
public class CommentTextOnTouchListener implements View.OnTouchListener { public class CommentTextOnTouchListener implements View.OnTouchListener {
public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener(); public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener();
private static final Pattern TIMESTAMP_PATTERN = Pattern.compile("(.*)#timestamp=(\\d+)");
@Override @Override
public boolean onTouch(final View v, final MotionEvent event) { public boolean onTouch(final View v, final MotionEvent event) {
if (!(v instanceof TextView)) { if (!(v instanceof TextView)) {
@ -66,7 +47,8 @@ public class CommentTextOnTouchListener implements View.OnTouchListener {
if (action == MotionEvent.ACTION_UP) { if (action == MotionEvent.ACTION_UP) {
boolean handled = false; boolean handled = false;
if (link[0] instanceof URLSpan) { if (link[0] instanceof URLSpan) {
handled = handleUrl(v.getContext(), (URLSpan) link[0]); handled = URLHandler.handleUrl(v.getContext(),
((URLSpan) link[0]).getURL(), 1);
} }
if (!handled) { if (!handled) {
ShareUtils.openUrlInBrowser(v.getContext(), ShareUtils.openUrlInBrowser(v.getContext(),
@ -83,52 +65,4 @@ public class CommentTextOnTouchListener implements View.OnTouchListener {
} }
return false; return false;
} }
private boolean handleUrl(final Context context, final URLSpan urlSpan) {
String url = urlSpan.getURL();
int seconds = -1;
final Matcher matcher = TIMESTAMP_PATTERN.matcher(url);
if (matcher.matches()) {
url = matcher.group(1);
seconds = Integer.parseInt(matcher.group(2));
}
final StreamingService service;
final StreamingService.LinkType linkType;
try {
service = NewPipe.getServiceByUrl(url);
linkType = service.getLinkTypeByUrl(url);
} catch (final ExtractionException e) {
return false;
}
if (linkType == StreamingService.LinkType.NONE) {
return false;
}
if (linkType == StreamingService.LinkType.STREAM && seconds != -1) {
return playOnPopup(context, url, service, seconds);
} else {
NavigationHelper.openRouterActivity(context, url);
return true;
}
}
private boolean playOnPopup(final Context context, final String url,
final StreamingService service, final int seconds) {
final LinkHandlerFactory factory = service.getStreamLHFactory();
final String cleanUrl;
try {
cleanUrl = factory.getUrl(factory.getId(url));
} catch (final ParsingException e) {
return false;
}
final Single single
= ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false);
single.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(info -> {
final PlayQueue playQueue
= new SinglePlayQueue((StreamInfo) info, seconds * 1000);
NavigationHelper.playOnPopupPlayer(context, playQueue, false);
});
return true;
}
} }

View File

@ -115,7 +115,9 @@ public final class TextLinkifier {
for (final URLSpan span : urls) { for (final URLSpan span : urls) {
final ClickableSpan clickableSpan = new ClickableSpan() { final ClickableSpan clickableSpan = new ClickableSpan() {
public void onClick(@NonNull final View view) { public void onClick(@NonNull final View view) {
ShareUtils.openUrlInBrowser(context, span.getURL(), false); if (!URLHandler.handleUrl(context, span.getURL(), 0)) {
ShareUtils.openUrlInBrowser(context, span.getURL(), false);
}
} }
}; };

View File

@ -0,0 +1,113 @@
package org.schabi.newpipe.util;
import android.content.Context;
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.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
public final class URLHandler {
private URLHandler() {
}
/**
* Check if an URL can be handled in NewPipe.
* <p>
* This method will check if the provided url can be handled in NewPipe or not. If this is a
* service URL with a timestamp, the popup player will be opened.
* <p>
* The timestamp param accepts two integers, corresponding to two timestamps types:
* 0 for {@code &t=} (used for timestamps in descriptions),
* 1 for {@code #timestamp=} (used for timestamps in comments).
* Any other value of this integer will return false.
*
* @param context the context to be used
* @param url the URL to check if it can be handled
* @param timestampType the type of timestamp
* @return true if the URL can be handled by NewPipe, false if it cannot
*/
public static boolean handleUrl(final Context context, final String url, final int timestampType) {
String matchedUrl = "";
int seconds = -1;
final Pattern TIMESTAMP_PATTERN;
if (timestampType == 0) {
TIMESTAMP_PATTERN = Pattern.compile("(.*)&t=(\\d+)");
} else if (timestampType == 1) {
TIMESTAMP_PATTERN = Pattern.compile("(.*)#timestamp=(\\d+)");
} else {
return false;
}
final Matcher matcher = TIMESTAMP_PATTERN.matcher(url);
if (matcher.matches()) {
matchedUrl = matcher.group(1);
seconds = Integer.parseInt(matcher.group(2));
}
final StreamingService service;
final StreamingService.LinkType linkType;
try {
service = NewPipe.getServiceByUrl(matchedUrl);
linkType = service.getLinkTypeByUrl(matchedUrl);
} catch (final ExtractionException e) {
return false;
}
if (linkType == StreamingService.LinkType.NONE) {
return false;
}
if (linkType == StreamingService.LinkType.STREAM && seconds != -1) {
return playOnPopup(context, matchedUrl, service, seconds);
} else {
NavigationHelper.openRouterActivity(context, matchedUrl);
return true;
}
}
/**
* Play a content in the floating player.
*
* @param context the context to be used
* @param url the URL of the content
* @param service the service of the content
* @param seconds the position in seconds at which the floating player will start
* @return true if the playback of the content has successfully started or false if not
*/
private static boolean playOnPopup(final Context context, final String url,
final StreamingService service, final int seconds) {
final LinkHandlerFactory factory = service.getStreamLHFactory();
final String cleanUrl;
try {
cleanUrl = factory.getUrl(factory.getId(url));
} catch (final ParsingException e) {
return false;
}
final Single single
= ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false);
single.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(info -> {
final PlayQueue playQueue
= new SinglePlayQueue((StreamInfo) info, seconds * 1000);
NavigationHelper.playOnPopupPlayer(context, playQueue, false);
});
return true;
}
}