Merge branch 'master' of github.com:theScrabi/NewPipe

This commit is contained in:
Christian Schabesberger 2017-04-13 19:41:28 +02:00
commit f9f48a5eb6
49 changed files with 2133 additions and 1519 deletions

View File

@ -8,8 +8,8 @@ android {
applicationId "org.schabi.newpipe" applicationId "org.schabi.newpipe"
minSdkVersion 15 minSdkVersion 15
targetSdkVersion 25 targetSdkVersion 25
versionCode 28 versionCode 29
versionName "0.9.1" versionName "0.9.2"
} }
buildTypes { buildTypes {
release { release {

View File

@ -19,22 +19,15 @@
tools:ignore="AllowBackup"> tools:ignore="AllowBackup">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:label="@string/app_name"> android:label="@string/app_name"
android:launchMode="singleTask">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </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 <activity
android:name=".player.PlayVideoActivity" android:name=".player.PlayVideoActivity"
android:configChanges="orientation|keyboardHidden|screenSize" android:configChanges="orientation|keyboardHidden|screenSize"
@ -50,7 +43,7 @@
android:name=".player.ExoPlayerActivity" android:name=".player.ExoPlayerActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize" android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleInstance" android:launchMode="singleTask"
android:theme="@style/PlayerTheme"/> android:theme="@style/PlayerTheme"/>
<activity <activity
@ -87,9 +80,6 @@
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTop" android:launchMode="singleTop"
android:theme="@style/FilePickerTheme"/> android:theme="@style/FilePickerTheme"/>
<activity
android:name=".ChannelActivity"
android:launchMode="singleTask" />
<activity <activity
android:name=".ReCaptchaActivity" android:name=".ReCaptchaActivity"
android:label="@string/reCaptchaActivity" /> android:label="@string/reCaptchaActivity" />
@ -104,7 +94,9 @@
android:resource="@xml/provider_paths" /> android:resource="@xml/provider_paths" />
</provider> </provider>
<activity android:name=".RouterActivity" <activity
android:name=".RouterActivity"
android:taskAffinity=""
android:theme="@android:style/Theme.NoDisplay"> android:theme="@android:style/Theme.NoDisplay">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -161,7 +153,9 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".PopupActivity" <activity
android:name=".RouterPopupActivity"
android:taskAffinity=""
android:theme="@android:style/Theme.NoDisplay" android:theme="@android:style/Theme.NoDisplay"
android:label="@string/popup_mode_share_menu_title"> android:label="@string/popup_mode_share_menu_title">
<intent-filter> <intent-filter>
@ -182,9 +176,6 @@
<data android:pathPrefix="/embed/" /> <data android:pathPrefix="/embed/" />
<data android:pathPrefix="/watch" /> <data android:pathPrefix="/watch" />
<data android:pathPrefix="/attribution_link" /> <data android:pathPrefix="/attribution_link" />
<!-- channel prefix -->
<data android:pathPrefix="/channel/"/>
<data android:pathPrefix="/user/"/>
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
@ -212,9 +203,7 @@
</intent-filter> </intent-filter>
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" /> <data android:mimeType="text/plain" />
</intent-filter> </intent-filter>
</activity> </activity>

View File

@ -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;
}
}

View File

@ -1,14 +1,14 @@
package org.schabi.newpipe; package org.schabi.newpipe;
import android.app.Activity; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.view.View; import android.view.View;
import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.report.ErrorActivity;
/** /**
* Created by Christian Schabesberger on 01.08.16. * Created by Christian Schabesberger on 01.08.16.
@ -33,11 +33,11 @@ import org.schabi.newpipe.extractor.NewPipe;
public class ImageErrorLoadingListener implements ImageLoadingListener { public class ImageErrorLoadingListener implements ImageLoadingListener {
private int serviceId = -1; private int serviceId = -1;
private Activity activity = null; private Context context = null;
private View rootView = null; private View rootView = null;
public ImageErrorLoadingListener(Activity activity, View rootView, int serviceId) { public ImageErrorLoadingListener(Context context, View rootView, int serviceId) {
this.activity = activity; this.context = context;
this.serviceId= serviceId; this.serviceId= serviceId;
this.rootView = rootView; this.rootView = rootView;
} }
@ -47,7 +47,7 @@ public class ImageErrorLoadingListener implements ImageLoadingListener {
@Override @Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) { public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
ErrorActivity.reportError(activity, ErrorActivity.reportError(context,
failReason.getCause(), null, rootView, failReason.getCause(), null, rootView,
ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE, ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
NewPipe.getNameOfService(serviceId), imageUri, NewPipe.getNameOfService(serviceId), imageUri,

View File

@ -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; package org.schabi.newpipe;
import android.content.Intent; import android.content.Intent;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.Fragment; 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.support.v7.app.AppCompatActivity;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import com.nostra13.universalimageloader.core.ImageLoader;
import org.schabi.newpipe.download.DownloadActivity; 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.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.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
/** public class MainActivity extends AppCompatActivity implements OnItemSelectedListener {
* 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;
private static final String TAG = MainActivity.class.toString(); private static final String TAG = MainActivity.class.toString();
/*//////////////////////////////////////////////////////////////////////////
// Activity's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ThemeHelper.setTheme(this, true); ThemeHelper.setTheme(this, true);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); setContentView(R.layout.activity_main);
setVolumeControlStream(AudioManager.STREAM_MUSIC); setVolumeControlStream(AudioManager.STREAM_MUSIC);
mainFragment = getSupportFragmentManager() if (savedInstanceState == null) initFragments();
.findFragmentById(R.id.search_fragment);
} }
@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 @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu); super.onCreateOptionsMenu(menu);
@ -63,9 +104,10 @@ public class MainActivity extends AppCompatActivity {
switch (id) { switch (id) {
case android.R.id.home: { case android.R.id.home: {
Intent intent = new Intent(this, MainActivity.class); Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_holder);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); if (fragment instanceof VideoDetailFragment) ((VideoDetailFragment) fragment).clearHistory();
NavUtils.navigateUpTo(this, intent);
NavigationHelper.openMainActivity(this);
return true; return true;
} }
case R.id.action_settings: { case R.id.action_settings: {
@ -85,4 +127,112 @@ public class MainActivity extends AppCompatActivity {
return super.onOptionsItemSelected(item); 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);
}
} }

View File

@ -3,19 +3,14 @@ package org.schabi.newpipe;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import org.schabi.newpipe.detail.VideoItemDetailActivity; import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.util.NavStack;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
/** /*
* Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org> * Copyright (C) Christian Schabesberger 2017 <chris.schabesberger@mailbox.org>
* RouterActivity .java is part of NewPipe. * 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. * to the part of the service which can handle the url.
*/ */
public class RouterActivity extends Activity { 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 * Removes invisible separators (\p{Z}) and punctuation characters including
@ -54,6 +49,25 @@ public class RouterActivity extends Activity {
finish(); 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) { private static String removeHeadingGibberish(final String input) {
int start = 0; int start = 0;
@ -107,50 +121,4 @@ public class RouterActivity extends Activity {
return result.toArray(new String[result.size()]); 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);
}
}
} }

View File

