fix merge with add_cahnnels
This commit is contained in:
commit
54ab0ab17e
79 changed files with 2611 additions and 1659 deletions
|
@ -31,6 +31,7 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
testCompile 'junit:junit:4.12'
|
||||
compile 'com.android.support:appcompat-v7:24.1.1'
|
||||
compile 'com.android.support:support-v4:24.1.1'
|
||||
compile 'com.android.support:design:24.1.1'
|
||||
|
|
|
@ -79,6 +79,10 @@ public class YoutubeStreamExtractorDefaultTest extends AndroidTestCase {
|
|||
assertTrue(extractor.getUploadDate().length() > 0);
|
||||
}
|
||||
|
||||
public void testGetChannelUrl() throws ParsingException {
|
||||
assertTrue(extractor.getChannelUrl().length() > 0);
|
||||
}
|
||||
|
||||
public void testGetThumbnailUrl() throws ParsingException {
|
||||
assertTrue(extractor.getThumbnailUrl(),
|
||||
extractor.getThumbnailUrl().contains(HTTPS));
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
android:logo="@mipmap/ic_launcher"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:ignore="AllowBackup">
|
||||
|
||||
<activity
|
||||
android:name=".VideoItemListActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/SplashScreenTheme">
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
|
@ -27,12 +27,12 @@
|
|||
</intent-filter>
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".VideoItemDetailActivity"
|
||||
android:name=".detail.VideoItemDetailActivity"
|
||||
android:label="@string/title_videoitem_detail"
|
||||
android:theme="@style/AppTheme">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".VideoItemListActivity" />
|
||||
android:value=".MainActivity" />
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
@ -80,12 +80,14 @@
|
|||
android:name=".player.PlayVideoActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:theme="@style/VideoPlayerTheme"
|
||||
tools:ignore="UnusedAttribute"/>
|
||||
tools:ignore="UnusedAttribute" />
|
||||
|
||||
<service
|
||||
android:name=".player.BackgroundPlayer"
|
||||
android:exported="false"
|
||||
android:label="@string/background_player_name"/>
|
||||
<activity
|
||||
android:label="@string/background_player_name" />
|
||||
|
||||
<activity
|
||||
android:name=".player.ExoPlayerActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:label="@string/app_name"
|
||||
|
@ -103,12 +105,14 @@
|
|||
<data android:scheme="file" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".player.BackgroundPlayer"
|
||||
android:label="@string/background_player_name"
|
||||
android:exported="false" />
|
||||
android:exported="false"
|
||||
android:label="@string/background_player_name" />
|
||||
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:name=".settings.SettingsActivity"
|
||||
android:label="@string/settings_activity_title" />
|
||||
<activity
|
||||
android:name=".PanicResponderActivity"
|
||||
|
@ -125,18 +129,16 @@
|
|||
android:name=".ExitActivity"
|
||||
android:label="@string/general_error"
|
||||
android:theme="@android:style/Theme.NoDisplay" />
|
||||
<activity android:name=".ErrorActivity"/>
|
||||
<activity android:name=".ErrorActivity" />
|
||||
|
||||
<!-- giga get related -->
|
||||
<activity
|
||||
android:name=".download.MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme"
|
||||
android:launchMode="singleTask">
|
||||
</activity>
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/AppTheme" />
|
||||
|
||||
<service
|
||||
android:name="us.shandian.giga.service.DownloadManagerService"/>
|
||||
<service android:name="us.shandian.giga.service.DownloadManagerService" />
|
||||
|
||||
<activity
|
||||
android:name="com.nononsenseapps.filepicker.FilePickerActivity"
|
||||
|
@ -145,5 +147,11 @@
|
|||
android:launchMode="singleTop">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ChannelActivity"
|
||||
android:label="@string/title_activity_channel"
|
||||
android:theme="@style/AppTheme.NoActionBar" />
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
</manifest>
|
|
@ -43,7 +43,7 @@ public class ActivityCommunicator {
|
|||
public volatile Bitmap backgroundPlayerThumbnail;
|
||||
|
||||
// Sent from any activity to ErrorActivity.
|
||||
public volatile List<Exception> errorList;
|
||||
public volatile List<Throwable> errorList;
|
||||
public volatile Class returnActivity;
|
||||
public volatile ErrorActivity.ErrorInfo errorInfo;
|
||||
}
|
||||
|
|
|
@ -2,12 +2,12 @@ package org.schabi.newpipe;
|
|||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
|
||||
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
|
||||
import info.guardianproject.netcipher.NetCipher;
|
||||
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
||||
|
||||
|
|
248
app/src/main/java/org/schabi/newpipe/ChannelActivity.java
Normal file
248
app/src/main/java/org/schabi/newpipe/ChannelActivity.java
Normal file
|
@ -0,0 +1,248 @@
|
|||
package org.schabi.newpipe;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.support.design.widget.CollapsingToolbarLayout;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
import org.schabi.newpipe.detail.VideoItemDetailActivity;
|
||||
import org.schabi.newpipe.detail.VideoItemDetailFragment;
|
||||
import org.schabi.newpipe.extractor.ChannelExtractor;
|
||||
import org.schabi.newpipe.extractor.ChannelInfo;
|
||||
import org.schabi.newpipe.extractor.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.ParsingException;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.info_list.InfoListAdapter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class ChannelActivity extends AppCompatActivity {
|
||||
|
||||
|
||||
private static final String TAG = ChannelActivity.class.toString();
|
||||
private View rootView = null;
|
||||
|
||||
// intent const
|
||||
public static final String CHANNEL_URL = "channel_url";
|
||||
public static final String SERVICE_ID = "service_id";
|
||||
|
||||
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;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_channel);
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
rootView = findViewById(R.id.rootView);
|
||||
setSupportActionBar(toolbar);
|
||||
Intent i = getIntent();
|
||||
channelUrl = i.getStringExtra(CHANNEL_URL);
|
||||
serviceId = i.getIntExtra(SERVICE_ID, -1);
|
||||
|
||||
infoListAdapter = new InfoListAdapter(this, rootView);
|
||||
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.channel_streams_view);
|
||||
final LinearLayoutManager layoutManager = new LinearLayoutManager(this);
|
||||
recyclerView.setLayoutManager(layoutManager);
|
||||
recyclerView.setAdapter(infoListAdapter);
|
||||
infoListAdapter.setOnItemSelectedListener(new InfoListAdapter.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void selected(String url) {
|
||||
Intent detailIntent = new Intent(ChannelActivity.this, VideoItemDetailActivity.class);
|
||||
detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, url);
|
||||
detailIntent.putExtra(
|
||||
VideoItemDetailFragment.STREAMING_SERVICE, serviceId);
|
||||
startActivity(detailIntent);
|
||||
}
|
||||
});
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
requestData(false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void updateUi(final ChannelInfo info) {
|
||||
CollapsingToolbarLayout ctl = (CollapsingToolbarLayout) findViewById(R.id.channel_toolbar_layout);
|
||||
ProgressBar progressBar = (ProgressBar) findViewById(R.id.progressBar);
|
||||
ImageView channelBanner = (ImageView) findViewById(R.id.channel_banner_image);
|
||||
FloatingActionButton feedButton = (FloatingActionButton) findViewById(R.id.channel_rss_fab);
|
||||
ImageView avatarView = (ImageView) findViewById(R.id.channel_avatar_view);
|
||||
ImageView haloView = (ImageView) findViewById(R.id.channel_avatar_halo);
|
||||
|
||||
progressBar.setVisibility(View.GONE);
|
||||
|
||||
if(info.channel_name != null && !info.channel_name.isEmpty()) {
|
||||
ctl.setTitle(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);
|
||||
haloView.setVisibility(View.VISIBLE);
|
||||
imageLoader.displayImage(info.avatar_url, avatarView,
|
||||
new ImageErrorLoadingListener(this, rootView ,info.service_id));
|
||||
}
|
||||
|
||||
if(info.feed_url != null && !info.feed_url.isEmpty()) {
|
||||
feedButton.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 {
|
||||
feedButton.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
private void addVideos(final ChannelInfo info) {
|
||||
infoListAdapter.addStreamItemList(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;
|
||||
Thread channelExtractorThread = new Thread(new Runnable() {
|
||||
Handler h = new Handler();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
StreamingService service = null;
|
||||
try {
|
||||
service = ServiceList.getService(serviceId);
|
||||
ChannelExtractor extractor = service.getChannelExtractorInstance(
|
||||
channelUrl, pageNumber, new Downloader());
|
||||
|
||||
final ChannelInfo info = ChannelInfo.getInfo(extractor, new Downloader());
|
||||
|
||||
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
isLoading = false;
|
||||
if(!onlyVideos) {
|
||||
updateUi(info);
|
||||
}
|
||||
hasNextPage = info.hasNextPage;
|
||||
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_STREAM,
|
||||
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, VideoItemDetailFragment.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().name, channelUrl, R.string.parsing_error));
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ChannelActivity.this.finish();
|
||||
}
|
||||
});
|
||||
pe.printStackTrace();
|
||||
} catch(ExtractionException ex) {
|
||||
ErrorActivity.reportError(h, ChannelActivity.this, ex, VideoItemDetailFragment.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().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, VideoItemDetailFragment.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().name, channelUrl, R.string.general_error));
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ChannelActivity.this.finish();
|
||||
}
|
||||
});
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
channelExtractorThread.start();
|
||||
}
|
||||
|
||||
}
|
|
@ -5,10 +5,12 @@ import java.io.IOException;
|
|||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
import info.guardianproject.netcipher.NetCipher;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 28.01.16.
|
||||
|
@ -40,10 +42,26 @@ public class Downloader implements org.schabi.newpipe.extractor.Downloader {
|
|||
* @param language the language (usually a 2-character code) to set as the preferred language
|
||||
* @return the contents of the specified text file*/
|
||||
public String download(String siteUrl, String language) throws IOException {
|
||||
Map<String, String> requestProperties = new HashMap<>();
|
||||
requestProperties.put("Accept-Language", language);
|
||||
return download(siteUrl, requestProperties);
|
||||
}
|
||||
|
||||
|
||||
/**Download the text file at the supplied URL as in download(String),
|
||||
* but set the HTTP header field "Accept-Language" to the supplied string.
|
||||
* @param siteUrl the URL of the text file to return the contents of
|
||||
* @param customProperties set request header properties
|
||||
* @return the contents of the specified text file
|
||||
* @throws IOException*/
|
||||
public String download(String siteUrl, Map<String, String> customProperties) throws IOException {
|
||||
URL url = new URL(siteUrl);
|
||||
HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
|
||||
//HttpsURLConnection con = NetCipher.getHttpsURLConnection(url);
|
||||
con.setRequestProperty("Accept-Language", language);
|
||||
Iterator it = customProperties.entrySet().iterator();
|
||||
while(it.hasNext()) {
|
||||
Map.Entry pair = (Map.Entry)it.next();
|
||||
con.setRequestProperty((String)pair.getKey(), (String)pair.getValue());
|
||||
}
|
||||
return dl(con);
|
||||
}
|
||||
|
||||
|
|
|
@ -79,16 +79,19 @@ public class ErrorActivity extends AppCompatActivity {
|
|||
public static final int GET_SUGGESTIONS = 2;
|
||||
public static final int SOMETHING_ELSE = 3;
|
||||
public static final int USER_REPORT = 4;
|
||||
public static final int LOAD_IMAGE = 5;
|
||||
public static final String SEARCHED_STRING = "searched";
|
||||
public static final String REQUESTED_STREAM_STRING = "requested stream";
|
||||
public static final String GET_SUGGESTIONS_STRING = "get suggestions";
|
||||
public static final String SOMETHING_ELSE_STRING = "something";
|
||||
public static final String USER_REPORT_STRING = "user report";
|
||||
public static final String LOAD_IMAGE_STRING = "load image";
|
||||
|
||||
|
||||
public static final String ERROR_EMAIL_ADDRESS = "crashreport@newpipe.schabi.org";
|
||||
public static final String ERROR_EMAIL_SUBJECT = "Exception in NewPipe " + BuildConfig.VERSION_NAME;
|
||||
|
||||
private List<Exception> errorList;
|
||||
private List<Throwable> errorList;
|
||||
private ErrorInfo errorInfo;
|
||||
private Class returnActivity;
|
||||
private String currentTimeStamp;
|
||||
|
@ -102,7 +105,7 @@ public class ErrorActivity extends AppCompatActivity {
|
|||
private TextView infoView;
|
||||
private TextView errorMessageView;
|
||||
|
||||
public static void reportError(final Context context, final List<Exception> el,
|
||||
public static void reportError(final Context context, final List<Throwable> el,
|
||||
final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) {
|
||||
|
||||
if (rootView != null) {
|
||||
|
@ -129,9 +132,9 @@ public class ErrorActivity extends AppCompatActivity {
|
|||
}
|
||||
}
|
||||
|
||||
public static void reportError(final Context context, final Exception e,
|
||||
public static void reportError(final Context context, final Throwable e,
|
||||
final Class returnAcitivty, View rootView, final ErrorInfo errorInfo) {
|
||||
List<Exception> el = null;
|
||||
List<Throwable> el = null;
|
||||
if(e != null) {
|
||||
el = new Vector<>();
|
||||
el.add(e);
|
||||
|
@ -140,10 +143,10 @@ public class ErrorActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
// async call
|
||||
public static void reportError(Handler handler, final Context context, final Exception e,
|
||||
public static void reportError(Handler handler, final Context context, final Throwable e,
|
||||
final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) {
|
||||
|
||||
List<Exception> el = null;
|
||||
List<Throwable> el = null;
|
||||
if(e != null) {
|
||||
el = new Vector<>();
|
||||
el.add(e);
|
||||
|
@ -152,7 +155,7 @@ public class ErrorActivity extends AppCompatActivity {
|
|||
}
|
||||
|
||||
// async call
|
||||
public static void reportError(Handler handler, final Context context, final List<Exception> el,
|
||||
public static void reportError(Handler handler, final Context context, final List<Throwable> el,
|
||||
final Class returnAcitivty, final View rootView, final ErrorInfo errorInfo) {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
|
@ -171,7 +174,7 @@ public class ErrorActivity extends AppCompatActivity {
|
|||
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||
actionBar.setTitle(R.string.error_report_title);
|
||||
actionBar.setDisplayShowTitleEnabled(true);
|
||||
} catch (Exception e) {
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "Error turing exception handling");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
@ -252,10 +255,10 @@ public class ErrorActivity extends AppCompatActivity {
|
|||
return sw.getBuffer().toString();
|
||||
}
|
||||
|
||||
private String formErrorText(List<Exception> el) {
|
||||
private String formErrorText(List<Throwable> el) {
|
||||
String text = "";
|
||||
if(el != null) {
|
||||
for (Exception e : el) {
|
||||
for (Throwable e : el) {
|
||||
text += "-------------------------------------\n"
|
||||
+ getStackTrace(e);
|
||||
}
|
||||
|
@ -273,7 +276,7 @@ public class ErrorActivity extends AppCompatActivity {
|
|||
returnActivity.isAssignableFrom(Activity.class)) {
|
||||
intent = new Intent(this, returnActivity);
|
||||
} else {
|
||||
intent = new Intent(this, VideoItemListActivity.class);
|
||||
intent = new Intent(this, MainActivity.class);
|
||||
}
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
NavUtils.navigateUpTo(this, intent);
|
||||
|
@ -313,7 +316,7 @@ public class ErrorActivity extends AppCompatActivity {
|
|||
|
||||
JSONArray exceptionArray = new JSONArray();
|
||||
if(errorList != null) {
|
||||
for (Exception e : errorList) {
|
||||
for (Throwable e : errorList) {
|
||||
exceptionArray.put(getStackTrace(e));
|
||||
}
|
||||
}
|
||||
|
@ -322,7 +325,7 @@ public class ErrorActivity extends AppCompatActivity {
|
|||
errorObject.put("user_comment", userCommentBox.getText().toString());
|
||||
|
||||
return errorObject.toString(3);
|
||||
} catch (Exception e) {
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "Error while erroring: Could not build json");
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
@ -342,6 +345,8 @@ public class ErrorActivity extends AppCompatActivity {
|
|||
return SOMETHING_ELSE_STRING;
|
||||
case USER_REPORT:
|
||||
return USER_REPORT_STRING;
|
||||
case LOAD_IMAGE:
|
||||
return LOAD_IMAGE_STRING;
|
||||
default:
|
||||
return "Your description is in another castle.";
|
||||
}
|
||||
|
@ -390,7 +395,7 @@ public class ErrorActivity extends AppCompatActivity {
|
|||
|
||||
ipRange = Parser.matchGroup1("([0-9]*\\.[0-9]*\\.)[0-9]*\\.[0-9]*", ip)
|
||||
+ "0.0";
|
||||
} catch(Exception e) {
|
||||
} catch(Throwable e) {
|
||||
Log.d(TAG, "Error while error: could not get iprange");
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package org.schabi.newpipe;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.graphics.Bitmap;
|
||||
import android.view.View;
|
||||
|
||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
||||
|
||||
import org.schabi.newpipe.ErrorActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 01.08.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
||||
* StreamInfoItemViewCreator.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 ImageErrorLoadingListener implements ImageLoadingListener {
|
||||
|
||||
private int serviceId = -1;
|
||||
private Activity activity = null;
|
||||
private View rootView = null;
|
||||
|
||||
public ImageErrorLoadingListener(Activity activity, View rootView, int serviceId) {
|
||||
this.activity = activity;
|
||||
this.serviceId= serviceId;
|
||||
this.rootView = rootView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingStarted(String imageUri, View view) {}
|
||||
|
||||
@Override
|
||||
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
||||
ErrorActivity.reportError(activity,
|
||||
failReason.getCause(), null, rootView,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
|
||||
ServiceList.getNameOfService(serviceId), imageUri,
|
||||
R.string.could_not_load_image));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingCancelled(String imageUri, View view) {}
|
||||
}
|
69
app/src/main/java/org/schabi/newpipe/MainActivity.java
Normal file
69
app/src/main/java/org/schabi/newpipe/MainActivity.java
Normal file
|
@ -0,0 +1,69 @@
|
|||
package org.schabi.newpipe;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.os.Bundle;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 02.08.16.
|
||||
*/
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
private Fragment mainFragment = null;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
mainFragment = getSupportFragmentManager()
|
||||
.findFragmentById(R.id.search_fragment);
|
||||
}
|
||||
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
|
||||
inflater.inflate(R.menu.main_menu, menu);
|
||||
|
||||
mainFragment.onCreateOptionsMenu(menu, inflater);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
|
||||
switch(id) {
|
||||
case android.R.id.home: {
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
NavUtils.navigateUpTo(this, intent);
|
||||
return true;
|
||||
}
|
||||
case R.id.action_settings: {
|
||||
Intent intent = new Intent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
case R.id.action_show_downloads: {
|
||||
Intent intent = new Intent(this, org.schabi.newpipe.download.MainActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return mainFragment.onOptionsItemSelected(item) ||
|
||||
super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,411 +0,0 @@
|
|||
package org.schabi.newpipe;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.media.AudioManager;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.SearchView;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.schabi.newpipe.extractor.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.SearchEngine;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
||||
* VideoItemListActivity.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 VideoItemListActivity extends AppCompatActivity
|
||||
implements VideoItemListFragment.Callbacks {
|
||||
|
||||
private static final String TAG = VideoItemListFragment.class.toString();
|
||||
|
||||
// arguments to give to this activity
|
||||
public static final String VIDEO_INFO_ITEMS = "video_info_items";
|
||||
|
||||
// savedInstanceBundle arguments
|
||||
private static final String QUERY = "query";
|
||||
private static final String STREAMING_SERVICE = "streaming_service";
|
||||
|
||||
// activity modes
|
||||
private static final int SEARCH_MODE = 0;
|
||||
private static final int PRESENT_VIDEOS_MODE = 1;
|
||||
|
||||
private int mode;
|
||||
private int currentStreamingServiceId = -1;
|
||||
private String searchQuery = "";
|
||||
|
||||
private VideoItemListFragment listFragment;
|
||||
private VideoItemDetailFragment videoFragment;
|
||||
private Menu menu;
|
||||
|
||||
private SuggestionListAdapter suggestionListAdapter;
|
||||
private SuggestionSearchRunnable suggestionSearchRunnable;
|
||||
private Thread searchThread;
|
||||
|
||||
private class SearchVideoQueryListener implements SearchView.OnQueryTextListener {
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
try {
|
||||
searchQuery = query;
|
||||
listFragment.search(query);
|
||||
|
||||
// hide virtual keyboard
|
||||
InputMethodManager inputManager =
|
||||
(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
try {
|
||||
//noinspection ConstantConditions
|
||||
inputManager.hideSoftInputFromWindow(
|
||||
getCurrentFocus().getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
|
||||
} catch(NullPointerException e) {
|
||||
Log.e(TAG, "Could not get widget with focus");
|
||||
Toast.makeText(VideoItemListActivity.this, "Could not get widget with focus",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
e.printStackTrace();
|
||||
}
|
||||
// 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
|
||||
getCurrentFocus().clearFocus();
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
View bg = findViewById(R.id.mainBG);
|
||||
bg.setVisibility(View.GONE);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
if(!newText.isEmpty()) {
|
||||
searchSuggestions(newText);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
private class SearchSuggestionListener implements SearchView.OnSuggestionListener{
|
||||
|
||||
private SearchView searchView;
|
||||
|
||||
private SearchSuggestionListener(SearchView searchView) {
|
||||
this.searchView = searchView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSuggestionSelect(int position) {
|
||||
String suggestion = suggestionListAdapter.getSuggestion(position);
|
||||
searchView.setQuery(suggestion,true);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSuggestionClick(int position) {
|
||||
String suggestion = suggestionListAdapter.getSuggestion(position);
|
||||
searchView.setQuery(suggestion,true);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private class SuggestionResultRunnable implements Runnable{
|
||||
|
||||
private List<String> suggestions;
|
||||
|
||||
private SuggestionResultRunnable(List<String> suggestions) {
|
||||
this.suggestions = suggestions;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
suggestionListAdapter.updateAdapter(suggestions);
|
||||
}
|
||||
}
|
||||
|
||||
private class SuggestionSearchRunnable implements Runnable{
|
||||
private final int serviceId;
|
||||
private final String query;
|
||||
final Handler h = new Handler();
|
||||
private Context context;
|
||||
private SuggestionSearchRunnable(int serviceId, String query) {
|
||||
this.serviceId = serviceId;
|
||||
this.query = query;
|
||||
context = VideoItemListActivity.this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
SearchEngine engine =
|
||||
ServiceList.getService(serviceId).getSearchEngineInstance(new Downloader());
|
||||
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
String searchLanguageKey = context.getString(R.string.search_language_key);
|
||||
String searchLanguage = sp.getString(searchLanguageKey,
|
||||
getString(R.string.default_language_value));
|
||||
List<String> suggestions = engine.suggestionList(query,searchLanguage,new Downloader());
|
||||
h.post(new SuggestionResultRunnable(suggestions));
|
||||
} catch (ExtractionException e) {
|
||||
ErrorActivity.reportError(h, VideoItemListActivity.this, e, null, findViewById(R.id.videoitem_list),
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
|
||||
ServiceList.getNameOfService(serviceId), query, R.string.parsing_error));
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
postNewErrorToast(h, R.string.network_error);
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportError(h, VideoItemListActivity.this, e, null, findViewById(R.id.videoitem_list),
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
|
||||
ServiceList.getNameOfService(serviceId), query, R.string.general_error));
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
|
||||
* device.
|
||||
*/
|
||||
private boolean mTwoPane;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
setTheme(R.style.AppTheme);
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_videoitem_list);
|
||||
StreamingService streamingService = null;
|
||||
|
||||
View bg = findViewById(R.id.mainBG);
|
||||
bg.setVisibility(View.VISIBLE);
|
||||
|
||||
try {
|
||||
//------ todo: remove this line when multiservice support is implemented ------
|
||||
currentStreamingServiceId = ServiceList.getIdOfService("Youtube");
|
||||
streamingService = ServiceList.getService(currentStreamingServiceId);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
ErrorActivity.reportError(VideoItemListActivity.this, e, null, findViewById(R.id.videoitem_list),
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
|
||||
ServiceList.getNameOfService(currentStreamingServiceId), "", R.string.general_error));
|
||||
}
|
||||
setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||
listFragment = (VideoItemListFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.videoitem_list);
|
||||
listFragment.setStreamingService(streamingService);
|
||||
|
||||
if(savedInstanceState != null
|
||||
&& mode != PRESENT_VIDEOS_MODE) {
|
||||
searchQuery = savedInstanceState.getString(QUERY);
|
||||
currentStreamingServiceId = savedInstanceState.getInt(STREAMING_SERVICE);
|
||||
if(!searchQuery.isEmpty()) {
|
||||
listFragment.search(searchQuery);
|
||||
}
|
||||
}
|
||||
|
||||
if (findViewById(R.id.videoitem_detail_container) != null) {
|
||||
// The detail container view will be present only in the
|
||||
// large-screen layouts (res/values-large and
|
||||
// res/values-sw600dp). If this view is present, then the
|
||||
// activity should be in two-pane mode.
|
||||
mTwoPane = true;
|
||||
|
||||
// In two-pane mode, list items should be given the
|
||||
// 'activated' state when touched.
|
||||
|
||||
((VideoItemListFragment) getSupportFragmentManager()
|
||||
.findFragmentById(R.id.videoitem_list))
|
||||
.setActivateOnItemClick(true);
|
||||
|
||||
SearchView searchView = (SearchView)findViewById(R.id.searchViewTablet);
|
||||
if(mode != PRESENT_VIDEOS_MODE) {
|
||||
// Somehow the seticonifiedbydefault property set by the layout xml is not working on
|
||||
// the support version on SearchView, so it needs to be set programmatically.
|
||||
searchView.setIconifiedByDefault(false);
|
||||
searchView.setIconified(false);
|
||||
if(!searchQuery.isEmpty()) {
|
||||
searchView.setQuery(searchQuery,false);
|
||||
}
|
||||
searchView.setOnQueryTextListener(new SearchVideoQueryListener());
|
||||
suggestionListAdapter = new SuggestionListAdapter(this);
|
||||
searchView.setSuggestionsAdapter(suggestionListAdapter);
|
||||
searchView.setOnSuggestionListener(new SearchSuggestionListener(searchView));
|
||||
} else {
|
||||
searchView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
PreferenceManager.setDefaultValues(this, R.xml.settings, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
App.checkStartTor(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback method from {@link VideoItemListFragment.Callbacks}
|
||||
* indicating that the item with the given ID was selected.
|
||||
*/
|
||||
@Override
|
||||
public void onItemSelected(String id) {
|
||||
VideoListAdapter listAdapter = (VideoListAdapter) ((VideoItemListFragment)
|
||||
getSupportFragmentManager()
|
||||
.findFragmentById(R.id.videoitem_list))
|
||||
.getListAdapter();
|
||||
String webpageUrl = listAdapter.getVideoList().get((int) Long.parseLong(id)).webpage_url;
|
||||
if (mTwoPane) {
|
||||
// In two-pane mode, show the detail view in this activity by
|
||||
// adding or replacing the detail fragment using a
|
||||
// fragment transaction.
|
||||
Bundle arguments = new Bundle();
|
||||
//arguments.putString(VideoItemDetailFragment.ARG_ITEM_ID, id);
|
||||
arguments.putString(VideoItemDetailFragment.VIDEO_URL, webpageUrl);
|
||||
arguments.putInt(VideoItemDetailFragment.STREAMING_SERVICE, currentStreamingServiceId);
|
||||
videoFragment = new VideoItemDetailFragment();
|
||||
videoFragment.setArguments(arguments);
|
||||
videoFragment.setOnInvokeCreateOptionsMenuListener(new VideoItemDetailFragment.OnInvokeCreateOptionsMenuListener() {
|
||||
@Override
|
||||
public void createOptionsMenu() {
|
||||
menu.clear();
|
||||
onCreateOptionsMenu(menu);
|
||||
}
|
||||
});
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
.replace(R.id.videoitem_detail_container, videoFragment)
|
||||
.commit();
|
||||
} else {
|
||||
// In single-pane mode, simply start the detail activity
|
||||
// for the selected item ID.
|
||||
Intent detailIntent = new Intent(this, VideoItemDetailActivity.class);
|
||||
//detailIntent.putExtra(VideoItemDetailFragment.ARG_ITEM_ID, id);
|
||||
detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, webpageUrl);
|
||||
detailIntent.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, currentStreamingServiceId);
|
||||
startActivity(detailIntent);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
super.onCreateOptionsMenu(menu);
|
||||
this.menu = menu;
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
if(mode != PRESENT_VIDEOS_MODE &&
|
||||
findViewById(R.id.videoitem_detail_container) == null) {
|
||||
inflater.inflate(R.menu.videoitem_list, menu);
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search);
|
||||
SearchView searchView = (SearchView) searchItem.getActionView();
|
||||
searchView.setFocusable(false);
|
||||
searchView.setOnQueryTextListener(
|
||||
new SearchVideoQueryListener());
|
||||
suggestionListAdapter = new SuggestionListAdapter(this);
|
||||
searchView.setSuggestionsAdapter(suggestionListAdapter);
|
||||
searchView.setOnSuggestionListener(new SearchSuggestionListener(searchView));
|
||||
if(!searchQuery.isEmpty()) {
|
||||
searchView.setQuery(searchQuery,false);
|
||||
searchView.setIconifiedByDefault(false);
|
||||
}
|
||||
} else if (videoFragment != null){
|
||||
videoFragment.onCreateOptionsMenu(menu, inflater);
|
||||
} else {
|
||||
inflater.inflate(R.menu.videoitem_two_pannel, menu);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
int id = item.getItemId();
|
||||
|
||||
switch(id) {
|
||||
case android.R.id.home: {
|
||||
Intent intent = new Intent(this, VideoItemListActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
NavUtils.navigateUpTo(this, intent);
|
||||
return true;
|
||||
}
|
||||
case R.id.action_settings: {
|
||||
Intent intent = new Intent(this, SettingsActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
case R.id.action_report_error: {
|
||||
ErrorActivity.reportError(VideoItemListActivity.this, new Vector<Exception>(),
|
||||
null, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.USER_REPORT,
|
||||
ServiceList.getNameOfService(currentStreamingServiceId),
|
||||
"user_report", R.string.user_report));
|
||||
return true;
|
||||
}
|
||||
case R.id.action_show_downloads: {
|
||||
Intent intent = new Intent(this, org.schabi.newpipe.download.MainActivity.class);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return videoFragment.onOptionsItemSelected(item) ||
|
||||
super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
/*
|
||||
if (mActivatedPosition != ListView.INVALID_POSITION) {
|
||||
// Serialize and persist the activated item position.
|
||||
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
|
||||
}
|
||||
*/
|
||||
outState.putString(QUERY, searchQuery);
|
||||
outState.putInt(STREAMING_SERVICE, currentStreamingServiceId);
|
||||
}
|
||||
|
||||
private void searchSuggestions(String query) {
|
||||
suggestionSearchRunnable =
|
||||
new SuggestionSearchRunnable(currentStreamingServiceId, query);
|
||||
searchThread = new Thread(suggestionSearchRunnable);
|
||||
searchThread.start();
|
||||
|
||||
}
|
||||
|
||||
private void postNewErrorToast(Handler h, final int stringResource) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(VideoItemListActivity.this, getString(stringResource),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,370 +0,0 @@
|
|||
package org.schabi.newpipe;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.AbsListView;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
import org.schabi.newpipe.extractor.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.SearchResult;
|
||||
import org.schabi.newpipe.extractor.StreamPreviewInfo;
|
||||
import org.schabi.newpipe.extractor.SearchEngine;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
|
||||
|
||||
/**
|
||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
||||
* VideoItemListFragment.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 VideoItemListFragment extends ListFragment {
|
||||
|
||||
private static final String TAG = VideoItemListFragment.class.toString();
|
||||
|
||||
private StreamingService streamingService;
|
||||
private VideoListAdapter videoListAdapter;
|
||||
|
||||
// activity modes
|
||||
private static final int SEARCH_MODE = 0;
|
||||
private static final int PRESENT_VIDEOS_MODE = 1;
|
||||
|
||||
private int mode;
|
||||
private String query = "";
|
||||
private int lastPage;
|
||||
|
||||
private Thread searchThread;
|
||||
private SearchRunnable searchRunnable;
|
||||
// used to track down if results posted by threads ar still valid
|
||||
private int currentRequestId = -1;
|
||||
private ListView list;
|
||||
|
||||
private View footer;
|
||||
|
||||
// used to suppress request for loading a new page while another page is already loading.
|
||||
private boolean loadingNextPage = true;
|
||||
|
||||
private class ResultRunnable implements Runnable {
|
||||
private final SearchResult result;
|
||||
private final int requestId;
|
||||
public ResultRunnable(SearchResult result, int requestId) {
|
||||
this.result = result;
|
||||
this.requestId = requestId;
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
updateListOnResult(result, requestId);
|
||||
if (android.os.Build.VERSION.SDK_INT >= 19) {
|
||||
getListView().removeFooterView(footer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SearchRunnable implements Runnable {
|
||||
public static final String YOUTUBE = "Youtube";
|
||||
private final SearchEngine engine;
|
||||
private final String query;
|
||||
private final int page;
|
||||
final Handler h = new Handler();
|
||||
private volatile boolean runs = true;
|
||||
private final int requestId;
|
||||
public SearchRunnable(SearchEngine engine, String query, int page, int requestId) {
|
||||
this.engine = engine;
|
||||
this.query = query;
|
||||
this.page = page;
|
||||
this.requestId = requestId;
|
||||
}
|
||||
void terminate() {
|
||||
runs = false;
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
SearchResult result = null;
|
||||
try {
|
||||
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getContext());
|
||||
String searchLanguageKey = getContext().getString(R.string.search_language_key);
|
||||
String searchLanguage = sp.getString(searchLanguageKey,
|
||||
getString(R.string.default_language_value));
|
||||
result = SearchResult
|
||||
.getSearchResult(engine, query, page, searchLanguage, new Downloader());
|
||||
|
||||
if(runs) {
|
||||
h.post(new ResultRunnable(result, requestId));
|
||||
}
|
||||
|
||||
// look for errors during extraction
|
||||
// soft errors:
|
||||
if(result != null &&
|
||||
!result.errors.isEmpty()) {
|
||||
Log.e(TAG, "OCCURRED ERRORS DURING SEARCH EXTRACTION:");
|
||||
for(Exception e : result.errors) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "------");
|
||||
}
|
||||
|
||||
Activity a = getActivity();
|
||||
View rootView = a.findViewById(R.id.videoitem_list);
|
||||
ErrorActivity.reportError(h, getActivity(), result.errors, null, rootView,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
|
||||
/* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.light_parsing_error));
|
||||
|
||||
}
|
||||
// hard errors:
|
||||
} catch(IOException e) {
|
||||
postNewNothingFoundToast(h, R.string.network_error);
|
||||
e.printStackTrace();
|
||||
} catch(SearchEngine.NothingFoundException e) {
|
||||
postNewErrorToast(h, e.getMessage());
|
||||
} catch(ExtractionException e) {
|
||||
ErrorActivity.reportError(h, getActivity(), e, null, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
|
||||
/* todo: this shoudl not be assigned static */
|
||||
YOUTUBE, query, R.string.parsing_error));
|
||||
//postNewErrorToast(h, R.string.parsing_error);
|
||||
e.printStackTrace();
|
||||
|
||||
} catch(Exception e) {
|
||||
ErrorActivity.reportError(h, getActivity(), e, null, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
|
||||
/* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.general_error));
|
||||
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void present(List<StreamPreviewInfo> videoList) {
|
||||
mode = PRESENT_VIDEOS_MODE;
|
||||
setListShown(true);
|
||||
getListView().smoothScrollToPosition(0);
|
||||
|
||||
updateList(videoList);
|
||||
}
|
||||
|
||||
public void search(String query) {
|
||||
mode = SEARCH_MODE;
|
||||
this.query = query;
|
||||
this.lastPage = 1;
|
||||
videoListAdapter.clearVideoList();
|
||||
setListShown(false);
|
||||
startSearch(query, lastPage);
|
||||
//todo: Somehow this command is not working on older devices,
|
||||
// although it was introduced with API level 8. Test this and find a solution.
|
||||
getListView().smoothScrollToPosition(0);
|
||||
}
|
||||
|
||||
private void nextPage() {
|
||||
loadingNextPage = true;
|
||||
lastPage++;
|
||||
Log.d(TAG, getString(R.string.search_page) + Integer.toString(lastPage));
|
||||
startSearch(query, lastPage);
|
||||
}
|
||||
|
||||
private void startSearch(String query, int page) {
|
||||
currentRequestId++;
|
||||
terminateThreads();
|
||||
searchRunnable = new SearchRunnable(streamingService.getSearchEngineInstance(new Downloader()),
|
||||
query, page, currentRequestId);
|
||||
searchThread = new Thread(searchRunnable);
|
||||
searchThread.start();
|
||||
}
|
||||
|
||||
public void setStreamingService(StreamingService streamingService) {
|
||||
this.streamingService = streamingService;
|
||||
}
|
||||
|
||||
private void updateListOnResult(SearchResult result, int requestId) {
|
||||
if(requestId == currentRequestId) {
|
||||
setListShown(true);
|
||||
updateList(result.resultList);
|
||||
if(!result.suggestion.isEmpty()) {
|
||||
Toast.makeText(getActivity(),
|
||||
String.format(getString(R.string.did_you_mean), result.suggestion),
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void updateList(List<StreamPreviewInfo> list) {
|
||||
try {
|
||||
videoListAdapter.addVideoList(list);
|
||||
terminateThreads();
|
||||
} catch(java.lang.IllegalStateException e) {
|
||||
Toast.makeText(getActivity(), "Trying to set value while activity doesn't exist anymore.",
|
||||
Toast.LENGTH_SHORT).show();
|
||||
Log.w(TAG, "Trying to set value while activity doesn't exist anymore.");
|
||||
} catch(Exception e) {
|
||||
Toast.makeText(getActivity(), getString(R.string.general_error),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
loadingNextPage = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void terminateThreads() {
|
||||
if(searchThread != null) {
|
||||
searchRunnable.terminate();
|
||||
// No need to join, since we don't really terminate the thread. We just demand
|
||||
// it to post its result runnable into the gui main loop.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The serialization (saved instance state) Bundle key representing the
|
||||
* activated item position. Only used on tablets.
|
||||
*/
|
||||
private static final String STATE_ACTIVATED_POSITION = "activated_position";
|
||||
|
||||
/**
|
||||
* The current activated item position. Only used on tablets.
|
||||
*/
|
||||
private int mActivatedPosition = ListView.INVALID_POSITION;
|
||||
|
||||
/**
|
||||
* A callback interface that all activities containing this fragment must
|
||||
* implement. This mechanism allows activities to be notified of item
|
||||
* selections.
|
||||
*/
|
||||
public interface Callbacks {
|
||||
/**
|
||||
* Callback for when an item has been selected.
|
||||
*/
|
||||
void onItemSelected(String id);
|
||||
}
|
||||
|
||||
private Callbacks mCallbacks;
|
||||
|
||||
@Override
|
||||
public void onViewCreated(View view, Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
list = getListView();
|
||||
videoListAdapter = new VideoListAdapter(getActivity(), this);
|
||||
footer = ((LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE))
|
||||
.inflate(R.layout.paginate_footer, null, false);
|
||||
|
||||
|
||||
setListAdapter(videoListAdapter);
|
||||
|
||||
// Restore the previously serialized activated item position.
|
||||
if (savedInstanceState != null
|
||||
|
||||
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
|
||||
setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
|
||||
}
|
||||
|
||||
|
||||
getListView().setOnScrollListener(new AbsListView.OnScrollListener() {
|
||||
long lastScrollDate;
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(AbsListView view, int scrollState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScroll(AbsListView view, int firstVisibleItem,
|
||||
int visibleItemCount, int totalItemCount) {
|
||||
if (mode != PRESENT_VIDEOS_MODE
|
||||
&& list.getChildAt(0) != null
|
||||
&& list.getLastVisiblePosition() == list.getAdapter().getCount() - 1
|
||||
&& list.getChildAt(list.getChildCount() - 1).getBottom() >= list.getHeight()) {
|
||||
long time = System.currentTimeMillis();
|
||||
if ((time - lastScrollDate) > 200
|
||||
&& !loadingNextPage) {
|
||||
lastScrollDate = time;
|
||||
getListView().addFooterView(footer);
|
||||
nextPage();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
|
||||
// Activities containing this fragment must implement its callbacks.
|
||||
if (!(context instanceof Callbacks)) {
|
||||
throw new IllegalStateException("Activity must implement fragment's callbacks.");
|
||||
}
|
||||
|
||||
mCallbacks = (Callbacks) context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(ListView listView, View view, int position, long id) {
|
||||
super.onListItemClick(listView, view, position, id);
|
||||
setActivatedPosition(position);
|
||||
mCallbacks.onItemSelected(Long.toString(id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on activate-on-click mode. When this mode is on, list items will be
|
||||
* given the 'activated' state when touched.
|
||||
*/
|
||||
public void setActivateOnItemClick(@SuppressWarnings("SameParameterValue") boolean activateOnItemClick) {
|
||||
// When setting CHOICE_MODE_SINGLE, ListView will automatically
|
||||
// give items the 'activated' state when touched.
|
||||
getListView().setChoiceMode(activateOnItemClick
|
||||
? ListView.CHOICE_MODE_SINGLE
|
||||
: ListView.CHOICE_MODE_NONE);
|
||||
}
|
||||
|
||||
private void setActivatedPosition(int position) {
|
||||
if (position == ListView.INVALID_POSITION) {
|
||||
getListView().setItemChecked(mActivatedPosition, false);
|
||||
} else {
|
||||
getListView().setItemChecked(position, true);
|
||||
}
|
||||
|
||||
mActivatedPosition = position;
|
||||
}
|
||||
|
||||
private void postNewErrorToast(Handler h, final String message) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setListShown(true);
|
||||
Toast.makeText(getActivity(), message,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void postNewNothingFoundToast(Handler h, final int stringResource) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
setListShown(true);
|
||||
Toast.makeText(getActivity(), getString(stringResource),
|
||||
Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
package org.schabi.newpipe;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
import android.widget.ListView;
|
||||
|
||||
import org.schabi.newpipe.extractor.StreamPreviewInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 11.08.15.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
||||
* VideoListAdapter.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 VideoListAdapter extends BaseAdapter {
|
||||
private final Context context;
|
||||
private final VideoInfoItemViewCreator viewCreator;
|
||||
private Vector<StreamPreviewInfo> videoList = new Vector<>();
|
||||
private final ListView listView;
|
||||
|
||||
public VideoListAdapter(Context context, VideoItemListFragment videoListFragment) {
|
||||
viewCreator = new VideoInfoItemViewCreator(LayoutInflater.from(context));
|
||||
this.listView = videoListFragment.getListView();
|
||||
this.listView.setDivider(null);
|
||||
this.listView.setDividerHeight(0);
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public void addVideoList(List<StreamPreviewInfo> videos) {
|
||||
videoList.addAll(videos);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void clearVideoList() {
|
||||
videoList = new Vector<>();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public Vector<StreamPreviewInfo> getVideoList() {
|
||||
return videoList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return videoList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getItem(int position) {
|
||||
return videoList.get(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View convertView, ViewGroup parent) {
|
||||
convertView = viewCreator.getViewFromVideoInfoItem(convertView, parent, videoList.get(position));
|
||||
|
||||
if(listView.isItemChecked(position)) {
|
||||
convertView.setBackgroundColor(ContextCompat.getColor(context,R.color.light_youtube_primary_color));
|
||||
} else {
|
||||
convertView.setBackgroundColor(0);
|
||||
}
|
||||
|
||||
return convertView;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.schabi.newpipe;
|
||||
package org.schabi.newpipe.detail;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
|
@ -11,8 +11,9 @@ import android.view.MenuInflater;
|
|||
import android.view.MenuItem;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.VideoStream;
|
||||
|
||||
import java.util.List;
|
|
@ -0,0 +1,204 @@
|
|||
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.Downloader;
|
||||
import org.schabi.newpipe.ErrorActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.ParsingException;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
import org.schabi.newpipe.extractor.StreamExtractor;
|
||||
import org.schabi.newpipe.extractor.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Created by the-scrabi on 02.08.16.
|
||||
*/
|
||||
|
||||
public class StreamInfoWorker {
|
||||
|
||||
private static final String TAG = StreamInfoWorker.class.toString();
|
||||
|
||||
public interface OnStreamInfoReceivedListener {
|
||||
void onReceive(StreamInfo info);
|
||||
void onError(int messageId);
|
||||
void onBlockedByGemaError();
|
||||
void onContentErrorWithMessage(int messageId);
|
||||
void onContentError();
|
||||
}
|
||||
|
||||
private class StreamExtractorRunnable implements Runnable {
|
||||
private final Handler h = new Handler();
|
||||
private StreamExtractor streamExtractor;
|
||||
private final int serviceId;
|
||||
private final String videoUrl;
|
||||
private Activity a;
|
||||
|
||||
public StreamExtractorRunnable(Activity a, String videoUrl, int serviceId) {
|
||||
this.serviceId = serviceId;
|
||||
this.videoUrl = videoUrl;
|
||||
this.a = a;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
StreamInfo streamInfo = null;
|
||||
StreamingService service = null;
|
||||
try {
|
||||
service = ServiceList.getService(serviceId);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
"", videoUrl, R.string.could_not_get_stream));
|
||||
return;
|
||||
}
|
||||
try {
|
||||
streamExtractor = service.getExtractorInstance(videoUrl, new Downloader());
|
||||
streamInfo = StreamInfo.getVideoInfo(streamExtractor, new Downloader());
|
||||
|
||||
final StreamInfo info = streamInfo;
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onStreamInfoReceivedListener.onReceive(info);
|
||||
}
|
||||
});
|
||||
|
||||
// look for errors during extraction
|
||||
// this if statement only covers extra information.
|
||||
// if these are not available or caused an error, they are just not available
|
||||
// but don't render the stream information unusalbe.
|
||||
if(streamInfo != null &&
|
||||
!streamInfo.errors.isEmpty()) {
|
||||
Log.e(TAG, "OCCURRED ERRORS DURING EXTRACTION:");
|
||||
for (Throwable e : streamInfo.errors) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "------");
|
||||
}
|
||||
|
||||
View rootView = a != null ? a.findViewById(R.id.video_item_detail) : null;
|
||||
ErrorActivity.reportError(h, a,
|
||||
streamInfo.errors, null, rootView,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().name, videoUrl, 0 /* no message for the user */));
|
||||
}
|
||||
|
||||
// These errors render the stream information unusable.
|
||||
} catch (IOException e) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onStreamInfoReceivedListener.onError(R.string.network_error);
|
||||
}
|
||||
});
|
||||
e.printStackTrace();
|
||||
}
|
||||
// custom service related exceptions
|
||||
catch (YoutubeStreamExtractor.DecryptException de) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onStreamInfoReceivedListener.onError(R.string.youtube_signature_decryption_error);
|
||||
}
|
||||
});
|
||||
de.printStackTrace();
|
||||
} catch (YoutubeStreamExtractor.GemaException ge) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onStreamInfoReceivedListener.onBlockedByGemaError();
|
||||
}
|
||||
});
|
||||
} catch(YoutubeStreamExtractor.LiveStreamException e) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onStreamInfoReceivedListener
|
||||
.onContentErrorWithMessage(R.string.live_streams_not_supported);
|
||||
}
|
||||
});
|
||||
}
|
||||
// ----------------------------------------
|
||||
catch(StreamExtractor.ContentNotAvailableException e) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onStreamInfoReceivedListener
|
||||
.onContentError();
|
||||
}
|
||||
});
|
||||
e.printStackTrace();
|
||||
} catch(StreamInfo.StreamExctractException e) {
|
||||
if(!streamInfo.errors.isEmpty()) {
|
||||
// !!! if this case ever kicks in someone gets kicked out !!!
|
||||
ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream));
|
||||
} else {
|
||||
ErrorActivity.reportError(h, a, streamInfo.errors, VideoItemDetailFragment.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream));
|
||||
}
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
a.finish();
|
||||
}
|
||||
});
|
||||
e.printStackTrace();
|
||||
} catch (ParsingException e) {
|
||||
ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().name, videoUrl, R.string.parsing_error));
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
a.finish();
|
||||
}
|
||||
});
|
||||
e.printStackTrace();
|
||||
} catch(Exception e) {
|
||||
ErrorActivity.reportError(h, a, e, VideoItemDetailFragment.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().name, videoUrl, R.string.general_error));
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
a.finish();
|
||||
}
|
||||
});
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static StreamInfoWorker streamInfoWorker = null;
|
||||
private StreamExtractorRunnable runnable = null;
|
||||
private OnStreamInfoReceivedListener onStreamInfoReceivedListener = null;
|
||||
|
||||
private StreamInfoWorker() {
|
||||
|
||||
}
|
||||
|
||||
public static StreamInfoWorker getInstance() {
|
||||
return streamInfoWorker == null ? (streamInfoWorker = new StreamInfoWorker()) : streamInfoWorker;
|
||||
}
|
||||
|
||||
public void search(int serviceId, String url, Activity a) {
|
||||
runnable = new StreamExtractorRunnable(a, url, serviceId);
|
||||
Thread thread = new Thread(runnable);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void setOnStreamInfoReceivedListener(
|
||||
OnStreamInfoReceivedListener onStreamInfoReceivedListener) {
|
||||
this.onStreamInfoReceivedListener = onStreamInfoReceivedListener;
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.schabi.newpipe;
|
||||
package org.schabi.newpipe.detail;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.media.AudioManager;
|
||||
|
@ -11,6 +11,9 @@ import android.view.Menu;
|
|||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.schabi.newpipe.App;
|
||||
import org.schabi.newpipe.MainActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
|
||||
|
@ -85,7 +88,7 @@ public class VideoItemDetailActivity extends AppCompatActivity {
|
|||
.show();
|
||||
}
|
||||
//arguments.putString(VideoItemDetailFragment.VIDEO_URL,
|
||||
// videoExtractor.getVideoUrl(videoExtractor.getVideoId(videoUrl)));//cleans URL
|
||||
// videoExtractor.getUrl(videoExtractor.getId(videoUrl)));//cleans URL
|
||||
arguments.putString(VideoItemDetailFragment.VIDEO_URL, videoUrl);
|
||||
|
||||
arguments.putBoolean(VideoItemDetailFragment.AUTO_PLAY,
|
||||
|
@ -138,7 +141,7 @@ public class VideoItemDetailActivity extends AppCompatActivity {
|
|||
|
||||
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
|
||||
|
||||
Intent intent = new Intent(this, VideoItemListActivity.class);
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
NavUtils.navigateUpTo(this, intent);
|
||||
return true;
|
|
@ -1,22 +1,21 @@
|
|||
package org.schabi.newpipe;
|
||||
package org.schabi.newpipe.detail;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Point;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.text.Html;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.util.Log;
|
||||
|
@ -27,38 +26,34 @@ import android.view.MotionEvent;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
import com.nostra13.universalimageloader.core.assist.FailReason;
|
||||
import com.nostra13.universalimageloader.core.listener.ImageLoadingListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Vector;
|
||||
|
||||
|
||||
import org.schabi.newpipe.ActivityCommunicator;
|
||||
import org.schabi.newpipe.ChannelActivity;
|
||||
import org.schabi.newpipe.ErrorActivity;
|
||||
import org.schabi.newpipe.ImageErrorLoadingListener;
|
||||
import org.schabi.newpipe.Localization;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.download.DownloadDialog;
|
||||
import org.schabi.newpipe.extractor.AudioStream;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.ParsingException;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
import org.schabi.newpipe.extractor.StreamExtractor;
|
||||
import org.schabi.newpipe.extractor.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.StreamPreviewInfo;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.VideoStream;
|
||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeStreamExtractor;
|
||||
import org.schabi.newpipe.info_list.InfoListAdapter;
|
||||
import org.schabi.newpipe.player.BackgroundPlayer;
|
||||
import org.schabi.newpipe.player.PlayVideoActivity;
|
||||
import org.schabi.newpipe.player.ExoPlayerActivity;
|
||||
|
@ -102,19 +97,20 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
private int streamingServiceId = -1;
|
||||
|
||||
private boolean autoPlayEnabled;
|
||||
private boolean showNextVideoItem;
|
||||
private Bitmap videoThumbnail;
|
||||
private boolean showNextStreamItem;
|
||||
|
||||
private View thumbnailWindowLayout;
|
||||
//this only remains due to downwards compatibility
|
||||
private FloatingActionButton playVideoButton;
|
||||
private final Point initialThumbnailPos = new Point(0, 0);
|
||||
|
||||
private View rootView = null;
|
||||
private Bitmap streamThumbnail = null;
|
||||
|
||||
private ImageLoader imageLoader = ImageLoader.getInstance();
|
||||
private DisplayImageOptions displayImageOptions =
|
||||
new DisplayImageOptions.Builder().cacheInMemory(true).build();
|
||||
|
||||
private InfoListAdapter similarStreamsAdapter = null;
|
||||
|
||||
public interface OnInvokeCreateOptionsMenuListener {
|
||||
void createOptionsMenu();
|
||||
|
@ -122,215 +118,57 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
|
||||
private OnInvokeCreateOptionsMenuListener onInvokeCreateOptionsMenuListener;
|
||||
|
||||
private class VideoExtractorRunnable implements Runnable {
|
||||
private final Handler h = new Handler();
|
||||
private StreamExtractor streamExtractor;
|
||||
private final StreamingService service;
|
||||
private final String videoUrl;
|
||||
|
||||
public VideoExtractorRunnable(String videoUrl, StreamingService service) {
|
||||
this.service = service;
|
||||
this.videoUrl = videoUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
StreamInfo streamInfo = null;
|
||||
try {
|
||||
streamExtractor = service.getExtractorInstance(videoUrl, new Downloader());
|
||||
streamInfo = StreamInfo.getVideoInfo(streamExtractor, new Downloader());
|
||||
|
||||
h.post(new VideoResultReturnedRunnable(streamInfo));
|
||||
|
||||
// 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 (Exception e : streamInfo.errors) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "------");
|
||||
}
|
||||
|
||||
Activity a = getActivity();
|
||||
View rootView = a != null ? a.findViewById(R.id.videoitem_detail) : null;
|
||||
ErrorActivity.reportError(h, getActivity(),
|
||||
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 (IOException e) {
|
||||
postNewErrorToast(h, R.string.network_error);
|
||||
e.printStackTrace();
|
||||
}
|
||||
// custom service related exceptions
|
||||
catch (YoutubeStreamExtractor.DecryptException de) {
|
||||
postNewErrorToast(h, R.string.youtube_signature_decryption_error);
|
||||
de.printStackTrace();
|
||||
} catch (YoutubeStreamExtractor.GemaException ge) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onErrorBlockedByGema();
|
||||
}
|
||||
});
|
||||
} catch(YoutubeStreamExtractor.LiveStreamException e) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onNotSpecifiedContentErrorWithMessage(R.string.live_streams_not_supported);
|
||||
}
|
||||
});
|
||||
}
|
||||
// ----------------------------------------
|
||||
catch(StreamExtractor.ContentNotAvailableException e) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
onNotSpecifiedContentError();
|
||||
}
|
||||
});
|
||||
e.printStackTrace();
|
||||
} catch(StreamInfo.StreamExctractException e) {
|
||||
if(!streamInfo.errors.isEmpty()) {
|
||||
// !!! if this case ever kicks in someone gets kicked out !!!
|
||||
ErrorActivity.reportError(h, getActivity(), e, VideoItemListActivity.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream));
|
||||
} else {
|
||||
ErrorActivity.reportError(h, getActivity(), streamInfo.errors, VideoItemListActivity.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().name, videoUrl, R.string.could_not_get_stream));
|
||||
}
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getActivity().finish();
|
||||
}
|
||||
});
|
||||
e.printStackTrace();
|
||||
} catch (ParsingException e) {
|
||||
ErrorActivity.reportError(h, getActivity(), e, VideoItemListActivity.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().name, videoUrl, R.string.parsing_error));
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getActivity().finish();
|
||||
}
|
||||
});
|
||||
e.printStackTrace();
|
||||
} catch(Exception e) {
|
||||
ErrorActivity.reportError(h, getActivity(), e, VideoItemListActivity.class, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.REQUESTED_STREAM,
|
||||
service.getServiceInfo().name, videoUrl, R.string.general_error));
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getActivity().finish();
|
||||
}
|
||||
});
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class VideoResultReturnedRunnable implements Runnable {
|
||||
private final StreamInfo streamInfo;
|
||||
public VideoResultReturnedRunnable(StreamInfo streamInfo) {
|
||||
this.streamInfo = streamInfo;
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
Activity a = getActivity();
|
||||
if(a != null) {
|
||||
boolean showAgeRestrictedContent = PreferenceManager.getDefaultSharedPreferences(a)
|
||||
.getBoolean(activity.getString(R.string.show_age_restricted_content), false);
|
||||
if (streamInfo.age_limit == 0 || showAgeRestrictedContent) {
|
||||
updateInfo(streamInfo);
|
||||
} else {
|
||||
onNotSpecifiedContentErrorWithMessage(R.string.video_is_age_restricted);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class ThumbnailLoadingListener implements ImageLoadingListener {
|
||||
@Override
|
||||
public void onLoadingStarted(String imageUri, View view) {}
|
||||
|
||||
@Override
|
||||
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
||||
if(getContext() != null) {
|
||||
Toast.makeText(VideoItemDetailFragment.this.getActivity(),
|
||||
R.string.could_not_load_thumbnails, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
failReason.getCause().printStackTrace();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {}
|
||||
|
||||
@Override
|
||||
public void onLoadingCancelled(String imageUri, View view) {}
|
||||
}
|
||||
|
||||
private void updateInfo(final StreamInfo info) {
|
||||
try {
|
||||
Context c = getContext();
|
||||
VideoInfoItemViewCreator videoItemViewCreator =
|
||||
new VideoInfoItemViewCreator(LayoutInflater.from(getActivity()));
|
||||
Activity a = getActivity();
|
||||
|
||||
RelativeLayout textContentLayout =
|
||||
(RelativeLayout) activity.findViewById(R.id.detailTextContentLayout);
|
||||
(RelativeLayout) activity.findViewById(R.id.detail_text_content_layout);
|
||||
final TextView videoTitleView =
|
||||
(TextView) activity.findViewById(R.id.detailVideoTitleView);
|
||||
TextView uploaderView = (TextView) activity.findViewById(R.id.detailUploaderView);
|
||||
TextView viewCountView = (TextView) activity.findViewById(R.id.detailViewCountView);
|
||||
TextView thumbsUpView = (TextView) activity.findViewById(R.id.detailThumbsUpCountView);
|
||||
(TextView) activity.findViewById(R.id.detail_video_title_view);
|
||||
TextView uploaderView = (TextView) activity.findViewById(R.id.detail_uploader_view);
|
||||
TextView viewCountView = (TextView) activity.findViewById(R.id.detail_view_count_view);
|
||||
TextView thumbsUpView = (TextView) activity.findViewById(R.id.detail_thumbs_up_count_view);
|
||||
TextView thumbsDownView =
|
||||
(TextView) activity.findViewById(R.id.detailThumbsDownCountView);
|
||||
TextView uploadDateView = (TextView) activity.findViewById(R.id.detailUploadDateView);
|
||||
TextView descriptionView = (TextView) activity.findViewById(R.id.detailDescriptionView);
|
||||
FrameLayout nextVideoFrame =
|
||||
(FrameLayout) activity.findViewById(R.id.detailNextVideoFrame);
|
||||
(TextView) activity.findViewById(R.id.detail_thumbs_down_count_view);
|
||||
TextView uploadDateView = (TextView) activity.findViewById(R.id.detail_upload_date_view);
|
||||
TextView descriptionView = (TextView) activity.findViewById(R.id.detail_description_view);
|
||||
RecyclerView nextStreamView =
|
||||
(RecyclerView) activity.findViewById(R.id.detail_next_stream_content);
|
||||
RelativeLayout nextVideoRootFrame =
|
||||
(RelativeLayout) activity.findViewById(R.id.detailNextVideoRootLayout);
|
||||
Button nextVideoButton = (Button) activity.findViewById(R.id.detailNextVideoButton);
|
||||
TextView similarTitle = (TextView) activity.findViewById(R.id.detailSimilarTitle);
|
||||
(RelativeLayout) activity.findViewById(R.id.detail_next_stream_root_layout);
|
||||
TextView similarTitle = (TextView) activity.findViewById(R.id.detail_similar_title);
|
||||
Button backgroundButton = (Button)
|
||||
activity.findViewById(R.id.detailVideoThumbnailWindowBackgroundButton);
|
||||
activity.findViewById(R.id.detail_stream_thumbnail_window_background_button);
|
||||
View topView = activity.findViewById(R.id.detailTopView);
|
||||
View nextVideoView = null;
|
||||
if(info.next_video != null) {
|
||||
nextVideoView = videoItemViewCreator
|
||||
.getViewFromVideoInfoItem(null, nextVideoFrame, info.next_video);
|
||||
} else {
|
||||
activity.findViewById(R.id.detailNextVidButtonAndContentLayout).setVisibility(View.GONE);
|
||||
activity.findViewById(R.id.detailNextVideoTitle).setVisibility(View.GONE);
|
||||
activity.findViewById(R.id.detailNextVideoButton).setVisibility(View.GONE);
|
||||
}
|
||||
Button channelButton = (Button) activity.findViewById(R.id.channel_button);
|
||||
|
||||
progressBar.setVisibility(View.GONE);
|
||||
if(nextVideoView != null) {
|
||||
nextVideoFrame.addView(nextVideoView);
|
||||
if(info.next_video != null) {
|
||||
InfoListAdapter adapter = new InfoListAdapter(a, rootView);
|
||||
nextStreamView.setAdapter(adapter);
|
||||
nextStreamView.setLayoutManager(new LinearLayoutManager(a));
|
||||
adapter.setOnItemSelectedListener(new InfoListAdapter.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void selected(String url) {
|
||||
openStreamUrl(url);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
nextStreamView.setVisibility(View.GONE);
|
||||
activity.findViewById(R.id.detail_similar_title).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
initThumbnailViews(info, nextVideoFrame);
|
||||
|
||||
textContentLayout.setVisibility(View.VISIBLE);
|
||||
if (android.os.Build.VERSION.SDK_INT < 18) {
|
||||
playVideoButton.setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
ImageView playArrowView = (ImageView) activity.findViewById(R.id.playArrowView);
|
||||
ImageView playArrowView = (ImageView) activity.findViewById(R.id.play_arrow_view);
|
||||
playArrowView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
if (!showNextVideoItem) {
|
||||
if (!showNextStreamItem) {
|
||||
nextVideoRootFrame.setVisibility(View.GONE);
|
||||
similarTitle.setVisibility(View.GONE);
|
||||
}
|
||||
|
@ -341,7 +179,7 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == android.view.MotionEvent.ACTION_UP) {
|
||||
ImageView arrow = (ImageView) activity.findViewById(R.id.toggleDescriptionView);
|
||||
ImageView arrow = (ImageView) activity.findViewById(R.id.toggle_description_view);
|
||||
View extra = activity.findViewById(R.id.detailExtraView);
|
||||
if (extra.getVisibility() == View.VISIBLE) {
|
||||
extra.setVisibility(View.GONE);
|
||||
|
@ -361,29 +199,29 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
if(!info.uploader.isEmpty()) {
|
||||
uploaderView.setText(info.uploader);
|
||||
} else {
|
||||
activity.findViewById(R.id.detailUploaderWrapView).setVisibility(View.GONE);
|
||||
activity.findViewById(R.id.detail_uploader_view).setVisibility(View.GONE);
|
||||
}
|
||||
if(info.view_count >= 0) {
|
||||
viewCountView.setText(Localization.localizeViewCount(info.view_count, c));
|
||||
viewCountView.setText(Localization.localizeViewCount(info.view_count, a));
|
||||
} else {
|
||||
viewCountView.setVisibility(View.GONE);
|
||||
}
|
||||
if(info.dislike_count >= 0) {
|
||||
thumbsDownView.setText(Localization.localizeNumber(info.dislike_count, c));
|
||||
thumbsDownView.setText(Localization.localizeNumber(info.dislike_count, a));
|
||||
} else {
|
||||
thumbsDownView.setVisibility(View.INVISIBLE);
|
||||
activity.findViewById(R.id.detailThumbsDownImgView).setVisibility(View.GONE);
|
||||
activity.findViewById(R.id.detail_thumbs_down_count_view).setVisibility(View.GONE);
|
||||
}
|
||||
if(info.like_count >= 0) {
|
||||
thumbsUpView.setText(Localization.localizeNumber(info.like_count, c));
|
||||
thumbsUpView.setText(Localization.localizeNumber(info.like_count, a));
|
||||
} else {
|
||||
thumbsUpView.setVisibility(View.GONE);
|
||||
activity.findViewById(R.id.detailThumbsUpImgView).setVisibility(View.GONE);
|
||||
activity.findViewById(R.id.detail_thumbs_up_img_view).setVisibility(View.GONE);
|
||||
thumbsDownView.setVisibility(View.GONE);
|
||||
activity.findViewById(R.id.detailThumbsDownImgView).setVisibility(View.GONE);
|
||||
activity.findViewById(R.id.detail_thumbs_down_img_view).setVisibility(View.GONE);
|
||||
}
|
||||
if(!info.upload_date.isEmpty()) {
|
||||
uploadDateView.setText(Localization.localizeDate(info.upload_date, c));
|
||||
uploadDateView.setText(Localization.localizeDate(info.upload_date, a));
|
||||
} else {
|
||||
uploadDateView.setVisibility(View.GONE);
|
||||
}
|
||||
|
@ -403,26 +241,17 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
}
|
||||
}
|
||||
|
||||
nextVideoButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Intent detailIntent =
|
||||
new Intent(getActivity(), VideoItemDetailActivity.class);
|
||||
/*detailIntent.putExtra(
|
||||
VideoItemDetailFragment.ARG_ITEM_ID, currentVideoInfo.nextVideo.id); */
|
||||
detailIntent.putExtra(
|
||||
VideoItemDetailFragment.VIDEO_URL, info.next_video.webpage_url);
|
||||
detailIntent.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
|
||||
startActivity(detailIntent);
|
||||
}
|
||||
});
|
||||
textContentLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
if(info.related_videos != null && !info.related_videos.isEmpty()) {
|
||||
initSimilarVideos(info, videoItemViewCreator);
|
||||
if(info.next_video == null) {
|
||||
activity.findViewById(R.id.detail_next_stream_title).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
if(info.related_streams != null && !info.related_streams.isEmpty()) {
|
||||
initSimilarVideos(info);
|
||||
} else {
|
||||
activity.findViewById(R.id.detailSimilarTitle).setVisibility(View.GONE);
|
||||
activity.findViewById(R.id.similarVideosView).setVisibility(View.GONE);
|
||||
activity.findViewById(R.id.detail_similar_title).setVisibility(View.GONE);
|
||||
activity.findViewById(R.id.similar_streams_view).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
setupActionBarHandler(info);
|
||||
|
@ -447,18 +276,32 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
}
|
||||
});
|
||||
|
||||
if(info.channel_url != null && info.channel_url != "") {
|
||||
channelButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent i = new Intent(activity, ChannelActivity.class);
|
||||
i.putExtra(ChannelActivity.CHANNEL_URL, info.channel_url);
|
||||
i.putExtra(ChannelActivity.SERVICE_ID, info.service_id);
|
||||
startActivity(i);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
channelButton.setVisibility(Button.GONE);
|
||||
}
|
||||
|
||||
initThumbnailViews(info);
|
||||
|
||||
} catch (java.lang.NullPointerException e) {
|
||||
Log.w(TAG, "updateInfo(): Fragment closed before thread ended work... or else");
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void initThumbnailViews(StreamInfo info, View nextVideoFrame) {
|
||||
ImageView videoThumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView);
|
||||
private void initThumbnailViews(final StreamInfo info) {
|
||||
ImageView videoThumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view);
|
||||
ImageView uploaderThumb
|
||||
= (ImageView) activity.findViewById(R.id.detailUploaderThumbnailView);
|
||||
ImageView nextVideoThumb =
|
||||
(ImageView) nextVideoFrame.findViewById(R.id.itemThumbnailView);
|
||||
= (ImageView) activity.findViewById(R.id.detail_uploader_thumbnail_view);
|
||||
|
||||
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
|
||||
imageLoader.displayImage(info.thumbnail_url, videoThumbnailView,
|
||||
|
@ -469,14 +312,16 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
|
||||
@Override
|
||||
public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
|
||||
Toast.makeText(VideoItemDetailFragment.this.getActivity(),
|
||||
R.string.could_not_load_thumbnails, Toast.LENGTH_LONG).show();
|
||||
failReason.getCause().printStackTrace();
|
||||
ErrorActivity.reportError(getActivity(),
|
||||
failReason.getCause(), null, rootView,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.LOAD_IMAGE,
|
||||
ServiceList.getNameOfService(info.service_id), imageUri,
|
||||
R.string.could_not_load_thumbnails));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
|
||||
videoThumbnail = loadedImage;
|
||||
streamThumbnail = loadedImage;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -488,11 +333,8 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
}
|
||||
if(info.uploader_thumbnail_url != null && !info.uploader_thumbnail_url.isEmpty()) {
|
||||
imageLoader.displayImage(info.uploader_thumbnail_url,
|
||||
uploaderThumb, displayImageOptions, new ThumbnailLoadingListener());
|
||||
}
|
||||
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty() && info.next_video != null) {
|
||||
imageLoader.displayImage(info.next_video.thumbnail_url,
|
||||
nextVideoThumb, displayImageOptions, new ThumbnailLoadingListener());
|
||||
uploaderThumb, displayImageOptions,
|
||||
new ImageErrorLoadingListener(activity, rootView, info.service_id));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -602,9 +444,9 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
info.audio_streams.get(getPreferredAudioStreamId(info));
|
||||
if (!useExternalAudioPlayer && android.os.Build.VERSION.SDK_INT >= 18) {
|
||||
//internal music player: explicit intent
|
||||
if (!BackgroundPlayer.isRunning && videoThumbnail != null) {
|
||||
if (!BackgroundPlayer.isRunning && streamThumbnail != null) {
|
||||
ActivityCommunicator.getCommunicator()
|
||||
.backgroundPlayerThumbnail = videoThumbnail;
|
||||
.backgroundPlayerThumbnail = streamThumbnail;
|
||||
intent = new Intent(activity, BackgroundPlayer.class);
|
||||
|
||||
intent.setAction(Intent.ACTION_VIEW);
|
||||
|
@ -684,47 +526,14 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
return 0;
|
||||
}
|
||||
|
||||
private void initSimilarVideos(final StreamInfo info, VideoInfoItemViewCreator videoItemViewCreator) {
|
||||
LinearLayout similarLayout = (LinearLayout) activity.findViewById(R.id.similarVideosView);
|
||||
ArrayList<StreamPreviewInfo> similar = new ArrayList<>(info.related_videos);
|
||||
for (final StreamPreviewInfo item : similar) {
|
||||
View similarView = videoItemViewCreator
|
||||
.getViewFromVideoInfoItem(null, similarLayout, item);
|
||||
|
||||
similarView.setClickable(true);
|
||||
similarView.setFocusable(true);
|
||||
int[] attrs = new int[]{R.attr.selectableItemBackground};
|
||||
TypedArray typedArray = activity.obtainStyledAttributes(attrs);
|
||||
int backgroundResource = typedArray.getResourceId(0, 0);
|
||||
similarView.setBackgroundResource(backgroundResource);
|
||||
typedArray.recycle();
|
||||
|
||||
similarView.setOnTouchListener(new View.OnTouchListener() {
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
if (event.getAction() == MotionEvent.ACTION_UP) {
|
||||
Intent detailIntent = new Intent(activity, VideoItemDetailActivity.class);
|
||||
detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, item.webpage_url);
|
||||
detailIntent.putExtra(
|
||||
VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
|
||||
startActivity(detailIntent);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
similarLayout.addView(similarView);
|
||||
ImageView rthumb = (ImageView)similarView.findViewById(R.id.itemThumbnailView);
|
||||
imageLoader.displayImage(item.thumbnail_url, rthumb,
|
||||
displayImageOptions, new ThumbnailLoadingListener());
|
||||
}
|
||||
private void initSimilarVideos(final StreamInfo info) {
|
||||
similarStreamsAdapter.addStreamItemList(info.related_streams);
|
||||
}
|
||||
|
||||
private void onErrorBlockedByGema() {
|
||||
Button backgroundButton = (Button)
|
||||
activity.findViewById(R.id.detailVideoThumbnailWindowBackgroundButton);
|
||||
ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView);
|
||||
activity.findViewById(R.id.detail_stream_thumbnail_window_background_button);
|
||||
ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view);
|
||||
|
||||
progressBar.setVisibility(View.GONE);
|
||||
thumbnailView.setImageBitmap(BitmapFactory.decodeResource(
|
||||
|
@ -744,7 +553,7 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
}
|
||||
|
||||
private void onNotSpecifiedContentError() {
|
||||
ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView);
|
||||
ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
thumbnailView.setImageBitmap(BitmapFactory.decodeResource(
|
||||
getResources(), R.drawable.not_available_monkey));
|
||||
|
@ -753,7 +562,7 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
}
|
||||
|
||||
private void onNotSpecifiedContentErrorWithMessage(int resourceId) {
|
||||
ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView);
|
||||
ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view);
|
||||
progressBar.setVisibility(View.GONE);
|
||||
thumbnailView.setImageBitmap(BitmapFactory.decodeResource(
|
||||
getResources(), R.drawable.not_available_monkey));
|
||||
|
@ -779,16 +588,44 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
activity = (AppCompatActivity) getActivity();
|
||||
showNextVideoItem = PreferenceManager.getDefaultSharedPreferences(getActivity())
|
||||
showNextStreamItem = PreferenceManager.getDefaultSharedPreferences(getActivity())
|
||||
.getBoolean(activity.getString(R.string.show_next_video_key), true);
|
||||
|
||||
|
||||
StreamInfoWorker siw = StreamInfoWorker.getInstance();
|
||||
siw.setOnStreamInfoReceivedListener(new StreamInfoWorker.OnStreamInfoReceivedListener() {
|
||||
@Override
|
||||
public void onReceive(StreamInfo info) {
|
||||
updateInfo(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(int messageId) {
|
||||
postNewErrorToast(messageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockedByGemaError() {
|
||||
onErrorBlockedByGema();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContentErrorWithMessage(int messageId) {
|
||||
onNotSpecifiedContentErrorWithMessage(messageId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContentError() {
|
||||
onNotSpecifiedContentError();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View rootView = inflater.inflate(R.layout.fragment_videoitem_detail, container, false);
|
||||
progressBar = (ProgressBar) rootView.findViewById(R.id.detailProgressBar);
|
||||
rootView = inflater.inflate(R.layout.fragment_videoitem_detail, container, false);
|
||||
progressBar = (ProgressBar) rootView.findViewById(R.id.detail_progress_bar);
|
||||
|
||||
actionBarHandler = new ActionBarHandler(activity);
|
||||
actionBarHandler.setupNavMenu(activity);
|
||||
|
@ -804,30 +641,25 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
super.onActivityCreated(savedInstanceBundle);
|
||||
Activity a = getActivity();
|
||||
if (android.os.Build.VERSION.SDK_INT < 18) {
|
||||
playVideoButton = (FloatingActionButton) a.findViewById(R.id.playVideoButton);
|
||||
playVideoButton = (FloatingActionButton) a.findViewById(R.id.play_video_button);
|
||||
}
|
||||
thumbnailWindowLayout = a.findViewById(R.id.detailVideoThumbnailWindowLayout);
|
||||
thumbnailWindowLayout = a.findViewById(R.id.detail_stream_thumbnail_window_layout);
|
||||
Button backgroundButton = (Button)
|
||||
a.findViewById(R.id.detailVideoThumbnailWindowBackgroundButton);
|
||||
a.findViewById(R.id.detail_stream_thumbnail_window_background_button);
|
||||
|
||||
// Sometimes when this fragment is not visible it still gets initiated
|
||||
// then we must not try to access objects of this fragment.
|
||||
// Otherwise the applications would crash.
|
||||
if(backgroundButton != null) {
|
||||
try {
|
||||
streamingServiceId = getArguments().getInt(STREAMING_SERVICE);
|
||||
StreamingService streamingService = ServiceList.getService(streamingServiceId);
|
||||
Thread videoExtractorThread = new Thread(new VideoExtractorRunnable(
|
||||
getArguments().getString(VIDEO_URL), streamingService));
|
||||
streamingServiceId = getArguments().getInt(STREAMING_SERVICE);
|
||||
String videoUrl = getArguments().getString(VIDEO_URL);
|
||||
StreamInfoWorker siw = StreamInfoWorker.getInstance();
|
||||
siw.search(streamingServiceId, videoUrl, getActivity());
|
||||
|
||||
autoPlayEnabled = getArguments().getBoolean(AUTO_PLAY);
|
||||
videoExtractorThread.start();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
autoPlayEnabled = getArguments().getBoolean(AUTO_PLAY);
|
||||
|
||||
if(Build.VERSION.SDK_INT >= 18) {
|
||||
ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detailThumbnailView);
|
||||
ImageView thumbnailView = (ImageView) activity.findViewById(R.id.detail_thumbnail_view);
|
||||
thumbnailView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
|
||||
// This is used to synchronize the thumbnailWindowButton and the playVideoButton
|
||||
// inside the ScrollView with the actual size of the thumbnail.
|
||||
|
@ -849,6 +681,17 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
similarStreamsAdapter = new InfoListAdapter(getActivity(), rootView);
|
||||
RecyclerView rv = (RecyclerView) getActivity().findViewById(R.id.similar_streams_view);
|
||||
rv.setLayoutManager(new LinearLayoutManager(getActivity()));
|
||||
rv.setAdapter(similarStreamsAdapter);
|
||||
similarStreamsAdapter.setOnItemSelectedListener(new InfoListAdapter.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void selected(String url) {
|
||||
openStreamUrl(url);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -946,23 +789,16 @@ public class VideoItemDetailFragment extends Fragment {
|
|||
this.onInvokeCreateOptionsMenuListener = listener;
|
||||
}
|
||||
|
||||
private void postNewErrorToast(Handler h, final int stringResource) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(VideoItemDetailFragment.this.getActivity(),
|
||||
stringResource, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
private void postNewErrorToast(final int stringResource) {
|
||||
Toast.makeText(VideoItemDetailFragment.this.getActivity(),
|
||||
stringResource, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
private void postNewErrorToast(Handler h, final String message) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(VideoItemDetailFragment.this.getActivity(),
|
||||
message, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
});
|
||||
private void openStreamUrl(String url) {
|
||||
Intent detailIntent = new Intent(activity, VideoItemDetailActivity.class);
|
||||
detailIntent.putExtra(VideoItemDetailFragment.VIDEO_URL, url);
|
||||
detailIntent.putExtra(
|
||||
VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
|
||||
activity.startActivity(detailIntent);
|
||||
}
|
||||
}
|
|
@ -25,7 +25,6 @@ import android.widget.SeekBar;
|
|||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.App;
|
||||
import org.schabi.newpipe.NewPipeSettings;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
import java.io.File;
|
||||
|
|
|
@ -8,14 +8,12 @@ import android.content.Context;
|
|||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.support.v4.app.NavUtils;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
|
@ -23,25 +21,16 @@ import android.view.MenuItem;
|
|||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.SeekBar;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.support.v7.widget.SearchView;
|
||||
|
||||
import org.schabi.newpipe.ErrorActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.SettingsActivity;
|
||||
import org.schabi.newpipe.VideoItemDetailActivity;
|
||||
import org.schabi.newpipe.VideoItemListActivity;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
import org.schabi.newpipe.settings.SettingsActivity;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.Vector;
|
||||
|
||||
import us.shandian.giga.get.DownloadManager;
|
||||
|
@ -257,7 +246,7 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
|
|||
|
||||
switch (id) {
|
||||
case android.R.id.home: {
|
||||
Intent intent = new Intent(this, VideoItemListActivity.class);
|
||||
Intent intent = new Intent(this, org.schabi.newpipe.MainActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
NavUtils.navigateUpTo(this, intent);
|
||||
return true;
|
||||
|
@ -268,7 +257,7 @@ public class MainActivity extends AppCompatActivity implements AdapterView.OnIte
|
|||
return true;
|
||||
}
|
||||
case R.id.action_report_error: {
|
||||
ErrorActivity.reportError(MainActivity.this, new Vector<Exception>(),
|
||||
ErrorActivity.reportError(MainActivity.this, new Vector<Throwable>(),
|
||||
null, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.USER_REPORT,
|
||||
null,
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package org.schabi.newpipe.extractor;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 25.07.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* ChannelExtractor.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 abstract class ChannelExtractor {
|
||||
private int serviceId;
|
||||
private String url;
|
||||
private UrlIdHandler urlIdHandler;
|
||||
private Downloader downloader;
|
||||
private StreamPreviewInfoCollector previewInfoCollector;
|
||||
|
||||
public ChannelExtractor(UrlIdHandler urlIdHandler, String url, int page, Downloader dl, int serviceId)
|
||||
throws ExtractionException, IOException {
|
||||
this.serviceId = serviceId;
|
||||
this.urlIdHandler = urlIdHandler;
|
||||
previewInfoCollector = new StreamPreviewInfoCollector(urlIdHandler, serviceId);
|
||||
}
|
||||
|
||||
public String getUrl() { return url; }
|
||||
public UrlIdHandler getUrlIdHandler() { return urlIdHandler; }
|
||||
public Downloader getDownloader() { return downloader; }
|
||||
public StreamPreviewInfoCollector getStreamPreviewInfoCollector() {
|
||||
return previewInfoCollector;
|
||||
}
|
||||
|
||||
public abstract String getChannelName() throws ParsingException;
|
||||
public abstract String getAvatarUrl() throws ParsingException;
|
||||
public abstract String getBannerUrl() throws ParsingException;
|
||||
public abstract String getFeedUrl() throws ParsingException;
|
||||
public abstract StreamPreviewInfoCollector getStreams() throws ParsingException;
|
||||
public abstract boolean hasNextPage() throws ParsingException;
|
||||
public int getServiceId() {
|
||||
return serviceId;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package org.schabi.newpipe.extractor;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 31.07.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* ChannelInfo.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 ChannelInfo {
|
||||
|
||||
|
||||
public void addException(Exception e) {
|
||||
errors.add(e);
|
||||
}
|
||||
|
||||
public static ChannelInfo getInfo(ChannelExtractor extractor, Downloader dl)
|
||||
throws ParsingException {
|
||||
ChannelInfo info = new ChannelInfo();
|
||||
|
||||
// importand data
|
||||
info.service_id = extractor.getServiceId();
|
||||
info.channel_name = extractor.getChannelName();
|
||||
info.hasNextPage = extractor.hasNextPage();
|
||||
|
||||
try {
|
||||
info.avatar_url = extractor.getAvatarUrl();
|
||||
} catch (Exception e) {
|
||||
info.errors.add(e);
|
||||
}
|
||||
try {
|
||||
info.banner_url = extractor.getBannerUrl();
|
||||
} catch (Exception e) {
|
||||
info.errors.add(e);
|
||||
}
|
||||
try {
|
||||
info.feed_url = extractor.getFeedUrl();
|
||||
} catch(Exception e) {
|
||||
info.errors.add(e);
|
||||
}
|
||||
try {
|
||||
StreamPreviewInfoCollector c = extractor.getStreams();
|
||||
info.related_streams = c.getItemList();
|
||||
info.errors.addAll(c.getErrors());
|
||||
} catch(Exception e) {
|
||||
info.errors.add(e);
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
public int service_id = -1;
|
||||
public String channel_name = "";
|
||||
public String avatar_url = "";
|
||||
public String banner_url = "";
|
||||
public String feed_url = "";
|
||||
public List<StreamPreviewInfo> related_streams = null;
|
||||
public boolean hasNextPage = false;
|
||||
|
||||
public List<Throwable> errors = new Vector<>();
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package org.schabi.newpipe.extractor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 28.01.16.
|
||||
|
@ -32,6 +33,14 @@ public interface Downloader {
|
|||
* @throws IOException*/
|
||||
String download(String siteUrl, String language) throws IOException;
|
||||
|
||||
/**Download the text file at the supplied URL as in download(String),
|
||||
* but set the HTTP header field "Accept-Language" to the supplied string.
|
||||
* @param siteUrl the URL of the text file to return the contents of
|
||||
* @param customProperties set request header properties
|
||||
* @return the contents of the specified text file
|
||||
* @throws IOException*/
|
||||
String download(String siteUrl, Map<String, String> customProperties) throws IOException;
|
||||
|
||||
/**Download (via HTTP) the text file located at the supplied URL, and return its contents.
|
||||
* Primarily intended for downloading web pages.
|
||||
* @param siteUrl the URL of the text file to download
|
||||
|
|
|
@ -33,7 +33,7 @@ public abstract class SearchEngine {
|
|||
|
||||
private StreamPreviewInfoSearchCollector collector;
|
||||
|
||||
public SearchEngine(StreamUrlIdHandler urlIdHandler, int serviceId) {
|
||||
public SearchEngine(UrlIdHandler urlIdHandler, int serviceId) {
|
||||
collector = new StreamPreviewInfoSearchCollector(urlIdHandler, serviceId);
|
||||
}
|
||||
|
||||
|
|
|
@ -43,5 +43,5 @@ public class SearchResult {
|
|||
|
||||
public String suggestion = "";
|
||||
public List<StreamPreviewInfo> resultList = new Vector<>();
|
||||
public List<Exception> errors = new Vector<>();
|
||||
public List<Throwable> errors = new Vector<>();
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@ public class ServiceList {
|
|||
public static StreamingService[] getServices() {
|
||||
return services;
|
||||
}
|
||||
public static StreamingService getService(int serviceId) throws ExtractionException {
|
||||
public static StreamingService getService(int serviceId)throws ExtractionException {
|
||||
for(StreamingService s : services) {
|
||||
if(s.getServiceId() == serviceId) {
|
||||
return s;
|
||||
|
|
|
@ -30,7 +30,7 @@ public abstract class StreamExtractor {
|
|||
|
||||
private int serviceId;
|
||||
private String url;
|
||||
private StreamUrlIdHandler urlIdHandler;
|
||||
private UrlIdHandler urlIdHandler;
|
||||
private Downloader downloader;
|
||||
private StreamPreviewInfoCollector previewInfoCollector;
|
||||
|
||||
|
@ -55,7 +55,7 @@ public abstract class StreamExtractor {
|
|||
}
|
||||
}
|
||||
|
||||
public StreamExtractor(StreamUrlIdHandler urlIdHandler, String url, Downloader dl, int serviceId) {
|
||||
public StreamExtractor(UrlIdHandler urlIdHandler, String url, Downloader dl, int serviceId) {
|
||||
this.serviceId = serviceId;
|
||||
this.urlIdHandler = urlIdHandler;
|
||||
previewInfoCollector = new StreamPreviewInfoCollector(urlIdHandler, serviceId);
|
||||
|
@ -69,7 +69,7 @@ public abstract class StreamExtractor {
|
|||
return url;
|
||||
}
|
||||
|
||||
public StreamUrlIdHandler getUrlIdHandler() {
|
||||
public UrlIdHandler getUrlIdHandler() {
|
||||
return urlIdHandler;
|
||||
}
|
||||
|
||||
|
@ -81,6 +81,7 @@ public abstract class StreamExtractor {
|
|||
public abstract String getTitle() throws ParsingException;
|
||||
public abstract String getDescription() throws ParsingException;
|
||||
public abstract String getUploader() throws ParsingException;
|
||||
public abstract String getChannelUrl() throws ParsingException;
|
||||
public abstract int getLength() throws ParsingException;
|
||||
public abstract long getViewCount() throws ParsingException;
|
||||
public abstract String getUploadDate() throws ParsingException;
|
||||
|
|
|
@ -85,12 +85,12 @@ public class StreamInfo extends AbstractVideoInfo {
|
|||
/* ---- importand data, withoug the video can't be displayed goes here: ---- */
|
||||
// if one of these is not available an exception is ment to be thrown directly into the frontend.
|
||||
|
||||
StreamUrlIdHandler uiconv = extractor.getUrlIdHandler();
|
||||
UrlIdHandler uiconv = extractor.getUrlIdHandler();
|
||||
|
||||
streamInfo.service_id = extractor.getServiceId();
|
||||
streamInfo.webpage_url = extractor.getPageUrl();
|
||||
streamInfo.stream_type = extractor.getStreamType();
|
||||
streamInfo.id = uiconv.getVideoId(extractor.getPageUrl());
|
||||
streamInfo.id = uiconv.getId(extractor.getPageUrl());
|
||||
streamInfo.title = extractor.getTitle();
|
||||
streamInfo.age_limit = extractor.getAgeLimit();
|
||||
|
||||
|
@ -188,6 +188,11 @@ public class StreamInfo extends AbstractVideoInfo {
|
|||
} catch(Exception e) {
|
||||
streamInfo.addException(e);
|
||||
}
|
||||
try {
|
||||
streamInfo.channel_url = extractor.getChannelUrl();
|
||||
} catch(Exception e) {
|
||||
streamInfo.addException(e);
|
||||
}
|
||||
try {
|
||||
streamInfo.description = extractor.getDescription();
|
||||
} catch(Exception e) {
|
||||
|
@ -248,7 +253,7 @@ public class StreamInfo extends AbstractVideoInfo {
|
|||
try {
|
||||
// get related videos
|
||||
StreamPreviewInfoCollector c = extractor.getRelatedVideos();
|
||||
streamInfo.related_videos = c.getItemList();
|
||||
streamInfo.related_streams = c.getItemList();
|
||||
streamInfo.errors.addAll(c.getErrors());
|
||||
} catch(Exception e) {
|
||||
streamInfo.addException(e);
|
||||
|
@ -258,6 +263,7 @@ public class StreamInfo extends AbstractVideoInfo {
|
|||
}
|
||||
|
||||
public String uploader_thumbnail_url = "";
|
||||
public String channel_url = "";
|
||||
public String description = "";
|
||||
|
||||
public List<VideoStream> video_streams = null;
|
||||
|
@ -275,9 +281,9 @@ public class StreamInfo extends AbstractVideoInfo {
|
|||
public int dislike_count = -1;
|
||||
public String average_rating = "";
|
||||
public StreamPreviewInfo next_video = null;
|
||||
public List<StreamPreviewInfo> related_videos = null;
|
||||
public List<StreamPreviewInfo> related_streams = null;
|
||||
//in seconds. some metadata is not passed using a StreamInfo object!
|
||||
public int start_position = 0;
|
||||
|
||||
public List<Exception> errors = new Vector<>();
|
||||
public List<Throwable> errors = new Vector<>();
|
||||
}
|
|
@ -27,11 +27,11 @@ import java.util.Vector;
|
|||
|
||||
public class StreamPreviewInfoCollector {
|
||||
private List<StreamPreviewInfo> itemList = new Vector<>();
|
||||
private List<Exception> errors = new Vector<>();
|
||||
private StreamUrlIdHandler urlIdHandler;
|
||||
private List<Throwable> errors = new Vector<>();
|
||||
private UrlIdHandler urlIdHandler;
|
||||
private int serviceId = -1;
|
||||
|
||||
public StreamPreviewInfoCollector(StreamUrlIdHandler handler, int serviceId) {
|
||||
public StreamPreviewInfoCollector(UrlIdHandler handler, int serviceId) {
|
||||
urlIdHandler = handler;
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ public class StreamPreviewInfoCollector {
|
|||
return itemList;
|
||||
}
|
||||
|
||||
public List<Exception> getErrors() {
|
||||
public List<Throwable> getErrors() {
|
||||
return errors;
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ public class StreamPreviewInfoCollector {
|
|||
if (urlIdHandler == null) {
|
||||
throw new ParsingException("Error: UrlIdHandler not set");
|
||||
} else if(!resultItem.webpage_url.isEmpty()) {
|
||||
resultItem.id = (new YoutubeStreamUrlIdHandler()).getVideoId(resultItem.webpage_url);
|
||||
resultItem.id = (new YoutubeStreamUrlIdHandler()).getId(resultItem.webpage_url);
|
||||
}
|
||||
resultItem.title = extractor.getTitle();
|
||||
resultItem.stream_type = extractor.getStreamType();
|
||||
|
|
|
@ -24,7 +24,7 @@ public class StreamPreviewInfoSearchCollector extends StreamPreviewInfoCollector
|
|||
|
||||
private String suggestion = "";
|
||||
|
||||
public StreamPreviewInfoSearchCollector(StreamUrlIdHandler handler, int serviceId) {
|
||||
public StreamPreviewInfoSearchCollector(UrlIdHandler handler, int serviceId) {
|
||||
super(handler, serviceId);
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,10 @@ public abstract class StreamingService {
|
|||
public abstract StreamExtractor getExtractorInstance(String url, Downloader downloader)
|
||||
throws IOException, ExtractionException;
|
||||
public abstract SearchEngine getSearchEngineInstance(Downloader downloader);
|
||||
public abstract StreamUrlIdHandler getUrlIdHandlerInstance();
|
||||
public abstract UrlIdHandler getUrlIdHandlerInstance();
|
||||
public abstract UrlIdHandler getChannelUrlIdHandlerInstance();
|
||||
public abstract ChannelExtractor getChannelExtractorInstance(String url, int page, Downloader downloader)
|
||||
throws ExtractionException, IOException;
|
||||
|
||||
public final int getServiceId() {
|
||||
return serviceId;
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package org.schabi.newpipe.extractor;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 02.02.16.
|
||||
* Created by Christian Schabesberger on 26.07.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* StreamUrlIdHandler.java is part of NewPipe.
|
||||
* UrlIdHandler.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
|
||||
|
@ -20,9 +20,9 @@ package org.schabi.newpipe.extractor;
|
|||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public interface StreamUrlIdHandler {
|
||||
String getVideoUrl(String videoId);
|
||||
String getVideoId(String siteUrl) throws ParsingException;
|
||||
public interface UrlIdHandler {
|
||||
String getUrl(String videoId);
|
||||
String getId(String siteUrl) throws ParsingException;
|
||||
String cleanUrl(String siteUrl) throws ParsingException;
|
||||
|
||||
/**When a VIEW_ACTION is caught this function will test if the url delivered within the calling
|
|
@ -0,0 +1,331 @@
|
|||
package org.schabi.newpipe.extractor.services.youtube;
|
||||
|
||||
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.schabi.newpipe.extractor.AbstractVideoInfo;
|
||||
import org.schabi.newpipe.extractor.ChannelExtractor;
|
||||
import org.schabi.newpipe.extractor.Downloader;
|
||||
import org.schabi.newpipe.extractor.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.Parser;
|
||||
import org.schabi.newpipe.extractor.ParsingException;
|
||||
import org.schabi.newpipe.extractor.StreamPreviewInfoCollector;
|
||||
import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
|
||||
import org.schabi.newpipe.extractor.UrlIdHandler;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 25.07.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* YoutubeChannelExtractor.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 YoutubeChannelExtractor extends ChannelExtractor {
|
||||
|
||||
private static final String TAG = YoutubeChannelExtractor.class.toString();
|
||||
|
||||
// private CSSOMParser cssParser = new CSSOMParser(new SACParserCSS3());
|
||||
|
||||
private Downloader downloader;
|
||||
private Document doc = null;
|
||||
|
||||
private boolean isAjaxPage = false;
|
||||
private static String userUrl = "";
|
||||
private static String channelName = "";
|
||||
private static String avatarUrl = "";
|
||||
private static String bannerUrl = "";
|
||||
private static String feedUrl = "";
|
||||
// the fist page is html all other pages are ajax. Every new page can be requested by sending
|
||||
// this request url.
|
||||
private static String nextPageUrl = "";
|
||||
|
||||
public YoutubeChannelExtractor(UrlIdHandler urlIdHandler, String url, int page, Downloader dl, int serviceId)
|
||||
throws ExtractionException, IOException {
|
||||
super(urlIdHandler, url, page, dl, serviceId);
|
||||
|
||||
url = urlIdHandler.cleanUrl(url) ; //+ "/video?veiw=0&flow=list&sort=dd";
|
||||
downloader = dl;
|
||||
|
||||
if(page == 0) {
|
||||
if (isUserUrl(url)) {
|
||||
userUrl = url;
|
||||
} else {
|
||||
// we first need to get the user url. Otherwise we can't find videos
|
||||
String channelPageContent = downloader.download(url);
|
||||
Document channelDoc = Jsoup.parse(channelPageContent, url);
|
||||
userUrl = getUserUrl(channelDoc);
|
||||
}
|
||||
|
||||
userUrl = userUrl + "/videos?veiw=0&flow=list&sort=dd&live_view=10000";
|
||||
String pageContent = downloader.download(userUrl);
|
||||
doc = Jsoup.parse(pageContent, userUrl);
|
||||
nextPageUrl = getNextPageUrl(doc);
|
||||
isAjaxPage = false;
|
||||
} else {
|
||||
String ajaxDataRaw = downloader.download(nextPageUrl);
|
||||
JSONObject ajaxData;
|
||||
try {
|
||||
ajaxData = new JSONObject(ajaxDataRaw);
|
||||
String htmlDataRaw = ajaxData.getString("content_html");
|
||||
doc = Jsoup.parse(htmlDataRaw, nextPageUrl);
|
||||
|
||||
String nextPageHtmlDataRaw = ajaxData.getString("load_more_widget_html");
|
||||
if(!nextPageHtmlDataRaw.isEmpty()) {
|
||||
Document nextPageData = Jsoup.parse(nextPageHtmlDataRaw, nextPageUrl);
|
||||
nextPageUrl = getNextPageUrl(nextPageData);
|
||||
} else {
|
||||
nextPageUrl = "";
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
throw new ParsingException("Could not parse json data for next page", e);
|
||||
}
|
||||
isAjaxPage = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelName() throws ParsingException {
|
||||
try {
|
||||
if(!isAjaxPage) {
|
||||
channelName = doc.select("span[class=\"qualified-channel-title-text\"]").first()
|
||||
.select("a").first().text();
|
||||
}
|
||||
return channelName;
|
||||
} catch(Exception e) {
|
||||
throw new ParsingException("Could not get channel name");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getAvatarUrl() throws ParsingException {
|
||||
try {
|
||||
if(!isAjaxPage) {
|
||||
avatarUrl = doc.select("img[class=\"channel-header-profile-image\"]")
|
||||
.first().attr("abs:src");
|
||||
}
|
||||
return avatarUrl;
|
||||
} catch(Exception e) {
|
||||
throw new ParsingException("Could not get avatar", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBannerUrl() throws ParsingException {
|
||||
try {
|
||||
if(!isAjaxPage) {
|
||||
Element el = doc.select("div[id=\"gh-banner\"]").first().select("style").first();
|
||||
String cssContent = el.html();
|
||||
String url = "https:" + Parser.matchGroup1("url\\((.*)\\)", cssContent);
|
||||
if (url.contains("s.ytimg.com")) {
|
||||
bannerUrl = null;
|
||||
} else {
|
||||
bannerUrl = url;
|
||||
}
|
||||
}
|
||||
return bannerUrl;
|
||||
} catch(Exception e) {
|
||||
throw new ParsingException("Could not get Banner", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamPreviewInfoCollector getStreams() throws ParsingException {
|
||||
StreamPreviewInfoCollector collector = getStreamPreviewInfoCollector();
|
||||
Element ul = null;
|
||||
if(isAjaxPage) {
|
||||
ul = doc.select("body").first();
|
||||
} else {
|
||||
ul = doc.select("ul[id=\"browse-items-primary\"]").first();
|
||||
}
|
||||
|
||||
for(final Element li : ul.children()) {
|
||||
if (li.select("div[class=\"feed-item-dismissable\"]").first() != null) {
|
||||
collector.commit(new StreamPreviewInfoExtractor() {
|
||||
@Override
|
||||
public AbstractVideoInfo.StreamType getStreamType() throws ParsingException {
|
||||
return AbstractVideoInfo.StreamType.VIDEO_STREAM;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getWebPageUrl() throws ParsingException {
|
||||
try {
|
||||
Element el = li.select("div[class=\"feed-item-dismissable\"]").first();
|
||||
Element dl = el.select("h3").first().select("a").first();
|
||||
return dl.attr("abs:href");
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException("Could not get web page url for the video", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() throws ParsingException {
|
||||
try {
|
||||
Element el = li.select("div[class=\"feed-item-dismissable\"]").first();
|
||||
Element dl = el.select("h3").first().select("a").first();
|
||||
return dl.text();
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException("Could not get title", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getDuration() throws ParsingException {
|
||||
try {
|
||||
return YoutubeParsingHelper.parseDurationString(
|
||||
li.select("span[class=\"video-time\"]").first().text());
|
||||
} catch(Exception e) {
|
||||
if(isLiveStream(li)) {
|
||||
// -1 for no duration
|
||||
return -1;
|
||||
} else {
|
||||
throw new ParsingException("Could not get Duration: " + getTitle(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploader() throws ParsingException {
|
||||
return getChannelName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploadDate() throws ParsingException {
|
||||
try {
|
||||
return li.select("div[class=\"yt-lockup-meta\"]").first()
|
||||
.select("li").first()
|
||||
.text();
|
||||
} catch(Exception e) {
|
||||
throw new ParsingException("Could not get uplaod date", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getViewCount() throws ParsingException {
|
||||
String output;
|
||||
String input;
|
||||
try {
|
||||
input = li.select("div[class=\"yt-lockup-meta\"]").first()
|
||||
.select("li").get(1)
|
||||
.text();
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
if(isLiveStream(li)) {
|
||||
// -1 for no view count
|
||||
return -1;
|
||||
} else {
|
||||
throw new ParsingException(
|
||||
"Could not parse yt-lockup-meta although available: " + getTitle(), e);
|
||||
}
|
||||
}
|
||||
|
||||
output = Parser.matchGroup1("([0-9,\\. ]*)", input)
|
||||
.replace(" ", "")
|
||||
.replace(".", "")
|
||||
.replace(",", "");
|
||||
|
||||
try {
|
||||
return Long.parseLong(output);
|
||||
} catch (NumberFormatException e) {
|
||||
// if this happens the video probably has no views
|
||||
if(!input.isEmpty()) {
|
||||
return 0;
|
||||
} else {
|
||||
throw new ParsingException("Could not handle input: " + input, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getThumbnailUrl() throws ParsingException {
|
||||
try {
|
||||
String url;
|
||||
Element te = li.select("span[class=\"yt-thumb-clip\"]").first()
|
||||
.select("img").first();
|
||||
url = te.attr("abs:src");
|
||||
// Sometimes youtube sends links to gif files which somehow seem to not exist
|
||||
// anymore. Items with such gif also offer a secondary image source. So we are going
|
||||
// to use that if we've caught such an item.
|
||||
if (url.contains(".gif")) {
|
||||
url = te.attr("abs:data-thumb");
|
||||
}
|
||||
return url;
|
||||
} catch (Exception e) {
|
||||
throw new ParsingException("Could not get thumbnail url", e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isLiveStream(Element item) {
|
||||
Element bla = item.select("span[class*=\"yt-badge-live\"]").first();
|
||||
|
||||
if(bla == null) {
|
||||
// sometimes livestreams dont have badges but sill are live streams
|
||||
// if video time is not available we most likly have an offline livestream
|
||||
if(item.select("span[class*=\"video-time\"]").first() == null) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return bla != null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return collector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFeedUrl() throws ParsingException {
|
||||
try {
|
||||
if(!isAjaxPage) {
|
||||
feedUrl = doc.select("link[title=\"RSS\"]").first().attr("abs:href");
|
||||
}
|
||||
return feedUrl;
|
||||
} catch(Exception e) {
|
||||
throw new ParsingException("Could not get feed url", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNextPage() throws ParsingException {
|
||||
return !nextPageUrl.isEmpty();
|
||||
}
|
||||
|
||||
private String getUserUrl(Document d) throws ParsingException {
|
||||
return d.select("span[class=\"qualified-channel-title-text\"]").first()
|
||||
.select("a").first().attr("abs:href");
|
||||
}
|
||||
|
||||
private boolean isUserUrl(String url) throws ParsingException {
|
||||
return url.contains("/user/");
|
||||
}
|
||||
|
||||
private String getNextPageUrl(Document d) throws ParsingException {
|
||||
try {
|
||||
Element button = d.select("button[class*=\"yt-uix-load-more\"]").first();
|
||||
return button.attr("abs:data-uix-load-more-href");
|
||||
} catch(Exception e) {
|
||||
throw new ParsingException("could not load next page url", e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
package org.schabi.newpipe.extractor.services.youtube;
|
||||
|
||||
import org.schabi.newpipe.extractor.Parser;
|
||||
import org.schabi.newpipe.extractor.ParsingException;
|
||||
import org.schabi.newpipe.extractor.UrlIdHandler;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 25.07.16.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2016 <chris.schabesberger@mailbox.org>
|
||||
* YoutubeChannelUrlIdHandler.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 YoutubeChannelUrlIdHandler implements UrlIdHandler {
|
||||
|
||||
public String getUrl(String channelId) {
|
||||
return "https://www.youtube.com/" + channelId;
|
||||
}
|
||||
|
||||
public String getId(String siteUrl) throws ParsingException {
|
||||
return Parser.matchGroup1("/(user/[A-Za-z0-9_-]*|channel/[A-Za-z0-9_-]*)", siteUrl);
|
||||
}
|
||||
|
||||
public String cleanUrl(String siteUrl) throws ParsingException {
|
||||
return getUrl(getId(siteUrl));
|
||||
}
|
||||
|
||||
public boolean acceptUrl(String videoUrl) {
|
||||
return (videoUrl.contains("youtube") ||
|
||||
videoUrl.contains("youtu.be")) &&
|
||||
( videoUrl.contains("/user/") ||
|
||||
videoUrl.contains("/channel/"));
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ import org.schabi.newpipe.extractor.SearchEngine;
|
|||
import org.schabi.newpipe.extractor.StreamPreviewInfoCollector;
|
||||
import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
|
||||
import org.schabi.newpipe.extractor.StreamPreviewInfoSearchCollector;
|
||||
import org.schabi.newpipe.extractor.StreamUrlIdHandler;
|
||||
import org.schabi.newpipe.extractor.UrlIdHandler;
|
||||
import org.w3c.dom.Node;
|
||||
import org.w3c.dom.NodeList;
|
||||
import org.xml.sax.InputSource;
|
||||
|
@ -55,7 +55,7 @@ public class YoutubeSearchEngine extends SearchEngine {
|
|||
private static final String TAG = YoutubeSearchEngine.class.toString();
|
||||
public static final String CHARSET_UTF_8 = "UTF-8";
|
||||
|
||||
public YoutubeSearchEngine(StreamUrlIdHandler urlIdHandler, int serviceId) {
|
||||
public YoutubeSearchEngine(UrlIdHandler urlIdHandler, int serviceId) {
|
||||
super(urlIdHandler, serviceId);
|
||||
}
|
||||
|
||||
|
@ -91,7 +91,6 @@ public class YoutubeSearchEngine extends SearchEngine {
|
|||
site = downloader.download(url);
|
||||
}
|
||||
|
||||
|
||||
Document doc = Jsoup.parse(site, url);
|
||||
Element list = doc.select("ol[class=\"item-section\"]").first();
|
||||
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package org.schabi.newpipe.extractor.services.youtube;
|
||||
|
||||
import org.schabi.newpipe.extractor.ChannelExtractor;
|
||||
import org.schabi.newpipe.extractor.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.Downloader;
|
||||
import org.schabi.newpipe.extractor.StreamExtractor;
|
||||
import org.schabi.newpipe.extractor.StreamingService;
|
||||
import org.schabi.newpipe.extractor.StreamUrlIdHandler;
|
||||
import org.schabi.newpipe.extractor.UrlIdHandler;
|
||||
import org.schabi.newpipe.extractor.SearchEngine;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -45,7 +46,7 @@ public class YoutubeService extends StreamingService {
|
|||
@Override
|
||||
public StreamExtractor getExtractorInstance(String url, Downloader downloader)
|
||||
throws ExtractionException, IOException {
|
||||
StreamUrlIdHandler urlIdHandler = new YoutubeStreamUrlIdHandler();
|
||||
UrlIdHandler urlIdHandler = new YoutubeStreamUrlIdHandler();
|
||||
if(urlIdHandler.acceptUrl(url)) {
|
||||
return new YoutubeStreamExtractor(urlIdHandler, url, downloader, getServiceId());
|
||||
}
|
||||
|
@ -59,7 +60,18 @@ public class YoutubeService extends StreamingService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public StreamUrlIdHandler getUrlIdHandlerInstance() {
|
||||
public UrlIdHandler getUrlIdHandlerInstance() {
|
||||
return new YoutubeStreamUrlIdHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public UrlIdHandler getChannelUrlIdHandlerInstance() {
|
||||
return new YoutubeChannelUrlIdHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ChannelExtractor getChannelExtractorInstance(String url, int page, Downloader downloader)
|
||||
throws ExtractionException, IOException {
|
||||
return new YoutubeChannelExtractor(getChannelUrlIdHandlerInstance(), url, page, downloader, getServiceId());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,10 +15,9 @@ import org.schabi.newpipe.extractor.Downloader;
|
|||
import org.schabi.newpipe.extractor.Parser;
|
||||
import org.schabi.newpipe.extractor.ParsingException;
|
||||
import org.schabi.newpipe.extractor.StreamInfo;
|
||||
import org.schabi.newpipe.extractor.StreamPreviewInfo;
|
||||
import org.schabi.newpipe.extractor.StreamPreviewInfoCollector;
|
||||
import org.schabi.newpipe.extractor.StreamPreviewInfoExtractor;
|
||||
import org.schabi.newpipe.extractor.StreamUrlIdHandler;
|
||||
import org.schabi.newpipe.extractor.UrlIdHandler;
|
||||
import org.schabi.newpipe.extractor.StreamExtractor;
|
||||
import org.schabi.newpipe.extractor.MediaFormat;
|
||||
import org.schabi.newpipe.extractor.VideoStream;
|
||||
|
@ -183,12 +182,12 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||
// cached values
|
||||
private static volatile String decryptionCode = "";
|
||||
|
||||
StreamUrlIdHandler urlidhandler = new YoutubeStreamUrlIdHandler();
|
||||
UrlIdHandler urlidhandler = new YoutubeStreamUrlIdHandler();
|
||||
String pageUrl = "";
|
||||
|
||||
private Downloader downloader;
|
||||
|
||||
public YoutubeStreamExtractor(StreamUrlIdHandler urlIdHandler, String pageUrl,
|
||||
public YoutubeStreamExtractor(UrlIdHandler urlIdHandler, String pageUrl,
|
||||
Downloader dl, int serviceId)
|
||||
throws ExtractionException, IOException {
|
||||
super(urlIdHandler ,pageUrl, dl, serviceId);
|
||||
|
@ -203,7 +202,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||
// Check if the video is age restricted
|
||||
if (pageContent.contains("<meta property=\"og:restrictions:age")) {
|
||||
String videoInfoUrl = GET_VIDEO_INFO_URL.replace("%%video_id%%",
|
||||
urlidhandler.getVideoId(pageUrl)).replace("$$el_type$$", "&" + EL_INFO);
|
||||
urlidhandler.getId(pageUrl)).replace("$$el_type$$", "&" + EL_INFO);
|
||||
String videoInfoPageString = downloader.download(videoInfoUrl);
|
||||
videoInfoPage = Parser.compatParseMap(videoInfoPageString);
|
||||
playerUrl = getPlayerUrlFromRestrictedVideo(pageUrl);
|
||||
|
@ -286,7 +285,7 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||
private String getPlayerUrlFromRestrictedVideo(String pageUrl) throws ParsingException {
|
||||
try {
|
||||
String playerUrl = "";
|
||||
String videoId = urlidhandler.getVideoId(pageUrl);
|
||||
String videoId = urlidhandler.getId(pageUrl);
|
||||
String embedUrl = "https://www.youtube.com/embed/" + videoId;
|
||||
String embedPageContent = downloader.download(embedUrl);
|
||||
//todo: find out if this can be reapaced by Parser.matchGroup1()
|
||||
|
@ -686,6 +685,16 @@ public class YoutubeStreamExtractor extends StreamExtractor {
|
|||
return pageUrl;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getChannelUrl() throws ParsingException {
|
||||
try {
|
||||
return doc.select("div[class=\"yt-user-info\"]").first().children()
|
||||
.select("a").first().attr("abs:href");
|
||||
} catch(Exception e) {
|
||||
throw new ParsingException("Could not get channel link", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamInfo.StreamType getStreamType() throws ParsingException {
|
||||
//todo: if implementing livestream support this value should be generated dynamically
|
||||
|
|
|
@ -66,8 +66,6 @@ public class YoutubeStreamPreviewInfoExtractor implements StreamPreviewInfoExtra
|
|||
} else {
|
||||
throw new ParsingException("Could not get Duration: " + getTitle(), e);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ package org.schabi.newpipe.extractor.services.youtube;
|
|||
|
||||
import org.schabi.newpipe.extractor.Parser;
|
||||
import org.schabi.newpipe.extractor.ParsingException;
|
||||
import org.schabi.newpipe.extractor.StreamUrlIdHandler;
|
||||
import org.schabi.newpipe.extractor.UrlIdHandler;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
|
@ -27,16 +27,16 @@ import java.net.URLDecoder;
|
|||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
public class YoutubeStreamUrlIdHandler implements StreamUrlIdHandler {
|
||||
public class YoutubeStreamUrlIdHandler implements UrlIdHandler {
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@Override
|
||||
public String getVideoUrl(String videoId) {
|
||||
public String getUrl(String videoId) {
|
||||
return "https://www.youtube.com/watch?v=" + videoId;
|
||||
}
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
@Override
|
||||
public String getVideoId(String url) throws ParsingException, IllegalArgumentException {
|
||||
public String getId(String url) throws ParsingException, IllegalArgumentException {
|
||||
if(url.isEmpty())
|
||||
{
|
||||
throw new IllegalArgumentException("The url parameter should not be empty");
|
||||
|
@ -81,7 +81,7 @@ public class YoutubeStreamUrlIdHandler implements StreamUrlIdHandler {
|
|||
}
|
||||
|
||||
public String cleanUrl(String complexUrl) throws ParsingException {
|
||||
return getVideoUrl(getVideoId(complexUrl));
|
||||
return getUrl(getId(complexUrl));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
package org.schabi.newpipe.info_list;
|
||||
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
/**
|
||||
* Created by the-scrabi on 01.08.16.
|
||||
*/
|
||||
public class InfoItemHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public ImageView itemThumbnailView;
|
||||
public TextView itemVideoTitleView,
|
||||
itemUploaderView,
|
||||
itemDurationView,
|
||||
itemUploadDateView,
|
||||
itemViewCountView;
|
||||
public Button itemButton;
|
||||
|
||||
public InfoItemHolder(View v) {
|
||||
super(v);
|
||||
itemThumbnailView = (ImageView) v.findViewById(R.id.itemThumbnailView);
|
||||
itemVideoTitleView = (TextView) v.findViewById(R.id.itemVideoTitleView);
|
||||
itemUploaderView = (TextView) v.findViewById(R.id.itemUploaderView);
|
||||
itemDurationView = (TextView) v.findViewById(R.id.itemDurationView);
|
||||
itemUploadDateView = (TextView) v.findViewById(R.id.itemUploadDateView);
|
||||
itemViewCountView = (TextView) v.findViewById(R.id.itemViewCountView);
|
||||
itemButton = (Button) v.findViewById(R.id.item_button);
|
||||
}
|
||||
}
|
|
@ -1,73 +1,79 @@
|
|||
package org.schabi.newpipe;
|
||||
package org.schabi.newpipe.info_list;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions;
|
||||
import com.nostra13.universalimageloader.core.ImageLoader;
|
||||
|
||||
import org.schabi.newpipe.ImageErrorLoadingListener;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.AbstractVideoInfo;
|
||||
import org.schabi.newpipe.extractor.StreamPreviewInfo;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 24.10.15.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
||||
* VideoInfoItemViewCreator.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/>.
|
||||
* Created by the-scrabi on 01.08.16.
|
||||
*/
|
||||
public class InfoListAdapter extends RecyclerView.Adapter<InfoItemHolder> {
|
||||
|
||||
public class VideoInfoItemViewCreator {
|
||||
private final LayoutInflater inflater;
|
||||
private ImageLoader imageLoader = ImageLoader.getInstance();
|
||||
private DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder().cacheInMemory(true).build();
|
||||
|
||||
public VideoInfoItemViewCreator(LayoutInflater inflater) {
|
||||
this.inflater = inflater;
|
||||
public interface OnItemSelectedListener {
|
||||
void selected(String url);
|
||||
}
|
||||
|
||||
public View getViewFromVideoInfoItem(View convertView, ViewGroup parent, StreamPreviewInfo info) {
|
||||
ViewHolder holder;
|
||||
private Activity activity = null;
|
||||
private View rootView = null;
|
||||
private List<StreamPreviewInfo> streamList = new Vector<>();
|
||||
private ImageLoader imageLoader = ImageLoader.getInstance();
|
||||
private DisplayImageOptions displayImageOptions =
|
||||
new DisplayImageOptions.Builder().cacheInMemory(true).build();
|
||||
private OnItemSelectedListener onItemSelectedListener;
|
||||
|
||||
// generate holder
|
||||
if(convertView == null) {
|
||||
convertView = inflater.inflate(R.layout.video_item, parent, false);
|
||||
holder = new ViewHolder();
|
||||
holder.itemThumbnailView = (ImageView) convertView.findViewById(R.id.itemThumbnailView);
|
||||
holder.itemVideoTitleView = (TextView) convertView.findViewById(R.id.itemVideoTitleView);
|
||||
holder.itemUploaderView = (TextView) convertView.findViewById(R.id.itemUploaderView);
|
||||
holder.itemDurationView = (TextView) convertView.findViewById(R.id.itemDurationView);
|
||||
holder.itemUploadDateView = (TextView) convertView.findViewById(R.id.itemUploadDateView);
|
||||
holder.itemViewCountView = (TextView) convertView.findViewById(R.id.itemViewCountView);
|
||||
convertView.setTag(holder);
|
||||
} else {
|
||||
holder = (ViewHolder) convertView.getTag();
|
||||
|
||||
|
||||
public InfoListAdapter(Activity a, View rootView) {
|
||||
activity = a;
|
||||
this.rootView = rootView;
|
||||
}
|
||||
|
||||
public void setOnItemSelectedListener(OnItemSelectedListener onItemSelectedListener) {
|
||||
this.onItemSelectedListener = onItemSelectedListener;
|
||||
}
|
||||
|
||||
public void addStreamItemList(List<StreamPreviewInfo> videos) {
|
||||
if(videos!= null) {
|
||||
streamList.addAll(videos);
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
// fill with information
|
||||
public void clearSteamItemList() {
|
||||
streamList = new Vector<>();
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
/*
|
||||
if(info.thumbnail == null) {
|
||||
holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
|
||||
} else {
|
||||
holder.itemThumbnailView.setImageBitmap(info.thumbnail);
|
||||
}
|
||||
*/
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return streamList.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public InfoItemHolder onCreateViewHolder(ViewGroup parent, int i) {
|
||||
View itemView = LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.video_item, parent, false);
|
||||
|
||||
return new InfoItemHolder(itemView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(InfoItemHolder holder, int i) {
|
||||
final StreamPreviewInfo info = streamList.get(i);
|
||||
// fill holder with information
|
||||
holder.itemVideoTitleView.setText(info.title);
|
||||
if(info.uploader != null && !info.uploader.isEmpty()) {
|
||||
holder.itemUploaderView.setText(info.uploader);
|
||||
|
@ -94,18 +100,22 @@ public class VideoInfoItemViewCreator {
|
|||
|
||||
holder.itemThumbnailView.setImageResource(R.drawable.dummy_thumbnail);
|
||||
if(info.thumbnail_url != null && !info.thumbnail_url.isEmpty()) {
|
||||
imageLoader.displayImage(info.thumbnail_url, holder.itemThumbnailView, displayImageOptions);
|
||||
imageLoader.displayImage(info.thumbnail_url,
|
||||
holder.itemThumbnailView,
|
||||
displayImageOptions,
|
||||
new ImageErrorLoadingListener(activity, rootView, info.service_id));
|
||||
}
|
||||
|
||||
return convertView;
|
||||
holder.itemButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
onItemSelectedListener.selected(info.webpage_url);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private class ViewHolder {
|
||||
public ImageView itemThumbnailView;
|
||||
public TextView itemVideoTitleView, itemUploaderView, itemDurationView, itemUploadDateView, itemViewCountView;
|
||||
}
|
||||
|
||||
private String shortViewCount(Long viewCount){
|
||||
public static String shortViewCount(Long viewCount){
|
||||
if(viewCount >= 1000000000){
|
||||
return Long.toString(viewCount/1000000000)+"B views";
|
||||
}else if(viewCount>=1000000){
|
|
@ -23,8 +23,8 @@ import android.widget.Toast;
|
|||
import org.schabi.newpipe.ActivityCommunicator;
|
||||
import org.schabi.newpipe.BuildConfig;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.VideoItemDetailActivity;
|
||||
import org.schabi.newpipe.VideoItemDetailFragment;
|
||||
import org.schabi.newpipe.detail.VideoItemDetailActivity;
|
||||
import org.schabi.newpipe.detail.VideoItemDetailFragment;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
|
|
|
@ -0,0 +1,265 @@
|
|||
package org.schabi.newpipe.search_fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.SearchView;
|
||||
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.view.inputmethod.InputMethodManager;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.schabi.newpipe.ErrorActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.detail.VideoItemDetailActivity;
|
||||
import org.schabi.newpipe.detail.VideoItemDetailFragment;
|
||||
import org.schabi.newpipe.extractor.SearchResult;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
import org.schabi.newpipe.info_list.InfoListAdapter;
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 02.08.16.
|
||||
*/
|
||||
|
||||
public class SearchInfoItemFragment extends Fragment {
|
||||
|
||||
private static final String TAG = SearchInfoItemFragment.class.toString();
|
||||
|
||||
public class SearchQueryListener implements SearchView.OnQueryTextListener {
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
Activity a = getActivity();
|
||||
try {
|
||||
searchQuery = query;
|
||||
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,
|
||||
ServiceList.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();
|
||||
}
|
||||
View bg = a.findViewById(R.id.mainBG);
|
||||
bg.setVisibility(View.GONE);
|
||||
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 SearchView searchView = null;
|
||||
private int pageNumber = 0;
|
||||
private SuggestionListAdapter suggestionListAdapter = null;
|
||||
private InfoListAdapter infoListAdapter = null;
|
||||
private LinearLayoutManager streamInfoListLayoutManager = null;
|
||||
private RecyclerView recyclerView = null;
|
||||
|
||||
// savedInstanceBundle arguments
|
||||
private static final String QUERY = "query";
|
||||
private static final String STREAMING_SERVICE = "streaming_service";
|
||||
|
||||
/**
|
||||
* Mandatory empty constructor for the fragment manager to instantiate the
|
||||
* fragment (e.g. upon screen orientation changes).
|
||||
*/
|
||||
public SearchInfoItemFragment() {
|
||||
}
|
||||
|
||||
// TODO: Customize parameter initialization
|
||||
@SuppressWarnings("unused")
|
||||
public static SearchInfoItemFragment newInstance(int columnCount) {
|
||||
SearchInfoItemFragment fragment = new SearchInfoItemFragment();
|
||||
return fragment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
if(savedInstanceState != null) {
|
||||
searchQuery = savedInstanceState.getString(QUERY);
|
||||
streamingServiceId = savedInstanceState.getInt(STREAMING_SERVICE);
|
||||
} else {
|
||||
try {
|
||||
streamingServiceId = ServiceList.getIdOfService("Youtube");
|
||||
} catch(Exception e) {
|
||||
e.printStackTrace();
|
||||
ErrorActivity.reportError(getActivity(), e, null,
|
||||
getActivity().findViewById(android.R.id.content),
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
|
||||
ServiceList.getNameOfService(streamingServiceId),
|
||||
"", R.string.general_error));
|
||||
}
|
||||
}
|
||||
|
||||
SearchWorker sw = SearchWorker.getInstance();
|
||||
sw.setSearchWorkerResultListner(new SearchWorker.SearchWorkerResultListner() {
|
||||
@Override
|
||||
public void onResult(SearchResult result) {
|
||||
infoListAdapter.addStreamItemList(result.resultList);
|
||||
isLoading = false;
|
||||
loadingIndicator.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingFound(int stringResource) {
|
||||
//setListShown(true);
|
||||
Toast.makeText(getActivity(), getString(stringResource),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
isLoading = false;
|
||||
loadingIndicator.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(String message) {
|
||||
//setListShown(true);
|
||||
Toast.makeText(getActivity(), message,
|
||||
Toast.LENGTH_LONG).show();
|
||||
isLoading = false;
|
||||
loadingIndicator.setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.fragment_searchinfoitem, container, false);
|
||||
|
||||
Context context = view.getContext();
|
||||
loadingIndicator = (ProgressBar) view.findViewById(R.id.progressBar);
|
||||
recyclerView = (RecyclerView) view.findViewById(R.id.list);
|
||||
streamInfoListLayoutManager = new LinearLayoutManager(context);
|
||||
recyclerView.setLayoutManager(streamInfoListLayoutManager);
|
||||
|
||||
infoListAdapter = new InfoListAdapter(getActivity(),
|
||||
getActivity().findViewById(android.R.id.content));
|
||||
infoListAdapter.setOnItemSelectedListener(new InfoListAdapter.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void selected(String url) {
|
||||
Intent i = new Intent(getActivity(), VideoItemDetailActivity.class);
|
||||
i.putExtra(VideoItemDetailFragment.STREAMING_SERVICE, streamingServiceId);
|
||||
i.putExtra(VideoItemDetailFragment.VIDEO_URL, url);
|
||||
getActivity().startActivity(i);
|
||||
}
|
||||
});
|
||||
recyclerView.setAdapter(infoListAdapter);
|
||||
|
||||
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 = streamInfoListLayoutManager.getChildCount();
|
||||
totalItemCount = streamInfoListLayoutManager.getItemCount();
|
||||
pastVisiblesItems = streamInfoListLayoutManager.findFirstVisibleItemPosition();
|
||||
|
||||
if ( (visibleItemCount + pastVisiblesItems) >= totalItemCount && !isLoading)
|
||||
{
|
||||
pageNumber++;
|
||||
search(searchQuery, pageNumber);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(Context context) {
|
||||
super.onAttach(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDetach() {
|
||||
super.onDetach();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
inflater.inflate(R.menu.search_menu, menu);
|
||||
|
||||
MenuItem searchItem = menu.findItem(R.id.action_search);
|
||||
searchView = (SearchView) searchItem.getActionView();
|
||||
setupSearchView(searchView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void setupSearchView(SearchView searchView) {
|
||||
suggestionListAdapter = new SuggestionListAdapter(getActivity());
|
||||
searchView.setSuggestionsAdapter(suggestionListAdapter);
|
||||
searchView.setOnSuggestionListener(new SearchSuggestionListener(searchView, suggestionListAdapter));
|
||||
searchView.setOnQueryTextListener(new SearchQueryListener());
|
||||
if(searchQuery != null && !searchQuery.isEmpty()) {
|
||||
searchView.setQuery(searchQuery, false);
|
||||
searchView.setIconifiedByDefault(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void search(String query) {
|
||||
infoListAdapter.clearSteamItemList();
|
||||
pageNumber = 0;
|
||||
search(query, pageNumber);
|
||||
loadingIndicator.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
private void search(String query, int page) {
|
||||
isLoading = true;
|
||||
SearchWorker sw = SearchWorker.getInstance();
|
||||
sw.search(streamingServiceId, query, page, getActivity());
|
||||
}
|
||||
|
||||
private void searchSuggestions(String query) {
|
||||
SuggestionSearchRunnable suggestionSearchRunnable =
|
||||
new SuggestionSearchRunnable(streamingServiceId, query, getActivity(), suggestionListAdapter);
|
||||
Thread suggestionThread = new Thread(suggestionSearchRunnable);
|
||||
suggestionThread.start();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package org.schabi.newpipe.search_fragment;
|
||||
|
||||
import android.support.v7.widget.SearchView;
|
||||
|
||||
/**
|
||||
* Created by the-scrabi on 02.08.16.
|
||||
*/
|
||||
|
||||
public class SearchSuggestionListener implements SearchView.OnSuggestionListener{
|
||||
|
||||
private SearchView searchView;
|
||||
private SuggestionListAdapter adapter;
|
||||
|
||||
public SearchSuggestionListener(SearchView searchView, SuggestionListAdapter adapter) {
|
||||
this.searchView = searchView;
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSuggestionSelect(int position) {
|
||||
String suggestion = adapter.getSuggestion(position);
|
||||
searchView.setQuery(suggestion,true);
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSuggestionClick(int position) {
|
||||
String suggestion = adapter.getSuggestion(position);
|
||||
searchView.setQuery(suggestion,true);
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,174 @@
|
|||
package org.schabi.newpipe.search_fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.schabi.newpipe.Downloader;
|
||||
import org.schabi.newpipe.ErrorActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.SearchEngine;
|
||||
import org.schabi.newpipe.extractor.SearchResult;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Created by the-scrabi on 02.08.16.
|
||||
*/
|
||||
|
||||
|
||||
public class SearchWorker {
|
||||
private static final String TAG = SearchWorker.class.toString();
|
||||
|
||||
public interface SearchWorkerResultListner {
|
||||
void onResult(SearchResult result);
|
||||
void onNothingFound(final int stringResource);
|
||||
void onError(String message);
|
||||
}
|
||||
|
||||
private class ResultRunnable implements Runnable {
|
||||
private final SearchResult result;
|
||||
private int requestId = 0;
|
||||
public ResultRunnable(SearchResult result, int requestId) {
|
||||
this.result = result;
|
||||
this.requestId = requestId;
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
if(this.requestId == SearchWorker.this.requestId) {
|
||||
searchWorkerResultListner.onResult(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class SearchRunnable implements Runnable {
|
||||
public static final String YOUTUBE = "Youtube";
|
||||
private final String query;
|
||||
private final int page;
|
||||
final Handler h = new Handler();
|
||||
private volatile boolean runs = true;
|
||||
private Activity a = null;
|
||||
private int serviceId = -1;
|
||||
public SearchRunnable(int serviceId, String query, int page, Activity activity, int requestId) {
|
||||
this.serviceId = serviceId;
|
||||
this.query = query;
|
||||
this.page = page;
|
||||
this.a = activity;
|
||||
}
|
||||
void terminate() {
|
||||
runs = false;
|
||||
}
|
||||
@Override
|
||||
public void run() {
|
||||
SearchResult result = null;
|
||||
SearchEngine engine = null;
|
||||
|
||||
try {
|
||||
engine = ServiceList.getService(serviceId)
|
||||
.getSearchEngineInstance(new Downloader());
|
||||
} catch(ExtractionException e) {
|
||||
ErrorActivity.reportError(h, a, e, null, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
|
||||
Integer.toString(serviceId), query, R.string.general_error));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(a);
|
||||
String searchLanguageKey = a.getString(R.string.search_language_key);
|
||||
String searchLanguage = sp.getString(searchLanguageKey,
|
||||
a.getString(R.string.default_language_value));
|
||||
result = SearchResult
|
||||
.getSearchResult(engine, query, page, searchLanguage, new Downloader());
|
||||
|
||||
if(runs) {
|
||||
h.post(new ResultRunnable(result, requestId));
|
||||
}
|
||||
|
||||
// look for errors during extraction
|
||||
// soft errors:
|
||||
if(result != null &&
|
||||
!result.errors.isEmpty()) {
|
||||
Log.e(TAG, "OCCURRED ERRORS DURING SEARCH EXTRACTION:");
|
||||
for(Throwable e : result.errors) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "------");
|
||||
}
|
||||
|
||||
View rootView = a.findViewById(android.R.id.content);
|
||||
ErrorActivity.reportError(h, a, result.errors, null, rootView,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
|
||||
/* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.light_parsing_error));
|
||||
|
||||
}
|
||||
// hard errors:
|
||||
} catch(IOException e) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
searchWorkerResultListner.onNothingFound(R.string.network_error);
|
||||
}
|
||||
});
|
||||
e.printStackTrace();
|
||||
} catch(final SearchEngine.NothingFoundException e) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
searchWorkerResultListner.onError(e.getMessage());
|
||||
}
|
||||
});
|
||||
} catch(ExtractionException e) {
|
||||
ErrorActivity.reportError(h, a, e, null, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
|
||||
/* todo: this shoudl not be assigned static */
|
||||
YOUTUBE, query, R.string.parsing_error));
|
||||
//postNewErrorToast(h, R.string.parsing_error);
|
||||
e.printStackTrace();
|
||||
|
||||
} catch(Exception e) {
|
||||
ErrorActivity.reportError(h, a, e, null, null,
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
|
||||
/* todo: this shoudl not be assigned static */ YOUTUBE, query, R.string.general_error));
|
||||
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static SearchWorker searchWorker = null;
|
||||
private SearchWorkerResultListner searchWorkerResultListner = null;
|
||||
private SearchRunnable runnable = null;
|
||||
private int requestId = 0; //prevents running requests that have already ben expired
|
||||
|
||||
public static SearchWorker getInstance() {
|
||||
return searchWorker == null ? (searchWorker = new SearchWorker()) : searchWorker;
|
||||
}
|
||||
|
||||
public void setSearchWorkerResultListner(SearchWorkerResultListner listener) {
|
||||
searchWorkerResultListner = listener;
|
||||
}
|
||||
|
||||
private SearchWorker() {
|
||||
|
||||
}
|
||||
|
||||
public void search(int serviceId, String query, int page, Activity a) {
|
||||
if(runnable != null) {
|
||||
terminate();
|
||||
}
|
||||
runnable = new SearchRunnable(serviceId, query, page, a, requestId);
|
||||
Thread thread = new Thread(runnable);
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public void terminate() {
|
||||
requestId++;
|
||||
runnable.terminate();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package org.schabi.newpipe;
|
||||
package org.schabi.newpipe.search_fragment;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
@ -9,27 +9,10 @@ import android.view.View;
|
|||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by Madiyar on 23.02.2016.
|
||||
*
|
||||
* Copyright (C) Christian Schabesberger 2015 <chris.schabesberger@mailbox.org>
|
||||
* SuggestionListAdapter.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/>.
|
||||
* Created by the-scrabi on 02.08.16.
|
||||
*/
|
||||
|
||||
public class SuggestionListAdapter extends CursorAdapter {
|
||||
|
@ -80,4 +63,4 @@ public class SuggestionListAdapter extends CursorAdapter {
|
|||
private class ViewHolder {
|
||||
public TextView suggestionTitle;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
package org.schabi.newpipe.search_fragment;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.schabi.newpipe.Downloader;
|
||||
import org.schabi.newpipe.ErrorActivity;
|
||||
import org.schabi.newpipe.R;
|
||||
import org.schabi.newpipe.extractor.ExtractionException;
|
||||
import org.schabi.newpipe.extractor.SearchEngine;
|
||||
import org.schabi.newpipe.extractor.ServiceList;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Created by the-scrabi on 02.08.16.
|
||||
*/
|
||||
|
||||
public class SuggestionSearchRunnable implements Runnable{
|
||||
|
||||
private class SuggestionResultRunnable implements Runnable{
|
||||
|
||||
private List<String> suggestions;
|
||||
private SuggestionListAdapter adapter;
|
||||
|
||||
private SuggestionResultRunnable(List<String> suggestions, SuggestionListAdapter adapter) {
|
||||
this.suggestions = suggestions;
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
adapter.updateAdapter(suggestions);
|
||||
}
|
||||
}
|
||||
|
||||
private final int serviceId;
|
||||
private final String query;
|
||||
final Handler h = new Handler();
|
||||
private Activity a = null;
|
||||
private SuggestionListAdapter adapter;
|
||||
public SuggestionSearchRunnable(int serviceId, String query,
|
||||
Activity activity, SuggestionListAdapter adapter) {
|
||||
this.serviceId = serviceId;
|
||||
this.query = query;
|
||||
this.a = activity;
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
SearchEngine engine =
|
||||
ServiceList.getService(serviceId).getSearchEngineInstance(new Downloader());
|
||||
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(a);
|
||||
String searchLanguageKey = a.getString(R.string.search_language_key);
|
||||
String searchLanguage = sp.getString(searchLanguageKey,
|
||||
a.getString(R.string.default_language_value));
|
||||
List<String> suggestions = engine.suggestionList(query,searchLanguage,new Downloader());
|
||||
h.post(new SuggestionResultRunnable(suggestions, adapter));
|
||||
} catch (ExtractionException e) {
|
||||
ErrorActivity.reportError(h, a, e, null, a.findViewById(android.R.id.content),
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
|
||||
ServiceList.getNameOfService(serviceId), query, R.string.parsing_error));
|
||||
e.printStackTrace();
|
||||
} catch (IOException e) {
|
||||
postNewErrorToast(h, R.string.network_error);
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
ErrorActivity.reportError(h, a, e, null, a.findViewById(android.R.id.content),
|
||||
ErrorActivity.ErrorInfo.make(ErrorActivity.SEARCHED,
|
||||
ServiceList.getNameOfService(serviceId), query, R.string.general_error));
|
||||
}
|
||||
}
|
||||
|
||||
private void postNewErrorToast(Handler h, final int stringResource) {
|
||||
h.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(a, a.getString(stringResource),
|
||||
Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
* along with NewPipe. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.schabi.newpipe;
|
||||
package org.schabi.newpipe.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
@ -26,6 +26,8 @@ import android.os.Environment;
|
|||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import us.shandian.giga.util.Utility;
|
|
@ -1,4 +1,4 @@
|
|||
package org.schabi.newpipe;
|
||||
package org.schabi.newpipe.settings;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
|
@ -14,6 +14,8 @@ import android.view.MenuItem;
|
|||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
|
||||
/**
|
||||
* Created by Christian Schabesberger on 31.08.15.
|
|
@ -1,4 +1,4 @@
|
|||
package org.schabi.newpipe;
|
||||
package org.schabi.newpipe.settings;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.ClipData;
|
||||
|
@ -16,6 +16,9 @@ import android.preference.PreferenceScreen;
|
|||
|
||||
import com.nononsenseapps.filepicker.FilePickerActivity;
|
||||
|
||||
import org.schabi.newpipe.App;
|
||||
import org.schabi.newpipe.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import info.guardianproject.netcipher.proxy.OrbotHelper;
|
|
@ -5,7 +5,7 @@ import android.util.Log;
|
|||
|
||||
import com.google.gson.Gson;
|
||||
|
||||
import org.schabi.newpipe.NewPipeSettings;
|
||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.RandomAccessFile;
|
||||
|
|
|
@ -14,7 +14,7 @@ import android.os.Message;
|
|||
import android.support.v4.app.NotificationCompat.Builder;
|
||||
import android.util.Log;
|
||||
|
||||
import org.schabi.newpipe.NewPipeSettings;
|
||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||
import org.schabi.newpipe.R;
|
||||
import us.shandian.giga.get.DownloadManager;
|
||||
import us.shandian.giga.get.DownloadManagerImpl;
|
||||
|
|
|
@ -17,9 +17,8 @@ import java.io.IOException;
|
|||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import org.schabi.newpipe.NewPipeSettings;
|
||||
import org.schabi.newpipe.settings.NewPipeSettings;
|
||||
import org.schabi.newpipe.R;
|
||||
import us.shandian.giga.get.DownloadMission;
|
||||
|
||||
import com.nononsenseapps.filepicker.FilePickerActivity;
|
||||
import com.nononsenseapps.filepicker.AbstractFilePickerFragment;
|
||||
|
|
BIN
app/src/main/res/drawable-hdpi/ic_rss_feed_white_24dp.png
Normal file
BIN
app/src/main/res/drawable-hdpi/ic_rss_feed_white_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 417 B |
BIN
app/src/main/res/drawable-mdpi/ic_rss_feed_white_24dp.png
Normal file
BIN
app/src/main/res/drawable-mdpi/ic_rss_feed_white_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 286 B |
BIN
app/src/main/res/drawable-xhdpi/ic_rss_feed_white_24dp.png
Normal file
BIN
app/src/main/res/drawable-xhdpi/ic_rss_feed_white_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 528 B |
BIN
app/src/main/res/drawable-xxhdpi/ic_rss_feed_white_24dp.png
Normal file
BIN
app/src/main/res/drawable-xxhdpi/ic_rss_feed_white_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 761 B |
BIN
app/src/main/res/drawable-xxxhdpi/ic_rss_feed_white_24dp.png
Normal file
BIN
app/src/main/res/drawable-xxxhdpi/ic_rss_feed_white_24dp.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1,012 B |
13
app/src/main/res/drawable/white_circle.xml
Normal file
13
app/src/main/res/drawable/white_circle.xml
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval">
|
||||
|
||||
<solid
|
||||
android:color="@android:color/white"/>
|
||||
|
||||
<size
|
||||
android:height="@dimen/channel_avatar_halo_size"
|
||||
android:width="@dimen/channel_avatar_halo_size"/>
|
||||
|
||||
</shape>
|
|
@ -1,55 +0,0 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:baselineAligned="false"
|
||||
android:divider="?android:attr/dividerHorizontal"
|
||||
android:orientation="horizontal"
|
||||
android:showDividers="middle"
|
||||
tools:context=".VideoItemListActivity">
|
||||
|
||||
<!--
|
||||
This layout is a two-pane layout for the VideoItems
|
||||
master/detail flow.
|
||||
|
||||
-->
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight = "2">
|
||||
|
||||
<android.support.v7.widget.SearchView
|
||||
android:id="@+id/searchViewTablet"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:iconifiedByDefault="false"
|
||||
android:focusable="false"
|
||||
tools:ignore="InconsistentLayout" />
|
||||
|
||||
<include layout="@layout/main_bg" />
|
||||
|
||||
<fragment android:id="@+id/videoitem_list"
|
||||
android:name="org.schabi.newpipe.VideoItemListFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:layout="@android:layout/list_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:orientation="vertical"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="4">
|
||||
|
||||
<FrameLayout android:id="@+id/videoitem_detail_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:ignore="InconsistentLayout" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -3,15 +3,15 @@
|
|||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:context=".VideoItemDetailFragment"
|
||||
tools:context=".detail.VideoItemDetailFragment"
|
||||
android:textIsSelectable="true"
|
||||
style="?android:attr/textAppearanceLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/videoitem_detail">
|
||||
android:id="@+id/video_item_detail">
|
||||
|
||||
<com.nirhart.parallaxscroll.views.ParallaxScrollView
|
||||
android:id="@+id/detailMainContent"
|
||||
android:id="@+id/detail_main_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="visible"
|
||||
|
@ -23,12 +23,12 @@
|
|||
android:layout_height="wrap_content">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/detailVideoThumbnailWindowLayout"
|
||||
android:id="@+id/detail_stream_thumbnail_window_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<ImageView android:id="@+id/detailThumbnailView"
|
||||
<ImageView android:id="@+id/detail_thumbnail_view"
|
||||
android:contentDescription="@string/detail_thumbnail_view_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -40,13 +40,13 @@
|
|||
android:background="@android:color/black"
|
||||
android:src="@drawable/dummy_thumbnail_dark"/>
|
||||
|
||||
<ProgressBar android:id="@+id/detailProgressBar"
|
||||
<ProgressBar android:id="@+id/detail_progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:indeterminate="true"/>
|
||||
|
||||
<ImageView android:id="@+id/playArrowView"
|
||||
<ImageView android:id="@+id/play_arrow_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@android:color/transparent"
|
||||
|
@ -55,18 +55,17 @@
|
|||
android:visibility="invisible"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/detailVideoThumbnailWindowBackgroundButton"
|
||||
android:id="@+id/detail_stream_thumbnail_window_background_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackground"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout android:id="@+id/detailTextContentLayout"
|
||||
|
||||
<RelativeLayout android:id="@+id/detail_text_content_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/detailVideoThumbnailWindowLayout"
|
||||
android:layout_below="@id/detail_stream_thumbnail_window_layout"
|
||||
android:background="@color/light_background_color"
|
||||
android:visibility="gone">
|
||||
|
||||
|
@ -75,7 +74,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:id="@+id/detailTopView">
|
||||
|
||||
<TextView android:id="@+id/detailVideoTitleView"
|
||||
<TextView android:id="@+id/detail_video_title_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight=".7"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -89,7 +88,7 @@
|
|||
<ImageView
|
||||
android:layout_width="15dp"
|
||||
android:layout_height="30dp"
|
||||
android:id="@+id/toggleDescriptionView"
|
||||
android:id="@+id/toggle_description_view"
|
||||
android:src="@drawable/arrow_down"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
|
@ -99,7 +98,7 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView android:id="@+id/detailViewCountView"
|
||||
<TextView android:id="@+id/detail_view_count_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/video_item_detail_views_text_size"
|
||||
|
@ -115,14 +114,14 @@
|
|||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/detailViewCountView"
|
||||
android:layout_below="@id/detail_view_count_view"
|
||||
android:id="@+id/detailExtraView"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:visibility="gone">
|
||||
<TextView android:id="@+id/detailUploadDateView"
|
||||
<TextView android:id="@+id/detail_upload_date_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/video_item_detail_upload_date_text_size"
|
||||
|
@ -130,12 +129,12 @@
|
|||
android:text="Upload date"
|
||||
android:layout_marginTop="3dp" />
|
||||
|
||||
<TextView android:id="@+id/detailDescriptionView"
|
||||
<TextView android:id="@+id/detail_description_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/video_item_detail_description_text_size"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_below="@id/detailUploadDateView"
|
||||
android:layout_below="@id/detail_upload_date_view"
|
||||
android:text="Description............."
|
||||
android:layout_marginTop="3dp" />
|
||||
</RelativeLayout>
|
||||
|
@ -143,27 +142,27 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/linearLayout"
|
||||
android:id="@+id/stream_info_layout"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_below="@+id/detailExtraView"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginTop="5dp">
|
||||
<ImageView android:id="@+id/detailThumbsUpImgView"
|
||||
<ImageView android:id="@+id/detail_thumbs_up_img_view"
|
||||
android:contentDescription="@string/detail_likes_img_view_description"
|
||||
android:layout_width="@dimen/video_item_detail_like_image_width"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
android:src="@drawable/thumbs_up" />
|
||||
|
||||
<TextView android:id="@+id/detailThumbsUpCountView"
|
||||
<TextView android:id="@+id/detail_thumbs_up_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/video_item_detail_likes_text_size"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="200" />
|
||||
|
||||
<ImageView android:id="@+id/detailThumbsDownImgView"
|
||||
<ImageView android:id="@+id/detail_thumbs_down_img_view"
|
||||
android:contentDescription="@string/detail_dislikes_img_view_description"
|
||||
android:layout_width="@dimen/video_item_detail_like_image_width"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
|
@ -171,7 +170,7 @@
|
|||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"/>
|
||||
|
||||
<TextView android:id="@+id/detailThumbsDownCountView"
|
||||
<TextView android:id="@+id/detail_thumbs_down_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/video_item_detail_likes_text_size"
|
||||
|
@ -179,59 +178,72 @@
|
|||
android:text="100" />
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/linearLayout"
|
||||
android:id="@+id/detailUploaderWrapView"
|
||||
android:layout_marginTop="12dp">
|
||||
|
||||
<View
|
||||
android:background="#000"
|
||||
android:id="@+id/detail_uploader_frame"
|
||||
android:layout_below="@id/stream_info_layout">
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px" />
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView android:id="@+id/detailUploaderThumbnailView"
|
||||
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
|
||||
android:layout_width="@dimen/video_item_detail_uploader_image_size"
|
||||
android:layout_height="@dimen/video_item_detail_uploader_image_size"
|
||||
android:src="@drawable/buddy"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"/>
|
||||
|
||||
<TextView android:id="@+id/detailUploaderView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:textSize="@dimen/video_item_detail_uploader_text_size"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:text="Uploader"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toRightOf="@+id/detailUploaderThumbnailView"
|
||||
android:layout_toEndOf="@+id/detailUploaderThumbnailView"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginStart="28dp" />
|
||||
android:id="@+id/detail_uploader_layout"
|
||||
android:layout_marginTop="12dp">
|
||||
|
||||
<View
|
||||
android:background="#000"
|
||||
<View
|
||||
android:background="#000"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px" />
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/detail_uploader_thumbnail_view"
|
||||
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
|
||||
android:layout_width="@dimen/video_item_detail_uploader_image_size"
|
||||
android:layout_height="@dimen/video_item_detail_uploader_image_size"
|
||||
android:src="@drawable/buddy"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"/>
|
||||
|
||||
<TextView android:id="@+id/detail_uploader_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:textSize="@dimen/video_item_detail_uploader_text_size"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:text="Uploader"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toRightOf="@+id/detail_uploader_thumbnail_view"
|
||||
android:layout_toEndOf="@+id/detail_uploader_thumbnail_view"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginStart="28dp" />
|
||||
|
||||
<View
|
||||
android:background="#000"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:layout_below="@id/detail_uploader_thumbnail_view"/>
|
||||
</RelativeLayout>
|
||||
<Button
|
||||
android:layout_marginTop="13dp"
|
||||
android:id="@+id/channel_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:layout_below="@id/detailUploaderThumbnailView"/>
|
||||
</RelativeLayout>
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackground"/>
|
||||
</FrameLayout>
|
||||
|
||||
<RelativeLayout android:id="@+id/detailNextVideoRootLayout"
|
||||
<RelativeLayout android:id="@+id/detail_next_stream_root_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_horizontal|bottom"
|
||||
android:layout_below="@+id/detailUploaderWrapView"
|
||||
android:layout_below="@+id/detail_uploader_frame"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="10dp">
|
||||
|
||||
<TextView android:id="@+id/detailNextVideoTitle"
|
||||
<TextView
|
||||
android:id="@+id/detail_next_stream_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
|
@ -240,38 +252,25 @@
|
|||
android:text="@string/next_video_title"
|
||||
android:textAllCaps="true" />
|
||||
|
||||
<RelativeLayout android:id="@+id/detailNextVidButtonAndContentLayout"
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/detailNextVideoTitle">
|
||||
<FrameLayout
|
||||
android:id="@+id/detailNextVideoFrame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
<Button
|
||||
android:id="@+id/detailNextVideoButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignTop="@id/detailNextVideoFrame"
|
||||
android:layout_alignBottom="@id/detailNextVideoFrame"
|
||||
android:background="?attr/selectableItemBackground"/>
|
||||
</RelativeLayout>
|
||||
<TextView android:id="@+id/detailSimilarTitle"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/detail_next_stream_content"/>
|
||||
<TextView android:id="@+id/detail_similar_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:textSize="@dimen/video_item_detail_next_text_size"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/similar_videos_btn_text"
|
||||
android:layout_below="@id/detailNextVidButtonAndContentLayout"
|
||||
android:layout_below="@id/detail_next_stream_content"
|
||||
android:textAllCaps="true" />
|
||||
<LinearLayout
|
||||
android:id="@+id/similarVideosView"
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/similar_streams_view"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/detailSimilarTitle">
|
||||
</LinearLayout>
|
||||
android:layout_below="@id/detail_similar_title"/>
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
|
96
app/src/main/res/layout/activity_channel.xml
Normal file
96
app/src/main/res/layout/activity_channel.xml
Normal file
|
@ -0,0 +1,96 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:id="@+id/rootView"
|
||||
tools:context="org.schabi.newpipe.ChannelActivity">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:id="@+id/channel_app_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/app_bar_height"
|
||||
android:fitsSystemWindows="true"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.design.widget.CollapsingToolbarLayout
|
||||
android:id="@+id/channel_toolbar_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
app:contentScrim="@color/light_youtube_primary_color"
|
||||
app:statusBarScrim="@color/light_youtube_dark_color"
|
||||
app:layout_scrollFlags="scroll|exitUntilCollapsed">
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/channel_banner_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:background="@color/light_youtube_dark_color"
|
||||
app:layout_collapseMode="parallax" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/channel_avatar_halo"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="28dp"
|
||||
android:layout_marginStart="28dp"
|
||||
android:layout_marginTop="38dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:src="@drawable/white_circle"/>
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/channel_avatar_view"
|
||||
android:visibility="gone"
|
||||
android:layout_width="@dimen/channel_avatar_size"
|
||||
android:layout_height="@dimen/channel_avatar_size"
|
||||
android:src="@drawable/buddy"
|
||||
android:layout_marginLeft="30dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"/>
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/cannel_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_collapseMode="pin"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay" />
|
||||
|
||||
</android.support.design.widget.CollapsingToolbarLayout>
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/channel_rss_fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/fab_margin"
|
||||
android:src="@drawable/ic_rss_feed_white_24dp"
|
||||
app:layout_anchor="@id/channel_app_bar"
|
||||
app:layout_anchorGravity="bottom|end" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/channel_loading">
|
||||
<ProgressBar android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:indeterminate="true"/>
|
||||
</RelativeLayout>
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/channel_streams_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
android:scrollbars="vertical"/>
|
||||
</android.support.design.widget.CoordinatorLayout>
|
17
app/src/main/res/layout/activity_main.xml
Normal file
17
app/src/main/res/layout/activity_main.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="org.schabi.newpipe.MainActivity"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/main_bg" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/search_fragment"
|
||||
android:name="org.schabi.newpipe.search_fragment.SearchInfoItemFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</RelativeLayout>
|
|
@ -1,4 +1,4 @@
|
|||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools" android:id="@+id/videoitem_detail_container"
|
||||
android:layout_width="match_parent" android:layout_height="match_parent"
|
||||
tools:context=".VideoItemDetailActivity" tools:ignore="MergeRootFrame" />
|
||||
tools:context=".detail.VideoItemDetailActivity" tools:ignore="MergeRootFrame" />
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/main_bg" />
|
||||
|
||||
<fragment
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/videoitem_list"
|
||||
android:name="org.schabi.newpipe.VideoItemListFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".VideoItemListActivity"
|
||||
tools:layout="@android:layout/list_content" />
|
||||
|
||||
</LinearLayout>
|
17
app/src/main/res/layout/content_channel.xml
Normal file
17
app/src/main/res/layout/content_channel.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.v4.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:context="org.schabi.newpipe.ChannelActivity"
|
||||
tools:showIn="@layout/activity_channel">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="@dimen/text_margin"
|
||||
android:text="@string/large_text" />
|
||||
|
||||
</android.support.v4.widget.NestedScrollView>
|
24
app/src/main/res/layout/fragment_searchinfoitem.xml
Normal file
24
app/src/main/res/layout/fragment_searchinfoitem.xml
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:name="org.schabi.newpipe.SearchInfoItemFragment"
|
||||
tools:context=".search_fragment.SearchInfoItemFragment">
|
||||
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layoutManager="LinearLayoutManager"
|
||||
tools:listitem="@layout/video_item"
|
||||
android:scrollbars="vertical"/>
|
||||
|
||||
<ProgressBar android:id="@+id/progressBar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"/>
|
||||
</RelativeLayout>
|
|
@ -3,15 +3,15 @@
|
|||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
tools:context=".VideoItemDetailFragment"
|
||||
tools:context=".detail.VideoItemDetailFragment"
|
||||
android:textIsSelectable="true"
|
||||
style="?android:attr/textAppearanceLarge"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/videoitem_detail">
|
||||
android:id="@+id/video_item_detail">
|
||||
|
||||
<com.nirhart.parallaxscroll.views.ParallaxScrollView
|
||||
android:id="@+id/detailMainContent"
|
||||
android:id="@+id/detail_main_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:visibility="visible"
|
||||
|
@ -23,12 +23,12 @@
|
|||
android:layout_height="wrap_content">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/detailVideoThumbnailWindowLayout"
|
||||
android:id="@+id/detail_stream_thumbnail_window_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground">
|
||||
|
||||
<ImageView android:id="@+id/detailThumbnailView"
|
||||
<ImageView android:id="@+id/detail_thumbnail_view"
|
||||
android:contentDescription="@string/detail_thumbnail_view_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -40,14 +40,14 @@
|
|||
android:background="@android:color/black"
|
||||
android:src="@drawable/dummy_thumbnail_dark"/>
|
||||
|
||||
<ProgressBar android:id="@+id/detailProgressBar"
|
||||
<ProgressBar android:id="@+id/detail_progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:indeterminate="true"/>
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/playVideoButton"
|
||||
android:id="@+id/play_video_button"
|
||||
android:visibility="invisible"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -57,18 +57,17 @@
|
|||
android:layout_margin="@dimen/video_item_detail_play_fab_margin"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/detailVideoThumbnailWindowBackgroundButton"
|
||||
android:id="@+id/detail_stream_thumbnail_window_background_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackground"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout android:id="@+id/detailTextContentLayout"
|
||||
|
||||
<RelativeLayout android:id="@+id/detail_text_content_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/detailVideoThumbnailWindowLayout"
|
||||
android:layout_below="@id/detail_stream_thumbnail_window_layout"
|
||||
android:background="@color/light_background_color"
|
||||
android:visibility="gone">
|
||||
|
||||
|
@ -77,7 +76,7 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:id="@+id/detailTopView">
|
||||
|
||||
<TextView android:id="@+id/detailVideoTitleView"
|
||||
<TextView android:id="@+id/detail_video_title_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight=".7"
|
||||
android:layout_height="wrap_content"
|
||||
|
@ -91,7 +90,7 @@
|
|||
<ImageView
|
||||
android:layout_width="15dp"
|
||||
android:layout_height="30dp"
|
||||
android:id="@+id/toggleDescriptionView"
|
||||
android:id="@+id/toggle_description_view"
|
||||
android:src="@drawable/arrow_down"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
|
@ -101,7 +100,7 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<TextView android:id="@+id/detailViewCountView"
|
||||
<TextView android:id="@+id/detail_view_count_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/video_item_detail_views_text_size"
|
||||
|
@ -117,14 +116,14 @@
|
|||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/detailViewCountView"
|
||||
android:layout_below="@id/detail_view_count_view"
|
||||
android:id="@+id/detailExtraView"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginRight="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:visibility="gone">
|
||||
<TextView android:id="@+id/detailUploadDateView"
|
||||
<TextView android:id="@+id/detail_upload_date_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/video_item_detail_upload_date_text_size"
|
||||
|
@ -132,12 +131,12 @@
|
|||
android:text="Upload date"
|
||||
android:layout_marginTop="3dp" />
|
||||
|
||||
<TextView android:id="@+id/detailDescriptionView"
|
||||
<TextView android:id="@+id/detail_description_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/video_item_detail_description_text_size"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:layout_below="@id/detailUploadDateView"
|
||||
android:layout_below="@id/detail_upload_date_view"
|
||||
android:text="Description............."
|
||||
android:layout_marginTop="3dp" />
|
||||
</RelativeLayout>
|
||||
|
@ -145,27 +144,27 @@
|
|||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/linearLayout"
|
||||
android:id="@+id/stream_info_layout"
|
||||
android:layout_marginLeft="12dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_below="@+id/detailExtraView"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginTop="5dp">
|
||||
<ImageView android:id="@+id/detailThumbsUpImgView"
|
||||
<ImageView android:id="@+id/detail_thumbs_up_img_view"
|
||||
android:contentDescription="@string/detail_likes_img_view_description"
|
||||
android:layout_width="@dimen/video_item_detail_like_image_width"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
android:src="@drawable/thumbs_up" />
|
||||
|
||||
<TextView android:id="@+id/detailThumbsUpCountView"
|
||||
<TextView android:id="@+id/detail_thumbs_up_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/video_item_detail_likes_text_size"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="200" />
|
||||
|
||||
<ImageView android:id="@+id/detailThumbsDownImgView"
|
||||
<ImageView android:id="@+id/detail_thumbs_down_img_view"
|
||||
android:contentDescription="@string/detail_dislikes_img_view_description"
|
||||
android:layout_width="@dimen/video_item_detail_like_image_width"
|
||||
android:layout_height="@dimen/video_item_detail_like_image_height"
|
||||
|
@ -173,7 +172,7 @@
|
|||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"/>
|
||||
|
||||
<TextView android:id="@+id/detailThumbsDownCountView"
|
||||
<TextView android:id="@+id/detail_thumbs_down_count_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="@dimen/video_item_detail_likes_text_size"
|
||||
|
@ -181,59 +180,72 @@
|
|||
android:text="100" />
|
||||
</LinearLayout>
|
||||
|
||||
<RelativeLayout
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/linearLayout"
|
||||
android:id="@+id/detailUploaderWrapView"
|
||||
android:layout_marginTop="12dp">
|
||||
|
||||
<View
|
||||
android:background="#000"
|
||||
android:id="@+id/detail_uploader_frame"
|
||||
android:layout_below="@id/stream_info_layout">
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px" />
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView android:id="@+id/detailUploaderThumbnailView"
|
||||
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
|
||||
android:layout_width="@dimen/video_item_detail_uploader_image_size"
|
||||
android:layout_height="@dimen/video_item_detail_uploader_image_size"
|
||||
android:src="@drawable/buddy"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"/>
|
||||
|
||||
<TextView android:id="@+id/detailUploaderView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:textSize="@dimen/video_item_detail_uploader_text_size"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:text="Uploader"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toRightOf="@+id/detailUploaderThumbnailView"
|
||||
android:layout_toEndOf="@+id/detailUploaderThumbnailView"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginStart="28dp" />
|
||||
android:id="@+id/detail_uploader_layout"
|
||||
android:layout_marginTop="12dp">
|
||||
|
||||
<View
|
||||
android:background="#000"
|
||||
<View
|
||||
android:background="#000"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px" />
|
||||
|
||||
<de.hdodenhof.circleimageview.CircleImageView
|
||||
android:id="@+id/detail_uploader_thumbnail_view"
|
||||
android:contentDescription="@string/detail_uploader_thumbnail_view_description"
|
||||
android:layout_width="@dimen/video_item_detail_uploader_image_size"
|
||||
android:layout_height="@dimen/video_item_detail_uploader_image_size"
|
||||
android:src="@drawable/buddy"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginBottom="5dp"/>
|
||||
|
||||
<TextView android:id="@+id/detail_uploader_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:textSize="@dimen/video_item_detail_uploader_text_size"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:text="Uploader"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toRightOf="@+id/detail_uploader_thumbnail_view"
|
||||
android:layout_toEndOf="@+id/detail_uploader_thumbnail_view"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginStart="28dp" />
|
||||
|
||||
<View
|
||||
android:background="#000"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:layout_below="@id/detail_uploader_thumbnail_view"/>
|
||||
</RelativeLayout>
|
||||
<Button
|
||||
android:layout_marginTop="13dp"
|
||||
android:id="@+id/channel_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="1px"
|
||||
android:layout_below="@id/detailUploaderThumbnailView"/>
|
||||
</RelativeLayout>
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackground"/>
|
||||
</FrameLayout>
|
||||
|
||||
<RelativeLayout android:id="@+id/detailNextVideoRootLayout"
|
||||
<RelativeLayout android:id="@+id/detail_next_stream_root_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_horizontal|bottom"
|
||||
android:layout_below="@+id/detailUploaderWrapView"
|
||||
android:layout_below="@+id/detail_uploader_frame"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginTop="10dp">
|
||||
|
||||
<TextView android:id="@+id/detailNextVideoTitle"
|
||||
<TextView
|
||||
android:id="@+id/detail_next_stream_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
|
@ -242,38 +254,25 @@
|
|||
android:text="@string/next_video_title"
|
||||
android:textAllCaps="true" />
|
||||
|
||||
<RelativeLayout android:id="@+id/detailNextVidButtonAndContentLayout"
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/detailNextVideoTitle">
|
||||
<FrameLayout
|
||||
android:id="@+id/detailNextVideoFrame"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
<Button
|
||||
android:id="@+id/detailNextVideoButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignTop="@id/detailNextVideoFrame"
|
||||
android:layout_alignBottom="@id/detailNextVideoFrame"
|
||||
android:background="?attr/selectableItemBackground"/>
|
||||
</RelativeLayout>
|
||||
<TextView android:id="@+id/detailSimilarTitle"
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/detail_next_stream_content"/>
|
||||
<TextView android:id="@+id/detail_similar_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:textSize="@dimen/video_item_detail_next_text_size"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:text="@string/similar_videos_btn_text"
|
||||
android:layout_below="@id/detailNextVidButtonAndContentLayout"
|
||||
android:layout_below="@id/detail_next_stream_content"
|
||||
android:textAllCaps="true" />
|
||||
<LinearLayout
|
||||
<android.support.v7.widget.RecyclerView
|
||||
android:id="@+id/similar_streams_view"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/similarVideosView"
|
||||
android:layout_below="@id/detailSimilarTitle">
|
||||
</LinearLayout>
|
||||
android:layout_below="@id/detail_similar_title"/>
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
android:orientation="vertical" android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/mainBG"
|
||||
tools:context=".VideoItemDetailActivity"
|
||||
tools:showIn="@layout/activity_videoitem_list">
|
||||
tools:context=".detail.VideoItemDetailActivity">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/paginate_progress_bar"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:paddingBottom="10dp"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -1,8 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/item_main_layout"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" >
|
||||
android:layout_height="wrap_content">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
|
@ -92,4 +98,11 @@
|
|||
</LinearLayout>
|
||||
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/item_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="?attr/selectableItemBackground"/>
|
||||
</FrameLayout>
|
11
app/src/main/res/menu/main_menu.xml
Normal file
11
app/src/main/res/menu/main_menu.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item android:id="@+id/action_settings"
|
||||
app:showAsAction="never"
|
||||
android:title="@string/settings"/>
|
||||
|
||||
<item android:id="@+id/action_show_downloads"
|
||||
app:showAsAction="never"
|
||||
android:title="@string/downloads" />
|
||||
</menu>
|
10
app/src/main/res/menu/menu_channel.xml
Normal file
10
app/src/main/res/menu/menu_channel.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="org.schabi.newpipe.ChannelActivity">
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/action_settings"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
9
app/src/main/res/menu/search_menu.xml
Normal file
9
app/src/main/res/menu/search_menu.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item android:id="@+id/action_search"
|
||||
android:icon="@android:drawable/ic_menu_search"
|
||||
app:showAsAction="ifRoom"
|
||||
android:title="@string/search"
|
||||
app:actionViewClass="android.support.v7.widget.SearchView" />
|
||||
</menu>
|
|
@ -10,7 +10,7 @@
|
|||
<item name="android:windowBackground">@color/light_background_color</item>
|
||||
</style>
|
||||
|
||||
<style name="NewPipeActionbarTheme" parent="Widget.AppCompat.Light.ActionBar.Solid" >
|
||||
<style name="NewPipeActionbarTheme" parent="Widget.AppCompat.Light.ActionBar.Solid">
|
||||
<item name="android:displayOptions">showHome</item>
|
||||
<item name="displayOptions">showHome</item>
|
||||
<item name="android:background">@color/light_youtube_primary_color</item>
|
||||
|
@ -28,11 +28,18 @@
|
|||
<item name="android:windowBackground">@android:color/black</item>
|
||||
</style>
|
||||
|
||||
<style name="VideoPlayerActionBarTheme" parent="Widget.AppCompat.Light.ActionBar.Solid.Inverse" >
|
||||
<style name="VideoPlayerActionBarTheme" parent="Widget.AppCompat.Light.ActionBar.Solid.Inverse">
|
||||
<item name="android:displayOptions">showHome</item>
|
||||
<item name="displayOptions">showHome</item>
|
||||
<item name="android:background">@color/video_overlay_color</item>
|
||||
<item name="background">@color/video_overlay_color</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
|
@ -31,11 +31,16 @@
|
|||
<dimen name="video_item_detail_uploader_image_size">50dp</dimen>
|
||||
<dimen name="video_item_detail_like_image_height">18sp</dimen>
|
||||
<dimen name="video_item_detail_like_image_width">18sp</dimen>
|
||||
<dimen name="channel_avatar_size">70dp</dimen>
|
||||
<dimen name="channel_avatar_halo_size">74dp</dimen>
|
||||
<!-- Paddings & Margins -->
|
||||
<dimen name="video_item_detail_like_margin">6sp</dimen>
|
||||
<dimen name="video_item_detail_play_fab_margin">20dp</dimen>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
<dimen name="app_bar_height">180dp</dimen>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
<dimen name="text_margin">16dp</dimen>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -96,6 +96,7 @@
|
|||
<string name="could_not_setup_download_menu">Could not setup download menu.</string>
|
||||
<string name="live_streams_not_supported">This is a LIVE STREAM. These are not yet supported.</string>
|
||||
<string name="could_not_get_stream">Could not get any stream.</string>
|
||||
<string name="could_not_load_image">Could not load Image</string>
|
||||
<!-- error activity -->
|
||||
<string name="sorry_string">Sorry that should not happen.</string>
|
||||
<string name="guru_meditation" translatable="false">Guru Meditation.</string>
|
||||
|
@ -125,7 +126,7 @@
|
|||
<string name="err_dir_create">Cannot create download directory \'%1$s\'</string>
|
||||
<string name="info_dir_created">Created download directory \'%1$s\'</string>
|
||||
|
||||
<string name="enable_background_audio">Play in background</string>
|
||||
<string name="enable_background_audio">Play in background</string>
|
||||
<string name="video">Video</string>
|
||||
<string name="audio">Audio</string>
|
||||
<string name="text">Text</string>
|
||||
|
@ -176,6 +177,97 @@
|
|||
<!-- Checksum types -->
|
||||
<string name="md5" translatable="false">MD5</string>
|
||||
<string name="sha1" translatable="false">SHA1</string>
|
||||
<string name="title_activity_channel">ChannelActivity</string>
|
||||
<string name="large_text">
|
||||
"Material is the metaphor.\n\n"
|
||||
|
||||
"A material metaphor is the unifying theory of a rationalized space and a system of motion."
|
||||
"The material is grounded in tactile reality, inspired by the study of paper and ink, yet "
|
||||
"technologically advanced and open to imagination and magic.\n"
|
||||
"Surfaces and edges of the material provide visual cues that are grounded in reality. The "
|
||||
"use of familiar tactile attributes helps users quickly understand affordances. Yet the "
|
||||
"flexibility of the material creates new affordances that supercede those in the physical "
|
||||
"world, without breaking the rules of physics.\n"
|
||||
"The fundamentals of light, surface, and movement are key to conveying how objects move, "
|
||||
"interact, and exist in space and in relation to each other. Realistic lighting shows "
|
||||
"seams, divides space, and indicates moving parts.\n\n"
|
||||
|
||||
"Bold, graphic, intentional.\n\n"
|
||||
|
||||
"The foundational elements of print based design typography, grids, space, scale, color, "
|
||||
"and use of imagery guide visual treatments. These elements do far more than please the "
|
||||
"eye. They create hierarchy, meaning, and focus. Deliberate color choices, edge to edge "
|
||||
"imagery, large scale typography, and intentional white space create a bold and graphic "
|
||||
"interface that immerse the user in the experience.\n"
|
||||
"An emphasis on user actions makes core functionality immediately apparent and provides "
|
||||
"waypoints for the user.\n\n"
|
||||
|
||||
"Motion provides meaning.\n\n"
|
||||
|
||||
"Motion respects and reinforces the user as the prime mover. Primary user actions are "
|
||||
"inflection points that initiate motion, transforming the whole design.\n"
|
||||
"All action takes place in a single environment. Objects are presented to the user without "
|
||||
"breaking the continuity of experience even as they transform and reorganize.\n"
|
||||
"Motion is meaningful and appropriate, serving to focus attention and maintain continuity. "
|
||||
"Feedback is subtle yet clear. Transitions are efficient yet coherent.\n\n"
|
||||
|
||||
"3D world.\n\n"
|
||||
|
||||
"The material environment is a 3D space, which means all objects have x, y, and z "
|
||||
"dimensions. The z-axis is perpendicularly aligned to the plane of the display, with the "
|
||||
"positive z-axis extending towards the viewer. Every sheet of material occupies a single "
|
||||
"position along the z-axis and has a standard 1dp thickness.\n"
|
||||
"On the web, the z-axis is used for layering and not for perspective. The 3D world is "
|
||||
"emulated by manipulating the y-axis.\n\n"
|
||||
|
||||
"Light and shadow.\n\n"
|
||||
|
||||
"Within the material environment, virtual lights illuminate the scene. Key lights create "
|
||||
"directional shadows, while ambient light creates soft shadows from all angles.\n"
|
||||
"Shadows in the material environment are cast by these two light sources. In Android "
|
||||
"development, shadows occur when light sources are blocked by sheets of material at "
|
||||
"various positions along the z-axis. On the web, shadows are depicted by manipulating the "
|
||||
"y-axis only. The following example shows the card with a height of 6dp.\n\n"
|
||||
|
||||
"Resting elevation.\n\n"
|
||||
|
||||
"All material objects, regardless of size, have a resting elevation, or default elevation "
|
||||
"that does not change. If an object changes elevation, it should return to its resting "
|
||||
"elevation as soon as possible.\n\n"
|
||||
|
||||
"Component elevations.\n\n"
|
||||
|
||||
"The resting elevation for a component type is consistent across apps (e.g., FAB elevation "
|
||||
"does not vary from 6dp in one app to 16dp in another app).\n"
|
||||
"Components may have different resting elevations across platforms, depending on the depth "
|
||||
"of the environment (e.g., TV has a greater depth than mobile or desktop).\n\n"
|
||||
|
||||
"Responsive elevation and dynamic elevation offsets.\n\n"
|
||||
|
||||
"Some component types have responsive elevation, meaning they change elevation in response "
|
||||
"to user input (e.g., normal, focused, and pressed) or system events. These elevation "
|
||||
"changes are consistently implemented using dynamic elevation offsets.\n"
|
||||
"Dynamic elevation offsets are the goal elevation that a component moves towards, relative "
|
||||
"to the component’s resting state. They ensure that elevation changes are consistent "
|
||||
"across actions and component types. For example, all components that lift on press have "
|
||||
"the same elevation change relative to their resting elevation.\n"
|
||||
"Once the input event is completed or cancelled, the component will return to its resting "
|
||||
"elevation.\n\n"
|
||||
|
||||
"Avoiding elevation interference.\n\n"
|
||||
|
||||
"Components with responsive elevations may encounter other components as they move between "
|
||||
"their resting elevations and dynamic elevation offsets. Because material cannot pass "
|
||||
"through other material, components avoid interfering with one another any number of ways, "
|
||||
"whether on a per component basis or using the entire app layout.\n"
|
||||
"On a component level, components can move or be removed before they cause interference. "
|
||||
"For example, a floating action button (FAB) can disappear or move off screen before a "
|
||||
"user picks up a card, or it can move if a snackbar appears.\n"
|
||||
"On the layout level, design your app layout to minimize opportunities for interference. "
|
||||
"For example, position the FAB to one side of stream of a cards so the FAB won’t interfere "
|
||||
"when a user tries to pick up one of cards.\n\n"
|
||||
</string>
|
||||
<string name="action_settings">Settings</string>
|
||||
|
||||
<!-- End of GigaGet's Strings -->
|
||||
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
<resources>
|
||||
|
||||
<style name="RootTheme" parent="android:Theme.Holo">
|
||||
</style>
|
||||
<style name="RootTheme" parent="android:Theme.Holo"></style>
|
||||
|
||||
<style name="PlayerTheme" parent="@style/RootTheme">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowBackground">@android:color/black</item>
|
||||
</style>
|
||||
<style name="PlayerTheme" parent="@style/RootTheme">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowBackground">@android:color/black</item>
|
||||
</style>
|
||||
|
||||
<style name="ExoPlayerButton">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:minWidth">40dp</item>
|
||||
</style>
|
||||
<style name="ExoPlayerButton">
|
||||
<item name="android:layout_width">wrap_content</item>
|
||||
<item name="android:layout_height">wrap_content</item>
|
||||
<item name="android:minWidth">40dp</item>
|
||||
</style>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light">
|
||||
|
@ -24,7 +23,7 @@
|
|||
<item name="android:windowBackground">@color/light_background_color</item>
|
||||
</style>
|
||||
|
||||
<style name="NewPipeActionbarTheme" parent="Widget.AppCompat.Light.ActionBar.Solid" >
|
||||
<style name="NewPipeActionbarTheme" parent="Widget.AppCompat.Light.ActionBar.Solid">
|
||||
<item name="android:displayOptions">showHome</item>
|
||||
<item name="displayOptions">showHome</item>
|
||||
<item name="android:background">@color/light_youtube_primary_color</item>
|
||||
|
@ -41,7 +40,7 @@
|
|||
<item name="android:windowBackground">@android:color/black</item>
|
||||
</style>
|
||||
|
||||
<style name="VideoPlayerActionBarTheme" parent="Widget.AppCompat.Light.ActionBar.Solid.Inverse" >
|
||||
<style name="VideoPlayerActionBarTheme" parent="Widget.AppCompat.Light.ActionBar.Solid.Inverse">
|
||||
<item name="android:displayOptions">showHome</item>
|
||||
<item name="displayOptions">showHome</item>
|
||||
<item name="android:background">@color/video_overlay_color</item>
|
||||
|
@ -72,4 +71,13 @@
|
|||
<item name="colorAccent">@color/light_youtube_accent_color</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -5,7 +5,7 @@ buildscript {
|
|||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:2.1.2'
|
||||
classpath 'com.android.tools.build:gradle:2.1.3'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
|
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
|||
#Mon Aug 10 20:55:41 CEST 2015
|
||||
#Sat Aug 20 00:40:54 CEST 2016
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.10-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
|
||||
|
|
Loading…
Reference in a new issue