show channelvideos

This commit is contained in:
Christian Schabesberger 2016-08-01 21:50:41 +02:00
parent c03b106118
commit 4164195fae
12 changed files with 334 additions and 109 deletions

View file

@ -7,12 +7,13 @@ import android.os.Bundle;
import android.os.Handler;
import android.support.design.widget.CollapsingToolbarLayout;
import android.support.design.widget.FloatingActionButton;
import android.support.design.widget.Snackbar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.Toast;
@ -25,9 +26,11 @@ import org.schabi.newpipe.extractor.ChannelInfo;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamPreviewInfo;
import org.schabi.newpipe.extractor.StreamingService;
import java.io.IOException;
import java.util.ArrayList;
public class ChannelActivity extends AppCompatActivity {
@ -105,6 +108,16 @@ public class ChannelActivity extends AppCompatActivity {
updateUi(info);
}
});
// look for non critical errors during extraction
if(info != null &&
!info.errors.isEmpty()) {
Log.e(TAG, "OCCURRED ERRORS DURING EXTRACTION:");
for (Throwable e : info.errors) {
e.printStackTrace();
Log.e(TAG, "------");
}
}
} catch(IOException ioe) {
postNewErrorToast(h, R.string.network_error);
ioe.printStackTrace();
@ -124,10 +137,12 @@ public class ChannelActivity extends AppCompatActivity {
private void updateUi(final ChannelInfo info) {
VideoInfoItemViewCreator viCreator =
new VideoInfoItemViewCreator(LayoutInflater.from(this), this, rootView);
CollapsingToolbarLayout ctl = (CollapsingToolbarLayout) findViewById(R.id.channel_toolbar_layout);
ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
ImageView channelBanner = (ImageView) findViewById(R.id.channel_banner_image);
View channelContentView = (View) findViewById(R.id.channel_content_view);
View channelContentView = findViewById(R.id.channel_content_view);
FloatingActionButton feedButton = (FloatingActionButton) findViewById(R.id.channel_rss_fab);
ImageView avatarView = (ImageView) findViewById(R.id.channel_avatar_view);
ImageView haloView = (ImageView) findViewById(R.id.channel_avatar_halo);
@ -163,6 +178,19 @@ public class ChannelActivity extends AppCompatActivity {
} else {
feedButton.setVisibility(View.GONE);
}
initVideos(info, viCreator);
}
private void initVideos(final ChannelInfo info, VideoInfoItemViewCreator viCreator) {
LinearLayout streamLayout = (LinearLayout) findViewById(R.id.channel_streams_view);
ArrayList<StreamPreviewInfo> streamsList = new ArrayList<>(info.related_streams);
for(final StreamPreviewInfo streamInfo : streamsList) {
View itemView = viCreator.getViewFromVideoInfoItem(null, streamLayout, streamInfo);
itemView = viCreator.setupView(itemView, streamInfo);
streamLayout.addView(itemView);
}
}
private void postNewErrorToast(Handler h, final int stringResource) {

View file

@ -0,0 +1,65 @@
package org.schabi.newpipe;
import android.app.Activity;
import android.graphics.Bitmap;
import android.view.View;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import org.schabi.newpipe.ErrorActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ServiceList;
/**
* Created by Christian Schabesberger on 01.08.16.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* VideoInfoItemViewCreator.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class ImageErrorLoadingListener implements ImageLoadingListener {
private int serviceId = -1;
private Activity activity = null;
private View rootView = null;
public ImageErrorLoadingListener(Activity activity, View rootView, int serviceId) {
this.activity = activity;
this.serviceId= serviceId;
this.rootView = rootView;
}
@Override
public void onLoadingStarted(String imageUri, View view) {}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
ErrorActivity.reportError(activity,
failReason.getCause(), null, rootView,
ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
ServiceList.getNameOfService(serviceId), imageUri,
R.string.could_not_load_image));
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
}
@Override
public void onLoadingCancelled(String imageUri, View view) {}
}

View file

@ -1,6 +1,10 @@
package org.schabi.newpipe;
import android.app.Activity;
import android.content.Intent;
import android.content.res.TypedArray;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
@ -33,15 +37,20 @@ import org.schabi.newpipe.extractor.StreamPreviewInfo;
*/
public class VideoInfoItemViewCreator {
private View rootView = null; //root view of the activty
private Activity activity = null;
private final LayoutInflater inflater;
private ImageLoader imageLoader = ImageLoader.getInstance();
private DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().cacheInMemory(true).build();
public VideoInfoItemViewCreator(LayoutInflater inflater) {
public VideoInfoItemViewCreator(LayoutInflater inflater, Activity a, View rootView) {
this.inflater = inflater;
activity = a;
this.rootView = rootView;
}
public View getViewFromVideoInfoItem(View convertView, ViewGroup parent, StreamPreviewInfo info) {
public View getViewFromVideoInfoItem(View convertView, ViewGroup parent, final StreamPreviewInfo info) {
ViewHolder holder;
// generate holder
@ -59,15 +68,7 @@ public class VideoInfoItemViewCreator {
holder = (ViewHolder) convertView.getTag();
}
// fill with information
/*
if(info.thumbnail == null) {
holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
} else {
holder.itemThumbnailView.setImageBitmap(info.thumbnail);
}
*/
// fill holder with information
holder.itemVideoTitleView.setText(info.title);
if(info.uploader != null && !info.uploader.isEmpty()) {
holder.itemUploaderView.setText(info.uploader);
@ -100,6 +101,39 @@ public class VideoInfoItemViewCreator {
return convertView;
}
public View setupView(View convertView, final StreamPreviewInfo info) {
convertView.setClickable(true);
convertView.setFocusable(true);
int[] attrs = new int[]{R.attr.selectableItemBackground};
TypedArray typedArray = activity.obtainStyledAttributes(attrs);
int backgroundResource = typedArray.getResourceId(0, 0);
convertView.setBackgroundResource(backgroundResource);
typedArray.recycle();
convertView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
Intent detailIntent = new Intent(activity, VideoItemDetailActivity.class);
detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, info.webpage_url);
detailIntent.putExtra(
VideoItemDetailFragment.STREAMING_SERVICE, info.service_id);
activity.startActivity(detailIntent);
return true;
}
return false;
}
});
ImageView rthumb = (ImageView) convertView.findViewById(R.id.itemThumbnailView);
imageLoader.displayImage(info.thumbnail_url, rthumb,
displayImageOptions, new ImageErrorLoadingListener(activity, rootView, info.service_id));
return convertView;
}
private class ViewHolder {
public ImageView itemThumbnailView;
public TextView itemVideoTitleView, itemUploaderView, itemDurationView, itemUploadDateView, itemViewCountView;

View file

@ -262,31 +262,13 @@ public class VideoItemDetailFragment extends Fragment {
}
}
private class ThumbnailLoadingListener implements ImageLoadingListener {
@Override
public void onLoadingStarted(String imageUri, View view) {}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
if(getContext() != null) {
Toast.makeText(VideoItemDetailFragment.this.getActivity(),
R.string.could_not_load_thumbnails, Toast.LENGTH_LONG).show();
}
failReason.getCause().printStackTrace();
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {}
@Override
public void onLoadingCancelled(String imageUri, View view) {}
}
private void updateInfo(final StreamInfo info) {
try {
Context c = getContext();
VideoInfoItemViewCreator videoItemViewCreator =
new VideoInfoItemViewCreator(LayoutInflater.from(getActivity()));
new VideoInfoItemViewCreator(LayoutInflater.from(getActivity()),
getActivity(), rootView);
RelativeLayout textContentLayout =
(RelativeLayout) activity.findViewById(R.id.detailTextContentLayout);
@ -422,7 +404,7 @@ public class VideoItemDetailFragment extends Fragment {
});
textContentLayout.setVisibility(View.VISIBLE);
if(info.related_videos != null && !info.related_videos.isEmpty()) {
if(info.related_streams != null && !info.related_streams.isEmpty()) {
initSimilarVideos(info, videoItemViewCreator);
} else {
activity.findViewById(R.id.detailSimilarTitle).setVisibility(View.GONE);
@ -487,10 +469,6 @@ public class VideoItemDetailFragment extends Fragment {
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
Toast.makeText(VideoItemDetailFragment.this.getActivity(),
R.string.could_not_load_thumbnails, Toast.LENGTH_LONG).show();
failReason.getCause().printStackTrace();
ErrorActivity.reportError(getActivity(),
failReason.getCause(), null, rootView,
ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
@ -512,11 +490,13 @@ public class VideoItemDetailFragment extends Fragment {
}
if(info.uploader_thumbnail_url != null && !info.uploader_thumbnail_url.isEmpty()) {
imageLoader.displayImage(info.uploader_thumbnail_url,
uploaderThumb, displayImageOptions, new ThumbnailLoadingListener());
uploaderThumb, displayImageOptions,
new ImageErrorLoadingListener(activity, rootView, info.service_id));
}
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty() && info.next_video != null) {
imageLoader.displayImage(info.next_video.thumbnail_url,
nextVideoThumb, displayImageOptions, new ThumbnailLoadingListener());
nextVideoThumb, displayImageOptions,
new ImageErrorLoadingListener(activity, rootView, info.service_id));
}
}
@ -710,38 +690,15 @@ public class VideoItemDetailFragment extends Fragment {
private void initSimilarVideos(final StreamInfo info, VideoInfoItemViewCreator videoItemViewCreator) {
LinearLayout similarLayout = (LinearLayout) activity.findViewById(R.id.similarVideosView);
ArrayList<StreamPreviewInfo> similar = new ArrayList<>(info.related_videos);
for (final StreamPreviewInfo item : similar) {
View similarView = videoItemViewCreator
ArrayList<StreamPreviewInfo> similarStreamsList = new ArrayList<>(info.related_streams);
for (final StreamPreviewInfo item : similarStreamsList) {
View itemView = videoItemViewCreator
.getViewFromVideoInfoItem(null, similarLayout, item);
similarView.setClickable(true);
similarView.setFocusable(true);
int[] attrs = new int[]{R.attr.selectableItemBackground};
TypedArray typedArray = activity.obtainStyledAttributes(attrs);
int backgroundResource = typedArray.getResourceId(0, 0);
similarView.setBackgroundResource(backgroundResource);
typedArray.recycle();
itemView = videoItemViewCreator.setupView(itemView, item);
similarView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
Intent detailIntent = new Intent(activity, VideoItemDetailActivity.class);
detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, item.webpage_url);
detailIntent.putExtra(
VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
startActivity(detailIntent);
return true;
}
return false;
}
});
similarLayout.addView(similarView);
ImageView rthumb = (ImageView)similarView.findViewById(R.id.itemThumbnailView);
imageLoader.displayImage(item.thumbnail_url, rthumb,
displayImageOptions, new ThumbnailLoadingListener());
similarLayout.addView(itemView);
}
}

View file

@ -202,8 +202,9 @@ public class VideoItemListFragment extends ListFragment {
setListShown(true);
updateList(result.resultList);
if(!result.suggestion.isEmpty()) {
Toast.makeText(getActivity(),
String.format(getString(R.string.did_you_mean), result.suggestion),
String.format(getActivity().getString(R.string.did_you_mean), result.suggestion),
Toast.LENGTH_LONG).show();
}
}

View file

@ -1,5 +1,6 @@
package org.schabi.newpipe;
import android.app.Activity;
import android.content.Context;
import android.support.v4.content.ContextCompat;
import android.view.LayoutInflater;
@ -39,12 +40,12 @@ class VideoListAdapter extends BaseAdapter {
private Vector<StreamPreviewInfo> videoList = new Vector<>();
private final ListView listView;
public VideoListAdapter(Context context, VideoItemListFragment videoListFragment) {
viewCreator = new VideoInfoItemViewCreator(LayoutInflater.from(context));
public VideoListAdapter(Activity activity, VideoItemListFragment videoListFragment) {
viewCreator = new VideoInfoItemViewCreator(LayoutInflater.from(activity), activity, null);
this.listView = videoListFragment.getListView();
this.listView.setDivider(null);
this.listView.setDividerHeight(0);
this.context = context;
this.context = activity;
}
public void addVideoList(List<StreamPreviewInfo> videos) {

View file

@ -39,11 +39,15 @@ public abstract class ChannelExtractor {
public String getUrl() { return url; }
public UrlIdHandler getUrlIdHandler() { return urlIdHandler; }
public Downloader getDownloader() { return downloader; }
public StreamPreviewInfoCollector getStreamPreviewInfoCollector() {
return previewInfoCollector;
}
public abstract String getChannelName() throws ParsingException;
public abstract String getAvatarUrl() throws ParsingException;
public abstract String getBannerUrl() throws ParsingException;
public abstract String getFeedUrl() throws ParsingException;
public abstract StreamPreviewInfoCollector getStreams() throws ParsingException;
public int getServiceId() {
return serviceId;
}

View file

@ -55,6 +55,13 @@ public class ChannelInfo {
} catch(Exception e) {
info.errors.add(e);
}
try {
StreamPreviewInfoCollector c = extractor.getStreams();
info.related_streams = c.getItemList();
info.errors.addAll(c.getErrors());
} catch(Exception e) {
info.errors.add(e);
}
return info;
}
@ -64,6 +71,7 @@ public class ChannelInfo {
public String avatar_url = "";
public String banner_url = "";
public String feed_url = "";
public List<StreamPreviewInfo> related_streams = null;
public List<Throwable> errors = new Vector<>();
}

View file

@ -253,7 +253,7 @@ public class StreamInfo extends AbstractVideoInfo {
try {
// get related videos
StreamPreviewInfoCollector c = extractor.getRelatedVideos();
streamInfo.related_videos = c.getItemList();
streamInfo.related_streams = c.getItemList();
streamInfo.errors.addAll(c.getErrors());
} catch(Exception e) {
streamInfo.addException(e);
@ -281,7 +281,7 @@ public class StreamInfo extends AbstractVideoInfo {
public int dislike_count = -1;
public String average_rating = "";
public StreamPreviewInfo next_video = null;
public List<StreamPreviewInfo> related_videos = null;
public List<StreamPreviewInfo> related_streams = null;
//in seconds. some metadata is not passed using a StreamInfo object!
public int start_position = 0;

View file

@ -18,11 +18,14 @@ import java.io.StringReader;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.schabi.newpipe.extractor.AbstractVideoInfo;
import org.schabi.newpipe.extractor.ChannelExtractor;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.StreamPreviewInfoCollector;
import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
import org.schabi.newpipe.extractor.UrlIdHandler;
@ -56,18 +59,24 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
private Downloader downloader;
private final Document doc;
private final String siteUrl;
private final String channelUrl;
private String vUrl ="";
public YoutubeChannelExtractor(UrlIdHandler urlIdHandler, String url, Downloader dl, int serviceId)
throws ExtractionException, IOException {
super(urlIdHandler, url, dl, serviceId);
siteUrl = urlIdHandler.cleanUrl(url);
Log.d(TAG, siteUrl);
channelUrl = urlIdHandler.cleanUrl(url) ; //+ "/video?veiw=0&flow=list&sort=dd";
downloader = dl;
String pageContent = downloader.download(url);
doc = Jsoup.parse(pageContent, url);
// we first need to get the user url. Otherwise we can't find videos
String channelPageContent = downloader.download(channelUrl);
Document channelDoc = Jsoup.parse(channelPageContent, channelUrl);
String userUrl = getUserUrl(channelDoc);
vUrl = userUrl + "/videos?veiw=0&flow=list&sort=dd";
String pageContent = downloader.download(vUrl);
doc = Jsoup.parse(pageContent, vUrl);
}
@Override
@ -120,6 +129,132 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
}
}
@Override
public StreamPreviewInfoCollector getStreams() throws ParsingException {
StreamPreviewInfoCollector collector = getStreamPreviewInfoCollector();
Element ul = doc.select("ul[id=\"browse-items-primary\"]").first();
for(final Element li : ul.children()) {
if (li.select("div[class=\"feed-item-dismissable\"]").first() != null) {
collector.commit(new StreamPreviewInfoExtractor() {
@Override
public AbstractVideoInfo.StreamType getStreamType() throws ParsingException {
return AbstractVideoInfo.StreamType.VIDEO_STREAM;
}
@Override
public String getWebPageUrl() throws ParsingException {
try {
Element el = li.select("div[class=\"feed-item-dismissable\"]").first();
Element dl = el.select("h3").first().select("a").first();
return dl.attr("abs:href");
} catch (Exception e) {
throw new ParsingException("Could not get web page url for the video", e);
}
}
@Override
public String getTitle() throws ParsingException {
try {
Element el = li.select("div[class=\"feed-item-dismissable\"]").first();
Element dl = el.select("h3").first().select("a").first();
return dl.text();
} catch (Exception e) {
throw new ParsingException("Could not get title", e);
}
}
@Override
public int getDuration() throws ParsingException {
try {
return YoutubeParsingHelper.parseDurationString(
li.select("span[class=\"video-time\"]").first().text());
} catch(Exception e) {
if(isLiveStream(li)) {
// -1 for no duration
return -1;
} else {
throw new ParsingException("Could not get Duration: " + getTitle(), e);
}
}
}
@Override
public String getUploader() throws ParsingException {
return getChannelName();
}
@Override
public String getUploadDate() throws ParsingException {
try {
return li.select("div[class=\"yt-lockup-meta\"]").first()
.select("li").first()
.text();
} catch(Exception e) {
throw new ParsingException("Could not get uplaod date", e);
}
}
@Override
public long getViewCount() throws ParsingException {
String output;
String input;
try {
input = li.select("div[class=\"yt-lockup-meta\"]").first()
.select("li").get(1)
.text();
} catch (IndexOutOfBoundsException e) {
if(isLiveStream(li)) {
// -1 for no view count
return -1;
} else {
throw new ParsingException(
"Could not parse yt-lockup-meta although available: " + getTitle(), e);
}
}
output = Parser.matchGroup1("([0-9,\\. ]*)", input)
.replace(" ", "")
.replace(".", "")
.replace(",", "");
try {
return Long.parseLong(output);
} catch (NumberFormatException e) {
// if this happens the video probably has no views
if(!input.isEmpty()) {
return 0;
} else {
throw new ParsingException("Could not handle input: " + input, e);
}
}
}
@Override
public String getThumbnailUrl() throws ParsingException {
try {
String url;
Element te = li.select("span[class=\"yt-thumb-clip\"]").first()
.select("img").first();
url = te.attr("abs:src");
// Sometimes youtube sends links to gif files which somehow seem to not exist
// anymore. Items with such gif also offer a secondary image source. So we are going
// to use that if we've caught such an item.
if (url.contains(".gif")) {
url = te.attr("abs:data-thumb");
}
return url;
} catch (Exception e) {
throw new ParsingException("Could not get thumbnail url", e);
}
}
});
}
}
return collector;
}
@Override
public String getFeedUrl() throws ParsingException {
try {
@ -129,8 +264,21 @@ public class YoutubeChannelExtractor extends ChannelExtractor {
}
}
private String getUserUrl() throws ParsingException {
return doc.select("span[class=\"qualified-channel-title-text\"]").first()
private String getUserUrl(Document d) throws ParsingException {
return d.select("span[class=\"qualified-channel-title-text\"]").first()
.select("a").first().attr("abs:href");
}
private boolean isLiveStream(Element item) {
Element bla = item.select("span[class*=\"yt-badge-live\"]").first();
if(bla == null) {
// sometimes livestreams dont have badges but sill are live streams
// if video time is not available we most likly have an offline livestream
if(item.select("span[class*=\"video-time\"]").first() == null) {
return true;
}
}
return bla != null;
}
}

View file

@ -66,8 +66,6 @@ public class YoutubeStreamPreviewInfoExtractor implements StreamPreviewInfoExtra
} else {
throw new ParsingException("Could not get Duration: " + getTitle(), e);
}
}
}