@ -4,24 +4,22 @@ import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.widget.Toast; import android.widget.Toast;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.player.PopupVideoPlayer; 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 org.schabi.newpipe.util.PermissionHelper;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; 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 RouterPopupActivity extends Activity {
public class PopupActivity extends Activity { //private static final String TAG = "RouterPopupActivity";
private static final String TAG = RouterActivity.class.toString();
/** /**
* Removes invisible separators (\p{Z}) and punctuation characters including * 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) { private static String removeHeadingGibberish(final String input) {
int start = 0; int start = 0;
for (int i = input.indexOf("://") - 1; i >= 0; i--) { 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()]); 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);
}
}
} }

View File

@ -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();
}
}

@ -1 +1 @@
Subproject commit 6ab3dc876ebab4ed32f4ae60d3d04d000a7ea0e8 Subproject commit b587d175bb9cd7e84b9bdb9b594db24e654ec7ae

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBar;
@ -12,9 +11,9 @@ import android.view.MenuItem;
import android.widget.ArrayAdapter; import android.widget.ArrayAdapter;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.settings.SettingsActivity;
import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.stream_info.VideoStream; import org.schabi.newpipe.extractor.stream_info.VideoStream;
import org.schabi.newpipe.util.Utils;
import java.util.List; import java.util.List;
@ -50,7 +49,7 @@ class ActionBarHandler {
private Menu menu; private Menu menu;
// Only callbacks are listed here, there are more actions which don't need a callback. // 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 onShareListener;
private OnActionListener onOpenInBrowserListener; private OnActionListener onOpenInBrowserListener;
private OnActionListener onOpenInPopupListener; private OnActionListener onOpenInPopupListener;
@ -89,7 +88,7 @@ class ActionBarHandler {
VideoStream item = videoStreams.get(i); VideoStream item = videoStreams.get(i);
itemArray[i] = MediaFormat.getNameById(item.format) + " " + item.resolution; 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(), ArrayAdapter<String> itemAdapter = new ArrayAdapter<>(activity.getBaseContext(),
android.R.layout.simple_spinner_dropdown_item, itemArray); 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) { public void setupMenu(Menu menu, MenuInflater inflater) {
this.menu = menu; this.menu = menu;
@ -187,11 +149,6 @@ class ActionBarHandler {
onDownloadListener.onActionSelected(selectedVideoStream); onDownloadListener.onActionSelected(selectedVideoStream);
} }
return true; 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: case R.id.action_play_with_kodi:
if(onPlayWithKodiListener != null) { if(onPlayWithKodiListener != null) {
onPlayWithKodiListener.onActionSelected(selectedVideoStream); onPlayWithKodiListener.onActionSelected(selectedVideoStream);
@ -202,12 +159,6 @@ class ActionBarHandler {
onPlayAudioListener.onActionSelected(selectedVideoStream); onPlayAudioListener.onActionSelected(selectedVideoStream);
} }
return true; 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: { case R.id.menu_item_popup: {
if(onOpenInPopupListener != null) { if(onOpenInPopupListener != null) {
onOpenInPopupListener.onActionSelected(selectedVideoStream); onOpenInPopupListener.onActionSelected(selectedVideoStream);

View File

@ -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();
}
}

View File

@ -1,7 +1,9 @@
package org.schabi.newpipe.detail; package org.schabi.newpipe.fragments.detail;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -11,14 +13,18 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.support.v4.content.ContextCompat; import android.support.v4.content.ContextCompat;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity; import android.support.v7.app.AppCompatActivity;
import android.text.Html; import android.text.Html;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.util.Log; import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; 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.AudioStream;
import org.schabi.newpipe.extractor.stream_info.StreamInfo; import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.extractor.stream_info.VideoStream; 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.info_list.InfoItemBuilder;
import org.schabi.newpipe.player.AbstractPlayer; import org.schabi.newpipe.player.AbstractPlayer;
import org.schabi.newpipe.player.BackgroundPlayer; 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.PlayVideoActivity;
import org.schabi.newpipe.player.PopupVideoPlayer; import org.schabi.newpipe.player.PopupVideoPlayer;
import org.schabi.newpipe.report.ErrorActivity; 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.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.ArrayList;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@SuppressWarnings("FieldCanBeLocal") @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 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"; public static final String AUTO_PLAY = "auto_play";
private AppCompatActivity activity;
private OnItemSelectedListener onItemSelectedListener;
private ActionBarHandler actionBarHandler; private ActionBarHandler actionBarHandler;
private InfoItemBuilder infoItemBuilder = null; private InfoItemBuilder infoItemBuilder = null;
private StreamInfo currentStreamInfo = null; private StreamInfo currentStreamInfo = null;
private StreamExtractorWorker curExtractorThread; private StreamExtractorWorker curExtractorWorker;
private String videoTitle;
private String videoUrl; private String videoUrl;
private int serviceId = -1; private int serviceId = -1;
@ -89,9 +104,9 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
private boolean autoPlayEnabled; private boolean autoPlayEnabled;
private boolean showRelatedStreams; private boolean showRelatedStreams;
private ImageLoader imageLoader = ImageLoader.getInstance(); private static final ImageLoader imageLoader = ImageLoader.getInstance();
private DisplayImageOptions displayImageOptions = private static final DisplayImageOptions displayImageOptions =
new DisplayImageOptions.Builder().displayer(new FadeInBitmapDisplayer(400)).cacheInMemory(true).build(); new DisplayImageOptions.Builder().displayer(new FadeInBitmapDisplayer(400)).cacheInMemory(false).build();
private Bitmap streamThumbnail = null; private Bitmap streamThumbnail = null;
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -130,55 +145,141 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
private RelativeLayout relatedStreamRootLayout; private RelativeLayout relatedStreamRootLayout;
private LinearLayout relatedStreamsView; 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 @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(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); showRelatedStreams = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean(getString(R.string.show_next_video_key), true);
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this); PreferenceManager.getDefaultSharedPreferences(activity).registerOnSharedPreferenceChangeListener(this);
activity.setVolumeControlStream(AudioManager.STREAM_MUSIC);
ThemeHelper.setTheme(this, true); isLoading.set(false);
setContentView(R.layout.activity_videoitem_detail); setHasOptionsMenu(true);
setVolumeControlStream(AudioManager.STREAM_MUSIC);
if (getSupportActionBar() != null) getSupportActionBar().setDisplayHomeAsUpEnabled(true);
else Log.e(TAG, "Could not get SupportActionBar");
initViews();
initListeners();
handleIntent(getIntent());
} }
@Override @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(); super.onResume();
// Currently only used for enable/disable related videos // 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 (needUpdate) {
if (relatedStreamsView != null) initRelatedVideos(currentStreamInfo); if (relatedStreamsView != null) initRelatedVideos(currentStreamInfo);
needUpdate = false; 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 @Override
protected void onNewIntent(Intent intent) { public void onStop() {
super.onNewIntent(intent); super.onStop();
setIntent(intent); if (curExtractorWorker != null && curExtractorWorker.isRunning()) curExtractorWorker.cancel();
handleIntent(intent);
} }
@Override @Override
public void onBackPressed() { public void onDestroy() {
try { super.onDestroy();
NavStack.getInstance().navBack(this); PreferenceManager.getDefaultSharedPreferences(activity).unregisterOnSharedPreferenceChangeListener(this);
} catch (Exception e) { imageLoader.clearMemoryCache();
ErrorActivity.reportUiError(this, e); }
}
@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 @Override
@ -186,9 +287,8 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) { switch (requestCode) {
case ReCaptchaActivity.RECAPTCHA_REQUEST: case ReCaptchaActivity.RECAPTCHA_REQUEST:
if (resultCode == RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
String videoUrl = getIntent().getStringExtra(NavStack.URL); NavigationHelper.openVideoDetail(onItemSelectedListener, serviceId, videoUrl, videoTitle);
NavStack.getInstance().openDetailActivity(this, videoUrl, serviceId);
} else Log.e(TAG, "ReCaptcha failed"); } else Log.e(TAG, "ReCaptcha failed");
break; break;
default: default:
@ -209,48 +309,47 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
// Init // Init
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
public void initViews() { private void initViews(View rootView) {
loadingProgressBar = (ProgressBar) findViewById(R.id.detail_loading_progress_bar); 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); //thumbnailRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_thumbnail_root_layout);
thumbnailBackgroundButton = (Button) findViewById(R.id.detail_stream_thumbnail_background_button); thumbnailBackgroundButton = (Button) rootView.findViewById(R.id.detail_stream_thumbnail_background_button);
thumbnailImageView = (ImageView) findViewById(R.id.detail_thumbnail_image_view); thumbnailImageView = (ImageView) rootView.findViewById(R.id.detail_thumbnail_image_view);
thumbnailPlayButton = (ImageView) findViewById(R.id.detail_thumbnail_play_button); 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); videoTitleRoot = rootView.findViewById(R.id.detail_title_root_layout);
videoTitleTextView = (TextView) findViewById(R.id.detail_video_title_view); videoTitleTextView = (TextView) rootView.findViewById(R.id.detail_video_title_view);
videoTitleToggleArrow = (ImageView) findViewById(R.id.detail_toggle_description_view); videoTitleToggleArrow = (ImageView) rootView.findViewById(R.id.detail_toggle_description_view);
videoCountView = (TextView) findViewById(R.id.detail_view_count_view); videoCountView = (TextView) rootView.findViewById(R.id.detail_view_count_view);
videoDescriptionRootLayout = (RelativeLayout) findViewById(R.id.detail_description_root_layout); videoDescriptionRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_description_root_layout);
videoUploadDateView = (TextView) findViewById(R.id.detail_upload_date_view); videoUploadDateView = (TextView) rootView.findViewById(R.id.detail_upload_date_view);
videoDescriptionView = (TextView) findViewById(R.id.detail_description_view); videoDescriptionView = (TextView) rootView.findViewById(R.id.detail_description_view);
//thumbsRootLayout = (LinearLayout) findViewById(R.id.detail_thumbs_root_layout); //thumbsRootLayout = (LinearLayout) rootView.findViewById(R.id.detail_thumbs_root_layout);
thumbsUpTextView = (TextView) findViewById(R.id.detail_thumbs_up_count_view); thumbsUpTextView = (TextView) rootView.findViewById(R.id.detail_thumbs_up_count_view);
thumbsUpImageView = (ImageView) findViewById(R.id.detail_thumbs_up_img_view); thumbsUpImageView = (ImageView) rootView.findViewById(R.id.detail_thumbs_up_img_view);
thumbsDownTextView = (TextView) findViewById(R.id.detail_thumbs_down_count_view); thumbsDownTextView = (TextView) rootView.findViewById(R.id.detail_thumbs_down_count_view);
thumbsDownImageView = (ImageView) findViewById(R.id.detail_thumbs_down_img_view); thumbsDownImageView = (ImageView) rootView.findViewById(R.id.detail_thumbs_down_img_view);
thumbsDisabledTextView = (TextView) findViewById(R.id.detail_thumbs_disabled_view); thumbsDisabledTextView = (TextView) rootView.findViewById(R.id.detail_thumbs_disabled_view);
//uploaderRootLayout = (FrameLayout) findViewById(R.id.detail_uploader_root_layout); //uploaderRootLayout = (FrameLayout) rootView.findViewById(R.id.detail_uploader_root_layout);
uploaderButton = (Button) findViewById(R.id.detail_uploader_button); uploaderButton = (Button) rootView.findViewById(R.id.detail_uploader_button);
uploaderTextView = (TextView) findViewById(R.id.detail_uploader_text_view); uploaderTextView = (TextView) rootView.findViewById(R.id.detail_uploader_text_view);
uploaderThumb = (ImageView) findViewById(R.id.detail_uploader_thumbnail_view); uploaderThumb = (ImageView) rootView.findViewById(R.id.detail_uploader_thumbnail_view);
relatedStreamRootLayout = (RelativeLayout) findViewById(R.id.detail_related_streams_root_layout); relatedStreamRootLayout = (RelativeLayout) rootView.findViewById(R.id.detail_related_streams_root_layout);
nextStreamTitle = (TextView) findViewById(R.id.detail_next_stream_title); nextStreamTitle = (TextView) rootView.findViewById(R.id.detail_next_stream_title);
relatedStreamsView = (LinearLayout) findViewById(R.id.detail_related_streams_view); relatedStreamsView = (LinearLayout) rootView.findViewById(R.id.detail_related_streams_view);
actionBarHandler = new ActionBarHandler(this); actionBarHandler = new ActionBarHandler(activity);
actionBarHandler.setupNavMenu(this);
videoDescriptionView.setMovementMethod(LinkMovementMethod.getInstance()); videoDescriptionView.setMovementMethod(LinkMovementMethod.getInstance());
infoItemBuilder = new InfoItemBuilder(this, findViewById(android.R.id.content)); infoItemBuilder = new InfoItemBuilder(activity, rootView.findViewById(android.R.id.content));
setHeightThumbnail(); setHeightThumbnail();
} }
@ -279,15 +378,16 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
infoItemBuilder.setOnStreamInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() { infoItemBuilder.setOnStreamInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override @Override
public void selected(String url, int serviceId) { public void selected(int serviceId, String url, String title) {
NavStack.getInstance().openDetailActivity(VideoItemDetailActivity.this, url, serviceId); //NavigationHelper.openVideoDetail(activity, url, serviceId);
selectAndLoadVideo(serviceId, url, title);
} }
}); });
uploaderButton.setOnClickListener(new View.OnClickListener() { uploaderButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { 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; ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
Intent intent = new Intent(AbstractPlayer.ACTION_UPDATE_THUMB); Intent intent = new Intent(AbstractPlayer.ACTION_UPDATE_THUMB);
intent.putExtra(AbstractPlayer.VIDEO_URL, currentStreamInfo.webpage_url); intent.putExtra(AbstractPlayer.VIDEO_URL, currentStreamInfo.webpage_url);
sendBroadcast(intent); activity.sendBroadcast(intent);
} }
} }
@Override @Override
public void onLoadingFailed(String imageUri, View view, FailReason failReason) { public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
ErrorActivity.reportError(VideoItemDetailActivity.this, ErrorActivity.reportError(activity,
failReason.getCause(), null, findViewById(android.R.id.content), failReason.getCause(), null, activity.findViewById(android.R.id.content),
ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE, ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
NewPipe.getNameOfService(currentStreamInfo.service_id), imageUri, NewPipe.getNameOfService(currentStreamInfo.service_id), imageUri,
R.string.could_not_load_thumbnails)); 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()) { if (info.uploader_thumbnail_url != null && !info.uploader_thumbnail_url.isEmpty()) {
imageLoader.displayImage(info.uploader_thumbnail_url, imageLoader.displayImage(info.uploader_thumbnail_url,
uploaderThumb, displayImageOptions, 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); nextStreamTitle.setVisibility(View.VISIBLE);
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, info.next_video)); relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, info.next_video));
relatedStreamsView.addView(getSeparatorView()); relatedStreamsView.addView(getSeparatorView());
relatedStreamsView.setVisibility(View.VISIBLE); relatedStreamRootLayout.setVisibility(View.VISIBLE);
} else nextStreamTitle.setVisibility(View.GONE); } else nextStreamTitle.setVisibility(View.GONE);
if (info.related_streams != null && !info.related_streams.isEmpty() && showRelatedStreams) { if (info.related_streams != null && !info.related_streams.isEmpty() && showRelatedStreams) {
for (InfoItem item : info.related_streams) { for (InfoItem item : info.related_streams) {
relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, item)); relatedStreamsView.addView(infoItemBuilder.buildView(relatedStreamsView, item));
} }
relatedStreamsView.setVisibility(View.VISIBLE); relatedStreamRootLayout.setVisibility(View.VISIBLE);
} else if (info.next_video == null) relatedStreamsView.setVisibility(View.GONE); } else if (info.next_video == null) relatedStreamRootLayout.setVisibility(View.GONE);
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////
@ -357,21 +457,29 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
actionBarHandler.setupMenu(menu, getMenuInflater()); actionBarHandler.setupMenu(menu, inflater);
return super.onCreateOptionsMenu(menu); actionBarHandler.setupNavMenu(activity);
ActionBar supportActionBar = activity.getSupportActionBar();
if (supportActionBar != null) {
supportActionBar.setDisplayHomeAsUpEnabled(true);
supportActionBar.setDisplayShowTitleEnabled(false);
//noinspection deprecation
supportActionBar.setNavigationMode(0);
}
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { 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); return actionBarHandler.onItemSelected(item) || super.onOptionsItemSelected(item);
} }
private void setupActionBarHandler(final StreamInfo info) { 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.setupStreamList(info.video_streams);
actionBarHandler.setOnShareListener(new ActionBarHandler.OnActionListener() { actionBarHandler.setOnShareListener(new ActionBarHandler.OnActionListener() {
@Override @Override
@ -382,7 +490,7 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
intent.setAction(Intent.ACTION_SEND); intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, info.webpage_url); intent.putExtra(Intent.EXTRA_TEXT, info.webpage_url);
intent.setType("text/plain"); 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 intent = new Intent();
intent.setAction(Intent.ACTION_VIEW); intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(info.webpage_url)); 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) { public void onActionSelected(int selectedStreamId) {
if (isLoading.get()) return; if (isLoading.get()) return;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !PermissionHelper.checkSystemAlertWindowPermission(VideoItemDetailActivity.this)) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !PermissionHelper.checkSystemAlertWindowPermission(activity)) {
Toast.makeText(VideoItemDetailActivity.this, R.string.msg_popup_permission, Toast.LENGTH_LONG).show(); Toast.makeText(activity, R.string.msg_popup_permission, Toast.LENGTH_LONG).show();
return; return;
} }
if (streamThumbnail != null) ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail; if (streamThumbnail != null) ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail;
Intent i = new Intent(VideoItemDetailActivity.this, PopupVideoPlayer.class); Intent i = new Intent(activity, PopupVideoPlayer.class);
Toast.makeText(VideoItemDetailActivity.this, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show(); Toast.makeText(activity, R.string.popup_playing_toast, Toast.LENGTH_SHORT).show();
i.putExtra(AbstractPlayer.VIDEO_TITLE, info.title) i.putExtra(AbstractPlayer.VIDEO_TITLE, info.title)
.putExtra(AbstractPlayer.CHANNEL_NAME, info.uploader) .putExtra(AbstractPlayer.CHANNEL_NAME, info.uploader)
.putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url) .putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url)
.putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, selectedStreamId) .putExtra(AbstractPlayer.INDEX_SEL_VIDEO_STREAM, selectedStreamId)
.putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, new ArrayList<>(info.video_streams)); .putExtra(AbstractPlayer.VIDEO_STREAMS_LIST, new ArrayList<>(info.video_streams));
if (info.start_position > 0) i.putExtra(AbstractPlayer.START_POSITION, info.start_position * 1000); 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 intent = new Intent(Intent.ACTION_VIEW);
intent.setPackage(KORE_PACKET); intent.setPackage(KORE_PACKET);
intent.setData(Uri.parse(info.webpage_url.replace("https", "http"))); intent.setData(Uri.parse(info.webpage_url.replace("https", "http")));
VideoItemDetailActivity.this.startActivity(intent); activity.startActivity(intent);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
AlertDialog.Builder builder = new AlertDialog.Builder(VideoItemDetailActivity.this); AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(R.string.kore_not_found) builder.setMessage(R.string.kore_not_found)
.setPositiveButton(R.string.install, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.install, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(); Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW); intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(VideoItemDetailActivity.this.getString(R.string.fdroid_kore_url))); intent.setData(Uri.parse(activity.getString(R.string.fdroid_kore_url)));
VideoItemDetailActivity.this.startActivity(intent); activity.startActivity(intent);
} }
}) })
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@ -459,7 +567,7 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
@Override @Override
public void onActionSelected(int selectedStreamId) { public void onActionSelected(int selectedStreamId) {
if (isLoading.get() || !PermissionHelper.checkStoragePermissions(VideoItemDetailActivity.this)) { if (isLoading.get() || !PermissionHelper.checkStoragePermissions(activity)) {
return; return;
} }
@ -471,7 +579,7 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
if (info.audio_streams != null) { if (info.audio_streams != null) {
AudioStream audioStream = AudioStream audioStream =
info.audio_streams.get(getPreferredAudioStreamId(info)); info.audio_streams.get(Utils.getPreferredAudioFormat(activity, info.audio_streams));
String audioSuffix = "." + MediaFormat.getSuffixById(audioStream.format); String audioSuffix = "." + MediaFormat.getSuffixById(audioStream.format);
args.putString(DownloadDialog.AUDIO_URL, audioStream.url); args.putString(DownloadDialog.AUDIO_URL, audioStream.url);
@ -487,9 +595,9 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
args.putString(DownloadDialog.TITLE, info.title); args.putString(DownloadDialog.TITLE, info.title);
DownloadDialog downloadDialog = DownloadDialog.newInstance(args); DownloadDialog downloadDialog = DownloadDialog.newInstance(args);
downloadDialog.show(VideoItemDetailActivity.this.getSupportFragmentManager(), "downloadDialog"); downloadDialog.show(activity.getSupportFragmentManager(), "downloadDialog");
} catch (Exception e) { } catch (Exception e) {
Toast.makeText(VideoItemDetailActivity.this, Toast.makeText(activity,
R.string.could_not_setup_download_menu, Toast.LENGTH_LONG).show(); R.string.could_not_setup_download_menu, Toast.LENGTH_LONG).show();
e.printStackTrace(); e.printStackTrace();
} }
@ -504,17 +612,17 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
public void onActionSelected(int selectedStreamId) { public void onActionSelected(int selectedStreamId) {
if (isLoading.get()) return; if (isLoading.get()) return;
boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(VideoItemDetailActivity.this) boolean useExternalAudioPlayer = PreferenceManager.getDefaultSharedPreferences(activity)
.getBoolean(VideoItemDetailActivity.this.getString(R.string.use_external_audio_player_key), false); .getBoolean(activity.getString(R.string.use_external_audio_player_key), false);
Intent intent; Intent intent;
AudioStream audioStream = 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) { if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 18) {
//internal music player: explicit intent //internal music player: explicit intent
if (!BackgroundPlayer.isRunning && streamThumbnail != null) { if (!BackgroundPlayer.isRunning && streamThumbnail != null) {
ActivityCommunicator.getCommunicator() ActivityCommunicator.getCommunicator()
.backgroundPlayerThumbnail = streamThumbnail; .backgroundPlayerThumbnail = streamThumbnail;
intent = new Intent(VideoItemDetailActivity.this, BackgroundPlayer.class); intent = new Intent(activity, BackgroundPlayer.class);
intent.setAction(Intent.ACTION_VIEW); intent.setAction(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse(audioStream.url), 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.WEB_URL, info.webpage_url);
intent.putExtra(BackgroundPlayer.SERVICE_ID, serviceId); intent.putExtra(BackgroundPlayer.SERVICE_ID, serviceId);
intent.putExtra(BackgroundPlayer.CHANNEL_NAME, info.uploader); intent.putExtra(BackgroundPlayer.CHANNEL_NAME, info.uploader);
VideoItemDetailActivity.this.startService(intent); activity.startService(intent);
} }
} else { } else {
intent = new Intent(); intent = new Intent();
@ -534,18 +642,18 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
intent.putExtra(Intent.EXTRA_TITLE, info.title); intent.putExtra(Intent.EXTRA_TITLE, info.title);
intent.putExtra("title", info.title); intent.putExtra("title", info.title);
// HERE !!! // HERE !!!
VideoItemDetailActivity.this.startActivity(intent); activity.startActivity(intent);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
AlertDialog.Builder builder = new AlertDialog.Builder(VideoItemDetailActivity.this); AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(R.string.no_player_found) builder.setMessage(R.string.no_player_found)
.setPositiveButton(R.string.install, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.install, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
Intent intent = new Intent(); Intent intent = new Intent();
intent.setAction(Intent.ACTION_VIEW); intent.setAction(Intent.ACTION_VIEW);
intent.setData(Uri.parse(VideoItemDetailActivity.this.getString(R.string.fdroid_vlc_url))); intent.setData(Uri.parse(activity.getString(R.string.fdroid_vlc_url)));
VideoItemDetailActivity.this.startActivity(intent); activity.startActivity(intent);
} }
}) })
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { .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 // Utils
//////////////////////////////////////////////////////////////////////////*/ //////////////////////////////////////////////////////////////////////////*/
private void handleIntent(Intent intent) {
if (intent == null) return;
serviceId = intent.getIntExtra(NavStack.SERVICE_ID, 0); public void setAutoplay(boolean autoplay) {
videoUrl = intent.getStringExtra(NavStack.URL); this.autoPlayEnabled = autoplay;
autoPlayEnabled = intent.getBooleanExtra(AUTO_PLAY, false);
selectVideo(videoUrl, serviceId);
} }
private void selectVideo(String url, int serviceId) { public void selectVideo(int serviceId, String videoUrl, String videoTitle) {
if (curExtractorThread != null && curExtractorThread.isRunning()) curExtractorThread.cancel(); 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); loadingProgressBar.setVisibility(View.VISIBLE);
imageLoader.cancelDisplayTask(thumbnailImageView); imageLoader.cancelDisplayTask(thumbnailImageView);
imageLoader.cancelDisplayTask(uploaderThumb); 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); isLoading.set(true);
} }
@ -597,7 +782,7 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
// ----------- THE MAGIC MOMENT --------------- // ----------- THE MAGIC MOMENT ---------------
VideoStream selectedVideoStream = info.video_streams.get(actionBarHandler.getSelectedVideoStream()); 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 // External Player
Intent intent = new Intent(); Intent intent = new Intent();
@ -609,7 +794,7 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
this.startActivity(intent); this.startActivity(intent);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
AlertDialog.Builder builder = new AlertDialog.Builder(this); AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setMessage(R.string.no_player_found) builder.setMessage(R.string.no_player_found)
.setPositiveButton(R.string.install, new DialogInterface.OnClickListener() { .setPositiveButton(R.string.install, new DialogInterface.OnClickListener() {
@Override @Override
@ -629,14 +814,13 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
} }
} else { } else {
Intent intent; Intent intent;
boolean useOldPlayer = PreferenceManager boolean useOldPlayer = PreferenceManager.getDefaultSharedPreferences(activity)
.getDefaultSharedPreferences(this)
.getBoolean(getString(R.string.use_old_player_key), false) .getBoolean(getString(R.string.use_old_player_key), false)
|| (Build.VERSION.SDK_INT < 16); || (Build.VERSION.SDK_INT < 16);
if (!useOldPlayer) { if (!useOldPlayer) {
// ExoPlayer // ExoPlayer
if (streamThumbnail != null) ActivityCommunicator.getCommunicator().backgroundPlayerThumbnail = streamThumbnail; 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_TITLE, info.title)
.putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url) .putExtra(AbstractPlayer.VIDEO_URL, info.webpage_url)
.putExtra(AbstractPlayer.CHANNEL_NAME, info.uploader) .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); if (info.start_position > 0) intent.putExtra(AbstractPlayer.START_POSITION, info.start_position * 1000);
} else { } else {
// Internal Player // Internal Player
intent = new Intent(this, PlayVideoActivity.class) intent = new Intent(activity, PlayVideoActivity.class)
.putExtra(PlayVideoActivity.VIDEO_TITLE, info.title) .putExtra(PlayVideoActivity.VIDEO_TITLE, info.title)
.putExtra(PlayVideoActivity.STREAM_URL, selectedVideoStream.url) .putExtra(PlayVideoActivity.STREAM_URL, selectedVideoStream.url)
.putExtra(PlayVideoActivity.VIDEO_URL, info.webpage_url) .putExtra(PlayVideoActivity.VIDEO_URL, info.webpage_url)
.putExtra(PlayVideoActivity.START_POSITION, info.start_position); .putExtra(PlayVideoActivity.START_POSITION, info.start_position);
} }
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent); 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() { private View getSeparatorView() {
View separator = new View(this); View separator = new View(activity);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1); LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 1);
int m8 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics()); int m8 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8, getResources().getDisplayMetrics());
int m5 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, 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); separator.setLayoutParams(params);
TypedValue typedValue = new TypedValue(); TypedValue typedValue = new TypedValue();
getTheme().resolveAttribute(R.attr.separatorColor, typedValue, true); activity.getTheme().resolveAttribute(R.attr.separatorColor, typedValue, true);
separator.setBackgroundColor(typedValue.data); separator.setBackgroundColor(typedValue.data);
return separator; return separator;
} }
@ -762,11 +919,11 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
@Override @Override
public void onReceive(StreamInfo info) { public void onReceive(StreamInfo info) {
currentStreamInfo = info; if (info == null || isRemoving() || !isVisible()) return;
currentStreamInfo = info;
loadingProgressBar.setVisibility(View.GONE); loadingProgressBar.setVisibility(View.GONE);
thumbnailPlayButton.setVisibility(View.VISIBLE); animateView(thumbnailPlayButton, true, 200, null);
relatedStreamRootLayout.setVisibility(showRelatedStreams ? View.VISIBLE : View.GONE);
parallaxScrollRootView.scrollTo(0, 0); parallaxScrollRootView.scrollTo(0, 0);
// Since newpipe is designed to work even if certain information is not available, // 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); if (!info.uploader.isEmpty()) uploaderTextView.setText(info.uploader);
uploaderTextView.setVisibility(!info.uploader.isEmpty() ? View.VISIBLE : View.GONE); uploaderTextView.setVisibility(!info.uploader.isEmpty() ? View.VISIBLE : View.GONE);
uploaderButton.setVisibility(!info.channel_url.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); videoCountView.setVisibility(info.view_count >= 0 ? View.VISIBLE : View.GONE);
if (info.dislike_count == -1 && info.like_count == -1) { if (info.dislike_count == -1 && info.like_count == -1) {
@ -790,54 +947,64 @@ public class VideoItemDetailActivity extends AppCompatActivity implements Stream
} else { } else {
thumbsDisabledTextView.setVisibility(View.GONE); 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); thumbsDownTextView.setVisibility(info.dislike_count >= 0 ? View.VISIBLE : View.GONE);
thumbsDownImageView.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); thumbsUpTextView.setVisibility(info.like_count >= 0 ? View.VISIBLE : View.GONE);
thumbsUpImageView.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); videoUploadDateView.setVisibility(!info.upload_date.isEmpty() ? View.VISIBLE : View.GONE);
if (!info.description.isEmpty()) videoDescriptionView.setText( if (!info.description.isEmpty()) { //noinspection deprecation
Build.VERSION.SDK_INT >= 24 ? Html.fromHtml(info.description, 0) : Html.fromHtml(info.description) 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); videoDescriptionView.setVisibility(!info.description.isEmpty() ? View.VISIBLE : View.GONE);
videoDescriptionRootLayout.setVisibility(View.GONE); videoDescriptionRootLayout.setVisibility(View.GONE);
videoTitleToggleArrow.setImageResource(R.drawable.arrow_down); videoTitleToggleArrow.setImageResource(R.drawable.arrow_down);
videoTitleToggleArrow.setVisibility(View.VISIBLE);
videoTitleRoot.setClickable(true);
setupActionBarHandler(info); setupActionBarHandler(info);
initRelatedVideos(info); initRelatedVideos(info);
initThumbnailViews(info); initThumbnailViews(info);
setTitleToUrl(info.webpage_url, info.title);
animateView(contentRootLayout, true, 200, null); animateView(contentRootLayout, true, 200, null);
if (autoPlayEnabled) {
playVideo(info);
// Only auto play in the first open
autoPlayEnabled = false;
}
isLoading.set(false); isLoading.set(false);
if (autoPlayEnabled) playVideo(info);
} }
@Override @Override
public void onError(int messageId) { 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); 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 @Override
public void onReCaptchaException() { 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 // Starting ReCaptcha Challenge Activity
startActivityForResult(new Intent(this, ReCaptchaActivity.class), ReCaptchaActivity.RECAPTCHA_REQUEST); startActivityForResult(new Intent(activity, ReCaptchaActivity.class), ReCaptchaActivity.RECAPTCHA_REQUEST);
} }
@Override @Override
public void onBlockedByGemaError() { public void onBlockedByGemaError() {
loadingProgressBar.setVisibility(View.GONE); 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() { thumbnailBackgroundButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { 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 @Override
public void onContentErrorWithMessage(int messageId) { public void onContentErrorWithMessage(int messageId) {
loadingProgressBar.setVisibility(View.GONE); loadingProgressBar.setVisibility(View.GONE);
thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.not_available_monkey)); thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.not_available_monkey));
Toast.makeText(this, messageId, Toast.LENGTH_LONG).show(); Toast.makeText(activity, messageId, Toast.LENGTH_LONG).show();
} }
@Override @Override
public void onContentError() { public void onContentError() {
loadingProgressBar.setVisibility(View.GONE); loadingProgressBar.setVisibility(View.GONE);
thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.not_available_monkey)); thumbnailImageView.setImageDrawable(ContextCompat.getDrawable(activity, R.drawable.not_available_monkey));
Toast.makeText(this, R.string.content_not_available, Toast.LENGTH_LONG).show(); Toast.makeText(activity, R.string.content_not_available, Toast.LENGTH_LONG).show();
} }
} }

