diff --git a/app/src/main/java/org/schabi/newpipe/ActionBarHandler.java b/app/src/main/java/org/schabi/newpipe/ActionBarHandler.java index 23ae0724d..b1967fd0b 100644 --- a/app/src/main/java/org/schabi/newpipe/ActionBarHandler.java +++ b/app/src/main/java/org/schabi/newpipe/ActionBarHandler.java @@ -16,6 +16,8 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.widget.ArrayAdapter; +import org.schabi.newpipe.services.VideoInfo; + /** * Created by Christian Schabesberger on 18.08.15. * diff --git a/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java b/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java index c925a40d3..b7eaa4285 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java +++ b/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java @@ -33,7 +33,7 @@ class VideoInfoItemViewCreator { this.inflater = inflater; } - public View getViewByVideoInfoItem(View convertView, ViewGroup parent, VideoPreviewInfo info) { + public View getViewFromVideoInfoItem(View convertView, ViewGroup parent, VideoPreviewInfo info) { ViewHolder holder; if(convertView == null) { convertView = inflater.inflate(R.layout.video_item, parent, false); diff --git a/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java b/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java index d1e3d10be..0173c07de 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java @@ -8,6 +8,7 @@ import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Point; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -31,6 +32,7 @@ import android.widget.ProgressBar; import android.widget.RelativeLayout; import android.widget.TextView; import android.view.MenuItem; +import android.widget.Toast; import java.net.URL; import java.text.DateFormat; @@ -45,6 +47,7 @@ import java.util.Vector; import org.schabi.newpipe.services.VideoExtractor; import org.schabi.newpipe.services.ServiceList; import org.schabi.newpipe.services.StreamingService; +import org.schabi.newpipe.services.VideoInfo; /** @@ -114,7 +117,7 @@ public class VideoItemDetailFragment extends Fragment { this.videoExtractor = service.getExtractorInstance(videoUrl); VideoInfo videoInfo = videoExtractor.getVideoInfo(); h.post(new VideoResultReturnedRunnable(videoInfo)); - if (videoInfo.videoAvailableStatus == VideoInfo.VIDEO_AVAILABLE) { + if (videoInfo.errorCode == VideoInfo.NO_ERROR) { h.post(new SetThumbnailRunnable( BitmapFactory.decodeStream( new URL(videoInfo.thumbnail_url) @@ -224,22 +227,28 @@ public class VideoItemDetailFragment extends Fragment { FrameLayout nextVideoFrame = (FrameLayout) activity.findViewById(R.id.detailNextVideoFrame); RelativeLayout nextVideoRootFrame = (RelativeLayout) activity.findViewById(R.id.detailNextVideoRootLayout); - View nextVideoView = videoItemViewCreator - .getViewByVideoInfoItem(null, nextVideoFrame, info.nextVideo); - nextVideoFrame.addView(nextVideoView); - Button nextVideoButton = (Button) activity.findViewById(R.id.detailNextVideoButton); - Button similarVideosButton = (Button) activity.findViewById(R.id.detailShowSimilarButton); + Button backgroundButton = (Button) + activity.findViewById(R.id.detailVideoThumbnailWindowBackgroundButton); - textContentLayout.setVisibility(View.VISIBLE); - playVideoButton.setVisibility(View.VISIBLE); progressBar.setVisibility(View.GONE); - if(!showNextVideoItem) { - nextVideoRootFrame.setVisibility(View.GONE); - similarVideosButton.setVisibility(View.GONE); - } - switch (info.videoAvailableStatus) { - case VideoInfo.VIDEO_AVAILABLE: { + switch (info.errorCode) { + case VideoInfo.NO_ERROR: { + View nextVideoView = videoItemViewCreator + .getViewFromVideoInfoItem(null, nextVideoFrame, info.nextVideo); + nextVideoFrame.addView(nextVideoView); + + + Button nextVideoButton = (Button) activity.findViewById(R.id.detailNextVideoButton); + Button similarVideosButton = (Button) activity.findViewById(R.id.detailShowSimilarButton); + + textContentLayout.setVisibility(View.VISIBLE); + playVideoButton.setVisibility(View.VISIBLE); + if (!showNextVideoItem) { + nextVideoRootFrame.setVisibility(View.GONE); + similarVideosButton.setVisibility(View.GONE); + } + videoTitleView.setText(info.title); uploaderView.setText(info.uploader); actionBarHandler.setChannelName(info.uploader); @@ -287,30 +296,41 @@ public class VideoItemDetailFragment extends Fragment { streamList[i] = streamsToUse.get(i); } actionBarHandler.setStreams(streamList, info.audioStreams); - } - nextVideoButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent detailIntent = - new Intent(getActivity(), VideoItemDetailActivity.class); + nextVideoButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent detailIntent = + new Intent(getActivity(), VideoItemDetailActivity.class); /*detailIntent.putExtra( VideoItemDetailFragment.ARG_ITEM_ID, currentVideoInfo.nextVideo.id); */ - detailIntent.putExtra( - VideoItemDetailFragment.VIDEO_URL, currentVideoInfo.nextVideo.webpage_url); - //todo: make id dynamic the following line is crap - detailIntent.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId); - startActivity(detailIntent); - } - }); + detailIntent.putExtra( + VideoItemDetailFragment.VIDEO_URL, currentVideoInfo.nextVideo.webpage_url); + //todo: make id dynamic the following line is crap + detailIntent.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId); + startActivity(detailIntent); + } + }); + } break; - case VideoInfo.VIDEO_UNAVAILABLE_GEMA: + case VideoInfo.ERROR_BLOCKED_BY_GEMA: thumbnailView.setImageBitmap(BitmapFactory.decodeResource( getResources(), R.drawable.gruese_die_gema_unangebracht)); + backgroundButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse(activity.getString(R.string.c3sUrl))); + activity.startActivity(intent); + } + }); break; - case VideoInfo.VIDEO_UNAVAILABLE: + case VideoInfo.ERROR_NO_SPECIFIED_ERROR: thumbnailView.setImageBitmap(BitmapFactory.decodeResource( getResources(), R.drawable.not_available_monkey)); + Toast.makeText(activity, info.errorMessage, Toast.LENGTH_LONG) + .show(); break; default: Log.e(TAG, "Video Available Status not known."); diff --git a/app/src/main/java/org/schabi/newpipe/VideoListAdapter.java b/app/src/main/java/org/schabi/newpipe/VideoListAdapter.java index 4e35a5a20..e85e74e22 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/VideoListAdapter.java @@ -96,7 +96,7 @@ class VideoListAdapter extends BaseAdapter { @Override public View getView(int position, View convertView, ViewGroup parent) { - convertView = viewCreator.getViewByVideoInfoItem(convertView, parent, videoList.get(position)); + convertView = viewCreator.getViewFromVideoInfoItem(convertView, parent, videoList.get(position)); if(listView.isItemChecked(position)) { convertView.setBackgroundColor(ContextCompat.getColor(context,R.color.primaryColorYoutube)); diff --git a/app/src/main/java/org/schabi/newpipe/services/VideoExtractor.java b/app/src/main/java/org/schabi/newpipe/services/VideoExtractor.java index ff2b736d4..9da25fc92 100644 --- a/app/src/main/java/org/schabi/newpipe/services/VideoExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/services/VideoExtractor.java @@ -20,8 +20,6 @@ package org.schabi.newpipe.services; * along with NewPipe. If not, see . */ -import org.schabi.newpipe.VideoInfo; - /**Scrapes information from a video streaming service (eg, YouTube).*/ @SuppressWarnings("ALL") @@ -46,54 +44,60 @@ public abstract class VideoExtractor { videoInfo.webpage_url = pageUrl; } - if(videoInfo.title.isEmpty()) { - videoInfo.title = getTitle(); - } + if(getErrorCode() == VideoInfo.NO_ERROR) { - if(videoInfo.duration < 1) { - videoInfo.duration = getLength(); - } + if (videoInfo.title.isEmpty()) { + videoInfo.title = getTitle(); + } + + if (videoInfo.duration < 1) { + videoInfo.duration = getLength(); + } - if(videoInfo.uploader.isEmpty()) { - videoInfo.uploader = getUploader(); - } + if (videoInfo.uploader.isEmpty()) { + videoInfo.uploader = getUploader(); + } - if(videoInfo.description.isEmpty()) { - videoInfo.description = getDescription(); - } + if (videoInfo.description.isEmpty()) { + videoInfo.description = getDescription(); + } - if(videoInfo.view_count == -1) { - videoInfo.view_count = getViews(); - } + if (videoInfo.view_count == -1) { + videoInfo.view_count = getViews(); + } - if(videoInfo.upload_date.isEmpty()) { - videoInfo.upload_date = getUploadDate(); - } + if (videoInfo.upload_date.isEmpty()) { + videoInfo.upload_date = getUploadDate(); + } - if(videoInfo.thumbnail_url.isEmpty()) { - videoInfo.thumbnail_url = getThumbnailUrl(); - } + if (videoInfo.thumbnail_url.isEmpty()) { + videoInfo.thumbnail_url = getThumbnailUrl(); + } - if(videoInfo.id.isEmpty()) { - videoInfo.id = getVideoId(pageUrl); - } + if (videoInfo.id.isEmpty()) { + videoInfo.id = getVideoId(pageUrl); + } - /** Load and extract audio*/ - if(videoInfo.audioStreams == null) { - videoInfo.audioStreams = getAudioStreams(); - } - /** Extract video stream url*/ - if(videoInfo.videoStreams == null) { - videoInfo.videoStreams = getVideoStreams(); - } + /** Load and extract audio*/ + if (videoInfo.audioStreams == null) { + videoInfo.audioStreams = getAudioStreams(); + } + /** Extract video stream url*/ + if (videoInfo.videoStreams == null) { + videoInfo.videoStreams = getVideoStreams(); + } - if(videoInfo.uploader_thumbnail_url.isEmpty()) { - videoInfo.uploader_thumbnail_url = getUploaderThumbnailUrl(); - } + if (videoInfo.uploader_thumbnail_url.isEmpty()) { + videoInfo.uploader_thumbnail_url = getUploaderThumbnailUrl(); + } - if(videoInfo.startPosition < 0) { - videoInfo.startPosition = getTimeStamp(); + if (videoInfo.startPosition < 0) { + videoInfo.startPosition = getTimeStamp(); + } + } else { + videoInfo.errorCode = getErrorCode(); + videoInfo.errorMessage = getErrorMessage(); } //Bitmap thumbnail = null; @@ -102,6 +106,9 @@ public abstract class VideoExtractor { return videoInfo; } + + protected abstract int getErrorCode(); + protected abstract String getErrorMessage(); protected abstract String getVideoUrl(String videoId); protected abstract String getVideoId(String siteUrl); protected abstract int getTimeStamp(); diff --git a/app/src/main/java/org/schabi/newpipe/VideoInfo.java b/app/src/main/java/org/schabi/newpipe/services/VideoInfo.java similarity index 86% rename from app/src/main/java/org/schabi/newpipe/VideoInfo.java rename to app/src/main/java/org/schabi/newpipe/services/VideoInfo.java index 89e576aae..dcf871d8c 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoInfo.java +++ b/app/src/main/java/org/schabi/newpipe/services/VideoInfo.java @@ -1,5 +1,6 @@ -package org.schabi.newpipe; +package org.schabi.newpipe.services; +import org.schabi.newpipe.VideoPreviewInfo; import org.schabi.newpipe.services.AbstractVideoInfo; import java.util.List; @@ -28,11 +29,20 @@ import java.util.List; @SuppressWarnings("ALL") public class VideoInfo extends AbstractVideoInfo { + // If a video could not be parsed, this predefined error codes + // will be returned AND can be parsed by the frontend of the app. + // Error codes: + public final static int NO_ERROR = 0x0; + public final static int ERROR_NO_SPECIFIED_ERROR = 0x1; + // GEMA a german music colecting society. + public final static int ERROR_BLOCKED_BY_GEMA = 0x2; + public String uploader_thumbnail_url = ""; public String description = ""; public VideoStream[] videoStreams = null; public AudioStream[] audioStreams = null; - public int videoAvailableStatus = VIDEO_AVAILABLE; + public int errorCode = NO_ERROR; + public String errorMessage = ""; public int duration = -1; /*YouTube-specific fields @@ -45,11 +55,6 @@ public class VideoInfo extends AbstractVideoInfo { public List relatedVideos = null; public int startPosition = -1;//in seconds. some metadata is not passed using a VideoInfo object! - public static final int VIDEO_AVAILABLE = 0x00; - public static final int VIDEO_UNAVAILABLE = 0x01; - public static final int VIDEO_UNAVAILABLE_GEMA = 0x02;//German DRM organisation - - public VideoInfo() {} diff --git a/app/src/main/java/org/schabi/newpipe/services/youtube/YoutubeVideoExtractor.java b/app/src/main/java/org/schabi/newpipe/services/youtube/YoutubeVideoExtractor.java index bb8338909..659b0b7e4 100644 --- a/app/src/main/java/org/schabi/newpipe/services/youtube/YoutubeVideoExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/services/youtube/YoutubeVideoExtractor.java @@ -15,7 +15,7 @@ import org.mozilla.javascript.ScriptableObject; import org.schabi.newpipe.Downloader; import org.schabi.newpipe.services.VideoExtractor; import org.schabi.newpipe.MediaFormat; -import org.schabi.newpipe.VideoInfo; +import org.schabi.newpipe.services.VideoInfo; import org.schabi.newpipe.VideoPreviewInfo; import org.xmlpull.v1.XmlPullParser; @@ -53,6 +53,8 @@ public class YoutubeVideoExtractor extends VideoExtractor { private final Document doc; private JSONObject jsonObj; private JSONObject playerArgs; + private int errorCode = VideoInfo.NO_ERROR; + private String errorMessage = ""; // static values private static final String DECRYPTION_FUNC_NAME="decrypt"; @@ -60,7 +62,6 @@ public class YoutubeVideoExtractor extends VideoExtractor { // cached values private static volatile String decryptionCode = ""; - public YoutubeVideoExtractor(String pageUrl) { super(pageUrl);//most common videoInfo fields are now set in our superclass, for all services String pageContents = Downloader.download(cleanUrl(pageUrl)); @@ -69,12 +70,18 @@ public class YoutubeVideoExtractor extends VideoExtractor { //attempt to load the youtube js player JSON arguments try { String jsonString = matchGroup1("ytplayer.config\\s*=\\s*(\\{.*?\\});", pageContents); + //todo: implement this by try and catch. TESTING THE STRING AGAINST EMPTY IS CONSIDERED POOR STYLE !!! + if(jsonString.isEmpty()) { + errorCode = findErrorReason(doc); + return; + } + jsonObj = new JSONObject(jsonString); playerArgs = jsonObj.getJSONObject("args"); } catch (Exception e) {//if this fails, the video is most likely not available. // Determining why is done later. - videoInfo.videoAvailableStatus = VideoInfo.VIDEO_UNAVAILABLE; + videoInfo.errorCode = VideoInfo.ERROR_NO_SPECIFIED_ERROR; Log.e(TAG, "Could not load JSON data for Youtube video \""+pageUrl+"\". This most likely means the video is unavailable"); } @@ -367,80 +374,95 @@ public class YoutubeVideoExtractor extends VideoExtractor { @Override public VideoInfo getVideoInfo() { + //todo: @medovax i like your work, but what the fuck: videoInfo = super.getVideoInfo(); - //todo: replace this with a call to getVideoId, if possible - videoInfo.id = matchGroup1("v=([0-9a-zA-Z_-]{11})", pageUrl); - if(videoInfo.audioStreams == null - || videoInfo.audioStreams.length == 0) { - Log.e(TAG, "uninitialised audio streams!"); - } + if(errorCode == VideoInfo.NO_ERROR) { + //todo: replace this with a call to getVideoId, if possible + videoInfo.id = matchGroup1("v=([0-9a-zA-Z_-]{11})", pageUrl); - if(videoInfo.videoStreams == null - || videoInfo.videoStreams.length == 0) { - Log.e(TAG, "uninitialised video streams!"); - } + if (videoInfo.audioStreams == null + || videoInfo.audioStreams.length == 0) { + Log.e(TAG, "uninitialised audio streams!"); + } - videoInfo.age_limit = 0; + if (videoInfo.videoStreams == null + || videoInfo.videoStreams.length == 0) { + Log.e(TAG, "uninitialised video streams!"); + } - //average rating - try { - videoInfo.average_rating = playerArgs.getString("avg_rating"); - } catch (JSONException e) { - e.printStackTrace(); - } + videoInfo.age_limit = 0; - //--------------------------------------- - // extracting information from html page - //--------------------------------------- + //average rating + try { + videoInfo.average_rating = playerArgs.getString("avg_rating"); + } catch (JSONException e) { + e.printStackTrace(); + } + //--------------------------------------- + // extracting information from html page + //--------------------------------------- + + /* Code does not work here anymore. // Determine what went wrong when the Video is not available - if(videoInfo.videoAvailableStatus == VideoInfo.VIDEO_UNAVAILABLE) { + if(videoInfo.errorCode == VideoInfo.ERROR_NO_SPECIFIED_ERROR) { if(doc.select("h1[id=\"unavailable-message\"]").first().text().contains("GEMA")) { videoInfo.videoAvailableStatus = VideoInfo.VIDEO_UNAVAILABLE_GEMA; } } + */ - String likesString = ""; - String dislikesString = ""; - try { - // likes - likesString = doc.select("button.like-button-renderer-like-button").first() - .select("span.yt-uix-button-content").first().text(); - videoInfo.like_count = Integer.parseInt(likesString.replaceAll("[^\\d]", "")); - // dislikes - dislikesString = doc.select("button.like-button-renderer-dislike-button").first() - .select("span.yt-uix-button-content").first().text(); + String likesString = ""; + String dislikesString = ""; + try { + // likes + likesString = doc.select("button.like-button-renderer-like-button").first() + .select("span.yt-uix-button-content").first().text(); + videoInfo.like_count = Integer.parseInt(likesString.replaceAll("[^\\d]", "")); + // dislikes + dislikesString = doc.select("button.like-button-renderer-dislike-button").first() + .select("span.yt-uix-button-content").first().text(); - videoInfo.dislike_count = Integer.parseInt(dislikesString.replaceAll("[^\\d]", "")); - } catch(NumberFormatException nfe) { - Log.e(TAG, "failed to parse likesString \""+likesString+"\" and dislikesString \""+ - dislikesString+"\" as integers"); - } catch(Exception e) { - // if it fails we know that the video does not offer dislikes. - e.printStackTrace(); - videoInfo.like_count = 0; - videoInfo.dislike_count = 0; - } - - // next video - videoInfo.nextVideo = extractVideoPreviewInfo(doc.select("div[class=\"watch-sidebar-section\"]").first() - .select("li").first()); - - // related videos - Vector relatedVideos = new Vector<>(); - for(Element li : doc.select("ul[id=\"watch-related\"]").first().children()) { - // first check if we have a playlist. If so leave them out - if(li.select("a[class*=\"content-link\"]").first() != null) { - relatedVideos.add(extractVideoPreviewInfo(li)); + videoInfo.dislike_count = Integer.parseInt(dislikesString.replaceAll("[^\\d]", "")); + } catch (NumberFormatException nfe) { + Log.e(TAG, "failed to parse likesString \"" + likesString + "\" and dislikesString \"" + + dislikesString + "\" as integers"); + } catch (Exception e) { + // if it fails we know that the video does not offer dislikes. + e.printStackTrace(); + videoInfo.like_count = 0; + videoInfo.dislike_count = 0; } + + // next video + videoInfo.nextVideo = extractVideoPreviewInfo(doc.select("div[class=\"watch-sidebar-section\"]").first() + .select("li").first()); + + // related videos + Vector relatedVideos = new Vector<>(); + for (Element li : doc.select("ul[id=\"watch-related\"]").first().children()) { + // first check if we have a playlist. If so leave them out + if (li.select("a[class*=\"content-link\"]").first() != null) { + relatedVideos.add(extractVideoPreviewInfo(li)); + } + } + //todo: replace conversion + videoInfo.relatedVideos = relatedVideos; + //videoInfo.relatedVideos = relatedVideos.toArray(new VideoPreviewInfo[relatedVideos.size()]); } - //todo: replace conversion - videoInfo.relatedVideos = relatedVideos; - //videoInfo.relatedVideos = relatedVideos.toArray(new VideoPreviewInfo[relatedVideos.size()]); return videoInfo; } + @Override + public int getErrorCode() { + return errorCode; + } + + @Override + public String getErrorMessage() { + return errorMessage; + } private VideoInfo.AudioStream[] parseDashManifest(String dashManifest, String decryptoinCode) { if(!dashManifest.contains("/signature/")) { @@ -610,6 +632,13 @@ public class YoutubeVideoExtractor extends VideoExtractor { new Exception("failed to find pattern \""+pattern+"\"").printStackTrace(); return ""; } + } + private int findErrorReason(Document doc) { + errorMessage = doc.select("h1[id=\"unavailable-message\"]").first().text(); + if(errorMessage.contains("GEMA")) { + return VideoInfo.ERROR_BLOCKED_BY_GEMA; + } + return VideoInfo.ERROR_NO_SPECIFIED_ERROR; } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fa767bdf4..177280277 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -52,6 +52,7 @@ ETC %1$s - NewPipe Playing in background + https://www.c3s.cc/ Video preview thumbnail