diff --git a/app/build.gradle b/app/build.gradle index 38d72bb01..10e4df714 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -42,4 +42,5 @@ dependencies { compile 'de.hdodenhof:circleimageview:2.0.0' compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' compile 'com.github.nirhart:parallaxscroll:1.0' + compile 'org.apache.directory.studio:org.apache.commons.lang:2.6' } diff --git a/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeSearchEngineTest.java b/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeSearchEngineTest.java index ed6d17aae..70e955d7d 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeSearchEngineTest.java +++ b/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeSearchEngineTest.java @@ -2,7 +2,9 @@ package org.schabi.newpipe.extractor.youtube; import android.test.AndroidTestCase; -import org.schabi.newpipe.extractor.VideoPreviewInfo; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.schabi.newpipe.extractor.SearchResult; +import org.schabi.newpipe.extractor.StreamPreviewInfo; import org.schabi.newpipe.extractor.SearchEngine; import org.schabi.newpipe.extractor.services.youtube.YoutubeSearchEngine; import org.schabi.newpipe.Downloader; @@ -30,7 +32,7 @@ import java.util.ArrayList; */ public class YoutubeSearchEngineTest extends AndroidTestCase { - private SearchEngine.Result result; + private SearchResult result; private ArrayList suggestionReply; @Override @@ -39,12 +41,13 @@ public class YoutubeSearchEngineTest extends AndroidTestCase { SearchEngine engine = new YoutubeSearchEngine(); result = engine.search("bla", - 0, "de", new Downloader()); + 0, "de", new Downloader()).getSearchResult(); suggestionReply = engine.suggestionList("hello","de",new Downloader()); } public void testIfNoErrorOccur() { - assertEquals(result.errorMessage, ""); + assertTrue(result.errors.isEmpty() ? "" : ExceptionUtils.getStackTrace(result.errors.get(0)) + ,result.errors.isEmpty()); } public void testIfListIsNotEmpty() { @@ -52,44 +55,44 @@ public class YoutubeSearchEngineTest extends AndroidTestCase { } public void testItemsHaveTitle() { - for(VideoPreviewInfo i : result.resultList) { + for(StreamPreviewInfo i : result.resultList) { assertEquals(i.title.isEmpty(), false); } } public void testItemsHaveUploader() { - for(VideoPreviewInfo i : result.resultList) { + for(StreamPreviewInfo i : result.resultList) { assertEquals(i.uploader.isEmpty(), false); } } public void testItemsHaveRightDuration() { - for(VideoPreviewInfo i : result.resultList) { + for(StreamPreviewInfo i : result.resultList) { assertTrue(i.duration, i.duration.contains(":")); } } public void testItemsHaveRightThumbnail() { - for (VideoPreviewInfo i : result.resultList) { + for (StreamPreviewInfo i : result.resultList) { assertTrue(i.thumbnail_url, i.thumbnail_url.contains("https://")); } } public void testItemsHaveRightVideoUrl() { - for (VideoPreviewInfo i : result.resultList) { + for (StreamPreviewInfo i : result.resultList) { assertTrue(i.webpage_url, i.webpage_url.contains("https://")); } } public void testViewCount() { /* - for(VideoPreviewInfo i : result.resultList) { + for(StreamPreviewInfo i : result.resultList) { assertTrue(Long.toString(i.view_count), i.view_count != -1); } */ // that specific link used for this test, there are no videos with less // than 10.000 views, so we can test against that. - for(VideoPreviewInfo i : result.resultList) { + for(StreamPreviewInfo i : result.resultList) { assertTrue(Long.toString(i.view_count), i.view_count >= 10000); } } diff --git a/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeStreamExtractorDefaultTest.java b/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeStreamExtractorDefaultTest.java index 4f4c529e5..e35fd86d8 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeStreamExtractorDefaultTest.java +++ b/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeStreamExtractorDefaultTest.java @@ -6,7 +6,7 @@ import org.schabi.newpipe.Downloader; import org.schabi.newpipe.extractor.ExtractionException; import org.schabi.newpipe.extractor.ParsingException; import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor; -import org.schabi.newpipe.extractor.VideoInfo; +import org.schabi.newpipe.extractor.StreamInfo; import java.io.IOException; @@ -94,7 +94,7 @@ public class YoutubeStreamExtractorDefaultTest extends AndroidTestCase { } public void testGetVideoStreams() throws ParsingException { - for(VideoInfo.VideoStream s : extractor.getVideoStreams()) { + for(StreamInfo.VideoStream s : extractor.getVideoStreams()) { assertTrue(s.url, s.url.contains("https://")); assertTrue(s.resolution.length() > 0); diff --git a/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeStreamExtractorRestrictedTest.java b/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeStreamExtractorRestrictedTest.java index c00a91ac8..12d8a65c7 100644 --- a/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeStreamExtractorRestrictedTest.java +++ b/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeStreamExtractorRestrictedTest.java @@ -5,7 +5,7 @@ import android.test.AndroidTestCase; import org.schabi.newpipe.Downloader; import org.schabi.newpipe.extractor.ExtractionException; import org.schabi.newpipe.extractor.ParsingException; -import org.schabi.newpipe.extractor.VideoInfo; +import org.schabi.newpipe.extractor.StreamInfo; import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor; import java.io.IOException; @@ -73,7 +73,7 @@ public class YoutubeStreamExtractorRestrictedTest extends AndroidTestCase { } public void testGetVideoStreams() throws ParsingException { - for(VideoInfo.VideoStream s : extractor.getVideoStreams()) { + for(StreamInfo.VideoStream s : extractor.getVideoStreams()) { assertTrue(s.url, s.url.contains("https://")); assertTrue(s.resolution.length() > 0); diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 323da8bd6..64b7aec4f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,23 +1,25 @@ - - - - + package="org.schabi.newpipe"> + + + + + + + android:configChanges="orientation|screenSize" + android:label="@string/app_name"> @@ -26,10 +28,10 @@ + android:label="@string/title_videoitem_detail" + android:screenOrientation="portrait" + android:theme="@style/AppTheme"> @@ -76,20 +78,21 @@ - - + android:theme="@style/VideoPlayerTheme" + tools:ignore="UnusedAttribute"> + + android:exported="false" + android:label="@string/background_player_name" /> + - + android:label="@string/settings_activity_title"> + + + diff --git a/app/src/main/java/org/schabi/newpipe/ActionBarHandler.java b/app/src/main/java/org/schabi/newpipe/ActionBarHandler.java index 5123f494b..6836f223e 100644 --- a/app/src/main/java/org/schabi/newpipe/ActionBarHandler.java +++ b/app/src/main/java/org/schabi/newpipe/ActionBarHandler.java @@ -12,7 +12,7 @@ import android.view.MenuItem; import android.widget.ArrayAdapter; import org.schabi.newpipe.extractor.MediaFormat; -import org.schabi.newpipe.extractor.VideoInfo; +import org.schabi.newpipe.extractor.StreamInfo; import java.util.List; @@ -75,7 +75,7 @@ class ActionBarHandler { } } - public void setupStreamList(final List videoStreams) { + public void setupStreamList(final List videoStreams) { if (activity != null) { selectedVideoStream = 0; @@ -83,7 +83,7 @@ class ActionBarHandler { // this array will be shown in the dropdown menu for selecting the stream/resolution. String[] itemArray = new String[videoStreams.size()]; for (int i = 0; i < videoStreams.size(); i++) { - VideoInfo.VideoStream item = videoStreams.get(i); + StreamInfo.VideoStream item = videoStreams.get(i); itemArray[i] = MediaFormat.getNameById(item.format) + " " + item.resolution; } int defaultResolution = getDefaultResolution(videoStreams); @@ -108,13 +108,13 @@ class ActionBarHandler { } - private int getDefaultResolution(final List videoStreams) { + private int getDefaultResolution(final List videoStreams) { String defaultResolution = defaultPreferences .getString(activity.getString(R.string.default_resolution_key), activity.getString(R.string.default_resolution_value)); for (int i = 0; i < videoStreams.size(); i++) { - VideoInfo.VideoStream item = videoStreams.get(i); + StreamInfo.VideoStream item = videoStreams.get(i); if (defaultResolution.equals(item.resolution)) { return i; } diff --git a/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java b/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java index b09652a6c..4ea6ca657 100644 --- a/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java +++ b/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java @@ -22,10 +22,12 @@ package org.schabi.newpipe; import android.graphics.Bitmap; +import java.util.List; + /** * Singleton: * Used to send data between certain Activity/Services within the same process. - * This can be considered as hack inside the Android universe. **/ + * This can be considered as an ugly hack inside the Android universe. **/ public class ActivityCommunicator { private static ActivityCommunicator activityCommunicator = null; @@ -39,4 +41,9 @@ public class ActivityCommunicator { // Thumbnail send from VideoItemDetailFragment to BackgroundPlayer public volatile Bitmap backgroundPlayerThumbnail; + + // Sent from any activity to ErrorActivity. + public volatile List errorList; + public volatile Class returnActivity; + public volatile ErrorActivity.ErrorInfo errorInfo; } diff --git a/app/src/main/java/org/schabi/newpipe/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/ErrorActivity.java new file mode 100644 index 000000000..0de0cdabb --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/ErrorActivity.java @@ -0,0 +1,385 @@ + + +package org.schabi.newpipe; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Build; +import android.preference.PreferenceManager; +import android.support.design.widget.Snackbar; +import android.support.v4.app.NavUtils; +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.Toast; + +import org.apache.commons.lang.exception.ExceptionUtils; +import org.json.JSONArray; +import org.json.JSONObject; +import org.schabi.newpipe.extractor.Parser; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.TimeZone; +import java.util.Vector; + +/** + * Created by Christian Schabesberger on 24.10.15. + *

+ * Copyright (C) Christian Schabesberger 2016 + * ErrorActivity.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 . + */ + +public class ErrorActivity extends AppCompatActivity { + public static class ErrorInfo { + public int userAction; + public String request; + public String serviceName; + public int message; + + public static ErrorInfo make(int userAction, String serviceName, String request, int message) { + ErrorInfo info = new ErrorInfo(); + info.userAction = userAction; + info.serviceName = serviceName; + info.request = request; + info.message = message; + return info; + } + } + + public static final String TAG = ErrorActivity.class.toString(); + public static final int SEARCHED = 0; + public static final int REQUESTED_STREAM = 1; + public static final int GET_SUGGESTIONS = 2; + public static final String SEARCHED_STRING = "searched"; + public static final String REQUESTED_STREAM_STRING = "requested stream"; + public static final String GET_SUGGESTIONS_STRING = "get suggestions"; + + public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org"; + public static final String ERROR_EMAIL_SUBJECT = "Exception in NewPipe " + BuildConfig.VERSION_NAME; + + private List errorList; + private ErrorInfo errorInfo; + private Class returnActivity; + private String currentTimeStamp; + private String globIpRange; + Thread globIpRangeThread = null; + + // views + private TextView errorView; + private EditText userCommentBox; + private Button reportButton; + private TextView infoView; + private TextView errorMessageView; + + public static void reportError(final Context context, final List el, + final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) { + + if (rootView != null) { + Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG) + .setActionTextColor(Color.YELLOW) + .setAction(R.string.error_snackbar_action, new View.OnClickListener() { + @Override + public void onClick(View v) { + ActivityCommunicator ac = ActivityCommunicator.getCommunicator(); + ac.errorList = el; + ac.returnActivity = returnAcitivty; + ac.errorInfo = errorInfo; + Intent intent = new Intent(context, ErrorActivity.class); + context.startActivity(intent); + } + }).show(); + } else { + ActivityCommunicator ac = ActivityCommunicator.getCommunicator(); + ac.errorList = el; + ac.returnActivity = returnAcitivty; + ac.errorInfo = errorInfo; + Intent intent = new Intent(context, ErrorActivity.class); + context.startActivity(intent); + } + } + + public static void reportError(final Context context, final Exception e, + final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) { + List el = new Vector<>(); + el.add(e); + reportError(context, el, returnAcitivty, rootView, errorInfo); + } + + // async call + public static void reportError(Handler handler, final Context context, final Exception e, + final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) { + List el = new Vector<>(); + el.add(e); + reportError(handler, context, el, returnAcitivty, rootView, errorInfo); + } + + // async call + public static void reportError(Handler handler, final Context context, final List el, + final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) { + handler.post(new Runnable() { + @Override + public void run() { + reportError(context, el, returnAcitivty, rootView, errorInfo); + } + }); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_error); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + ActivityCommunicator ac = ActivityCommunicator.getCommunicator(); + errorList = ac.errorList; + returnActivity = ac.returnActivity; + errorInfo = ac.errorInfo; + + reportButton = (Button) findViewById(R.id.errorReportButton); + userCommentBox = (EditText) findViewById(R.id.errorCommentBox); + errorView = (TextView) findViewById(R.id.errorView); + infoView = (TextView) findViewById(R.id.errorInfosView); + errorMessageView = (TextView) findViewById(R.id.errorMessageView); + + errorView.setText(formErrorText(errorList)); + + //importand add gurumeditaion + addGuruMeditaion(); + currentTimeStamp = getCurrentTimeStamp(); + buildInfo(errorInfo); + + reportButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + + Intent intent = new Intent(Intent.ACTION_SENDTO); + intent.setData(Uri.parse("mailto:" + ERROR_EMAIL_ADDRESS)) + .putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT) + .putExtra(Intent.EXTRA_TEXT, buildJson()); + + startActivity(Intent.createChooser(intent, "Send Email")); + } + }); + reportButton.setEnabled(false); + + globIpRangeThread = new Thread(new IpRagneRequester()); + globIpRangeThread.start(); + + if(errorInfo.message != 0) { + errorMessageView.setText(errorInfo.message); + } else { + errorMessageView.setVisibility(View.GONE); + findViewById(R.id.messageWhatHappenedView).setVisibility(View.GONE); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.error_menu, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + switch (id) { + case android.R.id.home: + goToReturnActivity(); + break; + case R.id.menu_item_share_error: { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_SEND); + intent.putExtra(Intent.EXTRA_TEXT, buildJson()); + intent.setType("text/plain"); + startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title))); + } + break; + } + return false; + } + + private String formErrorText(List el) { + String text = ""; + for (Exception e : el) { + text += "-------------------------------------\n" + + ExceptionUtils.getStackTrace(e); + } + text += "-------------------------------------"; + return text; + } + + private void goToReturnActivity() { + if (returnActivity == null) { + super.onBackPressed(); + } else { + Intent intent; + if (returnActivity != null && + returnActivity.isAssignableFrom(Activity.class)) { + intent = new Intent(this, returnActivity); + } else { + intent = new Intent(this, VideoItemListActivity.class); + } + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + NavUtils.navigateUpTo(this, intent); + } + } + + private void buildInfo(ErrorInfo info) { + TextView infoLabelView = (TextView) findViewById(R.id.errorInfoLabelsView); + TextView infoView = (TextView) findViewById(R.id.errorInfosView); + String text = ""; + + infoLabelView.setText(getString(R.string.info_labels).replace("\\n", "\n")); + + text += getUserActionString(info.userAction) + + "\n" + info.request + + "\n" + getContentLangString() + + "\n" + info.serviceName + + "\n" + currentTimeStamp + + "\n" + BuildConfig.VERSION_NAME + + "\n" + getOsString(); + + infoView.setText(text); + } + + private String buildJson() { + JSONObject errorObject = new JSONObject(); + + try { + errorObject.put("user_action", getUserActionString(errorInfo.userAction)) + .put("request", errorInfo.request) + .put("content_language", getContentLangString()) + .put("service", errorInfo.serviceName) + .put("version", BuildConfig.VERSION_NAME) + .put("os", getOsString()) + .put("time", currentTimeStamp) + .put("ip_range", globIpRange); + + JSONArray exceptionArray = new JSONArray(); + for (Exception e : errorList) { + exceptionArray.put(ExceptionUtils.getStackTrace(e)); + } + + errorObject.put("exceptions", exceptionArray); + errorObject.put("user_comment", userCommentBox.getText().toString()); + + return errorObject.toString(3); + } catch (Exception e) { + Log.e(TAG, "Error while erroring: Could not build json"); + e.printStackTrace(); + } + + return ""; + } + + private String getUserActionString(int userAction) { + switch (userAction) { + case REQUESTED_STREAM: + return REQUESTED_STREAM_STRING; + case SEARCHED: + return SEARCHED_STRING; + case GET_SUGGESTIONS: + return GET_SUGGESTIONS_STRING; + default: + return "Your description is in another castle."; + } + } + + private String getContentLangString() { + return PreferenceManager.getDefaultSharedPreferences(this) + .getString(this.getString(R.string.search_language_key), "none"); + } + + private String getOsString() { + String osBase = Build.VERSION.SDK_INT >= 23 ? Build.VERSION.BASE_OS : "Android"; + return System.getProperty("os.name") + + " " + (osBase.isEmpty() ? "Android" : osBase) + + " " + Build.VERSION.RELEASE + + " - " + Integer.toString(Build.VERSION.SDK_INT); + } + + private void addGuruMeditaion() { + //just an easter egg + TextView sorryView = (TextView) findViewById(R.id.errorSorryView); + String text = sorryView.getText().toString(); + text += "\n" + getString(R.string.guru_meditation); + sorryView.setText(text); + } + + @Override + public void onBackPressed() { + //super.onBackPressed(); + goToReturnActivity(); + } + + public String getCurrentTimeStamp() { + SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm"); + df.setTimeZone(TimeZone.getTimeZone("GMT")); + return df.format(new Date()); + } + + private class IpRagneRequester implements Runnable { + Handler h = new Handler(); + public void run() { + String ipRange = "none"; + try { + Downloader dl = new Downloader(); + String ip = dl.download("https://ifcfg.me/ip"); + + ipRange = Parser.matchGroup1("([0-9]*\\.[0-9]*\\.)[0-9]*\\.[0-9]*", ip) + + "0.0"; + } catch(Exception e) { + Log.d(TAG, "Error while error: could not get iprange"); + e.printStackTrace(); + } finally { + h.post(new IpRageReturnRunnable(ipRange)); + } + } + } + + + + private class IpRageReturnRunnable implements Runnable { + String ipRange; + public IpRageReturnRunnable(String ipRange) { + this.ipRange = ipRange; + } + public void run() { + globIpRange = ipRange; + if(infoView != null) { + String text = infoView.getText().toString(); + text += "\n" + globIpRange; + infoView.setText(text); + reportButton.setEnabled(true); + } + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java b/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java index 0cf8645d0..b9dbebea8 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java +++ b/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java @@ -7,7 +7,8 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; -import org.schabi.newpipe.extractor.VideoPreviewInfo; +import org.schabi.newpipe.extractor.StreamPreviewInfo; + import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; @@ -40,8 +41,10 @@ class VideoInfoItemViewCreator { this.inflater = inflater; } - public View getViewFromVideoInfoItem(View convertView, ViewGroup parent, VideoPreviewInfo info, Context context) { + public View getViewFromVideoInfoItem(View convertView, ViewGroup parent, StreamPreviewInfo info) { ViewHolder holder; + + // generate holder if(convertView == null) { convertView = inflater.inflate(R.layout.video_item, parent, false); holder = new ViewHolder(); @@ -56,20 +59,41 @@ 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); } + */ holder.itemVideoTitleView.setText(info.title); - holder.itemUploaderView.setText(info.uploader); - holder.itemDurationView.setText(info.duration); - holder.itemViewCountView.setText(shortViewCount(info.view_count)); + if(info.uploader != null && !info.uploader.isEmpty()) { + holder.itemUploaderView.setText(info.uploader); + } else { + holder.itemDurationView.setVisibility(View.INVISIBLE); + } + if(info.duration != null && !info.duration.isEmpty()) { + holder.itemDurationView.setText(info.duration); + } else { + holder.itemDurationView.setVisibility(View.GONE); + } + if(info.view_count >= 0) { + holder.itemViewCountView.setText(shortViewCount(info.view_count)); + } else { + holder.itemViewCountView.setVisibility(View.GONE); + } if(!info.upload_date.isEmpty()) { holder.itemUploadDateView.setText(info.upload_date+" • "); } - imageLoader.displayImage(info.thumbnail_url, holder.itemThumbnailView, displayImageOptions); + if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) { + imageLoader.displayImage(info.thumbnail_url, holder.itemThumbnailView, displayImageOptions); + } else { + holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail); + } + return convertView; } diff --git a/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java b/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java index 625f1d1dc..1a35a4d38 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java @@ -50,9 +50,9 @@ import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.ParsingException; import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.StreamExtractor; -import org.schabi.newpipe.extractor.VideoPreviewInfo; +import org.schabi.newpipe.extractor.StreamInfo; +import org.schabi.newpipe.extractor.StreamPreviewInfo; import org.schabi.newpipe.extractor.StreamingService; -import org.schabi.newpipe.extractor.VideoInfo; import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor; @@ -127,12 +127,34 @@ public class VideoItemDetailFragment extends Fragment { @Override public void run() { - VideoInfo videoInfo = null; + StreamInfo streamInfo = null; try { streamExtractor = service.getExtractorInstance(videoUrl, new Downloader()); - videoInfo = VideoInfo.getVideoInfo(streamExtractor, new Downloader()); + streamInfo = StreamInfo.getVideoInfo(streamExtractor, new Downloader()); - h.post(new VideoResultReturnedRunnable(videoInfo)); + h.post(new VideoResultReturnedRunnable(streamInfo)); + + // look for errors during extraction + // this if statement only covers extra information. + // if these are not available or caused an error, they are just not available + // but don't render the stream information unusalbe. + if(streamInfo != null && + !streamInfo.errors.isEmpty()) { + Log.e(TAG, "OCCURRED ERRORS DURING EXTRACTION:"); + for (Exception e : streamInfo.errors) { + e.printStackTrace(); + Log.e(TAG, "------"); + } + + Activity a = getActivity(); + View rootView = a != null ? a.findViewById(R.id.videoitem_detail) : null; + ErrorActivity.reportError(h, getActivity(), + streamInfo.errors, null, rootView, + ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, + service.getServiceInfo().name, videoUrl, 0 /* no message for the user */)); + } + + // These errors render the stream information unusable. } catch (IOException e) { postNewErrorToast(h, R.string.network_error); e.printStackTrace(); @@ -165,37 +187,66 @@ public class VideoItemDetailFragment extends Fragment { } }); e.printStackTrace(); + } catch(StreamInfo.StreamExctractException e) { + if(!streamInfo.errors.isEmpty()) { + // !!! if this case ever kicks in someone gets kicked out !!! + ErrorActivity.reportError(h, getActivity(), e, VideoItemListActivity.class, null, + ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, + service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream)); + } else { + ErrorActivity.reportError(h, getActivity(), streamInfo.errors, VideoItemListActivity.class, null, + ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, + service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream)); + } + h.post(new Runnable() { + @Override + public void run() { + getActivity().finish(); + } + }); + e.printStackTrace(); } catch (ParsingException e) { - postNewErrorToast(h, e.getMessage()); + ErrorActivity.reportError(h, getActivity(), e, VideoItemListActivity.class, null, + ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, + service.getServiceInfo().name, videoUrl, R.string.parsing_error)); + h.post(new Runnable() { + @Override + public void run() { + getActivity().finish(); + } + }); e.printStackTrace(); } catch(Exception e) { - postNewErrorToast(h, R.string.general_error); + ErrorActivity.reportError(h, getActivity(), e, VideoItemListActivity.class, null, + ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, + service.getServiceInfo().name, videoUrl, R.string.general_error)); + h.post(new Runnable() { + @Override + public void run() { + getActivity().finish(); + } + }); e.printStackTrace(); - } finally { - if(videoInfo != null && - !videoInfo.errors.isEmpty()) { - Log.e(TAG, "OCCURRED ERRORS DURING EXTRACTION:"); - for(Exception e : videoInfo.errors) { - e.printStackTrace(); - } - } } } } private class VideoResultReturnedRunnable implements Runnable { - private final VideoInfo videoInfo; - public VideoResultReturnedRunnable(VideoInfo videoInfo) { - this.videoInfo = videoInfo; + private final StreamInfo streamInfo; + public VideoResultReturnedRunnable(StreamInfo streamInfo) { + this.streamInfo = streamInfo; } @Override public void run() { - boolean show_age_restricted_content = PreferenceManager.getDefaultSharedPreferences(getActivity()) - .getBoolean(activity.getString(R.string.show_age_restricted_content), false); - if(videoInfo.age_limit == 0 || show_age_restricted_content) { - updateInfo(videoInfo); - } else { - onNotSpecifiedContentErrorWithMessage(R.string.video_is_age_restricted); + Activity a = getActivity(); + if(a != null) { + boolean show_age_restricted_content = PreferenceManager.getDefaultSharedPreferences(a) + .getBoolean(activity.getString(R.string.show_age_restricted_content), false); + if (streamInfo.age_limit == 0 || show_age_restricted_content) { + updateInfo(streamInfo); + } else { + onNotSpecifiedContentErrorWithMessage(R.string.video_is_age_restricted); + } } } } @@ -220,7 +271,7 @@ public class VideoItemDetailFragment extends Fragment { public void onLoadingCancelled(String imageUri, View view) {} } - private void updateInfo(final VideoInfo info) { + private void updateInfo(final StreamInfo info) { try { Context c = getContext(); VideoInfoItemViewCreator videoItemViewCreator = @@ -249,7 +300,7 @@ public class VideoItemDetailFragment extends Fragment { View nextVideoView = null; if(info.next_video != null) { nextVideoView = videoItemViewCreator - .getViewFromVideoInfoItem(null, nextVideoFrame, info.next_video, getContext()); + .getViewFromVideoInfoItem(null, nextVideoFrame, info.next_video); } else { activity.findViewById(R.id.detailNextVidButtonAndContentLayout).setVisibility(View.GONE); activity.findViewById(R.id.detailNextVideoTitle).setVisibility(View.GONE); @@ -337,8 +388,8 @@ public class VideoItemDetailFragment extends Fragment { descriptionView.setMovementMethod(LinkMovementMethod.getInstance()); // parse streams - Vector streamsToUse = new Vector<>(); - for (VideoInfo.VideoStream i : info.video_streams) { + Vector streamsToUse = new Vector<>(); + for (StreamInfo.VideoStream i : info.video_streams) { if (useStream(i, streamsToUse)) { streamsToUse.add(i); } @@ -394,7 +445,7 @@ public class VideoItemDetailFragment extends Fragment { } } - private void initThumbnailViews(VideoInfo info, View nextVideoFrame) { + private void initThumbnailViews(StreamInfo info, View nextVideoFrame) { ImageView videoThumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView); ImageView uploaderThumb = (ImageView) activity.findViewById(R.id.detailUploaderThumbnailView); @@ -437,7 +488,7 @@ public class VideoItemDetailFragment extends Fragment { } } - private void setupActionBarHandler(final VideoInfo info) { + private void setupActionBarHandler(final StreamInfo info) { actionBarHandler.setupStreamList(info.video_streams); actionBarHandler.setOnShareListener(new ActionBarHandler.OnActionListener() { @@ -504,7 +555,7 @@ public class VideoItemDetailFragment extends Fragment { // website which was crawled. Then the ui has to understand this and act right. if (info.audio_streams != null) { - VideoInfo.AudioStream audioStream = + StreamInfo.AudioStream audioStream = info.audio_streams.get(getPreferredAudioStreamId(info)); String audioSuffix = "." + MediaFormat.getSuffixById(audioStream.format); @@ -513,7 +564,7 @@ public class VideoItemDetailFragment extends Fragment { } if (info.video_streams != null) { - VideoInfo.VideoStream selectedStreamItem = info.video_streams.get(selectedStreamId); + StreamInfo.VideoStream selectedStreamItem = info.video_streams.get(selectedStreamId); String videoSuffix = "." + MediaFormat.getSuffixById(selectedStreamItem.format); args.putString(DownloadDialog.FILE_SUFFIX_VIDEO, videoSuffix); args.putString(DownloadDialog.VIDEO_URL, selectedStreamItem.url); @@ -540,7 +591,7 @@ public class VideoItemDetailFragment extends Fragment { boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity) .getBoolean(activity.getString(R.string.use_external_audio_player_key), false); Intent intent; - VideoInfo.AudioStream audioStream = + StreamInfo.AudioStream audioStream = info.audio_streams.get(getPreferredAudioStreamId(info)); if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 18) { //internal music player: explicit intent @@ -599,7 +650,7 @@ public class VideoItemDetailFragment extends Fragment { } } - private int getPreferredAudioStreamId(final VideoInfo info) { + private int getPreferredAudioStreamId(final StreamInfo info) { String preferredFormatString = PreferenceManager.getDefaultSharedPreferences(getActivity()) .getString(activity.getString(R.string.default_audio_format_key), "webm"); @@ -626,12 +677,12 @@ public class VideoItemDetailFragment extends Fragment { return 0; } - private void initSimilarVideos(final VideoInfo info, VideoInfoItemViewCreator videoItemViewCreator) { + private void initSimilarVideos(final StreamInfo info, VideoInfoItemViewCreator videoItemViewCreator) { LinearLayout similarLayout = (LinearLayout) activity.findViewById(R.id.similarVideosView); - ArrayList similar = new ArrayList<>(info.related_videos); - for (final VideoPreviewInfo item : similar) { + ArrayList similar = new ArrayList<>(info.related_videos); + for (final StreamPreviewInfo item : similar) { View similarView = videoItemViewCreator - .getViewFromVideoInfoItem(null, similarLayout, item, getContext()); + .getViewFromVideoInfoItem(null, similarLayout, item); similarView.setClickable(true); similarView.setFocusable(true); @@ -703,8 +754,8 @@ public class VideoItemDetailFragment extends Fragment { .show(); } - private boolean useStream(VideoInfo.VideoStream stream, Vector streams) { - for(VideoInfo.VideoStream i : streams) { + private boolean useStream(StreamInfo.VideoStream stream, Vector streams) { + for(StreamInfo.VideoStream i : streams) { if(i.resolution.equals(stream.resolution)) { return false; } @@ -794,9 +845,9 @@ public class VideoItemDetailFragment extends Fragment { } } - public void playVideo(final VideoInfo info) { + public void playVideo(final StreamInfo info) { // ----------- THE MAGIC MOMENT --------------- - VideoInfo.VideoStream selectedVideoStream = + StreamInfo.VideoStream selectedVideoStream = info.video_streams.get(actionBarHandler.getSelectedVideoStream()); if (PreferenceManager.getDefaultSharedPreferences(activity) diff --git a/app/src/main/java/org/schabi/newpipe/VideoItemListActivity.java b/app/src/main/java/org/schabi/newpipe/VideoItemListActivity.java index ae80d6448..533c69e85 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoItemListActivity.java +++ b/app/src/main/java/org/schabi/newpipe/VideoItemListActivity.java @@ -21,7 +21,6 @@ import org.schabi.newpipe.extractor.ExtractionException; import org.schabi.newpipe.extractor.SearchEngine; import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.StreamingService; -import org.schabi.newpipe.extractor.VideoPreviewInfo; import java.io.IOException; import java.util.ArrayList; @@ -173,11 +172,17 @@ public class VideoItemListActivity extends AppCompatActivity ArrayListsuggestions = engine.suggestionList(query,searchLanguage,new Downloader()); h.post(new SuggestionResultRunnable(suggestions)); } catch (ExtractionException e) { - postNewErrorToast(h, R.string.parsing_error); + ErrorActivity.reportError(h, VideoItemListActivity.this, e, null, findViewById(R.id.videoitem_list), + ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, + /* todo: this shoudl not be assigned static */ "Youtube", query, R.string.parsing_error)); e.printStackTrace(); } catch (IOException e) { postNewErrorToast(h, R.string.network_error); e.printStackTrace(); + } catch (Exception e) { + ErrorActivity.reportError(h, VideoItemListActivity.this, e, null, findViewById(R.id.videoitem_list), + ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, + /* todo: this shoudl not be assigned static */ "Youtube", query, R.string.general_error)); } } } diff --git a/app/src/main/java/org/schabi/newpipe/VideoItemListFragment.java b/app/src/main/java/org/schabi/newpipe/VideoItemListFragment.java index e6a6a5060..fc20aadb9 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoItemListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/VideoItemListFragment.java @@ -1,5 +1,6 @@ package org.schabi.newpipe; +import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.os.Bundle; @@ -17,8 +18,8 @@ import java.io.IOException; import java.util.List; import org.schabi.newpipe.extractor.ExtractionException; -import org.schabi.newpipe.extractor.ParsingException; -import org.schabi.newpipe.extractor.VideoPreviewInfo; +import org.schabi.newpipe.extractor.SearchResult; +import org.schabi.newpipe.extractor.StreamPreviewInfo; import org.schabi.newpipe.extractor.SearchEngine; import org.schabi.newpipe.extractor.StreamingService; @@ -68,9 +69,9 @@ public class VideoItemListFragment extends ListFragment { private boolean loadingNextPage = true; private class ResultRunnable implements Runnable { - private final SearchEngine.Result result; + private final SearchResult result; private final int requestId; - public ResultRunnable(SearchEngine.Result result, int requestId) { + public ResultRunnable(SearchResult result, int requestId) { this.result = result; this.requestId = requestId; } @@ -101,32 +102,60 @@ public class VideoItemListFragment extends ListFragment { } @Override public void run() { + SearchResult result = null; try { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); String searchLanguageKey = getContext().getString(R.string.search_language_key); String searchLanguage = sp.getString(searchLanguageKey, getString(R.string.default_language_value)); - SearchEngine.Result result = engine.search(query, page, searchLanguage, - new Downloader()); + result = SearchResult + .getSearchResult(engine, query, page, searchLanguage, new Downloader()); - //Log.i(TAG, "language code passed:\""+searchLanguage+"\""); if(runs) { h.post(new ResultRunnable(result, requestId)); } + + // look for errors during extraction + // soft errors: + if(result != null && + !result.errors.isEmpty()) { + Log.e(TAG, "OCCURRED ERRORS DURING SEARCH EXTRACTION:"); + for(Exception e : result.errors) { + e.printStackTrace(); + Log.e(TAG, "------"); + } + + Activity a = getActivity(); + View rootView = a.findViewById(R.id.videoitem_list); + ErrorActivity.reportError(h, getActivity(), result.errors, null, rootView, + ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, + /* todo: this shoudl not be assigned static */ "Youtube", query, R.string.light_parsing_error)); + + } + // hard errors: } catch(IOException e) { - postNewErrorToast(h, R.string.network_error); + postNewNothingFoundToast(h, R.string.network_error); e.printStackTrace(); - } catch(ExtractionException ce) { - postNewErrorToast(h, R.string.parsing_error); - ce.printStackTrace(); + } catch(SearchEngine.NothingFoundException e) { + postNewErrorToast(h, e.getMessage()); + } catch(ExtractionException e) { + ErrorActivity.reportError(h, getActivity(), e, null, null, + ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, + /* todo: this shoudl not be assigned static */ "Youtube", query, R.string.parsing_error)); + //postNewErrorToast(h, R.string.parsing_error); + e.printStackTrace(); + } catch(Exception e) { - postNewErrorToast(h, R.string.general_error); + ErrorActivity.reportError(h, getActivity(), e, null, null, + ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED, + /* todo: this shoudl not be assigned static */ "Youtube", query, R.string.general_error)); + e.printStackTrace(); } } } - public void present(List videoList) { + public void present(List videoList) { mode = PRESENT_VIDEOS_MODE; setListShown(true); getListView().smoothScrollToPosition(0); @@ -166,22 +195,19 @@ public class VideoItemListFragment extends ListFragment { this.streamingService = streamingService; } - private void updateListOnResult(SearchEngine.Result result, int requestId) { + private void updateListOnResult(SearchResult result, int requestId) { if(requestId == currentRequestId) { setListShown(true); - if (result.resultList.isEmpty()) { - Toast.makeText(getActivity(), result.errorMessage, Toast.LENGTH_LONG).show(); - } else { - if (!result.suggestion.isEmpty()) { - Toast.makeText(getActivity(), getString(R.string.did_you_mean) + result.suggestion + " ?", - Toast.LENGTH_LONG).show(); - } - updateList(result.resultList); + updateList(result.resultList); + if(!result.suggestion.isEmpty()) { + Toast.makeText(getActivity(), + String.format(getString(R.string.did_you_mean), result.suggestion), + Toast.LENGTH_LONG).show(); } } } - private void updateList(List list) { + private void updateList(List list) { try { videoListAdapter.addVideoList(list); terminateThreads(); @@ -318,13 +344,24 @@ public class VideoItemListFragment extends ListFragment { mActivatedPosition = position; } - private void postNewErrorToast(Handler h, final int stringResource) { + private void postNewErrorToast(Handler h, final String message) { + h.post(new Runnable() { + @Override + public void run() { + setListShown(true); + Toast.makeText(getActivity(), message, + Toast.LENGTH_SHORT).show(); + } + }); + } + + private void postNewNothingFoundToast(Handler h, final int stringResource) { h.post(new Runnable() { @Override public void run() { setListShown(true); Toast.makeText(getActivity(), getString(stringResource), - Toast.LENGTH_SHORT).show(); + Toast.LENGTH_LONG).show(); } }); } diff --git a/app/src/main/java/org/schabi/newpipe/VideoListAdapter.java b/app/src/main/java/org/schabi/newpipe/VideoListAdapter.java index 702e2cfdd..d69b842cf 100644 --- a/app/src/main/java/org/schabi/newpipe/VideoListAdapter.java +++ b/app/src/main/java/org/schabi/newpipe/VideoListAdapter.java @@ -8,7 +8,7 @@ import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ListView; -import org.schabi.newpipe.extractor.VideoPreviewInfo; +import org.schabi.newpipe.extractor.StreamPreviewInfo; import java.util.List; import java.util.Vector; @@ -36,7 +36,7 @@ import java.util.Vector; class VideoListAdapter extends BaseAdapter { private final Context context; private final VideoInfoItemViewCreator viewCreator; - private Vector videoList = new Vector<>(); + private Vector videoList = new Vector<>(); private final ListView listView; public VideoListAdapter(Context context, VideoItemListFragment videoListFragment) { @@ -47,7 +47,7 @@ class VideoListAdapter extends BaseAdapter { this.context = context; } - public void addVideoList(List videos) { + public void addVideoList(List videos) { videoList.addAll(videos); notifyDataSetChanged(); } @@ -57,7 +57,7 @@ class VideoListAdapter extends BaseAdapter { notifyDataSetChanged(); } - public Vector getVideoList() { + public Vector getVideoList() { return videoList; } @@ -78,7 +78,7 @@ class VideoListAdapter extends BaseAdapter { @Override public View getView(int position, View convertView, ViewGroup parent) { - convertView = viewCreator.getViewFromVideoInfoItem(convertView, parent, videoList.get(position), context); + convertView = viewCreator.getViewFromVideoInfoItem(convertView, parent, videoList.get(position)); if(listView.isItemChecked(position)) { convertView.setBackgroundColor(ContextCompat.getColor(context,R.color.light_youtube_primary_color)); diff --git a/app/src/main/java/org/schabi/newpipe/extractor/AbstractVideoInfo.java b/app/src/main/java/org/schabi/newpipe/extractor/AbstractVideoInfo.java index 1fe776e5e..79d43770b 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/AbstractVideoInfo.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/AbstractVideoInfo.java @@ -20,7 +20,7 @@ import android.graphics.Bitmap; * along with NewPipe. If not, see . */ -/**Common properties between VideoInfo and VideoPreviewInfo.*/ +/**Common properties between StreamInfo and StreamPreviewInfo.*/ public abstract class AbstractVideoInfo { public String id = ""; public String title = ""; diff --git a/app/src/main/java/org/schabi/newpipe/extractor/DashMpdParser.java b/app/src/main/java/org/schabi/newpipe/extractor/DashMpdParser.java index 9dfbe8d14..55cae46fa 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/DashMpdParser.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/DashMpdParser.java @@ -37,7 +37,7 @@ public class DashMpdParser { } } - public static List getAudioStreams(String dashManifestUrl, + public static List getAudioStreams(String dashManifestUrl, Downloader downloader) throws DashMpdParsingException { String dashDoc; @@ -46,7 +46,7 @@ public class DashMpdParser { } catch(IOException ioe) { throw new DashMpdParsingException("Could not get dash mpd: " + dashManifestUrl, ioe); } - Vector audioStreams = new Vector<>(); + Vector audioStreams = new Vector<>(); try { XmlPullParser parser = Xml.newPullParser(); parser.setInput(new StringReader(dashDoc)); @@ -83,7 +83,7 @@ public class DashMpdParser { } else if(currentMimeType.equals(MediaFormat.M4A.mimeType)) { format = MediaFormat.M4A.id; } - audioStreams.add(new VideoInfo.AudioStream(parser.getText(), + audioStreams.add(new StreamInfo.AudioStream(parser.getText(), format, currentBandwidth, currentSamplingRate)); } break; diff --git a/app/src/main/java/org/schabi/newpipe/extractor/ExtractionException.java b/app/src/main/java/org/schabi/newpipe/extractor/ExtractionException.java index 68ded9c26..fbbca89c1 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/ExtractionException.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/ExtractionException.java @@ -21,8 +21,6 @@ package org.schabi.newpipe.extractor; */ public class ExtractionException extends Exception { - public ExtractionException() {} - public ExtractionException(String message) { super(message); } diff --git a/app/src/main/java/org/schabi/newpipe/extractor/ParsingException.java b/app/src/main/java/org/schabi/newpipe/extractor/ParsingException.java index c978743ba..5803537bb 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/ParsingException.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/ParsingException.java @@ -22,13 +22,9 @@ package org.schabi.newpipe.extractor; public class ParsingException extends ExtractionException { - public ParsingException() {} public ParsingException(String message) { super(message); } - public ParsingException(Throwable cause) { - super(cause); - } public ParsingException(String message, Throwable cause) { super(message, cause); } diff --git a/app/src/main/java/org/schabi/newpipe/extractor/SearchEngine.java b/app/src/main/java/org/schabi/newpipe/extractor/SearchEngine.java index 74f3f0f60..677287afe 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/SearchEngine.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/SearchEngine.java @@ -27,16 +27,16 @@ import java.util.Vector; @SuppressWarnings("ALL") public interface SearchEngine { - class Result { - public String errorMessage = ""; - public String suggestion = ""; - public final List resultList = new Vector<>(); + public class NothingFoundException extends ExtractionException { + public NothingFoundException(String message) { + super(message); + } } ArrayList suggestionList(String query,String contentCountry, Downloader dl) throws ExtractionException, IOException; //Result search(String query, int page); - Result search(String query, int page, String contentCountry, Downloader dl) + StreamPreviewInfoCollector search(String query, int page, String contentCountry, Downloader dl) throws ExtractionException, IOException; } diff --git a/app/src/main/java/org/schabi/newpipe/extractor/SearchResult.java b/app/src/main/java/org/schabi/newpipe/extractor/SearchResult.java new file mode 100644 index 000000000..d21e2ba62 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/extractor/SearchResult.java @@ -0,0 +1,47 @@ +package org.schabi.newpipe.extractor; + +import java.io.IOException; +import java.util.List; +import java.util.Vector; + +/** + * Created by Christian Schabesberger on 29.02.16. + * + * Copyright (C) Christian Schabesberger 2016 + * SearchResult.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 . + */ + +public class SearchResult { + public static SearchResult getSearchResult(SearchEngine engine, String query, + int page, String languageCode, Downloader dl) + throws ExtractionException, IOException { + + SearchResult result = engine.search(query, page, languageCode, dl).getSearchResult(); + if(result.resultList.isEmpty()) { + if(result.suggestion.isEmpty()) { + throw new ExtractionException("Empty result despite no error"); + } else { + // This is used as a fallback. Do not relay on it !!! + throw new SearchEngine.NothingFoundException(result.suggestion); + } + } + return result; + } + + public String suggestion = ""; + public final List resultList = new Vector<>(); + public List errors = new Vector<>(); +} diff --git a/app/src/main/java/org/schabi/newpipe/extractor/StreamExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/StreamExtractor.java index 2fcea90c2..b1f0249fb 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/StreamExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/StreamExtractor.java @@ -3,7 +3,7 @@ package org.schabi.newpipe.extractor; /** * Created by Christian Schabesberger on 10.08.15. * - * Copyright (C) Christian Schabesberger 2015 + * Copyright (C) Christian Schabesberger 2016 * StreamExtractor.java is part of NewPipe. * * NewPipe is free software: you can redistribute it and/or modify @@ -29,7 +29,6 @@ import java.util.List; public interface StreamExtractor { public class ExctractorInitException extends ExtractionException { - public ExctractorInitException() {} public ExctractorInitException(String message) { super(message); } @@ -42,13 +41,9 @@ public interface StreamExtractor { } public class ContentNotAvailableException extends ParsingException { - public ContentNotAvailableException() {} public ContentNotAvailableException(String message) { super(message); } - public ContentNotAvailableException(Throwable cause) { - super(cause); - } public ContentNotAvailableException(String message, Throwable cause) { super(message, cause); } @@ -63,16 +58,16 @@ public interface StreamExtractor { public abstract String getUploadDate() throws ParsingException; public abstract String getThumbnailUrl() throws ParsingException; public abstract String getUploaderThumbnailUrl() throws ParsingException; - public abstract List getAudioStreams() throws ParsingException; - public abstract List getVideoStreams() throws ParsingException; - public abstract List getVideoOnlyStreams() throws ParsingException; + public abstract List getAudioStreams() throws ParsingException; + public abstract List getVideoStreams() throws ParsingException; + public abstract List getVideoOnlyStreams() throws ParsingException; public abstract String getDashMpdUrl() throws ParsingException; public abstract int getAgeLimit() throws ParsingException; public abstract String getAverageRating() throws ParsingException; public abstract int getLikeCount() throws ParsingException; public abstract int getDislikeCount() throws ParsingException; - public abstract VideoPreviewInfo getNextVideo() throws ParsingException; - public abstract List getRelatedVideos() throws ParsingException; - public abstract VideoUrlIdHandler getUrlIdConverter(); + public abstract StreamPreviewInfo getNextVideo() throws ParsingException; + public abstract List getRelatedVideos() throws ParsingException; + public abstract StreamUrlIdHandler getUrlIdConverter(); public abstract String getPageUrl(); } diff --git a/app/src/main/java/org/schabi/newpipe/extractor/VideoInfo.java b/app/src/main/java/org/schabi/newpipe/extractor/StreamInfo.java similarity index 58% rename from app/src/main/java/org/schabi/newpipe/extractor/VideoInfo.java rename to app/src/main/java/org/schabi/newpipe/extractor/StreamInfo.java index 2ae58a9f2..b6da26404 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/VideoInfo.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/StreamInfo.java @@ -7,8 +7,8 @@ import java.util.Vector; /** * Created by Christian Schabesberger on 26.08.15. * - * Copyright (C) Christian Schabesberger 2015 - * VideoInfo.java is part of NewPipe. + * Copyright (C) Christian Schabesberger 2016 + * StreamInfo.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 @@ -26,181 +26,188 @@ import java.util.Vector; /**Info object for opened videos, ie the video ready to play.*/ @SuppressWarnings("ALL") -public class VideoInfo extends AbstractVideoInfo { +public class StreamInfo extends AbstractVideoInfo { + + public static class StreamExctractException extends ExtractionException { + StreamExctractException(String message) { + super(message); + } + } /**Fills out the video info fields which are common to all services. * Probably needs to be overridden by subclasses*/ - public static VideoInfo getVideoInfo(StreamExtractor extractor, Downloader downloader) + public static StreamInfo getVideoInfo(StreamExtractor extractor, Downloader downloader) throws ExtractionException, IOException { - VideoInfo videoInfo = new VideoInfo(); + StreamInfo streamInfo = new StreamInfo(); - videoInfo = extractImportantData(videoInfo, extractor, downloader); - videoInfo = extractStreams(videoInfo, extractor, downloader); - videoInfo = extractOptionalData(videoInfo, extractor, downloader); + streamInfo = extractImportantData(streamInfo, extractor, downloader); + streamInfo = extractStreams(streamInfo, extractor, downloader); + streamInfo = extractOptionalData(streamInfo, extractor, downloader); - return videoInfo; + return streamInfo; } - private static VideoInfo extractImportantData( - VideoInfo videoInfo, StreamExtractor extractor, Downloader downloader) + private static StreamInfo extractImportantData( + StreamInfo streamInfo, StreamExtractor extractor, Downloader downloader) throws ExtractionException, IOException { /* ---- importand data, withoug the video can't be displayed goes here: ---- */ // if one of these is not available an exception is ment to be thrown directly into the frontend. - VideoUrlIdHandler uiconv = extractor.getUrlIdConverter(); + StreamUrlIdHandler uiconv = extractor.getUrlIdConverter(); - videoInfo.webpage_url = extractor.getPageUrl(); - videoInfo.id = uiconv.getVideoId(extractor.getPageUrl()); - videoInfo.title = extractor.getTitle(); - videoInfo.age_limit = extractor.getAgeLimit(); + streamInfo.webpage_url = extractor.getPageUrl(); + streamInfo.id = uiconv.getVideoId(extractor.getPageUrl()); + streamInfo.title = extractor.getTitle(); + streamInfo.age_limit = extractor.getAgeLimit(); - if((videoInfo.webpage_url == null || videoInfo.webpage_url.isEmpty()) - || (videoInfo.id == null || videoInfo.id.isEmpty()) - || (videoInfo.title == null /* videoInfo.title can be empty of course */) - || (videoInfo.age_limit == -1)); + if((streamInfo.webpage_url == null || streamInfo.webpage_url.isEmpty()) + || (streamInfo.id == null || streamInfo.id.isEmpty()) + || (streamInfo.title == null /* streamInfo.title can be empty of course */) + || (streamInfo.age_limit == -1)); - return videoInfo; + return streamInfo; } - private static VideoInfo extractStreams( - VideoInfo videoInfo, StreamExtractor extractor, Downloader downloader) + private static StreamInfo extractStreams( + StreamInfo streamInfo, StreamExtractor extractor, Downloader downloader) throws ExtractionException, IOException { /* ---- stream extraction goes here ---- */ // At least one type of stream has to be available, // otherwise an exception will be thrown directly into the frontend. try { - videoInfo.dashMpdUrl = extractor.getDashMpdUrl(); + streamInfo.dashMpdUrl = extractor.getDashMpdUrl(); } catch(Exception e) { - videoInfo.addException(new ExtractionException("Couldn't get Dash manifest", e)); + streamInfo.addException(new ExtractionException("Couldn't get Dash manifest", e)); } /* Load and extract audio */ try { - videoInfo.audio_streams = extractor.getAudioStreams(); + streamInfo.audio_streams = extractor.getAudioStreams(); } catch(Exception e) { - videoInfo.addException(new ExtractionException("Couldn't get audio streams", e)); + streamInfo.addException(new ExtractionException("Couldn't get audio streams", e)); } // also try to get streams from the dashMpd - if(videoInfo.dashMpdUrl != null && !videoInfo.dashMpdUrl.isEmpty()) { - if(videoInfo.audio_streams == null) { - videoInfo.audio_streams = new Vector(); + if(streamInfo.dashMpdUrl != null && !streamInfo.dashMpdUrl.isEmpty()) { + if(streamInfo.audio_streams == null) { + streamInfo.audio_streams = new Vector(); } //todo: make this quick and dirty solution a real fallback // same as the quick and dirty aboth try { - videoInfo.audio_streams.addAll( - DashMpdParser.getAudioStreams(videoInfo.dashMpdUrl, downloader)); + streamInfo.audio_streams.addAll( + DashMpdParser.getAudioStreams(streamInfo.dashMpdUrl, downloader)); } catch(Exception e) { - videoInfo.addException( + streamInfo.addException( new ExtractionException("Couldn't get audio streams from dash mpd", e)); } } /* Extract video stream url*/ try { - videoInfo.video_streams = extractor.getVideoStreams(); + streamInfo.video_streams = extractor.getVideoStreams(); } catch (Exception e) { - videoInfo.addException( + streamInfo.addException( new ExtractionException("Couldn't get video streams", e)); } /* Extract video only stream url*/ try { - videoInfo.video_only_streams = extractor.getVideoOnlyStreams(); + streamInfo.video_only_streams = extractor.getVideoOnlyStreams(); } catch(Exception e) { - videoInfo.addException( + streamInfo.addException( new ExtractionException("Couldn't get video only streams", e)); } // either dash_mpd audio_only or video has to be available, otherwise we didn't get a stream, // and therefore failed. (Since video_only_streams are just optional they don't caunt). - if((videoInfo.video_streams == null || videoInfo.video_streams.isEmpty()) - && (videoInfo.audio_streams == null || videoInfo.audio_streams.isEmpty()) - && (videoInfo.dashMpdUrl == null || videoInfo.dashMpdUrl.isEmpty())) { - throw new ExtractionException("Could not get any stream. See error variable to get further details."); + if((streamInfo.video_streams == null || streamInfo.video_streams.isEmpty()) + && (streamInfo.audio_streams == null || streamInfo.audio_streams.isEmpty()) + && (streamInfo.dashMpdUrl == null || streamInfo.dashMpdUrl.isEmpty())) { + throw new StreamExctractException( + "Could not get any stream. See error variable to get further details."); } - return videoInfo; + return streamInfo; } - private static VideoInfo extractOptionalData( - VideoInfo videoInfo, StreamExtractor extractor, Downloader downloader) { + private static StreamInfo extractOptionalData( + StreamInfo streamInfo, StreamExtractor extractor, Downloader downloader) { /* ---- optional data goes here: ---- */ // If one of these failes, the frontend neets to handle that they are not available. // Exceptions are therfore not thrown into the frontend, but stored into the error List, // so the frontend can afterwads check where errors happend. try { - videoInfo.thumbnail_url = extractor.getThumbnailUrl(); + streamInfo.thumbnail_url = extractor.getThumbnailUrl(); } catch(Exception e) { - videoInfo.addException(e); + streamInfo.addException(e); } try { - videoInfo.duration = extractor.getLength(); + streamInfo.duration = extractor.getLength(); } catch(Exception e) { - videoInfo.addException(e); + streamInfo.addException(e); } try { - videoInfo.uploader = extractor.getUploader(); + streamInfo.uploader = extractor.getUploader(); } catch(Exception e) { - videoInfo.addException(e); + streamInfo.addException(e); } try { - videoInfo.description = extractor.getDescription(); + streamInfo.description = extractor.getDescription(); } catch(Exception e) { - videoInfo.addException(e); + streamInfo.addException(e); } try { - videoInfo.view_count = extractor.getViewCount(); + streamInfo.view_count = extractor.getViewCount(); } catch(Exception e) { - videoInfo.addException(e); + streamInfo.addException(e); } try { - videoInfo.upload_date = extractor.getUploadDate(); + streamInfo.upload_date = extractor.getUploadDate(); } catch(Exception e) { - videoInfo.addException(e); + streamInfo.addException(e); } try { - videoInfo.uploader_thumbnail_url = extractor.getUploaderThumbnailUrl(); + streamInfo.uploader_thumbnail_url = extractor.getUploaderThumbnailUrl(); } catch(Exception e) { - videoInfo.addException(e); + streamInfo.addException(e); } try { - videoInfo.start_position = extractor.getTimeStamp(); + streamInfo.start_position = extractor.getTimeStamp(); } catch(Exception e) { - videoInfo.addException(e); + streamInfo.addException(e); } try { - videoInfo.average_rating = extractor.getAverageRating(); + streamInfo.average_rating = extractor.getAverageRating(); } catch(Exception e) { - videoInfo.addException(e); + streamInfo.addException(e); } try { - videoInfo.like_count = extractor.getLikeCount(); + streamInfo.like_count = extractor.getLikeCount(); } catch(Exception e) { - videoInfo.addException(e); + streamInfo.addException(e); } try { - videoInfo.dislike_count = extractor.getDislikeCount(); + streamInfo.dislike_count = extractor.getDislikeCount(); } catch(Exception e) { - videoInfo.addException(e); + streamInfo.addException(e); } try { - videoInfo.next_video = extractor.getNextVideo(); + streamInfo.next_video = extractor.getNextVideo(); } catch(Exception e) { - videoInfo.addException(e); + streamInfo.addException(e); } try { - videoInfo.related_videos = extractor.getRelatedVideos(); + streamInfo.related_videos = extractor.getRelatedVideos(); } catch(Exception e) { - videoInfo.addException(e); + streamInfo.addException(e); } try { } catch (Exception e) { - videoInfo.addException(e); + streamInfo.addException(e); } - return videoInfo; + return streamInfo; } public String uploader_thumbnail_url = ""; @@ -220,20 +227,20 @@ public class VideoInfo extends AbstractVideoInfo { public int like_count = -1; public int dislike_count = -1; public String average_rating = ""; - public VideoPreviewInfo next_video = null; - public List related_videos = null; - //in seconds. some metadata is not passed using a VideoInfo object! + public StreamPreviewInfo next_video = null; + public List related_videos = null; + //in seconds. some metadata is not passed using a StreamInfo object! public int start_position = 0; //todo: public int service_id = -1; public List errors = new Vector<>(); - public VideoInfo() {} + public StreamInfo() {} - /**Creates a new VideoInfo object from an existing AbstractVideoInfo. - * All the shared properties are copied to the new VideoInfo.*/ + /**Creates a new StreamInfo object from an existing AbstractVideoInfo. + * All the shared properties are copied to the new StreamInfo.*/ @SuppressWarnings("WeakerAccess") - public VideoInfo(AbstractVideoInfo avi) { + public StreamInfo(AbstractVideoInfo avi) { this.id = avi.id; this.title = avi.title; this.uploader = avi.uploader; @@ -245,9 +252,9 @@ public class VideoInfo extends AbstractVideoInfo { this.view_count = avi.view_count; //todo: better than this - if(avi instanceof VideoPreviewInfo) { + if(avi instanceof StreamPreviewInfo) { //shitty String to convert code - String dur = ((VideoPreviewInfo)avi).duration; + String dur = ((StreamPreviewInfo)avi).duration; int minutes = Integer.parseInt(dur.substring(0, dur.indexOf(":"))); int seconds = Integer.parseInt(dur.substring(dur.indexOf(":")+1, dur.length())); this.duration = (minutes*60)+seconds; diff --git a/app/src/main/java/org/schabi/newpipe/extractor/VideoPreviewInfo.java b/app/src/main/java/org/schabi/newpipe/extractor/StreamPreviewInfo.java similarity index 77% rename from app/src/main/java/org/schabi/newpipe/extractor/VideoPreviewInfo.java rename to app/src/main/java/org/schabi/newpipe/extractor/StreamPreviewInfo.java index 29bab20bd..5ab1f75fc 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/VideoPreviewInfo.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/StreamPreviewInfo.java @@ -1,14 +1,10 @@ package org.schabi.newpipe.extractor; -import android.graphics.Bitmap; -import android.os.Parcel; -import android.os.Parcelable; - /** * Created by Christian Schabesberger on 26.08.15. * - * Copyright (C) Christian Schabesberger 2015 - * VideoPreviewInfo.java is part of NewPipe. + * Copyright (C) Christian Schabesberger 2016 + * StreamPreviewInfo.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 @@ -25,6 +21,6 @@ import android.os.Parcelable; */ /**Info object for previews of unopened videos, eg search results, related videos*/ -public class VideoPreviewInfo extends AbstractVideoInfo { +public class StreamPreviewInfo extends AbstractVideoInfo { public String duration = ""; } \ No newline at end of file diff --git a/app/src/main/java/org/schabi/newpipe/extractor/StreamPreviewInfoCollector.java b/app/src/main/java/org/schabi/newpipe/extractor/StreamPreviewInfoCollector.java new file mode 100644 index 000000000..c22670065 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/extractor/StreamPreviewInfoCollector.java @@ -0,0 +1,91 @@ +package org.schabi.newpipe.extractor; + +import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamUrlIdHandler; + +/** + * Created by Christian Schabesberger on 28.02.16. + * + * Copyright (C) Christian Schabesberger 2016 + * StreamPreviewInfoCollector.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 . + */ + +public class StreamPreviewInfoCollector { + SearchResult result = new SearchResult(); + StreamUrlIdHandler urlIdHandler = null; + + public StreamPreviewInfoCollector(StreamUrlIdHandler handler) { + urlIdHandler = handler; + } + + public void setSuggestion(String suggestion) { + result.suggestion = suggestion; + } + + public void addError(Exception e) { + result.errors.add(e); + } + + public SearchResult getSearchResult() { + return result; + } + + public void commit(StreamPreviewInfoExtractor extractor) throws ParsingException { + try { + StreamPreviewInfo resultItem = new StreamPreviewInfo(); + // importand information + resultItem.webpage_url = extractor.getWebPageUrl(); + if (urlIdHandler == null) { + throw new ParsingException("Error: UrlIdHandler not set"); + } else { + resultItem.id = (new YoutubeStreamUrlIdHandler()).getVideoId(resultItem.webpage_url); + } + resultItem.title = extractor.getTitle(); + + // optional iformation + try { + resultItem.duration = extractor.getDuration(); + } catch (Exception e) { + addError(e); + } + try { + resultItem.uploader = extractor.getUploader(); + } catch (Exception e) { + addError(e); + } + try { + resultItem.upload_date = extractor.getUploadDate(); + } catch (Exception e) { + addError(e); + } + try { + resultItem.view_count = extractor.getViewCount(); + } catch (Exception e) { + addError(e); + } + try { + resultItem.thumbnail_url = extractor.getThumbnailUrl(); + } catch (Exception e) { + addError(e); + } + + + result.resultList.add(resultItem); + } catch (Exception e) { + addError(e); + } + + } +} diff --git a/app/src/main/java/org/schabi/newpipe/extractor/StreamPreviewInfoExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/StreamPreviewInfoExtractor.java new file mode 100644 index 000000000..4146acd84 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/extractor/StreamPreviewInfoExtractor.java @@ -0,0 +1,31 @@ +package org.schabi.newpipe.extractor; + +/** + * Created by Christian Schabesberger on 28.02.16. + * + * Copyright (C) Christian Schabesberger 2016 + * StreamPreviewInfoExtractor.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 . + */ + +public interface StreamPreviewInfoExtractor { + String getWebPageUrl() throws ParsingException; + String getTitle() throws ParsingException; + String getDuration() throws ParsingException; + String getUploader() throws ParsingException; + String getUploadDate() throws ParsingException; + long getViewCount() throws ParsingException; + String getThumbnailUrl() throws ParsingException; +} diff --git a/app/src/main/java/org/schabi/newpipe/extractor/VideoUrlIdHandler.java b/app/src/main/java/org/schabi/newpipe/extractor/StreamUrlIdHandler.java similarity index 93% rename from app/src/main/java/org/schabi/newpipe/extractor/VideoUrlIdHandler.java rename to app/src/main/java/org/schabi/newpipe/extractor/StreamUrlIdHandler.java index 88736896f..4f91ac80e 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/VideoUrlIdHandler.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/StreamUrlIdHandler.java @@ -4,7 +4,7 @@ package org.schabi.newpipe.extractor; * Created by Christian Schabesberger on 02.02.16. * * Copyright (C) Christian Schabesberger 2016 - * VideoUrlIdHandler.java is part of NewPipe. + * StreamUrlIdHandler.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 @@ -20,7 +20,7 @@ package org.schabi.newpipe.extractor; * along with NewPipe. If not, see . */ -public interface VideoUrlIdHandler { +public interface StreamUrlIdHandler { String getVideoUrl(String videoId); String getVideoId(String siteUrl) throws ParsingException; String cleanUrl(String siteUrl) throws ParsingException; diff --git a/app/src/main/java/org/schabi/newpipe/extractor/StreamingService.java b/app/src/main/java/org/schabi/newpipe/extractor/StreamingService.java index 88e2757e4..5e929e9d1 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/StreamingService.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/StreamingService.java @@ -5,7 +5,7 @@ import java.io.IOException; /** * Created by Christian Schabesberger on 23.08.15. * - * Copyright (C) Christian Schabesberger 2015 + * Copyright (C) Christian Schabesberger 2016 * StreamingService.java is part of NewPipe. * * NewPipe is free software: you can redistribute it and/or modify @@ -31,7 +31,7 @@ public interface StreamingService { throws IOException, ExtractionException; SearchEngine getSearchEngineInstance(); - VideoUrlIdHandler getUrlIdHandler(); + StreamUrlIdHandler getUrlIdHandler(); } diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngine.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngine.java index ed0b09036..4e6010108 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngine.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngine.java @@ -7,10 +7,13 @@ import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; 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.SearchEngine; -import org.schabi.newpipe.extractor.VideoPreviewInfo; +import org.schabi.newpipe.extractor.StreamExtractor; +import org.schabi.newpipe.extractor.StreamPreviewInfoCollector; +import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; @@ -49,9 +52,10 @@ public class YoutubeSearchEngine implements SearchEngine { private static final String TAG = YoutubeSearchEngine.class.toString(); @Override - public Result search(String query, int page, String languageCode, Downloader downloader) - throws IOException, ParsingException { - Result result = new Result(); + public StreamPreviewInfoCollector search(String query, int page, String languageCode, Downloader downloader) + throws IOException, ExtractionException { + StreamPreviewInfoCollector collector = new StreamPreviewInfoCollector( + new YoutubeStreamUrlIdHandler()); Uri.Builder builder = new Uri.Builder(); builder.scheme("https") .authority("www.youtube.com") @@ -71,12 +75,11 @@ public class YoutubeSearchEngine implements SearchEngine { site = downloader.download(url); } - try { - Document doc = Jsoup.parse(site, url); - Element list = doc.select("ol[class=\"item-section\"]").first(); + Document doc = Jsoup.parse(site, url); + Element list = doc.select("ol[class=\"item-section\"]").first(); - for (Element item : list.children()) { + for (Element item : list.children()) { /* First we need to determine which kind of item we are working with. Youtube depicts five different kinds of items on its search result page. These are regular videos, playlists, channels, two types of video suggestions, and a "no video @@ -88,66 +91,33 @@ public class YoutubeSearchEngine implements SearchEngine { playlists now. */ - Element el; + Element el; - // both types of spell correction item - if (!((el = item.select("div[class*=\"spell-correction\"]").first()) == null)) { - result.suggestion = el.select("a").first().text(); - // search message item - } else if (!((el = item.select("div[class*=\"search-message\"]").first()) == null)) { - result.errorMessage = el.text(); - - // video item type - } else if (!((el = item.select("div[class*=\"yt-lockup-video\"").first()) == null)) { - VideoPreviewInfo resultItem = new VideoPreviewInfo(); - - // importand information - resultItem.webpage_url = getWebpageUrl(item); - resultItem.id = (new YoutubeVideoUrlIdHandler()).getVideoId(resultItem.webpage_url); - resultItem.title = getTitle(item); - - // optional iformation - //todo: make this a proper error handling - try { - resultItem.duration = getDuration(item); - } catch (Exception e) { - e.printStackTrace(); - } - try { - resultItem.uploader = getUploader(item); - } catch (Exception e) { - e.printStackTrace(); - } - try { - resultItem.upload_date = getUploadDate(item); - } catch (Exception e) { - e.printStackTrace(); - } - try { - resultItem.view_count = getViewCount(item); - } catch (Exception e) { - e.printStackTrace(); - } - try { - resultItem.thumbnail_url = getThumbnailUrl(item); - } catch (Exception e) { - e.printStackTrace(); - } - - result.resultList.add(resultItem); - } else { - //noinspection ConstantConditions - Log.e(TAG, "unexpected element found:\"" + el + "\""); + // both types of spell correction item + if (!((el = item.select("div[class*=\"spell-correction\"]").first()) == null)) { + collector.setSuggestion(el.select("a").first().text()); + if(list.children().size() == 1) { + throw new NothingFoundException("Did you mean: " + el.select("a").first().text()); } + // search message item + } else if (!((el = item.select("div[class*=\"search-message\"]").first()) == null)) { + //result.errorMessage = el.text(); + throw new NothingFoundException(el.text()); + + // video item type + } else if (!((el = item.select("div[class*=\"yt-lockup-video\"").first()) == null)) { + collector.commit(extractPreviewInfo(el)); + } else { + //noinspection ConstantConditions + collector.addError(new Exception("unexpected element found:\"" + el + "\"")); } - } catch(Exception e) { - throw new ParsingException(e); } - return result; + + return collector; } @Override - public ArrayList suggestionList(String query,String contentCountry, Downloader dl) + public ArrayList suggestionList(String query, String contentCountry, Downloader dl) throws IOException, ParsingException { ArrayList suggestions = new ArrayList<>(); @@ -167,103 +137,115 @@ public class YoutubeSearchEngine implements SearchEngine { String response = dl.download(url); + //TODO: Parse xml data using Jsoup not done + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder; + org.w3c.dom.Document doc = null; + try { + dBuilder = dbFactory.newDocumentBuilder(); + doc = dBuilder.parse(new InputSource( + new ByteArrayInputStream(response.getBytes("utf-8")))); + doc.getDocumentElement().normalize(); + } catch (ParserConfigurationException | SAXException | IOException e) { + throw new ParsingException("Could not parse document."); + } - //TODO: Parse xml data using Jsoup not done - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder dBuilder; - org.w3c.dom.Document doc = null; + try { + NodeList nList = doc.getElementsByTagName("CompleteSuggestion"); + for (int temp = 0; temp < nList.getLength(); temp++) { - try { - dBuilder = dbFactory.newDocumentBuilder(); - doc = dBuilder.parse(new InputSource( - new ByteArrayInputStream(response.getBytes("utf-8")))); - doc.getDocumentElement().normalize(); - } catch (ParserConfigurationException | SAXException | IOException e) { - e.printStackTrace(); - } - - if (doc != null) { - NodeList nList = doc.getElementsByTagName("CompleteSuggestion"); - for (int temp = 0; temp < nList.getLength(); temp++) { - - NodeList nList1 = doc.getElementsByTagName("suggestion"); - Node nNode1 = nList1.item(temp); - if (nNode1.getNodeType() == Node.ELEMENT_NODE) { - org.w3c.dom.Element eElement = (org.w3c.dom.Element) nNode1; - suggestions.add(eElement.getAttribute("data")); - } + NodeList nList1 = doc.getElementsByTagName("suggestion"); + Node nNode1 = nList1.item(temp); + if (nNode1.getNodeType() == Node.ELEMENT_NODE) { + org.w3c.dom.Element eElement = (org.w3c.dom.Element) nNode1; + suggestions.add(eElement.getAttribute("data")); } - } else { - Log.e(TAG, "GREAT FUCKING ERROR"); } return suggestions; } catch(Exception e) { - throw new ParsingException(e); + throw new ParsingException("Could not get suggestions form document.", e); } } - private String getWebpageUrl(Element item) { - Element el = item.select("div[class*=\"yt-lockup-video\"").first(); - Element dl = el.select("h3").first().select("a").first(); - return dl.attr("abs:href"); - } + private StreamPreviewInfoExtractor extractPreviewInfo(final Element item) { + return new StreamPreviewInfoExtractor() { + @Override + public String getWebPageUrl() throws ParsingException { + Element el = item.select("div[class*=\"yt-lockup-video\"").first(); + Element dl = el.select("h3").first().select("a").first(); + return dl.attr("abs:href"); + } - private String getTitle(Element item) { - Element el = item.select("div[class*=\"yt-lockup-video\"").first(); - Element dl = el.select("h3").first().select("a").first(); - return dl.text(); - } + @Override + public String getTitle() throws ParsingException { + Element el = item.select("div[class*=\"yt-lockup-video\"").first(); + Element dl = el.select("h3").first().select("a").first(); + return dl.text(); + } - private String getDuration(Element item) { - try { - return item.select("span[class=\"video-time\"]").first().text(); - } catch(Exception e) { - e.printStackTrace(); - } - return ""; - } + @Override + public String getDuration() throws ParsingException { + try { + return item.select("span[class=\"video-time\"]").first().text(); + } catch(Exception e) { + e.printStackTrace(); + } + return ""; + } - private String getUploader(Element item) { - return item.select("div[class=\"yt-lockup-byline\"]").first() - .select("a").first() - .text(); - } + @Override + public String getUploader() throws ParsingException { + return item.select("div[class=\"yt-lockup-byline\"]").first() + .select("a").first() + .text(); + } - private String getUploadDate(Element item) { - return item.select("div[class=\"yt-lockup-meta\"]").first() - .select("li").first() - .text(); - } + @Override + public String getUploadDate() throws ParsingException { + return item.select("div[class=\"yt-lockup-meta\"]").first() + .select("li").first() + .text(); + } - private long getViewCount(Element item) throws Parser.RegexException{ - String output; - String input = item.select("div[class=\"yt-lockup-meta\"]").first() - .select("li").get(1) - .text(); - output = Parser.matchGroup1("([0-9,\\. ]*)", input) - .replace(" ", "") - .replace(".", "") - .replace(",", ""); + @Override + public long getViewCount() throws ParsingException { + String output; + String input = item.select("div[class=\"yt-lockup-meta\"]").first() + .select("li").get(1) + .text(); + output = Parser.matchGroup1("([0-9,\\. ]*)", input) + .replace(" ", "") + .replace(".", "") + .replace(",", ""); - if(Long.parseLong(output) == 30) { - Log.d(TAG, "bla"); - } - return Long.parseLong(output); - } + 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); + } + } + } - private String getThumbnailUrl(Element item) { - String url; - Element te = item.select("div[class=\"yt-thumb video-thumb\"]").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"); - } + @Override + public String getThumbnailUrl() throws ParsingException { + String url; + Element te = item.select("div[class=\"yt-thumb video-thumb\"]").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; + return url; + } + }; } } diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java index 768337043..f35ae4044 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java @@ -4,7 +4,7 @@ import org.schabi.newpipe.extractor.ExtractionException; import org.schabi.newpipe.extractor.Downloader; import org.schabi.newpipe.extractor.StreamExtractor; import org.schabi.newpipe.extractor.StreamingService; -import org.schabi.newpipe.extractor.VideoUrlIdHandler; +import org.schabi.newpipe.extractor.StreamUrlIdHandler; import org.schabi.newpipe.extractor.SearchEngine; import java.io.IOException; @@ -40,7 +40,7 @@ public class YoutubeService implements StreamingService { @Override public StreamExtractor getExtractorInstance(String url, Downloader downloader) throws ExtractionException, IOException { - VideoUrlIdHandler urlIdHandler = new YoutubeVideoUrlIdHandler(); + StreamUrlIdHandler urlIdHandler = new YoutubeStreamUrlIdHandler(); if(urlIdHandler.acceptUrl(url)) { return new YoutubeStreamExtractor(url, downloader) ; } @@ -54,7 +54,7 @@ public class YoutubeService implements StreamingService { } @Override - public VideoUrlIdHandler getUrlIdHandler() { - return new YoutubeVideoUrlIdHandler(); + public StreamUrlIdHandler getUrlIdHandler() { + return new YoutubeStreamUrlIdHandler(); } } diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java index b8dfe9014..db137a145 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java @@ -14,11 +14,11 @@ import org.schabi.newpipe.extractor.ExtractionException; import org.schabi.newpipe.extractor.Downloader; import org.schabi.newpipe.extractor.Parser; import org.schabi.newpipe.extractor.ParsingException; -import org.schabi.newpipe.extractor.VideoUrlIdHandler; +import org.schabi.newpipe.extractor.StreamInfo; +import org.schabi.newpipe.extractor.StreamPreviewInfo; +import org.schabi.newpipe.extractor.StreamUrlIdHandler; import org.schabi.newpipe.extractor.StreamExtractor; import org.schabi.newpipe.extractor.MediaFormat; -import org.schabi.newpipe.extractor.VideoInfo; -import org.schabi.newpipe.extractor.VideoPreviewInfo; import java.io.IOException; import java.util.List; @@ -52,9 +52,6 @@ public class YoutubeStreamExtractor implements StreamExtractor { // exceptions public class DecryptException extends ParsingException { - DecryptException(Throwable cause) { - super(cause); - } DecryptException(String message, Throwable cause) { super(message, cause); } @@ -69,8 +66,8 @@ public class YoutubeStreamExtractor implements StreamExtractor { } public class LiveStreamException extends ContentNotAvailableException { - LiveStreamException() { - super(); + LiveStreamException(String message) { + super(message); } } @@ -179,7 +176,7 @@ public class YoutubeStreamExtractor implements StreamExtractor { // cached values private static volatile String decryptionCode = ""; - VideoUrlIdHandler urlidhandler = new YoutubeVideoUrlIdHandler(); + StreamUrlIdHandler urlidhandler = new YoutubeStreamUrlIdHandler(); String pageUrl = ""; private Downloader downloader; @@ -250,7 +247,7 @@ public class YoutubeStreamExtractor implements StreamExtractor { throw new ParsingException("Could not parse yt player config", e); } if (isLiveStream) { - throw new LiveStreamException(); + throw new LiveStreamException("This is a Life stream. Can't use those right now."); } return playerArgs; @@ -433,8 +430,8 @@ public class YoutubeStreamExtractor implements StreamExtractor { @Override - public List getAudioStreams() throws ParsingException { - Vector audioStreams = new Vector<>(); + public List getAudioStreams() throws ParsingException { + Vector audioStreams = new Vector<>(); try{ String encoded_url_map; // playerArgs could be null if the video is age restricted @@ -461,7 +458,7 @@ public class YoutubeStreamExtractor implements StreamExtractor { + decryptSignature(tags.get("s"), decryptionCode); } - audioStreams.add(new VideoInfo.AudioStream(streamUrl, + audioStreams.add(new StreamInfo.AudioStream(streamUrl, itagItem.mediaFormatId, itagItem.bandWidth, itagItem.samplingRate)); @@ -475,8 +472,8 @@ public class YoutubeStreamExtractor implements StreamExtractor { } @Override - public List getVideoStreams() throws ParsingException { - Vector videoStreams = new Vector<>(); + public List getVideoStreams() throws ParsingException { + Vector videoStreams = new Vector<>(); try{ String encoded_url_map; @@ -504,7 +501,7 @@ public class YoutubeStreamExtractor implements StreamExtractor { streamUrl = streamUrl + "&signature=" + decryptSignature(tags.get("s"), decryptionCode); } - videoStreams.add(new VideoInfo.VideoStream( + videoStreams.add(new StreamInfo.VideoStream( streamUrl, itagItem.mediaFormatId, itagItem.resolutionString)); @@ -527,7 +524,7 @@ public class YoutubeStreamExtractor implements StreamExtractor { } @Override - public List getVideoOnlyStreams() throws ParsingException { + public List getVideoOnlyStreams() throws ParsingException { return null; } @@ -638,7 +635,7 @@ public class YoutubeStreamExtractor implements StreamExtractor { } @Override - public VideoPreviewInfo getNextVideo() throws ParsingException { + public StreamPreviewInfo getNextVideo() throws ParsingException { try { return extractVideoPreviewInfo(doc.select("div[class=\"watch-sidebar-section\"]").first() .select("li").first()); @@ -648,9 +645,9 @@ public class YoutubeStreamExtractor implements StreamExtractor { } @Override - public Vector getRelatedVideos() throws ParsingException { + public Vector getRelatedVideos() throws ParsingException { try { - Vector relatedVideos = new Vector<>(); + 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) { @@ -664,8 +661,8 @@ public class YoutubeStreamExtractor implements StreamExtractor { } @Override - public VideoUrlIdHandler getUrlIdConverter() { - return new YoutubeVideoUrlIdHandler(); + public StreamUrlIdHandler getUrlIdConverter() { + return new YoutubeStreamUrlIdHandler(); } @Override @@ -674,10 +671,10 @@ public class YoutubeStreamExtractor implements StreamExtractor { } /**Provides information about links to other videos on the video page, such as related videos. - * This is encapsulated in a VideoPreviewInfo object, - * which is a subset of the fields in a full VideoInfo.*/ - private VideoPreviewInfo extractVideoPreviewInfo(Element li) throws ParsingException { - VideoPreviewInfo info = new VideoPreviewInfo(); + * This is encapsulated in a StreamPreviewInfo object, + * which is a subset of the fields in a full StreamInfo.*/ + private StreamPreviewInfo extractVideoPreviewInfo(Element li) throws ParsingException { + StreamPreviewInfo info = new StreamPreviewInfo(); try { info.webpage_url = li.select("a.content-link").first() @@ -718,7 +715,7 @@ public class YoutubeStreamExtractor implements StreamExtractor { info.thumbnail_url = "https:" + info.thumbnail_url; } } catch (Exception e) { - throw new ParsingException(e); + throw new ParsingException("Could not get video preview info", e); } return info; } @@ -772,7 +769,7 @@ public class YoutubeStreamExtractor implements StreamExtractor { Function decryptionFunc = (Function) scope.get("decrypt", scope); result = decryptionFunc.call(context, scope, scope, new Object[]{encryptedSig}); } catch (Exception e) { - throw new DecryptException(e); + throw new DecryptException("could not get decrypt signature", e); } finally { Context.exit(); } diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeVideoUrlIdHandler.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamUrlIdHandler.java similarity index 93% rename from app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeVideoUrlIdHandler.java rename to app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamUrlIdHandler.java index 23a294741..998e93a51 100644 --- a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeVideoUrlIdHandler.java +++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamUrlIdHandler.java @@ -2,7 +2,7 @@ package org.schabi.newpipe.extractor.services.youtube; import org.schabi.newpipe.extractor.Parser; import org.schabi.newpipe.extractor.ParsingException; -import org.schabi.newpipe.extractor.VideoUrlIdHandler; +import org.schabi.newpipe.extractor.StreamUrlIdHandler; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; @@ -11,7 +11,7 @@ import java.net.URLDecoder; * Created by Christian Schabesberger on 02.02.16. * * Copyright (C) Christian Schabesberger 2016 - * YoutubeVideoUrlIdHandler.java is part of NewPipe. + * YoutubeStreamUrlIdHandler.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 @@ -27,7 +27,7 @@ import java.net.URLDecoder; * along with NewPipe. If not, see . */ -public class YoutubeVideoUrlIdHandler implements VideoUrlIdHandler { +public class YoutubeStreamUrlIdHandler implements StreamUrlIdHandler { @SuppressWarnings("WeakerAccess") @Override public String getVideoUrl(String videoId) { diff --git a/app/src/main/res/layout/activity_error.xml b/app/src/main/res/layout/activity_error.xml new file mode 100644 index 000000000..c6684263f --- /dev/null +++ b/app/src/main/res/layout/activity_error.xml @@ -0,0 +1,125 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +