Merge branch 'advancedErrorHandling'

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

View file

@ -42,4 +42,5 @@ dependencies {
compile 'de.hdodenhof:circleimageview:2.0.0'
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
compile 'com.github.nirhart:parallaxscroll:1.0'
compile 'org.apache.directory.studio:org.apache.commons.lang:2.6'
}

View file

@ -2,7 +2,9 @@ package org.schabi.newpipe.extractor.youtube;
import android.test.AndroidTestCase;
import org.schabi.newpipe.extractor.VideoPreviewInfo;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.schabi.newpipe.extractor.SearchResult;
import org.schabi.newpipe.extractor.StreamPreviewInfo;
import org.schabi.newpipe.extractor.SearchEngine;
import org.schabi.newpipe.extractor.services.youtube.YoutubeSearchEngine;
import org.schabi.newpipe.Downloader;
@ -30,7 +32,7 @@ import java.util.ArrayList;
*/
public class YoutubeSearchEngineTest extends AndroidTestCase {
private SearchEngine.Result result;
private SearchResult result;
private ArrayList<String> suggestionReply;
@Override
@ -39,12 +41,13 @@ public class YoutubeSearchEngineTest extends AndroidTestCase {
SearchEngine engine = new YoutubeSearchEngine();
result = engine.search("bla",
0, "de", new Downloader());
0, "de", new Downloader()).getSearchResult();
suggestionReply = engine.suggestionList("hello","de",new Downloader());
}
public void testIfNoErrorOccur() {
assertEquals(result.errorMessage, "");
assertTrue(result.errors.isEmpty() ? "" : ExceptionUtils.getStackTrace(result.errors.get(0))
,result.errors.isEmpty());
}
public void testIfListIsNotEmpty() {
@ -52,44 +55,44 @@ public class YoutubeSearchEngineTest extends AndroidTestCase {
}
public void testItemsHaveTitle() {
for(VideoPreviewInfo i : result.resultList) {
for(StreamPreviewInfo i : result.resultList) {
assertEquals(i.title.isEmpty(), false);
}
}
public void testItemsHaveUploader() {
for(VideoPreviewInfo i : result.resultList) {
for(StreamPreviewInfo i : result.resultList) {
assertEquals(i.uploader.isEmpty(), false);
}
}
public void testItemsHaveRightDuration() {
for(VideoPreviewInfo i : result.resultList) {
for(StreamPreviewInfo i : result.resultList) {
assertTrue(i.duration, i.duration.contains(":"));
}
}
public void testItemsHaveRightThumbnail() {
for (VideoPreviewInfo i : result.resultList) {
for (StreamPreviewInfo i : result.resultList) {
assertTrue(i.thumbnail_url, i.thumbnail_url.contains("https://"));
}
}
public void testItemsHaveRightVideoUrl() {
for (VideoPreviewInfo i : result.resultList) {
for (StreamPreviewInfo i : result.resultList) {
assertTrue(i.webpage_url, i.webpage_url.contains("https://"));
}
}
public void testViewCount() {
/*
for(VideoPreviewInfo i : result.resultList) {
for(StreamPreviewInfo i : result.resultList) {
assertTrue(Long.toString(i.view_count), i.view_count != -1);
}
*/
// that specific link used for this test, there are no videos with less
// than 10.000 views, so we can test against that.
for(VideoPreviewInfo i : result.resultList) {
for(StreamPreviewInfo i : result.resultList) {
assertTrue(Long.toString(i.view_count), i.view_count >= 10000);
}
}

View file

@ -6,7 +6,7 @@ import org.schabi.newpipe.Downloader;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
import org.schabi.newpipe.extractor.VideoInfo;
import org.schabi.newpipe.extractor.StreamInfo;
import java.io.IOException;
@ -94,7 +94,7 @@ public class YoutubeStreamExtractorDefaultTest extends AndroidTestCase {
}
public void testGetVideoStreams() throws ParsingException {
for(VideoInfo.VideoStream s : extractor.getVideoStreams()) {
for(StreamInfo.VideoStream s : extractor.getVideoStreams()) {
assertTrue(s.url,
s.url.contains("https://"));
assertTrue(s.resolution.length() > 0);

View file

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

View file

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

View file

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

View file

@ -22,10 +22,12 @@ package org.schabi.newpipe;
import android.graphics.Bitmap;
import java.util.List;
/**
* Singleton:
* Used to send data between certain Activity/Services within the same process.
* This can be considered as hack inside the Android universe. **/
* This can be considered as an ugly hack inside the Android universe. **/
public class ActivityCommunicator {
private static ActivityCommunicator activityCommunicator = null;
@ -39,4 +41,9 @@ public class ActivityCommunicator {
// Thumbnail send from VideoItemDetailFragment to BackgroundPlayer
public volatile Bitmap backgroundPlayerThumbnail;
// Sent from any activity to ErrorActivity.
public volatile List<Exception> errorList;
public volatile Class returnActivity;
public volatile ErrorActivity.ErrorInfo errorInfo;
}

View file

@ -0,0 +1,385 @@
package org.schabi.newpipe;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.design.widget.Snackbar;
import android.support.v4.app.NavUtils;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.json.JSONArray;
import org.json.JSONObject;
import org.schabi.newpipe.extractor.Parser;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.TimeZone;
import java.util.Vector;
/**
* Created by Christian Schabesberger on 24.10.15.
* <p>
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* ErrorActivity.java is part of NewPipe.
* <p>
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* <p>
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class ErrorActivity extends AppCompatActivity {
public static class ErrorInfo {
public int userAction;
public String request;
public String serviceName;
public int message;
public static ErrorInfo make(int userAction, String serviceName, String request, int message) {
ErrorInfo info = new ErrorInfo();
info.userAction = userAction;
info.serviceName = serviceName;
info.request = request;
info.message = message;
return info;
}
}
public static final String TAG = ErrorActivity.class.toString();
public static final int SEARCHED = 0;
public static final int REQUESTED_STREAM = 1;
public static final int GET_SUGGESTIONS = 2;
public static final String SEARCHED_STRING = "searched";
public static final String REQUESTED_STREAM_STRING = "requested stream";
public static final String GET_SUGGESTIONS_STRING = "get suggestions";
public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org";
public static final String ERROR_EMAIL_SUBJECT = "Exception in NewPipe " + BuildConfig.VERSION_NAME;
private List<Exception> errorList;
private ErrorInfo errorInfo;
private Class returnActivity;
private String currentTimeStamp;
private String globIpRange;
Thread globIpRangeThread = null;
// views
private TextView errorView;
private EditText userCommentBox;
private Button reportButton;
private TextView infoView;
private TextView errorMessageView;
public static void reportError(final Context context, final List<Exception> el,
final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) {
if (rootView != null) {
Snackbar.make(rootView, R.string.error_snackbar_message, Snackbar.LENGTH_LONG)
.setActionTextColor(Color.YELLOW)
.setAction(R.string.error_snackbar_action, new View.OnClickListener() {
@Override
public void onClick(View v) {
ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
ac.errorList = el;
ac.returnActivity = returnAcitivty;
ac.errorInfo = errorInfo;
Intent intent = new Intent(context, ErrorActivity.class);
context.startActivity(intent);
}
}).show();
} else {
ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
ac.errorList = el;
ac.returnActivity = returnAcitivty;
ac.errorInfo = errorInfo;
Intent intent = new Intent(context, ErrorActivity.class);
context.startActivity(intent);
}
}
public static void reportError(final Context context, final Exception e,
final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) {
List<Exception> el = new Vector<>();
el.add(e);
reportError(context, el, returnAcitivty, rootView, errorInfo);
}
// async call
public static void reportError(Handler handler, final Context context, final Exception e,
final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) {
List<Exception> el = new Vector<>();
el.add(e);
reportError(handler, context, el, returnAcitivty, rootView, errorInfo);
}
// async call
public static void reportError(Handler handler, final Context context, final List<Exception> el,
final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) {
handler.post(new Runnable() {
@Override
public void run() {
reportError(context, el, returnAcitivty, rootView, errorInfo);
}
});
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_error);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
ActivityCommunicator ac = ActivityCommunicator.getCommunicator();
errorList = ac.errorList;
returnActivity = ac.returnActivity;
errorInfo = ac.errorInfo;
reportButton = (Button) findViewById(R.id.errorReportButton);
userCommentBox = (EditText) findViewById(R.id.errorCommentBox);
errorView = (TextView) findViewById(R.id.errorView);
infoView = (TextView) findViewById(R.id.errorInfosView);
errorMessageView = (TextView) findViewById(R.id.errorMessageView);
errorView.setText(formErrorText(errorList));
//importand add gurumeditaion
addGuruMeditaion();
currentTimeStamp = getCurrentTimeStamp();
buildInfo(errorInfo);
reportButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_SENDTO);
intent.setData(Uri.parse("mailto:" + ERROR_EMAIL_ADDRESS))
.putExtra(Intent.EXTRA_SUBJECT, ERROR_EMAIL_SUBJECT)
.putExtra(Intent.EXTRA_TEXT, buildJson());
startActivity(Intent.createChooser(intent, "Send Email"));
}
});
reportButton.setEnabled(false);
globIpRangeThread = new Thread(new IpRagneRequester());
globIpRangeThread.start();
if(errorInfo.message != 0) {
errorMessageView.setText(errorInfo.message);
} else {
errorMessageView.setVisibility(View.GONE);
findViewById(R.id.messageWhatHappenedView).setVisibility(View.GONE);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.error_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case android.R.id.home:
goToReturnActivity();
break;
case R.id.menu_item_share_error: {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, buildJson());
intent.setType("text/plain");
startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title)));
}
break;
}
return false;
}
private String formErrorText(List<Exception> el) {
String text = "";
for (Exception e : el) {
text += "-------------------------------------\n"
+ ExceptionUtils.getStackTrace(e);
}
text += "-------------------------------------";
return text;
}
private void goToReturnActivity() {
if (returnActivity == null) {
super.onBackPressed();
} else {
Intent intent;
if (returnActivity != null &&
returnActivity.isAssignableFrom(Activity.class)) {
intent = new Intent(this, returnActivity);
} else {
intent = new Intent(this, VideoItemListActivity.class);
}
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent);
}
}
private void buildInfo(ErrorInfo info) {
TextView infoLabelView = (TextView) findViewById(R.id.errorInfoLabelsView);
TextView infoView = (TextView) findViewById(R.id.errorInfosView);
String text = "";
infoLabelView.setText(getString(R.string.info_labels).replace("\\n", "\n"));
text += getUserActionString(info.userAction)
+ "\n" + info.request
+ "\n" + getContentLangString()
+ "\n" + info.serviceName
+ "\n" + currentTimeStamp
+ "\n" + BuildConfig.VERSION_NAME
+ "\n" + getOsString();
infoView.setText(text);
}
private String buildJson() {
JSONObject errorObject = new JSONObject();
try {
errorObject.put("user_action", getUserActionString(errorInfo.userAction))
.put("request", errorInfo.request)
.put("content_language", getContentLangString())
.put("service", errorInfo.serviceName)
.put("version", BuildConfig.VERSION_NAME)
.put("os", getOsString())
.put("time", currentTimeStamp)
.put("ip_range", globIpRange);
JSONArray exceptionArray = new JSONArray();
for (Exception e : errorList) {
exceptionArray.put(ExceptionUtils.getStackTrace(e));
}
errorObject.put("exceptions", exceptionArray);
errorObject.put("user_comment", userCommentBox.getText().toString());
return errorObject.toString(3);
} catch (Exception e) {
Log.e(TAG, "Error while erroring: Could not build json");
e.printStackTrace();
}
return "";
}
private String getUserActionString(int userAction) {
switch (userAction) {
case REQUESTED_STREAM:
return REQUESTED_STREAM_STRING;
case SEARCHED:
return SEARCHED_STRING;
case GET_SUGGESTIONS:
return GET_SUGGESTIONS_STRING;
default:
return "Your description is in another castle.";
}
}
private String getContentLangString() {
return PreferenceManager.getDefaultSharedPreferences(this)
.getString(this.getString(R.string.search_language_key), "none");
}
private String getOsString() {
String osBase = Build.VERSION.SDK_INT >= 23 ? Build.VERSION.BASE_OS : "Android";
return System.getProperty("os.name")
+ " " + (osBase.isEmpty() ? "Android" : osBase)
+ " " + Build.VERSION.RELEASE
+ " - " + Integer.toString(Build.VERSION.SDK_INT);
}
private void addGuruMeditaion() {
//just an easter egg
TextView sorryView = (TextView) findViewById(R.id.errorSorryView);
String text = sorryView.getText().toString();
text += "\n" + getString(R.string.guru_meditation);
sorryView.setText(text);
}
@Override
public void onBackPressed() {
//super.onBackPressed();
goToReturnActivity();
}
public String getCurrentTimeStamp() {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm");
df.setTimeZone(TimeZone.getTimeZone("GMT"));
return df.format(new Date());
}
private class IpRagneRequester implements Runnable {
Handler h = new Handler();
public void run() {
String ipRange = "none";
try {
Downloader dl = new Downloader();
String ip = dl.download("https://ifcfg.me/ip");
ipRange = Parser.matchGroup1("([0-9]*\\.[0-9]*\\.)[0-9]*\\.[0-9]*", ip)
+ "0.0";
} catch(Exception e) {
Log.d(TAG, "Error while error: could not get iprange");
e.printStackTrace();
} finally {
h.post(new IpRageReturnRunnable(ipRange));
}
}
}
private class IpRageReturnRunnable implements Runnable {
String ipRange;
public IpRageReturnRunnable(String ipRange) {
this.ipRange = ipRange;
}
public void run() {
globIpRange = ipRange;
if(infoView != null) {
String text = infoView.getText().toString();
text += "\n" + globIpRange;
infoView.setText(text);
reportButton.setEnabled(true);
}
}
}
}

View file

@ -7,7 +7,8 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.schabi.newpipe.extractor.VideoPreviewInfo;
import org.schabi.newpipe.extractor.StreamPreviewInfo;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
@ -40,8 +41,10 @@ class VideoInfoItemViewCreator {
this.inflater = inflater;
}
public View getViewFromVideoInfoItem(View convertView, ViewGroup parent, VideoPreviewInfo info, Context context) {
public View getViewFromVideoInfoItem(View convertView, ViewGroup parent, StreamPreviewInfo info) {
ViewHolder holder;
// generate holder
if(convertView == null) {
convertView = inflater.inflate(R.layout.video_item, parent, false);
holder = new ViewHolder();
@ -56,20 +59,41 @@ class VideoInfoItemViewCreator {
holder = (ViewHolder) convertView.getTag();
}
// fill with information
/*
if(info.thumbnail == null) {
holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
} else {
holder.itemThumbnailView.setImageBitmap(info.thumbnail);
}
*/
holder.itemVideoTitleView.setText(info.title);
if(info.uploader != null && !info.uploader.isEmpty()) {
holder.itemUploaderView.setText(info.uploader);
} else {
holder.itemDurationView.setVisibility(View.INVISIBLE);
}
if(info.duration != null && !info.duration.isEmpty()) {
holder.itemDurationView.setText(info.duration);
} else {
holder.itemDurationView.setVisibility(View.GONE);
}
if(info.view_count >= 0) {
holder.itemViewCountView.setText(shortViewCount(info.view_count));
} else {
holder.itemViewCountView.setVisibility(View.GONE);
}
if(!info.upload_date.isEmpty()) {
holder.itemUploadDateView.setText(info.upload_date+"");
}
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
imageLoader.displayImage(info.thumbnail_url, holder.itemThumbnailView, displayImageOptions);
} else {
holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
}
return convertView;
}

View file

@ -50,9 +50,9 @@ import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamExtractor;
import org.schabi.newpipe.extractor.VideoPreviewInfo;
import org.schabi.newpipe.extractor.StreamInfo;
import org.schabi.newpipe.extractor.StreamPreviewInfo;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.VideoInfo;
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
@ -127,12 +127,34 @@ public class VideoItemDetailFragment extends Fragment {
@Override
public void run() {
VideoInfo videoInfo = null;
StreamInfo streamInfo = null;
try {
streamExtractor = service.getExtractorInstance(videoUrl, new Downloader());
videoInfo = VideoInfo.getVideoInfo(streamExtractor, new Downloader());
streamInfo = StreamInfo.getVideoInfo(streamExtractor, new Downloader());
h.post(new VideoResultReturnedRunnable(videoInfo));
h.post(new VideoResultReturnedRunnable(streamInfo));
// look for errors during extraction
// this if statement only covers extra information.
// if these are not available or caused an error, they are just not available
// but don't render the stream information unusalbe.
if(streamInfo != null &&
!streamInfo.errors.isEmpty()) {
Log.e(TAG, "OCCURRED ERRORS DURING EXTRACTION:");
for (Exception e : streamInfo.errors) {
e.printStackTrace();
Log.e(TAG, "------");
}
Activity a = getActivity();
View rootView = a != null ? a.findViewById(R.id.videoitem_detail) : null;
ErrorActivity.reportError(h, getActivity(),
streamInfo.errors, null, rootView,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, videoUrl, 0 /* no message for the user */));
}
// These errors render the stream information unusable.
} catch (IOException e) {
postNewErrorToast(h, R.string.network_error);
e.printStackTrace();
@ -165,40 +187,69 @@ public class VideoItemDetailFragment extends Fragment {
}
});
e.printStackTrace();
} catch(StreamInfo.StreamExctractException e) {
if(!streamInfo.errors.isEmpty()) {
// !!! if this case ever kicks in someone gets kicked out !!!
ErrorActivity.reportError(h, getActivity(), e, VideoItemListActivity.class, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream));
} else {
ErrorActivity.reportError(h, getActivity(), streamInfo.errors, VideoItemListActivity.class, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream));
}
h.post(new Runnable() {
@Override
public void run() {
getActivity().finish();
}
});
e.printStackTrace();
} catch (ParsingException e) {
postNewErrorToast(h, e.getMessage());
ErrorActivity.reportError(h, getActivity(), e, VideoItemListActivity.class, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
service.getServiceInfo().name, videoUrl, R.string.parsing_error));
h.post(new Runnable() {
@Override
public void run() {
getActivity().finish();
}
});
e.printStackTrace();
} catch(Exception e) {
postNewErrorToast(h, R.string.general_error);
e.printStackTrace();
} finally {
if(videoInfo != null &&
!videoInfo.errors.isEmpty()) {
Log.e(TAG, "OCCURRED ERRORS DURING EXTRACTION:");
for(Exception e : videoInfo.errors) {
e.printStackTrace();
}
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();
}
}
}
private class VideoResultReturnedRunnable implements Runnable {
private final VideoInfo videoInfo;
public VideoResultReturnedRunnable(VideoInfo videoInfo) {
this.videoInfo = videoInfo;
private final StreamInfo streamInfo;
public VideoResultReturnedRunnable(StreamInfo streamInfo) {
this.streamInfo = streamInfo;
}
@Override
public void run() {
boolean show_age_restricted_content = PreferenceManager.getDefaultSharedPreferences(getActivity())
Activity a = getActivity();
if(a != null) {
boolean show_age_restricted_content = PreferenceManager.getDefaultSharedPreferences(a)
.getBoolean(activity.getString(R.string.show_age_restricted_content), false);
if(videoInfo.age_limit == 0 || show_age_restricted_content) {
updateInfo(videoInfo);
if (streamInfo.age_limit == 0 || show_age_restricted_content) {
updateInfo(streamInfo);
} else {
onNotSpecifiedContentErrorWithMessage(R.string.video_is_age_restricted);
}
}
}
}
private class ThumbnailLoadingListener implements ImageLoadingListener {
@Override
@ -220,7 +271,7 @@ public class VideoItemDetailFragment extends Fragment {
public void onLoadingCancelled(String imageUri, View view) {}
}
private void updateInfo(final VideoInfo info) {
private void updateInfo(final StreamInfo info) {
try {
Context c = getContext();
VideoInfoItemViewCreator videoItemViewCreator =
@ -249,7 +300,7 @@ public class VideoItemDetailFragment extends Fragment {
View nextVideoView = null;
if(info.next_video != null) {
nextVideoView = videoItemViewCreator
.getViewFromVideoInfoItem(null, nextVideoFrame, info.next_video, getContext());
.getViewFromVideoInfoItem(null, nextVideoFrame, info.next_video);
} else {
activity.findViewById(R.id.detailNextVidButtonAndContentLayout).setVisibility(View.GONE);
activity.findViewById(R.id.detailNextVideoTitle).setVisibility(View.GONE);
@ -337,8 +388,8 @@ public class VideoItemDetailFragment extends Fragment {
descriptionView.setMovementMethod(LinkMovementMethod.getInstance());
// parse streams
Vector<VideoInfo.VideoStream> streamsToUse = new Vector<>();
for (VideoInfo.VideoStream i : info.video_streams) {
Vector<StreamInfo.VideoStream> streamsToUse = new Vector<>();
for (StreamInfo.VideoStream i : info.video_streams) {
if (useStream(i, streamsToUse)) {
streamsToUse.add(i);
}
@ -394,7 +445,7 @@ public class VideoItemDetailFragment extends Fragment {
}
}
private void initThumbnailViews(VideoInfo info, View nextVideoFrame) {
private void initThumbnailViews(StreamInfo info, View nextVideoFrame) {
ImageView videoThumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView);
ImageView uploaderThumb
= (ImageView) activity.findViewById(R.id.detailUploaderThumbnailView);
@ -437,7 +488,7 @@ public class VideoItemDetailFragment extends Fragment {
}
}
private void setupActionBarHandler(final VideoInfo info) {
private void setupActionBarHandler(final StreamInfo info) {
actionBarHandler.setupStreamList(info.video_streams);
actionBarHandler.setOnShareListener(new ActionBarHandler.OnActionListener() {
@ -504,7 +555,7 @@ public class VideoItemDetailFragment extends Fragment {
// website which was crawled. Then the ui has to understand this and act right.
if (info.audio_streams != null) {
VideoInfo.AudioStream audioStream =
StreamInfo.AudioStream audioStream =
info.audio_streams.get(getPreferredAudioStreamId(info));
String audioSuffix = "." + MediaFormat.getSuffixById(audioStream.format);
@ -513,7 +564,7 @@ public class VideoItemDetailFragment extends Fragment {
}
if (info.video_streams != null) {
VideoInfo.VideoStream selectedStreamItem = info.video_streams.get(selectedStreamId);
StreamInfo.VideoStream selectedStreamItem = info.video_streams.get(selectedStreamId);
String videoSuffix = "." + MediaFormat.getSuffixById(selectedStreamItem.format);
args.putString(DownloadDialog.FILE_SUFFIX_VIDEO, videoSuffix);
args.putString(DownloadDialog.VIDEO_URL, selectedStreamItem.url);
@ -540,7 +591,7 @@ public class VideoItemDetailFragment extends Fragment {
boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(activity.getString(R.string.use_external_audio_player_key), false);
Intent intent;
VideoInfo.AudioStream audioStream =
StreamInfo.AudioStream audioStream =
info.audio_streams.get(getPreferredAudioStreamId(info));
if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 18) {
//internal music player: explicit intent
@ -599,7 +650,7 @@ public class VideoItemDetailFragment extends Fragment {
}
}
private int getPreferredAudioStreamId(final VideoInfo info) {
private int getPreferredAudioStreamId(final StreamInfo info) {
String preferredFormatString = PreferenceManager.getDefaultSharedPreferences(getActivity())
.getString(activity.getString(R.string.default_audio_format_key), "webm");
@ -626,12 +677,12 @@ public class VideoItemDetailFragment extends Fragment {
return 0;
}
private void initSimilarVideos(final VideoInfo info, VideoInfoItemViewCreator videoItemViewCreator) {
private void initSimilarVideos(final StreamInfo info, VideoInfoItemViewCreator videoItemViewCreator) {
LinearLayout similarLayout = (LinearLayout) activity.findViewById(R.id.similarVideosView);
ArrayList<VideoPreviewInfo> similar = new ArrayList<>(info.related_videos);
for (final VideoPreviewInfo item : similar) {
ArrayList<StreamPreviewInfo> similar = new ArrayList<>(info.related_videos);
for (final StreamPreviewInfo item : similar) {
View similarView = videoItemViewCreator
.getViewFromVideoInfoItem(null, similarLayout, item, getContext());
.getViewFromVideoInfoItem(null, similarLayout, item);
similarView.setClickable(true);
similarView.setFocusable(true);
@ -703,8 +754,8 @@ public class VideoItemDetailFragment extends Fragment {
.show();
}
private boolean useStream(VideoInfo.VideoStream stream, Vector<VideoInfo.VideoStream> streams) {
for(VideoInfo.VideoStream i : streams) {
private boolean useStream(StreamInfo.VideoStream stream, Vector<StreamInfo.VideoStream> streams) {
for(StreamInfo.VideoStream i : streams) {
if(i.resolution.equals(stream.resolution)) {
return false;
}
@ -794,9 +845,9 @@ public class VideoItemDetailFragment extends Fragment {
}
}
public void playVideo(final VideoInfo info) {
public void playVideo(final StreamInfo info) {
// ----------- THE MAGIC MOMENT ---------------
VideoInfo.VideoStream selectedVideoStream =
StreamInfo.VideoStream selectedVideoStream =
info.video_streams.get(actionBarHandler.getSelectedVideoStream());
if (PreferenceManager.getDefaultSharedPreferences(activity)

View file

@ -21,7 +21,6 @@ import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.SearchEngine;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.VideoPreviewInfo;
import java.io.IOException;
import java.util.ArrayList;
@ -173,11 +172,17 @@ public class VideoItemListActivity extends AppCompatActivity
ArrayList<String>suggestions = engine.suggestionList(query,searchLanguage,new Downloader());
h.post(new SuggestionResultRunnable(suggestions));
} catch (ExtractionException e) {
postNewErrorToast(h, R.string.parsing_error);
ErrorActivity.reportError(h, VideoItemListActivity.this, e, null, findViewById(R.id.videoitem_list),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
/* todo: this shoudl not be assigned static */ "Youtube", query, R.string.parsing_error));
e.printStackTrace();
} catch (IOException e) {
postNewErrorToast(h, R.string.network_error);
e.printStackTrace();
} catch (Exception e) {
ErrorActivity.reportError(h, VideoItemListActivity.this, e, null, findViewById(R.id.videoitem_list),
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
/* todo: this shoudl not be assigned static */ "Youtube", query, R.string.general_error));
}
}
}

View file

@ -1,5 +1,6 @@
package org.schabi.newpipe;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
@ -17,8 +18,8 @@ import java.io.IOException;
import java.util.List;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.VideoPreviewInfo;
import org.schabi.newpipe.extractor.SearchResult;
import org.schabi.newpipe.extractor.StreamPreviewInfo;
import org.schabi.newpipe.extractor.SearchEngine;
import org.schabi.newpipe.extractor.StreamingService;
@ -68,9 +69,9 @@ public class VideoItemListFragment extends ListFragment {
private boolean loadingNextPage = true;
private class ResultRunnable implements Runnable {
private final SearchEngine.Result result;
private final SearchResult result;
private final int requestId;
public ResultRunnable(SearchEngine.Result result, int requestId) {
public ResultRunnable(SearchResult result, int requestId) {
this.result = result;
this.requestId = requestId;
}
@ -101,32 +102,60 @@ public class VideoItemListFragment extends ListFragment {
}
@Override
public void run() {
SearchResult result = null;
try {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
String searchLanguageKey = getContext().getString(R.string.search_language_key);
String searchLanguage = sp.getString(searchLanguageKey,
getString(R.string.default_language_value));
SearchEngine.Result result = engine.search(query, page, searchLanguage,
new Downloader());
result = SearchResult
.getSearchResult(engine, query, page, searchLanguage, new Downloader());
//Log.i(TAG, "language code passed:\""+searchLanguage+"\"");
if(runs) {
h.post(new ResultRunnable(result, requestId));
}
} catch(IOException e) {
postNewErrorToast(h, R.string.network_error);
// 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();
} catch(ExtractionException ce) {
postNewErrorToast(h, R.string.parsing_error);
ce.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) {
postNewNothingFoundToast(h, R.string.network_error);
e.printStackTrace();
} catch(SearchEngine.NothingFoundException e) {
postNewErrorToast(h, e.getMessage());
} catch(ExtractionException e) {
ErrorActivity.reportError(h, getActivity(), e, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
/* todo: this shoudl not be assigned static */ "Youtube", query, R.string.parsing_error));
//postNewErrorToast(h, R.string.parsing_error);
e.printStackTrace();
} catch(Exception e) {
postNewErrorToast(h, R.string.general_error);
ErrorActivity.reportError(h, getActivity(), e, null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
/* todo: this shoudl not be assigned static */ "Youtube", query, R.string.general_error));
e.printStackTrace();
}
}
}
public void present(List<VideoPreviewInfo> videoList) {
public void present(List<StreamPreviewInfo> videoList) {
mode = PRESENT_VIDEOS_MODE;
setListShown(true);
getListView().smoothScrollToPosition(0);
@ -166,22 +195,19 @@ public class VideoItemListFragment extends ListFragment {
this.streamingService = streamingService;
}
private void updateListOnResult(SearchEngine.Result result, int requestId) {
private void updateListOnResult(SearchResult result, int requestId) {
if(requestId == currentRequestId) {
setListShown(true);
if (result.resultList.isEmpty()) {
Toast.makeText(getActivity(), result.errorMessage, Toast.LENGTH_LONG).show();
} else {
if (!result.suggestion.isEmpty()) {
Toast.makeText(getActivity(), getString(R.string.did_you_mean) + result.suggestion + " ?",
Toast.LENGTH_LONG).show();
}
updateList(result.resultList);
if(!result.suggestion.isEmpty()) {
Toast.makeText(getActivity(),
String.format(getString(R.string.did_you_mean), result.suggestion),
Toast.LENGTH_LONG).show();
}
}
}
private void updateList(List<VideoPreviewInfo> list) {
private void updateList(List<StreamPreviewInfo> list) {
try {
videoListAdapter.addVideoList(list);
terminateThreads();
@ -318,13 +344,24 @@ public class VideoItemListFragment extends ListFragment {
mActivatedPosition = position;
}
private void postNewErrorToast(Handler h, final int stringResource) {
private void postNewErrorToast(Handler h, final String message) {
h.post(new Runnable() {
@Override
public void run() {
setListShown(true);
Toast.makeText(getActivity(), message,
Toast.LENGTH_SHORT).show();
}
});
}
private void postNewNothingFoundToast(Handler h, final int stringResource) {
h.post(new Runnable() {
@Override
public void run() {
setListShown(true);
Toast.makeText(getActivity(), getString(stringResource),
Toast.LENGTH_SHORT).show();
Toast.LENGTH_LONG).show();
}
});
}

View file

@ -8,7 +8,7 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import org.schabi.newpipe.extractor.VideoPreviewInfo;
import org.schabi.newpipe.extractor.StreamPreviewInfo;
import java.util.List;
import java.util.Vector;
@ -36,7 +36,7 @@ import java.util.Vector;
class VideoListAdapter extends BaseAdapter {
private final Context context;
private final VideoInfoItemViewCreator viewCreator;
private Vector<VideoPreviewInfo> videoList = new Vector<>();
private Vector<StreamPreviewInfo> videoList = new Vector<>();
private final ListView listView;
public VideoListAdapter(Context context, VideoItemListFragment videoListFragment) {
@ -47,7 +47,7 @@ class VideoListAdapter extends BaseAdapter {
this.context = context;
}
public void addVideoList(List<VideoPreviewInfo> videos) {
public void addVideoList(List<StreamPreviewInfo> videos) {
videoList.addAll(videos);
notifyDataSetChanged();
}
@ -57,7 +57,7 @@ class VideoListAdapter extends BaseAdapter {
notifyDataSetChanged();
}
public Vector<VideoPreviewInfo> getVideoList() {
public Vector<StreamPreviewInfo> getVideoList() {
return videoList;
}
@ -78,7 +78,7 @@ class VideoListAdapter extends BaseAdapter {
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = viewCreator.getViewFromVideoInfoItem(convertView, parent, videoList.get(position), context);
convertView = viewCreator.getViewFromVideoInfoItem(convertView, parent, videoList.get(position));
if(listView.isItemChecked(position)) {
convertView.setBackgroundColor(ContextCompat.getColor(context,R.color.light_youtube_primary_color));

View file

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

View file

@ -37,7 +37,7 @@ public class DashMpdParser {
}
}
public static List<VideoInfo.AudioStream> getAudioStreams(String dashManifestUrl,
public static List<StreamInfo.AudioStream> getAudioStreams(String dashManifestUrl,
Downloader downloader)
throws DashMpdParsingException {
String dashDoc;
@ -46,7 +46,7 @@ public class DashMpdParser {
} catch(IOException ioe) {
throw new DashMpdParsingException("Could not get dash mpd: " + dashManifestUrl, ioe);
}
Vector<VideoInfo.AudioStream> audioStreams = new Vector<>();
Vector<StreamInfo.AudioStream> audioStreams = new Vector<>();
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(new StringReader(dashDoc));
@ -83,7 +83,7 @@ public class DashMpdParser {
} else if(currentMimeType.equals(MediaFormat.M4A.mimeType)) {
format = MediaFormat.M4A.id;
}
audioStreams.add(new VideoInfo.AudioStream(parser.getText(),
audioStreams.add(new StreamInfo.AudioStream(parser.getText(),
format, currentBandwidth, currentSamplingRate));
}
break;

View file

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

View file

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

View file

@ -27,16 +27,16 @@ import java.util.Vector;
@SuppressWarnings("ALL")
public interface SearchEngine {
class Result {
public String errorMessage = "";
public String suggestion = "";
public final List<VideoPreviewInfo> resultList = new Vector<>();
public class NothingFoundException extends ExtractionException {
public NothingFoundException(String message) {
super(message);
}
}
ArrayList<String> suggestionList(String query,String contentCountry, Downloader dl)
throws ExtractionException, IOException;
//Result search(String query, int page);
Result search(String query, int page, String contentCountry, Downloader dl)
StreamPreviewInfoCollector search(String query, int page, String contentCountry, Downloader dl)
throws ExtractionException, IOException;
}

View file

@ -0,0 +1,47 @@
package org.schabi.newpipe.extractor;
import java.io.IOException;
import java.util.List;
import java.util.Vector;
/**
* Created by Christian Schabesberger on 29.02.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* SearchResult.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class SearchResult {
public static SearchResult getSearchResult(SearchEngine engine, String query,
int page, String languageCode, Downloader dl)
throws ExtractionException, IOException {
SearchResult result = engine.search(query, page, languageCode, dl).getSearchResult();
if(result.resultList.isEmpty()) {
if(result.suggestion.isEmpty()) {
throw new ExtractionException("Empty result despite no error");
} else {
// This is used as a fallback. Do not relay on it !!!
throw new SearchEngine.NothingFoundException(result.suggestion);
}
}
return result;
}
public String suggestion = "";
public final List<StreamPreviewInfo> resultList = new Vector<>();
public List<Exception> errors = new Vector<>();
}

View file

@ -3,7 +3,7 @@ package org.schabi.newpipe.extractor;
/**
* Created by Christian Schabesberger on 10.08.15.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* StreamExtractor.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
@ -29,7 +29,6 @@ import java.util.List;
public interface StreamExtractor {
public class ExctractorInitException extends ExtractionException {
public ExctractorInitException() {}
public ExctractorInitException(String message) {
super(message);
}
@ -42,13 +41,9 @@ public interface StreamExtractor {
}
public class ContentNotAvailableException extends ParsingException {
public ContentNotAvailableException() {}
public ContentNotAvailableException(String message) {
super(message);
}
public ContentNotAvailableException(Throwable cause) {
super(cause);
}
public ContentNotAvailableException(String message, Throwable cause) {
super(message, cause);
}
@ -63,16 +58,16 @@ public interface StreamExtractor {
public abstract String getUploadDate() throws ParsingException;
public abstract String getThumbnailUrl() throws ParsingException;
public abstract String getUploaderThumbnailUrl() throws ParsingException;
public abstract List<VideoInfo.AudioStream> getAudioStreams() throws ParsingException;
public abstract List<VideoInfo.VideoStream> getVideoStreams() throws ParsingException;
public abstract List<VideoInfo.VideoStream> getVideoOnlyStreams() throws ParsingException;
public abstract List<StreamInfo.AudioStream> getAudioStreams() throws ParsingException;
public abstract List<StreamInfo.VideoStream> getVideoStreams() throws ParsingException;
public abstract List<StreamInfo.VideoStream> getVideoOnlyStreams() throws ParsingException;
public abstract String getDashMpdUrl() throws ParsingException;
public abstract int getAgeLimit() throws ParsingException;
public abstract String getAverageRating() throws ParsingException;
public abstract int getLikeCount() throws ParsingException;
public abstract int getDislikeCount() throws ParsingException;
public abstract VideoPreviewInfo getNextVideo() throws ParsingException;
public abstract List<VideoPreviewInfo> getRelatedVideos() throws ParsingException;
public abstract VideoUrlIdHandler getUrlIdConverter();
public abstract StreamPreviewInfo getNextVideo() throws ParsingException;
public abstract List<StreamPreviewInfo> getRelatedVideos() throws ParsingException;
public abstract StreamUrlIdHandler getUrlIdConverter();
public abstract String getPageUrl();
}

View file

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

View file

@ -1,14 +1,10 @@
package org.schabi.newpipe.extractor;
import android.graphics.Bitmap;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Created by Christian Schabesberger on 26.08.15.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* VideoPreviewInfo.java is part of NewPipe.
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* StreamPreviewInfo.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -25,6 +21,6 @@ import android.os.Parcelable;
*/
/**Info object for previews of unopened videos, eg search results, related videos*/
public class VideoPreviewInfo extends AbstractVideoInfo {
public class StreamPreviewInfo extends AbstractVideoInfo {
public String duration = "";
}

View file

@ -0,0 +1,91 @@
package org.schabi.newpipe.extractor;
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamUrlIdHandler;
/**
* Created by Christian Schabesberger on 28.02.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* StreamPreviewInfoCollector.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class StreamPreviewInfoCollector {
SearchResult result = new SearchResult();
StreamUrlIdHandler urlIdHandler = null;
public StreamPreviewInfoCollector(StreamUrlIdHandler handler) {
urlIdHandler = handler;
}
public void setSuggestion(String suggestion) {
result.suggestion = suggestion;
}
public void addError(Exception e) {
result.errors.add(e);
}
public SearchResult getSearchResult() {
return result;
}
public void commit(StreamPreviewInfoExtractor extractor) throws ParsingException {
try {
StreamPreviewInfo resultItem = new StreamPreviewInfo();
// importand information
resultItem.webpage_url = extractor.getWebPageUrl();
if (urlIdHandler == null) {
throw new ParsingException("Error: UrlIdHandler not set");
} else {
resultItem.id = (new YoutubeStreamUrlIdHandler()).getVideoId(resultItem.webpage_url);
}
resultItem.title = extractor.getTitle();
// optional iformation
try {
resultItem.duration = extractor.getDuration();
} catch (Exception e) {
addError(e);
}
try {
resultItem.uploader = extractor.getUploader();
} catch (Exception e) {
addError(e);
}
try {
resultItem.upload_date = extractor.getUploadDate();
} catch (Exception e) {
addError(e);
}
try {
resultItem.view_count = extractor.getViewCount();
} catch (Exception e) {
addError(e);
}
try {
resultItem.thumbnail_url = extractor.getThumbnailUrl();
} catch (Exception e) {
addError(e);
}
result.resultList.add(resultItem);
} catch (Exception e) {
addError(e);
}
}
}

View file

@ -0,0 +1,31 @@
package org.schabi.newpipe.extractor;
/**
* Created by Christian Schabesberger on 28.02.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* StreamPreviewInfoExtractor.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public interface StreamPreviewInfoExtractor {
String getWebPageUrl() throws ParsingException;
String getTitle() throws ParsingException;
String getDuration() throws ParsingException;
String getUploader() throws ParsingException;
String getUploadDate() throws ParsingException;
long getViewCount() throws ParsingException;
String getThumbnailUrl() throws ParsingException;
}

View file

@ -4,7 +4,7 @@ package org.schabi.newpipe.extractor;
* Created by Christian Schabesberger on 02.02.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* VideoUrlIdHandler.java is part of NewPipe.
* StreamUrlIdHandler.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -20,7 +20,7 @@ package org.schabi.newpipe.extractor;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public interface VideoUrlIdHandler {
public interface StreamUrlIdHandler {
String getVideoUrl(String videoId);
String getVideoId(String siteUrl) throws ParsingException;
String cleanUrl(String siteUrl) throws ParsingException;

View file

@ -5,7 +5,7 @@ import java.io.IOException;
/**
* Created by Christian Schabesberger on 23.08.15.
*
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* StreamingService.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
@ -31,7 +31,7 @@ public interface StreamingService {
throws IOException, ExtractionException;
SearchEngine getSearchEngineInstance();
VideoUrlIdHandler getUrlIdHandler();
StreamUrlIdHandler getUrlIdHandler();
}

View file

@ -7,10 +7,13 @@ import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.SearchEngine;
import org.schabi.newpipe.extractor.VideoPreviewInfo;
import org.schabi.newpipe.extractor.StreamExtractor;
import org.schabi.newpipe.extractor.StreamPreviewInfoCollector;
import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
@ -49,9 +52,10 @@ public class YoutubeSearchEngine implements SearchEngine {
private static final String TAG = YoutubeSearchEngine.class.toString();
@Override
public Result search(String query, int page, String languageCode, Downloader downloader)
throws IOException, ParsingException {
Result result = new Result();
public StreamPreviewInfoCollector search(String query, int page, String languageCode, Downloader downloader)
throws IOException, ExtractionException {
StreamPreviewInfoCollector collector = new StreamPreviewInfoCollector(
new YoutubeStreamUrlIdHandler());
Uri.Builder builder = new Uri.Builder();
builder.scheme("https")
.authority("www.youtube.com")
@ -71,7 +75,6 @@ public class YoutubeSearchEngine implements SearchEngine {
site = downloader.download(url);
}
try {
Document doc = Jsoup.parse(site, url);
Element list = doc.select("ol[class=\"item-section\"]").first();
@ -92,62 +95,29 @@ public class YoutubeSearchEngine implements SearchEngine {
// both types of spell correction item
if (!((el = item.select("div[class*=\"spell-correction\"]").first()) == null)) {
result.suggestion = el.select("a").first().text();
collector.setSuggestion(el.select("a").first().text());
if(list.children().size() == 1) {
throw new NothingFoundException("Did you mean: " + el.select("a").first().text());
}
// search message item
} else if (!((el = item.select("div[class*=\"search-message\"]").first()) == null)) {
result.errorMessage = el.text();
//result.errorMessage = el.text();
throw new NothingFoundException(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);
collector.commit(extractPreviewInfo(el));
} else {
//noinspection ConstantConditions
Log.e(TAG, "unexpected element found:\"" + el + "\"");
collector.addError(new Exception("unexpected element found:\"" + el + "\""));
}
}
} catch(Exception e) {
throw new ParsingException(e);
}
return result;
return collector;
}
@Override
public ArrayList<String> suggestionList(String query,String contentCountry, Downloader dl)
public ArrayList<String> suggestionList(String query, String contentCountry, Downloader dl)
throws IOException, ParsingException {
ArrayList<String> suggestions = new ArrayList<>();
@ -167,8 +137,6 @@ public class YoutubeSearchEngine implements SearchEngine {
String response = dl.download(url);
try {
//TODO: Parse xml data using Jsoup not done
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder;
@ -180,10 +148,10 @@ public class YoutubeSearchEngine implements SearchEngine {
new ByteArrayInputStream(response.getBytes("utf-8"))));
doc.getDocumentElement().normalize();
} catch (ParserConfigurationException | SAXException | IOException e) {
e.printStackTrace();
throw new ParsingException("Could not parse document.");
}
if (doc != null) {
try {
NodeList nList = doc.getElementsByTagName("CompleteSuggestion");
for (int temp = 0; temp < nList.getLength(); temp++) {
@ -194,28 +162,30 @@ public class YoutubeSearchEngine implements SearchEngine {
suggestions.add(eElement.getAttribute("data"));
}
}
} else {
Log.e(TAG, "GREAT FUCKING ERROR");
}
return suggestions;
} catch(Exception e) {
throw new ParsingException(e);
throw new ParsingException("Could not get suggestions form document.", e);
}
}
private String getWebpageUrl(Element item) {
private StreamPreviewInfoExtractor extractPreviewInfo(final Element item) {
return new StreamPreviewInfoExtractor() {
@Override
public String getWebPageUrl() throws ParsingException {
Element el = item.select("div[class*=\"yt-lockup-video\"").first();
Element dl = el.select("h3").first().select("a").first();
return dl.attr("abs:href");
}
private String getTitle(Element item) {
@Override
public String getTitle() throws ParsingException {
Element el = item.select("div[class*=\"yt-lockup-video\"").first();
Element dl = el.select("h3").first().select("a").first();
return dl.text();
}
private String getDuration(Element item) {
@Override
public String getDuration() throws ParsingException {
try {
return item.select("span[class=\"video-time\"]").first().text();
} catch(Exception e) {
@ -224,19 +194,22 @@ public class YoutubeSearchEngine implements SearchEngine {
return "";
}
private String getUploader(Element item) {
@Override
public String getUploader() throws ParsingException {
return item.select("div[class=\"yt-lockup-byline\"]").first()
.select("a").first()
.text();
}
private String getUploadDate(Element item) {
@Override
public String getUploadDate() throws ParsingException {
return item.select("div[class=\"yt-lockup-meta\"]").first()
.select("li").first()
.text();
}
private long getViewCount(Element item) throws Parser.RegexException{
@Override
public long getViewCount() throws ParsingException {
String output;
String input = item.select("div[class=\"yt-lockup-meta\"]").first()
.select("li").get(1)
@ -246,13 +219,20 @@ public class YoutubeSearchEngine implements SearchEngine {
.replace(".", "")
.replace(",", "");
if(Long.parseLong(output) == 30) {
Log.d(TAG, "bla");
}
try {
return Long.parseLong(output);
} catch (NumberFormatException e) {
// if this happens the video probably has no views
if(!input.isEmpty()) {
return 0;
} else {
throw new ParsingException("Could not handle input: " + input, e);
}
}
}
private String getThumbnailUrl(Element item) {
@Override
public String getThumbnailUrl() throws ParsingException {
String url;
Element te = item.select("div[class=\"yt-thumb video-thumb\"]").first()
.select("img").first();
@ -266,4 +246,6 @@ public class YoutubeSearchEngine implements SearchEngine {
return url;
}
};
}
}

View file

@ -4,7 +4,7 @@ import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.StreamExtractor;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.VideoUrlIdHandler;
import org.schabi.newpipe.extractor.StreamUrlIdHandler;
import org.schabi.newpipe.extractor.SearchEngine;
import java.io.IOException;
@ -40,7 +40,7 @@ public class YoutubeService implements StreamingService {
@Override
public StreamExtractor getExtractorInstance(String url, Downloader downloader)
throws ExtractionException, IOException {
VideoUrlIdHandler urlIdHandler = new YoutubeVideoUrlIdHandler();
StreamUrlIdHandler urlIdHandler = new YoutubeStreamUrlIdHandler();
if(urlIdHandler.acceptUrl(url)) {
return new YoutubeStreamExtractor(url, downloader) ;
}
@ -54,7 +54,7 @@ public class YoutubeService implements StreamingService {
}
@Override
public VideoUrlIdHandler getUrlIdHandler() {
return new YoutubeVideoUrlIdHandler();
public StreamUrlIdHandler getUrlIdHandler() {
return new YoutubeStreamUrlIdHandler();
}
}

View file

@ -14,11 +14,11 @@ import org.schabi.newpipe.extractor.ExtractionException;
import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.VideoUrlIdHandler;
import org.schabi.newpipe.extractor.StreamInfo;
import org.schabi.newpipe.extractor.StreamPreviewInfo;
import org.schabi.newpipe.extractor.StreamUrlIdHandler;
import org.schabi.newpipe.extractor.StreamExtractor;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.VideoInfo;
import org.schabi.newpipe.extractor.VideoPreviewInfo;
import java.io.IOException;
import java.util.List;
@ -52,9 +52,6 @@ public class YoutubeStreamExtractor implements StreamExtractor {
// exceptions
public class DecryptException extends ParsingException {
DecryptException(Throwable cause) {
super(cause);
}
DecryptException(String message, Throwable cause) {
super(message, cause);
}
@ -69,8 +66,8 @@ public class YoutubeStreamExtractor implements StreamExtractor {
}
public class LiveStreamException extends ContentNotAvailableException {
LiveStreamException() {
super();
LiveStreamException(String message) {
super(message);
}
}
@ -179,7 +176,7 @@ public class YoutubeStreamExtractor implements StreamExtractor {
// cached values
private static volatile String decryptionCode = "";
VideoUrlIdHandler urlidhandler = new YoutubeVideoUrlIdHandler();
StreamUrlIdHandler urlidhandler = new YoutubeStreamUrlIdHandler();
String pageUrl = "";
private Downloader downloader;
@ -250,7 +247,7 @@ public class YoutubeStreamExtractor implements StreamExtractor {
throw new ParsingException("Could not parse yt player config", e);
}
if (isLiveStream) {
throw new LiveStreamException();
throw new LiveStreamException("This is a Life stream. Can't use those right now.");
}
return playerArgs;
@ -433,8 +430,8 @@ public class YoutubeStreamExtractor implements StreamExtractor {
@Override
public List<VideoInfo.AudioStream> getAudioStreams() throws ParsingException {
Vector<VideoInfo.AudioStream> audioStreams = new Vector<>();
public List<StreamInfo.AudioStream> getAudioStreams() throws ParsingException {
Vector<StreamInfo.AudioStream> audioStreams = new Vector<>();
try{
String encoded_url_map;
// playerArgs could be null if the video is age restricted
@ -461,7 +458,7 @@ public class YoutubeStreamExtractor implements StreamExtractor {
+ decryptSignature(tags.get("s"), decryptionCode);
}
audioStreams.add(new VideoInfo.AudioStream(streamUrl,
audioStreams.add(new StreamInfo.AudioStream(streamUrl,
itagItem.mediaFormatId,
itagItem.bandWidth,
itagItem.samplingRate));
@ -475,8 +472,8 @@ public class YoutubeStreamExtractor implements StreamExtractor {
}
@Override
public List<VideoInfo.VideoStream> getVideoStreams() throws ParsingException {
Vector<VideoInfo.VideoStream> videoStreams = new Vector<>();
public List<StreamInfo.VideoStream> getVideoStreams() throws ParsingException {
Vector<StreamInfo.VideoStream> videoStreams = new Vector<>();
try{
String encoded_url_map;
@ -504,7 +501,7 @@ public class YoutubeStreamExtractor implements StreamExtractor {
streamUrl = streamUrl + "&signature="
+ decryptSignature(tags.get("s"), decryptionCode);
}
videoStreams.add(new VideoInfo.VideoStream(
videoStreams.add(new StreamInfo.VideoStream(
streamUrl,
itagItem.mediaFormatId,
itagItem.resolutionString));
@ -527,7 +524,7 @@ public class YoutubeStreamExtractor implements StreamExtractor {
}
@Override
public List<VideoInfo.VideoStream> getVideoOnlyStreams() throws ParsingException {
public List<StreamInfo.VideoStream> getVideoOnlyStreams() throws ParsingException {
return null;
}
@ -638,7 +635,7 @@ public class YoutubeStreamExtractor implements StreamExtractor {
}
@Override
public VideoPreviewInfo getNextVideo() throws ParsingException {
public StreamPreviewInfo getNextVideo() throws ParsingException {
try {
return extractVideoPreviewInfo(doc.select("div[class=\"watch-sidebar-section\"]").first()
.select("li").first());
@ -648,9 +645,9 @@ public class YoutubeStreamExtractor implements StreamExtractor {
}
@Override
public Vector<VideoPreviewInfo> getRelatedVideos() throws ParsingException {
public Vector<StreamPreviewInfo> getRelatedVideos() throws ParsingException {
try {
Vector<VideoPreviewInfo> relatedVideos = new Vector<>();
Vector<StreamPreviewInfo> relatedVideos = new Vector<>();
for (Element li : doc.select("ul[id=\"watch-related\"]").first().children()) {
// first check if we have a playlist. If so leave them out
if (li.select("a[class*=\"content-link\"]").first() != null) {
@ -664,8 +661,8 @@ public class YoutubeStreamExtractor implements StreamExtractor {
}
@Override
public VideoUrlIdHandler getUrlIdConverter() {
return new YoutubeVideoUrlIdHandler();
public StreamUrlIdHandler getUrlIdConverter() {
return new YoutubeStreamUrlIdHandler();
}
@Override
@ -674,10 +671,10 @@ public class YoutubeStreamExtractor implements StreamExtractor {
}
/**Provides information about links to other videos on the video page, such as related videos.
* This is encapsulated in a VideoPreviewInfo object,
* which is a subset of the fields in a full VideoInfo.*/
private VideoPreviewInfo extractVideoPreviewInfo(Element li) throws ParsingException {
VideoPreviewInfo info = new VideoPreviewInfo();
* This is encapsulated in a StreamPreviewInfo object,
* which is a subset of the fields in a full StreamInfo.*/
private StreamPreviewInfo extractVideoPreviewInfo(Element li) throws ParsingException {
StreamPreviewInfo info = new StreamPreviewInfo();
try {
info.webpage_url = li.select("a.content-link").first()
@ -718,7 +715,7 @@ public class YoutubeStreamExtractor implements StreamExtractor {
info.thumbnail_url = "https:" + info.thumbnail_url;
}
} catch (Exception e) {
throw new ParsingException(e);
throw new ParsingException("Could not get video preview info", e);
}
return info;
}
@ -772,7 +769,7 @@ public class YoutubeStreamExtractor implements StreamExtractor {
Function decryptionFunc = (Function) scope.get("decrypt", scope);
result = decryptionFunc.call(context, scope, scope, new Object[]{encryptedSig});
} catch (Exception e) {
throw new DecryptException(e);
throw new DecryptException("could not get decrypt signature", e);
} finally {
Context.exit();
}

View file

@ -2,7 +2,7 @@ package org.schabi.newpipe.extractor.services.youtube;
import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.VideoUrlIdHandler;
import org.schabi.newpipe.extractor.StreamUrlIdHandler;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
@ -11,7 +11,7 @@ import java.net.URLDecoder;
* Created by Christian Schabesberger on 02.02.16.
*
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
* YoutubeVideoUrlIdHandler.java is part of NewPipe.
* YoutubeStreamUrlIdHandler.java is part of NewPipe.
*
* NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -27,7 +27,7 @@ import java.net.URLDecoder;
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
*/
public class YoutubeVideoUrlIdHandler implements VideoUrlIdHandler {
public class YoutubeStreamUrlIdHandler implements StreamUrlIdHandler {
@SuppressWarnings("WeakerAccess")
@Override
public String getVideoUrl(String videoId) {

View file

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

View file

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

View file

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

View file

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

View file

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