+ * DownloadActivity.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 .
+ */
+
package org.schabi.newpipe;
import android.content.Intent;
import android.media.AudioManager;
import android.os.Bundle;
import android.support.v4.app.Fragment;
-import android.support.v4.app.NavUtils;
+import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
+
+import com.nostra13.universalimageloader.core.ImageLoader;
+
import org.schabi.newpipe.download.DownloadActivity;
+import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.fragments.OnItemSelectedListener;
+import org.schabi.newpipe.fragments.channel.ChannelFragment;
+import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
+import org.schabi.newpipe.fragments.search.SearchFragment;
import org.schabi.newpipe.settings.SettingsActivity;
+import org.schabi.newpipe.util.Constants;
+import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
-/**
- * Created by Christian Schabesberger on 02.08.16.
- *
- * Copyright (C) Christian Schabesberger 2016
- * DownloadActivity.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 MainActivity extends AppCompatActivity {
- private Fragment mainFragment = null;
+public class MainActivity extends AppCompatActivity implements OnItemSelectedListener {
private static final String TAG = MainActivity.class.toString();
+ /*//////////////////////////////////////////////////////////////////////////
+ // Activity's LifeCycle
+ //////////////////////////////////////////////////////////////////////////*/
+
@Override
protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this, true);
+ super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
- mainFragment = getSupportFragmentManager()
- .findFragmentById(R.id.search_fragment);
+ if (savedInstanceState == null) initFragments();
}
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ handleIntent(intent);
+ }
+
+ @Override
+ public void onBackPressed() {
+ Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
+ if (fragment instanceof VideoDetailFragment) if (((VideoDetailFragment) fragment).onActivityBackPressed()) return;
+
+ if (getSupportFragmentManager().getBackStackEntryCount() >= 2) {
+ getSupportFragmentManager().popBackStackImmediate();
+ } else {
+ if (fragment instanceof SearchFragment) {
+ SearchFragment searchFragment = (SearchFragment) fragment;
+ if (!searchFragment.isMainBgVisible()) {
+ getSupportFragmentManager().beginTransaction().remove(fragment).commitNow();
+ NavigationHelper.openMainActivity(this);
+ return;
+ }
+ }
+ finish();
+ }
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Menu
+ //////////////////////////////////////////////////////////////////////////*/
+
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
@@ -63,9 +104,10 @@ public class MainActivity extends AppCompatActivity {
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);
+ Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
+ if (fragment instanceof VideoDetailFragment) ((VideoDetailFragment) fragment).clearHistory();
+
+ NavigationHelper.openMainActivity(this);
return true;
}
case R.id.action_settings: {
@@ -85,4 +127,112 @@ public class MainActivity extends AppCompatActivity {
return super.onOptionsItemSelected(item);
}
}
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Init
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private void initFragments() {
+ if (getIntent() != null && getIntent().hasExtra(Constants.KEY_URL)) {
+ handleIntent(getIntent());
+ } else openSearchFragment();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // OnItemSelectedListener
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onItemSelected(StreamingService.LinkType linkType, int serviceId, String url, String name) {
+ switch (linkType) {
+ case STREAM:
+ openVideoDetailFragment(serviceId, url, name, false);
+ break;
+ case CHANNEL:
+ openChannelFragment(serviceId, url, name);
+ break;
+ }
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Utils
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private void handleIntent(Intent intent) {
+ if (intent.hasExtra(Constants.KEY_LINK_TYPE)) {
+ String url = intent.getStringExtra(Constants.KEY_URL);
+ int serviceId = intent.getIntExtra(Constants.KEY_SERVICE_ID, 0);
+ try {
+ switch (((StreamingService.LinkType) intent.getSerializableExtra(Constants.KEY_LINK_TYPE))) {
+ case STREAM:
+ handleVideoDetailIntent(serviceId, url, intent);
+ break;
+ case CHANNEL:
+ handleChannelIntent(serviceId, url, intent);
+ break;
+ case NONE:
+ throw new Exception("Url not known to service. service=" + Integer.toString(serviceId) + " url=" + url);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ } else {
+ getSupportFragmentManager().popBackStackImmediate(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ openSearchFragment();
+ }
+ }
+
+ private void openSearchFragment() {
+ ImageLoader.getInstance().clearMemoryCache();
+ getSupportFragmentManager().beginTransaction()
+ .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out, android.R.anim.fade_in, android.R.anim.fade_out)
+ .replace(R.id.fragment_holder, new SearchFragment())
+ .addToBackStack(null)
+ .commit();
+ }
+
+ private void openVideoDetailFragment(int serviceId, String url, String title, boolean autoPlay) {
+ ImageLoader.getInstance().clearMemoryCache();
+
+ Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
+ if (title == null) title = "";
+
+ if (fragment instanceof VideoDetailFragment && fragment.isVisible()) {
+ VideoDetailFragment detailFragment = (VideoDetailFragment) fragment;
+ detailFragment.setAutoplay(autoPlay);
+ detailFragment.selectAndLoadVideo(serviceId, url, title);
+ return;
+ }
+
+ VideoDetailFragment instance = VideoDetailFragment.getInstance(serviceId, url, title);
+ instance.setAutoplay(autoPlay);
+
+ getSupportFragmentManager().beginTransaction()
+ .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out, android.R.anim.fade_in, android.R.anim.fade_out)
+ .replace(R.id.fragment_holder, instance)
+ .addToBackStack(null)
+ .commit();
+ }
+
+ private void openChannelFragment(int serviceId, String url, String name) {
+ ImageLoader.getInstance().clearMemoryCache();
+ if (name == null) name = "";
+ getSupportFragmentManager().beginTransaction()
+ .setCustomAnimations(android.R.anim.fade_in, android.R.anim.fade_out, android.R.anim.fade_in, android.R.anim.fade_out)
+ .replace(R.id.fragment_holder, ChannelFragment.newInstance(serviceId, url, name))
+ .addToBackStack(null)
+ .commit();
+ }
+
+ private void handleVideoDetailIntent(int serviceId, String url, Intent intent) {
+ boolean autoPlay = intent.getBooleanExtra(VideoDetailFragment.AUTO_PLAY, false);
+ String title = intent.getStringExtra(Constants.KEY_TITLE);
+ openVideoDetailFragment(serviceId, url, title, autoPlay);
+ }
+
+ private void handleChannelIntent(int serviceId, String url, Intent intent) {
+ String name = intent.getStringExtra(Constants.KEY_TITLE);
+ openChannelFragment(serviceId, url, name);
+ }
+
}
diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
index e9cddf16d..134a8b102 100644
--- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
@@ -3,19 +3,14 @@ package org.schabi.newpipe;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
-import android.preference.PreferenceManager;
-import android.util.Log;
import android.widget.Toast;
-import org.schabi.newpipe.detail.VideoItemDetailActivity;
-import org.schabi.newpipe.extractor.NewPipe;
-import org.schabi.newpipe.extractor.StreamingService;
-import org.schabi.newpipe.util.NavStack;
+import org.schabi.newpipe.util.NavigationHelper;
import java.util.Collection;
import java.util.HashSet;
-/**
+/*
* Copyright (C) Christian Schabesberger 2017
* RouterActivity .java is part of NewPipe.
*
@@ -38,7 +33,7 @@ import java.util.HashSet;
* to the part of the service which can handle the url.
*/
public class RouterActivity extends Activity {
- private static final String TAG = RouterActivity.class.toString();
+ //private static final String TAG = "RouterActivity"
/**
* Removes invisible separators (\p{Z}) and punctuation characters including
@@ -54,6 +49,25 @@ public class RouterActivity extends Activity {
finish();
}
+ private void handleIntent(Intent intent) {
+ String videoUrl = "";
+
+ // first gather data and find service
+ if (intent.getData() != null) {
+ // this means the video was called though another app
+ videoUrl = intent.getData().toString();
+ } else if (intent.getStringExtra(Intent.EXTRA_TEXT) != null) {
+ //this means that vidoe was called through share menu
+ String extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
+ videoUrl = getUris(extraText)[0];
+ }
+
+ try {
+ NavigationHelper.openByLink(this, videoUrl);
+ } catch (Exception e) {
+ Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
+ }
+ }
private static String removeHeadingGibberish(final String input) {
int start = 0;
@@ -107,50 +121,4 @@ public class RouterActivity extends Activity {
return result.toArray(new String[result.size()]);
}
- private void handleIntent(Intent intent) {
- String videoUrl = "";
- StreamingService service = null;
-
- // first gather data and find service
- if (intent.getData() != null) {
- // this means the video was called though another app
- videoUrl = intent.getData().toString();
- } else if(intent.getStringExtra(Intent.EXTRA_TEXT) != null) {
- //this means that vidoe was called through share menu
- String extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
- videoUrl = getUris(extraText)[0];
- }
-
- service = NewPipe.getServiceByUrl(videoUrl);
- if(service == null) {
- Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG)
- .show();
- return;
- } else {
- Intent callIntent = new Intent();
- switch(service.getLinkTypeByUrl(videoUrl)) {
- case CHANNEL:
- callIntent.setClass(this, ChannelActivity.class);
- break;
- case STREAM:
- callIntent.setClass(this, VideoItemDetailActivity.class);
- callIntent.putExtra(VideoItemDetailActivity.AUTO_PLAY,
- PreferenceManager.getDefaultSharedPreferences(this)
- .getBoolean(
- getString(R.string.autoplay_through_intent_key), false));
- break;
- case PLAYLIST:
- Log.e(TAG, "NOT YET DEFINED");
- break;
- default:
- Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG)
- .show();
- return;
- }
-
- callIntent.putExtra(NavStack.URL, videoUrl);
- callIntent.putExtra(NavStack.SERVICE_ID, service.getServiceId());
- startActivity(callIntent);
- }
- }
}
diff --git a/app/src/main/java/org/schabi/newpipe/PopupActivity.java b/app/src/main/java/org/schabi/newpipe/RouterPopupActivity.java
similarity index 79%
rename from app/src/main/java/org/schabi/newpipe/PopupActivity.java
rename to app/src/main/java/org/schabi/newpipe/RouterPopupActivity.java
index f0f1c1ea8..1cfcd29f2 100644
--- a/app/src/main/java/org/schabi/newpipe/PopupActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/RouterPopupActivity.java
@@ -4,24 +4,22 @@ import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
-import android.util.Log;
import android.widget.Toast;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.player.PopupVideoPlayer;
-import org.schabi.newpipe.util.NavStack;
+import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.PermissionHelper;
import java.util.Collection;
import java.util.HashSet;
/**
- * This activity is thought to open video streams form an external app using the popup playser.
+ * This activity is thought to open video streams form an external app using the popup player.
*/
-
-public class PopupActivity extends Activity {
- private static final String TAG = RouterActivity.class.toString();
+public class RouterPopupActivity extends Activity {
+ //private static final String TAG = "RouterPopupActivity";
/**
* Removes invisible separators (\p{Z}) and punctuation characters including
@@ -38,6 +36,45 @@ public class PopupActivity extends Activity {
}
+ private void handleIntent(Intent intent) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ && !PermissionHelper.checkSystemAlertWindowPermission(this)) {
+ Toast.makeText(this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
+ return;
+ }
+ String videoUrl = "";
+ StreamingService service;
+
+ // first gather data and find service
+ if (intent.getData() != null) {
+ // this means the video was called though another app
+ videoUrl = intent.getData().toString();
+ } else if (intent.getStringExtra(Intent.EXTRA_TEXT) != null) {
+ //this means that vidoe was called through share menu
+ String extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
+ videoUrl = getUris(extraText)[0];
+ }
+
+ service = NewPipe.getServiceByUrl(videoUrl);
+ if (service == null) {
+ Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ Intent callIntent = new Intent(this, PopupVideoPlayer.class);
+ switch (service.getLinkTypeByUrl(videoUrl)) {
+ case STREAM:
+ break;
+ default:
+ Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ callIntent.putExtra(Constants.KEY_URL, videoUrl);
+ callIntent.putExtra(Constants.KEY_SERVICE_ID, service.getServiceId());
+ startService(callIntent);
+ }
+
private static String removeHeadingGibberish(final String input) {
int start = 0;
for (int i = input.indexOf("://") - 1; i >= 0; i--) {
@@ -90,47 +127,4 @@ public class PopupActivity extends Activity {
return result.toArray(new String[result.size()]);
}
- private void handleIntent(Intent intent) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
- && !PermissionHelper.checkSystemAlertWindowPermission(this)) {
- Toast.makeText(this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
- return;
- }
- String videoUrl = "";
- StreamingService service = null;
-
- // first gather data and find service
- if (intent.getData() != null) {
- // this means the video was called though another app
- videoUrl = intent.getData().toString();
- } else if (intent.getStringExtra(Intent.EXTRA_TEXT) != null) {
- //this means that vidoe was called through share menu
- String extraText = intent.getStringExtra(Intent.EXTRA_TEXT);
- videoUrl = getUris(extraText)[0];
- }
-
- service = NewPipe.getServiceByUrl(videoUrl);
- if (service == null) {
- Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG)
- .show();
- return;
- } else {
- Intent callIntent = new Intent();
- switch (service.getLinkTypeByUrl(videoUrl)) {
- case STREAM:
- callIntent.setClass(this, PopupVideoPlayer.class);
- break;
- case PLAYLIST:
- Log.e(TAG, "NOT YET DEFINED");
- break;
- default:
- Toast.makeText(this, R.string.url_not_supported_toast, Toast.LENGTH_LONG).show();
- return;
- }
-
- callIntent.putExtra(NavStack.URL, videoUrl);
- callIntent.putExtra(NavStack.SERVICE_ID, service.getServiceId());
- startService(callIntent);
- }
- }
}
diff --git a/app/src/main/java/org/schabi/newpipe/detail/StreamExtractorWorker.java b/app/src/main/java/org/schabi/newpipe/detail/StreamExtractorWorker.java
deleted file mode 100644
index 730b5c653..000000000
--- a/app/src/main/java/org/schabi/newpipe/detail/StreamExtractorWorker.java
+++ /dev/null
@@ -1,250 +0,0 @@
-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.R;
-import org.schabi.newpipe.extractor.NewPipe;
-import org.schabi.newpipe.extractor.StreamingService;
-import org.schabi.newpipe.extractor.exceptions.ParsingException;
-import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
-import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
-import org.schabi.newpipe.extractor.stream_info.StreamExtractor;
-import org.schabi.newpipe.extractor.stream_info.StreamInfo;
-import org.schabi.newpipe.report.ErrorActivity;
-
-import java.io.IOException;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-/**
- * Extract {@link StreamInfo} with {@link StreamExtractor} from the given url of the given service
- */
-@SuppressWarnings("WeakerAccess")
-public class StreamExtractorWorker extends Thread {
- private static final String TAG = "StreamExtractorWorker";
-
- private Activity activity;
- private final String videoUrl;
- private final int serviceId;
- private OnStreamInfoReceivedListener callback;
-
- private final AtomicBoolean isRunning = new AtomicBoolean(false);
- private final Handler handler = new Handler();
-
-
- public interface OnStreamInfoReceivedListener {
- void onReceive(StreamInfo info);
- void onError(int messageId);
- void onReCaptchaException();
- void onBlockedByGemaError();
- void onContentErrorWithMessage(int messageId);
- void onContentError();
- }
-
- public StreamExtractorWorker(Activity activity, String videoUrl, int serviceId, OnStreamInfoReceivedListener callback) {
- this.serviceId = serviceId;
- this.videoUrl = videoUrl;
- this.activity = activity;
- this.callback = callback;
- }
-
- /**
- * Returns a new instance already started of {@link StreamExtractorWorker}.
- * The caller is responsible to check if {@link StreamExtractorWorker#isRunning()}, or {@link StreamExtractorWorker#cancel()} it
- *
- * @param serviceId id of the request service
- * @param url videoUrl of the service (e.g. https://www.youtube.com/watch?v=HyHNuVaZJ-k)
- * @param activity activity for error reporting purposes
- * @param callback listener that will be called-back when events occur (check {@link OnStreamInfoReceivedListener})
- * @return new instance already started of {@link StreamExtractorWorker}
- */
- public static StreamExtractorWorker startExtractorThread(int serviceId, String url, Activity activity, OnStreamInfoReceivedListener callback) {
- StreamExtractorWorker extractorThread = getExtractorThread(serviceId, url, activity, callback);
- extractorThread.start();
- return extractorThread;
- }
-
- /**
- * Returns a new instance of {@link StreamExtractorWorker}.
- * The caller is responsible to check if {@link StreamExtractorWorker#isRunning()}, or {@link StreamExtractorWorker#cancel()}
- * when it doesn't need it anymore
- *
- * Note: this instance is not started yet
- *
- * @param serviceId id of the request service
- * @param url videoUrl of the service (e.g. https://www.youtube.com/watch?v=HyHNuVaZJ-k)
- * @param activity activity for error reporting purposes
- * @param callback listener that will be called-back when events occur (check {@link OnStreamInfoReceivedListener})
- * @return instance of {@link StreamExtractorWorker}
- */
- public static StreamExtractorWorker getExtractorThread(int serviceId, String url, Activity activity, OnStreamInfoReceivedListener callback) {
- return new StreamExtractorWorker(activity, url, serviceId, callback);
- }
-
- @Override
- //Just ignore the errors for now
- @SuppressWarnings("ConstantConditions")
- public void run() {
- // TODO: Improve error checking
- // and this method in general
-
- StreamInfo streamInfo = null;
- StreamingService service;
- try {
- service = NewPipe.getService(serviceId);
- } catch (Exception e) {
- e.printStackTrace();
- ErrorActivity.reportError(handler, activity, e, VideoItemDetailActivity.class, null,
- ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
- "", videoUrl, R.string.could_not_get_stream));
- return;
- }
- try {
- isRunning.set(true);
- StreamExtractor streamExtractor = service.getExtractorInstance(videoUrl);
- streamInfo = StreamInfo.getVideoInfo(streamExtractor);
-
- final StreamInfo info = streamInfo;
- if (callback != null) handler.post(new Runnable() {
- @Override
- public void run() {
- callback.onReceive(info);
- }
- });
- isRunning.set(false);
- // 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 = activity != null ? activity.findViewById(R.id.video_item_detail) : null;
- ErrorActivity.reportError(handler, activity,
- 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 (ReCaptchaException e) {
- if (callback != null) handler.post(new Runnable() {
- @Override
- public void run() {
- callback.onReCaptchaException();
- }
- });
- } catch (IOException e) {
- if (callback != null) handler.post(new Runnable() {
- @Override
- public void run() {
- callback.onError(R.string.network_error);
- }
- });
- if (callback != null) e.printStackTrace();
- } catch (YoutubeStreamExtractor.DecryptException de) {
- // custom service related exceptions
- ErrorActivity.reportError(handler, activity, de, VideoItemDetailActivity.class, null,
- ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
- service.getServiceInfo().name, videoUrl, R.string.youtube_signature_decryption_error));
- handler.post(new Runnable() {
- @Override
- public void run() {
- activity.finish();
- }
- });
- de.printStackTrace();
- } catch (YoutubeStreamExtractor.GemaException ge) {
- if (callback != null) handler.post(new Runnable() {
- @Override
- public void run() {
- callback.onBlockedByGemaError();
- }
- });
- } catch (YoutubeStreamExtractor.LiveStreamException e) {
- if (callback != null) handler.post(new Runnable() {
- @Override
- public void run() {
- callback.onContentErrorWithMessage(R.string.live_streams_not_supported);
- }
- });
- }
- // ----------------------------------------
- catch (StreamExtractor.ContentNotAvailableException e) {
- if (callback != null) handler.post(new Runnable() {
- @Override
- public void run() {
- callback.onContentError();
- }
- });
- e.printStackTrace();
- } catch (StreamInfo.StreamExctractException e) {
- if (!streamInfo.errors.isEmpty()) {
- // !!! if this case ever kicks in someone gets kicked out !!!
- ErrorActivity.reportError(handler, activity, e, VideoItemDetailActivity.class, null,
- ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
- service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream));
- } else {
- ErrorActivity.reportError(handler, activity, streamInfo.errors, VideoItemDetailActivity.class, null,
- ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
- service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream));
- }
- handler.post(new Runnable() {
- @Override
- public void run() {
- activity.finish();
- }
- });
- e.printStackTrace();
- } catch (ParsingException e) {
- ErrorActivity.reportError(handler, activity, e, VideoItemDetailActivity.class, null,
- ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
- service.getServiceInfo().name, videoUrl, R.string.parsing_error));
- handler.post(new Runnable() {
- @Override
- public void run() {
- activity.finish();
- }
- });
- e.printStackTrace();
- } catch (Exception e) {
- ErrorActivity.reportError(handler, activity, e, VideoItemDetailActivity.class, null,
- ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
- service.getServiceInfo().name, videoUrl, R.string.general_error));
- handler.post(new Runnable() {
- @Override
- public void run() {
- activity.finish();
- }
- });
- e.printStackTrace();
- }
- }
-
- /**
- * Return true if the extraction is not completed yet
- *
- * @return the value of the AtomicBoolean {@link #isRunning}
- */
- public boolean isRunning() {
- return isRunning.get();
- }
-
- /**
- * Cancel this ExtractorThread, setting the callback to null, the AtomicBoolean {@link #isRunning} to false and interrupt this thread.
- *
- * Note: Any I/O that is active in the moment that this method is called will be canceled and a Exception will be thrown, because of the {@link #interrupt()}.
- * This is useful when you don't want the resulting {@link StreamInfo} anymore, but don't want to waste bandwidth, otherwise it'd run till it receives the StreamInfo.
- */
- public void cancel() {
- this.callback = null;
- this.isRunning.set(false);
- this.interrupt();
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/extractor b/app/src/main/java/org/schabi/newpipe/extractor
index 6ab3dc876..b587d175b 160000
--- a/app/src/main/java/org/schabi/newpipe/extractor
+++ b/app/src/main/java/org/schabi/newpipe/extractor
@@ -1 +1 @@
-Subproject commit 6ab3dc876ebab4ed32f4ae60d3d04d000a7ea0e8
+Subproject commit b587d175bb9cd7e84b9bdb9b594db24e654ec7ae
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/OnItemSelectedListener.java b/app/src/main/java/org/schabi/newpipe/fragments/OnItemSelectedListener.java
new file mode 100644
index 000000000..d35c063da
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/fragments/OnItemSelectedListener.java
@@ -0,0 +1,10 @@
+package org.schabi.newpipe.fragments;
+
+import org.schabi.newpipe.extractor.StreamingService;
+
+/**
+ * Interface for communication purposes between activity and fragment
+ */
+public interface OnItemSelectedListener {
+ void onItemSelected(StreamingService.LinkType linkType, int serviceId, String url, String name);
+}
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/channel/ChannelFragment.java
new file mode 100644
index 000000000..fd28a3d23
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/fragments/channel/ChannelFragment.java
@@ -0,0 +1,416 @@
+package org.schabi.newpipe.fragments.channel;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v4.content.ContextCompat;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+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.widget.Button;
+import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.nostra13.universalimageloader.core.ImageLoader;
+
+import org.schabi.newpipe.ImageErrorLoadingListener;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.channel.ChannelInfo;
+import org.schabi.newpipe.fragments.OnItemSelectedListener;
+import org.schabi.newpipe.info_list.InfoItemBuilder;
+import org.schabi.newpipe.info_list.InfoListAdapter;
+import org.schabi.newpipe.report.ErrorActivity;
+import org.schabi.newpipe.util.Constants;
+import org.schabi.newpipe.util.NavigationHelper;
+import org.schabi.newpipe.workers.ChannelExtractorWorker;
+
+import java.text.NumberFormat;
+
+import static android.os.Build.VERSION.SDK_INT;
+
+
+/**
+ * Copyright (C) Christian Schabesberger 2016
+ * ChannelFragment.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 ChannelFragment extends Fragment implements ChannelExtractorWorker.OnChannelInfoReceive {
+ private static final String TAG = "ChannelFragment";
+
+ private AppCompatActivity activity;
+ private OnItemSelectedListener onItemSelectedListener;
+ private InfoListAdapter infoListAdapter;
+
+ private ChannelExtractorWorker currentExtractorWorker;
+ private ChannelInfo currentChannelInfo;
+ private int serviceId = -1;
+ private String channelName = "";
+ private String channelUrl = "";
+
+ private boolean isLoading = false;
+ private int pageNumber = 0;
+ private boolean hasNextPage = true;
+
+ private ImageLoader imageLoader = ImageLoader.getInstance();
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Views
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private View rootView = null;
+
+ private RecyclerView channelVideosList;
+ private LinearLayoutManager layoutManager;
+ private ProgressBar loadingProgressBar;
+
+ private View headerRootLayout;
+ private ImageView headerChannelBanner;
+ private ImageView headerAvatarView;
+ private TextView headerTitleView;
+ private TextView headerSubscriberView;
+ private Button headerSubscriberButton;
+ private View headerSubscriberLayout;
+
+ /*////////////////////////////////////////////////////////////////////////*/
+
+ public ChannelFragment() {
+ }
+
+ public static ChannelFragment newInstance(int serviceId, String url, String name) {
+ ChannelFragment instance = newInstance();
+
+ Bundle bundle = new Bundle();
+ bundle.putString(Constants.KEY_URL, url);
+ bundle.putString(Constants.KEY_TITLE, name);
+ bundle.putInt(Constants.KEY_SERVICE_ID, serviceId);
+
+ instance.setArguments(bundle);
+ return instance;
+ }
+
+ public static ChannelFragment newInstance() {
+ return new ChannelFragment();
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Fragment's LifeCycle
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ activity = ((AppCompatActivity) context);
+ onItemSelectedListener = ((OnItemSelectedListener) context);
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setHasOptionsMenu(true);
+ isLoading = false;
+ if (savedInstanceState != null) {
+ channelUrl = savedInstanceState.getString(Constants.KEY_URL);
+ channelName = savedInstanceState.getString(Constants.KEY_TITLE);
+ serviceId = savedInstanceState.getInt(Constants.KEY_SERVICE_ID, -1);
+ } else {
+ try {
+ Bundle args = getArguments();
+ if (args != null) {
+ channelUrl = args.getString(Constants.KEY_URL);
+ channelName = args.getString(Constants.KEY_TITLE);
+ serviceId = args.getInt(Constants.KEY_SERVICE_ID, 0);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ ErrorActivity.reportError(getActivity(), e, null,
+ getActivity().findViewById(android.R.id.content),
+ ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
+ NewPipe.getNameOfService(serviceId),
+ "", R.string.general_error));
+ }
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ rootView = inflater.inflate(R.layout.fragment_channel, container, false);
+ return rootView;
+ }
+
+ @Override
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ loadingProgressBar = (ProgressBar) view.findViewById(R.id.loading_progress_bar);
+ channelVideosList = (RecyclerView) view.findViewById(R.id.channel_streams_view);
+
+ infoListAdapter = new InfoListAdapter(activity, rootView);
+ layoutManager = new LinearLayoutManager(activity);
+ channelVideosList.setLayoutManager(layoutManager);
+
+ headerRootLayout = activity.getLayoutInflater().inflate(R.layout.channel_header, channelVideosList, false);
+ infoListAdapter.setHeader(headerRootLayout);
+ infoListAdapter.setFooter(activity.getLayoutInflater().inflate(R.layout.pignate_footer, channelVideosList, false));
+ channelVideosList.setAdapter(infoListAdapter);
+
+ headerChannelBanner = (ImageView) headerRootLayout.findViewById(R.id.channel_banner_image);
+ headerAvatarView = (ImageView) headerRootLayout.findViewById(R.id.channel_avatar_view);
+ headerTitleView = (TextView) headerRootLayout.findViewById(R.id.channel_title_view);
+ headerSubscriberView = (TextView) headerRootLayout.findViewById(R.id.channel_subscriber_view);
+ headerSubscriberButton = (Button) headerRootLayout.findViewById(R.id.channel_subscribe_button);
+ headerSubscriberLayout = headerRootLayout.findViewById(R.id.channel_subscriber_layout);
+
+ initListeners();
+
+ isLoading = true;
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ headerAvatarView.setImageBitmap(null);
+ headerChannelBanner.setImageBitmap(null);
+ channelVideosList.removeAllViews();
+
+ rootView = null;
+ channelVideosList = null;
+ layoutManager = null;
+ loadingProgressBar = null;
+ headerRootLayout = null;
+ headerChannelBanner = null;
+ headerAvatarView = null;
+ headerTitleView = null;
+ headerSubscriberView = null;
+ headerSubscriberButton = null;
+ headerSubscriberLayout = null;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (isLoading) {
+ requestData(false);
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (currentExtractorWorker != null) currentExtractorWorker.cancel();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ imageLoader.clearMemoryCache();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putString(Constants.KEY_URL, channelUrl);
+ outState.putString(Constants.KEY_TITLE, channelName);
+ outState.putInt(Constants.KEY_SERVICE_ID, serviceId);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Menu
+ //////////////////////////////////////////////////////////////////////////*/
+ @Override
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ super.onCreateOptionsMenu(menu, inflater);
+ inflater.inflate(R.menu.menu_channel, menu);
+
+ ActionBar supportActionBar = activity.getSupportActionBar();
+ if (supportActionBar != null) {
+ supportActionBar.setDisplayShowTitleEnabled(true);
+ supportActionBar.setDisplayHomeAsUpEnabled(true);
+ //noinspection deprecation
+ supportActionBar.setNavigationMode(0);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ super.onOptionsItemSelected(item);
+ switch (item.getItemId()) {
+ case R.id.menu_item_openInBrowser: {
+ Intent intent = new Intent();
+ intent.setAction(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse(channelUrl));
+
+ startActivity(Intent.createChooser(intent, getString(R.string.choose_browser)));
+ return true;
+ }
+ case R.id.menu_item_share:
+ Intent intent = new Intent();
+ intent.setAction(Intent.ACTION_SEND);
+ intent.putExtra(Intent.EXTRA_TEXT, channelUrl);
+ intent.setType("text/plain");
+ startActivity(Intent.createChooser(intent, getString(R.string.share_dialog_title)));
+ return true;
+ default:
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Init's
+ //////////////////////////////////////////////////////////////////////////*/
+
+ private void initListeners() {
+ infoListAdapter.setOnStreamInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
+ @Override
+ public void selected(int serviceId, String url, String title) {
+ NavigationHelper.openVideoDetail(onItemSelectedListener, serviceId, url, title);
+ }
+ });
+
+ // detect if list has ben scrolled to the bottom
+ channelVideosList.setOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ int pastVisiblesItems, visibleItemCount, totalItemCount;
+ super.onScrolled(recyclerView, dx, dy);
+ //check for scroll down
+ if (dy > 0) {
+ visibleItemCount = layoutManager.getChildCount();
+ totalItemCount = layoutManager.getItemCount();
+ pastVisiblesItems = layoutManager.findFirstVisibleItemPosition();
+
+ if ((visibleItemCount + pastVisiblesItems) >= totalItemCount && !currentExtractorWorker.isRunning() && hasNextPage) {
+ pageNumber++;
+ requestData(true);
+ }
+ }
+ }
+ });
+
+ headerSubscriberButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Log.d(TAG, currentChannelInfo.feed_url);
+ Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(currentChannelInfo.feed_url));
+ startActivity(i);
+ }
+ });
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Utils
+ //////////////////////////////////////////////////////////////////////////*/
+ private String buildSubscriberString(long count) {
+ String out = NumberFormat.getNumberInstance().format(count);
+ out += " " + getString(count > 1 ? R.string.subscriber_plural : R.string.subscriber);
+ return out;
+ }
+
+ private void requestData(boolean onlyVideos) {
+ if (currentExtractorWorker != null && currentExtractorWorker.isRunning()) currentExtractorWorker.cancel();
+
+ isLoading = true;
+ if (!onlyVideos) {
+ //delete already displayed content
+ loadingProgressBar.setVisibility(View.VISIBLE);
+ infoListAdapter.clearSteamItemList();
+ pageNumber = 0;
+ headerSubscriberLayout.setVisibility(View.GONE);
+ headerTitleView.setText(channelName != null ? channelName : "");
+ if (activity.getSupportActionBar() != null) activity.getSupportActionBar().setTitle(channelName != null ? channelName : "");
+ if (SDK_INT >= 21) {
+ headerChannelBanner.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.channel_banner));
+ headerAvatarView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy));
+ }
+ infoListAdapter.showFooter(false);
+ }
+
+ currentExtractorWorker = new ChannelExtractorWorker(activity, serviceId, channelUrl, pageNumber, this);
+ currentExtractorWorker.setOnlyVideos(onlyVideos);
+ currentExtractorWorker.start();
+ }
+
+ private void addVideos(ChannelInfo info) {
+ infoListAdapter.addInfoItemList(info.related_streams);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // OnChannelInfoReceiveListener
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onReceive(ChannelInfo info) {
+ if (info == null || isRemoving() || !isVisible()) return;
+
+ currentChannelInfo = info;
+
+ if (!currentExtractorWorker.isOnlyVideos()) {
+ headerRootLayout.setVisibility(View.VISIBLE);
+ loadingProgressBar.setVisibility(View.GONE);
+
+ if (info.channel_name != null && !info.channel_name.isEmpty()) {
+ if (activity.getSupportActionBar() != null) activity.getSupportActionBar().setTitle(info.channel_name);
+ headerTitleView.setText(info.channel_name);
+ channelName = info.channel_name;
+ } else channelName = "";
+
+ if (info.banner_url != null && !info.banner_url.isEmpty()) {
+ imageLoader.displayImage(info.banner_url, headerChannelBanner,
+ new ImageErrorLoadingListener(activity, rootView, info.service_id));
+ }
+
+ if (info.avatar_url != null && !info.avatar_url.isEmpty()) {
+ headerAvatarView.setVisibility(View.VISIBLE);
+ imageLoader.displayImage(info.avatar_url, headerAvatarView,
+ new ImageErrorLoadingListener(activity, rootView, info.service_id));
+ }
+
+ if (info.subscriberCount != -1) {
+ headerSubscriberView.setText(buildSubscriberString(info.subscriberCount));
+ }
+
+ if ((info.feed_url != null && !info.feed_url.isEmpty()) || (info.subscriberCount != -1)) {
+ headerSubscriberLayout.setVisibility(View.VISIBLE);
+ }
+
+ if (info.feed_url == null || info.feed_url.isEmpty()) {
+ headerSubscriberButton.setVisibility(View.INVISIBLE);
+ }
+
+ infoListAdapter.showFooter(true);
+ }
+ hasNextPage = info.hasNextPage;
+ if (!hasNextPage) infoListAdapter.showFooter(false);
+ addVideos(info);
+ isLoading = false;
+ }
+
+ @Override
+ public void onError(int messageId) {
+ Toast.makeText(activity, messageId, Toast.LENGTH_LONG).show();
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/detail/ActionBarHandler.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/ActionBarHandler.java
similarity index 77%
rename from app/src/main/java/org/schabi/newpipe/detail/ActionBarHandler.java
rename to app/src/main/java/org/schabi/newpipe/fragments/detail/ActionBarHandler.java
index 5b3c68c2e..d60fac737 100644
--- a/app/src/main/java/org/schabi/newpipe/detail/ActionBarHandler.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/ActionBarHandler.java
@@ -1,6 +1,5 @@
-package org.schabi.newpipe.detail;
+package org.schabi.newpipe.fragments.detail;
-import android.content.Intent;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import android.support.v7.app.ActionBar;
@@ -12,9 +11,9 @@ 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.stream_info.VideoStream;
+import org.schabi.newpipe.util.Utils;
import java.util.List;
@@ -50,7 +49,7 @@ class ActionBarHandler {
private Menu menu;
// Only callbacks are listed here, there are more actions which don't need a callback.
- // those are edited directly. Typically VideoItemDetailFragment will implement those callbacks.
+ // those are edited directly. Typically VideoDetailFragment will implement those callbacks.
private OnActionListener onShareListener;
private OnActionListener onOpenInBrowserListener;
private OnActionListener onOpenInPopupListener;
@@ -89,7 +88,7 @@ class ActionBarHandler {
VideoStream item = videoStreams.get(i);
itemArray[i] = MediaFormat.getNameById(item.format) + " " + item.resolution;
}
- int defaultResolution = getDefaultResolution(videoStreams);
+ int defaultResolution = Utils.getPreferredResolution(activity, videoStreams);
ArrayAdapter itemAdapter = new ArrayAdapter<>(activity.getBaseContext(),
android.R.layout.simple_spinner_dropdown_item, itemArray);
@@ -110,43 +109,6 @@ class ActionBarHandler {
}
}
-
- private int getDefaultResolution(final List videoStreams) {
- if (defaultPreferences == null)
- return 0;
-
- String defaultResolution = defaultPreferences
- .getString(activity.getString(R.string.default_resolution_key),
- activity.getString(R.string.default_resolution_value));
-
- String preferedFormat = defaultPreferences
- .getString(activity.getString(R.string.preferred_video_format_key),
- activity.getString(R.string.preferred_video_format_default));
-
- // first try to find the one with the right resolution
- int selectedFormat = 0;
- for (int i = 0; i < videoStreams.size(); i++) {
- VideoStream item = videoStreams.get(i);
- if (defaultResolution.equals(item.resolution)) {
- selectedFormat = i;
- }
- }
-
- // than try to find the one with the right resolution and format
- for (int i = 0; i < videoStreams.size(); i++) {
- VideoStream item = videoStreams.get(i);
- if (defaultResolution.equals(item.resolution)
- && preferedFormat.equals(MediaFormat.getNameById(item.format))) {
- selectedFormat = i;
- }
- }
-
-
- // this is actually an error,
- // but maybe there is really no stream fitting to the default value.
- return selectedFormat;
- }
-
public void setupMenu(Menu menu, MenuInflater inflater) {
this.menu = menu;
@@ -187,11 +149,6 @@ class ActionBarHandler {
onDownloadListener.onActionSelected(selectedVideoStream);
}
return true;
- case R.id.action_settings: {
- Intent intent = new Intent(activity, SettingsActivity.class);
- activity.startActivity(intent);
- return true;
- }
case R.id.action_play_with_kodi:
if(onPlayWithKodiListener != null) {
onPlayWithKodiListener.onActionSelected(selectedVideoStream);
@@ -202,12 +159,6 @@ class ActionBarHandler {
onPlayAudioListener.onActionSelected(selectedVideoStream);
}
return true;
- case R.id.menu_item_downloads: {
- Intent intent =
- new Intent(activity, org.schabi.newpipe.download.DownloadActivity.class);
- activity.startActivity(intent);
- return true;
- }
case R.id.menu_item_popup: {
if(onOpenInPopupListener != null) {
onOpenInPopupListener.onActionSelected(selectedVideoStream);
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/StackItem.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/StackItem.java
new file mode 100644
index 000000000..e335be1e3
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/StackItem.java
@@ -0,0 +1,31 @@
+package org.schabi.newpipe.fragments.detail;
+
+import java.io.Serializable;
+
+
+@SuppressWarnings("WeakerAccess")
+public class StackItem implements Serializable {
+ private String title, url;
+
+ public StackItem(String url, String title) {
+ this.title = title;
+ this.url = url;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ @Override
+ public String toString() {
+ return getUrl() + " > " + getTitle();
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailActivity.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
similarity index 63%
rename from app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailActivity.java
rename to app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
index 188479009..8e803ff87 100644
--- a/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java
@@ -1,7 +1,9 @@
-package org.schabi.newpipe.detail;
+package org.schabi.newpipe.fragments.detail;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.app.Activity;
+import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -11,14 +13,18 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
+import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat;
+import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.util.Log;
import android.util.TypedValue;
+import android.view.LayoutInflater;
import android.view.Menu;
+import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
@@ -49,6 +55,7 @@ import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.stream_info.AudioStream;
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
+import org.schabi.newpipe.fragments.OnItemSelectedListener;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.player.AbstractPlayer;
import org.schabi.newpipe.player.BackgroundPlayer;
@@ -56,30 +63,38 @@ import org.schabi.newpipe.player.ExoPlayerActivity;
import org.schabi.newpipe.player.PlayVideoActivity;
import org.schabi.newpipe.player.PopupVideoPlayer;
import org.schabi.newpipe.report.ErrorActivity;
-import org.schabi.newpipe.util.NavStack;
+import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
-import org.schabi.newpipe.util.ThemeHelper;
+import org.schabi.newpipe.util.Utils;
+import org.schabi.newpipe.workers.StreamExtractorWorker;
+import java.io.Serializable;
import java.util.ArrayList;
+import java.util.Stack;
import java.util.concurrent.atomic.AtomicBoolean;
@SuppressWarnings("FieldCanBeLocal")
-public class VideoItemDetailActivity extends AppCompatActivity implements StreamExtractorWorker.OnStreamInfoReceivedListener, SharedPreferences.OnSharedPreferenceChangeListener {
+public class VideoDetailFragment extends Fragment implements StreamExtractorWorker.OnStreamInfoReceivedListener, SharedPreferences.OnSharedPreferenceChangeListener {
+
+ private final String TAG = "VideoDetailFragment@" + Integer.toHexString(hashCode());
- private static final String TAG = "VideoItemDetailActivity";
private static final String KORE_PACKET = "org.xbmc.kore";
+ private static final String SERVICE_ID_KEY = "service_id_key";
+ private static final String VIDEO_URL_KEY = "video_url_key";
+ private static final String VIDEO_TITLE_KEY = "video_title_key";
+ private static final String STACK_KEY = "stack_key";
- /**
- * The fragment argument representing the item ID that this fragment
- * represents.
- */
public static final String AUTO_PLAY = "auto_play";
+ private AppCompatActivity activity;
+ private OnItemSelectedListener onItemSelectedListener;
private ActionBarHandler actionBarHandler;
private InfoItemBuilder infoItemBuilder = null;
private StreamInfo currentStreamInfo = null;
- private StreamExtractorWorker curExtractorThread;
+ private StreamExtractorWorker curExtractorWorker;
+
+ private String videoTitle;
private String videoUrl;
private int serviceId = -1;
@@ -89,9 +104,9 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
private boolean autoPlayEnabled;
private boolean showRelatedStreams;
- private ImageLoader imageLoader = ImageLoader.getInstance();
- private DisplayImageOptions displayImageOptions =
- new DisplayImageOptions.Builder().displayer(new FadeInBitmapDisplayer(400)).cacheInMemory(true).build();
+ private static final ImageLoader imageLoader = ImageLoader.getInstance();
+ private static final DisplayImageOptions displayImageOptions =
+ new DisplayImageOptions.Builder().displayer(new FadeInBitmapDisplayer(400)).cacheInMemory(false).build();
private Bitmap streamThumbnail = null;
/*//////////////////////////////////////////////////////////////////////////
@@ -130,55 +145,141 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
private RelativeLayout relatedStreamRootLayout;
private LinearLayout relatedStreamsView;
+ /*////////////////////////////////////////////////////////////////////////*/
+
+ public static VideoDetailFragment getInstance(int serviceId, String url) {
+ return getInstance(serviceId, url, "");
+ }
+
+ public static VideoDetailFragment getInstance(int serviceId, String videoUrl, String videoTitle) {
+ VideoDetailFragment instance = getInstance();
+ instance.selectVideo(serviceId, videoUrl, videoTitle);
+ return instance;
+ }
+
+ public static VideoDetailFragment getInstance() {
+ return new VideoDetailFragment();
+ }
/*//////////////////////////////////////////////////////////////////////////
- // Activity's Lifecycle
+ // Fragment's Lifecycle
//////////////////////////////////////////////////////////////////////////*/
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ activity = (AppCompatActivity) context;
+ onItemSelectedListener = (OnItemSelectedListener) context;
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ videoTitle = savedInstanceState.getString(VIDEO_TITLE_KEY);
+ videoUrl = savedInstanceState.getString(VIDEO_URL_KEY);
+ serviceId = savedInstanceState.getInt(SERVICE_ID_KEY);
+ Serializable serializable = savedInstanceState.getSerializable(STACK_KEY);
+ if (serializable instanceof Stack) {
+ //noinspection unchecked
+ Stack list = (Stack) serializable;
+ stack.clear();
+ stack.addAll(list);
+ }
+ }
- showRelatedStreams = PreferenceManager.getDefaultSharedPreferences(this).getBoolean(getString(R.string.show_next_video_key), true);
- PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
-
- ThemeHelper.setTheme(this, true);
- setContentView(R.layout.activity_videoitem_detail);
- setVolumeControlStream(AudioManager.STREAM_MUSIC);
-
- if (getSupportActionBar() != null) getSupportActionBar().setDisplayHomeAsUpEnabled(true);
- else Log.e(TAG, "Could not get SupportActionBar");
-
- initViews();
- initListeners();
- handleIntent(getIntent());
+ showRelatedStreams = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(getString(R.string.show_next_video_key), true);
+ PreferenceManager.getDefaultSharedPreferences(activity).registerOnSharedPreferenceChangeListener(this);
+ activity.setVolumeControlStream(AudioManager.STREAM_MUSIC);
+ isLoading.set(false);
+ setHasOptionsMenu(true);
}
@Override
- protected void onResume() {
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_video_detail, container, false);
+ }
+
+ @Override
+ public void onViewCreated(View rootView, Bundle savedInstanceState) {
+ initViews(rootView);
+ initListeners();
+ isLoading.set(true);
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ thumbnailImageView.setImageBitmap(null);
+ relatedStreamsView.removeAllViews();
+
+ loadingProgressBar = null;
+
+ parallaxScrollRootView = null;
+ contentRootLayout = null;
+
+ thumbnailBackgroundButton = null;
+ thumbnailImageView = null;
+ thumbnailPlayButton = null;
+
+ videoTitleRoot = null;
+ videoTitleTextView = null;
+ videoTitleToggleArrow = null;
+ videoCountView = null;
+
+ videoDescriptionRootLayout = null;
+ videoUploadDateView = null;
+ videoDescriptionView = null;
+
+ uploaderButton = null;
+ uploaderTextView = null;
+ uploaderThumb = null;
+
+ thumbsUpTextView = null;
+ thumbsUpImageView = null;
+ thumbsDownTextView = null;
+ thumbsDownImageView = null;
+ thumbsDisabledTextView = null;
+
+ nextStreamTitle = null;
+ relatedStreamRootLayout = null;
+ relatedStreamsView = null;
+ }
+
+ @Override
+ public void onResume() {
super.onResume();
// Currently only used for enable/disable related videos
- // but can be extended for other live settings change
+ // but can be extended for other live settings changes
if (needUpdate) {
if (relatedStreamsView != null) initRelatedVideos(currentStreamInfo);
needUpdate = false;
}
+
+ // Check if it was loading when the activity was stopped/paused,
+ // because when this happen, the curExtractorWorker is cancelled
+ if (isLoading.get()) selectAndLoadVideo(serviceId, videoUrl, videoTitle);
}
@Override
- protected void onNewIntent(Intent intent) {
- super.onNewIntent(intent);
- setIntent(intent);
- handleIntent(intent);
+ public void onStop() {
+ super.onStop();
+ if (curExtractorWorker != null && curExtractorWorker.isRunning()) curExtractorWorker.cancel();
}
@Override
- public void onBackPressed() {
- try {
- NavStack.getInstance().navBack(this);
- } catch (Exception e) {
- ErrorActivity.reportUiError(this, e);
- }
+ public void onDestroy() {
+ super.onDestroy();
+ PreferenceManager.getDefaultSharedPreferences(activity).unregisterOnSharedPreferenceChangeListener(this);
+ imageLoader.clearMemoryCache();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ outState.putString(VIDEO_URL_KEY, videoUrl);
+ outState.putString(VIDEO_TITLE_KEY, videoTitle);
+ outState.putInt(SERVICE_ID_KEY, serviceId);
+ outState.putSerializable(STACK_KEY, stack);
}
@Override
@@ -186,9 +287,8 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case ReCaptchaActivity.RECAPTCHA_REQUEST:
- if (resultCode == RESULT_OK) {
- String videoUrl = getIntent().getStringExtra(NavStack.URL);
- NavStack.getInstance().openDetailActivity(this, videoUrl, serviceId);
+ if (resultCode == Activity.RESULT_OK) {
+ NavigationHelper.openVideoDetail(onItemSelectedListener, serviceId, videoUrl, videoTitle);
} else Log.e(TAG, "ReCaptcha failed");
break;
default:
@@ -209,48 +309,47 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
// Init
//////////////////////////////////////////////////////////////////////////*/
- public void initViews() {
- loadingProgressBar = (ProgressBar) findViewById(R.id.detail_loading_progress_bar);
+ private void initViews(View rootView) {
+ loadingProgressBar = (ProgressBar) rootView.findViewById(R.id.detail_loading_progress_bar);
- parallaxScrollRootView = (ParallaxScrollView) findViewById(R.id.detail_main_content);
+ parallaxScrollRootView = (ParallaxScrollView) rootView.findViewById(R.id.detail_main_content);
- //thumbnailRootLayout = (RelativeLayout) findViewById(R.id.detail_thumbnail_root_layout);
- thumbnailBackgroundButton = (Button) findViewById(R.id.detail_stream_thumbnail_background_button);
- thumbnailImageView = (ImageView) findViewById(R.id.detail_thumbnail_image_view);
- thumbnailPlayButton = (ImageView) findViewById(R.id.detail_thumbnail_play_button);
+ //thumbnailRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_thumbnail_root_layout);
+ thumbnailBackgroundButton = (Button) rootView.findViewById(R.id.detail_stream_thumbnail_background_button);
+ thumbnailImageView = (ImageView) rootView.findViewById(R.id.detail_thumbnail_image_view);
+ thumbnailPlayButton = (ImageView) rootView.findViewById(R.id.detail_thumbnail_play_button);
- contentRootLayout = (RelativeLayout) findViewById(R.id.detail_content_root_layout);
+ contentRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_content_root_layout);
- videoTitleRoot = findViewById(R.id.detail_title_root_layout);
- videoTitleTextView = (TextView) findViewById(R.id.detail_video_title_view);
- videoTitleToggleArrow = (ImageView) findViewById(R.id.detail_toggle_description_view);
- videoCountView = (TextView) findViewById(R.id.detail_view_count_view);
+ videoTitleRoot = rootView.findViewById(R.id.detail_title_root_layout);
+ videoTitleTextView = (TextView) rootView.findViewById(R.id.detail_video_title_view);
+ videoTitleToggleArrow = (ImageView) rootView.findViewById(R.id.detail_toggle_description_view);
+ videoCountView = (TextView) rootView.findViewById(R.id.detail_view_count_view);
- videoDescriptionRootLayout = (RelativeLayout) findViewById(R.id.detail_description_root_layout);
- videoUploadDateView = (TextView) findViewById(R.id.detail_upload_date_view);
- videoDescriptionView = (TextView) findViewById(R.id.detail_description_view);
+ videoDescriptionRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_description_root_layout);
+ videoUploadDateView = (TextView) rootView.findViewById(R.id.detail_upload_date_view);
+ videoDescriptionView = (TextView) rootView.findViewById(R.id.detail_description_view);
- //thumbsRootLayout = (LinearLayout) findViewById(R.id.detail_thumbs_root_layout);
- thumbsUpTextView = (TextView) findViewById(R.id.detail_thumbs_up_count_view);
- thumbsUpImageView = (ImageView) findViewById(R.id.detail_thumbs_up_img_view);
- thumbsDownTextView = (TextView) findViewById(R.id.detail_thumbs_down_count_view);
- thumbsDownImageView = (ImageView) findViewById(R.id.detail_thumbs_down_img_view);
- thumbsDisabledTextView = (TextView) findViewById(R.id.detail_thumbs_disabled_view);
+ //thumbsRootLayout = (LinearLayout) rootView.findViewById(R.id.detail_thumbs_root_layout);
+ thumbsUpTextView = (TextView) rootView.findViewById(R.id.detail_thumbs_up_count_view);
+ thumbsUpImageView = (ImageView) rootView.findViewById(R.id.detail_thumbs_up_img_view);
+ thumbsDownTextView = (TextView) rootView.findViewById(R.id.detail_thumbs_down_count_view);
+ thumbsDownImageView = (ImageView) rootView.findViewById(R.id.detail_thumbs_down_img_view);
+ thumbsDisabledTextView = (TextView) rootView.findViewById(R.id.detail_thumbs_disabled_view);
- //uploaderRootLayout = (FrameLayout) findViewById(R.id.detail_uploader_root_layout);
- uploaderButton = (Button) findViewById(R.id.detail_uploader_button);
- uploaderTextView = (TextView) findViewById(R.id.detail_uploader_text_view);
- uploaderThumb = (ImageView) findViewById(R.id.detail_uploader_thumbnail_view);
+ //uploaderRootLayout = (FrameLayout) rootView.findViewById(R.id.detail_uploader_root_layout);
+ uploaderButton = (Button) rootView.findViewById(R.id.detail_uploader_button);
+ uploaderTextView = (TextView) rootView.findViewById(R.id.detail_uploader_text_view);
+ uploaderThumb = (ImageView) rootView.findViewById(R.id.detail_uploader_thumbnail_view);
- relatedStreamRootLayout = (RelativeLayout) findViewById(R.id.detail_related_streams_root_layout);
- nextStreamTitle = (TextView) findViewById(R.id.detail_next_stream_title);
- relatedStreamsView = (LinearLayout) findViewById(R.id.detail_related_streams_view);
+ relatedStreamRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_related_streams_root_layout);
+ nextStreamTitle = (TextView) rootView.findViewById(R.id.detail_next_stream_title);
+ relatedStreamsView = (LinearLayout) rootView.findViewById(R.id.detail_related_streams_view);
- actionBarHandler = new ActionBarHandler(this);
- actionBarHandler.setupNavMenu(this);
+ actionBarHandler = new ActionBarHandler(activity);
videoDescriptionView.setMovementMethod(LinkMovementMethod.getInstance());
- infoItemBuilder = new InfoItemBuilder(this, findViewById(android.R.id.content));
+ infoItemBuilder = new InfoItemBuilder(activity, rootView.findViewById(android.R.id.content));
setHeightThumbnail();
}
@@ -279,15 +378,16 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
infoItemBuilder.setOnStreamInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
- public void selected(String url, int serviceId) {
- NavStack.getInstance().openDetailActivity(VideoItemDetailActivity.this, url, serviceId);
+ public void selected(int serviceId, String url, String title) {
+ //NavigationHelper.openVideoDetail(activity, url, serviceId);
+ selectAndLoadVideo(serviceId, url, title);
}
});
uploaderButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
- NavStack.getInstance().openChannelActivity(VideoItemDetailActivity.this, currentStreamInfo.channel_url, currentStreamInfo.service_id);
+ NavigationHelper.openChannel(onItemSelectedListener, currentStreamInfo.service_id, currentStreamInfo.channel_url, currentStreamInfo.uploader);
}
});
}
@@ -311,14 +411,14 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
Intent intent = new Intent(AbstractPlayer.ACTION_UPDATE_THUMB);
intent.putExtra(AbstractPlayer.VIDEO_URL, currentStreamInfo.webpage_url);
- sendBroadcast(intent);
+ activity.sendBroadcast(intent);
}
}
@Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
- ErrorActivity.reportError(VideoItemDetailActivity.this,
- failReason.getCause(), null, findViewById(android.R.id.content),
+ ErrorActivity.reportError(activity,
+ failReason.getCause(), null, activity.findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
NewPipe.getNameOfService(currentStreamInfo.service_id), imageUri,
R.string.could_not_load_thumbnails));
@@ -330,7 +430,7 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
if (info.uploader_thumbnail_url != null && !info.uploader_thumbnail_url.isEmpty()) {
imageLoader.displayImage(info.uploader_thumbnail_url,
uploaderThumb, displayImageOptions,
- new ImageErrorLoadingListener(this, findViewById(android.R.id.content), info.service_id));
+ new ImageErrorLoadingListener(activity, activity.findViewById(android.R.id.content), info.service_id));
}
}
@@ -341,15 +441,15 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
nextStreamTitle.setVisibility(View.VISIBLE);
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, info.next_video));
relatedStreamsView.addView(getSeparatorView());
- relatedStreamsView.setVisibility(View.VISIBLE);
+ relatedStreamRootLayout.setVisibility(View.VISIBLE);
} else nextStreamTitle.setVisibility(View.GONE);
if (info.related_streams != null && !info.related_streams.isEmpty() && showRelatedStreams) {
for (InfoItem item : info.related_streams) {
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, item));
}
- relatedStreamsView.setVisibility(View.VISIBLE);
- } else if (info.next_video == null) relatedStreamsView.setVisibility(View.GONE);
+ relatedStreamRootLayout.setVisibility(View.VISIBLE);
+ } else if (info.next_video == null) relatedStreamRootLayout.setVisibility(View.GONE);
}
/*//////////////////////////////////////////////////////////////////////////
@@ -357,21 +457,29 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
//////////////////////////////////////////////////////////////////////////*/
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
- actionBarHandler.setupMenu(menu, getMenuInflater());
- return super.onCreateOptionsMenu(menu);
+ public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
+ actionBarHandler.setupMenu(menu, inflater);
+ actionBarHandler.setupNavMenu(activity);
+ ActionBar supportActionBar = activity.getSupportActionBar();
+ if (supportActionBar != null) {
+ supportActionBar.setDisplayHomeAsUpEnabled(true);
+ supportActionBar.setDisplayShowTitleEnabled(false);
+ //noinspection deprecation
+ supportActionBar.setNavigationMode(0);
+ }
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- if (item.getItemId() == android.R.id.home) {
- NavStack.getInstance().openMainActivity(this);
- return true;
- }
return actionBarHandler.onItemSelected(item) || super.onOptionsItemSelected(item);
}
private void setupActionBarHandler(final StreamInfo info) {
+ if (activity.getSupportActionBar() != null) {
+ //noinspection deprecation
+ activity.getSupportActionBar().setNavigationMode(ActionBar.NAVIGATION_MODE_LIST);
+ }
+
actionBarHandler.setupStreamList(info.video_streams);
actionBarHandler.setOnShareListener(new ActionBarHandler.OnActionListener() {
@Override
@@ -382,7 +490,7 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, info.webpage_url);
intent.setType("text/plain");
- startActivity(Intent.createChooser(intent, VideoItemDetailActivity.this.getString(R.string.share_dialog_title)));
+ startActivity(Intent.createChooser(intent, activity.getString(R.string.share_dialog_title)));
}
});
@@ -394,7 +502,7 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(info.webpage_url));
- startActivity(Intent.createChooser(intent, VideoItemDetailActivity.this.getString(R.string.choose_browser)));
+ startActivity(Intent.createChooser(intent, activity.getString(R.string.choose_browser)));
}
});
@@ -403,21 +511,21 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
public void onActionSelected(int selectedStreamId) {
if (isLoading.get()) return;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !PermissionHelper.checkSystemAlertWindowPermission(VideoItemDetailActivity.this)) {
- Toast.makeText(VideoItemDetailActivity.this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !PermissionHelper.checkSystemAlertWindowPermission(activity)) {
+ Toast.makeText(activity, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
return;
}
if (streamThumbnail != null) ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
- Intent i = new Intent(VideoItemDetailActivity.this, PopupVideoPlayer.class);
- Toast.makeText(VideoItemDetailActivity.this, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
+ Intent i = new Intent(activity, PopupVideoPlayer.class);
+ Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
i.putExtra(AbstractPlayer.VIDEO_TITLE, info.title)
.putExtra(AbstractPlayer.CHANNEL_NAME, info.uploader)
.putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url)
.putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, selectedStreamId)
.putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, new ArrayList<>(info.video_streams));
if (info.start_position > 0) i.putExtra(AbstractPlayer.START_POSITION, info.start_position * 1000);
- VideoItemDetailActivity.this.startService(i);
+ activity.startService(i);
}
});
@@ -430,18 +538,18 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setPackage(KORE_PACKET);
intent.setData(Uri.parse(info.webpage_url.replace("https", "http")));
- VideoItemDetailActivity.this.startActivity(intent);
+ activity.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
- AlertDialog.Builder builder = new AlertDialog.Builder(VideoItemDetailActivity.this);
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(R.string.kore_not_found)
.setPositiveButton(R.string.install, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
- intent.setData(Uri.parse(VideoItemDetailActivity.this.getString(R.string.fdroid_kore_url)));
- VideoItemDetailActivity.this.startActivity(intent);
+ intent.setData(Uri.parse(activity.getString(R.string.fdroid_kore_url)));
+ activity.startActivity(intent);
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@@ -459,7 +567,7 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
@Override
public void onActionSelected(int selectedStreamId) {
- if (isLoading.get() || !PermissionHelper.checkStoragePermissions(VideoItemDetailActivity.this)) {
+ if (isLoading.get() || !PermissionHelper.checkStoragePermissions(activity)) {
return;
}
@@ -471,7 +579,7 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
if (info.audio_streams != null) {
AudioStream audioStream =
- info.audio_streams.get(getPreferredAudioStreamId(info));
+ info.audio_streams.get(Utils.getPreferredAudioFormat(activity, info.audio_streams));
String audioSuffix = "." + MediaFormat.getSuffixById(audioStream.format);
args.putString(DownloadDialog.AUDIO_URL, audioStream.url);
@@ -487,9 +595,9 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
args.putString(DownloadDialog.TITLE, info.title);
DownloadDialog downloadDialog = DownloadDialog.newInstance(args);
- downloadDialog.show(VideoItemDetailActivity.this.getSupportFragmentManager(), "downloadDialog");
+ downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
} catch (Exception e) {
- Toast.makeText(VideoItemDetailActivity.this,
+ Toast.makeText(activity,
R.string.could_not_setup_download_menu, Toast.LENGTH_LONG).show();
e.printStackTrace();
}
@@ -504,17 +612,17 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
public void onActionSelected(int selectedStreamId) {
if (isLoading.get()) return;
- boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(VideoItemDetailActivity.this)
- .getBoolean(VideoItemDetailActivity.this.getString(R.string.use_external_audio_player_key), false);
+ boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity)
+ .getBoolean(activity.getString(R.string.use_external_audio_player_key), false);
Intent intent;
AudioStream audioStream =
- info.audio_streams.get(getPreferredAudioStreamId(info));
+ info.audio_streams.get(Utils.getPreferredAudioFormat(activity, info.audio_streams));
if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 18) {
//internal music player: explicit intent
if (!BackgroundPlayer.isRunning && streamThumbnail != null) {
ActivityCommunicator.getCommunicator()
.backgroundPlayerThumbnail = streamThumbnail;
- intent = new Intent(VideoItemDetailActivity.this, BackgroundPlayer.class);
+ intent = new Intent(activity, BackgroundPlayer.class);
intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(audioStream.url),
@@ -523,7 +631,7 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
intent.putExtra(BackgroundPlayer.WEB_URL, info.webpage_url);
intent.putExtra(BackgroundPlayer.SERVICE_ID, serviceId);
intent.putExtra(BackgroundPlayer.CHANNEL_NAME, info.uploader);
- VideoItemDetailActivity.this.startService(intent);
+ activity.startService(intent);
}
} else {
intent = new Intent();
@@ -534,18 +642,18 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
intent.putExtra(Intent.EXTRA_TITLE, info.title);
intent.putExtra("title", info.title);
// HERE !!!
- VideoItemDetailActivity.this.startActivity(intent);
+ activity.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
- AlertDialog.Builder builder = new AlertDialog.Builder(VideoItemDetailActivity.this);
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(R.string.no_player_found)
.setPositiveButton(R.string.install, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW);
- intent.setData(Uri.parse(VideoItemDetailActivity.this.getString(R.string.fdroid_vlc_url)));
- VideoItemDetailActivity.this.startActivity(intent);
+ intent.setData(Uri.parse(activity.getString(R.string.fdroid_vlc_url)));
+ activity.startActivity(intent);
}
})
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@@ -564,32 +672,109 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
}
}
+ /*//////////////////////////////////////////////////////////////////////////
+ // OwnStack
+ //////////////////////////////////////////////////////////////////////////*/
+
+ /**
+ * Stack that contains the "navigation history".
+ * The peek is the current video.
+ */
+ private final Stack stack = new Stack<>();
+
+ public void clearHistory() {
+ stack.clear();
+ }
+
+ public void pushToStack(String videoUrl, String videoTitle) {
+
+ if (stack.size() > 0 && stack.peek().getUrl().equals(videoUrl)) return;
+ stack.push(new StackItem(videoUrl, videoTitle));
+
+ }
+
+ public void setTitleToUrl(String videoUrl, String videoTitle) {
+ if (videoTitle != null && !videoTitle.isEmpty()) {
+ for (StackItem stackItem : stack) {
+ if (stackItem.getUrl().equals(videoUrl)) stackItem.setTitle(videoTitle);
+ }
+ }
+ }
+
+ public boolean onActivityBackPressed() {
+ // That means that we are on the start of the stack,
+ // return false to let the MainActivity handle the onBack
+ if (stack.size() == 1) return false;
+ // Remove top
+ stack.pop();
+ // Get url from the new top
+ StackItem peek = stack.peek();
+ selectAndLoadVideo(0, peek.getUrl(),
+ peek.getTitle() != null && !peek.getTitle().isEmpty() ? peek.getTitle() : ""
+ );
+ return true;
+ }
+
+
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
- private void handleIntent(Intent intent) {
- if (intent == null) return;
- serviceId = intent.getIntExtra(NavStack.SERVICE_ID, 0);
- videoUrl = intent.getStringExtra(NavStack.URL);
- autoPlayEnabled = intent.getBooleanExtra(AUTO_PLAY, false);
- selectVideo(videoUrl, serviceId);
+ public void setAutoplay(boolean autoplay) {
+ this.autoPlayEnabled = autoplay;
}
- private void selectVideo(String url, int serviceId) {
- if (curExtractorThread != null && curExtractorThread.isRunning()) curExtractorThread.cancel();
+ public void selectVideo(int serviceId, String videoUrl, String videoTitle) {
+ this.videoUrl = videoUrl;
+ this.videoTitle = videoTitle;
+ this.serviceId = serviceId;
+ }
- animateView(contentRootLayout, false, 200, null);
+ public void selectAndLoadVideo(int serviceId, String videoUrl, String videoTitle) {
+ selectVideo(serviceId, videoUrl, videoTitle);
+ loadSelectedVideo();
+ }
- thumbnailPlayButton.setVisibility(View.GONE);
+ public void loadSelectedVideo() {
+ pushToStack(videoUrl, videoTitle);
+
+ if (curExtractorWorker != null && curExtractorWorker.isRunning()) curExtractorWorker.cancel();
+
+ if (activity.getSupportActionBar() != null) {
+ //noinspection deprecation
+ activity.getSupportActionBar().setNavigationMode(0);
+ }
+
+ animateView(contentRootLayout, false, 50, null);
+
+ videoTitleTextView.setMaxLines(1);
+ int scrollY = parallaxScrollRootView.getScrollY();
+ if (scrollY < 30) animateView(videoTitleTextView, false, 200, new Runnable() {
+ @Override
+ public void run() {
+ videoTitleTextView.setText(videoTitle != null ? videoTitle : "");
+ animateView(videoTitleTextView, true, 400, null);
+ }
+ });
+ else videoTitleTextView.setText(videoTitle != null ? videoTitle : "");
+ //videoTitleTextView.setText(videoTitle != null ? videoTitle : "");
+ videoDescriptionRootLayout.setVisibility(View.GONE);
+ videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
+ videoTitleToggleArrow.setVisibility(View.GONE);
+ videoTitleRoot.setClickable(false);
+
+ //thumbnailPlayButton.setVisibility(View.GONE);
+ animateView(thumbnailPlayButton, false, 50, null);
loadingProgressBar.setVisibility(View.VISIBLE);
imageLoader.cancelDisplayTask(thumbnailImageView);
imageLoader.cancelDisplayTask(uploaderThumb);
- thumbnailImageView.setImageDrawable(null);
+ thumbnailImageView.setImageBitmap(null);
+ uploaderThumb.setImageBitmap(null);
- curExtractorThread = StreamExtractorWorker.startExtractorThread(serviceId, url, this, this);
+ curExtractorWorker = new StreamExtractorWorker(activity, serviceId, videoUrl, this);
+ curExtractorWorker.start();
isLoading.set(true);
}
@@ -597,7 +782,7 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
// ----------- THE MAGIC MOMENT ---------------
VideoStream selectedVideoStream = info.video_streams.get(actionBarHandler.getSelectedVideoStream());
- if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(this.getString(R.string.use_external_video_player_key), false)) {
+ if (PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(this.getString(R.string.use_external_video_player_key), false)) {
// External Player
Intent intent = new Intent();
@@ -609,7 +794,7 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
this.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(R.string.no_player_found)
.setPositiveButton(R.string.install, new DialogInterface.OnClickListener() {
@Override
@@ -629,14 +814,13 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
}
} else {
Intent intent;
- boolean useOldPlayer = PreferenceManager
- .getDefaultSharedPreferences(this)
+ boolean useOldPlayer = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(getString(R.string.use_old_player_key), false)
|| (Build.VERSION.SDK_INT < 16);
if (!useOldPlayer) {
// ExoPlayer
if (streamThumbnail != null) ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
- intent = new Intent(this, ExoPlayerActivity.class)
+ intent = new Intent(activity, ExoPlayerActivity.class)
.putExtra(AbstractPlayer.VIDEO_TITLE, info.title)
.putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url)
.putExtra(AbstractPlayer.CHANNEL_NAME, info.uploader)
@@ -645,46 +829,19 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
if (info.start_position > 0) intent.putExtra(AbstractPlayer.START_POSITION, info.start_position * 1000);
} else {
// Internal Player
- intent = new Intent(this, PlayVideoActivity.class)
+ intent = new Intent(activity, PlayVideoActivity.class)
.putExtra(PlayVideoActivity.VIDEO_TITLE, info.title)
.putExtra(PlayVideoActivity.STREAM_URL, selectedVideoStream.url)
.putExtra(PlayVideoActivity.VIDEO_URL, info.webpage_url)
.putExtra(PlayVideoActivity.START_POSITION, info.start_position);
}
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
}
- private int getPreferredAudioStreamId(final StreamInfo info) {
- String preferredFormatString = PreferenceManager.getDefaultSharedPreferences(this)
- .getString(getString(R.string.default_audio_format_key), "webm");
-
- int preferredFormat = MediaFormat.WEBMA.id;
- switch (preferredFormatString) {
- case "webm":
- preferredFormat = MediaFormat.WEBMA.id;
- break;
- case "m4a":
- preferredFormat = MediaFormat.M4A.id;
- break;
- default:
- break;
- }
-
- for (int i = 0; i < info.audio_streams.size(); i++) {
- if (info.audio_streams.get(i).format == preferredFormat) {
- return i;
- }
- }
-
- //todo: make this a proper error
- Log.e(TAG, "FAILED to set audioStream value!");
- return 0;
- }
-
private View getSeparatorView() {
- View separator = new View(this);
+ View separator = new View(activity);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1);
int m8 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics());
int m5 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getResources().getDisplayMetrics());
@@ -692,7 +849,7 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
separator.setLayoutParams(params);
TypedValue typedValue = new TypedValue();
- getTheme().resolveAttribute(R.attr.separatorColor, typedValue, true);
+ activity.getTheme().resolveAttribute(R.attr.separatorColor, typedValue, true);
separator.setBackgroundColor(typedValue.data);
return separator;
}
@@ -762,11 +919,11 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
@Override
public void onReceive(StreamInfo info) {
- currentStreamInfo = info;
+ if (info == null || isRemoving() || !isVisible()) return;
+ currentStreamInfo = info;
loadingProgressBar.setVisibility(View.GONE);
- thumbnailPlayButton.setVisibility(View.VISIBLE);
- relatedStreamRootLayout.setVisibility(showRelatedStreams ? View.VISIBLE : View.GONE);
+ animateView(thumbnailPlayButton, true, 200, null);
parallaxScrollRootView.scrollTo(0, 0);
// Since newpipe is designed to work even if certain information is not available,
@@ -775,9 +932,9 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
if (!info.uploader.isEmpty()) uploaderTextView.setText(info.uploader);
uploaderTextView.setVisibility(!info.uploader.isEmpty() ? View.VISIBLE : View.GONE);
uploaderButton.setVisibility(!info.channel_url.isEmpty() ? View.VISIBLE : View.GONE);
- uploaderThumb.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.buddy));
+ uploaderThumb.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.buddy));
- if (info.view_count >= 0) videoCountView.setText(Localization.localizeViewCount(info.view_count, this));
+ if (info.view_count >= 0) videoCountView.setText(Localization.localizeViewCount(info.view_count, activity));
videoCountView.setVisibility(info.view_count >= 0 ? View.VISIBLE : View.GONE);
if (info.dislike_count == -1 && info.like_count == -1) {
@@ -790,54 +947,64 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
} else {
thumbsDisabledTextView.setVisibility(View.GONE);
- if (info.dislike_count >= 0) thumbsDownTextView.setText(Localization.localizeNumber(info.dislike_count, this));
+ if (info.dislike_count >= 0) thumbsDownTextView.setText(Localization.localizeNumber(info.dislike_count, activity));
thumbsDownTextView.setVisibility(info.dislike_count >= 0 ? View.VISIBLE : View.GONE);
thumbsDownImageView.setVisibility(info.dislike_count >= 0 ? View.VISIBLE : View.GONE);
- if (info.like_count >= 0) thumbsUpTextView.setText(Localization.localizeNumber(info.like_count, this));
+ if (info.like_count >= 0) thumbsUpTextView.setText(Localization.localizeNumber(info.like_count, activity));
thumbsUpTextView.setVisibility(info.like_count >= 0 ? View.VISIBLE : View.GONE);
thumbsUpImageView.setVisibility(info.like_count >= 0 ? View.VISIBLE : View.GONE);
}
- if (!info.upload_date.isEmpty()) videoUploadDateView.setText(Localization.localizeDate(info.upload_date, this));
+ if (!info.upload_date.isEmpty()) videoUploadDateView.setText(Localization.localizeDate(info.upload_date, activity));
videoUploadDateView.setVisibility(!info.upload_date.isEmpty() ? View.VISIBLE : View.GONE);
- if (!info.description.isEmpty()) videoDescriptionView.setText(
- Build.VERSION.SDK_INT >= 24 ? Html.fromHtml(info.description, 0) : Html.fromHtml(info.description)
- );
+ if (!info.description.isEmpty()) { //noinspection deprecation
+ videoDescriptionView.setText(Build.VERSION.SDK_INT >= 24 ? Html.fromHtml(info.description, 0) : Html.fromHtml(info.description));
+ }
videoDescriptionView.setVisibility(!info.description.isEmpty() ? View.VISIBLE : View.GONE);
videoDescriptionRootLayout.setVisibility(View.GONE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
+ videoTitleToggleArrow.setVisibility(View.VISIBLE);
+ videoTitleRoot.setClickable(true);
setupActionBarHandler(info);
initRelatedVideos(info);
initThumbnailViews(info);
+ setTitleToUrl(info.webpage_url, info.title);
+
animateView(contentRootLayout, true, 200, null);
+ if (autoPlayEnabled) {
+ playVideo(info);
+ // Only auto play in the first open
+ autoPlayEnabled = false;
+ }
+
isLoading.set(false);
- if (autoPlayEnabled) playVideo(info);
}
@Override
public void onError(int messageId) {
- Toast.makeText(this, messageId, Toast.LENGTH_LONG).show();
+ Toast.makeText(activity, messageId, Toast.LENGTH_LONG).show();
loadingProgressBar.setVisibility(View.GONE);
- thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.not_available_monkey));
+ videoTitleTextView.setText(getString(messageId));
+ thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.not_available_monkey));
}
@Override
public void onReCaptchaException() {
- Toast.makeText(this, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
+ Toast.makeText(activity, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show();
// Starting ReCaptcha Challenge Activity
- startActivityForResult(new Intent(this, ReCaptchaActivity.class), ReCaptchaActivity.RECAPTCHA_REQUEST);
+ startActivityForResult(new Intent(activity, ReCaptchaActivity.class), ReCaptchaActivity.RECAPTCHA_REQUEST);
}
@Override
public void onBlockedByGemaError() {
loadingProgressBar.setVisibility(View.GONE);
- thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.gruese_die_gema));
+ thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.gruese_die_gema));
thumbnailBackgroundButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -848,20 +1015,20 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
}
});
- Toast.makeText(this, R.string.blocked_by_gema, Toast.LENGTH_LONG).show();
+ Toast.makeText(activity, R.string.blocked_by_gema, Toast.LENGTH_LONG).show();
}
@Override
public void onContentErrorWithMessage(int messageId) {
loadingProgressBar.setVisibility(View.GONE);
- thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.not_available_monkey));
- Toast.makeText(this, messageId, Toast.LENGTH_LONG).show();
+ thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.not_available_monkey));
+ Toast.makeText(activity, messageId, Toast.LENGTH_LONG).show();
}
@Override
public void onContentError() {
loadingProgressBar.setVisibility(View.GONE);
- thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.not_available_monkey));
- Toast.makeText(this, R.string.content_not_available, Toast.LENGTH_LONG).show();
+ thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.not_available_monkey));
+ Toast.makeText(activity, R.string.content_not_available, Toast.LENGTH_LONG).show();
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java
similarity index 54%
rename from app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java
rename to app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java
index cb3869c01..e95473b2b 100644
--- a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchInfoItemFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/search/SearchFragment.java
@@ -1,10 +1,13 @@
-package org.schabi.newpipe.search_fragment;
+package org.schabi.newpipe.fragments.search;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
+import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView;
@@ -24,10 +27,11 @@ import org.schabi.newpipe.ReCaptchaActivity;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SearchResult;
+import org.schabi.newpipe.fragments.OnItemSelectedListener;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.report.ErrorActivity;
-import org.schabi.newpipe.util.NavStack;
+import org.schabi.newpipe.util.NavigationHelper;
import java.util.EnumSet;
@@ -36,120 +40,87 @@ import static org.schabi.newpipe.ReCaptchaActivity.RECAPTCHA_REQUEST;
/**
* Created by Christian Schabesberger on 02.08.16.
- *
+ *
* Copyright (C) Christian Schabesberger 2016
- * SearchInfoItemFragment.java is part of NewPipe.
- *
+ * SearchFragment.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 SearchInfoItemFragment extends Fragment {
+public class SearchFragment extends Fragment implements SearchView.OnQueryTextListener, SearchWorker.SearchWorkerResultListener {
- private static final String TAG = SearchInfoItemFragment.class.toString();
-
- private EnumSet filter =
- EnumSet.of(SearchEngine.Filter.CHANNEL, SearchEngine.Filter.STREAM);
-
- /**
- * Listener for search queries
- */
- public class SearchQueryListener implements SearchView.OnQueryTextListener {
-
- @Override
- public boolean onQueryTextSubmit(String query) {
- Activity a = getActivity();
- try {
- 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,
- NewPipe.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();
- }
- 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 int pageNumber = 0;
- private SuggestionListAdapter suggestionListAdapter = null;
- private InfoListAdapter infoListAdapter = null;
- private LinearLayoutManager streamInfoListLayoutManager = null;
+ private static final String TAG = SearchFragment.class.toString();
// savedInstanceBundle arguments
private static final String QUERY = "query";
private static final String STREAMING_SERVICE = "streaming_service";
+ private int streamingServiceId = -1;
+ private String searchQuery = "";
+ private boolean isLoading = false;
+
+ @SuppressWarnings("FieldCanBeLocal")
+ private SearchView searchView;
+ private RecyclerView recyclerView;
+ private ProgressBar loadingIndicator;
+ private int pageNumber = 0;
+ private SuggestionListAdapter suggestionListAdapter;
+ private InfoListAdapter infoListAdapter;
+ private LinearLayoutManager streamInfoListLayoutManager;
+
+ private EnumSet filter = EnumSet.of(SearchEngine.Filter.CHANNEL, SearchEngine.Filter.STREAM);
+ private OnItemSelectedListener onItemSelectedListener;
+
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
- public SearchInfoItemFragment() {
+ public SearchFragment() {
}
@SuppressWarnings("unused")
- public static SearchInfoItemFragment newInstance(int streamingServiceId, String searchQuery) {
+ public static SearchFragment newInstance(int streamingServiceId, String searchQuery) {
Bundle args = new Bundle();
args.putInt(STREAMING_SERVICE, streamingServiceId);
args.putString(QUERY, searchQuery);
- SearchInfoItemFragment fragment = new SearchInfoItemFragment();
+ SearchFragment fragment = new SearchFragment();
fragment.setArguments(args);
return fragment;
}
+ /*//////////////////////////////////////////////////////////////////////////
+ // Fragment's LifeCycle
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ onItemSelectedListener = ((OnItemSelectedListener) context);
+ }
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
searchQuery = "";
+ isLoading = false;
if (savedInstanceState != null) {
searchQuery = savedInstanceState.getString(QUERY);
streamingServiceId = savedInstanceState.getInt(STREAMING_SERVICE);
} else {
try {
Bundle args = getArguments();
- if(args != null) {
+ if (args != null) {
searchQuery = args.getString(QUERY);
streamingServiceId = args.getInt(STREAMING_SERVICE);
} else {
@@ -168,50 +139,16 @@ public class SearchInfoItemFragment extends Fragment {
setHasOptionsMenu(true);
SearchWorker sw = SearchWorker.getInstance();
- sw.setSearchWorkerResultListener(new SearchWorker.SearchWorkerResultListener() {
- @Override
- public void onResult(SearchResult result) {
- infoListAdapter.addInfoItemList(result.resultList);
- setDoneLoading();
- }
-
- @Override
- public void onNothingFound(int stringResource) {
- //setListShown(true);
- Toast.makeText(getActivity(), getString(stringResource),
- Toast.LENGTH_SHORT).show();
- setDoneLoading();
- }
-
- @Override
- public void onError(String message) {
- //setListShown(true);
- Toast.makeText(getActivity(), message,
- Toast.LENGTH_LONG).show();
- setDoneLoading();
- }
-
- @Override
- public void onReCaptchaChallenge() {
- Toast.makeText(getActivity(), "ReCaptcha Challenge requested",
- Toast.LENGTH_LONG).show();
-
- // Starting ReCaptcha Challenge Activity
- startActivityForResult(
- new Intent(getActivity(), ReCaptchaActivity.class),
- RECAPTCHA_REQUEST);
- }
- });
+ sw.setSearchWorkerResultListener(this);
}
@Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View view = inflater.inflate(R.layout.fragment_searchinfoitem, container, false);
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View view = inflater.inflate(R.layout.fragment_search, container, false);
Context context = view.getContext();
- loadingIndicator = (ProgressBar) view.findViewById(R.id.progressBar);
- RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list);
+ loadingIndicator = (ProgressBar) view.findViewById(R.id.loading_progress_bar);
+ recyclerView = (RecyclerView) view.findViewById(R.id.list);
streamInfoListLayoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(streamInfoListLayoutManager);
@@ -219,19 +156,16 @@ public class SearchInfoItemFragment extends Fragment {
getActivity().findViewById(android.R.id.content));
infoListAdapter.setFooter(inflater.inflate(R.layout.pignate_footer, recyclerView, false));
infoListAdapter.showFooter(false);
- infoListAdapter.setOnStreamInfoItemSelectedListener(
- new InfoItemBuilder.OnInfoItemSelectedListener() {
+ infoListAdapter.setOnStreamInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
- public void selected(String url, int serviceId) {
- NavStack.getInstance()
- .openDetailActivity(getContext(), url, serviceId);
+ public void selected(int serviceId, String url, String title) {
+ NavigationHelper.openVideoDetail(onItemSelectedListener, serviceId, url, title);
}
});
infoListAdapter.setOnChannelInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override
- public void selected(String url, int serviceId) {
- NavStack.getInstance()
- .openChannelActivity(getContext(), url, serviceId);
+ public void selected(int serviceId, String url, String title) {
+ NavigationHelper.openChannel(onItemSelectedListener, serviceId, url, title);
}
});
recyclerView.setAdapter(infoListAdapter);
@@ -249,6 +183,13 @@ public class SearchInfoItemFragment extends Fragment {
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount && !isLoading) {
pageNumber++;
+ recyclerView.post(new Runnable() {
+ @Override
+ public void run() {
+ infoListAdapter.showFooter(true);
+
+ }
+ });
search(searchQuery, pageNumber);
}
}
@@ -259,13 +200,35 @@ public class SearchInfoItemFragment extends Fragment {
}
@Override
- public void onStart() {
- super.onStart();
- if(!searchQuery.isEmpty()) {
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ if (!searchQuery.isEmpty()) {
search(searchQuery);
}
}
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ recyclerView.removeAllViews();
+ infoListAdapter.clearSteamItemList();
+ recyclerView = null;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (isLoading && !searchQuery.isEmpty()) {
+ search(searchQuery);
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ SearchWorker.getInstance().terminate();
+ }
+
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
@@ -273,19 +236,45 @@ public class SearchInfoItemFragment extends Fragment {
outState.putInt(STREAMING_SERVICE, streamingServiceId);
}
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case RECAPTCHA_REQUEST:
+ if (resultCode == RESULT_OK && searchQuery.length() != 0) {
+ search(searchQuery);
+ } else Log.e(TAG, "ReCaptcha failed");
+ break;
+
+ default:
+ Log.e(TAG, "Request code from activity not supported [" + requestCode + "]");
+ break;
+ }
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Menu
+ //////////////////////////////////////////////////////////////////////////*/
+
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, inflater);
+ ActionBar supportActionBar = ((AppCompatActivity) getActivity()).getSupportActionBar();
+ if (supportActionBar != null) {
+ supportActionBar.setDisplayHomeAsUpEnabled(false);
+ supportActionBar.setDisplayShowTitleEnabled(false);
+ //noinspection deprecation
+ supportActionBar.setNavigationMode(0);
+ }
inflater.inflate(R.menu.search_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search);
- SearchView searchView = (SearchView) searchItem.getActionView();
+ searchView = (SearchView) searchItem.getActionView();
setupSearchView(searchView);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch(item.getItemId()) {
+ switch (item.getItemId()) {
case R.id.menu_filter_all:
changeFilter(item, EnumSet.of(SearchEngine.Filter.STREAM, SearchEngine.Filter.CHANNEL));
return true;
@@ -300,11 +289,15 @@ public class SearchInfoItemFragment extends Fragment {
}
}
+ /*//////////////////////////////////////////////////////////////////////////
+ // Utils
+ //////////////////////////////////////////////////////////////////////////*/
+
private void changeFilter(MenuItem item, EnumSet filter) {
this.filter = filter;
item.setChecked(true);
- if(searchQuery != null && !searchQuery.isEmpty()) {
- Log.d(TAG, "Fuck+ " + searchQuery);
+ if (searchQuery != null && !searchQuery.isEmpty()) {
+ Log.e(TAG, "Fuck+ " + searchQuery);
search(searchQuery);
}
}
@@ -313,7 +306,7 @@ public class SearchInfoItemFragment extends Fragment {
suggestionListAdapter = new SuggestionListAdapter(getActivity());
searchView.setSuggestionsAdapter(suggestionListAdapter);
searchView.setOnSuggestionListener(new SearchSuggestionListener(searchView, suggestionListAdapter));
- searchView.setOnQueryTextListener(new SearchQueryListener());
+ searchView.setOnQueryTextListener(this);
if (searchQuery != null && !searchQuery.isEmpty()) {
searchView.setQuery(searchQuery, false);
searchView.setIconifiedByDefault(false);
@@ -341,9 +334,9 @@ public class SearchInfoItemFragment extends Fragment {
}
private void setDoneLoading() {
- this.isLoading = false;
+ isLoading = false;
loadingIndicator.setVisibility(View.GONE);
- infoListAdapter.showFooter(true);
+ infoListAdapter.showFooter(false);
}
/**
@@ -351,7 +344,7 @@ public class SearchInfoItemFragment extends Fragment {
*/
private void hideBackground() {
View view = getView();
- if(view == null) return;
+ if (view == null) return;
view.findViewById(R.id.mainBG).setVisibility(View.GONE);
}
@@ -362,22 +355,86 @@ public class SearchInfoItemFragment extends Fragment {
suggestionThread.start();
}
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data) {
- switch (requestCode) {
- case RECAPTCHA_REQUEST:
- if (resultCode == RESULT_OK) {
- if (searchQuery.length() != 0) {
- search(searchQuery);
- }
- } else {
- Log.d(TAG, "ReCaptcha failed");
- }
- break;
-
- default:
- Log.e(TAG, "Request code from activity not supported [" + requestCode + "]");
- break;
- }
+ public boolean isMainBgVisible() {
+ return getActivity().findViewById(R.id.mainBG).getVisibility() == View.VISIBLE;
}
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // OnQueryTextListener
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public boolean onQueryTextSubmit(String query) {
+ Activity a = getActivity();
+ try {
+ 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,
+ NewPipe.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();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onQueryTextChange(String newText) {
+ if (!newText.isEmpty()) {
+ searchSuggestions(newText);
+ }
+ return true;
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // SearchWorkerResultListener
+ //////////////////////////////////////////////////////////////////////////*/
+
+ @Override
+ public void onResult(SearchResult result) {
+ infoListAdapter.addInfoItemList(result.resultList);
+ setDoneLoading();
+ }
+
+ @Override
+ public void onNothingFound(int stringResource) {
+ //setListShown(true);
+ Toast.makeText(getActivity(), getString(stringResource), Toast.LENGTH_SHORT).show();
+ setDoneLoading();
+ }
+
+ @Override
+ public void onError(String message) {
+ //setListShown(true);
+ Toast.makeText(getActivity(), message, Toast.LENGTH_LONG).show();
+ setDoneLoading();
+ }
+
+ @Override
+ public void onReCaptchaChallenge() {
+ Toast.makeText(getActivity(), "ReCaptcha Challenge requested",
+ Toast.LENGTH_LONG).show();
+
+ // Starting ReCaptcha Challenge Activity
+ startActivityForResult(new Intent(getActivity(), ReCaptchaActivity.class), RECAPTCHA_REQUEST);
+ }
+
}
diff --git a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchSuggestionListener.java b/app/src/main/java/org/schabi/newpipe/fragments/search/SearchSuggestionListener.java
similarity index 97%
rename from app/src/main/java/org/schabi/newpipe/search_fragment/SearchSuggestionListener.java
rename to app/src/main/java/org/schabi/newpipe/fragments/search/SearchSuggestionListener.java
index a3d3c0e9b..9e2a74dad 100644
--- a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchSuggestionListener.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/search/SearchSuggestionListener.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe.search_fragment;
+package org.schabi.newpipe.fragments.search;
import android.support.v7.widget.SearchView;
diff --git a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchWorker.java b/app/src/main/java/org/schabi/newpipe/fragments/search/SearchWorker.java
similarity index 99%
rename from app/src/main/java/org/schabi/newpipe/search_fragment/SearchWorker.java
rename to app/src/main/java/org/schabi/newpipe/fragments/search/SearchWorker.java
index 6a18f42dd..582df9950 100644
--- a/app/src/main/java/org/schabi/newpipe/search_fragment/SearchWorker.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/search/SearchWorker.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe.search_fragment;
+package org.schabi.newpipe.fragments.search;
import android.app.Activity;
import android.content.SharedPreferences;
@@ -7,13 +7,13 @@ import android.preference.PreferenceManager;
import android.util.Log;
import android.view.View;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SearchResult;
import org.schabi.newpipe.report.ErrorActivity;
-import org.schabi.newpipe.R;
-import org.schabi.newpipe.extractor.NewPipe;
import java.io.IOException;
import java.util.EnumSet;
@@ -209,6 +209,7 @@ public class SearchWorker {
}
public void terminate() {
+ if (runnable == null) return;
requestId++;
runnable.terminate();
}
diff --git a/app/src/main/java/org/schabi/newpipe/search_fragment/SuggestionListAdapter.java b/app/src/main/java/org/schabi/newpipe/fragments/search/SuggestionListAdapter.java
similarity index 98%
rename from app/src/main/java/org/schabi/newpipe/search_fragment/SuggestionListAdapter.java
rename to app/src/main/java/org/schabi/newpipe/fragments/search/SuggestionListAdapter.java
index c37a71895..b8ecaad1d 100644
--- a/app/src/main/java/org/schabi/newpipe/search_fragment/SuggestionListAdapter.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/search/SuggestionListAdapter.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe.search_fragment;
+package org.schabi.newpipe.fragments.search;
import android.content.Context;
import android.database.Cursor;
diff --git a/app/src/main/java/org/schabi/newpipe/search_fragment/SuggestionSearchRunnable.java b/app/src/main/java/org/schabi/newpipe/fragments/search/SuggestionSearchRunnable.java
similarity index 98%
rename from app/src/main/java/org/schabi/newpipe/search_fragment/SuggestionSearchRunnable.java
rename to app/src/main/java/org/schabi/newpipe/fragments/search/SuggestionSearchRunnable.java
index 8b8f75e7e..10800c7e3 100644
--- a/app/src/main/java/org/schabi/newpipe/search_fragment/SuggestionSearchRunnable.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/search/SuggestionSearchRunnable.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe.search_fragment;
+package org.schabi.newpipe.fragments.search;
import android.app.Activity;
import android.content.SharedPreferences;
@@ -6,11 +6,11 @@ import android.os.Handler;
import android.preference.PreferenceManager;
import android.widget.Toast;
-import org.schabi.newpipe.extractor.NewPipe;
-import org.schabi.newpipe.extractor.exceptions.ExtractionException;
-import org.schabi.newpipe.extractor.SuggestionExtractor;
-import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.SuggestionExtractor;
+import org.schabi.newpipe.extractor.exceptions.ExtractionException;
+import org.schabi.newpipe.report.ErrorActivity;
import java.io.IOException;
import java.util.List;
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java
index 4ec6123ad..20dda18a2 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/InfoItemBuilder.java
@@ -1,11 +1,10 @@
package org.schabi.newpipe.info_list;
-import android.app.Activity;
+import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.AdapterView;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
@@ -39,20 +38,21 @@ import org.schabi.newpipe.extractor.stream_info.StreamInfoItem;
public class InfoItemBuilder {
- final String viewsS;
- final String videosS;
- final String subsS;
+ private final String viewsS;
+ private final String videosS;
+ private final String subsS;
+ private final String subsPluralS;
- final String thousand;
- final String million;
- final String billion;
+ private final String thousand;
+ private final String million;
+ private final String billion;
private static final String TAG = InfoItemBuilder.class.toString();
public interface OnInfoItemSelectedListener {
- void selected(String url, int serviceId);
+ void selected(int serviceId, String url, String title);
}
- private Activity activity = null;
+ private Context mContext = null;
private View rootView = null;
private ImageLoader imageLoader = ImageLoader.getInstance();
private DisplayImageOptions displayImageOptions =
@@ -60,15 +60,16 @@ public class InfoItemBuilder {
private OnInfoItemSelectedListener onStreamInfoItemSelectedListener;
private OnInfoItemSelectedListener onChannelInfoItemSelectedListener;
- public InfoItemBuilder(Activity a, View rootView) {
- activity = a;
+ public InfoItemBuilder(Context context, View rootView) {
+ mContext = context;
this.rootView = rootView;
- viewsS = a.getString(R.string.views);
- videosS = a.getString(R.string.videos);
- subsS = a.getString(R.string.subscriber);
- thousand = a.getString(R.string.short_thousand);
- million = a.getString(R.string.short_million);
- billion = a.getString(R.string.short_billion);
+ viewsS = context.getString(R.string.views);
+ videosS = context.getString(R.string.videos);
+ subsS = context.getString(R.string.subscriber);
+ subsPluralS = context.getString(R.string.subscriber_plural);
+ thousand = context.getString(R.string.short_thousand);
+ million = context.getString(R.string.short_million);
+ billion = context.getString(R.string.short_billion);
}
public void setOnStreamInfoItemSelectedListener(
@@ -156,13 +157,13 @@ public class InfoItemBuilder {
imageLoader.displayImage(info.thumbnail_url,
holder.itemThumbnailView,
displayImageOptions,
- new ImageErrorLoadingListener(activity, rootView, info.service_id));
+ new ImageErrorLoadingListener(mContext, rootView, info.service_id));
}
holder.itemButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
- onStreamInfoItemSelectedListener.selected(info.webpage_url, info.service_id);
+ onStreamInfoItemSelectedListener.selected(info.service_id, info.webpage_url, info.getTitle());
}
});
}
@@ -178,13 +179,13 @@ public class InfoItemBuilder {
imageLoader.displayImage(info.thumbnailUrl,
holder.itemThumbnailView,
displayImageOptions,
- new ImageErrorLoadingListener(activity, rootView, info.serviceId));
+ new ImageErrorLoadingListener(mContext, rootView, info.serviceId));
}
holder.itemButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
- onChannelInfoItemSelectedListener.selected(info.getLink(), info.serviceId);
+ onChannelInfoItemSelectedListener.selected(info.serviceId, info.getLink(), info.channelName);
}
});
}
@@ -202,15 +203,17 @@ public class InfoItemBuilder {
}
}
- public String shortSubscriber(Long count){
- if(count >= 1000000000){
- return Long.toString(count/1000000000)+ billion + " " + subsS;
- }else if(count>=1000000){
- return Long.toString(count/1000000)+ million + " " + subsS;
- }else if(count>=1000){
- return Long.toString(count/1000)+ thousand + " " + subsS;
- }else {
- return Long.toString(count)+ " " + subsS;
+ public String shortSubscriber(Long count) {
+ String curSubString = count > 1 ? subsPluralS : subsS;
+
+ if (count >= 1000000000) {
+ return Long.toString(count / 1000000000) + billion + " " + curSubString;
+ } else if (count >= 1000000) {
+ return Long.toString(count / 1000000) + million + " " + curSubString;
+ } else if (count >= 1000) {
+ return Long.toString(count / 1000) + thousand + " " + curSubString;
+ } else {
+ return Long.toString(count) + " " + curSubString;
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/player/AbstractPlayer.java b/app/src/main/java/org/schabi/newpipe/player/AbstractPlayer.java
index 1c6d4822b..e05477b98 100644
--- a/app/src/main/java/org/schabi/newpipe/player/AbstractPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/AbstractPlayer.java
@@ -299,7 +299,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
return;
}
- changeState(STATE_LOADING);
isPrepared = false;
qualityChanged = false;
@@ -312,6 +311,7 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
if (videoStartPos > 0) simpleExoPlayer.seekTo(videoStartPos);
simpleExoPlayer.prepare(videoSource);
simpleExoPlayer.setPlayWhenReady(autoPlay);
+ changeState(STATE_LOADING);
}
public void destroy() {
@@ -396,7 +396,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) playbackSeekBar.getThumb().setColorFilter(Color.RED, PorterDuff.Mode.SRC_IN);
animateView(endScreen, false, 0, 0);
- animateView(controlsRoot, false, 0, 0);
loadingPanel.setBackgroundColor(Color.BLACK);
animateView(loadingPanel, true, 0, 0);
animateView(surfaceForeground, true, 100, 0);
@@ -408,7 +407,12 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
if (!isProgressLoopRunning.get()) startProgressLoop();
showAndAnimateControl(-1, true);
loadingPanel.setVisibility(View.GONE);
- animateView(controlsRoot, false, 500, DEFAULT_CONTROLS_HIDE_TIME, true);
+ animateView(controlsRoot, true, 500, 0, new Runnable() {
+ @Override
+ public void run() {
+ animateView(controlsRoot, false, 500, DEFAULT_CONTROLS_HIDE_TIME, true);
+ }
+ });
animateView(currentDisplaySeek, false, 200, 0);
}
@@ -417,7 +421,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
if (DEBUG) Log.d(TAG, "onBuffering() called");
loadingPanel.setBackgroundColor(Color.TRANSPARENT);
animateView(loadingPanel, true, 500, 0);
- animateView(controlsRoot, false, 0, 0, true);
}
@Override
@@ -598,14 +601,12 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
if (DEBUG) Log.d(TAG, "onFastRewind() called");
seekBy(-FAST_FORWARD_REWIND_AMOUNT);
showAndAnimateControl(R.drawable.ic_action_av_fast_rewind, true);
- animateView(controlsRoot, false, 100, 0);
}
public void onFastForward() {
if (DEBUG) Log.d(TAG, "onFastForward() called");
seekBy(FAST_FORWARD_REWIND_AMOUNT);
showAndAnimateControl(R.drawable.ic_action_av_fast_forward, true);
- animateView(controlsRoot, false, 100, 0);
}
/*//////////////////////////////////////////////////////////////////////////
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 88a5a914d..3c6bbad81 100644
--- a/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/BackgroundPlayer.java
@@ -24,9 +24,10 @@ import android.widget.Toast;
import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.BuildConfig;
+import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.detail.VideoItemDetailActivity;
-import org.schabi.newpipe.util.NavStack;
+import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.util.Constants;
import java.io.IOException;
import java.util.Arrays;
@@ -353,10 +354,11 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
new Intent(ACTION_REWIND), PendingIntent.FLAG_UPDATE_CURRENT);
//build intent to return to video, on tapping notification
- Intent openDetailViewIntent = new Intent(getApplicationContext(),
- VideoItemDetailActivity.class);
- openDetailViewIntent.putExtra(NavStack.SERVICE_ID, serviceId);
- openDetailViewIntent.putExtra(NavStack.URL, webUrl);
+ Intent openDetailViewIntent = new Intent(getApplicationContext(), MainActivity.class);
+ openDetailViewIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId);
+ openDetailViewIntent.putExtra(Constants.KEY_URL, webUrl);
+ openDetailViewIntent.putExtra(Constants.KEY_TITLE, title);
+ openDetailViewIntent.putExtra(Constants.KEY_LINK_TYPE, StreamingService.LinkType.STREAM);
openDetailViewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent openDetailView = PendingIntent.getActivity(owner, noteID,
openDetailViewIntent, PendingIntent.FLAG_UPDATE_CURRENT);
diff --git a/app/src/main/java/org/schabi/newpipe/player/ExoPlayerActivity.java b/app/src/main/java/org/schabi/newpipe/player/ExoPlayerActivity.java
index 3a7c5e085..435fd189b 100644
--- a/app/src/main/java/org/schabi/newpipe/player/ExoPlayerActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/player/ExoPlayerActivity.java
@@ -24,7 +24,6 @@ import android.widget.TextView;
import android.widget.Toast;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.util.NavStack;
import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper;
@@ -70,6 +69,7 @@ public class ExoPlayerActivity extends Activity {
return;
}
+ showSystemUi();
setContentView(R.layout.activity_exo_player);
playerImpl = new AbstractPlayerImpl();
playerImpl.setup(findViewById(android.R.id.content));
@@ -88,7 +88,6 @@ public class ExoPlayerActivity extends Activity {
public void onBackPressed() {
if (DEBUG) Log.d(TAG, "onBackPressed() called");
super.onBackPressed();
- if (playerImpl.isStartedFromNewPipe()) NavStack.getInstance().openDetailActivity(this, playerImpl.getVideoUrl(), 0);
if (playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(false);
}
@@ -340,8 +339,7 @@ public class ExoPlayerActivity extends Activity {
public void onStopTrackingTouch(SeekBar seekBar) {
super.onStopTrackingTouch(seekBar);
if (playerImpl.wasPlaying()) {
- hideSystemUi();
- playerImpl.getControlsRoot().setVisibility(View.GONE);
+ animateView(playerImpl.getControlsRoot(), false, 100, 0);
}
}
@@ -365,6 +363,13 @@ public class ExoPlayerActivity extends Activity {
public void onLoading() {
super.onLoading();
playPauseButton.setImageResource(R.drawable.ic_pause_white);
+ animateView(playPauseButton, false, 100, 0);
+ }
+
+ @Override
+ public void onBuffering() {
+ super.onBuffering();
+ animateView(playPauseButton, false, 100, 0);
}
@Override
@@ -384,6 +389,7 @@ public class ExoPlayerActivity extends Activity {
public void onPlaying() {
super.onPlaying();
animateView(playPauseButton, true, 500, 0);
+ showSystemUi();
}
@Override
diff --git a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
index 090179a7a..6e4fa9776 100644
--- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
+++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java
@@ -34,16 +34,17 @@ import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListene
import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.BuildConfig;
+import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
-import org.schabi.newpipe.detail.VideoItemDetailActivity;
import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream_info.StreamExtractor;
import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.extractor.stream_info.VideoStream;
-import org.schabi.newpipe.util.NavStack;
+import org.schabi.newpipe.util.Constants;
import org.schabi.newpipe.util.ThemeHelper;
+import org.schabi.newpipe.util.Utils;
import java.io.IOException;
import java.util.ArrayList;
@@ -107,7 +108,7 @@ public class PopupVideoPlayer extends Service {
if (!playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(true);
if (imageLoader != null) imageLoader.clearMemoryCache();
- if (intent.getStringExtra(NavStack.URL) != null) {
+ if (intent.getStringExtra(Constants.KEY_URL) != null) {
playerImpl.setStartedFromNewPipe(false);
Thread fetcher = new Thread(new FetcherRunnable(intent));
fetcher.start();
@@ -158,7 +159,7 @@ public class PopupVideoPlayer extends Service {
playerImpl.onVideoPlayPause();
break;
case ACTION_OPEN_DETAIL:
- onOpenDetail(PopupVideoPlayer.this, playerImpl.getVideoUrl());
+ onOpenDetail(PopupVideoPlayer.this, playerImpl.getVideoUrl(), playerImpl.getVideoTitle());
break;
case ACTION_REPEAT:
playerImpl.onRepeatClicked();
@@ -266,12 +267,14 @@ public class PopupVideoPlayer extends Service {
stopSelf();
}
- public void onOpenDetail(Context context, String videoUrl) {
+ public void onOpenDetail(Context context, String videoUrl, String videoTitle) {
if (DEBUG) Log.d(TAG, "onOpenDetail() called with: context = [" + context + "], videoUrl = [" + videoUrl + "]");
- Intent i = new Intent(context, VideoItemDetailActivity.class);
- i.putExtra(NavStack.SERVICE_ID, 0)
- .putExtra(NavStack.URL, videoUrl)
- .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ Intent i = new Intent(context, MainActivity.class);
+ i.putExtra(Constants.KEY_SERVICE_ID, 0);
+ i.putExtra(Constants.KEY_URL, videoUrl);
+ i.putExtra(Constants.KEY_TITLE, videoTitle);
+ i.putExtra(Constants.KEY_LINK_TYPE, StreamingService.LinkType.STREAM);
+ i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(i);
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
@@ -510,8 +513,6 @@ public class PopupVideoPlayer extends Service {
private class FetcherRunnable implements Runnable {
private final Intent intent;
private final Handler mainHandler;
- private final boolean printStreams = true;
-
FetcherRunnable(Intent intent) {
this.intent = intent;
@@ -524,48 +525,22 @@ public class PopupVideoPlayer extends Service {
try {
StreamingService service = NewPipe.getService(0);
if (service == null) return;
- streamExtractor = service.getExtractorInstance(intent.getStringExtra(NavStack.URL));
+ streamExtractor = service.getExtractorInstance(intent.getStringExtra(Constants.KEY_URL));
StreamInfo info = StreamInfo.getVideoInfo(streamExtractor);
- String defaultResolution = playerImpl.getSharedPreferences().getString(
- getResources().getString(R.string.default_resolution_key),
- getResources().getString(R.string.default_resolution_value));
-
- VideoStream chosen = null, secondary = null, fallback = null;
playerImpl.setVideoStreamsList(info.video_streams instanceof ArrayList
? (ArrayList) info.video_streams
: new ArrayList<>(info.video_streams));
- for (VideoStream item : info.video_streams) {
- if (DEBUG && printStreams) {
- Log.d(TAG, "FetcherRunnable.StreamExtractor: current Item"
- + ", item.resolution = " + item.resolution
- + ", item.format = " + item.format
- + ", item.url = " + item.url);
- }
- if (defaultResolution.equals(item.resolution)) {
- if (item.format == MediaFormat.MPEG_4.id) {
- chosen = item;
- if (DEBUG) Log.d(TAG, "FetcherRunnable.StreamExtractor: CHOSEN item, item.resolution = " + item.resolution + ", item.format = " + item.format + ", item.url = " + item.url);
- } else if (item.format == 2) secondary = item;
- else fallback = item;
- }
+ int defaultResolution = Utils.getPreferredResolution(PopupVideoPlayer.this, info.video_streams);
+ playerImpl.setSelectedIndexStream(defaultResolution);
+
+ if (DEBUG) {
+ Log.d(TAG, "FetcherRunnable.StreamExtractor: chosen = "
+ + MediaFormat.getNameById(info.video_streams.get(defaultResolution).format) + " "
+ + info.video_streams.get(defaultResolution).resolution + " > "
+ + info.video_streams.get(defaultResolution).url);
}
- int selectedIndexStream;
-
- if (chosen != null) selectedIndexStream = info.video_streams.indexOf(chosen);
- else if (secondary != null) selectedIndexStream = info.video_streams.indexOf(secondary);
- else if (fallback != null) selectedIndexStream = info.video_streams.indexOf(fallback);
- else selectedIndexStream = 0;
-
- playerImpl.setSelectedIndexStream(selectedIndexStream);
-
- if (DEBUG && printStreams) Log.d(TAG, "FetcherRunnable.StreamExtractor: chosen = " + chosen
- + "\n, secondary = " + secondary
- + "\n, fallback = " + fallback
- + "\n, info.video_streams.get(0).url = " + info.video_streams.get(0).url);
-
-
playerImpl.setVideoUrl(info.webpage_url);
playerImpl.setVideoTitle(info.title);
playerImpl.setChannelName(info.uploader);
@@ -578,6 +553,8 @@ public class PopupVideoPlayer extends Service {
playerImpl.playVideo(playerImpl.getSelectedStreamUri(), true);
}
});
+
+ imageLoader.resume();
imageLoader.loadImage(info.thumbnail_url, displayImageOptions, new SimpleImageLoadingListener() {
@Override
public void onLoadingComplete(String imageUri, View view, final Bitmap loadedImage) {
diff --git a/app/src/main/java/org/schabi/newpipe/util/Constants.java b/app/src/main/java/org/schabi/newpipe/util/Constants.java
new file mode 100644
index 000000000..9f94729ae
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/Constants.java
@@ -0,0 +1,8 @@
+package org.schabi.newpipe.util;
+
+public class Constants {
+ public static final String KEY_SERVICE_ID = "key_service_id";
+ public static final String KEY_URL = "key_url";
+ public static final String KEY_TITLE = "key_title";
+ public static final String KEY_LINK_TYPE = "key_link_type";
+}
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavStack.java b/app/src/main/java/org/schabi/newpipe/util/NavStack.java
deleted file mode 100644
index d06aafe7e..000000000
--- a/app/src/main/java/org/schabi/newpipe/util/NavStack.java
+++ /dev/null
@@ -1,148 +0,0 @@
-package org.schabi.newpipe.util;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-import android.support.v4.app.NavUtils;
-
-import org.schabi.newpipe.ChannelActivity;
-import org.schabi.newpipe.MainActivity;
-import org.schabi.newpipe.detail.VideoItemDetailActivity;
-import org.schabi.newpipe.extractor.NewPipe;
-import org.schabi.newpipe.extractor.StreamingService;
-
-import java.util.ArrayList;
-import java.util.Stack;
-
-/**
- * Created by Christian Schabesberger on 16.02.17.
- *
- * Copyright (C) Christian Schabesberger 2016
- * NavStack.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 helps to navigate within the app
- * IMPORTAND: the top of the stack is the current activity !!!
- */
-public class NavStack {
- private static final String TAG = NavStack.class.toString();
- public static final String SERVICE_ID = "service_id";
- public static final String URL = "url";
-
- private static final String NAV_STACK="nav_stack";
-
- private enum ActivityId {
- CHANNEL,
- DETAIL
- }
-
- private class NavEntry {
- public NavEntry(String url, int serviceId) {
- this.url = url;
- this.serviceId = serviceId;
- }
- public String url;
- public int serviceId;
- }
-
- private static NavStack instance = new NavStack();
- private Stack stack = new Stack();
-
- private NavStack() {
- }
-
- public static NavStack getInstance() {
- return instance;
- }
-
- public void navBack(Activity activity) throws Exception {
- if(stack.size() == 0) { // if stack is already empty here, activity was probably called
- // from another app
- activity.finish();
- return;
- }
- stack.pop(); // remove curent activty, since we dont want to return to itself
- if (stack.size() == 0) {
- openMainActivity(activity); // if no more page is on the stack this means we are home
- return;
- }
- NavEntry entry = stack.pop(); // this element will reapear, since by calling the old page
- // this element will be pushed on top again
- try {
- StreamingService service = NewPipe.getService(entry.serviceId);
- switch (service.getLinkTypeByUrl(entry.url)) {
- case STREAM:
- openDetailActivity(activity, entry.url, entry.serviceId);
- break;
- case CHANNEL:
- openChannelActivity(activity, entry.url, entry.serviceId);
- break;
- case NONE:
- throw new Exception("Url not known to service. service="
- + Integer.toString(entry.serviceId) + " url=" + entry.url);
- default:
- openMainActivity(activity);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
-
- public void openChannelActivity(Context context, String url, int serviceId) {
- openActivity(context, url, serviceId, ChannelActivity.class);
- }
-
- public void openDetailActivity(Context context, String url, int serviceId) {
- openActivity(context, url, serviceId, VideoItemDetailActivity.class);
- }
-
- private void openActivity(Context context, String url, int serviceId, Class acitivtyClass) {
- //if last element has the same url do not push to stack again
- if(stack.isEmpty() || !stack.peek().url.equals(url)) {
- stack.push(new NavEntry(url, serviceId));
- }
- Intent i = new Intent(context, acitivtyClass);
- i.putExtra(SERVICE_ID, serviceId);
- i.putExtra(URL, url);
- context.startActivity(i);
- }
-
- public void openMainActivity(Activity a) {
- stack.clear();
- Intent i = new Intent(a, MainActivity.class);
- i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
- NavUtils.navigateUpTo(a, i);
- }
-
- public void onSaveInstanceState(Bundle state) {
- ArrayList sa = new ArrayList<>();
- for(NavEntry entry : stack) {
- sa.add(entry.url);
- }
- state.putStringArrayList(NAV_STACK, sa);
- }
-
- public void restoreSavedInstanceState(Bundle state) {
- ArrayList sa = state.getStringArrayList(NAV_STACK);
- stack.clear();
- for(String url : sa) {
- stack.push(new NavEntry(url, NewPipe.getServiceByUrl(url).getServiceId()));
- }
- }
-}
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
new file mode 100644
index 000000000..17708dbef
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -0,0 +1,96 @@
+package org.schabi.newpipe.util;
+
+import android.content.Context;
+import android.content.Intent;
+import android.preference.PreferenceManager;
+
+import org.schabi.newpipe.MainActivity;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.fragments.OnItemSelectedListener;
+import org.schabi.newpipe.fragments.detail.VideoDetailFragment;
+
+@SuppressWarnings({"unused", "WeakerAccess"})
+public class NavigationHelper {
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Through Interface (faster)
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public static void openChannel(OnItemSelectedListener listener, int serviceId, String url) {
+ openChannel(listener, serviceId, url, null);
+ }
+
+ public static void openChannel(OnItemSelectedListener listener, int serviceId, String url, String name) {
+ listener.onItemSelected(StreamingService.LinkType.CHANNEL, serviceId, url, name);
+ }
+
+ public static void openVideoDetail(OnItemSelectedListener listener, int serviceId, String url) {
+ openVideoDetail(listener, serviceId, url, null);
+ }
+
+ public static void openVideoDetail(OnItemSelectedListener listener, int serviceId, String url, String title) {
+ listener.onItemSelected(StreamingService.LinkType.STREAM, serviceId, url, title);
+ }
+
+ /*//////////////////////////////////////////////////////////////////////////
+ // Through Intents
+ //////////////////////////////////////////////////////////////////////////*/
+
+ public static void openByLink(Context context, String url) throws Exception {
+ context.startActivity(getIntentByLink(context, url));
+ }
+
+ public static void openChannel(Context context, int serviceId, String url) {
+ openChannel(context, serviceId, url, null);
+ }
+
+ public static void openChannel(Context context, int serviceId, String url, String name) {
+ Intent openIntent = getOpenIntent(context, url, serviceId, StreamingService.LinkType.CHANNEL);
+ if (name != null && !name.isEmpty()) openIntent.putExtra(Constants.KEY_TITLE, name);
+ context.startActivity(openIntent);
+ }
+
+ public static void openVideoDetail(Context context, int serviceId, String url) {
+ openVideoDetail(context, serviceId, url, null);
+ }
+
+ public static void openVideoDetail(Context context, int serviceId, String url, String title) {
+ Intent openIntent = getOpenIntent(context, url, serviceId, StreamingService.LinkType.STREAM);
+ if (title != null && !title.isEmpty()) openIntent.putExtra(Constants.KEY_TITLE, title);
+ context.startActivity(openIntent);
+ }
+
+ public static void openMainActivity(Context context) {
+ Intent mIntent = new Intent(context, MainActivity.class);
+ context.startActivity(mIntent);
+ }
+
+ private static Intent getOpenIntent(Context context, String url, int serviceId, StreamingService.LinkType type) {
+ Intent mIntent = new Intent(context, MainActivity.class);
+ mIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId);
+ mIntent.putExtra(Constants.KEY_URL, url);
+ mIntent.putExtra(Constants.KEY_LINK_TYPE, type);
+ return mIntent;
+ }
+
+ private static Intent getIntentByLink(Context context, String url) throws Exception {
+ StreamingService service = NewPipe.getServiceByUrl(url);
+ if (service == null) throw new Exception("NewPipe.getServiceByUrl returned null for url > \"" + url + "\"");
+ int serviceId = service.getServiceId();
+ switch (service.getLinkTypeByUrl(url)) {
+ case STREAM:
+ Intent sIntent = getOpenIntent(context, url, serviceId, StreamingService.LinkType.STREAM);
+ sIntent.putExtra(VideoDetailFragment.AUTO_PLAY, PreferenceManager.getDefaultSharedPreferences(context)
+ .getBoolean(context.getString(R.string.autoplay_through_intent_key), false));
+ return sIntent;
+ case CHANNEL:
+ return getOpenIntent(context, url, serviceId, StreamingService.LinkType.CHANNEL);
+ case NONE:
+ throw new Exception("Url not known to service. service="
+ + Integer.toString(serviceId) + " url=" + url);
+ }
+ return null;
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/util/Utils.java b/app/src/main/java/org/schabi/newpipe/util/Utils.java
new file mode 100644
index 000000000..98d8bf25b
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/util/Utils.java
@@ -0,0 +1,91 @@
+package org.schabi.newpipe.util;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.preference.PreferenceManager;
+
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.MediaFormat;
+import org.schabi.newpipe.extractor.stream_info.AudioStream;
+import org.schabi.newpipe.extractor.stream_info.VideoStream;
+
+import java.util.List;
+
+public class Utils {
+
+ /**
+ * Return the index of the default stream in the list, based on the
+ * preferred resolution and format chosen in the settings
+ *
+ * @param videoStreams the list that will be extracted the index
+ * @return index of the preferred resolution&format
+ */
+ public static int getPreferredResolution(Context context, List videoStreams) {
+ SharedPreferences defaultPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ if (defaultPreferences == null) return 0;
+
+ String defaultResolution = defaultPreferences
+ .getString(context.getString(R.string.default_resolution_key),
+ context.getString(R.string.default_resolution_value));
+
+ String preferredFormat = defaultPreferences
+ .getString(context.getString(R.string.preferred_video_format_key),
+ context.getString(R.string.preferred_video_format_default));
+
+ // first try to find the one with the right resolution
+ int selectedFormat = 0;
+ for (int i = 0; i < videoStreams.size(); i++) {
+ VideoStream item = videoStreams.get(i);
+ if (defaultResolution.equals(item.resolution)) {
+ selectedFormat = i;
+ }
+ }
+
+ // than try to find the one with the right resolution and format
+ for (int i = 0; i < videoStreams.size(); i++) {
+ VideoStream item = videoStreams.get(i);
+ if (defaultResolution.equals(item.resolution)
+ && preferredFormat.equals(MediaFormat.getNameById(item.format))) {
+ selectedFormat = i;
+ }
+ }
+
+ // this is actually an error,
+ // but maybe there is really no stream fitting to the default value.
+ return selectedFormat;
+ }
+
+ /**
+ * Return the index of the default stream in the list, based on the
+ * preferred audio format chosen in the settings
+ *
+ * @param audioStreams the list that will be extracted the index
+ * @return index of the preferred format
+ */
+ public static int getPreferredAudioFormat(Context context, List audioStreams) {
+ SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ if (sharedPreferences == null) return 0;
+
+ String preferredFormatString = sharedPreferences.getString(context.getString(R.string.default_audio_format_key), "webm");
+
+ int preferredFormat = MediaFormat.WEBMA.id;
+ switch (preferredFormatString) {
+ case "webm":
+ preferredFormat = MediaFormat.WEBMA.id;
+ break;
+ case "m4a":
+ preferredFormat = MediaFormat.M4A.id;
+ break;
+ default:
+ break;
+ }
+
+ for (int i = 0; i < audioStreams.size(); i++) {
+ if (audioStreams.get(i).format == preferredFormat) {
+ return i;
+ }
+ }
+
+ return 0;
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/workers/ChannelExtractorWorker.java b/app/src/main/java/org/schabi/newpipe/workers/ChannelExtractorWorker.java
new file mode 100644
index 000000000..6b815b493
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/workers/ChannelExtractorWorker.java
@@ -0,0 +1,102 @@
+package org.schabi.newpipe.workers;
+
+import android.content.Context;
+
+import org.schabi.newpipe.MainActivity;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.channel.ChannelExtractor;
+import org.schabi.newpipe.extractor.channel.ChannelInfo;
+import org.schabi.newpipe.extractor.exceptions.ExtractionException;
+import org.schabi.newpipe.extractor.exceptions.ParsingException;
+import org.schabi.newpipe.report.ErrorActivity;
+
+import java.io.IOException;
+
+/**
+ * Extract {@link ChannelInfo} with {@link ChannelExtractor} from the given url of the given service
+ *
+ * @author mauriciocolli
+ */
+@SuppressWarnings("WeakerAccess")
+public class ChannelExtractorWorker extends ExtractorWorker {
+ //private static final String TAG = "ChannelExtractorWorker";
+
+ private int pageNumber;
+ private boolean onlyVideos;
+
+ private ChannelInfo channelInfo = null;
+ private OnChannelInfoReceive callback;
+
+ /**
+ * Interface which will be called for result and errors
+ */
+ public interface OnChannelInfoReceive {
+ void onReceive(ChannelInfo info);
+ void onError(int messageId);
+ }
+
+ /**
+ * @param context context for error reporting purposes
+ * @param serviceId id of the request service
+ * @param channelUrl channelUrl of the service (e.g. https://www.youtube.com/channel/UC_aEa8K-EOJ3D6gOs7HcyNg)
+ * @param callback listener that will be called-back when events occur (check {@link ChannelExtractorWorker.OnChannelInfoReceive})
+ */
+ public ChannelExtractorWorker(Context context, int serviceId, String channelUrl, int pageNumber, OnChannelInfoReceive callback) {
+ super(context, channelUrl, serviceId);
+ this.pageNumber = pageNumber;
+ this.callback = callback;
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ this.callback = null;
+ this.channelInfo = null;
+ }
+
+ @Override
+ protected void doWork(int serviceId, String url) throws Exception {
+ ChannelExtractor extractor = getService().getChannelExtractorInstance(url, pageNumber);
+ channelInfo = ChannelInfo.getInfo(extractor);
+
+ if (!channelInfo.errors.isEmpty()) handleErrorsDuringExtraction(channelInfo.errors, ErrorActivity.REQUESTED_CHANNEL);
+
+ if (callback != null && channelInfo != null && !isInterrupted()) getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ if (isInterrupted() || callback == null) return;
+
+ callback.onReceive(channelInfo);
+ onDestroy();
+ }
+ });
+ }
+
+
+ @Override
+ protected void handleException(Exception exception, int serviceId, String url) {
+ if (exception instanceof IOException) {
+ if (callback != null) getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onError(R.string.network_error);
+ }
+ });
+ } else if (exception instanceof ParsingException || exception instanceof ExtractionException) {
+ ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_CHANNEL, getServiceName(), url, R.string.parsing_error));
+ finishIfActivity();
+ } else {
+ ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_CHANNEL, getServiceName(), url, R.string.general_error));
+ finishIfActivity();
+ }
+ }
+
+ public boolean isOnlyVideos() {
+ return onlyVideos;
+ }
+
+ public void setOnlyVideos(boolean onlyVideos) {
+ this.onlyVideos = onlyVideos;
+ }
+}
+
diff --git a/app/src/main/java/org/schabi/newpipe/workers/ExtractorWorker.java b/app/src/main/java/org/schabi/newpipe/workers/ExtractorWorker.java
new file mode 100644
index 000000000..b26d62ae8
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/workers/ExtractorWorker.java
@@ -0,0 +1,168 @@
+package org.schabi.newpipe.workers;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+
+import org.schabi.newpipe.extractor.NewPipe;
+import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.extractor.stream_info.StreamInfo;
+import org.schabi.newpipe.report.ErrorActivity;
+
+import java.io.InterruptedIOException;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Common properties of ExtractorWorkers
+ *
+ * @author mauriciocolli
+ */
+@SuppressWarnings("WeakerAccess")
+public abstract class ExtractorWorker extends Thread {
+
+ private final AtomicBoolean isRunning = new AtomicBoolean(false);
+
+ private final String url;
+ private final int serviceId;
+ private Context context;
+ private Handler handler;
+ private StreamingService service;
+
+ public ExtractorWorker(Context context, String url, int serviceId) {
+ this.context = context;
+ this.url = url;
+ this.serviceId = serviceId;
+ this.handler = new Handler(context.getMainLooper());
+ if (url.length() >= 40) setName("Thread-" + url.substring(url.length() - 11, url.length()));
+ }
+
+ @Override
+ public void run() {
+ try {
+ isRunning.set(true);
+ service = NewPipe.getService(serviceId);
+ doWork(serviceId, url);
+ } catch (Exception e) {
+ // Handle the exception only if thread is not interrupted
+ if (!isInterrupted() && !(e instanceof InterruptedIOException) && !(e.getCause() instanceof InterruptedIOException)) {
+ handleException(e, serviceId, url);
+ }
+ } finally {
+ isRunning.set(false);
+ }
+ }
+
+ /**
+ * Here is the place that the heavy work is realized
+ *
+ * @param serviceId serviceId that was passed when created this object
+ * @param url url that was passed when created this object
+ *
+ * @throws Exception these exceptions are handled by the {@link #handleException(Exception, int, String)}
+ */
+ protected abstract void doWork(int serviceId, String url) throws Exception;
+
+
+ /**
+ * Method that handle the exception thrown by the {@link #doWork(int, String)}.
+ *
+ * @param exception {@link Exception} that was thrown by {@link #doWork(int, String)}
+ */
+ protected abstract void handleException(Exception exception, int serviceId, String url);
+
+ /**
+ * Handle the errors during extraction and shows a Report button to the user.
+ * Subclasses maybe call this method.
+ *
+ * @param errorsList list of exceptions that happened during extraction
+ * @param errorUserAction what action was the user performing during the error.
+ * (One of the {@link ErrorActivity}.REQUEST_* error (message) ids)
+ */
+ protected void handleErrorsDuringExtraction(List errorsList, int errorUserAction){
+ String errorString = "";
+ switch (errorUserAction) {
+ case ErrorActivity.REQUESTED_STREAM:
+ errorString= ErrorActivity.REQUESTED_STREAM_STRING;
+ break;
+ case ErrorActivity.REQUESTED_CHANNEL:
+ errorString= ErrorActivity.REQUESTED_CHANNEL_STRING;
+ break;
+ }
+
+ Log.e(errorString, "OCCURRED ERRORS DURING EXTRACTION:");
+ for (Throwable e : errorsList) {
+ e.printStackTrace();
+ Log.e(errorString, "------");
+ }
+
+ if (getContext() instanceof Activity) {
+ View rootView = getContext() != null ? ((Activity) getContext()).findViewById(android.R.id.content) : null;
+ ErrorActivity.reportError(getHandler(), getContext(), errorsList, null, rootView, ErrorActivity.ErrorInfo.make(errorUserAction, getServiceName(), url, 0 /* no message for the user */));
+ }
+ }
+
+ /**
+ * Return true if the extraction is not completed yet
+ *
+ * @return the value of the AtomicBoolean {@link #isRunning}
+ */
+ public boolean isRunning() {
+ return isRunning.get();
+ }
+
+ /**
+ * Cancel this ExtractorWorker, calling {@link #onDestroy()} and interrupting this thread.
+ *
+ * Note: Any I/O that is active in the moment that this method is called will be canceled and a Exception will be thrown, because of the {@link #interrupt()}.
+ * This is useful when you don't want the resulting {@link StreamInfo} anymore, but don't want to waste bandwidth, otherwise it'd run till it receives the StreamInfo.
+ */
+ public void cancel() {
+ onDestroy();
+ this.interrupt();
+ }
+
+ /**
+ * Method that discards everything that doesn't need anymore.
+ * Subclasses can override this method to destroy their garbage.
+ */
+ protected void onDestroy() {
+ this.isRunning.set(false);
+ this.context = null;
+ this.handler = null;
+ this.service = null;
+ }
+
+ /**
+ * If the context passed in the constructor is an {@link Activity}, finish it.
+ */
+ protected void finishIfActivity() {
+ if (getContext() instanceof Activity) ((Activity) getContext()).finish();
+ }
+
+ public Handler getHandler() {
+ return handler;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public StreamingService getService() {
+ return service;
+ }
+
+ public int getServiceId() {
+ return serviceId;
+ }
+
+ public String getServiceName() {
+ return service == null ? "none" : service.getServiceInfo().name;
+ }
+
+ public Context getContext() {
+ return context;
+ }
+}
diff --git a/app/src/main/java/org/schabi/newpipe/workers/StreamExtractorWorker.java b/app/src/main/java/org/schabi/newpipe/workers/StreamExtractorWorker.java
new file mode 100644
index 000000000..bc65f8dc8
--- /dev/null
+++ b/app/src/main/java/org/schabi/newpipe/workers/StreamExtractorWorker.java
@@ -0,0 +1,136 @@
+package org.schabi.newpipe.workers;
+
+import android.content.Context;
+
+import org.schabi.newpipe.MainActivity;
+import org.schabi.newpipe.R;
+import org.schabi.newpipe.extractor.exceptions.ParsingException;
+import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
+import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
+import org.schabi.newpipe.extractor.stream_info.StreamExtractor;
+import org.schabi.newpipe.extractor.stream_info.StreamInfo;
+import org.schabi.newpipe.report.ErrorActivity;
+
+import java.io.IOException;
+
+/**
+ * Extract {@link StreamInfo} with {@link StreamExtractor} from the given url of the given service
+ *
+ * @author mauriciocolli
+ */
+@SuppressWarnings("WeakerAccess")
+public class StreamExtractorWorker extends ExtractorWorker {
+ //private static final String TAG = "StreamExtractorWorker";
+
+ private StreamInfo streamInfo = null;
+ private OnStreamInfoReceivedListener callback;
+
+ /**
+ * Interface which will be called for result and errors
+ */
+ public interface OnStreamInfoReceivedListener {
+ void onReceive(StreamInfo info);
+ void onError(int messageId);
+ void onReCaptchaException();
+ void onBlockedByGemaError();
+ void onContentErrorWithMessage(int messageId);
+ void onContentError();
+ }
+
+ /**
+ * @param context context for error reporting purposes
+ * @param serviceId id of the request service
+ * @param videoUrl videoUrl of the service (e.g. https://www.youtube.com/watch?v=HyHNuVaZJ-k)
+ * @param callback listener that will be called-back when events occur (check {@link StreamExtractorWorker.OnStreamInfoReceivedListener})
+ */
+ public StreamExtractorWorker(Context context, int serviceId, String videoUrl, OnStreamInfoReceivedListener callback) {
+ super(context, videoUrl, serviceId);
+ this.callback = callback;
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ this.callback = null;
+ this.streamInfo = null;
+ }
+
+ @Override
+ protected void doWork(int serviceId, String url) throws Exception {
+ StreamExtractor streamExtractor = getService().getExtractorInstance(url);
+ streamInfo = StreamInfo.getVideoInfo(streamExtractor);
+
+ if (streamInfo != null && !streamInfo.errors.isEmpty()) handleErrorsDuringExtraction(streamInfo.errors, ErrorActivity.REQUESTED_STREAM);
+
+ if (callback != null && streamInfo != null && !isInterrupted()) getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ if (isInterrupted() || callback == null) return;
+
+ callback.onReceive(streamInfo);
+ onDestroy();
+ }
+ });
+
+ }
+
+ @Override
+ protected void handleException(final Exception exception, int serviceId, String url) {
+ if (exception instanceof ReCaptchaException) {
+ if (callback != null) getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onReCaptchaException();
+ }
+ });
+ } else if (exception instanceof IOException) {
+ if (callback != null) getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onError(R.string.network_error);
+ }
+ });
+ } else if (exception instanceof YoutubeStreamExtractor.GemaException) {
+ if (callback != null) getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onBlockedByGemaError();
+ }
+ });
+ } else if (exception instanceof YoutubeStreamExtractor.LiveStreamException) {
+ if (callback != null) getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onContentErrorWithMessage(R.string.live_streams_not_supported);
+ }
+ });
+ } else if (exception instanceof StreamExtractor.ContentNotAvailableException) {
+ if (callback != null) getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ callback.onContentError();
+ }
+ });
+ } else if (exception instanceof YoutubeStreamExtractor.DecryptException) {
+ // custom service related exceptions
+ ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.youtube_signature_decryption_error));
+ finishIfActivity();
+ } else if (exception instanceof StreamInfo.StreamExctractException) {
+ if (!streamInfo.errors.isEmpty()) {
+ // !!! if this case ever kicks in someone gets kicked out !!!
+ ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.could_not_get_stream));
+ } else {
+ ErrorActivity.reportError(getHandler(), getContext(), streamInfo.errors, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.could_not_get_stream));
+ }
+ finishIfActivity();
+ } else if (exception instanceof ParsingException) {
+ ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.parsing_error));
+ finishIfActivity();
+ } else {
+ ErrorActivity.reportError(getHandler(), getContext(), exception, MainActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, getServiceName(), url, R.string.general_error));
+ finishIfActivity();
+ }
+
+ }
+
+}
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 31c1c587a..67292ab7d 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,15 +1,16 @@
-
+ android:orientation="vertical"
+ tools:context="org.schabi.newpipe.MainActivity">
-
+ android:layout_height="match_parent"/>
diff --git a/app/src/main/res/layout/activity_channel.xml b/app/src/main/res/layout/fragment_channel.xml
similarity index 50%
rename from app/src/main/res/layout/activity_channel.xml
rename to app/src/main/res/layout/fragment_channel.xml
index 5a3257a95..78388ee76 100644
--- a/app/src/main/res/layout/activity_channel.xml
+++ b/app/src/main/res/layout/fragment_channel.xml
@@ -3,24 +3,22 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
- android:title="Channel">
+ android:title="@string/channel">
+ android:scrollbars="vertical"
+ tools:listitem="@layout/stream_item"/>
-
-
-
+
diff --git a/app/src/main/res/layout/fragment_searchinfoitem.xml b/app/src/main/res/layout/fragment_search.xml
similarity index 83%
rename from app/src/main/res/layout/fragment_searchinfoitem.xml
rename to app/src/main/res/layout/fragment_search.xml
index 09064076d..2ed0ea0f7 100644
--- a/app/src/main/res/layout/fragment_searchinfoitem.xml
+++ b/app/src/main/res/layout/fragment_search.xml
@@ -5,7 +5,7 @@
android:layout_height="match_parent"
android:layout_width="match_parent"
android:name="org.schabi.newpipe.SearchInfoItemFragment"
- tools:context=".search_fragment.SearchInfoItemFragment">
+ tools:context=".fragments.search.SearchFragment">
@@ -17,10 +17,11 @@
tools:listitem="@layout/stream_item"
android:scrollbars="vertical"/>
-
+ android:visibility="gone"
+ tools:visibility="visible"/>
diff --git a/app/src/main/res/layout/activity_videoitem_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml
similarity index 96%
rename from app/src/main/res/layout/activity_videoitem_detail.xml
rename to app/src/main/res/layout/fragment_video_detail.xml
index 0763f7ee6..4b34bcb49 100644
--- a/app/src/main/res/layout/activity_videoitem_detail.xml
+++ b/app/src/main/res/layout/fragment_video_detail.xml
@@ -17,6 +17,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
+
+
+ android:layout_height="wrap_content"
+ android:layout_below="@+id/detail_thumbnail_root_layout"
+ android:background="?android:windowBackground">
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/main_bg.xml b/app/src/main/res/layout/main_bg.xml
index 00ab5916a..7c8c877d5 100644
--- a/app/src/main/res/layout/main_bg.xml
+++ b/app/src/main/res/layout/main_bg.xml
@@ -4,7 +4,7 @@
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/mainBG"
- tools:context=".detail.VideoItemDetailActivity">
+ tools:context=".fragments.detail.VideoDetailFragment">
+ android:orderInCategory="980"
+ android:title="@string/downloads"
+ app:showAsAction="never"/>
+ android:orderInCategory="990"
+ android:title="@string/settings"
+ app:showAsAction="never"/>
\ 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
index 58f0f7750..c371699be 100644
--- a/app/src/main/res/menu/menu_channel.xml
+++ b/app/src/main/res/menu/menu_channel.xml
@@ -1,7 +1,7 @@
diff --git a/app/src/main/res/menu/search_menu.xml b/app/src/main/res/menu/search_menu.xml
index 027df6668..d3df69b25 100644
--- a/app/src/main/res/menu/search_menu.xml
+++ b/app/src/main/res/menu/search_menu.xml
@@ -8,7 +8,8 @@
app:actionViewClass="android.support.v7.widget.SearchView" />
+ android:checkableBehavior="single"
+ android:orderInCategory="999">
diff --git a/app/src/main/res/menu/videoitem_detail.xml b/app/src/main/res/menu/videoitem_detail.xml
index c4f2d5fb1..6143ee974 100644
--- a/app/src/main/res/menu/videoitem_detail.xml
+++ b/app/src/main/res/menu/videoitem_detail.xml
@@ -29,14 +29,4 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index cdf20f441..c4340a093 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -158,6 +158,7 @@
Old build in Mediaframework player.
videos
subscriber
+ subscribers
Subscribe
views
K