diff --git a/app/build.gradle b/app/build.gradle
index 37ec0e38e..3d122e516 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -31,6 +31,7 @@ android {
}
dependencies {
+ testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.1.1'
compile 'com.android.support:support-v4:24.1.1'
compile 'com.android.support:design:24.1.1'
diff --git a/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeStreamExtractorDefaultTest.java b/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeStreamExtractorDefaultTest.java
index 9428a3605..cc163dbb9 100644
--- a/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeStreamExtractorDefaultTest.java
+++ b/app/src/androidTest/java/org/schabi/newpipe/extractor/youtube/YoutubeStreamExtractorDefaultTest.java
@@ -79,6 +79,10 @@ public class YoutubeStreamExtractorDefaultTest extends AndroidTestCase {
assertTrue(extractor.getUploadDate().length() > 0);
}
+ public void testGetChannelUrl() throws ParsingException {
+ assertTrue(extractor.getChannelUrl().length() > 0);
+ }
+
public void testGetThumbnailUrl() throws ParsingException {
assertTrue(extractor.getThumbnailUrl(),
extractor.getThumbnailUrl().contains(HTTPS));
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3692b6328..bffefd177 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -16,10 +16,10 @@
android:logo="@mipmap/ic_launcher"
android:theme="@style/AppTheme"
tools:ignore="AllowBackup">
+
+ android:name=".MainActivity"
+ android:label="@string/app_name">
@@ -27,12 +27,12 @@
+ android:value=".MainActivity" />
@@ -80,12 +80,14 @@
android:name=".player.PlayVideoActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:theme="@style/VideoPlayerTheme"
- tools:ignore="UnusedAttribute"/>
+ tools:ignore="UnusedAttribute" />
+
-
+
+
+
+ android:exported="false"
+ android:label="@string/background_player_name" />
+
-
+
-
+ android:launchMode="singleTask"
+ android:theme="@style/AppTheme" />
-
+
+
+
-
+
+
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java b/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java
index ea76b46f2..a097e087c 100644
--- a/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java
+++ b/app/src/main/java/org/schabi/newpipe/ActivityCommunicator.java
@@ -43,7 +43,7 @@ public class ActivityCommunicator {
public volatile Bitmap backgroundPlayerThumbnail;
// Sent from any activity to ErrorActivity.
- public volatile List errorList;
+ public volatile List errorList;
public volatile Class returnActivity;
public volatile ErrorActivity.ErrorInfo errorInfo;
}
diff --git a/app/src/main/java/org/schabi/newpipe/App.java b/app/src/main/java/org/schabi/newpipe/App.java
index c56893fc8..f0604f16f 100644
--- a/app/src/main/java/org/schabi/newpipe/App.java
+++ b/app/src/main/java/org/schabi/newpipe/App.java
@@ -2,12 +2,12 @@ package org.schabi.newpipe;
import android.app.Application;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
+import org.schabi.newpipe.settings.SettingsActivity;
+
import info.guardianproject.netcipher.NetCipher;
import info.guardianproject.netcipher.proxy.OrbotHelper;
diff --git a/app/src/main/java/org/schabi/newpipe/ChannelActivity.java b/app/src/main/java/org/schabi/newpipe/ChannelActivity.java
new file mode 100644
index 000000000..a58182a5e
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/ChannelActivity.java
@@ -0,0 +1,248 @@
+package org.schabi.newpipe;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.design.widget.CollapsingToolbarLayout;
+import android.support.design.widget.FloatingActionButton;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.Toolbar;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.Toast;
+
+import com.nostra13.universalimageloader.core.ImageLoader;
+
+import org.schabi.newpipe.detail.VideoItemDetailActivity;
+import org.schabi.newpipe.detail.VideoItemDetailFragment;
+import org.schabi.newpipe.extractor.ChannelExtractor;
+import org.schabi.newpipe.extractor.ChannelInfo;
+import org.schabi.newpipe.extractor.ExtractionException;
+import org.schabi.newpipe.extractor.ParsingException;
+import org.schabi.newpipe.extractor.ServiceList;
+import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.info_list.InfoListAdapter;
+
+import java.io.IOException;
+
+public class ChannelActivity extends AppCompatActivity {
+
+
+ private static final String TAG = ChannelActivity.class.toString();
+ private View rootView = null;
+
+ // intent const
+ public static final String CHANNEL_URL = "channel_url";
+ public static final String SERVICE_ID = "service_id";
+
+ private int serviceId = -1;
+ private String channelUrl = "";
+ private int pageNumber = 0;
+ private boolean hasNextPage = true;
+ private boolean isLoading = false;
+
+ private ImageLoader imageLoader = ImageLoader.getInstance();
+ private InfoListAdapter infoListAdapter = null;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_channel);
+ Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
+ rootView = findViewById(R.id.rootView);
+ setSupportActionBar(toolbar);
+ Intent i = getIntent();
+ channelUrl = i.getStringExtra(CHANNEL_URL);
+ serviceId = i.getIntExtra(SERVICE_ID, -1);
+
+ infoListAdapter = new InfoListAdapter(this, rootView);
+ RecyclerView recyclerView = (RecyclerView) findViewById(R.id.channel_streams_view);
+ final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
+ recyclerView.setLayoutManager(layoutManager);
+ recyclerView.setAdapter(infoListAdapter);
+ infoListAdapter.setOnItemSelectedListener(new InfoListAdapter.OnItemSelectedListener() {
+ @Override
+ public void selected(String url) {
+ Intent detailIntent = new Intent(ChannelActivity.this, VideoItemDetailActivity.class);
+ detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, url);
+ detailIntent.putExtra(
+ VideoItemDetailFragment.STREAMING_SERVICE, serviceId);
+ startActivity(detailIntent);
+ }
+ });
+
+ // detect if list has ben scrolled to the bottom
+ recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ int pastVisiblesItems, visibleItemCount, totalItemCount;
+ super.onScrolled(recyclerView, dx, dy);
+ if(dy > 0) //check for scroll down
+ {
+ visibleItemCount = layoutManager.getChildCount();
+ totalItemCount = layoutManager.getItemCount();
+ pastVisiblesItems = layoutManager.findFirstVisibleItemPosition();
+
+ if ( (visibleItemCount + pastVisiblesItems) >= totalItemCount
+ && !isLoading
+ && hasNextPage)
+ {
+ pageNumber++;
+ requestData(true);
+ }
+ }
+ }
+ });
+
+ requestData(false);
+ }
+
+
+
+ private void updateUi(final ChannelInfo info) {
+ CollapsingToolbarLayout ctl = (CollapsingToolbarLayout) findViewById(R.id.channel_toolbar_layout);
+ ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
+ ImageView channelBanner = (ImageView) findViewById(R.id.channel_banner_image);
+ FloatingActionButton feedButton = (FloatingActionButton) findViewById(R.id.channel_rss_fab);
+ ImageView avatarView = (ImageView) findViewById(R.id.channel_avatar_view);
+ ImageView haloView = (ImageView) findViewById(R.id.channel_avatar_halo);
+
+ progressBar.setVisibility(View.GONE);
+
+ if(info.channel_name != null && !info.channel_name.isEmpty()) {
+ ctl.setTitle(info.channel_name);
+ }
+
+ if(info.banner_url != null && !info.banner_url.isEmpty()) {
+ imageLoader.displayImage(info.banner_url, channelBanner,
+ new ImageErrorLoadingListener(this, rootView ,info.service_id));
+ }
+
+ if(info.avatar_url != null && !info.avatar_url.isEmpty()) {
+ avatarView.setVisibility(View.VISIBLE);
+ haloView.setVisibility(View.VISIBLE);
+ imageLoader.displayImage(info.avatar_url, avatarView,
+ new ImageErrorLoadingListener(this, rootView ,info.service_id));
+ }
+
+ if(info.feed_url != null && !info.feed_url.isEmpty()) {
+ feedButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Log.d(TAG, info.feed_url);
+ Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(info.feed_url));
+ startActivity(i);
+ }
+ });
+ } else {
+ feedButton.setVisibility(View.GONE);
+ }
+ }
+
+ private void addVideos(final ChannelInfo info) {
+ infoListAdapter.addStreamItemList(info.related_streams);
+ }
+
+ private void postNewErrorToast(Handler h, final int stringResource) {
+ h.post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(ChannelActivity.this,
+ stringResource, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ private void requestData(final boolean onlyVideos) {
+ // start processing
+ isLoading = true;
+ Thread channelExtractorThread = new Thread(new Runnable() {
+ Handler h = new Handler();
+
+ @Override
+ public void run() {
+ StreamingService service = null;
+ try {
+ service = ServiceList.getService(serviceId);
+ ChannelExtractor extractor = service.getChannelExtractorInstance(
+ channelUrl, pageNumber, new Downloader());
+
+ final ChannelInfo info = ChannelInfo.getInfo(extractor, new Downloader());
+
+
+ h.post(new Runnable() {
+ @Override
+ public void run() {
+ isLoading = false;
+ if(!onlyVideos) {
+ updateUi(info);
+ }
+ hasNextPage = info.hasNextPage;
+ addVideos(info);
+ }
+ });
+
+ // look for non critical errors during extraction
+ if(info != null &&
+ !info.errors.isEmpty()) {
+ Log.e(TAG, "OCCURRED ERRORS DURING EXTRACTION:");
+ for (Throwable e : info.errors) {
+ e.printStackTrace();
+ Log.e(TAG, "------");
+ }
+
+ View rootView = findViewById(android.R.id.content);
+ ErrorActivity.reportError(h, ChannelActivity.this,
+ info.errors, null, rootView,
+ ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
+ service.getServiceInfo().name, channelUrl, 0 /* no message for the user */));
+ }
+ } catch(IOException ioe) {
+ postNewErrorToast(h, R.string.network_error);
+ ioe.printStackTrace();
+ } catch(ParsingException pe) {
+ ErrorActivity.reportError(h, ChannelActivity.this, pe, VideoItemDetailFragment.class, null,
+ ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
+ service.getServiceInfo().name, channelUrl, R.string.parsing_error));
+ h.post(new Runnable() {
+ @Override
+ public void run() {
+ ChannelActivity.this.finish();
+ }
+ });
+ pe.printStackTrace();
+ } catch(ExtractionException ex) {
+ ErrorActivity.reportError(h, ChannelActivity.this, ex, VideoItemDetailFragment.class, null,
+ ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
+ service.getServiceInfo().name, channelUrl, R.string.parsing_error));
+ h.post(new Runnable() {
+ @Override
+ public void run() {
+ ChannelActivity.this.finish();
+ }
+ });
+ ex.printStackTrace();
+ } catch(Exception e) {
+ ErrorActivity.reportError(h, ChannelActivity.this, e, VideoItemDetailFragment.class, null,
+ ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
+ service.getServiceInfo().name, channelUrl, R.string.general_error));
+ h.post(new Runnable() {
+ @Override
+ public void run() {
+ ChannelActivity.this.finish();
+ }
+ });
+ e.printStackTrace();
+ }
+ }
+ });
+
+ channelExtractorThread.start();
+ }
+
+}
diff --git a/app/src/main/java/org/schabi/newpipe/Downloader.java b/app/src/main/java/org/schabi/newpipe/Downloader.java
index 833891762..04098a4d7 100644
--- a/app/src/main/java/org/schabi/newpipe/Downloader.java
+++ b/app/src/main/java/org/schabi/newpipe/Downloader.java
@@ -5,10 +5,12 @@ import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.UnknownHostException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
import javax.net.ssl.HttpsURLConnection;
-import info.guardianproject.netcipher.NetCipher;
/**
* Created by Christian Schabesberger on 28.01.16.
@@ -40,10 +42,26 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
* @param language the language (usually a 2-character code) to set as the preferred language
* @return the contents of the specified text file*/
public String download(String siteUrl, String language) throws IOException {
+ Map requestProperties = new HashMap<>();
+ requestProperties.put("Accept-Language", language);
+ return download(siteUrl, requestProperties);
+ }
+
+
+ /**Download the text file at the supplied URL as in download(String),
+ * but set the HTTP header field "Accept-Language" to the supplied string.
+ * @param siteUrl the URL of the text file to return the contents of
+ * @param customProperties set request header properties
+ * @return the contents of the specified text file
+ * @throws IOException*/
+ public String download(String siteUrl, Map customProperties) throws IOException {
URL url = new URL(siteUrl);
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
- //HttpsURLConnection con = NetCipher.getHttpsURLConnection(url);
- con.setRequestProperty("Accept-Language", language);
+ Iterator it = customProperties.entrySet().iterator();
+ while(it.hasNext()) {
+ Map.Entry pair = (Map.Entry)it.next();
+ con.setRequestProperty((String)pair.getKey(), (String)pair.getValue());
+ }
return dl(con);
}
diff --git a/app/src/main/java/org/schabi/newpipe/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/ErrorActivity.java
index ec43730dd..a992f0fd1 100644
--- a/app/src/main/java/org/schabi/newpipe/ErrorActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/ErrorActivity.java
@@ -79,16 +79,19 @@ public class ErrorActivity extends AppCompatActivity {
public static final int GET_SUGGESTIONS = 2;
public static final int SOMETHING_ELSE = 3;
public static final int USER_REPORT = 4;
+ public static final int LOAD_IMAGE = 5;
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 SOMETHING_ELSE_STRING = "something";
public static final String USER_REPORT_STRING = "user report";
+ public static final String LOAD_IMAGE_STRING = "load image";
+
public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org";
public static final String ERROR_EMAIL_SUBJECT = "Exception in NewPipe " + BuildConfig.VERSION_NAME;
- private List errorList;
+ private List errorList;
private ErrorInfo errorInfo;
private Class returnActivity;
private String currentTimeStamp;
@@ -102,7 +105,7 @@ public class ErrorActivity extends AppCompatActivity {
private TextView infoView;
private TextView errorMessageView;
- public static void reportError(final Context context, final List el,
+ public static void reportError(final Context context, final List el,
final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) {
if (rootView != null) {
@@ -129,9 +132,9 @@ public class ErrorActivity extends AppCompatActivity {
}
}
- public static void reportError(final Context context, final Exception e,
+ public static void reportError(final Context context, final Throwable e,
final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) {
- List el = null;
+ List el = null;
if(e != null) {
el = new Vector<>();
el.add(e);
@@ -140,10 +143,10 @@ public class ErrorActivity extends AppCompatActivity {
}
// async call
- public static void reportError(Handler handler, final Context context, final Exception e,
+ public static void reportError(Handler handler, final Context context, final Throwable e,
final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) {
- List el = null;
+ List el = null;
if(e != null) {
el = new Vector<>();
el.add(e);
@@ -152,7 +155,7 @@ public class ErrorActivity extends AppCompatActivity {
}
// async call
- public static void reportError(Handler handler, final Context context, final List el,
+ public static void reportError(Handler handler, final Context context, final List el,
final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) {
handler.post(new Runnable() {
@Override
@@ -171,7 +174,7 @@ public class ErrorActivity extends AppCompatActivity {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setTitle(R.string.error_report_title);
actionBar.setDisplayShowTitleEnabled(true);
- } catch (Exception e) {
+ } catch (Throwable e) {
Log.e(TAG, "Error turing exception handling");
e.printStackTrace();
}
@@ -252,10 +255,10 @@ public class ErrorActivity extends AppCompatActivity {
return sw.getBuffer().toString();
}
- private String formErrorText(List el) {
+ private String formErrorText(List el) {
String text = "";
if(el != null) {
- for (Exception e : el) {
+ for (Throwable e : el) {
text += "-------------------------------------\n"
+ getStackTrace(e);
}
@@ -273,7 +276,7 @@ public class ErrorActivity extends AppCompatActivity {
returnActivity.isAssignableFrom(Activity.class)) {
intent = new Intent(this, returnActivity);
} else {
- intent = new Intent(this, VideoItemListActivity.class);
+ intent = new Intent(this, MainActivity.class);
}
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent);
@@ -313,7 +316,7 @@ public class ErrorActivity extends AppCompatActivity {
JSONArray exceptionArray = new JSONArray();
if(errorList != null) {
- for (Exception e : errorList) {
+ for (Throwable e : errorList) {
exceptionArray.put(getStackTrace(e));
}
}
@@ -322,7 +325,7 @@ public class ErrorActivity extends AppCompatActivity {
errorObject.put("user_comment", userCommentBox.getText().toString());
return errorObject.toString(3);
- } catch (Exception e) {
+ } catch (Throwable e) {
Log.e(TAG, "Error while erroring: Could not build json");
e.printStackTrace();
}
@@ -342,6 +345,8 @@ public class ErrorActivity extends AppCompatActivity {
return SOMETHING_ELSE_STRING;
case USER_REPORT:
return USER_REPORT_STRING;
+ case LOAD_IMAGE:
+ return LOAD_IMAGE_STRING;
default:
return "Your description is in another castle.";
}
@@ -390,7 +395,7 @@ public class ErrorActivity extends AppCompatActivity {
ipRange = Parser.matchGroup1("([0-9]*\\.[0-9]*\\.)[0-9]*\\.[0-9]*", ip)
+ "0.0";
- } catch(Exception e) {
+ } catch(Throwable e) {
Log.d(TAG, "Error while error: could not get iprange");
e.printStackTrace();
} finally {
diff --git a/app/src/main/java/org/schabi/newpipe/ImageErrorLoadingListener.java b/app/src/main/java/org/schabi/newpipe/ImageErrorLoadingListener.java
new file mode 100644
index 000000000..0447ed647
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/ImageErrorLoadingListener.java
@@ -0,0 +1,65 @@
+package org.schabi.newpipe;
+
+import android.app.Activity;
+import android.graphics.Bitmap;
+import android.view.View;
+
+import com.nostra13.universalimageloader.core.assist.FailReason;
+import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
+
+import org.schabi.newpipe.ErrorActivity;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.ServiceList;
+
+/**
+ * Created by Christian Schabesberger on 01.08.16.
+ *
+ * Copyright (C) Christian Schabesberger 2015
+ * StreamInfoItemViewCreator.java is part of NewPipe.
+ *
+ * NewPipe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * NewPipe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with NewPipe. If not, see .
+ */
+
+public class ImageErrorLoadingListener implements ImageLoadingListener {
+
+ private int serviceId = -1;
+ private Activity activity = null;
+ private View rootView = null;
+
+ public ImageErrorLoadingListener(Activity activity, View rootView, int serviceId) {
+ this.activity = activity;
+ this.serviceId= serviceId;
+ this.rootView = rootView;
+ }
+
+ @Override
+ public void onLoadingStarted(String imageUri, View view) {}
+
+ @Override
+ public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
+ ErrorActivity.reportError(activity,
+ failReason.getCause(), null, rootView,
+ ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
+ ServiceList.getNameOfService(serviceId), imageUri,
+ R.string.could_not_load_image));
+ }
+
+ @Override
+ public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
+
+ }
+
+ @Override
+ public void onLoadingCancelled(String imageUri, View view) {}
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/MainActivity.java b/app/src/main/java/org/schabi/newpipe/MainActivity.java
new file mode 100644
index 000000000..e8f7189a4
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/MainActivity.java
@@ -0,0 +1,69 @@
+package org.schabi.newpipe;
+
+import android.content.Intent;
+import android.media.AudioManager;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.NavUtils;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import org.schabi.newpipe.settings.SettingsActivity;
+
+/**
+ * Created by Christian Schabesberger on 02.08.16.
+ */
+
+public class MainActivity extends AppCompatActivity {
+
+ private Fragment mainFragment = null;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+
+ setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ mainFragment = getSupportFragmentManager()
+ .findFragmentById(R.id.search_fragment);
+ }
+
+ public boolean onCreateOptionsMenu(Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ MenuInflater inflater = getMenuInflater();
+
+ inflater.inflate(R.menu.main_menu, menu);
+
+ mainFragment.onCreateOptionsMenu(menu, inflater);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int id = item.getItemId();
+
+ switch(id) {
+ case android.R.id.home: {
+ Intent intent = new Intent(this, MainActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ NavUtils.navigateUpTo(this, intent);
+ return true;
+ }
+ case R.id.action_settings: {
+ Intent intent = new Intent(this, SettingsActivity.class);
+ startActivity(intent);
+ return true;
+ }
+ case R.id.action_show_downloads: {
+ Intent intent = new Intent(this, org.schabi.newpipe.download.MainActivity.class);
+ startActivity(intent);
+ return true;
+ }
+ default:
+ return mainFragment.onOptionsItemSelected(item) ||
+ super.onOptionsItemSelected(item);
+ }
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/VideoItemListActivity.java b/app/src/main/java/org/schabi/newpipe/VideoItemListActivity.java
deleted file mode 100644
index 92b44f807..000000000
--- a/app/src/main/java/org/schabi/newpipe/VideoItemListActivity.java
+++ /dev/null
@@ -1,411 +0,0 @@
-package org.schabi.newpipe;
-
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.media.AudioManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.preference.PreferenceManager;
-import android.support.v4.app.NavUtils;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.SearchView;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.Toast;
-
-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 java.io.IOException;
-import java.util.List;
-import java.util.Vector;
-
-/**
- * Copyright (C) Christian Schabesberger 2015
- * VideoItemListActivity.java is part of NewPipe.
- *
- * NewPipe is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * NewPipe is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with NewPipe. If not, see .
- */
-
-public class VideoItemListActivity extends AppCompatActivity
- implements VideoItemListFragment.Callbacks {
-
- private static final String TAG = VideoItemListFragment.class.toString();
-
- // arguments to give to this activity
- public static final String VIDEO_INFO_ITEMS = "video_info_items";
-
- // savedInstanceBundle arguments
- private static final String QUERY = "query";
- private static final String STREAMING_SERVICE = "streaming_service";
-
- // activity modes
- private static final int SEARCH_MODE = 0;
- private static final int PRESENT_VIDEOS_MODE = 1;
-
- private int mode;
- private int currentStreamingServiceId = -1;
- private String searchQuery = "";
-
- private VideoItemListFragment listFragment;
- private VideoItemDetailFragment videoFragment;
- private Menu menu;
-
- private SuggestionListAdapter suggestionListAdapter;
- private SuggestionSearchRunnable suggestionSearchRunnable;
- private Thread searchThread;
-
- private class SearchVideoQueryListener implements SearchView.OnQueryTextListener {
-
- @Override
- public boolean onQueryTextSubmit(String query) {
- try {
- searchQuery = query;
- listFragment.search(query);
-
- // hide virtual keyboard
- InputMethodManager inputManager =
- (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
- try {
- //noinspection ConstantConditions
- inputManager.hideSoftInputFromWindow(
- getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
- } catch(NullPointerException e) {
- Log.e(TAG, "Could not get widget with focus");
- Toast.makeText(VideoItemListActivity.this, "Could not get widget with focus",
- Toast.LENGTH_SHORT).show();
- e.printStackTrace();
- }
- // clear focus
- // 1. to not open up the keyboard after switching back to this
- // 2. It's a workaround to a seeming bug by the Android OS it self, causing
- // onQueryTextSubmit to trigger twice when focus is not cleared.
- // See: http://stackoverflow.com/questions/17874951/searchview-onquerytextsubmit-runs-twice-while-i-pressed-once
- getCurrentFocus().clearFocus();
- } catch(Exception e) {
- e.printStackTrace();
- }
- View bg = findViewById(R.id.mainBG);
- bg.setVisibility(View.GONE);
- return true;
- }
-
- @Override
- public boolean onQueryTextChange(String newText) {
- if(!newText.isEmpty()) {
- searchSuggestions(newText);
- }
- return true;
- }
-
- }
- private class SearchSuggestionListener implements SearchView.OnSuggestionListener{
-
- private SearchView searchView;
-
- private SearchSuggestionListener(SearchView searchView) {
- this.searchView = searchView;
- }
-
- @Override
- public boolean onSuggestionSelect(int position) {
- String suggestion = suggestionListAdapter.getSuggestion(position);
- searchView.setQuery(suggestion,true);
- return false;
- }
-
- @Override
- public boolean onSuggestionClick(int position) {
- String suggestion = suggestionListAdapter.getSuggestion(position);
- searchView.setQuery(suggestion,true);
- return false;
- }
- }
-
- private class SuggestionResultRunnable implements Runnable{
-
- private List suggestions;
-
- private SuggestionResultRunnable(List suggestions) {
- this.suggestions = suggestions;
- }
-
- @Override
- public void run() {
- suggestionListAdapter.updateAdapter(suggestions);
- }
- }
-
- private class SuggestionSearchRunnable implements Runnable{
- private final int serviceId;
- private final String query;
- final Handler h = new Handler();
- private Context context;
- private SuggestionSearchRunnable(int serviceId, String query) {
- this.serviceId = serviceId;
- this.query = query;
- context = VideoItemListActivity.this;
- }
-
- @Override
- public void run() {
- try {
- SearchEngine engine =
- ServiceList.getService(serviceId).getSearchEngineInstance(new Downloader());
- SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
- String searchLanguageKey = context.getString(R.string.search_language_key);
- String searchLanguage = sp.getString(searchLanguageKey,
- getString(R.string.default_language_value));
- List suggestions = engine.suggestionList(query,searchLanguage,new Downloader());
- h.post(new SuggestionResultRunnable(suggestions));
- } catch (ExtractionException e) {
- ErrorActivity.reportError(h, VideoItemListActivity.this, e, null, findViewById(R.id.videoitem_list),
- ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
- ServiceList.getNameOfService(serviceId), 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,
- ServiceList.getNameOfService(serviceId), query, R.string.general_error));
- }
- }
- }
- /**
- * Whether or not the activity is in two-pane mode, i.e. running on a tablet
- * device.
- */
- private boolean mTwoPane;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- setTheme(R.style.AppTheme);
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_videoitem_list);
- StreamingService streamingService = null;
-
- View bg = findViewById(R.id.mainBG);
- bg.setVisibility(View.VISIBLE);
-
- try {
- //------ todo: remove this line when multiservice support is implemented ------
- currentStreamingServiceId = ServiceList.getIdOfService("Youtube");
- streamingService = ServiceList.getService(currentStreamingServiceId);
- } catch (Exception e) {
- e.printStackTrace();
- ErrorActivity.reportError(VideoItemListActivity.this, e, null, findViewById(R.id.videoitem_list),
- ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
- ServiceList.getNameOfService(currentStreamingServiceId), "", R.string.general_error));
- }
- setVolumeControlStream(AudioManager.STREAM_MUSIC);
- listFragment = (VideoItemListFragment) getSupportFragmentManager()
- .findFragmentById(R.id.videoitem_list);
- listFragment.setStreamingService(streamingService);
-
- if(savedInstanceState != null
- && mode != PRESENT_VIDEOS_MODE) {
- searchQuery = savedInstanceState.getString(QUERY);
- currentStreamingServiceId = savedInstanceState.getInt(STREAMING_SERVICE);
- if(!searchQuery.isEmpty()) {
- listFragment.search(searchQuery);
- }
- }
-
- if (findViewById(R.id.videoitem_detail_container) != null) {
- // The detail container view will be present only in the
- // large-screen layouts (res/values-large and
- // res/values-sw600dp). If this view is present, then the
- // activity should be in two-pane mode.
- mTwoPane = true;
-
- // In two-pane mode, list items should be given the
- // 'activated' state when touched.
-
- ((VideoItemListFragment) getSupportFragmentManager()
- .findFragmentById(R.id.videoitem_list))
- .setActivateOnItemClick(true);
-
- SearchView searchView = (SearchView)findViewById(R.id.searchViewTablet);
- if(mode != PRESENT_VIDEOS_MODE) {
- // Somehow the seticonifiedbydefault property set by the layout xml is not working on
- // the support version on SearchView, so it needs to be set programmatically.
- searchView.setIconifiedByDefault(false);
- searchView.setIconified(false);
- if(!searchQuery.isEmpty()) {
- searchView.setQuery(searchQuery,false);
- }
- searchView.setOnQueryTextListener(new SearchVideoQueryListener());
- suggestionListAdapter = new SuggestionListAdapter(this);
- searchView.setSuggestionsAdapter(suggestionListAdapter);
- searchView.setOnSuggestionListener(new SearchSuggestionListener(searchView));
- } else {
- searchView.setVisibility(View.GONE);
- }
- }
-
- PreferenceManager.setDefaultValues(this, R.xml.settings, false);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- App.checkStartTor(this);
- }
-
- /**
- * Callback method from {@link VideoItemListFragment.Callbacks}
- * indicating that the item with the given ID was selected.
- */
- @Override
- public void onItemSelected(String id) {
- VideoListAdapter listAdapter = (VideoListAdapter) ((VideoItemListFragment)
- getSupportFragmentManager()
- .findFragmentById(R.id.videoitem_list))
- .getListAdapter();
- String webpageUrl = listAdapter.getVideoList().get((int) Long.parseLong(id)).webpage_url;
- if (mTwoPane) {
- // In two-pane mode, show the detail view in this activity by
- // adding or replacing the detail fragment using a
- // fragment transaction.
- Bundle arguments = new Bundle();
- //arguments.putString(VideoItemDetailFragment.ARG_ITEM_ID, id);
- arguments.putString(VideoItemDetailFragment.VIDEO_URL, webpageUrl);
- arguments.putInt(VideoItemDetailFragment.STREAMING_SERVICE, currentStreamingServiceId);
- videoFragment = new VideoItemDetailFragment();
- videoFragment.setArguments(arguments);
- videoFragment.setOnInvokeCreateOptionsMenuListener(new VideoItemDetailFragment.OnInvokeCreateOptionsMenuListener() {
- @Override
- public void createOptionsMenu() {
- menu.clear();
- onCreateOptionsMenu(menu);
- }
- });
- getSupportFragmentManager().beginTransaction()
- .replace(R.id.videoitem_detail_container, videoFragment)
- .commit();
- } else {
- // In single-pane mode, simply start the detail activity
- // for the selected item ID.
- Intent detailIntent = new Intent(this, VideoItemDetailActivity.class);
- //detailIntent.putExtra(VideoItemDetailFragment.ARG_ITEM_ID, id);
- detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, webpageUrl);
- detailIntent.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, currentStreamingServiceId);
- startActivity(detailIntent);
- }
- }
-
-
- public boolean onCreateOptionsMenu(Menu menu) {
- super.onCreateOptionsMenu(menu);
- this.menu = menu;
- MenuInflater inflater = getMenuInflater();
- if(mode != PRESENT_VIDEOS_MODE &&
- findViewById(R.id.videoitem_detail_container) == null) {
- inflater.inflate(R.menu.videoitem_list, menu);
- MenuItem searchItem = menu.findItem(R.id.action_search);
- SearchView searchView = (SearchView) searchItem.getActionView();
- searchView.setFocusable(false);
- searchView.setOnQueryTextListener(
- new SearchVideoQueryListener());
- suggestionListAdapter = new SuggestionListAdapter(this);
- searchView.setSuggestionsAdapter(suggestionListAdapter);
- searchView.setOnSuggestionListener(new SearchSuggestionListener(searchView));
- if(!searchQuery.isEmpty()) {
- searchView.setQuery(searchQuery,false);
- searchView.setIconifiedByDefault(false);
- }
- } else if (videoFragment != null){
- videoFragment.onCreateOptionsMenu(menu, inflater);
- } else {
- inflater.inflate(R.menu.videoitem_two_pannel, menu);
- }
-
- return true;
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- int id = item.getItemId();
-
- switch(id) {
- case android.R.id.home: {
- Intent intent = new Intent(this, VideoItemListActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- NavUtils.navigateUpTo(this, intent);
- return true;
- }
- case R.id.action_settings: {
- Intent intent = new Intent(this, SettingsActivity.class);
- startActivity(intent);
- return true;
- }
- case R.id.action_report_error: {
- ErrorActivity.reportError(VideoItemListActivity.this, new Vector(),
- null, null,
- ErrorActivity.ErrorInfo.make(ErrorActivity.USER_REPORT,
- ServiceList.getNameOfService(currentStreamingServiceId),
- "user_report", R.string.user_report));
- return true;
- }
- case R.id.action_show_downloads: {
- Intent intent = new Intent(this, org.schabi.newpipe.download.MainActivity.class);
- startActivity(intent);
- return true;
- }
- default:
- return videoFragment.onOptionsItemSelected(item) ||
- super.onOptionsItemSelected(item);
- }
- }
-
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- /*
- if (mActivatedPosition != ListView.INVALID_POSITION) {
- // Serialize and persist the activated item position.
- outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
- }
- */
- outState.putString(QUERY, searchQuery);
- outState.putInt(STREAMING_SERVICE, currentStreamingServiceId);
- }
-
- private void searchSuggestions(String query) {
- suggestionSearchRunnable =
- new SuggestionSearchRunnable(currentStreamingServiceId, query);
- searchThread = new Thread(suggestionSearchRunnable);
- searchThread.start();
-
- }
-
- private void postNewErrorToast(Handler h, final int stringResource) {
- h.post(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(VideoItemListActivity.this, getString(stringResource),
- Toast.LENGTH_SHORT).show();
- }
- });
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/VideoItemListFragment.java b/app/src/main/java/org/schabi/newpipe/VideoItemListFragment.java
deleted file mode 100644
index 7c7d0588c..000000000
--- a/app/src/main/java/org/schabi/newpipe/VideoItemListFragment.java
+++ /dev/null
@@ -1,370 +0,0 @@
-package org.schabi.newpipe;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.Bundle;
-import android.os.Handler;
-import android.preference.PreferenceManager;
-import android.support.v4.app.ListFragment;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.AbsListView;
-import android.widget.ListView;
-import android.widget.Toast;
-
-import java.io.IOException;
-import java.util.List;
-
-import org.schabi.newpipe.extractor.ExtractionException;
-import org.schabi.newpipe.extractor.SearchResult;
-import org.schabi.newpipe.extractor.StreamPreviewInfo;
-import org.schabi.newpipe.extractor.SearchEngine;
-import org.schabi.newpipe.extractor.StreamingService;
-
-
-/**
- * Copyright (C) Christian Schabesberger 2015
- * VideoItemListFragment.java is part of NewPipe.
- *
- * NewPipe is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * NewPipe is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with NewPipe. If not, see .
- */
-
-public class VideoItemListFragment extends ListFragment {
-
- private static final String TAG = VideoItemListFragment.class.toString();
-
- private StreamingService streamingService;
- private VideoListAdapter videoListAdapter;
-
- // activity modes
- private static final int SEARCH_MODE = 0;
- private static final int PRESENT_VIDEOS_MODE = 1;
-
- private int mode;
- private String query = "";
- private int lastPage;
-
- private Thread searchThread;
- private SearchRunnable searchRunnable;
- // used to track down if results posted by threads ar still valid
- private int currentRequestId = -1;
- private ListView list;
-
- private View footer;
-
- // used to suppress request for loading a new page while another page is already loading.
- private boolean loadingNextPage = true;
-
- private class ResultRunnable implements Runnable {
- private final SearchResult result;
- private final int requestId;
- public ResultRunnable(SearchResult result, int requestId) {
- this.result = result;
- this.requestId = requestId;
- }
- @Override
- public void run() {
- updateListOnResult(result, requestId);
- if (android.os.Build.VERSION.SDK_INT >= 19) {
- getListView().removeFooterView(footer);
- }
- }
- }
-
- private class SearchRunnable implements Runnable {
- public static final String YOUTUBE = "Youtube";
- private final SearchEngine engine;
- private final String query;
- private final int page;
- final Handler h = new Handler();
- private volatile boolean runs = true;
- private final int requestId;
- public SearchRunnable(SearchEngine engine, String query, int page, int requestId) {
- this.engine = engine;
- this.query = query;
- this.page = page;
- this.requestId = requestId;
- }
- void terminate() {
- runs = false;
- }
- @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));
- result = SearchResult
- .getSearchResult(engine, query, page, searchLanguage, new Downloader());
-
- if(runs) {
- h.post(new ResultRunnable(result, requestId));
- }
-
- // look for errors during extraction
- // soft errors:
- if(result != null &&
- !result.errors.isEmpty()) {
- Log.e(TAG, "OCCURRED ERRORS DURING SEARCH EXTRACTION:");
- for(Exception e : result.errors) {
- e.printStackTrace();
- Log.e(TAG, "------");
- }
-
- Activity a = getActivity();
- View rootView = a.findViewById(R.id.videoitem_list);
- ErrorActivity.reportError(h, getActivity(), result.errors, null, rootView,
- ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
- /* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.light_parsing_error));
-
- }
- // hard errors:
- } catch(IOException e) {
- 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) {
- ErrorActivity.reportError(h, getActivity(), e, null, null,
- ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
- /* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.general_error));
-
- e.printStackTrace();
- }
- }
- }
-
- public void present(List videoList) {
- mode = PRESENT_VIDEOS_MODE;
- setListShown(true);
- getListView().smoothScrollToPosition(0);
-
- updateList(videoList);
- }
-
- public void search(String query) {
- mode = SEARCH_MODE;
- this.query = query;
- this.lastPage = 1;
- videoListAdapter.clearVideoList();
- setListShown(false);
- startSearch(query, lastPage);
- //todo: Somehow this command is not working on older devices,
- // although it was introduced with API level 8. Test this and find a solution.
- getListView().smoothScrollToPosition(0);
- }
-
- private void nextPage() {
- loadingNextPage = true;
- lastPage++;
- Log.d(TAG, getString(R.string.search_page) + Integer.toString(lastPage));
- startSearch(query, lastPage);
- }
-
- private void startSearch(String query, int page) {
- currentRequestId++;
- terminateThreads();
- searchRunnable = new SearchRunnable(streamingService.getSearchEngineInstance(new Downloader()),
- query, page, currentRequestId);
- searchThread = new Thread(searchRunnable);
- searchThread.start();
- }
-
- public void setStreamingService(StreamingService streamingService) {
- this.streamingService = streamingService;
- }
-
- private void updateListOnResult(SearchResult result, int requestId) {
- if(requestId == currentRequestId) {
- setListShown(true);
- updateList(result.resultList);
- if(!result.suggestion.isEmpty()) {
- Toast.makeText(getActivity(),
- String.format(getString(R.string.did_you_mean), result.suggestion),
- Toast.LENGTH_LONG).show();
- }
- }
- }
-
- private void updateList(List list) {
- try {
- videoListAdapter.addVideoList(list);
- terminateThreads();
- } catch(java.lang.IllegalStateException e) {
- Toast.makeText(getActivity(), "Trying to set value while activity doesn't exist anymore.",
- Toast.LENGTH_SHORT).show();
- Log.w(TAG, "Trying to set value while activity doesn't exist anymore.");
- } catch(Exception e) {
- Toast.makeText(getActivity(), getString(R.string.general_error),
- Toast.LENGTH_SHORT).show();
- e.printStackTrace();
- } finally {
- loadingNextPage = false;
- }
- }
-
- private void terminateThreads() {
- if(searchThread != null) {
- searchRunnable.terminate();
- // No need to join, since we don't really terminate the thread. We just demand
- // it to post its result runnable into the gui main loop.
- }
- }
-
- /**
- * The serialization (saved instance state) Bundle key representing the
- * activated item position. Only used on tablets.
- */
- private static final String STATE_ACTIVATED_POSITION = "activated_position";
-
- /**
- * The current activated item position. Only used on tablets.
- */
- private int mActivatedPosition = ListView.INVALID_POSITION;
-
- /**
- * A callback interface that all activities containing this fragment must
- * implement. This mechanism allows activities to be notified of item
- * selections.
- */
- public interface Callbacks {
- /**
- * Callback for when an item has been selected.
- */
- void onItemSelected(String id);
- }
-
- private Callbacks mCallbacks;
-
- @Override
- public void onViewCreated(View view, Bundle savedInstanceState) {
- super.onViewCreated(view, savedInstanceState);
- list = getListView();
- videoListAdapter = new VideoListAdapter(getActivity(), this);
- footer = ((LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE))
- .inflate(R.layout.paginate_footer, null, false);
-
-
- setListAdapter(videoListAdapter);
-
- // Restore the previously serialized activated item position.
- if (savedInstanceState != null
-
- && savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
- setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
- }
-
-
- getListView().setOnScrollListener(new AbsListView.OnScrollListener() {
- long lastScrollDate;
-
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- }
-
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem,
- int visibleItemCount, int totalItemCount) {
- if (mode != PRESENT_VIDEOS_MODE
- && list.getChildAt(0) != null
- && list.getLastVisiblePosition() == list.getAdapter().getCount() - 1
- && list.getChildAt(list.getChildCount() - 1).getBottom() >= list.getHeight()) {
- long time = System.currentTimeMillis();
- if ((time - lastScrollDate) > 200
- && !loadingNextPage) {
- lastScrollDate = time;
- getListView().addFooterView(footer);
- nextPage();
- }
- }
- }
-
- });
- }
-
- @Override
- public void onAttach(Context context) {
- super.onAttach(context);
-
- // Activities containing this fragment must implement its callbacks.
- if (!(context instanceof Callbacks)) {
- throw new IllegalStateException("Activity must implement fragment's callbacks.");
- }
-
- mCallbacks = (Callbacks) context;
- }
-
- @Override
- public void onListItemClick(ListView listView, View view, int position, long id) {
- super.onListItemClick(listView, view, position, id);
- setActivatedPosition(position);
- mCallbacks.onItemSelected(Long.toString(id));
- }
-
- /**
- * Turns on activate-on-click mode. When this mode is on, list items will be
- * given the 'activated' state when touched.
- */
- public void setActivateOnItemClick(@SuppressWarnings("SameParameterValue") boolean activateOnItemClick) {
- // When setting CHOICE_MODE_SINGLE, ListView will automatically
- // give items the 'activated' state when touched.
- getListView().setChoiceMode(activateOnItemClick
- ? ListView.CHOICE_MODE_SINGLE
- : ListView.CHOICE_MODE_NONE);
- }
-
- private void setActivatedPosition(int position) {
- if (position == ListView.INVALID_POSITION) {
- getListView().setItemChecked(mActivatedPosition, false);
- } else {
- getListView().setItemChecked(position, true);
- }
-
- mActivatedPosition = position;
- }
-
- 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_LONG).show();
- }
- });
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/VideoListAdapter.java b/app/src/main/java/org/schabi/newpipe/VideoListAdapter.java
deleted file mode 100644
index d69b842cf..000000000
--- a/app/src/main/java/org/schabi/newpipe/VideoListAdapter.java
+++ /dev/null
@@ -1,91 +0,0 @@
-package org.schabi.newpipe;
-
-import android.content.Context;
-import android.support.v4.content.ContextCompat;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.ListView;
-
-import org.schabi.newpipe.extractor.StreamPreviewInfo;
-
-import java.util.List;
-import java.util.Vector;
-
-/**
- * Created by Christian Schabesberger on 11.08.15.
- *
- * Copyright (C) Christian Schabesberger 2015
- * VideoListAdapter.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 .
- */
-
-class VideoListAdapter extends BaseAdapter {
- private final Context context;
- private final VideoInfoItemViewCreator viewCreator;
- private Vector videoList = new Vector<>();
- private final ListView listView;
-
- public VideoListAdapter(Context context, VideoItemListFragment videoListFragment) {
- viewCreator = new VideoInfoItemViewCreator(LayoutInflater.from(context));
- this.listView = videoListFragment.getListView();
- this.listView.setDivider(null);
- this.listView.setDividerHeight(0);
- this.context = context;
- }
-
- public void addVideoList(List videos) {
- videoList.addAll(videos);
- notifyDataSetChanged();
- }
-
- public void clearVideoList() {
- videoList = new Vector<>();
- notifyDataSetChanged();
- }
-
- public Vector getVideoList() {
- return videoList;
- }
-
- @Override
- public int getCount() {
- return videoList.size();
- }
-
- @Override
- public Object getItem(int position) {
- return videoList.get(position);
- }
-
- @Override
- public long getItemId(int position) {
- return position;
- }
-
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- convertView = viewCreator.getViewFromVideoInfoItem(convertView, parent, videoList.get(position));
-
- if(listView.isItemChecked(position)) {
- convertView.setBackgroundColor(ContextCompat.getColor(context,R.color.light_youtube_primary_color));
- } else {
- convertView.setBackgroundColor(0);
- }
-
- return convertView;
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/ActionBarHandler.java b/app/src/main/java/org/schabi/newpipe/detail/ActionBarHandler.java
similarity index 98%
rename from app/src/main/java/org/schabi/newpipe/ActionBarHandler.java
rename to app/src/main/java/org/schabi/newpipe/detail/ActionBarHandler.java
index 80c149885..feed040d1 100644
--- a/app/src/main/java/org/schabi/newpipe/ActionBarHandler.java
+++ b/app/src/main/java/org/schabi/newpipe/detail/ActionBarHandler.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe;
+package org.schabi.newpipe.detail;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -11,8 +11,9 @@ import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.extractor.MediaFormat;
-import org.schabi.newpipe.extractor.StreamInfo;
import org.schabi.newpipe.extractor.VideoStream;
import java.util.List;
diff --git a/app/src/main/java/org/schabi/newpipe/detail/StreamInfoWorker.java b/app/src/main/java/org/schabi/newpipe/detail/StreamInfoWorker.java
new file mode 100644
index 000000000..03178f9d1
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/detail/StreamInfoWorker.java
@@ -0,0 +1,204 @@
+package org.schabi.newpipe.detail;
+
+import android.app.Activity;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+
+import org.schabi.newpipe.Downloader;
+import org.schabi.newpipe.ErrorActivity;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.ParsingException;
+import org.schabi.newpipe.extractor.ServiceList;
+import org.schabi.newpipe.extractor.StreamExtractor;
+import org.schabi.newpipe.extractor.StreamInfo;
+import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
+
+import java.io.IOException;
+
+/**
+ * Created by the-scrabi on 02.08.16.
+ */
+
+public class StreamInfoWorker {
+
+ private static final String TAG = StreamInfoWorker.class.toString();
+
+ public interface OnStreamInfoReceivedListener {
+ void onReceive(StreamInfo info);
+ void onError(int messageId);
+ void onBlockedByGemaError();
+ void onContentErrorWithMessage(int messageId);
+ void onContentError();
+ }
+
+ private class StreamExtractorRunnable implements Runnable {
+ private final Handler h = new Handler();
+ private StreamExtractor streamExtractor;
+ private final int serviceId;
+ private final String videoUrl;
+ private Activity a;
+
+ public StreamExtractorRunnable(Activity a, String videoUrl, int serviceId) {
+ this.serviceId = serviceId;
+ this.videoUrl = videoUrl;
+ this.a = a;
+ }
+
+ @Override
+ public void run() {
+ StreamInfo streamInfo = null;
+ StreamingService service = null;
+ try {
+ service = ServiceList.getService(serviceId);
+ } catch (Exception e) {
+ e.printStackTrace();
+ ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null,
+ ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
+ "", videoUrl, R.string.could_not_get_stream));
+ return;
+ }
+ try {
+ streamExtractor = service.getExtractorInstance(videoUrl, new Downloader());
+ streamInfo = StreamInfo.getVideoInfo(streamExtractor, new Downloader());
+
+ final StreamInfo info = streamInfo;
+ h.post(new Runnable() {
+ @Override
+ public void run() {
+ onStreamInfoReceivedListener.onReceive(info);
+ }
+ });
+
+ // 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 (Throwable e : streamInfo.errors) {
+ e.printStackTrace();
+ Log.e(TAG, "------");
+ }
+
+ View rootView = a != null ? a.findViewById(R.id.video_item_detail) : null;
+ ErrorActivity.reportError(h, a,
+ 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) {
+ h.post(new Runnable() {
+ @Override
+ public void run() {
+ onStreamInfoReceivedListener.onError(R.string.network_error);
+ }
+ });
+ e.printStackTrace();
+ }
+ // custom service related exceptions
+ catch (YoutubeStreamExtractor.DecryptException de) {
+ h.post(new Runnable() {
+ @Override
+ public void run() {
+ onStreamInfoReceivedListener.onError(R.string.youtube_signature_decryption_error);
+ }
+ });
+ de.printStackTrace();
+ } catch (YoutubeStreamExtractor.GemaException ge) {
+ h.post(new Runnable() {
+ @Override
+ public void run() {
+ onStreamInfoReceivedListener.onBlockedByGemaError();
+ }
+ });
+ } catch(YoutubeStreamExtractor.LiveStreamException e) {
+ h.post(new Runnable() {
+ @Override
+ public void run() {
+ onStreamInfoReceivedListener
+ .onContentErrorWithMessage(R.string.live_streams_not_supported);
+ }
+ });
+ }
+ // ----------------------------------------
+ catch(StreamExtractor.ContentNotAvailableException e) {
+ h.post(new Runnable() {
+ @Override
+ public void run() {
+ onStreamInfoReceivedListener
+ .onContentError();
+ }
+ });
+ e.printStackTrace();
+ } catch(StreamInfo.StreamExctractException e) {
+ if(!streamInfo.errors.isEmpty()) {
+ // !!! if this case ever kicks in someone gets kicked out !!!
+ ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null,
+ ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
+ service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream));
+ } else {
+ ErrorActivity.reportError(h, a, streamInfo.errors, VideoItemDetailFragment.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() {
+ a.finish();
+ }
+ });
+ e.printStackTrace();
+ } catch (ParsingException e) {
+ ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null,
+ ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
+ service.getServiceInfo().name, videoUrl, R.string.parsing_error));
+ h.post(new Runnable() {
+ @Override
+ public void run() {
+ a.finish();
+ }
+ });
+ e.printStackTrace();
+ } catch(Exception e) {
+ ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null,
+ ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
+ service.getServiceInfo().name, videoUrl, R.string.general_error));
+ h.post(new Runnable() {
+ @Override
+ public void run() {
+ a.finish();
+ }
+ });
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private static StreamInfoWorker streamInfoWorker = null;
+ private StreamExtractorRunnable runnable = null;
+ private OnStreamInfoReceivedListener onStreamInfoReceivedListener = null;
+
+ private StreamInfoWorker() {
+
+ }
+
+ public static StreamInfoWorker getInstance() {
+ return streamInfoWorker == null ? (streamInfoWorker = new StreamInfoWorker()) : streamInfoWorker;
+ }
+
+ public void search(int serviceId, String url, Activity a) {
+ runnable = new StreamExtractorRunnable(a, url, serviceId);
+ Thread thread = new Thread(runnable);
+ thread.start();
+ }
+
+ public void setOnStreamInfoReceivedListener(
+ OnStreamInfoReceivedListener onStreamInfoReceivedListener) {
+ this.onStreamInfoReceivedListener = onStreamInfoReceivedListener;
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/VideoItemDetailActivity.java b/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailActivity.java
similarity index 95%
rename from app/src/main/java/org/schabi/newpipe/VideoItemDetailActivity.java
rename to app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailActivity.java
index 9ba18bdcc..04649bf4d 100644
--- a/app/src/main/java/org/schabi/newpipe/VideoItemDetailActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailActivity.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe;
+package org.schabi.newpipe.detail;
import android.content.Intent;
import android.media.AudioManager;
@@ -11,6 +11,9 @@ import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
+import org.schabi.newpipe.App;
+import org.schabi.newpipe.MainActivity;
+import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ServiceList;
import org.schabi.newpipe.extractor.StreamingService;
@@ -85,7 +88,7 @@ public class VideoItemDetailActivity extends AppCompatActivity {
.show();
}
//arguments.putString(VideoItemDetailFragment.VIDEO_URL,
- // videoExtractor.getVideoUrl(videoExtractor.getVideoId(videoUrl)));//cleans URL
+ // videoExtractor.getUrl(videoExtractor.getId(videoUrl)));//cleans URL
arguments.putString(VideoItemDetailFragment.VIDEO_URL, videoUrl);
arguments.putBoolean(VideoItemDetailFragment.AUTO_PLAY,
@@ -138,7 +141,7 @@ public class VideoItemDetailActivity extends AppCompatActivity {
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
- Intent intent = new Intent(this, VideoItemListActivity.class);
+ Intent intent = new Intent(this, MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent);
return true;
diff --git a/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java b/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java
similarity index 66%
rename from app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java
rename to app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java
index c9f277562..2c5f2f46e 100644
--- a/app/src/main/java/org/schabi/newpipe/VideoItemDetailFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java
@@ -1,22 +1,21 @@
-package org.schabi.newpipe;
+package org.schabi.newpipe.detail;
import android.app.Activity;
-import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Point;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.Handler;
import android.preference.PreferenceManager;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.Fragment;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.util.Log;
@@ -27,38 +26,34 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
-import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.view.MenuItem;
import android.widget.Toast;
-import java.io.IOException;
-
import com.google.android.exoplayer.util.Util;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
-import java.util.ArrayList;
import java.util.Vector;
-
+import org.schabi.newpipe.ActivityCommunicator;
+import org.schabi.newpipe.ChannelActivity;
+import org.schabi.newpipe.ErrorActivity;
+import org.schabi.newpipe.ImageErrorLoadingListener;
+import org.schabi.newpipe.Localization;
+import org.schabi.newpipe.R;
import org.schabi.newpipe.download.DownloadDialog;
import org.schabi.newpipe.extractor.AudioStream;
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.StreamInfo;
-import org.schabi.newpipe.extractor.StreamPreviewInfo;
-import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.VideoStream;
-import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
+import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.player.BackgroundPlayer;
import org.schabi.newpipe.player.PlayVideoActivity;
import org.schabi.newpipe.player.ExoPlayerActivity;
@@ -102,19 +97,20 @@ public class VideoItemDetailFragment extends Fragment {
private int streamingServiceId = -1;
private boolean autoPlayEnabled;
- private boolean showNextVideoItem;
- private Bitmap videoThumbnail;
+ private boolean showNextStreamItem;
private View thumbnailWindowLayout;
//this only remains due to downwards compatibility
private FloatingActionButton playVideoButton;
private final Point initialThumbnailPos = new Point(0, 0);
-
+ private View rootView = null;
+ private Bitmap streamThumbnail = null;
private ImageLoader imageLoader = ImageLoader.getInstance();
private DisplayImageOptions displayImageOptions =
new DisplayImageOptions.Builder().cacheInMemory(true).build();
+ private InfoListAdapter similarStreamsAdapter = null;
public interface OnInvokeCreateOptionsMenuListener {
void createOptionsMenu();
@@ -122,215 +118,57 @@ public class VideoItemDetailFragment extends Fragment {
private OnInvokeCreateOptionsMenuListener onInvokeCreateOptionsMenuListener;
- private class VideoExtractorRunnable implements Runnable {
- private final Handler h = new Handler();
- private StreamExtractor streamExtractor;
- private final StreamingService service;
- private final String videoUrl;
-
- public VideoExtractorRunnable(String videoUrl, StreamingService service) {
- this.service = service;
- this.videoUrl = videoUrl;
- }
-
- @Override
- public void run() {
- StreamInfo streamInfo = null;
- try {
- streamExtractor = service.getExtractorInstance(videoUrl, new Downloader());
- streamInfo = StreamInfo.getVideoInfo(streamExtractor, new Downloader());
-
- 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();
- }
- // custom service related exceptions
- catch (YoutubeStreamExtractor.DecryptException de) {
- postNewErrorToast(h, R.string.youtube_signature_decryption_error);
- de.printStackTrace();
- } catch (YoutubeStreamExtractor.GemaException ge) {
- h.post(new Runnable() {
- @Override
- public void run() {
- onErrorBlockedByGema();
- }
- });
- } catch(YoutubeStreamExtractor.LiveStreamException e) {
- h.post(new Runnable() {
- @Override
- public void run() {
- onNotSpecifiedContentErrorWithMessage(R.string.live_streams_not_supported);
- }
- });
- }
- // ----------------------------------------
- catch(StreamExtractor.ContentNotAvailableException e) {
- h.post(new Runnable() {
- @Override
- public void run() {
- onNotSpecifiedContentError();
- }
- });
- 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) {
- 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) {
- 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 StreamInfo streamInfo;
- public VideoResultReturnedRunnable(StreamInfo streamInfo) {
- this.streamInfo = streamInfo;
- }
- @Override
- public void run() {
- Activity a = getActivity();
- if(a != null) {
- boolean showAgeRestrictedContent = PreferenceManager.getDefaultSharedPreferences(a)
- .getBoolean(activity.getString(R.string.show_age_restricted_content), false);
- if (streamInfo.age_limit == 0 || showAgeRestrictedContent) {
- updateInfo(streamInfo);
- } else {
- onNotSpecifiedContentErrorWithMessage(R.string.video_is_age_restricted);
- }
- }
- }
- }
-
- private class ThumbnailLoadingListener implements ImageLoadingListener {
- @Override
- public void onLoadingStarted(String imageUri, View view) {}
-
- @Override
- public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
- if(getContext() != null) {
- Toast.makeText(VideoItemDetailFragment.this.getActivity(),
- R.string.could_not_load_thumbnails, Toast.LENGTH_LONG).show();
- }
- failReason.getCause().printStackTrace();
- }
-
- @Override
- public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {}
-
- @Override
- public void onLoadingCancelled(String imageUri, View view) {}
- }
-
private void updateInfo(final StreamInfo info) {
try {
- Context c = getContext();
- VideoInfoItemViewCreator videoItemViewCreator =
- new VideoInfoItemViewCreator(LayoutInflater.from(getActivity()));
+ Activity a = getActivity();
RelativeLayout textContentLayout =
- (RelativeLayout) activity.findViewById(R.id.detailTextContentLayout);
+ (RelativeLayout) activity.findViewById(R.id.detail_text_content_layout);
final TextView videoTitleView =
- (TextView) activity.findViewById(R.id.detailVideoTitleView);
- TextView uploaderView = (TextView) activity.findViewById(R.id.detailUploaderView);
- TextView viewCountView = (TextView) activity.findViewById(R.id.detailViewCountView);
- TextView thumbsUpView = (TextView) activity.findViewById(R.id.detailThumbsUpCountView);
+ (TextView) activity.findViewById(R.id.detail_video_title_view);
+ TextView uploaderView = (TextView) activity.findViewById(R.id.detail_uploader_view);
+ TextView viewCountView = (TextView) activity.findViewById(R.id.detail_view_count_view);
+ TextView thumbsUpView = (TextView) activity.findViewById(R.id.detail_thumbs_up_count_view);
TextView thumbsDownView =
- (TextView) activity.findViewById(R.id.detailThumbsDownCountView);
- TextView uploadDateView = (TextView) activity.findViewById(R.id.detailUploadDateView);
- TextView descriptionView = (TextView) activity.findViewById(R.id.detailDescriptionView);
- FrameLayout nextVideoFrame =
- (FrameLayout) activity.findViewById(R.id.detailNextVideoFrame);
+ (TextView) activity.findViewById(R.id.detail_thumbs_down_count_view);
+ TextView uploadDateView = (TextView) activity.findViewById(R.id.detail_upload_date_view);
+ TextView descriptionView = (TextView) activity.findViewById(R.id.detail_description_view);
+ RecyclerView nextStreamView =
+ (RecyclerView) activity.findViewById(R.id.detail_next_stream_content);
RelativeLayout nextVideoRootFrame =
- (RelativeLayout) activity.findViewById(R.id.detailNextVideoRootLayout);
- Button nextVideoButton = (Button) activity.findViewById(R.id.detailNextVideoButton);
- TextView similarTitle = (TextView) activity.findViewById(R.id.detailSimilarTitle);
+ (RelativeLayout) activity.findViewById(R.id.detail_next_stream_root_layout);
+ TextView similarTitle = (TextView) activity.findViewById(R.id.detail_similar_title);
Button backgroundButton = (Button)
- activity.findViewById(R.id.detailVideoThumbnailWindowBackgroundButton);
+ activity.findViewById(R.id.detail_stream_thumbnail_window_background_button);
View topView = activity.findViewById(R.id.detailTopView);
- View nextVideoView = null;
- if(info.next_video != null) {
- nextVideoView = videoItemViewCreator
- .getViewFromVideoInfoItem(null, nextVideoFrame, info.next_video);
- } else {
- activity.findViewById(R.id.detailNextVidButtonAndContentLayout).setVisibility(View.GONE);
- activity.findViewById(R.id.detailNextVideoTitle).setVisibility(View.GONE);
- activity.findViewById(R.id.detailNextVideoButton).setVisibility(View.GONE);
- }
+ Button channelButton = (Button) activity.findViewById(R.id.channel_button);
progressBar.setVisibility(View.GONE);
- if(nextVideoView != null) {
- nextVideoFrame.addView(nextVideoView);
+ if(info.next_video != null) {
+ InfoListAdapter adapter = new InfoListAdapter(a, rootView);
+ nextStreamView.setAdapter(adapter);
+ nextStreamView.setLayoutManager(new LinearLayoutManager(a));
+ adapter.setOnItemSelectedListener(new InfoListAdapter.OnItemSelectedListener() {
+ @Override
+ public void selected(String url) {
+ openStreamUrl(url);
+ }
+ });
+ } else {
+ nextStreamView.setVisibility(View.GONE);
+ activity.findViewById(R.id.detail_similar_title).setVisibility(View.GONE);
}
- initThumbnailViews(info, nextVideoFrame);
textContentLayout.setVisibility(View.VISIBLE);
if (android.os.Build.VERSION.SDK_INT < 18) {
playVideoButton.setVisibility(View.VISIBLE);
} else {
- ImageView playArrowView = (ImageView) activity.findViewById(R.id.playArrowView);
+ ImageView playArrowView = (ImageView) activity.findViewById(R.id.play_arrow_view);
playArrowView.setVisibility(View.VISIBLE);
}
- if (!showNextVideoItem) {
+ if (!showNextStreamItem) {
nextVideoRootFrame.setVisibility(View.GONE);
similarTitle.setVisibility(View.GONE);
}
@@ -341,7 +179,7 @@ public class VideoItemDetailFragment extends Fragment {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == android.view.MotionEvent.ACTION_UP) {
- ImageView arrow = (ImageView) activity.findViewById(R.id.toggleDescriptionView);
+ ImageView arrow = (ImageView) activity.findViewById(R.id.toggle_description_view);
View extra = activity.findViewById(R.id.detailExtraView);
if (extra.getVisibility() == View.VISIBLE) {
extra.setVisibility(View.GONE);
@@ -361,29 +199,29 @@ public class VideoItemDetailFragment extends Fragment {
if(!info.uploader.isEmpty()) {
uploaderView.setText(info.uploader);
} else {
- activity.findViewById(R.id.detailUploaderWrapView).setVisibility(View.GONE);
+ activity.findViewById(R.id.detail_uploader_view).setVisibility(View.GONE);
}
if(info.view_count >= 0) {
- viewCountView.setText(Localization.localizeViewCount(info.view_count, c));
+ viewCountView.setText(Localization.localizeViewCount(info.view_count, a));
} else {
viewCountView.setVisibility(View.GONE);
}
if(info.dislike_count >= 0) {
- thumbsDownView.setText(Localization.localizeNumber(info.dislike_count, c));
+ thumbsDownView.setText(Localization.localizeNumber(info.dislike_count, a));
} else {
thumbsDownView.setVisibility(View.INVISIBLE);
- activity.findViewById(R.id.detailThumbsDownImgView).setVisibility(View.GONE);
+ activity.findViewById(R.id.detail_thumbs_down_count_view).setVisibility(View.GONE);
}
if(info.like_count >= 0) {
- thumbsUpView.setText(Localization.localizeNumber(info.like_count, c));
+ thumbsUpView.setText(Localization.localizeNumber(info.like_count, a));
} else {
thumbsUpView.setVisibility(View.GONE);
- activity.findViewById(R.id.detailThumbsUpImgView).setVisibility(View.GONE);
+ activity.findViewById(R.id.detail_thumbs_up_img_view).setVisibility(View.GONE);
thumbsDownView.setVisibility(View.GONE);
- activity.findViewById(R.id.detailThumbsDownImgView).setVisibility(View.GONE);
+ activity.findViewById(R.id.detail_thumbs_down_img_view).setVisibility(View.GONE);
}
if(!info.upload_date.isEmpty()) {
- uploadDateView.setText(Localization.localizeDate(info.upload_date, c));
+ uploadDateView.setText(Localization.localizeDate(info.upload_date, a));
} else {
uploadDateView.setVisibility(View.GONE);
}
@@ -403,26 +241,17 @@ public class VideoItemDetailFragment extends Fragment {
}
}
- nextVideoButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent detailIntent =
- new Intent(getActivity(), VideoItemDetailActivity.class);
- /*detailIntent.putExtra(
- VideoItemDetailFragment.ARG_ITEM_ID, currentVideoInfo.nextVideo.id); */
- detailIntent.putExtra(
- VideoItemDetailFragment.VIDEO_URL, info.next_video.webpage_url);
- detailIntent.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
- startActivity(detailIntent);
- }
- });
textContentLayout.setVisibility(View.VISIBLE);
- if(info.related_videos != null && !info.related_videos.isEmpty()) {
- initSimilarVideos(info, videoItemViewCreator);
+ if(info.next_video == null) {
+ activity.findViewById(R.id.detail_next_stream_title).setVisibility(View.GONE);
+ }
+
+ if(info.related_streams != null && !info.related_streams.isEmpty()) {
+ initSimilarVideos(info);
} else {
- activity.findViewById(R.id.detailSimilarTitle).setVisibility(View.GONE);
- activity.findViewById(R.id.similarVideosView).setVisibility(View.GONE);
+ activity.findViewById(R.id.detail_similar_title).setVisibility(View.GONE);
+ activity.findViewById(R.id.similar_streams_view).setVisibility(View.GONE);
}
setupActionBarHandler(info);
@@ -447,18 +276,32 @@ public class VideoItemDetailFragment extends Fragment {
}
});
+ if(info.channel_url != null && info.channel_url != "") {
+ channelButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent i = new Intent(activity, ChannelActivity.class);
+ i.putExtra(ChannelActivity.CHANNEL_URL, info.channel_url);
+ i.putExtra(ChannelActivity.SERVICE_ID, info.service_id);
+ startActivity(i);
+ }
+ });
+ } else {
+ channelButton.setVisibility(Button.GONE);
+ }
+
+ initThumbnailViews(info);
+
} catch (java.lang.NullPointerException e) {
Log.w(TAG, "updateInfo(): Fragment closed before thread ended work... or else");
e.printStackTrace();
}
}
- private void initThumbnailViews(StreamInfo info, View nextVideoFrame) {
- ImageView videoThumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView);
+ private void initThumbnailViews(final StreamInfo info) {
+ ImageView videoThumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view);
ImageView uploaderThumb
- = (ImageView) activity.findViewById(R.id.detailUploaderThumbnailView);
- ImageView nextVideoThumb =
- (ImageView) nextVideoFrame.findViewById(R.id.itemThumbnailView);
+ = (ImageView) activity.findViewById(R.id.detail_uploader_thumbnail_view);
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
imageLoader.displayImage(info.thumbnail_url, videoThumbnailView,
@@ -469,14 +312,16 @@ public class VideoItemDetailFragment extends Fragment {
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
- Toast.makeText(VideoItemDetailFragment.this.getActivity(),
- R.string.could_not_load_thumbnails, Toast.LENGTH_LONG).show();
- failReason.getCause().printStackTrace();
+ ErrorActivity.reportError(getActivity(),
+ failReason.getCause(), null, rootView,
+ ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
+ ServiceList.getNameOfService(info.service_id), imageUri,
+ R.string.could_not_load_thumbnails));
}
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
- videoThumbnail = loadedImage;
+ streamThumbnail = loadedImage;
}
@Override
@@ -488,11 +333,8 @@ public class VideoItemDetailFragment extends Fragment {
}
if(info.uploader_thumbnail_url != null && !info.uploader_thumbnail_url.isEmpty()) {
imageLoader.displayImage(info.uploader_thumbnail_url,
- uploaderThumb, displayImageOptions, new ThumbnailLoadingListener());
- }
- if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty() && info.next_video != null) {
- imageLoader.displayImage(info.next_video.thumbnail_url,
- nextVideoThumb, displayImageOptions, new ThumbnailLoadingListener());
+ uploaderThumb, displayImageOptions,
+ new ImageErrorLoadingListener(activity, rootView, info.service_id));
}
}
@@ -602,9 +444,9 @@ public class VideoItemDetailFragment extends Fragment {
info.audio_streams.get(getPreferredAudioStreamId(info));
if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 18) {
//internal music player: explicit intent
- if (!BackgroundPlayer.isRunning && videoThumbnail != null) {
+ if (!BackgroundPlayer.isRunning && streamThumbnail != null) {
ActivityCommunicator.getCommunicator()
- .backgroundPlayerThumbnail = videoThumbnail;
+ .backgroundPlayerThumbnail = streamThumbnail;
intent = new Intent(activity, BackgroundPlayer.class);
intent.setAction(Intent.ACTION_VIEW);
@@ -684,47 +526,14 @@ public class VideoItemDetailFragment extends Fragment {
return 0;
}
- private void initSimilarVideos(final StreamInfo info, VideoInfoItemViewCreator videoItemViewCreator) {
- LinearLayout similarLayout = (LinearLayout) activity.findViewById(R.id.similarVideosView);
- ArrayList similar = new ArrayList<>(info.related_videos);
- for (final StreamPreviewInfo item : similar) {
- View similarView = videoItemViewCreator
- .getViewFromVideoInfoItem(null, similarLayout, item);
-
- similarView.setClickable(true);
- similarView.setFocusable(true);
- int[] attrs = new int[]{R.attr.selectableItemBackground};
- TypedArray typedArray = activity.obtainStyledAttributes(attrs);
- int backgroundResource = typedArray.getResourceId(0, 0);
- similarView.setBackgroundResource(backgroundResource);
- typedArray.recycle();
-
- similarView.setOnTouchListener(new View.OnTouchListener() {
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- if (event.getAction() == MotionEvent.ACTION_UP) {
- Intent detailIntent = new Intent(activity, VideoItemDetailActivity.class);
- detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, item.webpage_url);
- detailIntent.putExtra(
- VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
- startActivity(detailIntent);
- return true;
- }
- return false;
- }
- });
-
- similarLayout.addView(similarView);
- ImageView rthumb = (ImageView)similarView.findViewById(R.id.itemThumbnailView);
- imageLoader.displayImage(item.thumbnail_url, rthumb,
- displayImageOptions, new ThumbnailLoadingListener());
- }
+ private void initSimilarVideos(final StreamInfo info) {
+ similarStreamsAdapter.addStreamItemList(info.related_streams);
}
private void onErrorBlockedByGema() {
Button backgroundButton = (Button)
- activity.findViewById(R.id.detailVideoThumbnailWindowBackgroundButton);
- ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView);
+ activity.findViewById(R.id.detail_stream_thumbnail_window_background_button);
+ ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view);
progressBar.setVisibility(View.GONE);
thumbnailView.setImageBitmap(BitmapFactory.decodeResource(
@@ -744,7 +553,7 @@ public class VideoItemDetailFragment extends Fragment {
}
private void onNotSpecifiedContentError() {
- ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView);
+ ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view);
progressBar.setVisibility(View.GONE);
thumbnailView.setImageBitmap(BitmapFactory.decodeResource(
getResources(), R.drawable.not_available_monkey));
@@ -753,7 +562,7 @@ public class VideoItemDetailFragment extends Fragment {
}
private void onNotSpecifiedContentErrorWithMessage(int resourceId) {
- ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView);
+ ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view);
progressBar.setVisibility(View.GONE);
thumbnailView.setImageBitmap(BitmapFactory.decodeResource(
getResources(), R.drawable.not_available_monkey));
@@ -779,16 +588,44 @@ public class VideoItemDetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
activity = (AppCompatActivity) getActivity();
- showNextVideoItem = PreferenceManager.getDefaultSharedPreferences(getActivity())
+ showNextStreamItem = PreferenceManager.getDefaultSharedPreferences(getActivity())
.getBoolean(activity.getString(R.string.show_next_video_key), true);
+
+ StreamInfoWorker siw = StreamInfoWorker.getInstance();
+ siw.setOnStreamInfoReceivedListener(new StreamInfoWorker.OnStreamInfoReceivedListener() {
+ @Override
+ public void onReceive(StreamInfo info) {
+ updateInfo(info);
+ }
+
+ @Override
+ public void onError(int messageId) {
+ postNewErrorToast(messageId);
+ }
+
+ @Override
+ public void onBlockedByGemaError() {
+ onErrorBlockedByGema();
+ }
+
+ @Override
+ public void onContentErrorWithMessage(int messageId) {
+ onNotSpecifiedContentErrorWithMessage(messageId);
+ }
+
+ @Override
+ public void onContentError() {
+ onNotSpecifiedContentError();
+ }
+ });
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- View rootView = inflater.inflate(R.layout.fragment_videoitem_detail, container, false);
- progressBar = (ProgressBar) rootView.findViewById(R.id.detailProgressBar);
+ rootView = inflater.inflate(R.layout.fragment_videoitem_detail, container, false);
+ progressBar = (ProgressBar) rootView.findViewById(R.id.detail_progress_bar);
actionBarHandler = new ActionBarHandler(activity);
actionBarHandler.setupNavMenu(activity);
@@ -804,30 +641,25 @@ public class VideoItemDetailFragment extends Fragment {
super.onActivityCreated(savedInstanceBundle);
Activity a = getActivity();
if (android.os.Build.VERSION.SDK_INT < 18) {
- playVideoButton = (FloatingActionButton) a.findViewById(R.id.playVideoButton);
+ playVideoButton = (FloatingActionButton) a.findViewById(R.id.play_video_button);
}
- thumbnailWindowLayout = a.findViewById(R.id.detailVideoThumbnailWindowLayout);
+ thumbnailWindowLayout = a.findViewById(R.id.detail_stream_thumbnail_window_layout);
Button backgroundButton = (Button)
- a.findViewById(R.id.detailVideoThumbnailWindowBackgroundButton);
+ a.findViewById(R.id.detail_stream_thumbnail_window_background_button);
// Sometimes when this fragment is not visible it still gets initiated
// then we must not try to access objects of this fragment.
// Otherwise the applications would crash.
if(backgroundButton != null) {
- try {
- streamingServiceId = getArguments().getInt(STREAMING_SERVICE);
- StreamingService streamingService = ServiceList.getService(streamingServiceId);
- Thread videoExtractorThread = new Thread(new VideoExtractorRunnable(
- getArguments().getString(VIDEO_URL), streamingService));
+ streamingServiceId = getArguments().getInt(STREAMING_SERVICE);
+ String videoUrl = getArguments().getString(VIDEO_URL);
+ StreamInfoWorker siw = StreamInfoWorker.getInstance();
+ siw.search(streamingServiceId, videoUrl, getActivity());
- autoPlayEnabled = getArguments().getBoolean(AUTO_PLAY);
- videoExtractorThread.start();
- } catch (Exception e) {
- e.printStackTrace();
- }
+ autoPlayEnabled = getArguments().getBoolean(AUTO_PLAY);
if(Build.VERSION.SDK_INT >= 18) {
- ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView);
+ ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view);
thumbnailView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
// This is used to synchronize the thumbnailWindowButton and the playVideoButton
// inside the ScrollView with the actual size of the thumbnail.
@@ -849,6 +681,17 @@ public class VideoItemDetailFragment extends Fragment {
}
});
}
+
+ similarStreamsAdapter = new InfoListAdapter(getActivity(), rootView);
+ RecyclerView rv = (RecyclerView) getActivity().findViewById(R.id.similar_streams_view);
+ rv.setLayoutManager(new LinearLayoutManager(getActivity()));
+ rv.setAdapter(similarStreamsAdapter);
+ similarStreamsAdapter.setOnItemSelectedListener(new InfoListAdapter.OnItemSelectedListener() {
+ @Override
+ public void selected(String url) {
+ openStreamUrl(url);
+ }
+ });
}
}
@@ -946,23 +789,16 @@ public class VideoItemDetailFragment extends Fragment {
this.onInvokeCreateOptionsMenuListener = listener;
}
- private void postNewErrorToast(Handler h, final int stringResource) {
- h.post(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(VideoItemDetailFragment.this.getActivity(),
- stringResource, Toast.LENGTH_LONG).show();
- }
- });
+ private void postNewErrorToast(final int stringResource) {
+ Toast.makeText(VideoItemDetailFragment.this.getActivity(),
+ stringResource, Toast.LENGTH_LONG).show();
}
- private void postNewErrorToast(Handler h, final String message) {
- h.post(new Runnable() {
- @Override
- public void run() {
- Toast.makeText(VideoItemDetailFragment.this.getActivity(),
- message, Toast.LENGTH_LONG).show();
- }
- });
+ private void openStreamUrl(String url) {
+ Intent detailIntent = new Intent(activity, VideoItemDetailActivity.class);
+ detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, url);
+ detailIntent.putExtra(
+ VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
+ activity.startActivity(detailIntent);
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
index b9c5a7439..c7018ae1d 100644
--- a/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
+++ b/app/src/main/java/org/schabi/newpipe/download/DownloadDialog.java
@@ -25,7 +25,6 @@ import android.widget.SeekBar;
import android.widget.TextView;
import org.schabi.newpipe.App;
-import org.schabi.newpipe.NewPipeSettings;
import org.schabi.newpipe.R;
import java.io.File;
diff --git a/app/src/main/java/org/schabi/newpipe/download/MainActivity.java b/app/src/main/java/org/schabi/newpipe/download/MainActivity.java
index e6c583287..a62d645f7 100644
--- a/app/src/main/java/org/schabi/newpipe/download/MainActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/download/MainActivity.java
@@ -8,14 +8,12 @@ import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
-import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -23,25 +21,16 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.AdapterView;
-import android.widget.Button;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.Toast;
-import android.support.v7.widget.SearchView;
import org.schabi.newpipe.ErrorActivity;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.SettingsActivity;
-import org.schabi.newpipe.VideoItemDetailActivity;
-import org.schabi.newpipe.VideoItemListActivity;
-import org.schabi.newpipe.extractor.ServiceList;
+import org.schabi.newpipe.settings.SettingsActivity;
import java.io.File;
-import java.io.IOException;
-import java.net.HttpURLConnection;
-import java.net.MalformedURLException;
-import java.net.URL;
import java.util.Vector;
import us.shandian.giga.get.DownloadManager;
@@ -257,7 +246,7 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
switch (id) {
case android.R.id.home: {
- Intent intent = new Intent(this, VideoItemListActivity.class);
+ Intent intent = new Intent(this, org.schabi.newpipe.MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
NavUtils.navigateUpTo(this, intent);
return true;
@@ -268,7 +257,7 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
return true;
}
case R.id.action_report_error: {
- ErrorActivity.reportError(MainActivity.this, new Vector(),
+ ErrorActivity.reportError(MainActivity.this, new Vector(),
null, null,
ErrorActivity.ErrorInfo.make(ErrorActivity.USER_REPORT,
null,
diff --git a/app/src/main/java/org/schabi/newpipe/extractor/ChannelExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/ChannelExtractor.java
new file mode 100644
index 000000000..d8c931d08
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/extractor/ChannelExtractor.java
@@ -0,0 +1,55 @@
+package org.schabi.newpipe.extractor;
+
+import java.io.IOException;
+
+/**
+ * Created by Christian Schabesberger on 25.07.16.
+ *
+ * Copyright (C) Christian Schabesberger 2016
+ * ChannelExtractor.java is part of NewPipe.
+ *
+ * NewPipe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * NewPipe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with NewPipe. If not, see .
+ */
+
+public abstract class ChannelExtractor {
+ private int serviceId;
+ private String url;
+ private UrlIdHandler urlIdHandler;
+ private Downloader downloader;
+ private StreamPreviewInfoCollector previewInfoCollector;
+
+ public ChannelExtractor(UrlIdHandler urlIdHandler, String url, int page, Downloader dl, int serviceId)
+ throws ExtractionException, IOException {
+ this.serviceId = serviceId;
+ this.urlIdHandler = urlIdHandler;
+ previewInfoCollector = new StreamPreviewInfoCollector(urlIdHandler, serviceId);
+ }
+
+ public String getUrl() { return url; }
+ public UrlIdHandler getUrlIdHandler() { return urlIdHandler; }
+ public Downloader getDownloader() { return downloader; }
+ public StreamPreviewInfoCollector getStreamPreviewInfoCollector() {
+ return previewInfoCollector;
+ }
+
+ public abstract String getChannelName() throws ParsingException;
+ public abstract String getAvatarUrl() throws ParsingException;
+ public abstract String getBannerUrl() throws ParsingException;
+ public abstract String getFeedUrl() throws ParsingException;
+ public abstract StreamPreviewInfoCollector getStreams() throws ParsingException;
+ public abstract boolean hasNextPage() throws ParsingException;
+ public int getServiceId() {
+ return serviceId;
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/extractor/ChannelInfo.java b/app/src/main/java/org/schabi/newpipe/extractor/ChannelInfo.java
new file mode 100644
index 000000000..2f856dac0
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/extractor/ChannelInfo.java
@@ -0,0 +1,79 @@
+package org.schabi.newpipe.extractor;
+
+import android.util.Log;
+
+import java.util.List;
+import java.util.Vector;
+
+/**
+ * Created by Christian Schabesberger on 31.07.16.
+ *
+ * Copyright (C) Christian Schabesberger 2016
+ * ChannelInfo.java is part of NewPipe.
+ *
+ * NewPipe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * NewPipe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with NewPipe. If not, see .
+ */
+
+public class ChannelInfo {
+
+
+ public void addException(Exception e) {
+ errors.add(e);
+ }
+
+ public static ChannelInfo getInfo(ChannelExtractor extractor, Downloader dl)
+ throws ParsingException {
+ ChannelInfo info = new ChannelInfo();
+
+ // importand data
+ info.service_id = extractor.getServiceId();
+ info.channel_name = extractor.getChannelName();
+ info.hasNextPage = extractor.hasNextPage();
+
+ try {
+ info.avatar_url = extractor.getAvatarUrl();
+ } catch (Exception e) {
+ info.errors.add(e);
+ }
+ try {
+ info.banner_url = extractor.getBannerUrl();
+ } catch (Exception e) {
+ info.errors.add(e);
+ }
+ try {
+ info.feed_url = extractor.getFeedUrl();
+ } catch(Exception e) {
+ info.errors.add(e);
+ }
+ try {
+ StreamPreviewInfoCollector c = extractor.getStreams();
+ info.related_streams = c.getItemList();
+ info.errors.addAll(c.getErrors());
+ } catch(Exception e) {
+ info.errors.add(e);
+ }
+
+ return info;
+ }
+
+ public int service_id = -1;
+ public String channel_name = "";
+ public String avatar_url = "";
+ public String banner_url = "";
+ public String feed_url = "";
+ public List related_streams = null;
+ public boolean hasNextPage = false;
+
+ public List errors = new Vector<>();
+}
diff --git a/app/src/main/java/org/schabi/newpipe/extractor/Downloader.java b/app/src/main/java/org/schabi/newpipe/extractor/Downloader.java
index a4cde400d..14475dba7 100644
--- a/app/src/main/java/org/schabi/newpipe/extractor/Downloader.java
+++ b/app/src/main/java/org/schabi/newpipe/extractor/Downloader.java
@@ -1,6 +1,7 @@
package org.schabi.newpipe.extractor;
import java.io.IOException;
+import java.util.Map;
/**
* Created by Christian Schabesberger on 28.01.16.
@@ -32,6 +33,14 @@ public interface Downloader {
* @throws IOException*/
String download(String siteUrl, String language) throws IOException;
+ /**Download the text file at the supplied URL as in download(String),
+ * but set the HTTP header field "Accept-Language" to the supplied string.
+ * @param siteUrl the URL of the text file to return the contents of
+ * @param customProperties set request header properties
+ * @return the contents of the specified text file
+ * @throws IOException*/
+ String download(String siteUrl, Map customProperties) throws IOException;
+
/**Download (via HTTP) the text file located at the supplied URL, and return its contents.
* Primarily intended for downloading web pages.
* @param siteUrl the URL of the text file to download
diff --git a/app/src/main/java/org/schabi/newpipe/extractor/SearchEngine.java b/app/src/main/java/org/schabi/newpipe/extractor/SearchEngine.java
index a9c01fc22..3b700f129 100644
--- a/app/src/main/java/org/schabi/newpipe/extractor/SearchEngine.java
+++ b/app/src/main/java/org/schabi/newpipe/extractor/SearchEngine.java
@@ -33,7 +33,7 @@ public abstract class SearchEngine {
private StreamPreviewInfoSearchCollector collector;
- public SearchEngine(StreamUrlIdHandler urlIdHandler, int serviceId) {
+ public SearchEngine(UrlIdHandler urlIdHandler, int serviceId) {
collector = new StreamPreviewInfoSearchCollector(urlIdHandler, serviceId);
}
diff --git a/app/src/main/java/org/schabi/newpipe/extractor/SearchResult.java b/app/src/main/java/org/schabi/newpipe/extractor/SearchResult.java
index 36150d68b..8feccad0c 100644
--- a/app/src/main/java/org/schabi/newpipe/extractor/SearchResult.java
+++ b/app/src/main/java/org/schabi/newpipe/extractor/SearchResult.java
@@ -43,5 +43,5 @@ public class SearchResult {
public String suggestion = "";
public List resultList = new Vector<>();
- public List errors = new Vector<>();
+ public List errors = new Vector<>();
}
diff --git a/app/src/main/java/org/schabi/newpipe/extractor/ServiceList.java b/app/src/main/java/org/schabi/newpipe/extractor/ServiceList.java
index 63ccc5cb2..9571819a9 100644
--- a/app/src/main/java/org/schabi/newpipe/extractor/ServiceList.java
+++ b/app/src/main/java/org/schabi/newpipe/extractor/ServiceList.java
@@ -40,7 +40,7 @@ public class ServiceList {
public static StreamingService[] getServices() {
return services;
}
- public static StreamingService getService(int serviceId) throws ExtractionException {
+ public static StreamingService getService(int serviceId)throws ExtractionException {
for(StreamingService s : services) {
if(s.getServiceId() == serviceId) {
return s;
diff --git a/app/src/main/java/org/schabi/newpipe/extractor/StreamExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/StreamExtractor.java
index eddac4ce5..c3e8c6fcb 100644
--- a/app/src/main/java/org/schabi/newpipe/extractor/StreamExtractor.java
+++ b/app/src/main/java/org/schabi/newpipe/extractor/StreamExtractor.java
@@ -30,7 +30,7 @@ public abstract class StreamExtractor {
private int serviceId;
private String url;
- private StreamUrlIdHandler urlIdHandler;
+ private UrlIdHandler urlIdHandler;
private Downloader downloader;
private StreamPreviewInfoCollector previewInfoCollector;
@@ -55,7 +55,7 @@ public abstract class StreamExtractor {
}
}
- public StreamExtractor(StreamUrlIdHandler urlIdHandler, String url, Downloader dl, int serviceId) {
+ public StreamExtractor(UrlIdHandler urlIdHandler, String url, Downloader dl, int serviceId) {
this.serviceId = serviceId;
this.urlIdHandler = urlIdHandler;
previewInfoCollector = new StreamPreviewInfoCollector(urlIdHandler, serviceId);
@@ -69,7 +69,7 @@ public abstract class StreamExtractor {
return url;
}
- public StreamUrlIdHandler getUrlIdHandler() {
+ public UrlIdHandler getUrlIdHandler() {
return urlIdHandler;
}
@@ -81,6 +81,7 @@ public abstract class StreamExtractor {
public abstract String getTitle() throws ParsingException;
public abstract String getDescription() throws ParsingException;
public abstract String getUploader() throws ParsingException;
+ public abstract String getChannelUrl() throws ParsingException;
public abstract int getLength() throws ParsingException;
public abstract long getViewCount() throws ParsingException;
public abstract String getUploadDate() throws ParsingException;
diff --git a/app/src/main/java/org/schabi/newpipe/extractor/StreamInfo.java b/app/src/main/java/org/schabi/newpipe/extractor/StreamInfo.java
index cd79e463f..9c710114b 100644
--- a/app/src/main/java/org/schabi/newpipe/extractor/StreamInfo.java
+++ b/app/src/main/java/org/schabi/newpipe/extractor/StreamInfo.java
@@ -85,12 +85,12 @@ public class StreamInfo extends AbstractVideoInfo {
/* ---- 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.
- StreamUrlIdHandler uiconv = extractor.getUrlIdHandler();
+ UrlIdHandler uiconv = extractor.getUrlIdHandler();
streamInfo.service_id = extractor.getServiceId();
streamInfo.webpage_url = extractor.getPageUrl();
streamInfo.stream_type = extractor.getStreamType();
- streamInfo.id = uiconv.getVideoId(extractor.getPageUrl());
+ streamInfo.id = uiconv.getId(extractor.getPageUrl());
streamInfo.title = extractor.getTitle();
streamInfo.age_limit = extractor.getAgeLimit();
@@ -188,6 +188,11 @@ public class StreamInfo extends AbstractVideoInfo {
} catch(Exception e) {
streamInfo.addException(e);
}
+ try {
+ streamInfo.channel_url = extractor.getChannelUrl();
+ } catch(Exception e) {
+ streamInfo.addException(e);
+ }
try {
streamInfo.description = extractor.getDescription();
} catch(Exception e) {
@@ -248,7 +253,7 @@ public class StreamInfo extends AbstractVideoInfo {
try {
// get related videos
StreamPreviewInfoCollector c = extractor.getRelatedVideos();
- streamInfo.related_videos = c.getItemList();
+ streamInfo.related_streams = c.getItemList();
streamInfo.errors.addAll(c.getErrors());
} catch(Exception e) {
streamInfo.addException(e);
@@ -258,6 +263,7 @@ public class StreamInfo extends AbstractVideoInfo {
}
public String uploader_thumbnail_url = "";
+ public String channel_url = "";
public String description = "";
public List video_streams = null;
@@ -275,9 +281,9 @@ public class StreamInfo extends AbstractVideoInfo {
public int dislike_count = -1;
public String average_rating = "";
public StreamPreviewInfo next_video = null;
- public List related_videos = null;
+ public List related_streams = null;
//in seconds. some metadata is not passed using a StreamInfo object!
public int start_position = 0;
- public List errors = new Vector<>();
+ public List errors = new Vector<>();
}
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/extractor/StreamPreviewInfoCollector.java b/app/src/main/java/org/schabi/newpipe/extractor/StreamPreviewInfoCollector.java
index 013bbbc1d..676e0988c 100644
--- a/app/src/main/java/org/schabi/newpipe/extractor/StreamPreviewInfoCollector.java
+++ b/app/src/main/java/org/schabi/newpipe/extractor/StreamPreviewInfoCollector.java
@@ -27,11 +27,11 @@ import java.util.Vector;
public class StreamPreviewInfoCollector {
private List itemList = new Vector<>();
- private List errors = new Vector<>();
- private StreamUrlIdHandler urlIdHandler;
+ private List errors = new Vector<>();
+ private UrlIdHandler urlIdHandler;
private int serviceId = -1;
- public StreamPreviewInfoCollector(StreamUrlIdHandler handler, int serviceId) {
+ public StreamPreviewInfoCollector(UrlIdHandler handler, int serviceId) {
urlIdHandler = handler;
this.serviceId = serviceId;
}
@@ -40,7 +40,7 @@ public class StreamPreviewInfoCollector {
return itemList;
}
- public List getErrors() {
+ public List getErrors() {
return errors;
}
@@ -57,7 +57,7 @@ public class StreamPreviewInfoCollector {
if (urlIdHandler == null) {
throw new ParsingException("Error: UrlIdHandler not set");
} else if(!resultItem.webpage_url.isEmpty()) {
- resultItem.id = (new YoutubeStreamUrlIdHandler()).getVideoId(resultItem.webpage_url);
+ resultItem.id = (new YoutubeStreamUrlIdHandler()).getId(resultItem.webpage_url);
}
resultItem.title = extractor.getTitle();
resultItem.stream_type = extractor.getStreamType();
diff --git a/app/src/main/java/org/schabi/newpipe/extractor/StreamPreviewInfoSearchCollector.java b/app/src/main/java/org/schabi/newpipe/extractor/StreamPreviewInfoSearchCollector.java
index e0f02f461..905543be0 100644
--- a/app/src/main/java/org/schabi/newpipe/extractor/StreamPreviewInfoSearchCollector.java
+++ b/app/src/main/java/org/schabi/newpipe/extractor/StreamPreviewInfoSearchCollector.java
@@ -24,7 +24,7 @@ public class StreamPreviewInfoSearchCollector extends StreamPreviewInfoCollector
private String suggestion = "";
- public StreamPreviewInfoSearchCollector(StreamUrlIdHandler handler, int serviceId) {
+ public StreamPreviewInfoSearchCollector(UrlIdHandler handler, int serviceId) {
super(handler, serviceId);
}
diff --git a/app/src/main/java/org/schabi/newpipe/extractor/StreamingService.java b/app/src/main/java/org/schabi/newpipe/extractor/StreamingService.java
index de9a4ebfe..1805b48db 100644
--- a/app/src/main/java/org/schabi/newpipe/extractor/StreamingService.java
+++ b/app/src/main/java/org/schabi/newpipe/extractor/StreamingService.java
@@ -38,7 +38,10 @@ public abstract class StreamingService {
public abstract StreamExtractor getExtractorInstance(String url, Downloader downloader)
throws IOException, ExtractionException;
public abstract SearchEngine getSearchEngineInstance(Downloader downloader);
- public abstract StreamUrlIdHandler getUrlIdHandlerInstance();
+ public abstract UrlIdHandler getUrlIdHandlerInstance();
+ public abstract UrlIdHandler getChannelUrlIdHandlerInstance();
+ public abstract ChannelExtractor getChannelExtractorInstance(String url, int page, Downloader downloader)
+ throws ExtractionException, IOException;
public final int getServiceId() {
return serviceId;
diff --git a/app/src/main/java/org/schabi/newpipe/extractor/StreamUrlIdHandler.java b/app/src/main/java/org/schabi/newpipe/extractor/UrlIdHandler.java
similarity index 82%
rename from app/src/main/java/org/schabi/newpipe/extractor/StreamUrlIdHandler.java
rename to app/src/main/java/org/schabi/newpipe/extractor/UrlIdHandler.java
index 4f91ac80e..f38f60658 100644
--- a/app/src/main/java/org/schabi/newpipe/extractor/StreamUrlIdHandler.java
+++ b/app/src/main/java/org/schabi/newpipe/extractor/UrlIdHandler.java
@@ -1,10 +1,10 @@
package org.schabi.newpipe.extractor;
/**
- * Created by Christian Schabesberger on 02.02.16.
+ * Created by Christian Schabesberger on 26.07.16.
*
* Copyright (C) Christian Schabesberger 2016
- * StreamUrlIdHandler.java is part of NewPipe.
+ * UrlIdHandler.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,9 +20,9 @@ package org.schabi.newpipe.extractor;
* along with NewPipe. If not, see .
*/
-public interface StreamUrlIdHandler {
- String getVideoUrl(String videoId);
- String getVideoId(String siteUrl) throws ParsingException;
+public interface UrlIdHandler {
+ String getUrl(String videoId);
+ String getId(String siteUrl) throws ParsingException;
String cleanUrl(String siteUrl) throws ParsingException;
/**When a VIEW_ACTION is caught this function will test if the url delivered within the calling
diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractor.java
new file mode 100644
index 000000000..d4653d811
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelExtractor.java
@@ -0,0 +1,331 @@
+package org.schabi.newpipe.extractor.services.youtube;
+
+
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.jsoup.Jsoup;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.schabi.newpipe.extractor.AbstractVideoInfo;
+import org.schabi.newpipe.extractor.ChannelExtractor;
+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.StreamPreviewInfoCollector;
+import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
+import org.schabi.newpipe.extractor.UrlIdHandler;
+
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Created by Christian Schabesberger on 25.07.16.
+ *
+ * Copyright (C) Christian Schabesberger 2016
+ * YoutubeChannelExtractor.java is part of NewPipe.
+ *
+ * NewPipe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * NewPipe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with NewPipe. If not, see .
+ */
+
+public class YoutubeChannelExtractor extends ChannelExtractor {
+
+ private static final String TAG = YoutubeChannelExtractor.class.toString();
+
+ // private CSSOMParser cssParser = new CSSOMParser(new SACParserCSS3());
+
+ private Downloader downloader;
+ private Document doc = null;
+
+ private boolean isAjaxPage = false;
+ private static String userUrl = "";
+ private static String channelName = "";
+ private static String avatarUrl = "";
+ private static String bannerUrl = "";
+ private static String feedUrl = "";
+ // the fist page is html all other pages are ajax. Every new page can be requested by sending
+ // this request url.
+ private static String nextPageUrl = "";
+
+ public YoutubeChannelExtractor(UrlIdHandler urlIdHandler, String url, int page, Downloader dl, int serviceId)
+ throws ExtractionException, IOException {
+ super(urlIdHandler, url, page, dl, serviceId);
+
+ url = urlIdHandler.cleanUrl(url) ; //+ "/video?veiw=0&flow=list&sort=dd";
+ downloader = dl;
+
+ if(page == 0) {
+ if (isUserUrl(url)) {
+ userUrl = url;
+ } else {
+ // we first need to get the user url. Otherwise we can't find videos
+ String channelPageContent = downloader.download(url);
+ Document channelDoc = Jsoup.parse(channelPageContent, url);
+ userUrl = getUserUrl(channelDoc);
+ }
+
+ userUrl = userUrl + "/videos?veiw=0&flow=list&sort=dd&live_view=10000";
+ String pageContent = downloader.download(userUrl);
+ doc = Jsoup.parse(pageContent, userUrl);
+ nextPageUrl = getNextPageUrl(doc);
+ isAjaxPage = false;
+ } else {
+ String ajaxDataRaw = downloader.download(nextPageUrl);
+ JSONObject ajaxData;
+ try {
+ ajaxData = new JSONObject(ajaxDataRaw);
+ String htmlDataRaw = ajaxData.getString("content_html");
+ doc = Jsoup.parse(htmlDataRaw, nextPageUrl);
+
+ String nextPageHtmlDataRaw = ajaxData.getString("load_more_widget_html");
+ if(!nextPageHtmlDataRaw.isEmpty()) {
+ Document nextPageData = Jsoup.parse(nextPageHtmlDataRaw, nextPageUrl);
+ nextPageUrl = getNextPageUrl(nextPageData);
+ } else {
+ nextPageUrl = "";
+ }
+ } catch (JSONException e) {
+ throw new ParsingException("Could not parse json data for next page", e);
+ }
+ isAjaxPage = true;
+ }
+ }
+
+ @Override
+ public String getChannelName() throws ParsingException {
+ try {
+ if(!isAjaxPage) {
+ channelName = doc.select("span[class=\"qualified-channel-title-text\"]").first()
+ .select("a").first().text();
+ }
+ return channelName;
+ } catch(Exception e) {
+ throw new ParsingException("Could not get channel name");
+ }
+ }
+
+ @Override
+ public String getAvatarUrl() throws ParsingException {
+ try {
+ if(!isAjaxPage) {
+ avatarUrl = doc.select("img[class=\"channel-header-profile-image\"]")
+ .first().attr("abs:src");
+ }
+ return avatarUrl;
+ } catch(Exception e) {
+ throw new ParsingException("Could not get avatar", e);
+ }
+ }
+
+ @Override
+ public String getBannerUrl() throws ParsingException {
+ try {
+ if(!isAjaxPage) {
+ Element el = doc.select("div[id=\"gh-banner\"]").first().select("style").first();
+ String cssContent = el.html();
+ String url = "https:" + Parser.matchGroup1("url\\((.*)\\)", cssContent);
+ if (url.contains("s.ytimg.com")) {
+ bannerUrl = null;
+ } else {
+ bannerUrl = url;
+ }
+ }
+ return bannerUrl;
+ } catch(Exception e) {
+ throw new ParsingException("Could not get Banner", e);
+ }
+ }
+
+ @Override
+ public StreamPreviewInfoCollector getStreams() throws ParsingException {
+ StreamPreviewInfoCollector collector = getStreamPreviewInfoCollector();
+ Element ul = null;
+ if(isAjaxPage) {
+ ul = doc.select("body").first();
+ } else {
+ ul = doc.select("ul[id=\"browse-items-primary\"]").first();
+ }
+
+ for(final Element li : ul.children()) {
+ if (li.select("div[class=\"feed-item-dismissable\"]").first() != null) {
+ collector.commit(new StreamPreviewInfoExtractor() {
+ @Override
+ public AbstractVideoInfo.StreamType getStreamType() throws ParsingException {
+ return AbstractVideoInfo.StreamType.VIDEO_STREAM;
+ }
+
+ @Override
+ public String getWebPageUrl() throws ParsingException {
+ try {
+ Element el = li.select("div[class=\"feed-item-dismissable\"]").first();
+ Element dl = el.select("h3").first().select("a").first();
+ return dl.attr("abs:href");
+ } catch (Exception e) {
+ throw new ParsingException("Could not get web page url for the video", e);
+ }
+ }
+
+ @Override
+ public String getTitle() throws ParsingException {
+ try {
+ Element el = li.select("div[class=\"feed-item-dismissable\"]").first();
+ Element dl = el.select("h3").first().select("a").first();
+ return dl.text();
+ } catch (Exception e) {
+ throw new ParsingException("Could not get title", e);
+ }
+ }
+
+ @Override
+ public int getDuration() throws ParsingException {
+ try {
+ return YoutubeParsingHelper.parseDurationString(
+ li.select("span[class=\"video-time\"]").first().text());
+ } catch(Exception e) {
+ if(isLiveStream(li)) {
+ // -1 for no duration
+ return -1;
+ } else {
+ throw new ParsingException("Could not get Duration: " + getTitle(), e);
+ }
+ }
+ }
+
+ @Override
+ public String getUploader() throws ParsingException {
+ return getChannelName();
+ }
+
+ @Override
+ public String getUploadDate() throws ParsingException {
+ try {
+ return li.select("div[class=\"yt-lockup-meta\"]").first()
+ .select("li").first()
+ .text();
+ } catch(Exception e) {
+ throw new ParsingException("Could not get uplaod date", e);
+ }
+ }
+
+ @Override
+ public long getViewCount() throws ParsingException {
+ String output;
+ String input;
+ try {
+ input = li.select("div[class=\"yt-lockup-meta\"]").first()
+ .select("li").get(1)
+ .text();
+ } catch (IndexOutOfBoundsException e) {
+ if(isLiveStream(li)) {
+ // -1 for no view count
+ return -1;
+ } else {
+ throw new ParsingException(
+ "Could not parse yt-lockup-meta although available: " + getTitle(), e);
+ }
+ }
+
+ output = Parser.matchGroup1("([0-9,\\. ]*)", input)
+ .replace(" ", "")
+ .replace(".", "")
+ .replace(",", "");
+
+ 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);
+ }
+ }
+ }
+
+ @Override
+ public String getThumbnailUrl() throws ParsingException {
+ try {
+ String url;
+ Element te = li.select("span[class=\"yt-thumb-clip\"]").first()
+ .select("img").first();
+ url = te.attr("abs:src");
+ // Sometimes youtube sends links to gif files which somehow seem to not exist
+ // anymore. Items with such gif also offer a secondary image source. So we are going
+ // to use that if we've caught such an item.
+ if (url.contains(".gif")) {
+ url = te.attr("abs:data-thumb");
+ }
+ return url;
+ } catch (Exception e) {
+ throw new ParsingException("Could not get thumbnail url", e);
+ }
+ }
+
+ private boolean isLiveStream(Element item) {
+ Element bla = item.select("span[class*=\"yt-badge-live\"]").first();
+
+ if(bla == null) {
+ // sometimes livestreams dont have badges but sill are live streams
+ // if video time is not available we most likly have an offline livestream
+ if(item.select("span[class*=\"video-time\"]").first() == null) {
+ return true;
+ }
+ }
+ return bla != null;
+ }
+ });
+ }
+ }
+
+ return collector;
+ }
+
+ @Override
+ public String getFeedUrl() throws ParsingException {
+ try {
+ if(!isAjaxPage) {
+ feedUrl = doc.select("link[title=\"RSS\"]").first().attr("abs:href");
+ }
+ return feedUrl;
+ } catch(Exception e) {
+ throw new ParsingException("Could not get feed url", e);
+ }
+ }
+
+ @Override
+ public boolean hasNextPage() throws ParsingException {
+ return !nextPageUrl.isEmpty();
+ }
+
+ private String getUserUrl(Document d) throws ParsingException {
+ return d.select("span[class=\"qualified-channel-title-text\"]").first()
+ .select("a").first().attr("abs:href");
+ }
+
+ private boolean isUserUrl(String url) throws ParsingException {
+ return url.contains("/user/");
+ }
+
+ private String getNextPageUrl(Document d) throws ParsingException {
+ try {
+ Element button = d.select("button[class*=\"yt-uix-load-more\"]").first();
+ return button.attr("abs:data-uix-load-more-href");
+ } catch(Exception e) {
+ throw new ParsingException("could not load next page url", e);
+ }
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelUrlIdHandler.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelUrlIdHandler.java
new file mode 100644
index 000000000..5f6f85ae6
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeChannelUrlIdHandler.java
@@ -0,0 +1,47 @@
+package org.schabi.newpipe.extractor.services.youtube;
+
+import org.schabi.newpipe.extractor.Parser;
+import org.schabi.newpipe.extractor.ParsingException;
+import org.schabi.newpipe.extractor.UrlIdHandler;
+
+/**
+ * Created by Christian Schabesberger on 25.07.16.
+ *
+ * Copyright (C) Christian Schabesberger 2016
+ * YoutubeChannelUrlIdHandler.java is part of NewPipe.
+ *
+ * NewPipe is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * NewPipe is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with NewPipe. If not, see .
+ */
+
+public class YoutubeChannelUrlIdHandler implements UrlIdHandler {
+
+ public String getUrl(String channelId) {
+ return "https://www.youtube.com/" + channelId;
+ }
+
+ public String getId(String siteUrl) throws ParsingException {
+ return Parser.matchGroup1("/(user/[A-Za-z0-9_-]*|channel/[A-Za-z0-9_-]*)", siteUrl);
+ }
+
+ public String cleanUrl(String siteUrl) throws ParsingException {
+ return getUrl(getId(siteUrl));
+ }
+
+ public boolean acceptUrl(String videoUrl) {
+ return (videoUrl.contains("youtube") ||
+ videoUrl.contains("youtu.be")) &&
+ ( videoUrl.contains("/user/") ||
+ videoUrl.contains("/channel/"));
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngine.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngine.java
index 249814248..5e84eac77 100644
--- a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngine.java
+++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeSearchEngine.java
@@ -14,7 +14,7 @@ import org.schabi.newpipe.extractor.SearchEngine;
import org.schabi.newpipe.extractor.StreamPreviewInfoCollector;
import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
import org.schabi.newpipe.extractor.StreamPreviewInfoSearchCollector;
-import org.schabi.newpipe.extractor.StreamUrlIdHandler;
+import org.schabi.newpipe.extractor.UrlIdHandler;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
@@ -55,7 +55,7 @@ public class YoutubeSearchEngine extends SearchEngine {
private static final String TAG = YoutubeSearchEngine.class.toString();
public static final String CHARSET_UTF_8 = "UTF-8";
- public YoutubeSearchEngine(StreamUrlIdHandler urlIdHandler, int serviceId) {
+ public YoutubeSearchEngine(UrlIdHandler urlIdHandler, int serviceId) {
super(urlIdHandler, serviceId);
}
@@ -91,7 +91,6 @@ public class YoutubeSearchEngine extends SearchEngine {
site = downloader.download(url);
}
-
Document doc = Jsoup.parse(site, url);
Element list = doc.select("ol[class=\"item-section\"]").first();
diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java
index f2d56bcab..9c2198926 100644
--- a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java
+++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeService.java
@@ -1,10 +1,11 @@
package org.schabi.newpipe.extractor.services.youtube;
+import org.schabi.newpipe.extractor.ChannelExtractor;
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.StreamUrlIdHandler;
+import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.SearchEngine;
import java.io.IOException;
@@ -45,7 +46,7 @@ public class YoutubeService extends StreamingService {
@Override
public StreamExtractor getExtractorInstance(String url, Downloader downloader)
throws ExtractionException, IOException {
- StreamUrlIdHandler urlIdHandler = new YoutubeStreamUrlIdHandler();
+ UrlIdHandler urlIdHandler = new YoutubeStreamUrlIdHandler();
if(urlIdHandler.acceptUrl(url)) {
return new YoutubeStreamExtractor(urlIdHandler, url, downloader, getServiceId());
}
@@ -59,7 +60,18 @@ public class YoutubeService extends StreamingService {
}
@Override
- public StreamUrlIdHandler getUrlIdHandlerInstance() {
+ public UrlIdHandler getUrlIdHandlerInstance() {
return new YoutubeStreamUrlIdHandler();
}
+
+ @Override
+ public UrlIdHandler getChannelUrlIdHandlerInstance() {
+ return new YoutubeChannelUrlIdHandler();
+ }
+
+ @Override
+ public ChannelExtractor getChannelExtractorInstance(String url, int page, Downloader downloader)
+ throws ExtractionException, IOException {
+ return new YoutubeChannelExtractor(getChannelUrlIdHandlerInstance(), url, page, downloader, getServiceId());
+ }
}
diff --git a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java
index afdaff3a9..aac94b355 100644
--- a/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java
+++ b/app/src/main/java/org/schabi/newpipe/extractor/services/youtube/YoutubeStreamExtractor.java
@@ -15,10 +15,9 @@ import org.schabi.newpipe.extractor.Downloader;
import org.schabi.newpipe.extractor.Parser;
import org.schabi.newpipe.extractor.ParsingException;
import org.schabi.newpipe.extractor.StreamInfo;
-import org.schabi.newpipe.extractor.StreamPreviewInfo;
import org.schabi.newpipe.extractor.StreamPreviewInfoCollector;
import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
-import org.schabi.newpipe.extractor.StreamUrlIdHandler;
+import org.schabi.newpipe.extractor.UrlIdHandler;
import org.schabi.newpipe.extractor.StreamExtractor;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.VideoStream;
@@ -183,12 +182,12 @@ public class YoutubeStreamExtractor extends StreamExtractor {
// cached values
private static volatile String decryptionCode = "";
- StreamUrlIdHandler urlidhandler = new YoutubeStreamUrlIdHandler();
+ UrlIdHandler urlidhandler = new YoutubeStreamUrlIdHandler();
String pageUrl = "";
private Downloader downloader;
- public YoutubeStreamExtractor(StreamUrlIdHandler urlIdHandler, String pageUrl,
+ public YoutubeStreamExtractor(UrlIdHandler urlIdHandler, String pageUrl,
Downloader dl, int serviceId)
throws ExtractionException, IOException {
super(urlIdHandler ,pageUrl, dl, serviceId);
@@ -203,7 +202,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
// Check if the video is age restricted
if (pageContent.contains(".
*/
-public class YoutubeStreamUrlIdHandler implements StreamUrlIdHandler {
+public class YoutubeStreamUrlIdHandler implements UrlIdHandler {
@SuppressWarnings("WeakerAccess")
@Override
- public String getVideoUrl(String videoId) {
+ public String getUrl(String videoId) {
return "https://www.youtube.com/watch?v=" + videoId;
}
@SuppressWarnings("WeakerAccess")
@Override
- public String getVideoId(String url) throws ParsingException, IllegalArgumentException {
+ public String getId(String url) throws ParsingException, IllegalArgumentException {
if(url.isEmpty())
{
throw new IllegalArgumentException("The url parameter should not be empty");
@@ -81,7 +81,7 @@ public class YoutubeStreamUrlIdHandler implements StreamUrlIdHandler {
}
public String cleanUrl(String complexUrl) throws ParsingException {
- return getVideoUrl(getVideoId(complexUrl));
+ return getUrl(getId(complexUrl));
}
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemHolder.java
new file mode 100644
index 000000000..0e3374dcc
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemHolder.java
@@ -0,0 +1,34 @@
+package org.schabi.newpipe.info_list;
+
+import android.support.v7.widget.RecyclerView;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.schabi.newpipe.R;
+
+/**
+ * Created by the-scrabi on 01.08.16.
+ */
+public class InfoItemHolder extends RecyclerView.ViewHolder {
+
+ public ImageView itemThumbnailView;
+ public TextView itemVideoTitleView,
+ itemUploaderView,
+ itemDurationView,
+ itemUploadDateView,
+ itemViewCountView;
+ public Button itemButton;
+
+ public InfoItemHolder(View v) {
+ super(v);
+ itemThumbnailView = (ImageView) v.findViewById(R.id.itemThumbnailView);
+ itemVideoTitleView = (TextView) v.findViewById(R.id.itemVideoTitleView);
+ itemUploaderView = (TextView) v.findViewById(R.id.itemUploaderView);
+ itemDurationView = (TextView) v.findViewById(R.id.itemDurationView);
+ itemUploadDateView = (TextView) v.findViewById(R.id.itemUploadDateView);
+ itemViewCountView = (TextView) v.findViewById(R.id.itemViewCountView);
+ itemButton = (Button) v.findViewById(R.id.item_button);
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
similarity index 55%
rename from app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java
rename to app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
index 4b7c3c332..365ed246e 100644
--- a/app/src/main/java/org/schabi/newpipe/VideoInfoItemViewCreator.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoListAdapter.java
@@ -1,73 +1,79 @@
-package org.schabi.newpipe;
+package org.schabi.newpipe.info_list;
+import android.app.Activity;
+import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.TextView;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
+import org.schabi.newpipe.ImageErrorLoadingListener;
+import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.AbstractVideoInfo;
import org.schabi.newpipe.extractor.StreamPreviewInfo;
+import java.util.List;
+import java.util.Vector;
+
/**
- * Created by Christian Schabesberger on 24.10.15.
- *
- * Copyright (C) Christian Schabesberger 2015
- * VideoInfoItemViewCreator.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 .
+ * Created by the-scrabi on 01.08.16.
*/
+public class InfoListAdapter extends RecyclerView.Adapter {
-public class VideoInfoItemViewCreator {
- private final LayoutInflater inflater;
- private ImageLoader imageLoader = ImageLoader.getInstance();
- private DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().cacheInMemory(true).build();
-
- public VideoInfoItemViewCreator(LayoutInflater inflater) {
- this.inflater = inflater;
+ public interface OnItemSelectedListener {
+ void selected(String url);
}
- public View getViewFromVideoInfoItem(View convertView, ViewGroup parent, StreamPreviewInfo info) {
- ViewHolder holder;
+ private Activity activity = null;
+ private View rootView = null;
+ private List streamList = new Vector<>();
+ private ImageLoader imageLoader = ImageLoader.getInstance();
+ private DisplayImageOptions displayImageOptions =
+ new DisplayImageOptions.Builder().cacheInMemory(true).build();
+ private OnItemSelectedListener onItemSelectedListener;
- // generate holder
- if(convertView == null) {
- convertView = inflater.inflate(R.layout.video_item, parent, false);
- holder = new ViewHolder();
- holder.itemThumbnailView = (ImageView) convertView.findViewById(R.id.itemThumbnailView);
- holder.itemVideoTitleView = (TextView) convertView.findViewById(R.id.itemVideoTitleView);
- holder.itemUploaderView = (TextView) convertView.findViewById(R.id.itemUploaderView);
- holder.itemDurationView = (TextView) convertView.findViewById(R.id.itemDurationView);
- holder.itemUploadDateView = (TextView) convertView.findViewById(R.id.itemUploadDateView);
- holder.itemViewCountView = (TextView) convertView.findViewById(R.id.itemViewCountView);
- convertView.setTag(holder);
- } else {
- holder = (ViewHolder) convertView.getTag();
+
+
+ public InfoListAdapter(Activity a, View rootView) {
+ activity = a;
+ this.rootView = rootView;
+ }
+
+ public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {
+ this.onItemSelectedListener = onItemSelectedListener;
+ }
+
+ public void addStreamItemList(List videos) {
+ if(videos!= null) {
+ streamList.addAll(videos);
+ notifyDataSetChanged();
}
+ }
- // fill with information
+ public void clearSteamItemList() {
+ streamList = new Vector<>();
+ notifyDataSetChanged();
+ }
- /*
- if(info.thumbnail == null) {
- holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
- } else {
- holder.itemThumbnailView.setImageBitmap(info.thumbnail);
- }
- */
+ @Override
+ public int getItemCount() {
+ return streamList.size();
+ }
+
+ @Override
+ public InfoItemHolder onCreateViewHolder(ViewGroup parent, int i) {
+ View itemView = LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.video_item, parent, false);
+
+ return new InfoItemHolder(itemView);
+ }
+
+ @Override
+ public void onBindViewHolder(InfoItemHolder holder, int i) {
+ final StreamPreviewInfo info = streamList.get(i);
+ // fill holder with information
holder.itemVideoTitleView.setText(info.title);
if(info.uploader != null && !info.uploader.isEmpty()) {
holder.itemUploaderView.setText(info.uploader);
@@ -94,18 +100,22 @@ public class VideoInfoItemViewCreator {
holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
- imageLoader.displayImage(info.thumbnail_url, holder.itemThumbnailView, displayImageOptions);
+ imageLoader.displayImage(info.thumbnail_url,
+ holder.itemThumbnailView,
+ displayImageOptions,
+ new ImageErrorLoadingListener(activity, rootView, info.service_id));
}
- return convertView;
+ holder.itemButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ onItemSelectedListener.selected(info.webpage_url);
+ }
+ });
}
- private class ViewHolder {
- public ImageView itemThumbnailView;
- public TextView itemVideoTitleView, itemUploaderView, itemDurationView, itemUploadDateView, itemViewCountView;
- }
- private String shortViewCount(Long viewCount){
+ public static String shortViewCount(Long viewCount){
if(viewCount >= 1000000000){
return Long.toString(viewCount/1000000000)+"B views";
}else if(viewCount>=1000000){
diff --git a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
index e283d5e05..5b4f246d0 100644
--- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
@@ -23,8 +23,8 @@ import android.widget.Toast;
import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.VideoItemDetailActivity;
-import org.schabi.newpipe.VideoItemDetailFragment;
+import org.schabi.newpipe.detail.VideoItemDetailActivity;
+import org.schabi.newpipe.detail.VideoItemDetailFragment;
import java.io.IOException;
diff --git a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java b/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java
new file mode 100644
index 000000000..99d2a57dd
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java
@@ -0,0 +1,265 @@
+package org.schabi.newpipe.search_fragment;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.SearchView;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.ProgressBar;
+import android.widget.Toast;
+
+import org.schabi.newpipe.ErrorActivity;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.detail.VideoItemDetailActivity;
+import org.schabi.newpipe.detail.VideoItemDetailFragment;
+import org.schabi.newpipe.extractor.SearchResult;
+import org.schabi.newpipe.extractor.ServiceList;
+import org.schabi.newpipe.info_list.InfoListAdapter;
+
+/**
+ * Created by Christian Schabesberger on 02.08.16.
+ */
+
+public class SearchInfoItemFragment extends Fragment {
+
+ private static final String TAG = SearchInfoItemFragment.class.toString();
+
+ public class SearchQueryListener implements SearchView.OnQueryTextListener {
+
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ Activity a = getActivity();
+ try {
+ searchQuery = query;
+ search(query);
+
+ // hide virtual keyboard
+ InputMethodManager inputManager =
+ (InputMethodManager) a.getSystemService(Context.INPUT_METHOD_SERVICE);
+ try {
+ //noinspection ConstantConditions
+ inputManager.hideSoftInputFromWindow(
+ a.getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
+ } catch(NullPointerException e) {
+ e.printStackTrace();
+ ErrorActivity.reportError(a, e, null,
+ a.findViewById(android.R.id.content),
+ ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
+ ServiceList.getNameOfService(streamingServiceId),
+ "Could not get widget with focus", R.string.general_error));
+ }
+ // clear focus
+ // 1. to not open up the keyboard after switching back to this
+ // 2. It's a workaround to a seeming bug by the Android OS it self, causing
+ // onQueryTextSubmit to trigger twice when focus is not cleared.
+ // See: http://stackoverflow.com/questions/17874951/searchview-onquerytextsubmit-runs-twice-while-i-pressed-once
+ a.getCurrentFocus().clearFocus();
+ } catch(Exception e) {
+ e.printStackTrace();
+ }
+ View bg = a.findViewById(R.id.mainBG);
+ bg.setVisibility(View.GONE);
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newText) {
+ if(!newText.isEmpty()) {
+ searchSuggestions(newText);
+ }
+ return true;
+ }
+ }
+
+ private int streamingServiceId = -1;
+ private String searchQuery = "";
+ private boolean isLoading = false;
+
+ private ProgressBar loadingIndicator = null;
+ private SearchView searchView = null;
+ private int pageNumber = 0;
+ private SuggestionListAdapter suggestionListAdapter = null;
+ private InfoListAdapter infoListAdapter = null;
+ private LinearLayoutManager streamInfoListLayoutManager = null;
+ private RecyclerView recyclerView = null;
+
+ // savedInstanceBundle arguments
+ private static final String QUERY = "query";
+ private static final String STREAMING_SERVICE = "streaming_service";
+
+ /**
+ * Mandatory empty constructor for the fragment manager to instantiate the
+ * fragment (e.g. upon screen orientation changes).
+ */
+ public SearchInfoItemFragment() {
+ }
+
+ // TODO: Customize parameter initialization
+ @SuppressWarnings("unused")
+ public static SearchInfoItemFragment newInstance(int columnCount) {
+ SearchInfoItemFragment fragment = new SearchInfoItemFragment();
+ return fragment;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if(savedInstanceState != null) {
+ searchQuery = savedInstanceState.getString(QUERY);
+ streamingServiceId = savedInstanceState.getInt(STREAMING_SERVICE);
+ } else {
+ try {
+ streamingServiceId = ServiceList.getIdOfService("Youtube");
+ } catch(Exception e) {
+ e.printStackTrace();
+ ErrorActivity.reportError(getActivity(), e, null,
+ getActivity().findViewById(android.R.id.content),
+ ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
+ ServiceList.getNameOfService(streamingServiceId),
+ "", R.string.general_error));
+ }
+ }
+
+ SearchWorker sw = SearchWorker.getInstance();
+ sw.setSearchWorkerResultListner(new SearchWorker.SearchWorkerResultListner() {
+ @Override
+ public void onResult(SearchResult result) {
+ infoListAdapter.addStreamItemList(result.resultList);
+ isLoading = false;
+ loadingIndicator.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onNothingFound(int stringResource) {
+ //setListShown(true);
+ Toast.makeText(getActivity(), getString(stringResource),
+ Toast.LENGTH_SHORT).show();
+ isLoading = false;
+ loadingIndicator.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onError(String message) {
+ //setListShown(true);
+ Toast.makeText(getActivity(), message,
+ Toast.LENGTH_LONG).show();
+ isLoading = false;
+ loadingIndicator.setVisibility(View.GONE);
+ }
+ });
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_searchinfoitem, container, false);
+
+ Context context = view.getContext();
+ loadingIndicator = (ProgressBar) view.findViewById(R.id.progressBar);
+ recyclerView = (RecyclerView) view.findViewById(R.id.list);
+ streamInfoListLayoutManager = new LinearLayoutManager(context);
+ recyclerView.setLayoutManager(streamInfoListLayoutManager);
+
+ infoListAdapter = new InfoListAdapter(getActivity(),
+ getActivity().findViewById(android.R.id.content));
+ infoListAdapter.setOnItemSelectedListener(new InfoListAdapter.OnItemSelectedListener() {
+ @Override
+ public void selected(String url) {
+ Intent i = new Intent(getActivity(), VideoItemDetailActivity.class);
+ i.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
+ i.putExtra(VideoItemDetailFragment.VIDEO_URL, url);
+ getActivity().startActivity(i);
+ }
+ });
+ recyclerView.setAdapter(infoListAdapter);
+
+ recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ int pastVisiblesItems, visibleItemCount, totalItemCount;
+ super.onScrolled(recyclerView, dx, dy);
+ if(dy > 0) //check for scroll down
+ {
+ visibleItemCount = streamInfoListLayoutManager.getChildCount();
+ totalItemCount = streamInfoListLayoutManager.getItemCount();
+ pastVisiblesItems = streamInfoListLayoutManager.findFirstVisibleItemPosition();
+
+ if ( (visibleItemCount + pastVisiblesItems) >= totalItemCount && !isLoading)
+ {
+ pageNumber++;
+ search(searchQuery, pageNumber);
+ }
+ }
+ }
+ });
+
+ return view;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ }
+
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.search_menu, menu);
+
+ MenuItem searchItem = menu.findItem(R.id.action_search);
+ searchView = (SearchView) searchItem.getActionView();
+ setupSearchView(searchView);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void setupSearchView(SearchView searchView) {
+ suggestionListAdapter = new SuggestionListAdapter(getActivity());
+ searchView.setSuggestionsAdapter(suggestionListAdapter);
+ searchView.setOnSuggestionListener(new SearchSuggestionListener(searchView, suggestionListAdapter));
+ searchView.setOnQueryTextListener(new SearchQueryListener());
+ if(searchQuery != null && !searchQuery.isEmpty()) {
+ searchView.setQuery(searchQuery, false);
+ searchView.setIconifiedByDefault(false);
+ }
+ }
+
+ private void search(String query) {
+ infoListAdapter.clearSteamItemList();
+ pageNumber = 0;
+ search(query, pageNumber);
+ loadingIndicator.setVisibility(View.VISIBLE);
+ }
+
+ private void search(String query, int page) {
+ isLoading = true;
+ SearchWorker sw = SearchWorker.getInstance();
+ sw.search(streamingServiceId, query, page, getActivity());
+ }
+
+ private void searchSuggestions(String query) {
+ SuggestionSearchRunnable suggestionSearchRunnable =
+ new SuggestionSearchRunnable(streamingServiceId, query, getActivity(), suggestionListAdapter);
+ Thread suggestionThread = new Thread(suggestionSearchRunnable);
+ suggestionThread.start();
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchSuggestionListener.java b/app/src/main/java/org/schabi/newpipe/search_fragment/SearchSuggestionListener.java
new file mode 100644
index 000000000..833af8521
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/search_fragment/SearchSuggestionListener.java
@@ -0,0 +1,32 @@
+package org.schabi.newpipe.search_fragment;
+
+import android.support.v7.widget.SearchView;
+
+/**
+ * Created by the-scrabi on 02.08.16.
+ */
+
+public class SearchSuggestionListener implements SearchView.OnSuggestionListener{
+
+ private SearchView searchView;
+ private SuggestionListAdapter adapter;
+
+ public SearchSuggestionListener(SearchView searchView, SuggestionListAdapter adapter) {
+ this.searchView = searchView;
+ this.adapter = adapter;
+ }
+
+ @Override
+ public boolean onSuggestionSelect(int position) {
+ String suggestion = adapter.getSuggestion(position);
+ searchView.setQuery(suggestion,true);
+ return false;
+ }
+
+ @Override
+ public boolean onSuggestionClick(int position) {
+ String suggestion = adapter.getSuggestion(position);
+ searchView.setQuery(suggestion,true);
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchWorker.java b/app/src/main/java/org/schabi/newpipe/search_fragment/SearchWorker.java
new file mode 100644
index 000000000..d14cdbd41
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/search_fragment/SearchWorker.java
@@ -0,0 +1,174 @@
+package org.schabi.newpipe.search_fragment;
+
+import android.app.Activity;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.Toast;
+
+import org.schabi.newpipe.Downloader;
+import org.schabi.newpipe.ErrorActivity;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.ExtractionException;
+import org.schabi.newpipe.extractor.SearchEngine;
+import org.schabi.newpipe.extractor.SearchResult;
+import org.schabi.newpipe.extractor.ServiceList;
+
+import java.io.IOException;
+
+/**
+ * Created by the-scrabi on 02.08.16.
+ */
+
+
+public class SearchWorker {
+ private static final String TAG = SearchWorker.class.toString();
+
+ public interface SearchWorkerResultListner {
+ void onResult(SearchResult result);
+ void onNothingFound(final int stringResource);
+ void onError(String message);
+ }
+
+ private class ResultRunnable implements Runnable {
+ private final SearchResult result;
+ private int requestId = 0;
+ public ResultRunnable(SearchResult result, int requestId) {
+ this.result = result;
+ this.requestId = requestId;
+ }
+ @Override
+ public void run() {
+ if(this.requestId == SearchWorker.this.requestId) {
+ searchWorkerResultListner.onResult(result);
+ }
+ }
+ }
+
+ private class SearchRunnable implements Runnable {
+ public static final String YOUTUBE = "Youtube";
+ private final String query;
+ private final int page;
+ final Handler h = new Handler();
+ private volatile boolean runs = true;
+ private Activity a = null;
+ private int serviceId = -1;
+ public SearchRunnable(int serviceId, String query, int page, Activity activity, int requestId) {
+ this.serviceId = serviceId;
+ this.query = query;
+ this.page = page;
+ this.a = activity;
+ }
+ void terminate() {
+ runs = false;
+ }
+ @Override
+ public void run() {
+ SearchResult result = null;
+ SearchEngine engine = null;
+
+ try {
+ engine = ServiceList.getService(serviceId)
+ .getSearchEngineInstance(new Downloader());
+ } catch(ExtractionException e) {
+ ErrorActivity.reportError(h, a, e, null, null,
+ ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
+ Integer.toString(serviceId), query, R.string.general_error));
+ return;
+ }
+
+ try {
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(a);
+ String searchLanguageKey = a.getString(R.string.search_language_key);
+ String searchLanguage = sp.getString(searchLanguageKey,
+ a.getString(R.string.default_language_value));
+ result = SearchResult
+ .getSearchResult(engine, query, page, searchLanguage, new Downloader());
+
+ if(runs) {
+ h.post(new ResultRunnable(result, requestId));
+ }
+
+ // look for errors during extraction
+ // soft errors:
+ if(result != null &&
+ !result.errors.isEmpty()) {
+ Log.e(TAG, "OCCURRED ERRORS DURING SEARCH EXTRACTION:");
+ for(Throwable e : result.errors) {
+ e.printStackTrace();
+ Log.e(TAG, "------");
+ }
+
+ View rootView = a.findViewById(android.R.id.content);
+ ErrorActivity.reportError(h, a, 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) {
+ h.post(new Runnable() {
+ @Override
+ public void run() {
+ searchWorkerResultListner.onNothingFound(R.string.network_error);
+ }
+ });
+ e.printStackTrace();
+ } catch(final SearchEngine.NothingFoundException e) {
+ h.post(new Runnable() {
+ @Override
+ public void run() {
+ searchWorkerResultListner.onError(e.getMessage());
+ }
+ });
+ } catch(ExtractionException e) {
+ ErrorActivity.reportError(h, a, 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) {
+ ErrorActivity.reportError(h, a, e, null, null,
+ ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
+ /* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.general_error));
+
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private static SearchWorker searchWorker = null;
+ private SearchWorkerResultListner searchWorkerResultListner = null;
+ private SearchRunnable runnable = null;
+ private int requestId = 0; //prevents running requests that have already ben expired
+
+ public static SearchWorker getInstance() {
+ return searchWorker == null ? (searchWorker = new SearchWorker()) : searchWorker;
+ }
+
+ public void setSearchWorkerResultListner(SearchWorkerResultListner listener) {
+ searchWorkerResultListner = listener;
+ }
+
+ private SearchWorker() {
+
+ }
+
+ public void search(int serviceId, String query, int page, Activity a) {
+ if(runnable != null) {
+ terminate();
+ }
+ runnable = new SearchRunnable(serviceId, query, page, a, requestId);
+ Thread thread = new Thread(runnable);
+ thread.start();
+ }
+
+ public void terminate() {
+ requestId++;
+ runnable.terminate();
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/SuggestionListAdapter.java b/app/src/main/java/org/schabi/newpipe/search_fragment/SuggestionListAdapter.java
similarity index 66%
rename from app/src/main/java/org/schabi/newpipe/SuggestionListAdapter.java
rename to app/src/main/java/org/schabi/newpipe/search_fragment/SuggestionListAdapter.java
index 10ba5efb0..5a8ae2ce7 100644
--- a/app/src/main/java/org/schabi/newpipe/SuggestionListAdapter.java
+++ b/app/src/main/java/org/schabi/newpipe/search_fragment/SuggestionListAdapter.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe;
+package org.schabi.newpipe.search_fragment;
import android.content.Context;
import android.database.Cursor;
@@ -9,27 +9,10 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
-import java.util.ArrayList;
import java.util.List;
/**
- * Created by Madiyar on 23.02.2016.
- *
- * Copyright (C) Christian Schabesberger 2015
- * SuggestionListAdapter.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 .
+ * Created by the-scrabi on 02.08.16.
*/
public class SuggestionListAdapter extends CursorAdapter {
@@ -80,4 +63,4 @@ public class SuggestionListAdapter extends CursorAdapter {
private class ViewHolder {
public TextView suggestionTitle;
}
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/search_fragment/SuggestionSearchRunnable.java b/app/src/main/java/org/schabi/newpipe/search_fragment/SuggestionSearchRunnable.java
new file mode 100644
index 000000000..94308200d
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/search_fragment/SuggestionSearchRunnable.java
@@ -0,0 +1,90 @@
+package org.schabi.newpipe.search_fragment;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.preference.PreferenceManager;
+import android.widget.Toast;
+
+import org.schabi.newpipe.Downloader;
+import org.schabi.newpipe.ErrorActivity;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.ExtractionException;
+import org.schabi.newpipe.extractor.SearchEngine;
+import org.schabi.newpipe.extractor.ServiceList;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Created by the-scrabi on 02.08.16.
+ */
+
+public class SuggestionSearchRunnable implements Runnable{
+
+ private class SuggestionResultRunnable implements Runnable{
+
+ private List suggestions;
+ private SuggestionListAdapter adapter;
+
+ private SuggestionResultRunnable(List suggestions, SuggestionListAdapter adapter) {
+ this.suggestions = suggestions;
+ this.adapter = adapter;
+ }
+
+ @Override
+ public void run() {
+ adapter.updateAdapter(suggestions);
+ }
+ }
+
+ private final int serviceId;
+ private final String query;
+ final Handler h = new Handler();
+ private Activity a = null;
+ private SuggestionListAdapter adapter;
+ public SuggestionSearchRunnable(int serviceId, String query,
+ Activity activity, SuggestionListAdapter adapter) {
+ this.serviceId = serviceId;
+ this.query = query;
+ this.a = activity;
+ this.adapter = adapter;
+ }
+
+ @Override
+ public void run() {
+ try {
+ SearchEngine engine =
+ ServiceList.getService(serviceId).getSearchEngineInstance(new Downloader());
+ SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(a);
+ String searchLanguageKey = a.getString(R.string.search_language_key);
+ String searchLanguage = sp.getString(searchLanguageKey,
+ a.getString(R.string.default_language_value));
+ List suggestions = engine.suggestionList(query,searchLanguage,new Downloader());
+ h.post(new SuggestionResultRunnable(suggestions, adapter));
+ } catch (ExtractionException e) {
+ ErrorActivity.reportError(h, a, e, null, a.findViewById(android.R.id.content),
+ ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
+ ServiceList.getNameOfService(serviceId), query, R.string.parsing_error));
+ e.printStackTrace();
+ } catch (IOException e) {
+ postNewErrorToast(h, R.string.network_error);
+ e.printStackTrace();
+ } catch (Exception e) {
+ ErrorActivity.reportError(h, a, e, null, a.findViewById(android.R.id.content),
+ ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
+ ServiceList.getNameOfService(serviceId), query, R.string.general_error));
+ }
+ }
+
+ private void postNewErrorToast(Handler h, final int stringResource) {
+ h.post(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(a, a.getString(stringResource),
+ Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/schabi/newpipe/NewPipeSettings.java b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java
similarity index 98%
rename from app/src/main/java/org/schabi/newpipe/NewPipeSettings.java
rename to app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java
index 6a8cd7e84..0c3d29e59 100644
--- a/app/src/main/java/org/schabi/newpipe/NewPipeSettings.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/NewPipeSettings.java
@@ -18,7 +18,7 @@
* along with NewPipe. If not, see .
*/
-package org.schabi.newpipe;
+package org.schabi.newpipe.settings;
import android.content.Context;
import android.content.SharedPreferences;
@@ -26,6 +26,8 @@ import android.os.Environment;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
+import org.schabi.newpipe.R;
+
import java.io.File;
import us.shandian.giga.util.Utility;
diff --git a/app/src/main/java/org/schabi/newpipe/SettingsActivity.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
similarity index 98%
rename from app/src/main/java/org/schabi/newpipe/SettingsActivity.java
rename to app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
index 06f3a213d..e62f90c45 100644
--- a/app/src/main/java/org/schabi/newpipe/SettingsActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsActivity.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe;
+package org.schabi.newpipe.settings;
import android.content.Context;
import android.content.Intent;
@@ -14,6 +14,8 @@ import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
+import org.schabi.newpipe.R;
+
/**
* Created by Christian Schabesberger on 31.08.15.
diff --git a/app/src/main/java/org/schabi/newpipe/SettingsFragment.java b/app/src/main/java/org/schabi/newpipe/settings/SettingsFragment.java
similarity index 99%
rename from app/src/main/java/org/schabi/newpipe/SettingsFragment.java
rename to app/src/main/java/org/schabi/newpipe/settings/SettingsFragment.java
index be44e164a..401cef551 100644
--- a/app/src/main/java/org/schabi/newpipe/SettingsFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/settings/SettingsFragment.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe;
+package org.schabi.newpipe.settings;
import android.app.Activity;
import android.content.ClipData;
@@ -16,6 +16,9 @@ import android.preference.PreferenceScreen;
import com.nononsenseapps.filepicker.FilePickerActivity;
+import org.schabi.newpipe.App;
+import org.schabi.newpipe.R;
+
import java.util.ArrayList;
import info.guardianproject.netcipher.proxy.OrbotHelper;
diff --git a/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java b/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java
index 2dc123ce8..558602ca3 100755
--- a/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java
+++ b/app/src/main/java/us/shandian/giga/get/DownloadManagerImpl.java
@@ -5,7 +5,7 @@ import android.util.Log;
import com.google.gson.Gson;
-import org.schabi.newpipe.NewPipeSettings;
+import org.schabi.newpipe.settings.NewPipeSettings;
import java.io.File;
import java.io.RandomAccessFile;
diff --git a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java
index f28c2d19f..ca85ae727 100755
--- a/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java
+++ b/app/src/main/java/us/shandian/giga/service/DownloadManagerService.java
@@ -14,7 +14,7 @@ import android.os.Message;
import android.support.v4.app.NotificationCompat.Builder;
import android.util.Log;
-import org.schabi.newpipe.NewPipeSettings;
+import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.R;
import us.shandian.giga.get.DownloadManager;
import us.shandian.giga.get.DownloadManagerImpl;
diff --git a/app/src/main/java/us/shandian/giga/util/Utility.java b/app/src/main/java/us/shandian/giga/util/Utility.java
index cdec3e03d..d38958b4b 100644
--- a/app/src/main/java/us/shandian/giga/util/Utility.java
+++ b/app/src/main/java/us/shandian/giga/util/Utility.java
@@ -17,9 +17,8 @@ import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
-import org.schabi.newpipe.NewPipeSettings;
+import org.schabi.newpipe.settings.NewPipeSettings;
import org.schabi.newpipe.R;
-import us.shandian.giga.get.DownloadMission;
import com.nononsenseapps.filepicker.FilePickerActivity;
import com.nononsenseapps.filepicker.AbstractFilePickerFragment;
diff --git a/app/src/main/res/drawable-hdpi/ic_rss_feed_white_24dp.png b/app/src/main/res/drawable-hdpi/ic_rss_feed_white_24dp.png
new file mode 100644
index 000000000..c966a6e0a
Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_rss_feed_white_24dp.png differ
diff --git a/app/src/main/res/drawable-mdpi/ic_rss_feed_white_24dp.png b/app/src/main/res/drawable-mdpi/ic_rss_feed_white_24dp.png
new file mode 100644
index 000000000..067b10752
Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_rss_feed_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xhdpi/ic_rss_feed_white_24dp.png b/app/src/main/res/drawable-xhdpi/ic_rss_feed_white_24dp.png
new file mode 100644
index 000000000..650627286
Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_rss_feed_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxhdpi/ic_rss_feed_white_24dp.png b/app/src/main/res/drawable-xxhdpi/ic_rss_feed_white_24dp.png
new file mode 100644
index 000000000..2267aad8f
Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_rss_feed_white_24dp.png differ
diff --git a/app/src/main/res/drawable-xxxhdpi/ic_rss_feed_white_24dp.png b/app/src/main/res/drawable-xxxhdpi/ic_rss_feed_white_24dp.png
new file mode 100644
index 000000000..209688dc5
Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_rss_feed_white_24dp.png differ
diff --git a/app/src/main/res/drawable/white_circle.xml b/app/src/main/res/drawable/white_circle.xml
new file mode 100644
index 000000000..312f9c37c
--- /dev/null
+++ b/app/src/main/res/drawable/white_circle.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout-sw600dp-land/activity_videoitem_list.xml b/app/src/main/res/layout-sw600dp-land/activity_videoitem_list.xml
deleted file mode 100644
index 4704bd3d2..000000000
--- a/app/src/main/res/layout-sw600dp-land/activity_videoitem_list.xml
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout-v18/fragment_videoitem_detail.xml b/app/src/main/res/layout-v18/fragment_videoitem_detail.xml
index 8473c098c..0087bb749 100644
--- a/app/src/main/res/layout-v18/fragment_videoitem_detail.xml
+++ b/app/src/main/res/layout-v18/fragment_videoitem_detail.xml
@@ -3,15 +3,15 @@
+ android:id="@+id/video_item_detail">
-
-
-
-
@@ -75,7 +74,7 @@
android:layout_height="wrap_content"
android:id="@+id/detailTopView">
-
-
-
-
@@ -143,27 +142,27 @@
-
-
-
-
-
-
-
+
-
-
-
-
+ android:id="@+id/detail_uploader_layout"
+ android:layout_marginTop="12dp">
-
+
+
+
+
+
+
+
+
-
+ android:layout_height="match_parent"
+ android:background="?attr/selectableItemBackground"/>
+
-
-
-
-
-
-
-
+
-
-
+ android:layout_below="@id/detail_similar_title"/>
diff --git a/app/src/main/res/layout/activity_channel.xml b/app/src/main/res/layout/activity_channel.xml
new file mode 100644
index 000000000..f87553882
--- /dev/null
+++ b/app/src/main/res/layout/activity_channel.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
new file mode 100644
index 000000000..f35477c49
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_videoitem_detail.xml b/app/src/main/res/layout/activity_videoitem_detail.xml
index 4edf1611a..9edb96bcb 100644
--- a/app/src/main/res/layout/activity_videoitem_detail.xml
+++ b/app/src/main/res/layout/activity_videoitem_detail.xml
@@ -1,4 +1,4 @@
+ tools:context=".detail.VideoItemDetailActivity" tools:ignore="MergeRootFrame" />
diff --git a/app/src/main/res/layout/activity_videoitem_list.xml b/app/src/main/res/layout/activity_videoitem_list.xml
deleted file mode 100644
index 5c5a5fc8a..000000000
--- a/app/src/main/res/layout/activity_videoitem_list.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_channel.xml b/app/src/main/res/layout/content_channel.xml
new file mode 100644
index 000000000..79a41a628
--- /dev/null
+++ b/app/src/main/res/layout/content_channel.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_searchinfoitem.xml b/app/src/main/res/layout/fragment_searchinfoitem.xml
new file mode 100644
index 000000000..50b8e7f17
--- /dev/null
+++ b/app/src/main/res/layout/fragment_searchinfoitem.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_videoitem_detail.xml b/app/src/main/res/layout/fragment_videoitem_detail.xml
index 1695bf441..9b3d9856c 100644
--- a/app/src/main/res/layout/fragment_videoitem_detail.xml
+++ b/app/src/main/res/layout/fragment_videoitem_detail.xml
@@ -3,15 +3,15 @@
+ android:id="@+id/video_item_detail">
-
-
-
@@ -77,7 +76,7 @@
android:layout_height="wrap_content"
android:id="@+id/detailTopView">
-
-
-
-
@@ -145,27 +144,27 @@
-
-
-
-
-
-
-
+
-
-
-
-
+ android:id="@+id/detail_uploader_layout"
+ android:layout_marginTop="12dp">
-
+
+
+
+
+
+
+
+
-
+ android:layout_height="match_parent"
+ android:background="?attr/selectableItemBackground"/>
+
-
-
-
-
-
-
-
+
-
-
+ android:layout_below="@id/detail_similar_title"/>
diff --git a/app/src/main/res/layout/main_bg.xml b/app/src/main/res/layout/main_bg.xml
index bfc722636..00ab5916a 100644
--- a/app/src/main/res/layout/main_bg.xml
+++ b/app/src/main/res/layout/main_bg.xml
@@ -4,8 +4,7 @@
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/mainBG"
- tools:context=".VideoItemDetailActivity"
- tools:showIn="@layout/activity_videoitem_list">
+ tools:context=".detail.VideoItemDetailActivity">
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/video_item.xml b/app/src/main/res/layout/video_item.xml
index 8ab554543..d98bc92ac 100644
--- a/app/src/main/res/layout/video_item.xml
+++ b/app/src/main/res/layout/video_item.xml
@@ -1,8 +1,14 @@
-
+ android:layout_height="wrap_content">
+
-
\ No newline at end of file
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/main_menu.xml b/app/src/main/res/menu/main_menu.xml
new file mode 100644
index 000000000..b7e615bbc
--- /dev/null
+++ b/app/src/main/res/menu/main_menu.xml
@@ -0,0 +1,11 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_channel.xml b/app/src/main/res/menu/menu_channel.xml
new file mode 100644
index 000000000..e296e17ad
--- /dev/null
+++ b/app/src/main/res/menu/menu_channel.xml
@@ -0,0 +1,10 @@
+
diff --git a/app/src/main/res/menu/search_menu.xml b/app/src/main/res/menu/search_menu.xml
new file mode 100644
index 000000000..79158063a
--- /dev/null
+++ b/app/src/main/res/menu/search_menu.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml
index 2a278569f..ab385422f 100644
--- a/app/src/main/res/values-v21/styles.xml
+++ b/app/src/main/res/values-v21/styles.xml
@@ -10,7 +10,7 @@
- @color/light_background_color
-
-
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index afb085f6d..bcf367c86 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -31,11 +31,16 @@
50dp
18sp
18sp
+ 70dp
+ 74dp
6sp
20dp
16dp
16dp
+ 180dp
+ 16dp
+ 16dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 14fbd56fa..3d1a0fa35 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -96,6 +96,7 @@
Could not setup download menu.
This is a LIVE STREAM. These are not yet supported.
Could not get any stream.
+ Could not load Image
Sorry that should not happen.
Guru Meditation.
@@ -125,7 +126,7 @@
Cannot create download directory \'%1$s\'
Created download directory \'%1$s\'
- Play in background
+ Play in background
Video
Audio
Text
@@ -176,6 +177,97 @@
MD5
SHA1
+ ChannelActivity
+
+ "Material is the metaphor.\n\n"
+
+ "A material metaphor is the unifying theory of a rationalized space and a system of motion."
+ "The material is grounded in tactile reality, inspired by the study of paper and ink, yet "
+ "technologically advanced and open to imagination and magic.\n"
+ "Surfaces and edges of the material provide visual cues that are grounded in reality. The "
+ "use of familiar tactile attributes helps users quickly understand affordances. Yet the "
+ "flexibility of the material creates new affordances that supercede those in the physical "
+ "world, without breaking the rules of physics.\n"
+ "The fundamentals of light, surface, and movement are key to conveying how objects move, "
+ "interact, and exist in space and in relation to each other. Realistic lighting shows "
+ "seams, divides space, and indicates moving parts.\n\n"
+
+ "Bold, graphic, intentional.\n\n"
+
+ "The foundational elements of print based design typography, grids, space, scale, color, "
+ "and use of imagery guide visual treatments. These elements do far more than please the "
+ "eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge "
+ "imagery, large scale typography, and intentional white space create a bold and graphic "
+ "interface that immerse the user in the experience.\n"
+ "An emphasis on user actions makes core functionality immediately apparent and provides "
+ "waypoints for the user.\n\n"
+
+ "Motion provides meaning.\n\n"
+
+ "Motion respects and reinforces the user as the prime mover. Primary user actions are "
+ "inflection points that initiate motion, transforming the whole design.\n"
+ "All action takes place in a single environment. Objects are presented to the user without "
+ "breaking the continuity of experience even as they transform and reorganize.\n"
+ "Motion is meaningful and appropriate, serving to focus attention and maintain continuity. "
+ "Feedback is subtle yet clear. Transitions are efficient yet coherent.\n\n"
+
+ "3D world.\n\n"
+
+ "The material environment is a 3D space, which means all objects have x, y, and z "
+ "dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the "
+ "positive z-axis extending towards the viewer. Every sheet of material occupies a single "
+ "position along the z-axis and has a standard 1dp thickness.\n"
+ "On the web, the z-axis is used for layering and not for perspective. The 3D world is "
+ "emulated by manipulating the y-axis.\n\n"
+
+ "Light and shadow.\n\n"
+
+ "Within the material environment, virtual lights illuminate the scene. Key lights create "
+ "directional shadows, while ambient light creates soft shadows from all angles.\n"
+ "Shadows in the material environment are cast by these two light sources. In Android "
+ "development, shadows occur when light sources are blocked by sheets of material at "
+ "various positions along the z-axis. On the web, shadows are depicted by manipulating the "
+ "y-axis only. The following example shows the card with a height of 6dp.\n\n"
+
+ "Resting elevation.\n\n"
+
+ "All material objects, regardless of size, have a resting elevation, or default elevation "
+ "that does not change. If an object changes elevation, it should return to its resting "
+ "elevation as soon as possible.\n\n"
+
+ "Component elevations.\n\n"
+
+ "The resting elevation for a component type is consistent across apps (e.g., FAB elevation "
+ "does not vary from 6dp in one app to 16dp in another app).\n"
+ "Components may have different resting elevations across platforms, depending on the depth "
+ "of the environment (e.g., TV has a greater depth than mobile or desktop).\n\n"
+
+ "Responsive elevation and dynamic elevation offsets.\n\n"
+
+ "Some component types have responsive elevation, meaning they change elevation in response "
+ "to user input (e.g., normal, focused, and pressed) or system events. These elevation "
+ "changes are consistently implemented using dynamic elevation offsets.\n"
+ "Dynamic elevation offsets are the goal elevation that a component moves towards, relative "
+ "to the component’s resting state. They ensure that elevation changes are consistent "
+ "across actions and component types. For example, all components that lift on press have "
+ "the same elevation change relative to their resting elevation.\n"
+ "Once the input event is completed or cancelled, the component will return to its resting "
+ "elevation.\n\n"
+
+ "Avoiding elevation interference.\n\n"
+
+ "Components with responsive elevations may encounter other components as they move between "
+ "their resting elevations and dynamic elevation offsets. Because material cannot pass "
+ "through other material, components avoid interfering with one another any number of ways, "
+ "whether on a per component basis or using the entire app layout.\n"
+ "On a component level, components can move or be removed before they cause interference. "
+ "For example, a floating action button (FAB) can disappear or move off screen before a "
+ "user picks up a card, or it can move if a snackbar appears.\n"
+ "On the layout level, design your app layout to minimize opportunities for interference. "
+ "For example, position the FAB to one side of stream of a cards so the FAB won’t interfere "
+ "when a user tries to pick up one of cards.\n\n"
+
+ Settings
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index 6654a5797..5007e394b 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -1,18 +1,17 @@
-
+
-
+
-
+
-
-
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index e220f0b80..d14039de2 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:2.1.2'
+ classpath 'com.android.tools.build:gradle:2.1.3'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 28de4becc..1478212dc 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Mon Aug 10 20:55:41 CEST 2015
+#Sat Aug 20 00:40:54 CEST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip