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 'de.hdodenhof:circleimageview:2.0.0'
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5' compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
compile 'com.github.nirhart:parallaxscroll:1.0' 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 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.SearchEngine;
import org.schabi.newpipe.extractor.services.youtube.YoutubeSearchEngine; import org.schabi.newpipe.extractor.services.youtube.YoutubeSearchEngine;
import org.schabi.newpipe.Downloader; import org.schabi.newpipe.Downloader;
@ -30,7 +32,7 @@ import java.util.ArrayList;
*/ */
public class YoutubeSearchEngineTest extends AndroidTestCase { public class YoutubeSearchEngineTest extends AndroidTestCase {
private SearchEngine.Result result; private SearchResult result;
private ArrayList<String> suggestionReply; private ArrayList<String> suggestionReply;
@Override @Override
@ -39,12 +41,13 @@ public class YoutubeSearchEngineTest extends AndroidTestCase {
SearchEngine engine = new YoutubeSearchEngine(); SearchEngine engine = new YoutubeSearchEngine();
result = engine.search("bla", result = engine.search("bla",
0, "de", new Downloader()); 0, "de", new Downloader()).getSearchResult();
suggestionReply = engine.suggestionList("hello","de",new Downloader()); suggestionReply = engine.suggestionList("hello","de",new Downloader());
} }
public void testIfNoErrorOccur() { public void testIfNoErrorOccur() {
assertEquals(result.errorMessage, ""); assertTrue(result.errors.isEmpty() ? "" : ExceptionUtils.getStackTrace(result.errors.get(0))
,result.errors.isEmpty());
} }
public void testIfListIsNotEmpty() { public void testIfListIsNotEmpty() {
@ -52,44 +55,44 @@ public class YoutubeSearchEngineTest extends AndroidTestCase {
} }
public void testItemsHaveTitle() { public void testItemsHaveTitle() {
for(VideoPreviewInfo i : result.resultList) { for(StreamPreviewInfo i : result.resultList) {
assertEquals(i.title.isEmpty(), false); assertEquals(i.title.isEmpty(), false);
} }
} }
public void testItemsHaveUploader() { public void testItemsHaveUploader() {
for(VideoPreviewInfo i : result.resultList) { for(StreamPreviewInfo i : result.resultList) {
assertEquals(i.uploader.isEmpty(), false); assertEquals(i.uploader.isEmpty(), false);
} }
} }
public void testItemsHaveRightDuration() { public void testItemsHaveRightDuration() {
for(VideoPreviewInfo i : result.resultList) { for(StreamPreviewInfo i : result.resultList) {
assertTrue(i.duration, i.duration.contains(":")); assertTrue(i.duration, i.duration.contains(":"));
} }
} }
public void testItemsHaveRightThumbnail() { public void testItemsHaveRightThumbnail() {
for (VideoPreviewInfo i : result.resultList) { for (StreamPreviewInfo i : result.resultList) {
assertTrue(i.thumbnail_url, i.thumbnail_url.contains("https://")); assertTrue(i.thumbnail_url, i.thumbnail_url.contains("https://"));
} }
} }
public void testItemsHaveRightVideoUrl() { public void testItemsHaveRightVideoUrl() {
for (VideoPreviewInfo i : result.resultList) { for (StreamPreviewInfo i : result.resultList) {
assertTrue(i.webpage_url, i.webpage_url.contains("https://")); assertTrue(i.webpage_url, i.webpage_url.contains("https://"));
} }
} }
public void testViewCount() { public void testViewCount() {
/* /*
for(VideoPreviewInfo i : result.resultList) { for(StreamPreviewInfo i : result.resultList) {
assertTrue(Long.toString(i.view_count), i.view_count != -1); assertTrue(Long.toString(i.view_count), i.view_count != -1);
} }
*/ */
// that specific link used for this test, there are no videos with less // that specific link used for this test, there are no videos with less
// than 10.000 views, so we can test against that. // 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); 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.ExtractionException;
import org.schabi.newpipe.extractor.ParsingException; import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor; import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
import org.schabi.newpipe.extractor.VideoInfo; import org.schabi.newpipe.extractor.StreamInfo;
import java.io.IOException; import java.io.IOException;
@ -94,7 +94,7 @@ public class YoutubeStreamExtractorDefaultTest extends AndroidTestCase {
} }
public void testGetVideoStreams() throws ParsingException { public void testGetVideoStreams() throws ParsingException {
for(VideoInfo.VideoStream s : extractor.getVideoStreams()) { for(StreamInfo.VideoStream s : extractor.getVideoStreams()) {
assertTrue(s.url, assertTrue(s.url,
s.url.contains("https://")); s.url.contains("https://"));
assertTrue(s.resolution.length() > 0); assertTrue(s.resolution.length() > 0);

View file

@ -5,7 +5,7 @@ import android.test.AndroidTestCase;
import org.schabi.newpipe.Downloader; import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.extractor.ExtractionException; import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.ParsingException; 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 org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
import java.io.IOException; import java.io.IOException;
@ -73,7 +73,7 @@ public class YoutubeStreamExtractorRestrictedTest extends AndroidTestCase {
} }
public void testGetVideoStreams() throws ParsingException { public void testGetVideoStreams() throws ParsingException {
for(VideoInfo.VideoStream s : extractor.getVideoStreams()) { for(StreamInfo.VideoStream s : extractor.getVideoStreams()) {
assertTrue(s.url, assertTrue(s.url,
s.url.contains("https://")); s.url.contains("https://"));
assertTrue(s.resolution.length() > 0); assertTrue(s.resolution.length() > 0);

View file

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

View file

@ -12,7 +12,7 @@ import android.view.MenuItem;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.VideoInfo; import org.schabi.newpipe.extractor.StreamInfo;
import java.util.List; 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) { if (activity != null) {
selectedVideoStream = 0; selectedVideoStream = 0;
@ -83,7 +83,7 @@ class ActionBarHandler {
// this array will be shown in the dropdown menu for selecting the stream/resolution. // this array will be shown in the dropdown menu for selecting the stream/resolution.
String[] itemArray = new String[videoStreams.size()]; String[] itemArray = new String[videoStreams.size()];
for (int i = 0; i < videoStreams.size(); i++) { 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; itemArray[i] = MediaFormat.getNameById(item.format) + " " + item.resolution;
} }
int defaultResolution = getDefaultResolution(videoStreams); 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 String defaultResolution = defaultPreferences
.getString(activity.getString(R.string.default_resolution_key), .getString(activity.getString(R.string.default_resolution_key),
activity.getString(R.string.default_resolution_value)); activity.getString(R.string.default_resolution_value));
for (int i = 0; i < videoStreams.size(); i++) { 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)) { if (defaultResolution.equals(item.resolution)) {
return i; return i;
} }

View file

@ -22,10 +22,12 @@ package org.schabi.newpipe;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import java.util.List;
/** /**
* Singleton: * Singleton:
* Used to send data between certain Activity/Services within the same process. * 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 { public class ActivityCommunicator {
private static ActivityCommunicator activityCommunicator = null; private static ActivityCommunicator activityCommunicator = null;
@ -39,4 +41,9 @@ public class ActivityCommunicator {
// Thumbnail send from VideoItemDetailFragment to BackgroundPlayer // Thumbnail send from VideoItemDetailFragment to BackgroundPlayer
public volatile Bitmap backgroundPlayerThumbnail; 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.ImageView;
import android.widget.TextView; 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.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
@ -40,8 +41,10 @@ class VideoInfoItemViewCreator {
this.inflater = inflater; 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; ViewHolder holder;
// generate holder
if(convertView == null) { if(convertView == null) {
convertView = inflater.inflate(R.layout.video_item, parent, false); convertView = inflater.inflate(R.layout.video_item, parent, false);
holder = new ViewHolder(); holder = new ViewHolder();
@ -56,20 +59,41 @@ class VideoInfoItemViewCreator {
holder = (ViewHolder) convertView.getTag(); holder = (ViewHolder) convertView.getTag();
} }
// fill with information
/*
if(info.thumbnail == null) { if(info.thumbnail == null) {
holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail); holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
} else { } else {
holder.itemThumbnailView.setImageBitmap(info.thumbnail); holder.itemThumbnailView.setImageBitmap(info.thumbnail);
} }
*/
holder.itemVideoTitleView.setText(info.title); holder.itemVideoTitleView.setText(info.title);
holder.itemUploaderView.setText(info.uploader); if(info.uploader != null && !info.uploader.isEmpty()) {
holder.itemDurationView.setText(info.duration); holder.itemUploaderView.setText(info.uploader);
holder.itemViewCountView.setText(shortViewCount(info.view_count)); } 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()) { if(!info.upload_date.isEmpty()) {
holder.itemUploadDateView.setText(info.upload_date+""); 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; 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.ParsingException;
import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamExtractor; 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.StreamingService;
import org.schabi.newpipe.extractor.VideoInfo;
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor; import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
@ -127,12 +127,34 @@ public class VideoItemDetailFragment extends Fragment {
@Override @Override
public void run() { public void run() {
VideoInfo videoInfo = null; StreamInfo streamInfo = null;
try { try {
streamExtractor = service.getExtractorInstance(videoUrl, new Downloader()); 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) { } catch (IOException e) {
postNewErrorToast(h, R.string.network_error); postNewErrorToast(h, R.string.network_error);
e.printStackTrace(); e.printStackTrace();
@ -165,37 +187,66 @@ public class VideoItemDetailFragment extends Fragment {
} }
}); });
e.printStackTrace(); 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) { } 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(); e.printStackTrace();
} catch(Exception e) { } 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(); 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 class VideoResultReturnedRunnable implements Runnable {
private final VideoInfo videoInfo; private final StreamInfo streamInfo;
public VideoResultReturnedRunnable(VideoInfo videoInfo) { public VideoResultReturnedRunnable(StreamInfo streamInfo) {
this.videoInfo = videoInfo; this.streamInfo = streamInfo;
} }
@Override @Override
public void run() { public void run() {
boolean show_age_restricted_content = PreferenceManager.getDefaultSharedPreferences(getActivity()) Activity a = getActivity();
.getBoolean(activity.getString(R.string.show_age_restricted_content), false); if(a != null) {
if(videoInfo.age_limit == 0 || show_age_restricted_content) { boolean show_age_restricted_content = PreferenceManager.getDefaultSharedPreferences(a)
updateInfo(videoInfo); .getBoolean(activity.getString(R.string.show_age_restricted_content), false);
} else { if (streamInfo.age_limit == 0 || show_age_restricted_content) {
onNotSpecifiedContentErrorWithMessage(R.string.video_is_age_restricted); 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) {} public void onLoadingCancelled(String imageUri, View view) {}
} }
private void updateInfo(final VideoInfo info) { private void updateInfo(final StreamInfo info) {
try { try {
Context c = getContext(); Context c = getContext();
VideoInfoItemViewCreator videoItemViewCreator = VideoInfoItemViewCreator videoItemViewCreator =
@ -249,7 +300,7 @@ public class VideoItemDetailFragment extends Fragment {
View nextVideoView = null; View nextVideoView = null;
if(info.next_video != null) { if(info.next_video != null) {
nextVideoView = videoItemViewCreator nextVideoView = videoItemViewCreator
.getViewFromVideoInfoItem(null, nextVideoFrame, info.next_video, getContext()); .getViewFromVideoInfoItem(null, nextVideoFrame, info.next_video);
} else { } else {
activity.findViewById(R.id.detailNextVidButtonAndContentLayout).setVisibility(View.GONE); activity.findViewById(R.id.detailNextVidButtonAndContentLayout).setVisibility(View.GONE);
activity.findViewById(R.id.detailNextVideoTitle).setVisibility(View.GONE); activity.findViewById(R.id.detailNextVideoTitle).setVisibility(View.GONE);
@ -337,8 +388,8 @@ public class VideoItemDetailFragment extends Fragment {
descriptionView.setMovementMethod(LinkMovementMethod.getInstance()); descriptionView.setMovementMethod(LinkMovementMethod.getInstance());
// parse streams // parse streams
Vector<VideoInfo.VideoStream> streamsToUse = new Vector<>(); Vector<StreamInfo.VideoStream> streamsToUse = new Vector<>();
for (VideoInfo.VideoStream i : info.video_streams) { for (StreamInfo.VideoStream i : info.video_streams) {
if (useStream(i, streamsToUse)) { if (useStream(i, streamsToUse)) {
streamsToUse.add(i); 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 videoThumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView);
ImageView uploaderThumb ImageView uploaderThumb
= (ImageView) activity.findViewById(R.id.detailUploaderThumbnailView); = (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.setupStreamList(info.video_streams);
actionBarHandler.setOnShareListener(new ActionBarHandler.OnActionListener() { 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. // website which was crawled. Then the ui has to understand this and act right.
if (info.audio_streams != null) { if (info.audio_streams != null) {
VideoInfo.AudioStream audioStream = StreamInfo.AudioStream audioStream =
info.audio_streams.get(getPreferredAudioStreamId(info)); info.audio_streams.get(getPreferredAudioStreamId(info));
String audioSuffix = "." + MediaFormat.getSuffixById(audioStream.format); String audioSuffix = "." + MediaFormat.getSuffixById(audioStream.format);
@ -513,7 +564,7 @@ public class VideoItemDetailFragment extends Fragment {
} }
if (info.video_streams != null) { 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); String videoSuffix = "." + MediaFormat.getSuffixById(selectedStreamItem.format);
args.putString(DownloadDialog.FILE_SUFFIX_VIDEO, videoSuffix); args.putString(DownloadDialog.FILE_SUFFIX_VIDEO, videoSuffix);
args.putString(DownloadDialog.VIDEO_URL, selectedStreamItem.url); args.putString(DownloadDialog.VIDEO_URL, selectedStreamItem.url);
@ -540,7 +591,7 @@ public class VideoItemDetailFragment extends Fragment {
boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity) boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(activity.getString(R.string.use_external_audio_player_key), false); .getBoolean(activity.getString(R.string.use_external_audio_player_key), false);
Intent intent; Intent intent;
VideoInfo.AudioStream audioStream = StreamInfo.AudioStream audioStream =
info.audio_streams.get(getPreferredAudioStreamId(info)); info.audio_streams.get(getPreferredAudioStreamId(info));
if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 18) { if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 18) {
//internal music player: explicit intent //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()) String preferredFormatString = PreferenceManager.getDefaultSharedPreferences(getActivity())
.getString(activity.getString(R.string.default_audio_format_key), "webm"); .getString(activity.getString(R.string.default_audio_format_key), "webm");
@ -626,12 +677,12 @@ public class VideoItemDetailFragment extends Fragment {
return 0; 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); LinearLayout similarLayout = (LinearLayout) activity.findViewById(R.id.similarVideosView);
ArrayList<VideoPreviewInfo> similar = new ArrayList<>(info.related_videos); ArrayList<StreamPreviewInfo> similar = new ArrayList<>(info.related_videos);
for (final VideoPreviewInfo item : similar) { for (final StreamPreviewInfo item : similar) {
View similarView = videoItemViewCreator View similarView = videoItemViewCreator
.getViewFromVideoInfoItem(null, similarLayout, item, getContext()); .getViewFromVideoInfoItem(null, similarLayout, item);
similarView.setClickable(true); similarView.setClickable(true);
similarView.setFocusable(true); similarView.setFocusable(true);
@ -703,8 +754,8 @@ public class VideoItemDetailFragment extends Fragment {
.show(); .show();
} }
private boolean useStream(VideoInfo.VideoStream stream, Vector<VideoInfo.VideoStream> streams) { private boolean useStream(StreamInfo.VideoStream stream, Vector<StreamInfo.VideoStream> streams) {
for(VideoInfo.VideoStream i : streams) { for(StreamInfo.VideoStream i : streams) {
if(i.resolution.equals(stream.resolution)) { if(i.resolution.equals(stream.resolution)) {
return false; 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 --------------- // ----------- THE MAGIC MOMENT ---------------
VideoInfo.VideoStream selectedVideoStream = StreamInfo.VideoStream selectedVideoStream =
info.video_streams.get(actionBarHandler.getSelectedVideoStream()); info.video_streams.get(actionBarHandler.getSelectedVideoStream());
if (PreferenceManager.getDefaultSharedPreferences(activity) 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.SearchEngine;
import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.VideoPreviewInfo;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -173,11 +172,17 @@ public class VideoItemListActivity extends AppCompatActivity
ArrayList<String>suggestions = engine.suggestionList(query,searchLanguage,new Downloader()); ArrayList<String>suggestions = engine.suggestionList(query,searchLanguage,new Downloader());
h.post(new SuggestionResultRunnable(suggestions)); h.post(new SuggestionResultRunnable(suggestions));
} catch (ExtractionException e) { } 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(); e.printStackTrace();
} catch (IOException e) { } catch (IOException e) {
postNewErrorToast(h, R.string.network_error); postNewErrorToast(h, R.string.network_error);
e.printStackTrace(); 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; package org.schabi.newpipe;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
@ -17,8 +18,8 @@ import java.io.IOException;
import java.util.List; import java.util.List;
import org.schabi.newpipe.extractor.ExtractionException; import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.ParsingException; import org.schabi.newpipe.extractor.SearchResult;
import org.schabi.newpipe.extractor.VideoPreviewInfo; import org.schabi.newpipe.extractor.StreamPreviewInfo;
import org.schabi.newpipe.extractor.SearchEngine; import org.schabi.newpipe.extractor.SearchEngine;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
@ -68,9 +69,9 @@ public class VideoItemListFragment extends ListFragment {
private boolean loadingNextPage = true; private boolean loadingNextPage = true;
private class ResultRunnable implements Runnable { private class ResultRunnable implements Runnable {
private final SearchEngine.Result result; private final SearchResult result;
private final int requestId; private final int requestId;
public ResultRunnable(SearchEngine.Result result, int requestId) { public ResultRunnable(SearchResult result, int requestId) {
this.result = result; this.result = result;
this.requestId = requestId; this.requestId = requestId;
} }
@ -101,32 +102,60 @@ public class VideoItemListFragment extends ListFragment {
} }
@Override @Override
public void run() { public void run() {
SearchResult result = null;
try { try {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext()); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
String searchLanguageKey = getContext().getString(R.string.search_language_key); String searchLanguageKey = getContext().getString(R.string.search_language_key);
String searchLanguage = sp.getString(searchLanguageKey, String searchLanguage = sp.getString(searchLanguageKey,
getString(R.string.default_language_value)); getString(R.string.default_language_value));
SearchEngine.Result result = engine.search(query, page, searchLanguage, result = SearchResult
new Downloader()); .getSearchResult(engine, query, page, searchLanguage, new Downloader());
//Log.i(TAG, "language code passed:\""+searchLanguage+"\"");
if(runs) { if(runs) {
h.post(new ResultRunnable(result, requestId)); 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) { } catch(IOException e) {
postNewErrorToast(h, R.string.network_error); postNewNothingFoundToast(h, R.string.network_error);
e.printStackTrace(); e.printStackTrace();
} catch(ExtractionException ce) { } catch(SearchEngine.NothingFoundException e) {
postNewErrorToast(h, R.string.parsing_error); postNewErrorToast(h, e.getMessage());
ce.printStackTrace(); } 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) { } 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(); e.printStackTrace();
} }
} }
} }
public void present(List<VideoPreviewInfo> videoList) { public void present(List<StreamPreviewInfo> videoList) {
mode = PRESENT_VIDEOS_MODE; mode = PRESENT_VIDEOS_MODE;
setListShown(true); setListShown(true);
getListView().smoothScrollToPosition(0); getListView().smoothScrollToPosition(0);
@ -166,22 +195,19 @@ public class VideoItemListFragment extends ListFragment {
this.streamingService = streamingService; this.streamingService = streamingService;
} }
private void updateListOnResult(SearchEngine.Result result, int requestId) { private void updateListOnResult(SearchResult result, int requestId) {
if(requestId == currentRequestId) { if(requestId == currentRequestId) {
setListShown(true); setListShown(true);
if (result.resultList.isEmpty()) { updateList(result.resultList);
Toast.makeText(getActivity(), result.errorMessage, Toast.LENGTH_LONG).show(); if(!result.suggestion.isEmpty()) {
} else { Toast.makeText(getActivity(),
if (!result.suggestion.isEmpty()) { String.format(getString(R.string.did_you_mean), result.suggestion),
Toast.makeText(getActivity(), getString(R.string.did_you_mean) + result.suggestion + " ?", Toast.LENGTH_LONG).show();
Toast.LENGTH_LONG).show();
}
updateList(result.resultList);
} }
} }
} }
private void updateList(List<VideoPreviewInfo> list) { private void updateList(List<StreamPreviewInfo> list) {
try { try {
videoListAdapter.addVideoList(list); videoListAdapter.addVideoList(list);
terminateThreads(); terminateThreads();
@ -318,13 +344,24 @@ public class VideoItemListFragment extends ListFragment {
mActivatedPosition = position; 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() { h.post(new Runnable() {
@Override @Override
public void run() { public void run() {
setListShown(true); setListShown(true);
Toast.makeText(getActivity(), getString(stringResource), 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.BaseAdapter;
import android.widget.ListView; import android.widget.ListView;
import org.schabi.newpipe.extractor.VideoPreviewInfo; import org.schabi.newpipe.extractor.StreamPreviewInfo;
import java.util.List; import java.util.List;
import java.util.Vector; import java.util.Vector;
@ -36,7 +36,7 @@ import java.util.Vector;
class VideoListAdapter extends BaseAdapter { class VideoListAdapter extends BaseAdapter {
private final Context context; private final Context context;
private final VideoInfoItemViewCreator viewCreator; private final VideoInfoItemViewCreator viewCreator;
private Vector<VideoPreviewInfo> videoList = new Vector<>(); private Vector<StreamPreviewInfo> videoList = new Vector<>();
private final ListView listView; private final ListView listView;
public VideoListAdapter(Context context, VideoItemListFragment videoListFragment) { public VideoListAdapter(Context context, VideoItemListFragment videoListFragment) {
@ -47,7 +47,7 @@ class VideoListAdapter extends BaseAdapter {
this.context = context; this.context = context;
} }
public void addVideoList(List<VideoPreviewInfo> videos) { public void addVideoList(List<StreamPreviewInfo> videos) {
videoList.addAll(videos); videoList.addAll(videos);
notifyDataSetChanged(); notifyDataSetChanged();
} }
@ -57,7 +57,7 @@ class VideoListAdapter extends BaseAdapter {
notifyDataSetChanged(); notifyDataSetChanged();
} }
public Vector<VideoPreviewInfo> getVideoList() { public Vector<StreamPreviewInfo> getVideoList() {
return videoList; return videoList;
} }
@ -78,7 +78,7 @@ class VideoListAdapter extends BaseAdapter {
@Override @Override
public View getView(int position, View convertView, ViewGroup parent) { 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)) { if(listView.isItemChecked(position)) {
convertView.setBackgroundColor(ContextCompat.getColor(context,R.color.light_youtube_primary_color)); 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/>. * 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 abstract class AbstractVideoInfo {
public String id = ""; public String id = "";
public String title = ""; 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) Downloader downloader)
throws DashMpdParsingException { throws DashMpdParsingException {
String dashDoc; String dashDoc;
@ -46,7 +46,7 @@ public class DashMpdParser {
} catch(IOException ioe) { } catch(IOException ioe) {
throw new DashMpdParsingException("Could not get dash mpd: " + dashManifestUrl, ioe); throw new DashMpdParsingException("Could not get dash mpd: " + dashManifestUrl, ioe);
} }
Vector<VideoInfo.AudioStream> audioStreams = new Vector<>(); Vector<StreamInfo.AudioStream> audioStreams = new Vector<>();
try { try {
XmlPullParser parser = Xml.newPullParser(); XmlPullParser parser = Xml.newPullParser();
parser.setInput(new StringReader(dashDoc)); parser.setInput(new StringReader(dashDoc));
@ -83,7 +83,7 @@ public class DashMpdParser {
} else if(currentMimeType.equals(MediaFormat.M4A.mimeType)) { } else if(currentMimeType.equals(MediaFormat.M4A.mimeType)) {
format = MediaFormat.M4A.id; format = MediaFormat.M4A.id;
} }
audioStreams.add(new VideoInfo.AudioStream(parser.getText(), audioStreams.add(new StreamInfo.AudioStream(parser.getText(),
format, currentBandwidth, currentSamplingRate)); format, currentBandwidth, currentSamplingRate));
} }
break; break;

View file

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

View file

@ -22,13 +22,9 @@ package org.schabi.newpipe.extractor;
public class ParsingException extends ExtractionException { public class ParsingException extends ExtractionException {
public ParsingException() {}
public ParsingException(String message) { public ParsingException(String message) {
super(message); super(message);
} }
public ParsingException(Throwable cause) {
super(cause);
}
public ParsingException(String message, Throwable cause) { public ParsingException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }

View file

@ -27,16 +27,16 @@ import java.util.Vector;
@SuppressWarnings("ALL") @SuppressWarnings("ALL")
public interface SearchEngine { public interface SearchEngine {
class Result { public class NothingFoundException extends ExtractionException {
public String errorMessage = ""; public NothingFoundException(String message) {
public String suggestion = ""; super(message);
public final List<VideoPreviewInfo> resultList = new Vector<>(); }
} }
ArrayList<String> suggestionList(String query,String contentCountry, Downloader dl) ArrayList<String> suggestionList(String query,String contentCountry, Downloader dl)
throws ExtractionException, IOException; throws ExtractionException, IOException;
//Result search(String query, int page); //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; 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. * 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. * StreamExtractor.java is part of NewPipe.
* *
* NewPipe is free software: you can redistribute it and/or modify * NewPipe is free software: you can redistribute it and/or modify
@ -29,7 +29,6 @@ import java.util.List;
public interface StreamExtractor { public interface StreamExtractor {
public class ExctractorInitException extends ExtractionException { public class ExctractorInitException extends ExtractionException {
public ExctractorInitException() {}
public ExctractorInitException(String message) { public ExctractorInitException(String message) {
super(message); super(message);
} }
@ -42,13 +41,9 @@ public interface StreamExtractor {
} }
public class ContentNotAvailableException extends ParsingException { public class ContentNotAvailableException extends ParsingException {
public ContentNotAvailableException() {}
public ContentNotAvailableException(String message) { public ContentNotAvailableException(String message) {
super(message); super(message);
} }
public ContentNotAvailableException(Throwable cause) {
super(cause);
}
public ContentNotAvailableException(String message, Throwable cause) { public ContentNotAvailableException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }
@ -63,16 +58,16 @@ public interface StreamExtractor {
public abstract String getUploadDate() throws ParsingException; public abstract String getUploadDate() throws ParsingException;
public abstract String getThumbnailUrl() throws ParsingException; public abstract String getThumbnailUrl() throws ParsingException;
public abstract String getUploaderThumbnailUrl() throws ParsingException; public abstract String getUploaderThumbnailUrl() throws ParsingException;
public abstract List<VideoInfo.AudioStream> getAudioStreams() throws ParsingException; public abstract List<StreamInfo.AudioStream> getAudioStreams() throws ParsingException;
public abstract List<VideoInfo.VideoStream> getVideoStreams() throws ParsingException; public abstract List<StreamInfo.VideoStream> getVideoStreams() throws ParsingException;
public abstract List<VideoInfo.VideoStream> getVideoOnlyStreams() throws ParsingException; public abstract List<StreamInfo.VideoStream> getVideoOnlyStreams() throws ParsingException;
public abstract String getDashMpdUrl() throws ParsingException; public abstract String getDashMpdUrl() throws ParsingException;
public abstract int getAgeLimit() throws ParsingException; public abstract int getAgeLimit() throws ParsingException;
public abstract String getAverageRating() throws ParsingException; public abstract String getAverageRating() throws ParsingException;
public abstract int getLikeCount() throws ParsingException; public abstract int getLikeCount() throws ParsingException;
public abstract int getDislikeCount() throws ParsingException; public abstract int getDislikeCount() throws ParsingException;
public abstract VideoPreviewInfo getNextVideo() throws ParsingException; public abstract StreamPreviewInfo getNextVideo() throws ParsingException;
public abstract List<VideoPreviewInfo> getRelatedVideos() throws ParsingException; public abstract List<StreamPreviewInfo> getRelatedVideos() throws ParsingException;
public abstract VideoUrlIdHandler getUrlIdConverter(); public abstract StreamUrlIdHandler getUrlIdConverter();
public abstract String getPageUrl(); public abstract String getPageUrl();
} }

View file

@ -7,8 +7,8 @@ import java.util.Vector;
/** /**
* Created by Christian Schabesberger on 26.08.15. * Created by Christian Schabesberger on 26.08.15.
* *
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org> * Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* VideoInfo.java is part of NewPipe. * StreamInfo.java is part of NewPipe.
* *
* NewPipe is free software: you can redistribute it and/or modify * NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * 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.*/ /**Info object for opened videos, ie the video ready to play.*/
@SuppressWarnings("ALL") @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. /**Fills out the video info fields which are common to all services.
* Probably needs to be overridden by subclasses*/ * 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 { throws ExtractionException, IOException {
VideoInfo videoInfo = new VideoInfo(); StreamInfo streamInfo = new StreamInfo();
videoInfo = extractImportantData(videoInfo, extractor, downloader); streamInfo = extractImportantData(streamInfo, extractor, downloader);
videoInfo = extractStreams(videoInfo, extractor, downloader); streamInfo = extractStreams(streamInfo, extractor, downloader);
videoInfo = extractOptionalData(videoInfo, extractor, downloader); streamInfo = extractOptionalData(streamInfo, extractor, downloader);
return videoInfo; return streamInfo;
} }
private static VideoInfo extractImportantData( private static StreamInfo extractImportantData(
VideoInfo videoInfo, StreamExtractor extractor, Downloader downloader) StreamInfo streamInfo, StreamExtractor extractor, Downloader downloader)
throws ExtractionException, IOException { throws ExtractionException, IOException {
/* ---- importand data, withoug the video can't be displayed goes here: ---- */ /* ---- 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. // 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(); streamInfo.webpage_url = extractor.getPageUrl();
videoInfo.id = uiconv.getVideoId(extractor.getPageUrl()); streamInfo.id = uiconv.getVideoId(extractor.getPageUrl());
videoInfo.title = extractor.getTitle(); streamInfo.title = extractor.getTitle();
videoInfo.age_limit = extractor.getAgeLimit(); streamInfo.age_limit = extractor.getAgeLimit();
if((videoInfo.webpage_url == null || videoInfo.webpage_url.isEmpty()) if((streamInfo.webpage_url == null || streamInfo.webpage_url.isEmpty())
|| (videoInfo.id == null || videoInfo.id.isEmpty()) || (streamInfo.id == null || streamInfo.id.isEmpty())
|| (videoInfo.title == null /* videoInfo.title can be empty of course */) || (streamInfo.title == null /* streamInfo.title can be empty of course */)
|| (videoInfo.age_limit == -1)); || (streamInfo.age_limit == -1));
return videoInfo; return streamInfo;
} }
private static VideoInfo extractStreams( private static StreamInfo extractStreams(
VideoInfo videoInfo, StreamExtractor extractor, Downloader downloader) StreamInfo streamInfo, StreamExtractor extractor, Downloader downloader)
throws ExtractionException, IOException { throws ExtractionException, IOException {
/* ---- stream extraction goes here ---- */ /* ---- stream extraction goes here ---- */
// At least one type of stream has to be available, // At least one type of stream has to be available,
// otherwise an exception will be thrown directly into the frontend. // otherwise an exception will be thrown directly into the frontend.
try { try {
videoInfo.dashMpdUrl = extractor.getDashMpdUrl(); streamInfo.dashMpdUrl = extractor.getDashMpdUrl();
} catch(Exception e) { } 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 */ /* Load and extract audio */
try { try {
videoInfo.audio_streams = extractor.getAudioStreams(); streamInfo.audio_streams = extractor.getAudioStreams();
} catch(Exception e) { } 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 // also try to get streams from the dashMpd
if(videoInfo.dashMpdUrl != null && !videoInfo.dashMpdUrl.isEmpty()) { if(streamInfo.dashMpdUrl != null && !streamInfo.dashMpdUrl.isEmpty()) {
if(videoInfo.audio_streams == null) { if(streamInfo.audio_streams == null) {
videoInfo.audio_streams = new Vector<AudioStream>(); streamInfo.audio_streams = new Vector<AudioStream>();
} }
//todo: make this quick and dirty solution a real fallback //todo: make this quick and dirty solution a real fallback
// same as the quick and dirty aboth // same as the quick and dirty aboth
try { try {
videoInfo.audio_streams.addAll( streamInfo.audio_streams.addAll(
DashMpdParser.getAudioStreams(videoInfo.dashMpdUrl, downloader)); DashMpdParser.getAudioStreams(streamInfo.dashMpdUrl, downloader));
} catch(Exception e) { } catch(Exception e) {
videoInfo.addException( streamInfo.addException(
new ExtractionException("Couldn't get audio streams from dash mpd", e)); new ExtractionException("Couldn't get audio streams from dash mpd", e));
} }
} }
/* Extract video stream url*/ /* Extract video stream url*/
try { try {
videoInfo.video_streams = extractor.getVideoStreams(); streamInfo.video_streams = extractor.getVideoStreams();
} catch (Exception e) { } catch (Exception e) {
videoInfo.addException( streamInfo.addException(
new ExtractionException("Couldn't get video streams", e)); new ExtractionException("Couldn't get video streams", e));
} }
/* Extract video only stream url*/ /* Extract video only stream url*/
try { try {
videoInfo.video_only_streams = extractor.getVideoOnlyStreams(); streamInfo.video_only_streams = extractor.getVideoOnlyStreams();
} catch(Exception e) { } catch(Exception e) {
videoInfo.addException( streamInfo.addException(
new ExtractionException("Couldn't get video only streams", e)); 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, // 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). // and therefore failed. (Since video_only_streams are just optional they don't caunt).
if((videoInfo.video_streams == null || videoInfo.video_streams.isEmpty()) if((streamInfo.video_streams == null || streamInfo.video_streams.isEmpty())
&& (videoInfo.audio_streams == null || videoInfo.audio_streams.isEmpty()) && (streamInfo.audio_streams == null || streamInfo.audio_streams.isEmpty())
&& (videoInfo.dashMpdUrl == null || videoInfo.dashMpdUrl.isEmpty())) { && (streamInfo.dashMpdUrl == null || streamInfo.dashMpdUrl.isEmpty())) {
throw new ExtractionException("Could not get any stream. See error variable to get further details."); throw new StreamExctractException(
"Could not get any stream. See error variable to get further details.");
} }
return videoInfo; return streamInfo;
} }
private static VideoInfo extractOptionalData( private static StreamInfo extractOptionalData(
VideoInfo videoInfo, StreamExtractor extractor, Downloader downloader) { StreamInfo streamInfo, StreamExtractor extractor, Downloader downloader) {
/* ---- optional data goes here: ---- */ /* ---- optional data goes here: ---- */
// If one of these failes, the frontend neets to handle that they are not available. // 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, // Exceptions are therfore not thrown into the frontend, but stored into the error List,
// so the frontend can afterwads check where errors happend. // so the frontend can afterwads check where errors happend.
try { try {
videoInfo.thumbnail_url = extractor.getThumbnailUrl(); streamInfo.thumbnail_url = extractor.getThumbnailUrl();
} catch(Exception e) { } catch(Exception e) {
videoInfo.addException(e); streamInfo.addException(e);
} }
try { try {
videoInfo.duration = extractor.getLength(); streamInfo.duration = extractor.getLength();
} catch(Exception e) { } catch(Exception e) {
videoInfo.addException(e); streamInfo.addException(e);
} }
try { try {
videoInfo.uploader = extractor.getUploader(); streamInfo.uploader = extractor.getUploader();
} catch(Exception e) { } catch(Exception e) {
videoInfo.addException(e); streamInfo.addException(e);
} }
try { try {
videoInfo.description = extractor.getDescription(); streamInfo.description = extractor.getDescription();
} catch(Exception e) { } catch(Exception e) {
videoInfo.addException(e); streamInfo.addException(e);
} }
try { try {
videoInfo.view_count = extractor.getViewCount(); streamInfo.view_count = extractor.getViewCount();
} catch(Exception e) { } catch(Exception e) {
videoInfo.addException(e); streamInfo.addException(e);
} }
try { try {
videoInfo.upload_date = extractor.getUploadDate(); streamInfo.upload_date = extractor.getUploadDate();
} catch(Exception e) { } catch(Exception e) {
videoInfo.addException(e); streamInfo.addException(e);
} }
try { try {
videoInfo.uploader_thumbnail_url = extractor.getUploaderThumbnailUrl(); streamInfo.uploader_thumbnail_url = extractor.getUploaderThumbnailUrl();
} catch(Exception e) { } catch(Exception e) {
videoInfo.addException(e); streamInfo.addException(e);
} }
try { try {
videoInfo.start_position = extractor.getTimeStamp(); streamInfo.start_position = extractor.getTimeStamp();
} catch(Exception e) { } catch(Exception e) {
videoInfo.addException(e); streamInfo.addException(e);
} }
try { try {
videoInfo.average_rating = extractor.getAverageRating(); streamInfo.average_rating = extractor.getAverageRating();
} catch(Exception e) { } catch(Exception e) {
videoInfo.addException(e); streamInfo.addException(e);
} }
try { try {
videoInfo.like_count = extractor.getLikeCount(); streamInfo.like_count = extractor.getLikeCount();
} catch(Exception e) { } catch(Exception e) {
videoInfo.addException(e); streamInfo.addException(e);
} }
try { try {
videoInfo.dislike_count = extractor.getDislikeCount(); streamInfo.dislike_count = extractor.getDislikeCount();
} catch(Exception e) { } catch(Exception e) {
videoInfo.addException(e); streamInfo.addException(e);
} }
try { try {
videoInfo.next_video = extractor.getNextVideo(); streamInfo.next_video = extractor.getNextVideo();
} catch(Exception e) { } catch(Exception e) {
videoInfo.addException(e); streamInfo.addException(e);
} }
try { try {
videoInfo.related_videos = extractor.getRelatedVideos(); streamInfo.related_videos = extractor.getRelatedVideos();
} catch(Exception e) { } catch(Exception e) {
videoInfo.addException(e); streamInfo.addException(e);
} }
try { try {
} catch (Exception e) { } catch (Exception e) {
videoInfo.addException(e); streamInfo.addException(e);
} }
return videoInfo; return streamInfo;
} }
public String uploader_thumbnail_url = ""; public String uploader_thumbnail_url = "";
@ -220,20 +227,20 @@ public class VideoInfo extends AbstractVideoInfo {
public int like_count = -1; public int like_count = -1;
public int dislike_count = -1; public int dislike_count = -1;
public String average_rating = ""; public String average_rating = "";
public VideoPreviewInfo next_video = null; public StreamPreviewInfo next_video = null;
public List<VideoPreviewInfo> related_videos = null; public List<StreamPreviewInfo> related_videos = null;
//in seconds. some metadata is not passed using a VideoInfo object! //in seconds. some metadata is not passed using a StreamInfo object!
public int start_position = 0; public int start_position = 0;
//todo: public int service_id = -1; //todo: public int service_id = -1;
public List<Exception> errors = new Vector<>(); public List<Exception> errors = new Vector<>();
public VideoInfo() {} public StreamInfo() {}
/**Creates a new VideoInfo object from an existing AbstractVideoInfo. /**Creates a new StreamInfo object from an existing AbstractVideoInfo.
* All the shared properties are copied to the new VideoInfo.*/ * All the shared properties are copied to the new StreamInfo.*/
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
public VideoInfo(AbstractVideoInfo avi) { public StreamInfo(AbstractVideoInfo avi) {
this.id = avi.id; this.id = avi.id;
this.title = avi.title; this.title = avi.title;
this.uploader = avi.uploader; this.uploader = avi.uploader;
@ -245,9 +252,9 @@ public class VideoInfo extends AbstractVideoInfo {
this.view_count = avi.view_count; this.view_count = avi.view_count;
//todo: better than this //todo: better than this
if(avi instanceof VideoPreviewInfo) { if(avi instanceof StreamPreviewInfo) {
//shitty String to convert code //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 minutes = Integer.parseInt(dur.substring(0, dur.indexOf(":")));
int seconds = Integer.parseInt(dur.substring(dur.indexOf(":")+1, dur.length())); int seconds = Integer.parseInt(dur.substring(dur.indexOf(":")+1, dur.length()));
this.duration = (minutes*60)+seconds; this.duration = (minutes*60)+seconds;

View file

@ -1,14 +1,10 @@
package org.schabi.newpipe.extractor; 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. * Created by Christian Schabesberger on 26.08.15.
* *
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org> * Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* VideoPreviewInfo.java is part of NewPipe. * StreamPreviewInfo.java is part of NewPipe.
* *
* NewPipe is free software: you can redistribute it and/or modify * NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * 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*/ /**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 = ""; 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. * Created by Christian Schabesberger on 02.02.16.
* *
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org> * 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 * NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * 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/>. * along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/ */
public interface VideoUrlIdHandler { public interface StreamUrlIdHandler {
String getVideoUrl(String videoId); String getVideoUrl(String videoId);
String getVideoId(String siteUrl) throws ParsingException; String getVideoId(String siteUrl) throws ParsingException;
String cleanUrl(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. * 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. * StreamingService.java is part of NewPipe.
* *
* NewPipe is free software: you can redistribute it and/or modify * NewPipe is free software: you can redistribute it and/or modify
@ -31,7 +31,7 @@ public interface StreamingService {
throws IOException, ExtractionException; throws IOException, ExtractionException;
SearchEngine getSearchEngineInstance(); 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.Document;
import org.jsoup.nodes.Element; import org.jsoup.nodes.Element;
import org.schabi.newpipe.extractor.Downloader; import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.Parser; import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.ParsingException; import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.SearchEngine; 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.Node;
import org.w3c.dom.NodeList; import org.w3c.dom.NodeList;
import org.xml.sax.InputSource; import org.xml.sax.InputSource;
@ -49,9 +52,10 @@ public class YoutubeSearchEngine implements SearchEngine {
private static final String TAG = YoutubeSearchEngine.class.toString(); private static final String TAG = YoutubeSearchEngine.class.toString();
@Override @Override
public Result search(String query, int page, String languageCode, Downloader downloader) public StreamPreviewInfoCollector search(String query, int page, String languageCode, Downloader downloader)
throws IOException, ParsingException { throws IOException, ExtractionException {
Result result = new Result(); StreamPreviewInfoCollector collector = new StreamPreviewInfoCollector(
new YoutubeStreamUrlIdHandler());
Uri.Builder builder = new Uri.Builder(); Uri.Builder builder = new Uri.Builder();
builder.scheme("https") builder.scheme("https")
.authority("www.youtube.com") .authority("www.youtube.com")
@ -71,12 +75,11 @@ public class YoutubeSearchEngine implements SearchEngine {
site = downloader.download(url); site = downloader.download(url);
} }
try {
Document doc = Jsoup.parse(site, url); Document doc = Jsoup.parse(site, url);
Element list = doc.select("ol[class=\"item-section\"]").first(); 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. /* 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 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 regular videos, playlists, channels, two types of video suggestions, and a "no video
@ -88,66 +91,33 @@ public class YoutubeSearchEngine implements SearchEngine {
playlists now. playlists now.
*/ */
Element el; Element el;
// both types of spell correction item // both types of spell correction item
if (!((el = item.select("div[class*=\"spell-correction\"]").first()) == null)) { if (!((el = item.select("div[class*=\"spell-correction\"]").first()) == null)) {
result.suggestion = el.select("a").first().text(); collector.setSuggestion(el.select("a").first().text());
// search message item if(list.children().size() == 1) {
} else if (!((el = item.select("div[class*=\"search-message\"]").first()) == null)) { throw new NothingFoundException("Did you mean: " + el.select("a").first().text());
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 + "\"");
} }
// 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 @Override
public ArrayList<String> suggestionList(String query,String contentCountry, Downloader dl) public ArrayList<String> suggestionList(String query, String contentCountry, Downloader dl)
throws IOException, ParsingException { throws IOException, ParsingException {
ArrayList<String> suggestions = new ArrayList<>(); ArrayList<String> suggestions = new ArrayList<>();
@ -167,103 +137,115 @@ public class YoutubeSearchEngine implements SearchEngine {
String response = dl.download(url); 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 { 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 try {
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); NodeList nList = doc.getElementsByTagName("CompleteSuggestion");
DocumentBuilder dBuilder; for (int temp = 0; temp < nList.getLength(); temp++) {
org.w3c.dom.Document doc = null;
try { NodeList nList1 = doc.getElementsByTagName("suggestion");
dBuilder = dbFactory.newDocumentBuilder(); Node nNode1 = nList1.item(temp);
doc = dBuilder.parse(new InputSource( if (nNode1.getNodeType() == Node.ELEMENT_NODE) {
new ByteArrayInputStream(response.getBytes("utf-8")))); org.w3c.dom.Element eElement = (org.w3c.dom.Element) nNode1;
doc.getDocumentElement().normalize(); suggestions.add(eElement.getAttribute("data"));
} 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"));
}
} }
} else {
Log.e(TAG, "GREAT FUCKING ERROR");
} }
return suggestions; return suggestions;
} catch(Exception e) { } catch(Exception e) {
throw new ParsingException(e); throw new ParsingException("Could not get suggestions form document.", e);
} }
} }
private String getWebpageUrl(Element item) { private StreamPreviewInfoExtractor extractPreviewInfo(final Element item) {
Element el = item.select("div[class*=\"yt-lockup-video\"").first(); return new StreamPreviewInfoExtractor() {
Element dl = el.select("h3").first().select("a").first(); @Override
return dl.attr("abs:href"); 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) { @Override
Element el = item.select("div[class*=\"yt-lockup-video\"").first(); public String getTitle() throws ParsingException {
Element dl = el.select("h3").first().select("a").first(); Element el = item.select("div[class*=\"yt-lockup-video\"").first();
return dl.text(); Element dl = el.select("h3").first().select("a").first();
} return dl.text();
}
private String getDuration(Element item) { @Override
try { public String getDuration() throws ParsingException {
return item.select("span[class=\"video-time\"]").first().text(); try {
} catch(Exception e) { return item.select("span[class=\"video-time\"]").first().text();
e.printStackTrace(); } catch(Exception e) {
} e.printStackTrace();
return ""; }
} return "";
}
private String getUploader(Element item) { @Override
return item.select("div[class=\"yt-lockup-byline\"]").first() public String getUploader() throws ParsingException {
.select("a").first() return item.select("div[class=\"yt-lockup-byline\"]").first()
.text(); .select("a").first()
} .text();
}
private String getUploadDate(Element item) { @Override
return item.select("div[class=\"yt-lockup-meta\"]").first() public String getUploadDate() throws ParsingException {
.select("li").first() return item.select("div[class=\"yt-lockup-meta\"]").first()
.text(); .select("li").first()
} .text();
}
private long getViewCount(Element item) throws Parser.RegexException{ @Override
String output; public long getViewCount() throws ParsingException {
String input = item.select("div[class=\"yt-lockup-meta\"]").first() String output;
.select("li").get(1) String input = item.select("div[class=\"yt-lockup-meta\"]").first()
.text(); .select("li").get(1)
output = Parser.matchGroup1("([0-9,\\. ]*)", input) .text();
.replace(" ", "") output = Parser.matchGroup1("([0-9,\\. ]*)", input)
.replace(".", "") .replace(" ", "")
.replace(",", ""); .replace(".", "")
.replace(",", "");
if(Long.parseLong(output) == 30) { try {
Log.d(TAG, "bla"); return Long.parseLong(output);
} } catch (NumberFormatException e) {
return Long.parseLong(output); // 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) { @Override
String url; public String getThumbnailUrl() throws ParsingException {
Element te = item.select("div[class=\"yt-thumb video-thumb\"]").first() String url;
.select("img").first(); Element te = item.select("div[class=\"yt-thumb video-thumb\"]").first()
url = te.attr("abs:src"); .select("img").first();
// Sometimes youtube sends links to gif files which somehow seem to not exist url = te.attr("abs:src");
// anymore. Items with such gif also offer a secondary image source. So we are going // Sometimes youtube sends links to gif files which somehow seem to not exist
// to use that if we've caught such an item. // anymore. Items with such gif also offer a secondary image source. So we are going
if (url.contains(".gif")) { // to use that if we've caught such an item.
url = te.attr("abs:data-thumb"); 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.Downloader;
import org.schabi.newpipe.extractor.StreamExtractor; import org.schabi.newpipe.extractor.StreamExtractor;
import org.schabi.newpipe.extractor.StreamingService; 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 org.schabi.newpipe.extractor.SearchEngine;
import java.io.IOException; import java.io.IOException;
@ -40,7 +40,7 @@ public class YoutubeService implements StreamingService {
@Override @Override
public StreamExtractor getExtractorInstance(String url, Downloader downloader) public StreamExtractor getExtractorInstance(String url, Downloader downloader)
throws ExtractionException, IOException { throws ExtractionException, IOException {
VideoUrlIdHandler urlIdHandler = new YoutubeVideoUrlIdHandler(); StreamUrlIdHandler urlIdHandler = new YoutubeStreamUrlIdHandler();
if(urlIdHandler.acceptUrl(url)) { if(urlIdHandler.acceptUrl(url)) {
return new YoutubeStreamExtractor(url, downloader) ; return new YoutubeStreamExtractor(url, downloader) ;
} }
@ -54,7 +54,7 @@ public class YoutubeService implements StreamingService {
} }
@Override @Override
public VideoUrlIdHandler getUrlIdHandler() { public StreamUrlIdHandler getUrlIdHandler() {
return new YoutubeVideoUrlIdHandler(); 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.Downloader;
import org.schabi.newpipe.extractor.Parser; import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.ParsingException; 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.StreamExtractor;
import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.VideoInfo;
import org.schabi.newpipe.extractor.VideoPreviewInfo;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@ -52,9 +52,6 @@ public class YoutubeStreamExtractor implements StreamExtractor {
// exceptions // exceptions
public class DecryptException extends ParsingException { public class DecryptException extends ParsingException {
DecryptException(Throwable cause) {
super(cause);
}
DecryptException(String message, Throwable cause) { DecryptException(String message, Throwable cause) {
super(message, cause); super(message, cause);
} }
@ -69,8 +66,8 @@ public class YoutubeStreamExtractor implements StreamExtractor {
} }
public class LiveStreamException extends ContentNotAvailableException { public class LiveStreamException extends ContentNotAvailableException {
LiveStreamException() { LiveStreamException(String message) {
super(); super(message);
} }
} }
@ -179,7 +176,7 @@ public class YoutubeStreamExtractor implements StreamExtractor {
// cached values // cached values
private static volatile String decryptionCode = ""; private static volatile String decryptionCode = "";
VideoUrlIdHandler urlidhandler = new YoutubeVideoUrlIdHandler(); StreamUrlIdHandler urlidhandler = new YoutubeStreamUrlIdHandler();
String pageUrl = ""; String pageUrl = "";
private Downloader downloader; private Downloader downloader;
@ -250,7 +247,7 @@ public class YoutubeStreamExtractor implements StreamExtractor {
throw new ParsingException("Could not parse yt player config", e); throw new ParsingException("Could not parse yt player config", e);
} }
if (isLiveStream) { if (isLiveStream) {
throw new LiveStreamException(); throw new LiveStreamException("This is a Life stream. Can't use those right now.");
} }
return playerArgs; return playerArgs;
@ -433,8 +430,8 @@ public class YoutubeStreamExtractor implements StreamExtractor {
@Override @Override
public List<VideoInfo.AudioStream> getAudioStreams() throws ParsingException { public List<StreamInfo.AudioStream> getAudioStreams() throws ParsingException {
Vector<VideoInfo.AudioStream> audioStreams = new Vector<>(); Vector<StreamInfo.AudioStream> audioStreams = new Vector<>();
try{ try{
String encoded_url_map; String encoded_url_map;
// playerArgs could be null if the video is age restricted // playerArgs could be null if the video is age restricted
@ -461,7 +458,7 @@ public class YoutubeStreamExtractor implements StreamExtractor {
+ decryptSignature(tags.get("s"), decryptionCode); + decryptSignature(tags.get("s"), decryptionCode);
} }
audioStreams.add(new VideoInfo.AudioStream(streamUrl, audioStreams.add(new StreamInfo.AudioStream(streamUrl,
itagItem.mediaFormatId, itagItem.mediaFormatId,
itagItem.bandWidth, itagItem.bandWidth,
itagItem.samplingRate)); itagItem.samplingRate));
@ -475,8 +472,8 @@ public class YoutubeStreamExtractor implements StreamExtractor {
} }
@Override @Override
public List<VideoInfo.VideoStream> getVideoStreams() throws ParsingException { public List<StreamInfo.VideoStream> getVideoStreams() throws ParsingException {
Vector<VideoInfo.VideoStream> videoStreams = new Vector<>(); Vector<StreamInfo.VideoStream> videoStreams = new Vector<>();
try{ try{
String encoded_url_map; String encoded_url_map;
@ -504,7 +501,7 @@ public class YoutubeStreamExtractor implements StreamExtractor {
streamUrl = streamUrl + "&signature=" streamUrl = streamUrl + "&signature="
+ decryptSignature(tags.get("s"), decryptionCode); + decryptSignature(tags.get("s"), decryptionCode);
} }
videoStreams.add(new VideoInfo.VideoStream( videoStreams.add(new StreamInfo.VideoStream(
streamUrl, streamUrl,
itagItem.mediaFormatId, itagItem.mediaFormatId,
itagItem.resolutionString)); itagItem.resolutionString));
@ -527,7 +524,7 @@ public class YoutubeStreamExtractor implements StreamExtractor {
} }
@Override @Override
public List<VideoInfo.VideoStream> getVideoOnlyStreams() throws ParsingException { public List<StreamInfo.VideoStream> getVideoOnlyStreams() throws ParsingException {
return null; return null;
} }
@ -638,7 +635,7 @@ public class YoutubeStreamExtractor implements StreamExtractor {
} }
@Override @Override
public VideoPreviewInfo getNextVideo() throws ParsingException { public StreamPreviewInfo getNextVideo() throws ParsingException {
try { try {
return extractVideoPreviewInfo(doc.select("div[class=\"watch-sidebar-section\"]").first() return extractVideoPreviewInfo(doc.select("div[class=\"watch-sidebar-section\"]").first()
.select("li").first()); .select("li").first());
@ -648,9 +645,9 @@ public class YoutubeStreamExtractor implements StreamExtractor {
} }
@Override @Override
public Vector<VideoPreviewInfo> getRelatedVideos() throws ParsingException { public Vector<StreamPreviewInfo> getRelatedVideos() throws ParsingException {
try { try {
Vector<VideoPreviewInfo> relatedVideos = new Vector<>(); Vector<StreamPreviewInfo> relatedVideos = new Vector<>();
for (Element li : doc.select("ul[id=\"watch-related\"]").first().children()) { for (Element li : doc.select("ul[id=\"watch-related\"]").first().children()) {
// first check if we have a playlist. If so leave them out // first check if we have a playlist. If so leave them out
if (li.select("a[class*=\"content-link\"]").first() != null) { if (li.select("a[class*=\"content-link\"]").first() != null) {
@ -664,8 +661,8 @@ public class YoutubeStreamExtractor implements StreamExtractor {
} }
@Override @Override
public VideoUrlIdHandler getUrlIdConverter() { public StreamUrlIdHandler getUrlIdConverter() {
return new YoutubeVideoUrlIdHandler(); return new YoutubeStreamUrlIdHandler();
} }
@Override @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. /**Provides information about links to other videos on the video page, such as related videos.
* This is encapsulated in a VideoPreviewInfo object, * This is encapsulated in a StreamPreviewInfo object,
* which is a subset of the fields in a full VideoInfo.*/ * which is a subset of the fields in a full StreamInfo.*/
private VideoPreviewInfo extractVideoPreviewInfo(Element li) throws ParsingException { private StreamPreviewInfo extractVideoPreviewInfo(Element li) throws ParsingException {
VideoPreviewInfo info = new VideoPreviewInfo(); StreamPreviewInfo info = new StreamPreviewInfo();
try { try {
info.webpage_url = li.select("a.content-link").first() 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; info.thumbnail_url = "https:" + info.thumbnail_url;
} }
} catch (Exception e) { } catch (Exception e) {
throw new ParsingException(e); throw new ParsingException("Could not get video preview info", e);
} }
return info; return info;
} }
@ -772,7 +769,7 @@ public class YoutubeStreamExtractor implements StreamExtractor {
Function decryptionFunc = (Function) scope.get("decrypt", scope); Function decryptionFunc = (Function) scope.get("decrypt", scope);
result = decryptionFunc.call(context, scope, scope, new Object[]{encryptedSig}); result = decryptionFunc.call(context, scope, scope, new Object[]{encryptedSig});
} catch (Exception e) { } catch (Exception e) {
throw new DecryptException(e); throw new DecryptException("could not get decrypt signature", e);
} finally { } finally {
Context.exit(); 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.Parser;
import org.schabi.newpipe.extractor.ParsingException; import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.VideoUrlIdHandler; import org.schabi.newpipe.extractor.StreamUrlIdHandler;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.URLDecoder; import java.net.URLDecoder;
@ -11,7 +11,7 @@ import java.net.URLDecoder;
* Created by Christian Schabesberger on 02.02.16. * Created by Christian Schabesberger on 02.02.16.
* *
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org> * 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 * NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * 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/>. * along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/ */
public class YoutubeVideoUrlIdHandler implements VideoUrlIdHandler { public class YoutubeStreamUrlIdHandler implements StreamUrlIdHandler {
@SuppressWarnings("WeakerAccess") @SuppressWarnings("WeakerAccess")
@Override @Override
public String getVideoUrl(String videoId) { 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 --> <!-- Paddings & Margins -->
<dimen name="video_item_detail_like_margin">6sp</dimen> <dimen name="video_item_detail_like_margin">6sp</dimen>
<dimen name="video_item_detail_play_fab_margin">20dp</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> </resources>

View file

@ -15,7 +15,7 @@
<string name="download">Download</string> <string name="download">Download</string>
<string name="search">Search</string> <string name="search">Search</string>
<string name="settings">Settings</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="search_page">Search page: </string>
<string name="share_dialog_title">Share with:</string> <string name="share_dialog_title">Share with:</string>
<string name="choose_browser">Choose browser:</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="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="youtube_signature_decryption_error">Could not decrypt video url signature.</string>
<string name="parsing_error">Could not parse website.</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="content_not_available">Content not available.</string>
<string name="blocked_by_gema">Blocked by GEMA.</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="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="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) --> <!-- Content descriptions (for better accessibility) -->