View File

@ -1,10 +1,13 @@
package org.schabi.newpipe.search_fragment; package org.schabi.newpipe.fragments.search;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment; 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.LinearLayoutManager;
import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.SearchView; 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.NewPipe;
import org.schabi.newpipe.extractor.search.SearchEngine; import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SearchResult; 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.InfoItemBuilder;
import org.schabi.newpipe.info_list.InfoListAdapter; import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.util.NavStack; import org.schabi.newpipe.util.NavigationHelper;
import java.util.EnumSet; import java.util.EnumSet;
@ -36,120 +40,87 @@ import static org.schabi.newpipe.ReCaptchaActivity.RECAPTCHA_REQUEST;
/** /**
* Created by Christian Schabesberger on 02.08.16. * Created by Christian Schabesberger on 02.08.16.
* * <p>
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org> * 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 * NewPipe is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* * <p>
* NewPipe is distributed in the hope that it will be useful, * NewPipe is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details. * GNU General Public License for more details.
* * <p>
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>. * 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 static final String TAG = SearchFragment.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;
// savedInstanceBundle arguments // savedInstanceBundle arguments
private static final String QUERY = "query"; private static final String QUERY = "query";
private static final String STREAMING_SERVICE = "streaming_service"; 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 * Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes). * fragment (e.g. upon screen orientation changes).
*/ */
public SearchInfoItemFragment() { public SearchFragment() {
} }
@SuppressWarnings("unused") @SuppressWarnings("unused")
public static SearchInfoItemFragment newInstance(int streamingServiceId, String searchQuery) { public static SearchFragment newInstance(int streamingServiceId, String searchQuery) {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putInt(STREAMING_SERVICE, streamingServiceId); args.putInt(STREAMING_SERVICE, streamingServiceId);
args.putString(QUERY, searchQuery); args.putString(QUERY, searchQuery);
SearchInfoItemFragment fragment = new SearchInfoItemFragment(); SearchFragment fragment = new SearchFragment();
fragment.setArguments(args); fragment.setArguments(args);
return fragment; return fragment;
} }
/*//////////////////////////////////////////////////////////////////////////
// Fragment's LifeCycle
//////////////////////////////////////////////////////////////////////////*/
@Override
public void onAttach(Context context) {
super.onAttach(context);
onItemSelectedListener = ((OnItemSelectedListener) context);
}
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
searchQuery = ""; searchQuery = "";
isLoading = false;
if (savedInstanceState != null) { if (savedInstanceState != null) {
searchQuery = savedInstanceState.getString(QUERY); searchQuery = savedInstanceState.getString(QUERY);
streamingServiceId = savedInstanceState.getInt(STREAMING_SERVICE); streamingServiceId = savedInstanceState.getInt(STREAMING_SERVICE);
} else { } else {
try { try {
Bundle args = getArguments(); Bundle args = getArguments();
if(args != null) { if (args != null) {
searchQuery = args.getString(QUERY); searchQuery = args.getString(QUERY);
streamingServiceId = args.getInt(STREAMING_SERVICE); streamingServiceId = args.getInt(STREAMING_SERVICE);
} else { } else {
@ -168,50 +139,16 @@ public class SearchInfoItemFragment extends Fragment {
setHasOptionsMenu(true); setHasOptionsMenu(true);
SearchWorker sw = SearchWorker.getInstance(); SearchWorker sw = SearchWorker.getInstance();
sw.setSearchWorkerResultListener(new SearchWorker.SearchWorkerResultListener() { sw.setSearchWorkerResultListener(this);
@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);
}
});
} }
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_search, container, false);
View view = inflater.inflate(R.layout.fragment_searchinfoitem, container, false);
Context context = view.getContext(); Context context = view.getContext();
loadingIndicator = (ProgressBar) view.findViewById(R.id.progressBar); loadingIndicator = (ProgressBar) view.findViewById(R.id.loading_progress_bar);
RecyclerView recyclerView = (RecyclerView) view.findViewById(R.id.list); recyclerView = (RecyclerView) view.findViewById(R.id.list);
streamInfoListLayoutManager = new LinearLayoutManager(context); streamInfoListLayoutManager = new LinearLayoutManager(context);
recyclerView.setLayoutManager(streamInfoListLayoutManager); recyclerView.setLayoutManager(streamInfoListLayoutManager);
@ -219,19 +156,16 @@ public class SearchInfoItemFragment extends Fragment {
getActivity().findViewById(android.R.id.content)); getActivity().findViewById(android.R.id.content));
infoListAdapter.setFooter(inflater.inflate(R.layout.pignate_footer, recyclerView, false)); infoListAdapter.setFooter(inflater.inflate(R.layout.pignate_footer, recyclerView, false));
infoListAdapter.showFooter(false); infoListAdapter.showFooter(false);
infoListAdapter.setOnStreamInfoItemSelectedListener( infoListAdapter.setOnStreamInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override @Override
public void selected(String url, int serviceId) { public void selected(int serviceId, String url, String title) {
NavStack.getInstance() NavigationHelper.openVideoDetail(onItemSelectedListener, serviceId, url, title);
.openDetailActivity(getContext(), url, serviceId);
} }
}); });
infoListAdapter.setOnChannelInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() { infoListAdapter.setOnChannelInfoItemSelectedListener(new InfoItemBuilder.OnInfoItemSelectedListener() {
@Override @Override
public void selected(String url, int serviceId) { public void selected(int serviceId, String url, String title) {
NavStack.getInstance() NavigationHelper.openChannel(onItemSelectedListener, serviceId, url, title);
.openChannelActivity(getContext(), url, serviceId);
} }
}); });
recyclerView.setAdapter(infoListAdapter); recyclerView.setAdapter(infoListAdapter);
@ -249,6 +183,13 @@ public class SearchInfoItemFragment extends Fragment {
if ((visibleItemCount + pastVisiblesItems) >= totalItemCount && !isLoading) { if ((visibleItemCount + pastVisiblesItems) >= totalItemCount && !isLoading) {
pageNumber++; pageNumber++;
recyclerView.post(new Runnable() {
@Override
public void run() {
infoListAdapter.showFooter(true);
}
});
search(searchQuery, pageNumber); search(searchQuery, pageNumber);
} }
} }
@ -259,13 +200,35 @@ public class SearchInfoItemFragment extends Fragment {
} }
@Override @Override
public void onStart() { public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onStart(); super.onViewCreated(view, savedInstanceState);
if(!searchQuery.isEmpty()) { if (!searchQuery.isEmpty()) {
search(searchQuery); 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 @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState); super.onSaveInstanceState(outState);
@ -273,19 +236,45 @@ public class SearchInfoItemFragment extends Fragment {
outState.putInt(STREAMING_SERVICE, streamingServiceId); 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 @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
super.onCreateOptionsMenu(menu, 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); inflater.inflate(R.menu.search_menu, menu);
MenuItem searchItem = menu.findItem(R.id.action_search); MenuItem searchItem = menu.findItem(R.id.action_search);
SearchView searchView = (SearchView) searchItem.getActionView(); searchView = (SearchView) searchItem.getActionView();
setupSearchView(searchView); setupSearchView(searchView);
} }
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) { switch (item.getItemId()) {
case R.id.menu_filter_all: case R.id.menu_filter_all:
changeFilter(item, EnumSet.of(SearchEngine.Filter.STREAM, SearchEngine.Filter.CHANNEL)); changeFilter(item, EnumSet.of(SearchEngine.Filter.STREAM, SearchEngine.Filter.CHANNEL));
return true; return true;
@ -300,11 +289,15 @@ public class SearchInfoItemFragment extends Fragment {
} }
} }
/*//////////////////////////////////////////////////////////////////////////
// Utils
//////////////////////////////////////////////////////////////////////////*/
private void changeFilter(MenuItem item, EnumSet<SearchEngine.Filter> filter) { private void changeFilter(MenuItem item, EnumSet<SearchEngine.Filter> filter) {
this.filter = filter; this.filter = filter;
item.setChecked(true); item.setChecked(true);
if(searchQuery != null && !searchQuery.isEmpty()) { if (searchQuery != null && !searchQuery.isEmpty()) {
Log.d(TAG, "Fuck+ " + searchQuery); Log.e(TAG, "Fuck+ " + searchQuery);
search(searchQuery); search(searchQuery);
} }
} }
@ -313,7 +306,7 @@ public class SearchInfoItemFragment extends Fragment {
suggestionListAdapter = new SuggestionListAdapter(getActivity()); suggestionListAdapter = new SuggestionListAdapter(getActivity());
searchView.setSuggestionsAdapter(suggestionListAdapter); searchView.setSuggestionsAdapter(suggestionListAdapter);
searchView.setOnSuggestionListener(new SearchSuggestionListener(searchView, suggestionListAdapter)); searchView.setOnSuggestionListener(new SearchSuggestionListener(searchView, suggestionListAdapter));
searchView.setOnQueryTextListener(new SearchQueryListener()); searchView.setOnQueryTextListener(this);
if (searchQuery != null && !searchQuery.isEmpty()) { if (searchQuery != null && !searchQuery.isEmpty()) {
searchView.setQuery(searchQuery, false); searchView.setQuery(searchQuery, false);
searchView.setIconifiedByDefault(false); searchView.setIconifiedByDefault(false);
@ -341,9 +334,9 @@ public class SearchInfoItemFragment extends Fragment {
} }
private void setDoneLoading() { private void setDoneLoading() {
this.isLoading = false; isLoading = false;
loadingIndicator.setVisibility(View.GONE); loadingIndicator.setVisibility(View.GONE);
infoListAdapter.showFooter(true); infoListAdapter.showFooter(false);
} }
/** /**
@ -351,7 +344,7 @@ public class SearchInfoItemFragment extends Fragment {
*/ */
private void hideBackground() { private void hideBackground() {
View view = getView(); View view = getView();
if(view == null) return; if (view == null) return;
view.findViewById(R.id.mainBG).setVisibility(View.GONE); view.findViewById(R.id.mainBG).setVisibility(View.GONE);
} }
@ -362,22 +355,86 @@ public class SearchInfoItemFragment extends Fragment {
suggestionThread.start(); suggestionThread.start();
} }
@Override public boolean isMainBgVisible() {
public void onActivityResult(int requestCode, int resultCode, Intent data) { return getActivity().findViewById(R.id.mainBG).getVisibility() == View.VISIBLE;
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;
}
} }
/*//////////////////////////////////////////////////////////////////////////
// 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);
}
} }

View File

@ -1,4 +1,4 @@
package org.schabi.newpipe.search_fragment; package org.schabi.newpipe.fragments.search;
import android.support.v7.widget.SearchView; import android.support.v7.widget.SearchView;

View File

@ -1,4 +1,4 @@
package org.schabi.newpipe.search_fragment; package org.schabi.newpipe.fragments.search;
import android.app.Activity; import android.app.Activity;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -7,13 +7,13 @@ import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.view.View; 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.ExtractionException;
import org.schabi.newpipe.extractor.exceptions.ReCaptchaException; import org.schabi.newpipe.extractor.exceptions.ReCaptchaException;
import org.schabi.newpipe.extractor.search.SearchEngine; import org.schabi.newpipe.extractor.search.SearchEngine;
import org.schabi.newpipe.extractor.search.SearchResult; import org.schabi.newpipe.extractor.search.SearchResult;
import org.schabi.newpipe.report.ErrorActivity; import org.schabi.newpipe.report.ErrorActivity;
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.NewPipe;
import java.io.IOException; import java.io.IOException;
import java.util.EnumSet; import java.util.EnumSet;
@ -209,6 +209,7 @@ public class SearchWorker {
} }
public void terminate() { public void terminate() {
if (runnable == null) return;
requestId++; requestId++;
runnable.terminate(); runnable.terminate();
} }

View File

@ -1,4 +1,4 @@
package org.schabi.newpipe.search_fragment; package org.schabi.newpipe.fragments.search;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;

View File

@ -1,4 +1,4 @@
package org.schabi.newpipe.search_fragment; package org.schabi.newpipe.fragments.search;
import android.app.Activity; import android.app.Activity;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -6,11 +6,11 @@ import android.os.Handler;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.widget.Toast; 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.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.io.IOException;
import java.util.List; import java.util.List;

View File

@ -1,11 +1,10 @@
package org.schabi.newpipe.info_list; package org.schabi.newpipe.info_list;
import android.app.Activity; import android.content.Context;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView;
import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoader;
@ -39,20 +38,21 @@ import org.schabi.newpipe.extractor.stream_info.StreamInfoItem;
public class InfoItemBuilder { public class InfoItemBuilder {
final String viewsS; private final String viewsS;
final String videosS; private final String videosS;
final String subsS; private final String subsS;
private final String subsPluralS;
final String thousand; private final String thousand;
final String million; private final String million;
final String billion; private final String billion;
private static final String TAG = InfoItemBuilder.class.toString(); private static final String TAG = InfoItemBuilder.class.toString();
public interface OnInfoItemSelectedListener { 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 View rootView = null;
private ImageLoader imageLoader = ImageLoader.getInstance(); private ImageLoader imageLoader = ImageLoader.getInstance();
private DisplayImageOptions displayImageOptions = private DisplayImageOptions displayImageOptions =
@ -60,15 +60,16 @@ public class InfoItemBuilder {
private OnInfoItemSelectedListener onStreamInfoItemSelectedListener; private OnInfoItemSelectedListener onStreamInfoItemSelectedListener;
private OnInfoItemSelectedListener onChannelInfoItemSelectedListener; private OnInfoItemSelectedListener onChannelInfoItemSelectedListener;
public InfoItemBuilder(Activity a, View rootView) { public InfoItemBuilder(Context context, View rootView) {
activity = a; mContext = context;
this.rootView = rootView; this.rootView = rootView;
viewsS = a.getString(R.string.views); viewsS = context.getString(R.string.views);
videosS = a.getString(R.string.videos); videosS = context.getString(R.string.videos);
subsS = a.getString(R.string.subscriber); subsS = context.getString(R.string.subscriber);
thousand = a.getString(R.string.short_thousand); subsPluralS = context.getString(R.string.subscriber_plural);
million = a.getString(R.string.short_million); thousand = context.getString(R.string.short_thousand);
billion = a.getString(R.string.short_billion); million = context.getString(R.string.short_million);
billion = context.getString(R.string.short_billion);
} }
public void setOnStreamInfoItemSelectedListener( public void setOnStreamInfoItemSelectedListener(
@ -156,13 +157,13 @@ public class InfoItemBuilder {
imageLoader.displayImage(info.thumbnail_url, imageLoader.displayImage(info.thumbnail_url,
holder.itemThumbnailView, holder.itemThumbnailView,
displayImageOptions, displayImageOptions,
new ImageErrorLoadingListener(activity, rootView, info.service_id)); new ImageErrorLoadingListener(mContext, rootView, info.service_id));
} }
holder.itemButton.setOnClickListener(new View.OnClickListener() { holder.itemButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { 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, imageLoader.displayImage(info.thumbnailUrl,
holder.itemThumbnailView, holder.itemThumbnailView,
displayImageOptions, displayImageOptions,
new ImageErrorLoadingListener(activity, rootView, info.serviceId)); new ImageErrorLoadingListener(mContext, rootView, info.serviceId));
} }
holder.itemButton.setOnClickListener(new View.OnClickListener() { holder.itemButton.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View view) { 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){ public String shortSubscriber(Long count) {
if(count >= 1000000000){ String curSubString = count > 1 ? subsPluralS : subsS;
return Long.toString(count/1000000000)+ billion + " " + subsS;
}else if(count>=1000000){ if (count >= 1000000000) {
return Long.toString(count/1000000)+ million + " " + subsS; return Long.toString(count / 1000000000) + billion + " " + curSubString;
}else if(count>=1000){ } else if (count >= 1000000) {
return Long.toString(count/1000)+ thousand + " " + subsS; return Long.toString(count / 1000000) + million + " " + curSubString;
}else { } else if (count >= 1000) {
return Long.toString(count)+ " " + subsS; return Long.toString(count / 1000) + thousand + " " + curSubString;
} else {
return Long.toString(count) + " " + curSubString;
} }
} }

View File

@ -299,7 +299,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
return; return;
} }
changeState(STATE_LOADING);
isPrepared = false; isPrepared = false;
qualityChanged = false; qualityChanged = false;
@ -312,6 +311,7 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
if (videoStartPos > 0) simpleExoPlayer.seekTo(videoStartPos); if (videoStartPos > 0) simpleExoPlayer.seekTo(videoStartPos);
simpleExoPlayer.prepare(videoSource); simpleExoPlayer.prepare(videoSource);
simpleExoPlayer.setPlayWhenReady(autoPlay); simpleExoPlayer.setPlayWhenReady(autoPlay);
changeState(STATE_LOADING);
} }
public void destroy() { 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); 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(endScreen, false, 0, 0);
animateView(controlsRoot, false, 0, 0);
loadingPanel.setBackgroundColor(Color.BLACK); loadingPanel.setBackgroundColor(Color.BLACK);
animateView(loadingPanel, true, 0, 0); animateView(loadingPanel, true, 0, 0);
animateView(surfaceForeground, true, 100, 0); animateView(surfaceForeground, true, 100, 0);
@ -408,7 +407,12 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
if (!isProgressLoopRunning.get()) startProgressLoop(); if (!isProgressLoopRunning.get()) startProgressLoop();
showAndAnimateControl(-1, true); showAndAnimateControl(-1, true);
loadingPanel.setVisibility(View.GONE); 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); animateView(currentDisplaySeek, false, 200, 0);
} }
@ -417,7 +421,6 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
if (DEBUG) Log.d(TAG, "onBuffering() called"); if (DEBUG) Log.d(TAG, "onBuffering() called");
loadingPanel.setBackgroundColor(Color.TRANSPARENT); loadingPanel.setBackgroundColor(Color.TRANSPARENT);
animateView(loadingPanel, true, 500, 0); animateView(loadingPanel, true, 500, 0);
animateView(controlsRoot, false, 0, 0, true);
} }
@Override @Override
@ -598,14 +601,12 @@ public abstract class AbstractPlayer implements StateInterface, SeekBar.OnSeekBa
if (DEBUG) Log.d(TAG, "onFastRewind() called"); if (DEBUG) Log.d(TAG, "onFastRewind() called");
seekBy(-FAST_FORWARD_REWIND_AMOUNT); seekBy(-FAST_FORWARD_REWIND_AMOUNT);
showAndAnimateControl(R.drawable.ic_action_av_fast_rewind, true); showAndAnimateControl(R.drawable.ic_action_av_fast_rewind, true);
animateView(controlsRoot, false, 100, 0);
} }
public void onFastForward() { public void onFastForward() {
if (DEBUG) Log.d(TAG, "onFastForward() called"); if (DEBUG) Log.d(TAG, "onFastForward() called");
seekBy(FAST_FORWARD_REWIND_AMOUNT); seekBy(FAST_FORWARD_REWIND_AMOUNT);
showAndAnimateControl(R.drawable.ic_action_av_fast_forward, true); showAndAnimateControl(R.drawable.ic_action_av_fast_forward, true);
animateView(controlsRoot, false, 100, 0);
} }
/*////////////////////////////////////////////////////////////////////////// /*//////////////////////////////////////////////////////////////////////////

