handling timestamp links in comments
This commit is contained in:
parent
c0004e988a
commit
67d2b9131e
4 changed files with 114 additions and 13 deletions
|
@ -36,7 +36,6 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
import org.schabi.newpipe.extractor.stream.StreamInfo;
|
||||||
import org.schabi.newpipe.extractor.stream.VideoStream;
|
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.ChannelPlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlayQueue;
|
||||||
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
|
||||||
|
@ -81,10 +80,13 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
protected int selectedPreviously = -1;
|
protected int selectedPreviously = -1;
|
||||||
|
|
||||||
protected String currentUrl;
|
protected String currentUrl;
|
||||||
|
protected boolean internalRoute = false;
|
||||||
protected final CompositeDisposable disposables = new CompositeDisposable();
|
protected final CompositeDisposable disposables = new CompositeDisposable();
|
||||||
|
|
||||||
private boolean selectionIsDownload = false;
|
private boolean selectionIsDownload = false;
|
||||||
|
|
||||||
|
public static final String internalRouteKey = "internalRoute";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
@ -99,6 +101,8 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internalRoute = getIntent().getBooleanExtra(internalRouteKey, false);
|
||||||
|
|
||||||
setTheme(ThemeHelper.isLightThemeSelected(this)
|
setTheme(ThemeHelper.isLightThemeSelected(this)
|
||||||
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
|
? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark);
|
||||||
}
|
}
|
||||||
|
@ -383,8 +387,10 @@ public class RouterActivity extends AppCompatActivity {
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe(intent -> {
|
.subscribe(intent -> {
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
if(!internalRoute){
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||||
|
}
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
|
|
||||||
finish();
|
finish();
|
||||||
|
|
|
@ -15,6 +15,9 @@ import org.schabi.newpipe.util.CommentTextOnTouchListener;
|
||||||
import org.schabi.newpipe.util.ImageDisplayConstants;
|
import org.schabi.newpipe.util.ImageDisplayConstants;
|
||||||
import org.schabi.newpipe.util.NavigationHelper;
|
import org.schabi.newpipe.util.NavigationHelper;
|
||||||
|
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import de.hdodenhof.circleimageview.CircleImageView;
|
import de.hdodenhof.circleimageview.CircleImageView;
|
||||||
|
|
||||||
public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
|
@ -28,7 +31,23 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
private static final int commentExpandedLines = 1000;
|
private static final int commentExpandedLines = 1000;
|
||||||
|
|
||||||
private String commentText;
|
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) {
|
CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) {
|
||||||
super(infoItemBuilder, layoutId, parent);
|
super(infoItemBuilder, layoutId, parent);
|
||||||
|
@ -70,10 +89,12 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
streamUrl = item.getUrl();
|
||||||
|
|
||||||
itemContentView.setMaxLines(commentDefaultLines);
|
itemContentView.setMaxLines(commentDefaultLines);
|
||||||
commentText = item.getCommentText();
|
commentText = item.getCommentText();
|
||||||
itemContentView.setText(commentText);
|
itemContentView.setText(commentText);
|
||||||
containsLinks = linkify();
|
linkify();
|
||||||
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
|
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
|
||||||
|
|
||||||
if(itemContentView.getLineCount() == 0){
|
if(itemContentView.getLineCount() == 0){
|
||||||
|
@ -100,7 +121,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
int endOfLastLine = itemContentView.getLayout().getLineEnd(commentDefaultLines - 1);
|
int endOfLastLine = itemContentView.getLayout().getLineEnd(commentDefaultLines - 1);
|
||||||
String newVal = itemContentView.getText().subSequence(0, endOfLastLine - 3) + "...";
|
String newVal = itemContentView.getText().subSequence(0, endOfLastLine - 3) + "...";
|
||||||
itemContentView.setText(newVal);
|
itemContentView.setText(newVal);
|
||||||
if(containsLinks) linkify();
|
linkify();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,12 +136,12 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
||||||
private void expand() {
|
private void expand() {
|
||||||
itemContentView.setMaxLines(commentExpandedLines);
|
itemContentView.setMaxLines(commentExpandedLines);
|
||||||
itemContentView.setText(commentText);
|
itemContentView.setText(commentText);
|
||||||
if(containsLinks) linkify();
|
linkify();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean linkify(){
|
private void linkify(){
|
||||||
boolean res = Linkify.addLinks(itemContentView, Linkify.WEB_URLS);
|
Linkify.addLinks(itemContentView, Linkify.WEB_URLS);
|
||||||
|
Linkify.addLinks(itemContentView, pattern, null, null, timestampLink);
|
||||||
itemContentView.setMovementMethod(null);
|
itemContentView.setMovementMethod(null);
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,38 @@
|
||||||
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;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.style.ClickableSpan;
|
import android.text.style.ClickableSpan;
|
||||||
|
import android.text.style.URLSpan;
|
||||||
import android.view.MotionEvent;
|
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.Single;
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers;
|
||||||
|
import io.reactivex.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 timestampPattern = Pattern.compile(".*&t=(\\d+)");
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onTouch(View v, MotionEvent event) {
|
public boolean onTouch(View v, MotionEvent event) {
|
||||||
if(!(v instanceof TextView)){
|
if(!(v instanceof TextView)){
|
||||||
|
@ -45,7 +65,11 @@ public class CommentTextOnTouchListener implements View.OnTouchListener {
|
||||||
|
|
||||||
if (link.length != 0) {
|
if (link.length != 0) {
|
||||||
if (action == MotionEvent.ACTION_UP) {
|
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) {
|
} else if (action == MotionEvent.ACTION_DOWN) {
|
||||||
Selection.setSelection(buffer,
|
Selection.setSelection(buffer,
|
||||||
buffer.getSpanStart(link[0]),
|
buffer.getSpanStart(link[0]),
|
||||||
|
@ -59,4 +83,46 @@ public class CommentTextOnTouchListener implements View.OnTouchListener {
|
||||||
|
|
||||||
return false;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import com.nostra13.universalimageloader.core.ImageLoader;
|
||||||
|
|
||||||
import org.schabi.newpipe.MainActivity;
|
import org.schabi.newpipe.MainActivity;
|
||||||
import org.schabi.newpipe.R;
|
import org.schabi.newpipe.R;
|
||||||
|
import org.schabi.newpipe.RouterActivity;
|
||||||
import org.schabi.newpipe.about.AboutActivity;
|
import org.schabi.newpipe.about.AboutActivity;
|
||||||
import org.schabi.newpipe.download.DownloadActivity;
|
import org.schabi.newpipe.download.DownloadActivity;
|
||||||
import org.schabi.newpipe.extractor.NewPipe;
|
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.detail.VideoDetailFragment;
|
||||||
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
|
import org.schabi.newpipe.fragments.list.channel.ChannelFragment;
|
||||||
import org.schabi.newpipe.fragments.list.comments.CommentsFragment;
|
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.kiosk.KioskFragment;
|
||||||
import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment;
|
import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment;
|
||||||
import org.schabi.newpipe.fragments.list.search.SearchFragment;
|
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.history.StatisticsPlaylistFragment;
|
||||||
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
|
import org.schabi.newpipe.local.playlist.LocalPlaylistFragment;
|
||||||
import org.schabi.newpipe.local.subscription.SubscriptionFragment;
|
import org.schabi.newpipe.local.subscription.SubscriptionFragment;
|
||||||
|
@ -422,6 +423,13 @@ public class NavigationHelper {
|
||||||
context.startActivity(mIntent);
|
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) {
|
public static void openAbout(Context context) {
|
||||||
Intent intent = new Intent(context, AboutActivity.class);
|
Intent intent = new Intent(context, AboutActivity.class);
|
||||||
context.startActivity(intent);
|
context.startActivity(intent);
|
||||||
|
|
Loading…
Reference in a new issue