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);
}
}
}

View File

@ -94,32 +94,13 @@
android:layout_gravity="fill_vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:id="@+id/channel_content_view"
android:visibility="gone">
android:visibility="visible">
<RelativeLayout
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Lorem ipsum dolor sit amet, sed in vocent nonumes maluisset. No dicant munere apeirian qui, iisque invenire ius in, sea duis illud dolor ex. Ei cum dolorem conclusionemque, in persius feugait efficiendi est, duo cu nonumy graeco ponderum. Efficiendi interpretaris ei pro. Ea salutandi suavitate qualisque qui.
Cum ad sumo modus, duo et libris posidonium reprehendunt. Ex nam ornatus delectus. Eum quidam repudiandae ne. Te vidit senserit eos, has justo copiosae probatus ne. Ea vim nisl aeterno fabulas, per an legimus laoreet, natum abhorreant sit no. Epicurei cotidieque cu eum, novum tantas at has, ad eam malis reprimique intellegam.
Posse doming consulatu no mel. Eu tale iudico his. Nibh nostrum ea mei, te amet esse consequat mea. Corrumpit iracundia an eam, omnesque suavitate erroribus mel ei. Ea suavitate urbanitas nec, mei te dolore menandri theophrastus, ad quaeque delicata vix.
Has sale mucius menandri eu, pri veniam partem deterruisset ei. In nihil nominavi accusata mea, te sit quis veniam. Nam dissentiet conclusionemque id. Ex novum verterem usu.
Eum fastidii consulatu cu. In nobis iuvaret usu, paulo tincidunt no usu, minim corpora his te. Nam graeco delenit omittam cu. Et sed ipsum fastidii, mea cu altera ullamcorper. Sea id altera menandri deseruisse, munere audire utroque cu pri. Nec solet facilis id. Mucius delectus eu vis, has in augue veniam.
Lorem ipsum dolor sit amet, sed in vocent nonumes maluisset. No dicant munere apeirian qui, iisque invenire ius in, sea duis illud dolor ex. Ei cum dolorem conclusionemque, in persius feugait efficiendi est, duo cu nonumy graeco ponderum. Efficiendi interpretaris ei pro. Ea salutandi suavitate qualisque qui.
Cum ad sumo modus, duo et libris posidonium reprehendunt. Ex nam ornatus delectus. Eum quidam repudiandae ne. Te vidit senserit eos, has justo copiosae probatus ne. Ea vim nisl aeterno fabulas, per an legimus laoreet, natum abhorreant sit no. Epicurei cotidieque cu eum, novum tantas at has, ad eam malis reprimique intellegam.
Posse doming consulatu no mel. Eu tale iudico his. Nibh nostrum ea mei, te amet esse consequat mea. Corrumpit iracundia an eam, omnesque suavitate erroribus mel ei. Ea suavitate urbanitas nec, mei te dolore menandri theophrastus, ad quaeque delicata vix.
Has sale mucius menandri eu, pri veniam partem deterruisset ei. In nihil nominavi accusata mea, te sit quis veniam. Nam dissentiet conclusionemque id. Ex novum verterem usu.
Eum fastidii consulatu cu. In nobis iuvaret usu, paulo tincidunt no usu, minim corpora his te. Nam graeco delenit omittam cu. Et sed ipsum fastidii, mea cu altera ullamcorper. Sea id altera menandri deseruisse, munere audire utroque cu pri. Nec solet facilis id. Mucius delectus eu vis, has in augue veniam."/>
</RelativeLayout>
android:layout_height="match_parent"
android:id="@+id/channel_streams_view">
</LinearLayout>
</android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>