View File

@ -24,9 +24,10 @@ import android.widget.Toast;
import org.schabi.newpipe.ActivityCommunicator; import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.detail.VideoItemDetailActivity; import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.util.NavStack; import org.schabi.newpipe.util.Constants;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
@ -353,10 +354,11 @@ public class BackgroundPlayer extends Service /*implements MediaPlayer.OnPrepare
new Intent(ACTION_REWIND), PendingIntent.FLAG_UPDATE_CURRENT); new Intent(ACTION_REWIND), PendingIntent.FLAG_UPDATE_CURRENT);
//build intent to return to video, on tapping notification //build intent to return to video, on tapping notification
Intent openDetailViewIntent = new Intent(getApplicationContext(), Intent openDetailViewIntent = new Intent(getApplicationContext(), MainActivity.class);
VideoItemDetailActivity.class); openDetailViewIntent.putExtra(Constants.KEY_SERVICE_ID, serviceId);
openDetailViewIntent.putExtra(NavStack.SERVICE_ID, serviceId); openDetailViewIntent.putExtra(Constants.KEY_URL, webUrl);
openDetailViewIntent.putExtra(NavStack.URL, webUrl); openDetailViewIntent.putExtra(Constants.KEY_TITLE, title);
openDetailViewIntent.putExtra(Constants.KEY_LINK_TYPE, StreamingService.LinkType.STREAM);
openDetailViewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); openDetailViewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent openDetailView = PendingIntent.getActivity(owner, noteID, PendingIntent openDetailView = PendingIntent.getActivity(owner, noteID,
openDetailViewIntent, PendingIntent.FLAG_UPDATE_CURRENT); openDetailViewIntent, PendingIntent.FLAG_UPDATE_CURRENT);

View File

@ -24,7 +24,6 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.util.NavStack;
import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.PermissionHelper;
import org.schabi.newpipe.util.ThemeHelper; import org.schabi.newpipe.util.ThemeHelper;
@ -70,6 +69,7 @@ public class ExoPlayerActivity extends Activity {
return; return;
} }
showSystemUi();
setContentView(R.layout.activity_exo_player); setContentView(R.layout.activity_exo_player);
playerImpl = new AbstractPlayerImpl(); playerImpl = new AbstractPlayerImpl();
playerImpl.setup(findViewById(android.R.id.content)); playerImpl.setup(findViewById(android.R.id.content));
@ -88,7 +88,6 @@ public class ExoPlayerActivity extends Activity {
public void onBackPressed() { public void onBackPressed() {
if (DEBUG) Log.d(TAG, "onBackPressed() called"); if (DEBUG) Log.d(TAG, "onBackPressed() called");
super.onBackPressed(); super.onBackPressed();
if (playerImpl.isStartedFromNewPipe()) NavStack.getInstance().openDetailActivity(this, playerImpl.getVideoUrl(), 0);
if (playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(false); if (playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(false);
} }
@ -340,8 +339,7 @@ public class ExoPlayerActivity extends Activity {
public void onStopTrackingTouch(SeekBar seekBar) { public void onStopTrackingTouch(SeekBar seekBar) {
super.onStopTrackingTouch(seekBar); super.onStopTrackingTouch(seekBar);
if (playerImpl.wasPlaying()) { if (playerImpl.wasPlaying()) {
hideSystemUi(); animateView(playerImpl.getControlsRoot(), false, 100, 0);
playerImpl.getControlsRoot().setVisibility(View.GONE);
} }
} }
@ -365,6 +363,13 @@ public class ExoPlayerActivity extends Activity {
public void onLoading() { public void onLoading() {
super.onLoading(); super.onLoading();
playPauseButton.setImageResource(R.drawable.ic_pause_white); playPauseButton.setImageResource(R.drawable.ic_pause_white);
animateView(playPauseButton, false, 100, 0);
}
@Override
public void onBuffering() {
super.onBuffering();
animateView(playPauseButton, false, 100, 0);
} }
@Override @Override
@ -384,6 +389,7 @@ public class ExoPlayerActivity extends Activity {
public void onPlaying() { public void onPlaying() {
super.onPlaying(); super.onPlaying();
animateView(playPauseButton, true, 500, 0); animateView(playPauseButton, true, 500, 0);
showSystemUi();
} }
@Override @Override

View File

@ -34,16 +34,17 @@ import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListene
import org.schabi.newpipe.ActivityCommunicator; import org.schabi.newpipe.ActivityCommunicator;
import org.schabi.newpipe.BuildConfig; import org.schabi.newpipe.BuildConfig;
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R; import org.schabi.newpipe.R;
import org.schabi.newpipe.detail.VideoItemDetailActivity;
import org.schabi.newpipe.extractor.MediaFormat; import org.schabi.newpipe.extractor.MediaFormat;
import org.schabi.newpipe.extractor.NewPipe; import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.stream_info.StreamExtractor; import org.schabi.newpipe.extractor.stream_info.StreamExtractor;
import org.schabi.newpipe.extractor.stream_info.StreamInfo; import org.schabi.newpipe.extractor.stream_info.StreamInfo;
import org.schabi.newpipe.extractor.stream_info.VideoStream; 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.ThemeHelper;
import org.schabi.newpipe.util.Utils;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -107,7 +108,7 @@ public class PopupVideoPlayer extends Service {
if (!playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(true); if (!playerImpl.isPlaying()) playerImpl.getPlayer().setPlayWhenReady(true);
if (imageLoader != null) imageLoader.clearMemoryCache(); if (imageLoader != null) imageLoader.clearMemoryCache();
if (intent.getStringExtra(NavStack.URL) != null) { if (intent.getStringExtra(Constants.KEY_URL) != null) {
playerImpl.setStartedFromNewPipe(false); playerImpl.setStartedFromNewPipe(false);
Thread fetcher = new Thread(new FetcherRunnable(intent)); Thread fetcher = new Thread(new FetcherRunnable(intent));
fetcher.start(); fetcher.start();
@ -158,7 +159,7 @@ public class PopupVideoPlayer extends Service {
playerImpl.onVideoPlayPause(); playerImpl.onVideoPlayPause();
break; break;
case ACTION_OPEN_DETAIL: case ACTION_OPEN_DETAIL:
onOpenDetail(PopupVideoPlayer.this, playerImpl.getVideoUrl()); onOpenDetail(PopupVideoPlayer.this, playerImpl.getVideoUrl(), playerImpl.getVideoTitle());
break; break;
case ACTION_REPEAT: case ACTION_REPEAT:
playerImpl.onRepeatClicked(); playerImpl.onRepeatClicked();
@ -266,12 +267,14 @@ public class PopupVideoPlayer extends Service {
stopSelf(); 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 + "]"); if (DEBUG) Log.d(TAG, "onOpenDetail() called with: context = [" + context + "], videoUrl = [" + videoUrl + "]");
Intent i = new Intent(context, VideoItemDetailActivity.class); Intent i = new Intent(context, MainActivity.class);
i.putExtra(NavStack.SERVICE_ID, 0) i.putExtra(Constants.KEY_SERVICE_ID, 0);
.putExtra(NavStack.URL, videoUrl) i.putExtra(Constants.KEY_URL, videoUrl);
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 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.startActivity(i);
context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)); context.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
} }
@ -510,8 +513,6 @@ public class PopupVideoPlayer extends Service {
private class FetcherRunnable implements Runnable { private class FetcherRunnable implements Runnable {
private final Intent intent; private final Intent intent;
private final Handler mainHandler; private final Handler mainHandler;
private final boolean printStreams = true;
FetcherRunnable(Intent intent) { FetcherRunnable(Intent intent) {
this.intent = intent; this.intent = intent;
@ -524,48 +525,22 @@ public class PopupVideoPlayer extends Service {
try { try {
StreamingService service = NewPipe.getService(0); StreamingService service = NewPipe.getService(0);
if (service == null) return; if (service == null) return;
streamExtractor = service.getExtractorInstance(intent.getStringExtra(NavStack.URL)); streamExtractor = service.getExtractorInstance(intent.getStringExtra(Constants.KEY_URL));
StreamInfo info = StreamInfo.getVideoInfo(streamExtractor); 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 playerImpl.setVideoStreamsList(info.video_streams instanceof ArrayList
? (ArrayList<VideoStream>) info.video_streams ? (ArrayList<VideoStream>) info.video_streams
: new ArrayList<>(info.video_streams)); : new ArrayList<>(info.video_streams));
for (VideoStream item : info.video_streams) { int defaultResolution = Utils.getPreferredResolution(PopupVideoPlayer.this, info.video_streams);
if (DEBUG && printStreams) { playerImpl.setSelectedIndexStream(defaultResolution);
Log.d(TAG, "FetcherRunnable.StreamExtractor: current Item"
+ ", item.resolution = " + item.resolution if (DEBUG) {
+ ", item.format = " + item.format Log.d(TAG, "FetcherRunnable.StreamExtractor: chosen = "
+ ", item.url = " + item.url); + MediaFormat.getNameById(info.video_streams.get(defaultResolution).format) + " "
} + info.video_streams.get(defaultResolution).resolution + " > "
if (defaultResolution.equals(item.resolution)) { + info.video_streams.get(defaultResolution).url);
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 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.setVideoUrl(info.webpage_url);
playerImpl.setVideoTitle(info.title); playerImpl.setVideoTitle(info.title);
playerImpl.setChannelName(info.uploader); playerImpl.setChannelName(info.uploader);
@ -578,6 +553,8 @@ public class PopupVideoPlayer extends Service {
playerImpl.playVideo(playerImpl.getSelectedStreamUri(), true); playerImpl.playVideo(playerImpl.getSelectedStreamUri(), true);
} }
}); });
imageLoader.resume();
imageLoader.loadImage(info.thumbnail_url, displayImageOptions, new SimpleImageLoadingListener() { imageLoader.loadImage(info.thumbnail_url, displayImageOptions, new SimpleImageLoadingListener() {
@Override @Override
public void onLoadingComplete(String imageUri, View view, final Bitmap loadedImage) { public void onLoadingComplete(String imageUri, View view, final Bitmap loadedImage) {

View 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";
}

View File

@ -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()));
}
}
}

View File

@ -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;
}
}

View 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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}
}

View File

@ -1,15 +1,16 @@
<?xml version="1.0" encoding="utf-8"?> <?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" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="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 <FrameLayout
android:id="@+id/search_fragment" android:id="@+id/fragment_holder"
android:name="org.schabi.newpipe.search_fragment.SearchInfoItemFragment" android:name="org.schabi.newpipe.search_fragment.SearchInfoItemFragment"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" /> android:layout_height="match_parent"/>
</RelativeLayout> </RelativeLayout>

View File

@ -3,24 +3,22 @@
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical" android:orientation="vertical"
android:title="Channel"> android:title="@string/channel">
<android.support.v7.widget.RecyclerView <android.support.v7.widget.RecyclerView
android:id="@+id/channel_streams_view" android:id="@+id/channel_streams_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?android:windowBackground" android:background="?android:windowBackground"
android:scrollbars="vertical"/> android:scrollbars="vertical"
tools:listitem="@layout/stream_item"/>
<RelativeLayout <ProgressBar
android:layout_width="match_parent" android:id="@+id/loading_progress_bar"
android:layout_height="match_parent" android:layout_width="wrap_content"
android:id="@+id/channel_loading"> android:layout_height="wrap_content"
<ProgressBar android:id="@+id/progressBar" android:layout_centerInParent="true"
android:layout_width="wrap_content" android:indeterminate="true"/>
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:indeterminate="true"/>
</RelativeLayout>
</RelativeLayout> </RelativeLayout>

View File

@ -5,7 +5,7 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_width="match_parent" android:layout_width="match_parent"
android:name="org.schabi.newpipe.SearchInfoItemFragment" android:name="org.schabi.newpipe.SearchInfoItemFragment"
tools:context=".search_fragment.SearchInfoItemFragment"> tools:context=".fragments.search.SearchFragment">
<include layout="@layout/main_bg" /> <include layout="@layout/main_bg" />
@ -17,10 +17,11 @@
tools:listitem="@layout/stream_item" tools:listitem="@layout/stream_item"
android:scrollbars="vertical"/> android:scrollbars="vertical"/>
<ProgressBar android:id="@+id/progressBar" <ProgressBar android:id="@+id/loading_progress_bar"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerInParent="true" android:layout_centerInParent="true"
android:indeterminate="true" android:indeterminate="true"
android:visibility="gone"/> android:visibility="gone"
tools:visibility="visible"/>
</RelativeLayout> </RelativeLayout>

View File

@ -17,6 +17,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<!-- THUMBNAIL -->
<RelativeLayout <RelativeLayout
android:id="@+id/detail_thumbnail_root_layout" android:id="@+id/detail_thumbnail_root_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -57,14 +58,13 @@
</RelativeLayout> </RelativeLayout>
<!-- TITLE -->
<RelativeLayout <RelativeLayout
android:id="@+id/detail_content_root_layout" android:id="@+id/detail_title_background"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
android:layout_below="@id/detail_thumbnail_root_layout" android:layout_below="@+id/detail_thumbnail_root_layout"
android:background="?android:windowBackground" android:background="?android:windowBackground">
android:visibility="gone"
tools:visibility="visible">
<RelativeLayout <RelativeLayout
android:id="@+id/detail_title_root_layout" android:id="@+id/detail_title_root_layout"
@ -106,13 +106,25 @@
</RelativeLayout> </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 <TextView
android:id="@+id/detail_view_count_view" android:id="@+id/detail_view_count_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_alignParentStart="true" android:layout_alignParentStart="true"
android:layout_below="@id/detail_title_root_layout"
android:layout_marginLeft="12dp" android:layout_marginLeft="12dp"
android:layout_marginRight="12dp" android:layout_marginRight="12dp"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
@ -309,14 +321,15 @@
</RelativeLayout> </RelativeLayout>
<!-- LOADING BAR -->
<ProgressBar <ProgressBar
android:id="@+id/detail_loading_progress_bar" android:id="@+id/detail_loading_progress_bar"
style="@style/Widget.AppCompat.ProgressBar" style="@style/Widget.AppCompat.ProgressBar"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="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_centerHorizontal="true"
android:layout_marginTop="30dp" android:layout_marginTop="20dp"
android:indeterminate="true"/> android:indeterminate="true"/>
</RelativeLayout> </RelativeLayout>

View File

@ -4,7 +4,7 @@
android:orientation="vertical" android:layout_width="match_parent" android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:id="@+id/mainBG" android:id="@+id/mainBG"
tools:context=".detail.VideoItemDetailActivity"> tools:context=".fragments.detail.VideoDetailFragment">
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -3,11 +3,13 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/action_show_downloads" <item android:id="@+id/action_show_downloads"
app:showAsAction="never" android:orderInCategory="980"
android:title="@string/downloads" /> android:title="@string/downloads"
app:showAsAction="never"/>
<item android:id="@+id/action_settings" <item android:id="@+id/action_settings"
app:showAsAction="never" android:orderInCategory="990"
android:title="@string/settings"/> android:title="@string/settings"
app:showAsAction="never"/>
</menu> </menu>

View File

@ -1,7 +1,7 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" 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" <item android:id="@+id/menu_item_openInBrowser"
app:showAsAction="never" app:showAsAction="never"
@ -11,10 +11,4 @@
android:title="@string/share" android:title="@string/share"
app:showAsAction="ifRoom" app:showAsAction="ifRoom"
android:icon="?attr/share"/> android:icon="?attr/share"/>
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:title="@string/action_settings"
app:showAsAction="never" />
</menu> </menu>

View File

@ -8,7 +8,8 @@
app:actionViewClass="android.support.v7.widget.SearchView" /> app:actionViewClass="android.support.v7.widget.SearchView" />
<group android:id="@+id/search_filter_group" <group android:id="@+id/search_filter_group"
android:checkableBehavior="single"> android:checkableBehavior="single"
android:orderInCategory="999">
<item android:id="@+id/menu_filter_all" <item android:id="@+id/menu_filter_all"
android:title = "@string/all" android:title = "@string/all"
android:checked = "true"/> android:checked = "true"/>

View File

@ -29,14 +29,4 @@
<item android:id="@+id/menu_item_openInBrowser" <item android:id="@+id/menu_item_openInBrowser"
app:showAsAction="never" app:showAsAction="never"
android:title="@string/open_in_browser" /> 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> </menu>

View File

@ -135,7 +135,7 @@
<string name="switch_mode">Zwischen Liste und Gitter umschalten</string> <string name="switch_mode">Zwischen Liste und Gitter umschalten</string>
<string name="videos">Videos</string> <string name="videos">Videos</string>
<string name="subscriber">Abonnenten</string> <string name="subscriber">Abonnent</string>
<string name="views">Aufrufe</string> <string name="views">Aufrufe</string>
<string name="short_thousand">Tsd.</string> <string name="short_thousand">Tsd.</string>
<string name="short_million">Mio.</string> <string name="short_million">Mio.</string>
@ -186,4 +186,14 @@ Möchten Sie jetzt neu starten?</string>
<string name="disabled">Deaktiviert</string> <string name="disabled">Deaktiviert</string>
<string name="use_old_player_title">Benutze den alten Player</string> <string name="use_old_player_title">Benutze den alten Player</string>
<string name="open_in_popup_mode">Im Popup-Modus öffnen</string>
<string name="preferred_video_format_title">Bevorzugtes Videoformat</string>
<string name="popup_playing_toast">Spiele im Popup-Modus ab</string>
<string name="popup_mode_share_menu_title">NewPipe Popup-Modus</string>
<string name="subscriber_plural">Abonnenten</string>
<string name="msg_popup_permission">Diese Berechtigung ist für das
Öffnen im Popup-Modus erforderlich</string>
<string name="use_old_player_summary">Mediaframework Player der vorherigen Version.</string>
</resources> </resources>

View File

@ -19,7 +19,7 @@
<string name="download_path_title">Ruta de descarga de vídeo</string> <string name="download_path_title">Ruta de descarga de vídeo</string>
<string name="download_path_summary">Ruta para almacenar los vídeos descargados.</string> <string name="download_path_summary">Ruta para almacenar los vídeos descargados.</string>
<string name="download_path_dialog_title">Introducir directorio de descargas para vídeos</string> <string name="download_path_dialog_title">Introducir directorio de descargas para vídeos</string>
<string name="default_resolution_title">Resolución por defecto</string> <string name="default_resolution_title">Resolución de vídeo por defecto</string>
<string name="play_with_kodi_title">Reproducir con Kodi</string> <string name="play_with_kodi_title">Reproducir con Kodi</string>
<string name="kore_not_found">Aplicación Kore no encontrada. ¿Instalar Kore?</string> <string name="kore_not_found">Aplicación Kore no encontrada. ¿Instalar Kore?</string>
<string name="show_play_with_kodi_title">Mostrar opción \"Reproducir con Kodi\"</string> <string name="show_play_with_kodi_title">Mostrar opción \"Reproducir con Kodi\"</string>
@ -131,13 +131,13 @@
<string name="checksum">Checksum</string> <string name="checksum">Checksum</string>
<string name="add">Nueva misión</string> <string name="add">Nueva misión</string>
<string name="finish">Listo</string> <string name="finish">Ok</string>
<string name="switch_mode">Cambiar entre lista y cuadrícula</string> <string name="switch_mode">Cambiar entre lista y cuadrícula</string>
<string name="msg_url">URL de descarga</string> <string name="msg_url">URL de descarga</string>
<string name="msg_name">Nombre del archivo</string> <string name="msg_name">Nombre del archivo</string>
<string name="msg_threads">Hilos de conexión</string> <string name="msg_threads">Conexiones simultáneas</string>
<string name="msg_fetch_filename">Obtener nombre de archivo</string> <string name="msg_fetch_filename">Obtener nombre de archivo</string>
<string name="msg_error">Error</string> <string name="msg_error">Error</string>
<string name="msg_server_unsupported">Servidor no soportado</string> <string name="msg_server_unsupported">Servidor no soportado</string>
@ -188,4 +188,7 @@ abrir en modo popup</string>
<string name="popup_playing_toast">Reproduciendo en modo popup</string> <string name="popup_playing_toast">Reproduciendo en modo popup</string>
<string name="use_old_player_title">Usar reproductor antiguo</string> <string name="use_old_player_title">Usar reproductor antiguo</string>
<string name="use_old_player_summary">Versión antigua en reproductor Mediaframework.</string> <string name="use_old_player_summary">Versión antigua en reproductor Mediaframework.</string>
<string name="preferred_video_format_title">Formato de vídeo preferido</string>
<string name="disabled">Desactivado</string>
</resources> </resources>

View File

@ -189,4 +189,6 @@ membuka di mode popup</string>
<string name="use_old_player_summary">Versi lama dalam pemutar Mediaframework.</string> <string name="use_old_player_summary">Versi lama dalam pemutar Mediaframework.</string>
<string name="disabled">Dinonaktifkan</string> <string name="disabled">Dinonaktifkan</string>
<string name="preferred_video_format_title">Pilihan format video</string>
<string name="subscriber_plural">subscriber</string>
</resources> </resources>

View File

@ -196,4 +196,6 @@
<string name="use_old_player_summary">Mediaframework プレーヤーの古いビルド。</string> <string name="use_old_player_summary">Mediaframework プレーヤーの古いビルド。</string>
<string name="disabled">無効</string> <string name="disabled">無効</string>
<string name="preferred_video_format_title">お好みのビデオ フォーマット</string>
<string name="subscriber_plural">購読者</string>
</resources> </resources>

View File

@ -185,4 +185,8 @@ te openen in pop-upmodus</string>
<string name="popup_playing_toast">Speelt af in pop-upmodus</string> <string name="popup_playing_toast">Speelt af in pop-upmodus</string>
<string name="use_old_player_title">Gebruik oude speler</string> <string name="use_old_player_title">Gebruik oude speler</string>
<string name="use_old_player_summary">Oude build in Mediaframework-speler.</string> <string name="use_old_player_summary">Oude build in Mediaframework-speler.</string>
<string name="preferred_video_format_title">Voorkeursvideoformaat</string>
<string name="disabled">Uitgeschakeld</string>
<string name="subscriber_plural">abonnees</string>
</resources> </resources>

View File

@ -194,4 +194,5 @@ odpiranje v pojavnem načinu</string>
<string name="use_old_player_title">Uporabi star predvajalnik</string> <string name="use_old_player_title">Uporabi star predvajalnik</string>
<string name="use_old_player_summary">Zastarela različica v predvajalniku Mediaframework.</string> <string name="use_old_player_summary">Zastarela različica v predvajalniku Mediaframework.</string>
<string name="preferred_video_format_title">Prednostni zapis video datoteke</string>
</resources> </resources>

View File

@ -197,4 +197,5 @@
<string name="disabled">Искључено</string> <string name="disabled">Искључено</string>
<string name="use_old_player_title">Користи стари плејер</string> <string name="use_old_player_title">Користи стари плејер</string>
<string name="preferred_video_format_title">Пожељни формат видеа</string>
</resources> </resources>

View File

@ -1,8 +1,8 @@
<?xml version='1.0' encoding='UTF-8'?> <?xml version='1.0' encoding='UTF-8'?>
<resources><string name="background_player_name">Фоновий програвач NewPipe</string> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"><string name="background_player_name">Фоновий програвач NewPipe</string>
<string name="view_count_text">%1$s переглядів</string> <string name="view_count_text">%1$s переглядів</string>
<string name="upload_date_text">Завантажений %1$s</string> <string name="upload_date_text">Опубліковано %1$s</string>
<string name="no_player_found">Потоковий програвач не знайдено. Встановити VLC?</string> <string name="no_player_found">Потоковий програвач не знайдено. Бажаєте встановити VLC?</string>
<string name="install">Встановити</string> <string name="install">Встановити</string>
<string name="cancel">Скасувати</string> <string name="cancel">Скасувати</string>
<string name="open_in_browser">Відкрити в браузері</string> <string name="open_in_browser">Відкрити в браузері</string>
@ -27,10 +27,10 @@
<string name="download_path_summary">Шлях де будуть зберігатись завантажені відео.</string> <string name="download_path_summary">Шлях де будуть зберігатись завантажені відео.</string>
<string name="download_path_audio_title">Шлях для завантаження аудіо</string> <string name="download_path_audio_title">Шлях для завантаження аудіо</string>
<string name="download_path_audio_summary">Шлях де будуть зберігатись завантажені аудіо файли.</string> <string name="download_path_audio_summary">Шлях де будуть зберігатись завантажені аудіо файли</string>
<string name="autoplay_by_calling_app_title">Автоматично відтворювати при виклику з іншого додатку</string> <string name="autoplay_by_calling_app_title">Автоматично відтворювати при виклику з іншого додатку</string>
<string name="autoplay_by_calling_app_summary">Автоматично відтворювати відео коли NewPipe викликано з іншого додатку.</string> <string name="autoplay_by_calling_app_summary">Автоматично відтворювати відео коли NewPipe викликано з іншого додатку.</string>
<string name="default_resolution_title">Роздільна здатність за замовчуванням</string> <string name="default_resolution_title">Типова роздільна здатність</string>
<string name="play_with_kodi_title">Відтворювати за допомогою Kodi</string> <string name="play_with_kodi_title">Відтворювати за допомогою Kodi</string>
<string name="kore_not_found">Додаток Kore не знайдено. Встановити?</string> <string name="kore_not_found">Додаток Kore не знайдено. Встановити?</string>
<string name="show_play_with_kodi_title">Показувати опцію \"Програвати за допомогою Kodi\"</string> <string name="show_play_with_kodi_title">Показувати опцію \"Програвати за допомогою Kodi\"</string>
@ -48,7 +48,7 @@
<string name="show_next_and_similar_title">Показувати наступні та схожі відео</string> <string name="show_next_and_similar_title">Показувати наступні та схожі відео</string>
<string name="url_not_supported_toast">URL не підтримується</string> <string name="url_not_supported_toast">URL не підтримується</string>
<string name="similar_videos_btn_text">Схожі відео</string> <string name="similar_videos_btn_text">Схожі відео</string>
<string name="search_language_title">Мова контенту</string> <string name="search_language_title">Переважна мова контенту</string>
<string name="settings_category_video_audio_title">Відео та Аудіо</string> <string name="settings_category_video_audio_title">Відео та Аудіо</string>
<string name="settings_category_appearance_title">Зовнішній вигляд</string> <string name="settings_category_appearance_title">Зовнішній вигляд</string>
<string name="settings_category_other_title">Інше</string> <string name="settings_category_other_title">Інше</string>
@ -79,4 +79,75 @@
<string name="error_snackbar_action">ЗВІТУВАТИ</string> <string name="error_snackbar_action">ЗВІТУВАТИ</string>
<string name="what_device_headline">Інформація:</string> <string name="what_device_headline">Інформація:</string>
<string name="what_happened_headline">Що сталося:</string> <string name="what_happened_headline">Що сталося:</string>
<string name="main_bg_subtitle">Натисніть Пошук для початку</string>
<string name="black_theme_title">Чорна</string>
<string name="downloads">Завантаження</string>
<string name="downloads_title">Завантаження</string>
<string name="settings_title">Налаштування</string>
<string name="error_report_title">Звіт про помилку</string>
<string name="all">Все</string>
<string name="channel">Канал</string>
<string name="yes">Так</string>
<string name="later">Пізніше</string>
<string name="disabled">Вимкнено</string>
<string name="could_not_load_image">Не вдалося завантажити зображення</string>
<string name="app_ui_crash">Додаток/інтерфейс зазнав краху</string>
<string name="your_comment">Ваші коментарі (Англійською):</string>
<string name="error_details_headline">Деталі:</string>
<string name="list_thumbnail_view_description">Мініатюра попереднього перегляду відео</string>
<string name="detail_thumbnail_view_description">Мініатюра попереднього перегляду відео</string>
<string name="use_tor_title">Використовувати Tor</string>
<string name="use_tor_summary">(Експериментально) Перенаправляти трафік через Tor для підвищення конфіденційності (трансляція відео ще не підтримується).</string>
<string name="report_error">Звітувати про помилку</string>
<string name="err_dir_create">Не вдалося створити теку для завантаження \'%1$s\'</string>
<string name="info_dir_created">Створити теку для завантаження \'%1$s\'</string>
<string name="enable_background_audio">Відтворювати у фоні</string>
<string name="video">Відео</string>
<string name="audio">Аудіо</string>
<string name="text">Текст</string>
<string name="retry">Повторити</string>
<string name="off">[вимкнено]</string>
<string name="error_drm_not_supported">Захищений контент не підтримується версіями API нижче 18</string>
<string name="error_drm_unsupported_scheme">Поточний пристрій не підтримує необхідну схему DRM</string>
<string name="error_drm_unknown">Виникла невідома помилка DRM</string>
<string name="error_no_decoder">Поточний пристрій не підтримує декодер для <xliff:g id="mime_type">%1$s</xliff:g></string>
<string name="error_no_secure_decoder">Поточний пристрій не підтримує безпечний декодер для <xliff:g id="mime_type">%1$s</xliff:g></string>
<string name="logging">Звітування</string>
<string name="logging_normal">Нормальне</string>
<string name="logging_verbose">Детальне</string>
<string name="error_querying_decoders">На пристрої недоступний декодер</string>
<string name="error_instantiating_decoder">Не вдалося ініціювати декодер <xliff:g id="decoder_name">%1$s</xliff:g></string>
<string name="use_old_player_title">Використовувати старий плеер</string>
<string name="short_thousand">К</string>
<string name="short_million">М</string>
<string name="short_billion">Б</string>
<string name="restart_title">Перезапуск</string>
<string name="start">Почати</string>
<string name="pause">Пауза</string>
<string name="view">Відтворити</string>
<string name="delete">Видалити</string>
<string name="checksum">Контрольна сума</string>
<string name="finish">Гаразд</string>
<string name="msg_url">Завантажити URL</string>
<string name="msg_name">Назва файлу</string>
<string name="msg_threads">Потоки</string>
<string name="msg_error">Помилка</string>
<string name="msg_server_unsupported">Сервер не підтримується</string>
<string name="msg_exists">Файл вже існує</string>
<string name="msg_running">NewPipe завантажує</string>
<string name="msg_running_detail">Натисніть для подробиць</string>
<string name="msg_wait">Будь ласка, зачекайте…</string>
<string name="msg_copied">Скопійовано до буферу обміну.</string>
<string name="no_available_dir">Будь ласка, оберіть теку для завантаження.</string>
<string name="msg_restart">Ви маєте перезавантажити додаток, аби застосувати тему.
Бажаєте перезапустити зараз?</string>
<string name="action_settings">Налаштування</string>
</resources> </resources>

View File

@ -158,6 +158,7 @@
<string name="use_old_player_summary">Old build in Mediaframework player.</string> <string name="use_old_player_summary">Old build in Mediaframework player.</string>
<string name="videos">videos</string> <string name="videos">videos</string>
<string name="subscriber">subscriber</string> <string name="subscriber">subscriber</string>
<string name="subscriber_plural">subscribers</string>
<string name="subscribe">Subscribe</string> <string name="subscribe">Subscribe</string>
<string name="views">views</string> <string name="views">views</string>
<string name="short_thousand">K</string> <string name="short_thousand">K</string>