Merge branch 'advancedErrorHandling'

This commit is contained in:
Christian Schabesberger 2016-03-02 00:29:56 +01:00
commit 3aecd15916
35 changed files with 1234 additions and 416 deletions

View File

@ -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'
}

View File

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

View File

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

View File

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

View File

@ -1,23 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.schabi.newpipe" >
<uses-permission android:name= "android.permission.INTERNET" />
<uses-permission android:name= "android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
package="org.schabi.newpipe">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application
android:name=".App"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:logo="@mipmap/ic_launcher"
android:label="@string/app_name"
android:logo="@mipmap/ic_launcher"
android:theme="@style/AppTheme"
tools:ignore="AllowBackup">
<activity
android:name=".VideoItemListActivity"
android:label="@string/app_name"
android:configChanges="orientation|screenSize">
android:configChanges="orientation|screenSize"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
@ -26,10 +28,10 @@
</activity>
<activity
android:name=".VideoItemDetailActivity"
android:label="@string/title_videoitem_detail"
android:theme="@style/AppTheme"
android:configChanges="orientation|screenSize"
android:screenOrientation="portrait">
android:label="@string/title_videoitem_detail"
android:screenOrientation="portrait"
android:theme="@style/AppTheme">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".VideoItemListActivity" />
@ -76,20 +78,21 @@
<data android:scheme="vnd.youtube.launch" />
</intent-filter>
</activity>
<activity android:name=".PlayVideoActivity"
<activity
android:name=".PlayVideoActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/VideoPlayerTheme"
android:parentActivityName=".VideoItemDetailActivity"
tools:ignore="UnusedAttribute">
</activity>
android:theme="@style/VideoPlayerTheme"
tools:ignore="UnusedAttribute"></activity>
<service
android:name=".BackgroundPlayer"
android:label="@string/background_player_name"
android:exported="false" />
android:exported="false"
android:label="@string/background_player_name" />
<activity
android:name=".SettingsActivity"
android:label="@string/settings_activity_title" >
</activity>
android:label="@string/settings_activity_title"></activity>
<activity
android:name=".PanicResponderActivity"
android:launchMode="singleInstance"
@ -97,11 +100,15 @@
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="info.guardianproject.panic.action.TRIGGER" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".ExitActivity"
android:label="@string/general_error"
android:theme="@android:style/Theme.NoDisplay" />
<activity android:name=".ErrorActivity"></activity>
</application>
</manifest>

View File

@ -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<VideoInfo.VideoStream> videoStreams) {
public void setupStreamList(final List<StreamInfo.VideoStream> 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<VideoInfo.VideoStream> videoStreams) {
private int getDefaultResolution(final List<StreamInfo.VideoStream> 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;
}

View File

@ -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<Exception> errorList;
public volatile Class returnActivity;
public volatile ErrorActivity.ErrorInfo errorInfo;
}

View File

@ -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.
* <p>
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* ErrorActivity.java is part of NewPipe.
* <p>
* 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.
* <p>
* 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.
* <p>
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class 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<Exception> 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<Exception> 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<Exception> 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<Exception> 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<Exception> 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<Exception> 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);
}
}
}
}

View File

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

View File

@ -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<VideoInfo.VideoStream> streamsToUse = new Vector<>();
for (VideoInfo.VideoStream i : info.video_streams) {
Vector<StreamInfo.VideoStream> 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<VideoPreviewInfo> similar = new ArrayList<>(info.related_videos);
for (final VideoPreviewInfo item : similar) {
ArrayList<StreamPreviewInfo> 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<VideoInfo.VideoStream> streams) {
for(VideoInfo.VideoStream i : streams) {
private boolean useStream(StreamInfo.VideoStream stream, Vector<StreamInfo.VideoStream> 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)

View File

@ -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
ArrayList<String>suggestions = 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));
}
}
}

View File

@ -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<VideoPreviewInfo> videoList) {
public void present(List<StreamPreviewInfo> 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<VideoPreviewInfo> list) {
private void updateList(List<StreamPreviewInfo> 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();
}
});
}

View File

@ -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<VideoPreviewInfo> videoList = new Vector<>();
private Vector<StreamPreviewInfo> 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<VideoPreviewInfo> videos) {
public void addVideoList(List<StreamPreviewInfo> videos) {
videoList.addAll(videos);
notifyDataSetChanged();
}
@ -57,7 +57,7 @@ class VideoListAdapter extends BaseAdapter {
notifyDataSetChanged();
}
public Vector<VideoPreviewInfo> getVideoList() {
public Vector<StreamPreviewInfo> 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));

View File

