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