mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-11-04 01:03: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