diff --git a/README.md b/README.md index c2b3ad168..dee4ed2bd 100644 --- a/README.md +++ b/README.md @@ -37,23 +37,27 @@ NewPipe does not use any Google framework libraries, or the YouTube API. It only * Listen to YouTube videos (experimental) * Select the streaming player to watch the video with * Download videos -* Download audio only * Open a video in Kodi +* Download audio only +* Open a video in Kodi * Show Next/Related videos * Search YouTube in a specific language * Watch age restricted material * Display general information about channels * Search channels +* Watch videos from a channel +* Orbot/Tor support (not yet directly) ### Coming Features -* Orbot/Tor support * Bookmarks * View history * Search history * Subscribe to channels -* Watch videos from a channel * Search/Watch Playlists * Queeing videos +* Subtitles support +* 1080p support +* livestream support * ... and many more ### Multiservice support diff --git a/app/build.gradle b/app/build.gradle index 52b2c673f..49d609428 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -8,8 +8,8 @@ android { applicationId "org.schabi.newpipe" minSdkVersion 15 targetSdkVersion 25 - versionCode 26 - versionName "0.8.12" + versionCode 27 + versionName "0.9.0" } buildTypes { release { diff --git a/app/src/main/java/org/schabi/newpipe/ChannelActivity.java b/app/src/main/java/org/schabi/newpipe/ChannelActivity.java index 6258de152..efc1e29f7 100644 --- a/app/src/main/java/org/schabi/newpipe/ChannelActivity.java +++ b/app/src/main/java/org/schabi/newpipe/ChannelActivity.java @@ -19,7 +19,7 @@ import android.widget.Toast; import com.nostra13.universalimageloader.core.ImageLoader; -import org.schabi.newpipe.detail.VideoItemDetailFragment; +import org.schabi.newpipe.detail.VideoItemDetailActivity; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.channel.ChannelExtractor; @@ -31,10 +31,12 @@ import org.schabi.newpipe.info_list.InfoListAdapter; import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.settings.SettingsActivity; import org.schabi.newpipe.util.NavStack; -import java.io.IOException; -import static android.os.Build.VERSION.SDK_INT; import org.schabi.newpipe.util.ThemeHelper; +import java.io.IOException; + +import static android.os.Build.VERSION.SDK_INT; + /** * Copyright (C) Christian Schabesberger 2016 @@ -299,7 +301,7 @@ public class ChannelActivity extends AppCompatActivity { postNewErrorToast(h, R.string.network_error); ioe.printStackTrace(); } catch(ParsingException pe) { - ErrorActivity.reportError(h, ChannelActivity.this, pe, VideoItemDetailFragment.class, null, + ErrorActivity.reportError(h, ChannelActivity.this, pe, VideoItemDetailActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_CHANNEL, service.getServiceInfo().name, channelUrl, R.string.parsing_error)); h.post(new Runnable() { @@ -314,7 +316,7 @@ public class ChannelActivity extends AppCompatActivity { if(service != null) { name = service.getServiceInfo().name; } - ErrorActivity.reportError(h, ChannelActivity.this, ex, VideoItemDetailFragment.class, null, + ErrorActivity.reportError(h, ChannelActivity.this, ex, VideoItemDetailActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_CHANNEL, name, channelUrl, R.string.parsing_error)); h.post(new Runnable() { @@ -325,7 +327,7 @@ public class ChannelActivity extends AppCompatActivity { }); ex.printStackTrace(); } catch(Exception e) { - ErrorActivity.reportError(h, ChannelActivity.this, e, VideoItemDetailFragment.class, null, + ErrorActivity.reportError(h, ChannelActivity.this, e, VideoItemDetailActivity.class, null, ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_CHANNEL, service.getServiceInfo().name, channelUrl, R.string.general_error)); h.post(new Runnable() { diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index b87d97683..e9cddf16d 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -2,14 +2,12 @@ package org.schabi.newpipe; import android.app.Activity; import android.content.Intent; -import android.preference.PreferenceManager; -import android.support.v7.app.AppCompatActivity; 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.detail.VideoItemDetailFragment; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.util.NavStack; @@ -136,7 +134,7 @@ public class RouterActivity extends Activity { break; case STREAM: callIntent.setClass(this, VideoItemDetailActivity.class); - callIntent.putExtra(VideoItemDetailFragment.AUTO_PLAY, + callIntent.putExtra(VideoItemDetailActivity.AUTO_PLAY, PreferenceManager.getDefaultSharedPreferences(this) .getBoolean( getString(R.string.autoplay_through_intent_key), false)); diff --git a/app/src/main/java/org/schabi/newpipe/detail/StreamExtractorWorker.java b/app/src/main/java/org/schabi/newpipe/detail/StreamExtractorWorker.java new file mode 100644 index 000000000..730b5c653 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/detail/StreamExtractorWorker.java @@ -0,0 +1,250 @@ +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/detail/StreamInfoWorker.java b/app/src/main/java/org/schabi/newpipe/detail/StreamInfoWorker.java deleted file mode 100644 index 17a4572bd..000000000 --- a/app/src/main/java/org/schabi/newpipe/detail/StreamInfoWorker.java +++ /dev/null @@ -1,230 +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.extractor.exceptions.ParsingException; -import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; -import org.schabi.newpipe.extractor.stream_info.StreamExtractor; -import org.schabi.newpipe.extractor.stream_info.StreamInfo; -import org.schabi.newpipe.report.ErrorActivity; -import org.schabi.newpipe.R; -import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.extractor.StreamingService; -import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor; - -import java.io.IOException; - -/** - * Created by Christian Schabesberger on 02.08.16. - * - * Copyright (C) Christian Schabesberger 2016 - * StreamInfoWorker.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 StreamInfoWorker { - - private static final String TAG = StreamInfoWorker.class.toString(); - - public interface OnStreamInfoReceivedListener { - void onReceive(StreamInfo info); - void onError(int messageId); - void onReCaptchaException(); - void onBlockedByGemaError(); - void onContentErrorWithMessage(int messageId); - void onContentError(); - } - - private class StreamExtractorRunnable implements Runnable { - private final Handler h = new Handler(); - private StreamExtractor streamExtractor; - private final int serviceId; - private final String videoUrl; - private Activity a; - - public StreamExtractorRunnable(Activity a, String videoUrl, int serviceId) { - this.serviceId = serviceId; - this.videoUrl = videoUrl; - this.a = a; - } - - @Override - public void run() { - StreamInfo streamInfo = null; - StreamingService service = null; - try { - service = NewPipe.getService(serviceId); - } catch (Exception e) { - e.printStackTrace(); - ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null, - ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, - "", videoUrl, R.string.could_not_get_stream)); - return; - } - try { - streamExtractor = service.getExtractorInstance(videoUrl); - streamInfo = StreamInfo.getVideoInfo(streamExtractor); - - final StreamInfo info = streamInfo; - h.post(new Runnable() { - @Override - public void run() { - onStreamInfoReceivedListener.onReceive(info); - } - }); - - // look for errors during extraction - // this if statement only covers extra information. - // if these are not available or caused an error, they are just not available - // but don't render the stream information unusalbe. - if(streamInfo != null && - !streamInfo.errors.isEmpty()) { - Log.e(TAG, "OCCURRED ERRORS DURING EXTRACTION:"); - for (Throwable e : streamInfo.errors) { - e.printStackTrace(); - Log.e(TAG, "------"); - } - - View rootView = a != null ? a.findViewById(R.id.video_item_detail) : null; - ErrorActivity.reportError(h, a, - streamInfo.errors, null, rootView, - ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, - service.getServiceInfo().name, videoUrl, 0 /* no message for the user */)); - } - - // These errors render the stream information unusable. - } catch (ReCaptchaException e) { - h.post(new Runnable() { - @Override - public void run() { - onStreamInfoReceivedListener.onReCaptchaException(); - } - }); - } catch (IOException e) { - h.post(new Runnable() { - @Override - public void run() { - onStreamInfoReceivedListener.onError(R.string.network_error); - } - }); - e.printStackTrace(); - } catch (YoutubeStreamExtractor.DecryptException de) { - // custom service related exceptions - ErrorActivity.reportError(h, a, de, VideoItemDetailFragment.class, null, - ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, - service.getServiceInfo().name, videoUrl, R.string.youtube_signature_decryption_error)); - h.post(new Runnable() { - @Override - public void run() { - a.finish(); - } - }); - de.printStackTrace(); - } catch (YoutubeStreamExtractor.GemaException ge) { - h.post(new Runnable() { - @Override - public void run() { - onStreamInfoReceivedListener.onBlockedByGemaError(); - } - }); - } catch(YoutubeStreamExtractor.LiveStreamException e) { - h.post(new Runnable() { - @Override - public void run() { - onStreamInfoReceivedListener - .onContentErrorWithMessage(R.string.live_streams_not_supported); - } - }); - } - // ---------------------------------------- - catch(StreamExtractor.ContentNotAvailableException e) { - h.post(new Runnable() { - @Override - public void run() { - onStreamInfoReceivedListener - .onContentError(); - } - }); - e.printStackTrace(); - } catch(StreamInfo.StreamExctractException e) { - if(!streamInfo.errors.isEmpty()) { - // !!! if this case ever kicks in someone gets kicked out !!! - ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null, - ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, - service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream)); - } else { - ErrorActivity.reportError(h, a, streamInfo.errors, VideoItemDetailFragment.class, null, - ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, - service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream)); - } - h.post(new Runnable() { - @Override - public void run() { - a.finish(); - } - }); - e.printStackTrace(); - } catch (ParsingException e) { - ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null, - ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, - service.getServiceInfo().name, videoUrl, R.string.parsing_error)); - h.post(new Runnable() { - @Override - public void run() { - a.finish(); - } - }); - e.printStackTrace(); - } catch(Exception e) { - ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null, - ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM, - service.getServiceInfo().name, videoUrl, R.string.general_error)); - h.post(new Runnable() { - @Override - public void run() { - a.finish(); - } - }); - e.printStackTrace(); - } - } - } - - private static StreamInfoWorker streamInfoWorker = null; - private StreamExtractorRunnable runnable = null; - private OnStreamInfoReceivedListener onStreamInfoReceivedListener = null; - - private StreamInfoWorker() { - - } - - public static StreamInfoWorker getInstance() { - return streamInfoWorker == null ? (streamInfoWorker = new StreamInfoWorker()) : streamInfoWorker; - } - - public void search(int serviceId, String url, Activity a) { - runnable = new StreamExtractorRunnable(a, url, serviceId); - Thread thread = new Thread(runnable); - thread.start(); - } - - public void setOnStreamInfoReceivedListener( - OnStreamInfoReceivedListener onStreamInfoReceivedListener) { - this.onStreamInfoReceivedListener = onStreamInfoReceivedListener; - } -} diff --git a/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailActivity.java b/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailActivity.java index 97935edf4..188479009 100644 --- a/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailActivity.java +++ b/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailActivity.java @@ -1,77 +1,167 @@ package org.schabi.newpipe.detail; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Bitmap; import android.media.AudioManager; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v4.content.ContextCompat; +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.Menu; import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; -import org.schabi.newpipe.App; +import com.nirhart.parallaxscroll.views.ParallaxScrollView; +import com.nostra13.universalimageloader.core.DisplayImageOptions; +import com.nostra13.universalimageloader.core.ImageLoader; +import com.nostra13.universalimageloader.core.assist.FailReason; +import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; +import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; + +import org.schabi.newpipe.ActivityCommunicator; +import org.schabi.newpipe.ImageErrorLoadingListener; +import org.schabi.newpipe.Localization; import org.schabi.newpipe.R; +import org.schabi.newpipe.ReCaptchaActivity; +import org.schabi.newpipe.download.DownloadDialog; +import org.schabi.newpipe.extractor.InfoItem; +import org.schabi.newpipe.extractor.MediaFormat; +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.info_list.InfoItemBuilder; +import org.schabi.newpipe.player.AbstractPlayer; +import org.schabi.newpipe.player.BackgroundPlayer; +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.PermissionHelper; import org.schabi.newpipe.util.ThemeHelper; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicBoolean; -/** - * Copyright (C) Christian Schabesberger 2015 - * VideoItemDetailActivity.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 . - */ +@SuppressWarnings("FieldCanBeLocal") +public class VideoItemDetailActivity extends AppCompatActivity implements StreamExtractorWorker.OnStreamInfoReceivedListener, SharedPreferences.OnSharedPreferenceChangeListener { -public class VideoItemDetailActivity extends AppCompatActivity { - private static final String TAG = VideoItemDetailActivity.class.toString(); + private static final String TAG = "VideoItemDetailActivity"; + private static final String KORE_PACKET = "org.xbmc.kore"; - private VideoItemDetailFragment fragment; + /** + * The fragment argument representing the item ID that this fragment + * represents. + */ + public static final String AUTO_PLAY = "auto_play"; + private ActionBarHandler actionBarHandler; + + private InfoItemBuilder infoItemBuilder = null; + private StreamInfo currentStreamInfo = null; + private StreamExtractorWorker curExtractorThread; private String videoUrl; - private int currentStreamingService = -1; + private int serviceId = -1; - protected void onCreate(Bundle savedInstanceState) { + private AtomicBoolean isLoading = new AtomicBoolean(false); + private boolean needUpdate = false; + + 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 Bitmap streamThumbnail = null; + + /*////////////////////////////////////////////////////////////////////////// + // Views + //////////////////////////////////////////////////////////////////////////*/ + + private ProgressBar loadingProgressBar; + + private ParallaxScrollView parallaxScrollRootView; + private RelativeLayout contentRootLayout; + + private Button thumbnailBackgroundButton; + private ImageView thumbnailImageView; + private ImageView thumbnailPlayButton; + + private View videoTitleRoot; + private TextView videoTitleTextView; + private ImageView videoTitleToggleArrow; + private TextView videoCountView; + + private RelativeLayout videoDescriptionRootLayout; + private TextView videoUploadDateView; + private TextView videoDescriptionView; + + private Button uploaderButton; + private TextView uploaderTextView; + private ImageView uploaderThumb; + + private TextView thumbsUpTextView; + private ImageView thumbsUpImageView; + private TextView thumbsDownTextView; + private ImageView thumbsDownImageView; + private TextView thumbsDisabledTextView; + + private TextView nextStreamTitle; + private RelativeLayout relatedStreamRootLayout; + private LinearLayout relatedStreamsView; + + /*////////////////////////////////////////////////////////////////////////// + // Activity's Lifecycle + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + + 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); - // Show the Up button in the action bar. - try { - //noinspection ConstantConditions - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - } catch(Exception e) { - Log.d(TAG, "Could not get SupportActionBar"); - e.printStackTrace(); - } - // savedInstanceState is non-null when there is fragment state - // saved from previous configurations of this activity - // (e.g. when rotating the screen from portrait to landscape). - // In this case, the fragment will automatically be re-added - // to its container so we don't need to manually add it. - // For more information, see the Fragments API guide at: - // - // http://developer.android.com/guide/components/fragments.html - // + if (getSupportActionBar() != null) getSupportActionBar().setDisplayHomeAsUpEnabled(true); + else Log.e(TAG, "Could not get SupportActionBar"); - if (savedInstanceState == null) { - handleIntent(getIntent()); - } else { - videoUrl = savedInstanceState.getString(NavStack.URL); - currentStreamingService = savedInstanceState.getInt(NavStack.SERVICE_ID); - NavStack.getInstance() - .restoreSavedInstanceState(savedInstanceState); - addFragment(savedInstanceState); + initViews(); + initListeners(); + handleIntent(getIntent()); + } + + @Override + protected void onResume() { + super.onResume(); + + // Currently only used for enable/disable related videos + // but can be extended for other live settings change + if (needUpdate) { + if (relatedStreamsView != null) initRelatedVideos(currentStreamInfo); + needUpdate = false; } } @@ -82,74 +172,696 @@ public class VideoItemDetailActivity extends AppCompatActivity { handleIntent(intent); } - private void handleIntent(Intent intent) { - Bundle arguments = new Bundle(); - boolean autoplay = false; - - videoUrl = intent.getStringExtra(NavStack.URL); - currentStreamingService = intent.getIntExtra(NavStack.SERVICE_ID, -1); - if(intent.hasExtra(VideoItemDetailFragment.AUTO_PLAY)) { - arguments.putBoolean(VideoItemDetailFragment.AUTO_PLAY, - intent.getBooleanExtra(VideoItemDetailFragment.AUTO_PLAY, false)); - } - arguments.putString(NavStack.URL, videoUrl); - arguments.putInt(NavStack.SERVICE_ID, currentStreamingService); - addFragment(arguments); - } - - private void addFragment(final Bundle arguments) { - // Create the detail fragment and add it to the activity - // using a fragment transaction. - fragment = new VideoItemDetailFragment(); - fragment.setArguments(arguments); - getSupportFragmentManager().beginTransaction() - .replace(R.id.videoitem_detail_container, fragment) - .commit(); - } - - @Override - public void onResume() { - super.onResume(); - App.checkStartTor(this); - } - - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putString(NavStack.URL, videoUrl); - outState.putInt(NavStack.SERVICE_ID, currentStreamingService); - outState.putBoolean(VideoItemDetailFragment.AUTO_PLAY, false); - NavStack.getInstance() - .onSaveInstanceState(outState); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - super.onOptionsItemSelected(item); - int id = item.getItemId(); - if (id == android.R.id.home) { - // This ID represents the Home or Up button. In the case of this - // activity, the Up button is shown. Use NavUtils to allow users - // to navigate up one level in the application structure. For - // more details, see the Navigation pattern on Android Design: - - // http://developer.android.com/design/patterns/navigation.html#up-vs-back - - NavStack.getInstance() - .openMainActivity(this); - return true; - } else { - return super.onOptionsItemSelected(item); - } - } - @Override public void onBackPressed() { try { - NavStack.getInstance() - .navBack(this); + NavStack.getInstance().navBack(this); } catch (Exception e) { ErrorActivity.reportUiError(this, e); } } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + 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); + } else Log.e(TAG, "ReCaptcha failed"); + break; + default: + Log.e(TAG, "Request code from activity not supported [" + requestCode + "]"); + break; + } + } + + @Override + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (key.equals(getString(R.string.show_next_video_key))) { + showRelatedStreams = sharedPreferences.getBoolean(key, true); + needUpdate = true; + } + } + + /*////////////////////////////////////////////////////////////////////////// + // Init + //////////////////////////////////////////////////////////////////////////*/ + + public void initViews() { + loadingProgressBar = (ProgressBar) findViewById(R.id.detail_loading_progress_bar); + + parallaxScrollRootView = (ParallaxScrollView) 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); + + contentRootLayout = (RelativeLayout) 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); + + 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); + + //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); + + //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); + + 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); + + actionBarHandler = new ActionBarHandler(this); + actionBarHandler.setupNavMenu(this); + videoDescriptionView.setMovementMethod(LinkMovementMethod.getInstance()); + + infoItemBuilder = new InfoItemBuilder(this, findViewById(android.R.id.content)); + + setHeightThumbnail(); + } + + private void initListeners() { + videoTitleRoot.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (videoDescriptionRootLayout.getVisibility() == View.VISIBLE) { + videoTitleTextView.setMaxLines(1); + videoDescriptionRootLayout.setVisibility(View.GONE); + videoTitleToggleArrow.setImageResource(R.drawable.arrow_down); + } else { + videoTitleTextView.setMaxLines(10); + videoDescriptionRootLayout.setVisibility(View.VISIBLE); + videoTitleToggleArrow.setImageResource(R.drawable.arrow_up); + } + } + }); + thumbnailBackgroundButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (!isLoading.get() && currentStreamInfo != null) playVideo(currentStreamInfo); + } + }); + + infoItemBuilder.setOnStreamInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() { + @Override + public void selected(String url, int serviceId) { + NavStack.getInstance().openDetailActivity(VideoItemDetailActivity.this, url, serviceId); + } + }); + + uploaderButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + NavStack.getInstance().openChannelActivity(VideoItemDetailActivity.this, currentStreamInfo.channel_url, currentStreamInfo.service_id); + } + }); + } + + private void initThumbnailViews(StreamInfo info) { + if (info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) { + imageLoader.displayImage(info.thumbnail_url, thumbnailImageView, + displayImageOptions, new SimpleImageLoadingListener() { + + @Override + public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { + streamThumbnail = loadedImage; + + if (streamThumbnail != null) { + // TODO: Change the thumbnail implementation + + // When the thumbnail is not loaded yet, it not passes to the service in time + // so, I can notify the service through a broadcast, but the problem is + // when I click in another video, another thumbnail will be load, and will + // notify again, so I send the videoUrl and compare with the service's url + ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail; + Intent intent = new Intent(AbstractPlayer.ACTION_UPDATE_THUMB); + intent.putExtra(AbstractPlayer.VIDEO_URL, currentStreamInfo.webpage_url); + 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.ErrorInfo.make(ErrorActivity.LOAD_IMAGE, + NewPipe.getNameOfService(currentStreamInfo.service_id), imageUri, + R.string.could_not_load_thumbnails)); + } + + }); + } else thumbnailImageView.setImageResource(R.drawable.dummy_thumbnail_dark); + + 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)); + } + } + + private void initRelatedVideos(StreamInfo info) { + if (relatedStreamsView.getChildCount() > 0) relatedStreamsView.removeAllViews(); + + if (info.next_video != null && showRelatedStreams) { + nextStreamTitle.setVisibility(View.VISIBLE); + relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, info.next_video)); + relatedStreamsView.addView(getSeparatorView()); + relatedStreamsView.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); + } + + /*////////////////////////////////////////////////////////////////////////// + // Menu + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + actionBarHandler.setupMenu(menu, getMenuInflater()); + return super.onCreateOptionsMenu(menu); + } + + @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) { + actionBarHandler.setupStreamList(info.video_streams); + actionBarHandler.setOnShareListener(new ActionBarHandler.OnActionListener() { + @Override + public void onActionSelected(int selectedStreamId) { + if (isLoading.get()) return; + + Intent intent = new Intent(); + 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))); + } + }); + + actionBarHandler.setOnOpenInBrowserListener(new ActionBarHandler.OnActionListener() { + @Override + public void onActionSelected(int selectedStreamId) { + if (isLoading.get()) return; + + 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))); + } + }); + + actionBarHandler.setOnOpenInPopupListener(new ActionBarHandler.OnActionListener() { + @Override + 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(); + 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(); + 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); + } + }); + + actionBarHandler.setOnPlayWithKodiListener(new ActionBarHandler.OnActionListener() { + @Override + public void onActionSelected(int selectedStreamId) { + if (isLoading.get()) return; + + try { + 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); + } catch (Exception e) { + e.printStackTrace(); + AlertDialog.Builder builder = new AlertDialog.Builder(VideoItemDetailActivity.this); + 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); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + + } + }); + builder.create().show(); + } + } + }); + + actionBarHandler.setOnDownloadListener(new ActionBarHandler.OnActionListener() { + @Override + public void onActionSelected(int selectedStreamId) { + + if (isLoading.get() || !PermissionHelper.checkStoragePermissions(VideoItemDetailActivity.this)) { + return; + } + + try { + Bundle args = new Bundle(); + + // Sometimes it may be that some information is not available due to changes fo the + // website which was crawled. Then the ui has to understand this and act right. + + if (info.audio_streams != null) { + AudioStream audioStream = + info.audio_streams.get(getPreferredAudioStreamId(info)); + + String audioSuffix = "." + MediaFormat.getSuffixById(audioStream.format); + args.putString(DownloadDialog.AUDIO_URL, audioStream.url); + args.putString(DownloadDialog.FILE_SUFFIX_AUDIO, audioSuffix); + } + + if (info.video_streams != null) { + VideoStream selectedStreamItem = info.video_streams.get(selectedStreamId); + String videoSuffix = "." + MediaFormat.getSuffixById(selectedStreamItem.format); + args.putString(DownloadDialog.FILE_SUFFIX_VIDEO, videoSuffix); + args.putString(DownloadDialog.VIDEO_URL, selectedStreamItem.url); + } + + args.putString(DownloadDialog.TITLE, info.title); + DownloadDialog downloadDialog = DownloadDialog.newInstance(args); + downloadDialog.show(VideoItemDetailActivity.this.getSupportFragmentManager(), "downloadDialog"); + } catch (Exception e) { + Toast.makeText(VideoItemDetailActivity.this, + R.string.could_not_setup_download_menu, Toast.LENGTH_LONG).show(); + e.printStackTrace(); + } + } + }); + + if (info.audio_streams == null) { + actionBarHandler.showAudioAction(false); + } else { + actionBarHandler.setOnPlayAudioListener(new ActionBarHandler.OnActionListener() { + @Override + 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); + Intent intent; + AudioStream audioStream = + info.audio_streams.get(getPreferredAudioStreamId(info)); + 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.setAction(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.parse(audioStream.url), + MediaFormat.getMimeById(audioStream.format)); + intent.putExtra(BackgroundPlayer.TITLE, info.title); + 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); + } + } else { + intent = new Intent(); + try { + intent.setAction(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.parse(audioStream.url), + MediaFormat.getMimeById(audioStream.format)); + intent.putExtra(Intent.EXTRA_TITLE, info.title); + intent.putExtra("title", info.title); + // HERE !!! + VideoItemDetailActivity.this.startActivity(intent); + } catch (Exception e) { + e.printStackTrace(); + AlertDialog.Builder builder = new AlertDialog.Builder(VideoItemDetailActivity.this); + 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); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + Log.i(TAG, "You unlocked a secret unicorn."); + } + }); + builder.create().show(); + Log.e(TAG, "Either no Streaming player for audio was installed, or something important crashed:"); + e.printStackTrace(); + } + } + } + }); + } + } + + /*////////////////////////////////////////////////////////////////////////// + // 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); + } + + private void selectVideo(String url, int serviceId) { + if (curExtractorThread != null && curExtractorThread.isRunning()) curExtractorThread.cancel(); + + animateView(contentRootLayout, false, 200, null); + + thumbnailPlayButton.setVisibility(View.GONE); + loadingProgressBar.setVisibility(View.VISIBLE); + + imageLoader.cancelDisplayTask(thumbnailImageView); + imageLoader.cancelDisplayTask(uploaderThumb); + thumbnailImageView.setImageDrawable(null); + + curExtractorThread = StreamExtractorWorker.startExtractorThread(serviceId, url, this, this); + isLoading.set(true); + } + + public void playVideo(StreamInfo info) { + // ----------- 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)) { + + // External Player + Intent intent = new Intent(); + try { + intent.setAction(Intent.ACTION_VIEW) + .setDataAndType(Uri.parse(selectedVideoStream.url), MediaFormat.getMimeById(selectedVideoStream.format)) + .putExtra(Intent.EXTRA_TITLE, info.title) + .putExtra("title", info.title); + this.startActivity(intent); + } catch (Exception e) { + e.printStackTrace(); + AlertDialog.Builder builder = new AlertDialog.Builder(this); + 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() + .setAction(Intent.ACTION_VIEW) + .setData(Uri.parse(getString(R.string.fdroid_vlc_url))); + startActivity(intent); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + } + }); + builder.create().show(); + } + } else { + Intent intent; + boolean useOldPlayer = PreferenceManager + .getDefaultSharedPreferences(this) + .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) + .putExtra(AbstractPlayer.VIDEO_TITLE, info.title) + .putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url) + .putExtra(AbstractPlayer.CHANNEL_NAME, info.uploader) + .putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, actionBarHandler.getSelectedVideoStream()) + .putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, new ArrayList<>(info.video_streams)); + if (info.start_position > 0) intent.putExtra(AbstractPlayer.START_POSITION, info.start_position * 1000); + } else { + // Internal Player + intent = new Intent(this, 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); + 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); + 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()); + params.setMargins(m8, m5, m8, m5); + separator.setLayoutParams(params); + + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.separatorColor, typedValue, true); + separator.setBackgroundColor(typedValue.data); + return separator; + } + + private void setHeightThumbnail() { + boolean isPortrait = getResources().getDisplayMetrics().heightPixels > getResources().getDisplayMetrics().widthPixels; + int height = isPortrait ? (int) (getResources().getDisplayMetrics().widthPixels / (16.0f / 9.0f)) + : (int) (getResources().getDisplayMetrics().heightPixels / 2f); + thumbnailImageView.setScaleType(isPortrait ? ImageView.ScaleType.CENTER_CROP : ImageView.ScaleType.FIT_CENTER); + thumbnailImageView.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, height)); + thumbnailImageView.setMinimumHeight(height); + thumbnailBackgroundButton.setLayoutParams(new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, height)); + thumbnailBackgroundButton.setMinimumHeight(height); + } + + /** + * Animate the view + * + * @param view view that will be animated + * @param enterOrExit true to enter, false to exit + * @param duration how long the animation will take, in milliseconds + * @param execOnEnd runnable that will be executed when the animation ends + */ + public void animateView(final View view, final boolean enterOrExit, long duration, final Runnable execOnEnd) { + if (view.getVisibility() == View.VISIBLE && enterOrExit) { + view.animate().setListener(null).cancel(); + view.setVisibility(View.VISIBLE); + view.setAlpha(1f); + if (execOnEnd != null) execOnEnd.run(); + return; + } else if ((view.getVisibility() == View.GONE || view.getVisibility() == View.INVISIBLE) && !enterOrExit) { + view.animate().setListener(null).cancel(); + view.setVisibility(View.GONE); + view.setAlpha(0f); + if (execOnEnd != null) execOnEnd.run(); + return; + } + + view.animate().setListener(null).cancel(); + view.setVisibility(View.VISIBLE); + + if (enterOrExit) { + view.animate().alpha(1f).setDuration(duration) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + if (execOnEnd != null) execOnEnd.run(); + } + }).start(); + } else { + view.animate().alpha(0f) + .setDuration(duration) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + view.setVisibility(View.GONE); + if (execOnEnd != null) execOnEnd.run(); + } + }) + .start(); + } + } + + /*////////////////////////////////////////////////////////////////////////// + // OnStreamInfoReceivedListener callbacks + //////////////////////////////////////////////////////////////////////////*/ + + @Override + public void onReceive(StreamInfo info) { + currentStreamInfo = info; + + loadingProgressBar.setVisibility(View.GONE); + thumbnailPlayButton.setVisibility(View.VISIBLE); + relatedStreamRootLayout.setVisibility(showRelatedStreams ? View.VISIBLE : View.GONE); + parallaxScrollRootView.scrollTo(0, 0); + + // Since newpipe is designed to work even if certain information is not available, + // the UI has to react on missing information. + videoTitleTextView.setText(info.title); + 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)); + + if (info.view_count >= 0) videoCountView.setText(Localization.localizeViewCount(info.view_count, this)); + videoCountView.setVisibility(info.view_count >= 0 ? View.VISIBLE : View.GONE); + + if (info.dislike_count == -1 && info.like_count == -1) { + thumbsDownImageView.setVisibility(View.VISIBLE); + thumbsUpImageView.setVisibility(View.VISIBLE); + thumbsUpTextView.setVisibility(View.GONE); + thumbsDownTextView.setVisibility(View.GONE); + + thumbsDisabledTextView.setVisibility(View.VISIBLE); + } else { + thumbsDisabledTextView.setVisibility(View.GONE); + + if (info.dislike_count >= 0) thumbsDownTextView.setText(Localization.localizeNumber(info.dislike_count, this)); + 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)); + 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)); + 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) + ); + videoDescriptionView.setVisibility(!info.description.isEmpty() ? View.VISIBLE : View.GONE); + + videoDescriptionRootLayout.setVisibility(View.GONE); + videoTitleToggleArrow.setImageResource(R.drawable.arrow_down); + + setupActionBarHandler(info); + initRelatedVideos(info); + initThumbnailViews(info); + + animateView(contentRootLayout, true, 200, null); + + isLoading.set(false); + if (autoPlayEnabled) playVideo(info); + } + + @Override + public void onError(int messageId) { + Toast.makeText(this, messageId, Toast.LENGTH_LONG).show(); + loadingProgressBar.setVisibility(View.GONE); + thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.not_available_monkey)); + } + + @Override + public void onReCaptchaException() { + Toast.makeText(this, R.string.recaptcha_request_toast, Toast.LENGTH_LONG).show(); + // Starting ReCaptcha Challenge Activity + startActivityForResult(new Intent(this, ReCaptchaActivity.class), ReCaptchaActivity.RECAPTCHA_REQUEST); + } + + @Override + public void onBlockedByGemaError() { + loadingProgressBar.setVisibility(View.GONE); + thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.gruese_die_gema)); + thumbnailBackgroundButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent intent = new Intent(); + intent.setAction(Intent.ACTION_VIEW); + intent.setData(Uri.parse(getString(R.string.c3s_url))); + startActivity(intent); + } + }); + + Toast.makeText(this, 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(); + } + + @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(); + } } diff --git a/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java b/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java deleted file mode 100644 index 719a6bdbd..000000000 --- a/app/src/main/java/org/schabi/newpipe/detail/VideoItemDetailFragment.java +++ /dev/null @@ -1,866 +0,0 @@ -package org.schabi.newpipe.detail; - -import android.app.Activity; -import android.content.DialogInterface; -import android.content.Intent; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Point; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.preference.PreferenceManager; -import android.support.design.widget.FloatingActionButton; -import android.support.v4.app.Fragment; -import android.support.v7.app.AlertDialog; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.RecyclerView; -import android.text.Html; -import android.text.method.LinkMovementMethod; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ProgressBar; -import android.widget.RelativeLayout; -import android.widget.TextView; -import android.widget.Toast; - -import com.nostra13.universalimageloader.core.DisplayImageOptions; -import com.nostra13.universalimageloader.core.ImageLoader; -import com.nostra13.universalimageloader.core.assist.FailReason; -import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; - -import org.schabi.newpipe.ActivityCommunicator; -import org.schabi.newpipe.ImageErrorLoadingListener; -import org.schabi.newpipe.Localization; -import org.schabi.newpipe.R; -import org.schabi.newpipe.ReCaptchaActivity; -import org.schabi.newpipe.download.DownloadDialog; -import org.schabi.newpipe.extractor.InfoItem; -import org.schabi.newpipe.extractor.MediaFormat; -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.info_list.InfoItemBuilder; -import org.schabi.newpipe.player.AbstractPlayer; -import org.schabi.newpipe.player.BackgroundPlayer; -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.PermissionHelper; - -import java.util.ArrayList; -import java.util.Vector; - -import static android.app.Activity.RESULT_OK; -import static org.schabi.newpipe.ReCaptchaActivity.RECAPTCHA_REQUEST; - - -/** - * Copyright (C) Christian Schabesberger 2015 - * VideoItemDetailFragment.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 VideoItemDetailFragment extends Fragment { - - private static final String TAG = VideoItemDetailFragment.class.toString(); - private static final String KORE_PACKET = "org.xbmc.kore"; - - /** - * The fragment argument representing the item ID that this fragment - * represents. - */ - public static final String AUTO_PLAY = "auto_play"; - - private AppCompatActivity activity; - private ActionBarHandler actionBarHandler; - private ProgressBar progressBar; - - private int streamingServiceId = -1; - - private boolean autoPlayEnabled; - private boolean showNextStreamItem; - - private View thumbnailWindowLayout; - //this only remains due to downwards compatibility - private FloatingActionButton playVideoButton; - private final Point initialThumbnailPos = new Point(0, 0); - private View rootView = null; - private Bitmap streamThumbnail = null; - - private ImageLoader imageLoader = ImageLoader.getInstance(); - private DisplayImageOptions displayImageOptions = - new DisplayImageOptions.Builder().cacheInMemory(true).build(); - - private InfoItemBuilder infoItemBuilder = null; - - public interface OnInvokeCreateOptionsMenuListener { - void createOptionsMenu(); - } - - private OnInvokeCreateOptionsMenuListener onInvokeCreateOptionsMenuListener; - - private void updateInfo(final StreamInfo info) { - Activity a = getActivity(); - - RelativeLayout textContentLayout = - (RelativeLayout) activity.findViewById(R.id.detail_text_content_layout); - final TextView videoTitleView = - (TextView) activity.findViewById(R.id.detail_video_title_view); - TextView uploaderView = (TextView) activity.findViewById(R.id.detail_uploader_view); - TextView viewCountView = (TextView) activity.findViewById(R.id.detail_view_count_view); - TextView thumbsUpView = (TextView) activity.findViewById(R.id.detail_thumbs_up_count_view); - TextView thumbsDownView = - (TextView) activity.findViewById(R.id.detail_thumbs_down_count_view); - TextView uploadDateView = (TextView) activity.findViewById(R.id.detail_upload_date_view); - TextView descriptionView = (TextView) activity.findViewById(R.id.detail_description_view); - RecyclerView nextStreamView = - (RecyclerView) activity.findViewById(R.id.detail_next_stream_content); - RelativeLayout nextVideoRootFrame = - (RelativeLayout) activity.findViewById(R.id.detail_next_stream_root_layout); - TextView similarTitle = (TextView) activity.findViewById(R.id.detail_similar_title); - Button backgroundButton = (Button) - activity.findViewById(R.id.detail_stream_thumbnail_window_background_button); - View thumbnailView = activity.findViewById(R.id.detail_thumbnail_view); - View topView = activity.findViewById(R.id.detailTopView); - Button channelButton = (Button) activity.findViewById(R.id.channel_button); - - // prevents a crash if the activity/fragment was already left when the response came - if(channelButton != null) { - - progressBar.setVisibility(View.GONE); - if (info.next_video != null) { - // todo: activate this function or remove it - nextStreamView.setVisibility(View.GONE); - } else { - nextStreamView.setVisibility(View.GONE); - activity.findViewById(R.id.detail_similar_title).setVisibility(View.GONE); - } - - textContentLayout.setVisibility(View.VISIBLE); - if (android.os.Build.VERSION.SDK_INT < 18) { - playVideoButton.setVisibility(View.VISIBLE); - } else { - ImageView playArrowView = (ImageView) activity.findViewById(R.id.play_arrow_view); - playArrowView.setVisibility(View.VISIBLE); - } - - if (!showNextStreamItem) { - nextVideoRootFrame.setVisibility(View.GONE); - similarTitle.setVisibility(View.GONE); - } - - videoTitleView.setText(info.title); - - topView.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View v, MotionEvent event) { - if (event.getAction() == android.view.MotionEvent.ACTION_UP) { - ImageView arrow = (ImageView) activity.findViewById(R.id.toggle_description_view); - View extra = activity.findViewById(R.id.detailExtraView); - if (extra.getVisibility() == View.VISIBLE) { - extra.setVisibility(View.GONE); - arrow.setImageResource(R.drawable.arrow_down); - } else { - extra.setVisibility(View.VISIBLE); - arrow.setImageResource(R.drawable.arrow_up); - } - } - return true; - } - }); - - // Since newpipe is designed to work even if certain information is not available, - // the UI has to react on missing information. - videoTitleView.setText(info.title); - if (!info.uploader.isEmpty()) { - uploaderView.setText(info.uploader); - } else { - activity.findViewById(R.id.detail_uploader_view).setVisibility(View.GONE); - } - if (info.view_count >= 0) { - viewCountView.setText(Localization.localizeViewCount(info.view_count, a)); - } else { - viewCountView.setVisibility(View.GONE); - } - if (info.dislike_count >= 0) { - thumbsDownView.setText(Localization.localizeNumber(info.dislike_count, a)); - } else { - thumbsDownView.setVisibility(View.INVISIBLE); - activity.findViewById(R.id.detail_thumbs_down_count_view).setVisibility(View.GONE); - } - if (info.like_count >= 0) { - thumbsUpView.setText(Localization.localizeNumber(info.like_count, a)); - } else { - thumbsUpView.setVisibility(View.GONE); - activity.findViewById(R.id.detail_thumbs_up_img_view).setVisibility(View.GONE); - thumbsDownView.setVisibility(View.GONE); - activity.findViewById(R.id.detail_thumbs_down_img_view).setVisibility(View.GONE); - } - if (!info.upload_date.isEmpty()) { - uploadDateView.setText(Localization.localizeDate(info.upload_date, a)); - } else { - uploadDateView.setVisibility(View.GONE); - } - if (!info.description.isEmpty()) { - descriptionView.setText(Html.fromHtml(info.description)); - } else { - descriptionView.setVisibility(View.GONE); - } - - descriptionView.setMovementMethod(LinkMovementMethod.getInstance()); - - // parse streams - Vector streamsToUse = new Vector<>(); - for (VideoStream i : info.video_streams) { - if (useStream(i, streamsToUse)) { - streamsToUse.add(i); - } - } - - textContentLayout.setVisibility(View.VISIBLE); - - if (info.next_video == null) { - activity.findViewById(R.id.detail_next_stream_title).setVisibility(View.GONE); - } - - if (info.related_streams != null && !info.related_streams.isEmpty()) { - initSimilarVideos(info); - } else { - activity.findViewById(R.id.detail_similar_title).setVisibility(View.GONE); - activity.findViewById(R.id.similar_streams_view).setVisibility(View.GONE); - } - - setupActionBarHandler(info); - - if (autoPlayEnabled) { - playVideo(info); - } - - if (android.os.Build.VERSION.SDK_INT < 18) { - playVideoButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - playVideo(info); - } - }); - } - - backgroundButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - playVideo(info); - } - }); - - //todo: make backgroundButton handle this - thumbnailView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - playVideo(info); - } - }); - - if (info.channel_url != null && info.channel_url != "") { - channelButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - NavStack.getInstance() - .openChannelActivity(getActivity(), info.channel_url, info.service_id); - } - }); - } else { - channelButton.setVisibility(Button.GONE); - } - - initThumbnailViews(info); - } - } - - private void initThumbnailViews(final StreamInfo info) { - ImageView videoThumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view); - ImageView uploaderThumb - = (ImageView) activity.findViewById(R.id.detail_uploader_thumbnail_view); - - if (info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) { - imageLoader.displayImage(info.thumbnail_url, videoThumbnailView, - displayImageOptions, new ImageLoadingListener() { - @Override - public void onLoadingStarted(String imageUri, View view) { - } - - @Override - public void onLoadingFailed(String imageUri, View view, FailReason failReason) { - ErrorActivity.reportError(getActivity(), - failReason.getCause(), null, rootView, - ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE, - NewPipe.getNameOfService(info.service_id), imageUri, - R.string.could_not_load_thumbnails)); - } - - @Override - public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { - streamThumbnail = loadedImage; - - if (streamThumbnail != null) { - // TODO: Change the thumbnail implementation - - // When the thumbnail is not loaded yet, it not passes to the service in time - // so, I can notify the service through a broadcast, but the problem is - // when I click in another video, another thumbnail will be load, and will - // notify again, so I send the videoUrl and compare with the service's url - if (getContext() != null) { - ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail; - Intent intent = new Intent(AbstractPlayer.ACTION_UPDATE_THUMB); - intent.putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url); - getContext().sendBroadcast(intent); - } - } - } - - @Override - public void onLoadingCancelled(String imageUri, View view) { - } - }); - } else { - videoThumbnailView.setImageResource(R.drawable.dummy_thumbnail_dark); - } - if (info.uploader_thumbnail_url != null && !info.uploader_thumbnail_url.isEmpty()) { - imageLoader.displayImage(info.uploader_thumbnail_url, - uploaderThumb, displayImageOptions, - new ImageErrorLoadingListener(activity, rootView, info.service_id)); - } - } - - private void setupActionBarHandler(final StreamInfo info) { - actionBarHandler.setupStreamList(info.video_streams); - - actionBarHandler.setOnShareListener(new ActionBarHandler.OnActionListener() { - @Override - public void onActionSelected(int selectedStreamId) { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_SEND); - intent.putExtra(Intent.EXTRA_TEXT, info.webpage_url); - intent.setType("text/plain"); - activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.share_dialog_title))); - } - }); - - actionBarHandler.setOnOpenInBrowserListener(new ActionBarHandler.OnActionListener() { - @Override - public void onActionSelected(int selectedStreamId) { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_VIEW); - intent.setData(Uri.parse(info.webpage_url)); - - activity.startActivity(Intent.createChooser(intent, activity.getString(R.string.choose_browser))); - } - }); - - actionBarHandler.setOnOpenInPopupListener(new ActionBarHandler.OnActionListener() { - @Override - public void onActionSelected(int selectedStreamId) { - 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(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); - - activity.startService(i); - } - }); - - actionBarHandler.setOnPlayWithKodiListener(new ActionBarHandler.OnActionListener() { - @Override - public void onActionSelected(int selectedStreamId) { - try { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setPackage(KORE_PACKET); - intent.setData(Uri.parse(info.webpage_url.replace("https", "http"))); - activity.startActivity(intent); - } catch (Exception e) { - e.printStackTrace(); - 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(activity.getString(R.string.fdroid_kore_url))); - activity.startActivity(intent); - } - }) - .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - - } - }); - builder.create().show(); - } - } - }); - - actionBarHandler.setOnDownloadListener(new ActionBarHandler.OnActionListener() { - @Override - public void onActionSelected(int selectedStreamId) { - if(!PermissionHelper.checkStoragePermissions(getActivity())) { - return; - } - - try { - Bundle args = new Bundle(); - - // Sometimes it may be that some information is not available due to changes fo the - // website which was crawled. Then the ui has to understand this and act right. - - if (info.audio_streams != null) { - AudioStream audioStream = - info.audio_streams.get(getPreferredAudioStreamId(info)); - - String audioSuffix = "." + MediaFormat.getSuffixById(audioStream.format); - args.putString(DownloadDialog.AUDIO_URL, audioStream.url); - args.putString(DownloadDialog.FILE_SUFFIX_AUDIO, audioSuffix); - } - - if (info.video_streams != null) { - VideoStream selectedStreamItem = info.video_streams.get(selectedStreamId); - String videoSuffix = "." + MediaFormat.getSuffixById(selectedStreamItem.format); - args.putString(DownloadDialog.FILE_SUFFIX_VIDEO, videoSuffix); - args.putString(DownloadDialog.VIDEO_URL, selectedStreamItem.url); - } - - args.putString(DownloadDialog.TITLE, info.title); - DownloadDialog downloadDialog = DownloadDialog.newInstance(args); - downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog"); - } catch (Exception e) { - Toast.makeText(VideoItemDetailFragment.this.getActivity(), - R.string.could_not_setup_download_menu, Toast.LENGTH_LONG).show(); - e.printStackTrace(); - } - } - }); - - if (info.audio_streams == null) { - actionBarHandler.showAudioAction(false); - } else { - actionBarHandler.setOnPlayAudioListener(new ActionBarHandler.OnActionListener() { - @Override - public void onActionSelected(int selectedStreamId) { - 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)); - 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(activity, BackgroundPlayer.class); - - intent.setAction(Intent.ACTION_VIEW); - Log.i(TAG, "audioStream is null:" + (audioStream == null)); - Log.i(TAG, "audioStream.url is null:" + (audioStream.url == null)); - intent.setDataAndType(Uri.parse(audioStream.url), - MediaFormat.getMimeById(audioStream.format)); - intent.putExtra(BackgroundPlayer.TITLE, info.title); - intent.putExtra(BackgroundPlayer.WEB_URL, info.webpage_url); - intent.putExtra(BackgroundPlayer.SERVICE_ID, streamingServiceId); - intent.putExtra(BackgroundPlayer.CHANNEL_NAME, info.uploader); - activity.startService(intent); - } - } else { - intent = new Intent(); - try { - intent.setAction(Intent.ACTION_VIEW); - intent.setDataAndType(Uri.parse(audioStream.url), - MediaFormat.getMimeById(audioStream.format)); - intent.putExtra(Intent.EXTRA_TITLE, info.title); - intent.putExtra("title", info.title); - // HERE !!! - activity.startActivity(intent); - } catch (Exception e) { - e.printStackTrace(); - 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(activity.getString(R.string.fdroid_vlc_url))); - activity.startActivity(intent); - } - }) - .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Log.i(TAG, "You unlocked a secret unicorn."); - } - }); - builder.create().show(); - Log.e(TAG, "Either no Streaming player for audio was installed, or something important crashed:"); - e.printStackTrace(); - } - } - } - }); - } - } - - private int getPreferredAudioStreamId(final StreamInfo info) { - String preferredFormatString = PreferenceManager.getDefaultSharedPreferences(getActivity()) - .getString(activity.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 void initSimilarVideos(final StreamInfo info) { - LinearLayout similarLayout = (LinearLayout) activity.findViewById(R.id.similar_streams_view); - for (final InfoItem item : info.related_streams) { - similarLayout.addView(infoItemBuilder.buildView(similarLayout, item)); - } - infoItemBuilder.setOnStreamInfoItemSelectedListener( - new InfoItemBuilder.OnInfoItemSelectedListener() { - @Override - public void selected(String url, int serviceId) { - NavStack.getInstance() - .openDetailActivity(getContext(), url, serviceId); - } - }); - } - - private void onErrorBlockedByGema() { - Button backgroundButton = (Button) - activity.findViewById(R.id.detail_stream_thumbnail_window_background_button); - ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view); - - progressBar.setVisibility(View.GONE); - thumbnailView.setImageBitmap(BitmapFactory.decodeResource( - getResources(), R.drawable.gruese_die_gema)); - backgroundButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_VIEW); - intent.setData(Uri.parse(activity.getString(R.string.c3s_url))); - activity.startActivity(intent); - } - }); - - Toast.makeText(VideoItemDetailFragment.this.getActivity(), - R.string.blocked_by_gema, Toast.LENGTH_LONG).show(); - } - - private void onNotSpecifiedContentError() { - ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view); - progressBar.setVisibility(View.GONE); - thumbnailView.setImageBitmap(BitmapFactory.decodeResource( - getResources(), R.drawable.not_available_monkey)); - Toast.makeText(activity, R.string.content_not_available, Toast.LENGTH_LONG) - .show(); - } - - private void onNotSpecifiedContentErrorWithMessage(int resourceId) { - ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view); - progressBar.setVisibility(View.GONE); - thumbnailView.setImageBitmap(BitmapFactory.decodeResource( - getResources(), R.drawable.not_available_monkey)); - Toast.makeText(activity, resourceId, Toast.LENGTH_LONG) - .show(); - } - - private boolean useStream(VideoStream stream, Vector streams) { - for(VideoStream i : streams) { - if(i.resolution.equals(stream.resolution)) { - return false; - } - } - return true; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - activity = (AppCompatActivity) getActivity(); - showNextStreamItem = PreferenceManager.getDefaultSharedPreferences(getActivity()) - .getBoolean(activity.getString(R.string.show_next_video_key), true); - - - StreamInfoWorker siw = StreamInfoWorker.getInstance(); - siw.setOnStreamInfoReceivedListener(new StreamInfoWorker.OnStreamInfoReceivedListener() { - @Override - public void onReceive(StreamInfo info) { - updateInfo(info); - } - - @Override - public void onError(int messageId) { - postNewErrorToast(messageId); - } - - @Override - public void onReCaptchaException() { - Toast.makeText(getActivity(), R.string.recaptcha_request_toast, - Toast.LENGTH_LONG).show(); - - // Starting ReCaptcha Challenge Activity - startActivityForResult( - new Intent(getActivity(), ReCaptchaActivity.class), - RECAPTCHA_REQUEST); - } - - @Override - public void onBlockedByGemaError() { - onErrorBlockedByGema(); - } - - @Override - public void onContentErrorWithMessage(int messageId) { - onNotSpecifiedContentErrorWithMessage(messageId); - } - - @Override - public void onContentError() { - onNotSpecifiedContentError(); - } - }); - setHasOptionsMenu(true); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - rootView = inflater.inflate(R.layout.fragment_videoitem_detail, container, false); - progressBar = (ProgressBar) rootView.findViewById(R.id.detail_progress_bar); - - actionBarHandler = new ActionBarHandler(activity); - actionBarHandler.setupNavMenu(activity); - if(onInvokeCreateOptionsMenuListener != null) { - onInvokeCreateOptionsMenuListener.createOptionsMenu(); - } - - return rootView; - } - - @Override - public void onStart() { - super.onStart(); - Activity a = getActivity(); - infoItemBuilder = new InfoItemBuilder(a, a.findViewById(android.R.id.content)); - - if (android.os.Build.VERSION.SDK_INT < 18) { - playVideoButton = (FloatingActionButton) a.findViewById(R.id.play_video_button); - } - thumbnailWindowLayout = a.findViewById(R.id.detail_stream_thumbnail_window_layout); - Button backgroundButton = (Button) - a.findViewById(R.id.detail_stream_thumbnail_window_background_button); - - // Sometimes when this fragment is not visible it still gets initiated - // then we must not try to access objects of this fragment. - // Otherwise the applications would crash. - if(backgroundButton != null) { - streamingServiceId = getArguments().getInt(NavStack.SERVICE_ID); - String videoUrl = getArguments().getString(NavStack.URL); - StreamInfoWorker siw = StreamInfoWorker.getInstance(); - siw.search(streamingServiceId, videoUrl, getActivity()); - - autoPlayEnabled = getArguments().getBoolean(AUTO_PLAY); - - if(Build.VERSION.SDK_INT >= 18) { - ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view); - thumbnailView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { - // This is used to synchronize the thumbnailWindowButton and the playVideoButton - // inside the ScrollView with the actual size of the thumbnail. - //todo: onLayoutChage sometimes not triggered - // background buttons area seem to overlap the thumbnail view - // So although you just clicked slightly beneath the thumbnail the action still - // gets triggered. - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, - int oldLeft, int oldTop, int oldRight, int oldBottom) { - RelativeLayout.LayoutParams newWindowLayoutParams = - (RelativeLayout.LayoutParams) thumbnailWindowLayout.getLayoutParams(); - newWindowLayoutParams.height = bottom - top; - thumbnailWindowLayout.setLayoutParams(newWindowLayoutParams); - - //noinspection SuspiciousNameCombination - initialThumbnailPos.set(top, left); - - } - }); - } - } - } - - public void playVideo(final StreamInfo info) { - // ----------- THE MAGIC MOMENT --------------- - VideoStream selectedVideoStream = - info.video_streams.get(actionBarHandler.getSelectedVideoStream()); - - if (PreferenceManager.getDefaultSharedPreferences(activity) - .getBoolean(activity.getString(R.string.use_external_video_player_key), false)) { - - // External Player - Intent intent = new Intent(); - try { - intent.setAction(Intent.ACTION_VIEW) - .setDataAndType(Uri.parse(selectedVideoStream.url), - MediaFormat.getMimeById(selectedVideoStream.format)) - .putExtra(Intent.EXTRA_TITLE, info.title) - .putExtra("title", info.title); - - activity.startActivity(intent); // HERE !!! - } catch (Exception e) { - e.printStackTrace(); - 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() - .setAction(Intent.ACTION_VIEW) - .setData(Uri.parse(activity.getString(R.string.fdroid_vlc_url))); - activity.startActivity(intent); - } - }) - .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - - } - }); - builder.create().show(); - } - } else { - Intent intent; - boolean useOldPlayer = PreferenceManager - .getDefaultSharedPreferences(activity) - .getBoolean(activity.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(activity, ExoPlayerActivity.class) - .putExtra(AbstractPlayer.VIDEO_TITLE, info.title) - .putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url) - .putExtra(AbstractPlayer.CHANNEL_NAME, info.uploader) - .putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, actionBarHandler.getSelectedVideoStream()) - .putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, new ArrayList<>(info.video_streams)); - if (info.start_position > 0) intent.putExtra(AbstractPlayer.START_POSITION, info.start_position * 1000); - } else { - // Internal Player - 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); - activity.startActivity(intent); - } - - // -------------------------------------------- - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - actionBarHandler.setupMenu(menu, inflater); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if(!actionBarHandler.onItemSelected(item)) { - return super.onOptionsItemSelected(item); - } else { - return true; - } - } - - public void setOnInvokeCreateOptionsMenuListener(OnInvokeCreateOptionsMenuListener listener) { - this.onInvokeCreateOptionsMenuListener = listener; - } - - private void postNewErrorToast(final int stringResource) { - Toast.makeText(VideoItemDetailFragment.this.getActivity(), - stringResource, Toast.LENGTH_LONG).show(); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - switch (requestCode) { - case RECAPTCHA_REQUEST: - if (resultCode == RESULT_OK) { - String videoUrl = getArguments().getString(NavStack.URL); - StreamInfoWorker siw = StreamInfoWorker.getInstance(); - siw.search(streamingServiceId, videoUrl, getActivity()); - } else { - Log.d(TAG, "ReCaptcha failed"); - } - break; - - default: - Log.e(TAG, "Request code from activity not supported [" + requestCode + "]"); - break; - } - } -} 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 661cb1632..090179a7a 100644 --- a/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java +++ b/app/src/main/java/org/schabi/newpipe/player/PopupVideoPlayer.java @@ -273,7 +273,7 @@ public class PopupVideoPlayer extends Service { .putExtra(NavStack.URL, videoUrl) .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(i); - //NavStack.getInstance().openDetailActivity(context, videoUrl, 0); + context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); } /*////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/util/NavStack.java b/app/src/main/java/org/schabi/newpipe/util/NavStack.java index a5941ad83..d06aafe7e 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavStack.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavStack.java @@ -5,13 +5,10 @@ import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.v4.app.NavUtils; -import android.support.v7.app.AppCompatActivity; -import android.util.Log; import org.schabi.newpipe.ChannelActivity; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.detail.VideoItemDetailActivity; -import org.schabi.newpipe.detail.VideoItemDetailFragment; import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.StreamingService; diff --git a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java index a4fdb72f7..8021a0972 100644 --- a/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/ThemeHelper.java @@ -7,18 +7,21 @@ import org.schabi.newpipe.R; public class ThemeHelper { - public static void setTheme(Context context, boolean mode) { - // mode is true for normal theme, false for no action bar theme. - + /** + * Apply the selected theme (on NewPipe settings) in the context + * + * @param context context that the theme will be applied + * @param useActionbarTheme whether to use an action bar theme or not + */ + public static void setTheme(Context context, boolean useActionbarTheme) { String themeKey = context.getString(R.string.theme_key); - //String lightTheme = context.getResources().getString(R.string.light_theme_title); String darkTheme = context.getResources().getString(R.string.dark_theme_title); String blackTheme = context.getResources().getString(R.string.black_theme_title); String sp = PreferenceManager.getDefaultSharedPreferences(context) .getString(themeKey, context.getResources().getString(R.string.light_theme_title)); - if (mode) { + if (useActionbarTheme) { if (sp.equals(darkTheme)) context.setTheme(R.style.DarkTheme); else if (sp.equals(blackTheme)) context.setTheme(R.style.BlackTheme); else context.setTheme(R.style.AppTheme); diff --git a/app/src/main/res/layout-v18/fragment_videoitem_detail.xml b/app/src/main/res/layout-v18/fragment_videoitem_detail.xml deleted file mode 100644 index 6654f6c63..000000000 --- a/app/src/main/res/layout-v18/fragment_videoitem_detail.xml +++ /dev/null @@ -1,279 +0,0 @@ - - - - - - - - - - - - - - - - -