@ -20,7 +20,7 @@ import android.graphics.Bitmap;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
/**Common properties between VideoInfo and VideoPreviewInfo.*/
/**Common properties between StreamInfo and StreamPreviewInfo.*/
public abstract class AbstractVideoInfo {
public String id = "";
public String title = "";

View File

@ -37,7 +37,7 @@ public class DashMpdParser {
}
}
public static List<VideoInfo.AudioStream> getAudioStreams(String dashManifestUrl,
public static List<StreamInfo.AudioStream> 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<VideoInfo.AudioStream> audioStreams = new Vector<>();
Vector<StreamInfo.AudioStream> 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;

View File

@ -21,8 +21,6 @@ package org.schabi.newpipe.extractor;
*/
public class ExtractionException extends Exception {
public ExtractionException() {}
public ExtractionException(String message) {
super(message);
}

View File

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

View File

@ -27,16 +27,16 @@ import java.util.Vector;
@SuppressWarnings("ALL")
public interface SearchEngine {
class Result {
public String errorMessage = "";
public String suggestion = "";
public final List<VideoPreviewInfo> resultList = new Vector<>();
public class NothingFoundException extends ExtractionException {
public NothingFoundException(String message) {
super(message);
}
}
ArrayList<String> 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;
}

View File

@ -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 <chris.schabesberger@mailbox.org>
* 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 <http://www.gnu.org/licenses/>.
*/
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<StreamPreviewInfo> resultList = new Vector<>();
public List<Exception> errors = new Vector<>();
}

View File

@ -3,7 +3,7 @@ package org.schabi.newpipe.extractor;
/**
* Created by Christian Schabesberger on 10.08.15.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* 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<VideoInfo.AudioStream> getAudioStreams() throws ParsingException;
public abstract List<VideoInfo.VideoStream> getVideoStreams() throws ParsingException;
public abstract List<VideoInfo.VideoStream> getVideoOnlyStreams() throws ParsingException;
public abstract List<StreamInfo.AudioStream> getAudioStreams() throws ParsingException;
public abstract List<StreamInfo.VideoStream> getVideoStreams() throws ParsingException;
public abstract List<StreamInfo.VideoStream> 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<VideoPreviewInfo> getRelatedVideos() throws ParsingException;
public abstract VideoUrlIdHandler getUrlIdConverter();
public abstract StreamPreviewInfo getNextVideo() throws ParsingException;
public abstract List<StreamPreviewInfo> getRelatedVideos() throws ParsingException;
public abstract StreamUrlIdHandler getUrlIdConverter();
public abstract String getPageUrl();
}

View File

@ -7,8 +7,8 @@ import java.util.Vector;
/**
* Created by Christian Schabesberger on 26.08.15.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* VideoInfo.java is part of NewPipe.
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* 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<AudioStream>();
if(streamInfo.dashMpdUrl != null && !streamInfo.dashMpdUrl.isEmpty()) {
if(streamInfo.audio_streams == null) {
streamInfo.audio_streams = new Vector<AudioStream>();
}
//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<VideoPreviewInfo> related_videos = null;
//in seconds. some metadata is not passed using a VideoInfo object!
public StreamPreviewInfo next_video = null;
public List<StreamPreviewInfo> 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<Exception> 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;

View File

@ -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 <chris.schabesberger@mailbox.org>
* VideoPreviewInfo.java is part of NewPipe.
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* 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 = "";
}

View File

@ -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 <chris.schabesberger@mailbox.org>
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}
}

View File

@ -0,0 +1,31 @@
package org.schabi.newpipe.extractor;
/**
* Created by Christian Schabesberger on 28.02.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}

View File

@ -4,7 +4,7 @@ package org.schabi.newpipe.extractor;
* Created by Christian Schabesberger on 02.02.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* 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 <http://www.gnu.org/licenses/>.
*/
public interface VideoUrlIdHandler {
public interface StreamUrlIdHandler {
String getVideoUrl(String videoId);
String getVideoId(String siteUrl) throws ParsingException;
String cleanUrl(String siteUrl) throws ParsingException;

View File

@ -5,7 +5,7 @@ import java.io.IOException;
/**
* Created by Christian Schabesberger on 23.08.15.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* 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();
}

View File

@ -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<String> suggestionList(String query,String contentCountry, Downloader dl)
public ArrayList<String> suggestionList(String query, String contentCountry, Downloader dl)
throws IOException, ParsingException {
ArrayList<String> 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;
}
};
}
}

View File

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

View File

@ -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<VideoInfo.AudioStream> getAudioStreams() throws ParsingException {
Vector<VideoInfo.AudioStream> audioStreams = new Vector<>();
public List<StreamInfo.AudioStream> getAudioStreams() throws ParsingException {
Vector<StreamInfo.AudioStream> 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<VideoInfo.VideoStream> getVideoStreams() throws ParsingException {
Vector<VideoInfo.VideoStream> videoStreams = new Vector<>();
public List<StreamInfo.VideoStream> getVideoStreams() throws ParsingException {
Vector<StreamInfo.VideoStream> 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<VideoInfo.VideoStream> getVideoOnlyStreams() throws ParsingException {
public List<StreamInfo.VideoStream> 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<VideoPreviewInfo> getRelatedVideos() throws ParsingException {
public Vector<StreamPreviewInfo> getRelatedVideos() throws ParsingException {
try {
Vector<VideoPreviewInfo> relatedVideos = new Vector<>();
Vector<StreamPreviewInfo> 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();
}

View File

@ -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 <chris.schabesberger@mailbox.org>
* 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 <http://www.gnu.org/licenses/>.
*/
public class YoutubeVideoUrlIdHandler implements VideoUrlIdHandler {
public class YoutubeStreamUrlIdHandler implements StreamUrlIdHandler {
@SuppressWarnings("WeakerAccess")
@Override
public String getVideoUrl(String videoId) {

View File

@ -0,0 +1,125 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ErrorActivity">
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:focusable="true"
android:focusableInTouchMode="true">
<TextView
android:id="@+id/errorSorryView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:gravity="center"
android:text="@string/sorry_string"
android:textStyle="bold" />
<TextView
android:id="@+id/messageWhatHappenedView"
android:paddingTop="@dimen/activity_vertical_margin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/what_happened_headline"/>
<TextView
android:id="@+id/errorMessageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:text="@string/info_labels"/>
<TextView
android:id="@+id/errorDeviceHeadlineView"
android:paddingTop="@dimen/activity_vertical_margin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/what_device_headline"/>
<LinearLayout
android:id="@+id/errorInfoLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/errorInfoLabelsView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@android:color/black"
android:text="@string/info_labels"/>
<HorizontalScrollView
android:paddingLeft="16dp"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/errorInfosView"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</HorizontalScrollView>
</LinearLayout>
<TextView
android:id="@+id/errorDetailView"
android:paddingTop="@dimen/activity_vertical_margin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/error_details_headline"/>
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/horizontalScrollView"
android:layout_gravity="center" >
<TextView
android:id="@+id/errorView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:typeface="monospace"/>
</HorizontalScrollView>
<TextView
android:id="@+id/errorYourComment"
android:paddingTop="@dimen/activity_vertical_margin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="@string/your_comment"/>
<EditText
android:id="@+id/errorCommentBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/errorReportButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/error_report_button_text" />
</LinearLayout>
</ScrollView>
</FrameLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/menu_item_share_error"
android:title="@string/share"
app:showAsAction="ifRoom"
android:icon="@drawable/ic_share_black"/>
</menu>

View File

@ -0,0 +1,6 @@
<resources>
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
(such as screen margins) for screens with more than 820dp of available width. This
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
<dimen name="activity_horizontal_margin">64dp</dimen>
</resources>

View File

@ -35,5 +35,8 @@
<!-- Paddings & Margins -->
<dimen name="video_item_detail_like_margin">6sp</dimen>
<dimen name="video_item_detail_play_fab_margin">20dp</dimen>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>

View File

@ -15,7 +15,7 @@
<string name="download">Download</string>
<string name="search">Search</string>
<string name="settings">Settings</string>
<string name="did_you_mean">Did you mean: </string>
<string name="did_you_mean">Did you mean: %1$s ?</string>
<string name="search_page">Search page: </string>
<string name="share_dialog_title">Share with:</string>
<string name="choose_browser">Choose browser:</string>
@ -84,10 +84,25 @@
<string name="could_not_load_thumbnails">Could not load all Thumbnails</string>
<string name="youtube_signature_decryption_error">Could not decrypt video url signature.</string>
<string name="parsing_error">Could not parse website.</string>
<string name="light_parsing_error">Could not parse website complete.</string>
<string name="content_not_available">Content not available.</string>
<string name="blocked_by_gema">Blocked by GEMA.</string>
<string name="could_not_setup_download_menu">Could not setup download menu.</string>
<string name="live_streams_not_supported">This is a LIVE STREAM. These are not yet supported.</string>
<string name="could_not_get_stream">Could not get any stream.</string>
<!-- error activity -->
<string name="sorry_string">Sorry that should not happen.</string>
<string name="guru_meditation" translatable="false">Guru Meditation.</string>
<string name="error_report_button_text">Report error via mail</string>
<string name="error_snackbar_message">Sorry some errors occurred.</string>
<string name="error_snackbar_action">REPORT</string>
<string name="what_device_headline">Info:</string>
<string name="what_happened_headline">What happened:</string>
<string name="info_labels">What:\\nRequest:\\nContent Lang:\\nService:\\nGMT Time:\\nVersion:\\nOS version:\\nGlob. IP range:</string>
<string name="info_searched_lbl">Searched for:</string>
<string name="info_requested_stream_lbl">Requested stream:</string>
<string name="your_comment">Your comment (in English):</string>
<string name="error_details_headline">Details:</string>
<!-- Content descriptions (for better accessibility) -->